From baf2cb531dcb95519d0150381b62ce5c115d6af8 Mon Sep 17 00:00:00 2001 From: Dimitrij Date: Wed, 10 Apr 2024 16:59:08 +0100 Subject: [PATCH] patch for version .80 to be compatible with mRemoteNG (to have as less changes as possible - content completely removed - all changes will be done by script now) --- .gitignore | 567 -- Buildscr | 331 - Buildscr.cv | 37 - CHECKLST.txt | 263 - CMakeLists.txt | 158 - LATEST.VER | 1 - LICENCE | 28 - Makefile.am | 459 - PuTTYNG.ps1 | 116 + README | 40 - README.MD | 33 +- WINDOWS/CMakeLists.txt | 213 - WINDOWS/DEVCPP/PAGEANT/PAGEANT.DEV | 831 -- WINDOWS/DEVCPP/PLINK/PLINK.DEV | 1611 ---- WINDOWS/DEVCPP/PSCP/PSCP.DEV | 1571 ---- WINDOWS/DEVCPP/PSFTP/PSFTP.DEV | 1571 ---- WINDOWS/DEVCPP/PUTTY/PUTTY.DEV | 1691 ---- WINDOWS/DEVCPP/PUTTYGEN/PUTTYGEN.DEV | 901 -- WINDOWS/DEVCPP/PUTTYTEL/PUTTYTEL.DEV | 931 -- WINDOWS/MAKEFILE.LCC | 1658 ---- WINDOWS/MAKEFILE.NG | 1834 ---- WINDOWS/MAKEFILE.VC | 1831 ---- WINDOWS/README-msi.txt | 39 - WINDOWS/VS2010/putty.sln | 67 - WINDOWS/VS2012/putty.sln | 67 - WINDOWS/VS2015/PuTTYNG.sln | 21 - WINDOWS/agent-client.c | 293 - WINDOWS/cliloop.c | 134 - WINDOWS/config.c | 384 - WINDOWS/conpty.c | 433 - WINDOWS/console.c | 728 -- WINDOWS/controls.c | 2653 ------ WINDOWS/cryptoapi.h | 27 - WINDOWS/dialog.c | 1329 --- WINDOWS/gss.c | 694 -- WINDOWS/handle-io.c | 687 -- WINDOWS/handle-socket.c | 512 -- WINDOWS/handle-wait.c | 143 - WINDOWS/help.c | 250 - WINDOWS/help.h | 220 - WINDOWS/help.rc2 | 8 - WINDOWS/installer.wxs | 612 -- WINDOWS/jump-list.c | 746 -- WINDOWS/local-proxy.c | 118 - WINDOWS/make.cmd | 5 - WINDOWS/make17.cmd | 7 - WINDOWS/make19.cmd | 7 - WINDOWS/make_install_images.sh | 22 - WINDOWS/msifixup.py | 76 - WINDOWS/named-pipe-client.c | 95 - WINDOWS/named-pipe-server.c | 235 - WINDOWS/network.c | 1878 ---- WINDOWS/no-jump-list.c | 10 - WINDOWS/nohelp.c | 15 - WINDOWS/noise.c | 142 - WINDOWS/pageant-rc.h | 33 - WINDOWS/pageant.c | 1968 ---- WINDOWS/pageant.ico | Bin 4078 -> 0 bytes WINDOWS/pageant.mft | 31 - WINDOWS/pageant.rc | 95 - WINDOWS/pageants.ico | Bin 510 -> 0 bytes WINDOWS/platform.h | 832 -- WINDOWS/plink.c | 555 -- WINDOWS/plink.rc | 8 - WINDOWS/printing.c | 228 - WINDOWS/pscp.ico | Bin 4078 -> 0 bytes WINDOWS/pscp.rc | 8 - WINDOWS/psftp.rc | 8 - WINDOWS/psocks.c | 24 - WINDOWS/pterm.c | 65 - WINDOWS/pterm.ico | Bin 4078 -> 0 bytes WINDOWS/pterm.rc | 15 - WINDOWS/ptermcfg.ico | Bin 4078 -> 0 bytes WINDOWS/putty-common.rc2 | 98 - WINDOWS/putty-rc.h | 51 - WINDOWS/putty.c | 203 - WINDOWS/putty.ico | Bin 4078 -> 0 bytes WINDOWS/putty.mft | 35 - WINDOWS/putty.rc | 15 - WINDOWS/puttycfg.ico | Bin 4078 -> 0 bytes WINDOWS/puttygen-rc.h | 17 - WINDOWS/puttygen.c | 2612 ------ WINDOWS/puttygen.ico | Bin 4078 -> 0 bytes WINDOWS/puttygen.mft | 31 - WINDOWS/puttygen.rc | 99 - WINDOWS/puttyins.ico | Bin 11478 -> 0 bytes WINDOWS/puttytel.mft | 35 - WINDOWS/puttytel.rc | 15 - WINDOWS/rcstuff.h | 45 - WINDOWS/security-api.h | 49 - WINDOWS/select-cli.c | 78 - WINDOWS/select-gui.c | 65 - WINDOWS/serial.c | 468 - WINDOWS/sftp.c | 656 -- WINDOWS/sharing.c | 113 - WINDOWS/sign_binaries.ps1 | 76 - WINDOWS/sizetip.c | 192 - WINDOWS/storage.c | 868 -- WINDOWS/test/test_screenshot.c | 45 - WINDOWS/test/test_split_into_argv.c | 444 - WINDOWS/test_screenshot.c | 45 - WINDOWS/unicode.c | 1398 --- WINDOWS/utils/agent_mutex_name.c | 14 - WINDOWS/utils/agent_named_pipe_name.c | 17 - WINDOWS/utils/arm_arch_queries.c | 45 - WINDOWS/utils/aux_match_opt.c | 117 - WINDOWS/utils/centre_window.c | 20 - WINDOWS/utils/cryptoapi.c | 89 - WINDOWS/utils/defaults.c | 40 - WINDOWS/utils/dll_hijacking_protection.c | 43 - WINDOWS/utils/dputs.c | 39 - WINDOWS/utils/escape_registry_key.c | 48 - WINDOWS/utils/filename.c | 92 - WINDOWS/utils/fontspec.c | 48 - WINDOWS/utils/get_system_dir.c | 21 - WINDOWS/utils/get_username.c | 77 - WINDOWS/utils/getdlgitemtext_alloc.c | 33 - WINDOWS/utils/gui-timing.c | 56 - WINDOWS/utils/interprocess_mutex.c | 48 - WINDOWS/utils/is_console_handle.c | 13 - WINDOWS/utils/load_system32_dll.c | 18 - WINDOWS/utils/ltime.c | 27 - WINDOWS/utils/make_spr_sw_abort_winerror.c | 22 - WINDOWS/utils/makedlgitemborderless.c | 19 - WINDOWS/utils/message_box.c | 71 - WINDOWS/utils/minefield.c | 228 - .../utils/open_for_write_would_lose_data.c | 76 - WINDOWS/utils/pgp_fingerprints_msgbox.c | 25 - WINDOWS/utils/platform_get_x_display.c | 13 - WINDOWS/utils/registry.c | 184 - WINDOWS/utils/request_file.c | 114 - WINDOWS/utils/screenshot.c | 126 - WINDOWS/utils/security.c | 340 - WINDOWS/utils/shinydialogbox.c | 111 - WINDOWS/utils/split_into_argv.c | 321 - WINDOWS/utils/split_into_argv_w.c | 6 - WINDOWS/utils/strtoumax.c | 12 - WINDOWS/utils/version.c | 45 - WINDOWS/utils/win_strerror.c | 72 - WINDOWS/version.rc2 | 69 - WINDOWS/website.url | Bin 104 -> 0 bytes WINDOWS/win-gui-seat.h | 165 - WINDOWS/window.c | 5914 ------------ WINDOWS/x11.c | 19 - aqsync.c | 21 - be_all.c | 31 - be_all_s.c | 36 - be_list.c | 118 - callback.c | 133 - ...odeSigning_Cert_mRemoteNG_DigiCert.p12.enc | Bin 3120 -> 0 bytes cgtest.c | 788 -- charset/CMakeLists.txt | 30 - charset/README | 15 - charset/charset.h | 157 - charset/enum.c | 19 - charset/fromucs.c | 92 - charset/internal.h | 89 - charset/localenc.c | 126 - charset/macenc.c | 168 - charset/mimeenc.c | 219 - charset/sbcs.c | 53 - charset/sbcs.dat | 1139 --- charset/sbcsgen.pl | 118 - charset/slookup.c | 29 - charset/toucs.c | 90 - charset/utf8.c | 877 -- charset/xenc.c | 94 - clicons.c | 14 - cmake/cmake.h.in | 61 - cmake/gitcommit.cmake | 63 - cmake/gtk.cmake | 89 - cmake/licence.cmake | 39 - cmake/platforms/unix.cmake | 234 - cmake/platforms/windows.cmake | 202 - cmake/setup.cmake | 122 - cmake/toolchain-mingw.cmake | 12 - cmake/toolchain-winegcc.cmake | 33 - cmake/winegcc | 39 - cmdgen.c | 1655 ---- cmdline.c | 974 -- config.c | 3331 ------- console.c | 88 - console.h | 20 - contrib/authplugin-example.py | 290 - contrib/cygtermd/README | 11 - contrib/cygtermd/main.c | 174 - contrib/cygtermd/malloc.c | 43 - contrib/cygtermd/malloc.h | 56 - contrib/cygtermd/pty.c | 202 - contrib/cygtermd/pty.h | 28 - contrib/cygtermd/sel.c | 386 - contrib/cygtermd/sel.h | 161 - contrib/cygtermd/telnet.c | 565 -- contrib/cygtermd/telnet.h | 41 - contrib/encodelib.py | 132 - contrib/gdb.py | 289 - contrib/kh2reg.py | 442 - contrib/logparse.pl | 1329 --- contrib/logrewrap.pl | 44 - contrib/make1305.py | 374 - contrib/nice-ibeam.cur | Bin 766 -> 0 bytes contrib/plinkfs | 41 - contrib/proveprime.py | 162 - contrib/samplekex.py | 94 - crypto/CMakeLists.txt | 242 - crypto/aes-common.c | 20 - crypto/aes-neon.c | 359 - crypto/aes-ni.c | 341 - crypto/aes-select.c | 114 - crypto/aes-sw.c | 1133 --- crypto/aes.h | 160 - crypto/aesgcm-clmul.c | 180 - crypto/aesgcm-common.c | 8 - crypto/aesgcm-footer.h | 368 - crypto/aesgcm-neon.c | 164 - crypto/aesgcm-ref-poly.c | 364 - crypto/aesgcm-select.c | 38 - crypto/aesgcm-sw.c | 145 - crypto/aesgcm.h | 44 - crypto/arcfour.c | 143 - crypto/argon2.c | 565 -- crypto/bcrypt.c | 118 - crypto/blake2.c | 223 - crypto/blowfish.c | 702 -- crypto/blowfish.h | 14 - crypto/chacha20-poly1305.c | 1079 --- crypto/crc32.c | 113 - crypto/des.c | 1053 --- crypto/diffie-hellman.c | 439 - crypto/dsa.c | 517 -- crypto/ecc-arithmetic.c | 1171 --- crypto/ecc-ssh.c | 1802 ---- crypto/ecc.h | 243 - crypto/hash_simple.c | 13 - crypto/hmac.c | 279 - crypto/mac.c | 43 - crypto/mac_simple.c | 16 - crypto/md5.c | 245 - crypto/mpint.c | 2810 ------ crypto/mpint_i.h | 324 - crypto/ntru.c | 1883 ---- crypto/ntru.h | 53 - crypto/openssh-certs.c | 1162 --- crypto/prng.c | 287 - crypto/pubkey-pem.c | 32 - crypto/pubkey-ppk.c | 29 - crypto/pubkey-ssh1.c | 38 - crypto/rsa.c | 1158 --- crypto/sha1-common.c | 10 - crypto/sha1-neon.c | 190 - crypto/sha1-ni.c | 325 - crypto/sha1-select.c | 44 - crypto/sha1-sw.c | 155 - crypto/sha1.h | 109 - crypto/sha256-common.c | 30 - crypto/sha256-neon.c | 162 - crypto/sha256-ni.c | 342 - crypto/sha256-select.c | 44 - crypto/sha256-sw.c | 157 - crypto/sha256.h | 105 - crypto/sha3.c | 329 - crypto/sha512-common.c | 71 - crypto/sha512-neon.c | 329 - crypto/sha512-select.c | 61 - crypto/sha512-sw.c | 168 - crypto/sha512.h | 131 - crypto/xdmauth.c | 53 - defs.h | 268 - dialog.c | 469 - dialog.h | 695 -- doc/CMakeLists.txt.terabox.uploading.cfg | 0 doc/authplugin.but | 519 -- doc/blurb.but | 42 - doc/chm.css | 7 - doc/chmextra.but | 6 - doc/config.but | 4147 --------- doc/errors.but | 409 - doc/faq.but | 1650 ---- doc/feedback.but | 468 - doc/gs.but | 187 - doc/index.but | 971 -- doc/index.but.terabox.uploading.cfg | Bin 2692 -> 0 bytes doc/intro.but | 88 - doc/intro.but.terabox.uploading.cfg | Bin 1954 -> 0 bytes doc/man-pageant.but | 431 - doc/man-plink.but | 323 - doc/man-pscp.but | 226 - doc/man-psftp.but | 212 - doc/man-psocks.but | 92 - doc/man-psusan.but | 429 - doc/man-pterm.but | 678 -- doc/man-putty.but | 349 - doc/man-puttygen.but | 411 - doc/man-puttytel.but | 215 - doc/mancfg.but | 3 - doc/manpages.but | 3 - doc/pageant.but | 444 - doc/pgpkeys.but | 282 - doc/plink.but | 444 - doc/pscp.but | 340 - doc/psftp.but | 600 -- doc/pubkey.but | 662 -- doc/pubkeyfmt.but | 408 - doc/site.but | 4 - doc/sshnames.but | 130 - doc/udp.but | 764 -- doc/using.but | 1186 --- doc/vids.but | 4 - errsock.c | 74 - icons/cicon.pl | 28 - icons/icon.pl | 270 - icons/macicon.py | 164 - icons/mkicon.py | 1107 --- icons/mksvg.py | 938 -- import.c | 2322 ----- keygen/CMakeLists.txt | 10 - keygen/dsa.c | 103 - keygen/ecdsa.c | 37 - keygen/millerrabin.c | 288 - keygen/mpunsafe.c | 48 - keygen/mpunsafe.h | 39 - keygen/pockle.c | 450 - keygen/prime.c | 762 -- keygen/primecandidate.c | 447 - keygen/rsa.c | 292 - keygen/smallprimes.c | 44 - ldisc.c | 578 -- licence.h | 19 - licence.pl | 134 - logging.c | 521 -- WINDOWS/make22.cmd => make22.cmd | 3 +- marshal.h | 359 - misc.h | 526 -- mksrcarc.sh | 33 - mkunxarc.sh | 29 - mpint.h | 458 - network.h | 436 - otherbackends/CMakeLists.txt | 6 - otherbackends/raw.c | 378 - otherbackends/rlogin.c | 496 - otherbackends/supdup.c | 978 -- otherbackends/telnet.c | 1106 --- otherbackends/testback.c | 200 - pageant.c | 2691 ------ pageant.h | 257 - pinger.c | 72 - proxy/cproxy.c | 189 - proxy/cproxy.h | 99 - proxy/http.c | 781 -- proxy/interactor.c | 119 - proxy/local.c | 272 - proxy/nocproxy.c | 33 - proxy/noproxy.c | 32 - proxy/nosshproxy.c | 16 - proxy/pproxy.c | 17 - proxy/proxy.c | 655 -- proxy/proxy.h | 127 - proxy/socks.h | 72 - proxy/socks4.c | 136 - proxy/socks5.c | 498 - proxy/sshproxy.c | 783 -- proxy/telnet.c | 368 - pscp.c | 2374 ----- psftp.c | 2917 ------ psftp.h | 227 - psftpcommon.c | 102 - psocks.c | 569 -- psocks.h | 28 - putty.h | 2952 ------ puttymem.h | 129 - release.pl | 223 - settings.c | 1389 --- sign.sh | 60 - ssh.h | 1951 ---- ssh/CMakeLists.txt | 52 - ssh/agentf.c | 249 - ssh/bpp-bare.c | 206 - ssh/bpp.h | 181 - ssh/bpp1.c | 389 - ssh/bpp2.c | 998 -- ssh/ca-config.c | 497 - ssh/censor1.c | 76 - ssh/censor2.c | 107 - ssh/channel.h | 316 - ssh/common.c | 1277 --- ssh/connection1-client.c | 552 -- ssh/connection1-server.c | 365 - ssh/connection1.c | 812 -- ssh/connection1.h | 124 - ssh/connection2-client.c | 511 -- ssh/connection2-server.c | 306 - ssh/connection2.c | 1747 ---- ssh/connection2.h | 236 - ssh/crc-attack-detector.c | 171 - ssh/gss.h | 217 - ssh/gssc.c | 288 - ssh/gssc.h | 24 - ssh/kex2-client.c | 1062 --- ssh/kex2-server.c | 337 - ssh/login1-server.c | 444 - ssh/login1.c | 1194 --- ssh/mainchan.c | 539 -- ssh/nogss.c | 19 - ssh/nosharing.c | 25 - ssh/pgssapi.c | 135 - ssh/pgssapi.h | 333 - ssh/portfwd.c | 1179 --- ssh/ppl.h | 181 - ssh/scpserver.c | 1396 --- ssh/server.c | 611 -- ssh/server.h | 146 - ssh/sesschan.c | 800 -- ssh/sftp.c | 1204 --- ssh/sftp.h | 544 -- ssh/sftpcommon.c | 139 - ssh/sftpserver.c | 279 - ssh/sharing.c | 2176 ----- ssh/signal-list.h | 53 - ssh/ssh.c | 1330 --- ssh/transient-hostkey-cache.c | 126 - ssh/transport2.c | 2753 ------ ssh/transport2.h | 267 - ssh/ttymode-list.h | 179 - ssh/userauth2-client.c | 2620 ------ ssh/userauth2-server.c | 400 - ssh/verstring.c | 664 -- ssh/x11fwd.c | 659 -- ssh/zlib.c | 1252 --- sshcr.h | 86 - sshkeygen.h | 300 - sshpubk.c | 1975 ---- sshrand.c | 151 - storage.h | 143 - stubs/CMakeLists.txt | 31 - stubs/no-agent.c | 11 - stubs/no-ca-config.c | 14 - stubs/no-callback.c | 33 - stubs/no-cmdline.c | 22 - stubs/no-console.c | 15 - stubs/no-gss.c | 30 - stubs/no-ldisc.c | 37 - stubs/no-lineedit.c | 18 - stubs/no-logging.c | 20 - stubs/no-network.c | 144 - stubs/no-print.c | 38 - stubs/no-printing.c | 18 - stubs/no-rand.c | 22 - stubs/no-storage.c | 40 - stubs/no-term.c | 16 - stubs/no-timing.c | 26 - stubs/null-cipher.c | 11 - stubs/null-key.c | 22 - stubs/null-lp.c | 8 - stubs/null-mac.c | 11 - stubs/null-opener.c | 20 - stubs/null-plug.c | 40 - stubs/null-seat.c | 68 - terminal/bidi.c | 1685 ---- terminal/bidi.h | 147 - terminal/bidi_gettype.c | 33 - terminal/bidi_test.c | 372 - terminal/lineedit.c | 520 -- terminal/terminal.c | 8054 ----------------- terminal/terminal.h | 616 -- test/agentmulti.py | 56 - test/agenttest.py | 254 - test/agenttestdata.py | 14 - test/agenttestgen.py | 93 - test/ca.py | 198 - test/colours.txt | 15 - test/cryptsuite.py | 4118 --------- test/desref.py | 199 - test/display.txt | 14 - test/eccref.py | 331 - test/fuzzterm.c | 213 - test/lattrs.txt | 6 - test/list-accel.py | 38 - test/mpu-check.pl | 19 - test/numbertheory.py | 638 -- test/primegen.py | 73 - test/sclog/.gitignore | 7 - test/sclog/CMakeLists.txt | 18 - test/sclog/sclog.c | 672 -- test/scocols.txt | 3 - test/ssh.py | 109 - test/test_conf.c | 938 -- test/test_lineedit.c | 773 -- test/test_terminal.c | 508 -- test/testcrypt-enum.h | 179 - test/testcrypt-func.h | 582 -- test/testcrypt.c | 1714 ---- test/testcrypt.py | 441 - test/testsc.c | 1946 ---- test/testzlib.c | 114 - test/utf8.txt | 23 - test/vt100.txt | 16 - test/windowchange.py | 230 - timing.c | 222 - tree234.h | 195 - unicode/ambiguous_wide_chars.h | 189 - unicode/bidi_brackets.h | 139 - unicode/bidi_mirror.h | 437 - unicode/bidi_type.h | 1331 --- unicode/canonical_comp.h | 950 -- unicode/canonical_decomp.h | 2071 ----- unicode/combining_classes.h | 398 - unicode/known_chars.h | 716 -- unicode/nonspacing_chars.h | 366 - unicode/read_ucd.py | 510 -- unicode/version.h | 9 - unicode/wide_chars.h | 130 - unix/CMakeLists.txt | 248 - unix/agent-client.c | 226 - unix/agent-socket.c | 91 - unix/askpass.c | 637 -- unix/cliloop.c | 125 - unix/columns.c | 1276 --- unix/columns.h | 73 - unix/config-gtk.c | 160 - unix/config-unix.c | 49 - unix/console.c | 607 -- unix/dialog.c | 4398 --------- unix/fd-socket.c | 417 - unix/gss.c | 176 - unix/gtk-common.c | 243 - unix/gtkcompat.h | 220 - unix/gtkmisc.h | 19 - unix/keygen-noise.c | 64 - unix/local-proxy.c | 116 - unix/main-gtk-application.c | 326 - unix/main-gtk-simple.c | 679 -- unix/network.c | 1755 ---- unix/no-gtk.c | 11 - unix/noaskpass.c | 19 - unix/noise.c | 130 - unix/osxlaunch.c | 471 - unix/pageant.c | 1532 ---- unix/peerinfo.c | 32 - unix/platform.h | 480 - unix/plink.c | 983 -- unix/printing.c | 58 - unix/procnet.c | 229 - unix/psocks.c | 173 - unix/psusan.c | 421 - unix/pterm-config-xpm.c | 150 - unix/pterm-xpm.c | 143 - unix/pterm.bundle | 47 - unix/pterm.c | 49 - unix/pterm.plist | 30 - unix/pty.c | 1614 ---- unix/putty-config-xpm.c | 150 - unix/putty-xpm.c | 147 - unix/putty.bundle | 77 - unix/putty.c | 92 - unix/putty.plist | 30 - unix/serial.c | 596 -- unix/sftp.c | 581 -- unix/sftpserver.c | 703 -- unix/sharing.c | 370 - unix/storage.c | 974 -- unix/stubs/no-uxsel.c | 31 - unix/unicode.c | 278 - unix/unifont.c | 3806 -------- unix/unifont.h | 186 - unix/uppity.c | 971 -- unix/utils/align_label_left.c | 20 - unix/utils/arm_arch_queries.c | 105 - unix/utils/arm_arch_queries.h | 69 - unix/utils/block_signal.c | 21 - unix/utils/buildinfo_gtk_version.c | 14 - unix/utils/cloexec.c | 49 - unix/utils/dputs.c | 24 - unix/utils/filename.c | 72 - unix/utils/fontspec.c | 40 - unix/utils/get_label_text_dimensions.c | 42 - unix/utils/get_username.c | 52 - unix/utils/get_x11_display.c | 34 - unix/utils/getticks.c | 33 - unix/utils/keysym_to_unicode.c | 1011 --- unix/utils/make_dir_and_check_ours.c | 60 - unix/utils/make_dir_path.c | 39 - unix/utils/make_spr_sw_abort_errno.c | 22 - unix/utils/nonblock.c | 55 - unix/utils/open_for_write_would_lose_data.c | 44 - unix/utils/our_dialog.c | 135 - unix/utils/pgp_fingerprints.c | 23 - unix/utils/pollwrap.c | 190 - unix/utils/signal.c | 30 - unix/utils/string_width.c | 18 - unix/utils/x11_ignore_error.c | 88 - unix/uxsel.c | 127 - unix/window.c | 5682 ------------ unix/x11.c | 224 - unix/x11misc.h | 14 - utils/CMakeLists.txt | 85 - utils/antispoof.c | 28 - utils/backend_socket_log.c | 62 - utils/base64_decode.c | 37 - utils/base64_decode_atom.c | 54 - utils/base64_encode.c | 40 - utils/base64_encode_atom.c | 30 - utils/base64_valid.c | 54 - utils/bufchain.c | 193 - utils/buildinfo.c | 164 - utils/burnstr.c | 15 - utils/burnwcs.c | 18 - utils/cert-expr.c | 968 -- utils/chomp.c | 26 - utils/cmdline_get_passwd_input_state_new.c | 9 - utils/conf.c | 577 -- utils/conf_data.c | 53 - utils/conf_dest.c | 15 - utils/conf_launchable.c | 14 - utils/ctrlparse.c | 49 - utils/ctrlset_normalise.c | 60 - utils/debug.c | 56 - utils/decode_utf8.c | 264 - utils/decode_utf8_to_wchar.c | 21 - utils/decode_utf8_to_wide_string.c | 35 - utils/default_description.c | 22 - utils/dup_mb_to_wc.c | 34 - utils/dup_wc_to_mb.c | 44 - utils/dupcat.c | 48 - utils/dupprintf.c | 100 - utils/dupstr.c | 19 - utils/dupwcs.c | 19 - utils/encode_utf8.c | 26 - utils/encode_wide_string_as_utf8.c | 23 - utils/fgetline.c | 25 - utils/host_ca_new_free.c | 22 - utils/host_strchr.c | 18 - utils/host_strchr_internal.c | 80 - utils/host_strcspn.c | 19 - utils/host_strduptrim.c | 51 - utils/host_strrchr.c | 18 - utils/key_components.c | 93 - utils/log_proxy_stderr.c | 103 - utils/logeventf.c | 32 - utils/ltime.c | 16 - utils/make_spr_sw_abort_static.c | 21 - utils/marshal.c | 358 - utils/memory.c | 134 - utils/memxor.c | 34 - utils/nullstrcmp.c | 21 - utils/out_of_memory.c | 11 - utils/parse_blocksize.c | 40 - utils/percent_decode.c | 41 - utils/percent_encode.c | 34 - utils/prompts.c | 70 - utils/ptrlen.c | 110 - utils/read_file_into.c | 19 - utils/seat_connection_fatal.c | 20 - utils/seat_dialog_text.c | 41 - utils/seat_nonfatal.c | 19 - utils/sessprep.c | 84 - utils/sk_free_peer_info.c | 14 - utils/smemclr.c | 52 - utils/smemeq.c | 25 - utils/spr_get_error_message.c | 13 - utils/ssh2_pick_fingerprint.c | 27 - utils/ssh_key_clone.c | 32 - utils/sshutils.c | 128 - utils/strbuf.c | 128 - utils/string_length_for_printf.c | 21 - utils/stripctrl.c | 470 - utils/tempseat.c | 442 - utils/tree234.c | 1638 ---- utils/unicode-known.c | 59 - utils/unicode-norm.c | 453 - utils/utils.h | 12 - utils/validate_manual_hostkey.c | 121 - utils/version.c | 23 - utils/wcwidth.c | 209 - utils/wildcard.c | 486 - utils/wordwrap.c | 36 - utils/write_c_string_literal.c | 39 - utils/x11_dehexify.c | 29 - utils/x11_identify_auth_proto.c | 17 - utils/x11_make_greeting.c | 67 - utils/x11_parse_ip.c | 21 - utils/x11authfile.c | 244 - utils/x11authnames.c | 9 - version.h | 13 - x11disp.c | 189 - 684 files changed, 129 insertions(+), 256780 deletions(-) delete mode 100644 .gitignore delete mode 100644 Buildscr delete mode 100644 Buildscr.cv delete mode 100644 CHECKLST.txt delete mode 100644 CMakeLists.txt delete mode 100644 LATEST.VER delete mode 100644 LICENCE delete mode 100644 Makefile.am create mode 100644 PuTTYNG.ps1 delete mode 100644 README delete mode 100644 WINDOWS/CMakeLists.txt delete mode 100644 WINDOWS/DEVCPP/PAGEANT/PAGEANT.DEV delete mode 100644 WINDOWS/DEVCPP/PLINK/PLINK.DEV delete mode 100644 WINDOWS/DEVCPP/PSCP/PSCP.DEV delete mode 100644 WINDOWS/DEVCPP/PSFTP/PSFTP.DEV delete mode 100644 WINDOWS/DEVCPP/PUTTY/PUTTY.DEV delete mode 100644 WINDOWS/DEVCPP/PUTTYGEN/PUTTYGEN.DEV delete mode 100644 WINDOWS/DEVCPP/PUTTYTEL/PUTTYTEL.DEV delete mode 100644 WINDOWS/MAKEFILE.LCC delete mode 100644 WINDOWS/MAKEFILE.NG delete mode 100644 WINDOWS/MAKEFILE.VC delete mode 100644 WINDOWS/README-msi.txt delete mode 100644 WINDOWS/VS2010/putty.sln delete mode 100644 WINDOWS/VS2012/putty.sln delete mode 100644 WINDOWS/VS2015/PuTTYNG.sln delete mode 100644 WINDOWS/agent-client.c delete mode 100644 WINDOWS/cliloop.c delete mode 100644 WINDOWS/config.c delete mode 100644 WINDOWS/conpty.c delete mode 100644 WINDOWS/console.c delete mode 100644 WINDOWS/controls.c delete mode 100644 WINDOWS/cryptoapi.h delete mode 100644 WINDOWS/dialog.c delete mode 100644 WINDOWS/gss.c delete mode 100644 WINDOWS/handle-io.c delete mode 100644 WINDOWS/handle-socket.c delete mode 100644 WINDOWS/handle-wait.c delete mode 100644 WINDOWS/help.c delete mode 100644 WINDOWS/help.h delete mode 100644 WINDOWS/help.rc2 delete mode 100644 WINDOWS/installer.wxs delete mode 100644 WINDOWS/jump-list.c delete mode 100644 WINDOWS/local-proxy.c delete mode 100644 WINDOWS/make.cmd delete mode 100644 WINDOWS/make17.cmd delete mode 100644 WINDOWS/make19.cmd delete mode 100644 WINDOWS/make_install_images.sh delete mode 100644 WINDOWS/msifixup.py delete mode 100644 WINDOWS/named-pipe-client.c delete mode 100644 WINDOWS/named-pipe-server.c delete mode 100644 WINDOWS/network.c delete mode 100644 WINDOWS/no-jump-list.c delete mode 100644 WINDOWS/nohelp.c delete mode 100644 WINDOWS/noise.c delete mode 100644 WINDOWS/pageant-rc.h delete mode 100644 WINDOWS/pageant.c delete mode 100644 WINDOWS/pageant.ico delete mode 100644 WINDOWS/pageant.mft delete mode 100644 WINDOWS/pageant.rc delete mode 100644 WINDOWS/pageants.ico delete mode 100644 WINDOWS/platform.h delete mode 100644 WINDOWS/plink.c delete mode 100644 WINDOWS/plink.rc delete mode 100644 WINDOWS/printing.c delete mode 100644 WINDOWS/pscp.ico delete mode 100644 WINDOWS/pscp.rc delete mode 100644 WINDOWS/psftp.rc delete mode 100644 WINDOWS/psocks.c delete mode 100644 WINDOWS/pterm.c delete mode 100644 WINDOWS/pterm.ico delete mode 100644 WINDOWS/pterm.rc delete mode 100644 WINDOWS/ptermcfg.ico delete mode 100644 WINDOWS/putty-common.rc2 delete mode 100644 WINDOWS/putty-rc.h delete mode 100644 WINDOWS/putty.c delete mode 100644 WINDOWS/putty.ico delete mode 100644 WINDOWS/putty.mft delete mode 100644 WINDOWS/putty.rc delete mode 100644 WINDOWS/puttycfg.ico delete mode 100644 WINDOWS/puttygen-rc.h delete mode 100644 WINDOWS/puttygen.c delete mode 100644 WINDOWS/puttygen.ico delete mode 100644 WINDOWS/puttygen.mft delete mode 100644 WINDOWS/puttygen.rc delete mode 100644 WINDOWS/puttyins.ico delete mode 100644 WINDOWS/puttytel.mft delete mode 100644 WINDOWS/puttytel.rc delete mode 100644 WINDOWS/rcstuff.h delete mode 100644 WINDOWS/security-api.h delete mode 100644 WINDOWS/select-cli.c delete mode 100644 WINDOWS/select-gui.c delete mode 100644 WINDOWS/serial.c delete mode 100644 WINDOWS/sftp.c delete mode 100644 WINDOWS/sharing.c delete mode 100644 WINDOWS/sign_binaries.ps1 delete mode 100644 WINDOWS/sizetip.c delete mode 100644 WINDOWS/storage.c delete mode 100644 WINDOWS/test/test_screenshot.c delete mode 100644 WINDOWS/test/test_split_into_argv.c delete mode 100644 WINDOWS/test_screenshot.c delete mode 100644 WINDOWS/unicode.c delete mode 100644 WINDOWS/utils/agent_mutex_name.c delete mode 100644 WINDOWS/utils/agent_named_pipe_name.c delete mode 100644 WINDOWS/utils/arm_arch_queries.c delete mode 100644 WINDOWS/utils/aux_match_opt.c delete mode 100644 WINDOWS/utils/centre_window.c delete mode 100644 WINDOWS/utils/cryptoapi.c delete mode 100644 WINDOWS/utils/defaults.c delete mode 100644 WINDOWS/utils/dll_hijacking_protection.c delete mode 100644 WINDOWS/utils/dputs.c delete mode 100644 WINDOWS/utils/escape_registry_key.c delete mode 100644 WINDOWS/utils/filename.c delete mode 100644 WINDOWS/utils/fontspec.c delete mode 100644 WINDOWS/utils/get_system_dir.c delete mode 100644 WINDOWS/utils/get_username.c delete mode 100644 WINDOWS/utils/getdlgitemtext_alloc.c delete mode 100644 WINDOWS/utils/gui-timing.c delete mode 100644 WINDOWS/utils/interprocess_mutex.c delete mode 100644 WINDOWS/utils/is_console_handle.c delete mode 100644 WINDOWS/utils/load_system32_dll.c delete mode 100644 WINDOWS/utils/ltime.c delete mode 100644 WINDOWS/utils/make_spr_sw_abort_winerror.c delete mode 100644 WINDOWS/utils/makedlgitemborderless.c delete mode 100644 WINDOWS/utils/message_box.c delete mode 100644 WINDOWS/utils/minefield.c delete mode 100644 WINDOWS/utils/open_for_write_would_lose_data.c delete mode 100644 WINDOWS/utils/pgp_fingerprints_msgbox.c delete mode 100644 WINDOWS/utils/platform_get_x_display.c delete mode 100644 WINDOWS/utils/registry.c delete mode 100644 WINDOWS/utils/request_file.c delete mode 100644 WINDOWS/utils/screenshot.c delete mode 100644 WINDOWS/utils/security.c delete mode 100644 WINDOWS/utils/shinydialogbox.c delete mode 100644 WINDOWS/utils/split_into_argv.c delete mode 100644 WINDOWS/utils/split_into_argv_w.c delete mode 100644 WINDOWS/utils/strtoumax.c delete mode 100644 WINDOWS/utils/version.c delete mode 100644 WINDOWS/utils/win_strerror.c delete mode 100644 WINDOWS/version.rc2 delete mode 100644 WINDOWS/website.url delete mode 100644 WINDOWS/win-gui-seat.h delete mode 100644 WINDOWS/window.c delete mode 100644 WINDOWS/x11.c delete mode 100644 aqsync.c delete mode 100644 be_all.c delete mode 100644 be_all_s.c delete mode 100644 be_list.c delete mode 100644 callback.c delete mode 100644 cert/CodeSigning_Cert_mRemoteNG_DigiCert.p12.enc delete mode 100644 cgtest.c delete mode 100644 charset/CMakeLists.txt delete mode 100644 charset/README delete mode 100644 charset/charset.h delete mode 100644 charset/enum.c delete mode 100644 charset/fromucs.c delete mode 100644 charset/internal.h delete mode 100644 charset/localenc.c delete mode 100644 charset/macenc.c delete mode 100644 charset/mimeenc.c delete mode 100644 charset/sbcs.c delete mode 100644 charset/sbcs.dat delete mode 100644 charset/sbcsgen.pl delete mode 100644 charset/slookup.c delete mode 100644 charset/toucs.c delete mode 100644 charset/utf8.c delete mode 100644 charset/xenc.c delete mode 100644 clicons.c delete mode 100644 cmake/cmake.h.in delete mode 100644 cmake/gitcommit.cmake delete mode 100644 cmake/gtk.cmake delete mode 100644 cmake/licence.cmake delete mode 100644 cmake/platforms/unix.cmake delete mode 100644 cmake/platforms/windows.cmake delete mode 100644 cmake/setup.cmake delete mode 100644 cmake/toolchain-mingw.cmake delete mode 100644 cmake/toolchain-winegcc.cmake delete mode 100644 cmake/winegcc delete mode 100644 cmdgen.c delete mode 100644 cmdline.c delete mode 100644 config.c delete mode 100644 console.c delete mode 100644 console.h delete mode 100644 contrib/authplugin-example.py delete mode 100644 contrib/cygtermd/README delete mode 100644 contrib/cygtermd/main.c delete mode 100644 contrib/cygtermd/malloc.c delete mode 100644 contrib/cygtermd/malloc.h delete mode 100644 contrib/cygtermd/pty.c delete mode 100644 contrib/cygtermd/pty.h delete mode 100644 contrib/cygtermd/sel.c delete mode 100644 contrib/cygtermd/sel.h delete mode 100644 contrib/cygtermd/telnet.c delete mode 100644 contrib/cygtermd/telnet.h delete mode 100644 contrib/encodelib.py delete mode 100644 contrib/gdb.py delete mode 100644 contrib/kh2reg.py delete mode 100644 contrib/logparse.pl delete mode 100644 contrib/logrewrap.pl delete mode 100644 contrib/make1305.py delete mode 100644 contrib/nice-ibeam.cur delete mode 100644 contrib/plinkfs delete mode 100644 contrib/proveprime.py delete mode 100644 contrib/samplekex.py delete mode 100644 crypto/CMakeLists.txt delete mode 100644 crypto/aes-common.c delete mode 100644 crypto/aes-neon.c delete mode 100644 crypto/aes-ni.c delete mode 100644 crypto/aes-select.c delete mode 100644 crypto/aes-sw.c delete mode 100644 crypto/aes.h delete mode 100644 crypto/aesgcm-clmul.c delete mode 100644 crypto/aesgcm-common.c delete mode 100644 crypto/aesgcm-footer.h delete mode 100644 crypto/aesgcm-neon.c delete mode 100644 crypto/aesgcm-ref-poly.c delete mode 100644 crypto/aesgcm-select.c delete mode 100644 crypto/aesgcm-sw.c delete mode 100644 crypto/aesgcm.h delete mode 100644 crypto/arcfour.c delete mode 100644 crypto/argon2.c delete mode 100644 crypto/bcrypt.c delete mode 100644 crypto/blake2.c delete mode 100644 crypto/blowfish.c delete mode 100644 crypto/blowfish.h delete mode 100644 crypto/chacha20-poly1305.c delete mode 100644 crypto/crc32.c delete mode 100644 crypto/des.c delete mode 100644 crypto/diffie-hellman.c delete mode 100644 crypto/dsa.c delete mode 100644 crypto/ecc-arithmetic.c delete mode 100644 crypto/ecc-ssh.c delete mode 100644 crypto/ecc.h delete mode 100644 crypto/hash_simple.c delete mode 100644 crypto/hmac.c delete mode 100644 crypto/mac.c delete mode 100644 crypto/mac_simple.c delete mode 100644 crypto/md5.c delete mode 100644 crypto/mpint.c delete mode 100644 crypto/mpint_i.h delete mode 100644 crypto/ntru.c delete mode 100644 crypto/ntru.h delete mode 100644 crypto/openssh-certs.c delete mode 100644 crypto/prng.c delete mode 100644 crypto/pubkey-pem.c delete mode 100644 crypto/pubkey-ppk.c delete mode 100644 crypto/pubkey-ssh1.c delete mode 100644 crypto/rsa.c delete mode 100644 crypto/sha1-common.c delete mode 100644 crypto/sha1-neon.c delete mode 100644 crypto/sha1-ni.c delete mode 100644 crypto/sha1-select.c delete mode 100644 crypto/sha1-sw.c delete mode 100644 crypto/sha1.h delete mode 100644 crypto/sha256-common.c delete mode 100644 crypto/sha256-neon.c delete mode 100644 crypto/sha256-ni.c delete mode 100644 crypto/sha256-select.c delete mode 100644 crypto/sha256-sw.c delete mode 100644 crypto/sha256.h delete mode 100644 crypto/sha3.c delete mode 100644 crypto/sha512-common.c delete mode 100644 crypto/sha512-neon.c delete mode 100644 crypto/sha512-select.c delete mode 100644 crypto/sha512-sw.c delete mode 100644 crypto/sha512.h delete mode 100644 crypto/xdmauth.c delete mode 100644 defs.h delete mode 100644 dialog.c delete mode 100644 dialog.h delete mode 100644 doc/CMakeLists.txt.terabox.uploading.cfg delete mode 100644 doc/authplugin.but delete mode 100644 doc/blurb.but delete mode 100644 doc/chm.css delete mode 100644 doc/chmextra.but delete mode 100644 doc/config.but delete mode 100644 doc/errors.but delete mode 100644 doc/faq.but delete mode 100644 doc/feedback.but delete mode 100644 doc/gs.but delete mode 100644 doc/index.but delete mode 100644 doc/index.but.terabox.uploading.cfg delete mode 100644 doc/intro.but delete mode 100644 doc/intro.but.terabox.uploading.cfg delete mode 100644 doc/man-pageant.but delete mode 100644 doc/man-plink.but delete mode 100644 doc/man-pscp.but delete mode 100644 doc/man-psftp.but delete mode 100644 doc/man-psocks.but delete mode 100644 doc/man-psusan.but delete mode 100644 doc/man-pterm.but delete mode 100644 doc/man-putty.but delete mode 100644 doc/man-puttygen.but delete mode 100644 doc/man-puttytel.but delete mode 100644 doc/mancfg.but delete mode 100644 doc/manpages.but delete mode 100644 doc/pageant.but delete mode 100644 doc/pgpkeys.but delete mode 100644 doc/plink.but delete mode 100644 doc/pscp.but delete mode 100644 doc/psftp.but delete mode 100644 doc/pubkey.but delete mode 100644 doc/pubkeyfmt.but delete mode 100644 doc/site.but delete mode 100644 doc/sshnames.but delete mode 100644 doc/udp.but delete mode 100644 doc/using.but delete mode 100644 doc/vids.but delete mode 100644 errsock.c delete mode 100644 icons/cicon.pl delete mode 100644 icons/icon.pl delete mode 100644 icons/macicon.py delete mode 100644 icons/mkicon.py delete mode 100644 icons/mksvg.py delete mode 100644 import.c delete mode 100644 keygen/CMakeLists.txt delete mode 100644 keygen/dsa.c delete mode 100644 keygen/ecdsa.c delete mode 100644 keygen/millerrabin.c delete mode 100644 keygen/mpunsafe.c delete mode 100644 keygen/mpunsafe.h delete mode 100644 keygen/pockle.c delete mode 100644 keygen/prime.c delete mode 100644 keygen/primecandidate.c delete mode 100644 keygen/rsa.c delete mode 100644 keygen/smallprimes.c delete mode 100644 ldisc.c delete mode 100644 licence.h delete mode 100644 licence.pl delete mode 100644 logging.c rename WINDOWS/make22.cmd => make22.cmd (97%) delete mode 100644 marshal.h delete mode 100644 misc.h delete mode 100644 mksrcarc.sh delete mode 100644 mkunxarc.sh delete mode 100644 mpint.h delete mode 100644 network.h delete mode 100644 otherbackends/CMakeLists.txt delete mode 100644 otherbackends/raw.c delete mode 100644 otherbackends/rlogin.c delete mode 100644 otherbackends/supdup.c delete mode 100644 otherbackends/telnet.c delete mode 100644 otherbackends/testback.c delete mode 100644 pageant.c delete mode 100644 pageant.h delete mode 100644 pinger.c delete mode 100644 proxy/cproxy.c delete mode 100644 proxy/cproxy.h delete mode 100644 proxy/http.c delete mode 100644 proxy/interactor.c delete mode 100644 proxy/local.c delete mode 100644 proxy/nocproxy.c delete mode 100644 proxy/noproxy.c delete mode 100644 proxy/nosshproxy.c delete mode 100644 proxy/pproxy.c delete mode 100644 proxy/proxy.c delete mode 100644 proxy/proxy.h delete mode 100644 proxy/socks.h delete mode 100644 proxy/socks4.c delete mode 100644 proxy/socks5.c delete mode 100644 proxy/sshproxy.c delete mode 100644 proxy/telnet.c delete mode 100644 pscp.c delete mode 100644 psftp.c delete mode 100644 psftp.h delete mode 100644 psftpcommon.c delete mode 100644 psocks.c delete mode 100644 psocks.h delete mode 100644 putty.h delete mode 100644 puttymem.h delete mode 100644 release.pl delete mode 100644 settings.c delete mode 100644 sign.sh delete mode 100644 ssh.h delete mode 100644 ssh/CMakeLists.txt delete mode 100644 ssh/agentf.c delete mode 100644 ssh/bpp-bare.c delete mode 100644 ssh/bpp.h delete mode 100644 ssh/bpp1.c delete mode 100644 ssh/bpp2.c delete mode 100644 ssh/ca-config.c delete mode 100644 ssh/censor1.c delete mode 100644 ssh/censor2.c delete mode 100644 ssh/channel.h delete mode 100644 ssh/common.c delete mode 100644 ssh/connection1-client.c delete mode 100644 ssh/connection1-server.c delete mode 100644 ssh/connection1.c delete mode 100644 ssh/connection1.h delete mode 100644 ssh/connection2-client.c delete mode 100644 ssh/connection2-server.c delete mode 100644 ssh/connection2.c delete mode 100644 ssh/connection2.h delete mode 100644 ssh/crc-attack-detector.c delete mode 100644 ssh/gss.h delete mode 100644 ssh/gssc.c delete mode 100644 ssh/gssc.h delete mode 100644 ssh/kex2-client.c delete mode 100644 ssh/kex2-server.c delete mode 100644 ssh/login1-server.c delete mode 100644 ssh/login1.c delete mode 100644 ssh/mainchan.c delete mode 100644 ssh/nogss.c delete mode 100644 ssh/nosharing.c delete mode 100644 ssh/pgssapi.c delete mode 100644 ssh/pgssapi.h delete mode 100644 ssh/portfwd.c delete mode 100644 ssh/ppl.h delete mode 100644 ssh/scpserver.c delete mode 100644 ssh/server.c delete mode 100644 ssh/server.h delete mode 100644 ssh/sesschan.c delete mode 100644 ssh/sftp.c delete mode 100644 ssh/sftp.h delete mode 100644 ssh/sftpcommon.c delete mode 100644 ssh/sftpserver.c delete mode 100644 ssh/sharing.c delete mode 100644 ssh/signal-list.h delete mode 100644 ssh/ssh.c delete mode 100644 ssh/transient-hostkey-cache.c delete mode 100644 ssh/transport2.c delete mode 100644 ssh/transport2.h delete mode 100644 ssh/ttymode-list.h delete mode 100644 ssh/userauth2-client.c delete mode 100644 ssh/userauth2-server.c delete mode 100644 ssh/verstring.c delete mode 100644 ssh/x11fwd.c delete mode 100644 ssh/zlib.c delete mode 100644 sshcr.h delete mode 100644 sshkeygen.h delete mode 100644 sshpubk.c delete mode 100644 sshrand.c delete mode 100644 storage.h delete mode 100644 stubs/CMakeLists.txt delete mode 100644 stubs/no-agent.c delete mode 100644 stubs/no-ca-config.c delete mode 100644 stubs/no-callback.c delete mode 100644 stubs/no-cmdline.c delete mode 100644 stubs/no-console.c delete mode 100644 stubs/no-gss.c delete mode 100644 stubs/no-ldisc.c delete mode 100644 stubs/no-lineedit.c delete mode 100644 stubs/no-logging.c delete mode 100644 stubs/no-network.c delete mode 100644 stubs/no-print.c delete mode 100644 stubs/no-printing.c delete mode 100644 stubs/no-rand.c delete mode 100644 stubs/no-storage.c delete mode 100644 stubs/no-term.c delete mode 100644 stubs/no-timing.c delete mode 100644 stubs/null-cipher.c delete mode 100644 stubs/null-key.c delete mode 100644 stubs/null-lp.c delete mode 100644 stubs/null-mac.c delete mode 100644 stubs/null-opener.c delete mode 100644 stubs/null-plug.c delete mode 100644 stubs/null-seat.c delete mode 100644 terminal/bidi.c delete mode 100644 terminal/bidi.h delete mode 100644 terminal/bidi_gettype.c delete mode 100644 terminal/bidi_test.c delete mode 100644 terminal/lineedit.c delete mode 100644 terminal/terminal.c delete mode 100644 terminal/terminal.h delete mode 100644 test/agentmulti.py delete mode 100644 test/agenttest.py delete mode 100644 test/agenttestdata.py delete mode 100644 test/agenttestgen.py delete mode 100644 test/ca.py delete mode 100644 test/colours.txt delete mode 100644 test/cryptsuite.py delete mode 100644 test/desref.py delete mode 100644 test/display.txt delete mode 100644 test/eccref.py delete mode 100644 test/fuzzterm.c delete mode 100644 test/lattrs.txt delete mode 100644 test/list-accel.py delete mode 100644 test/mpu-check.pl delete mode 100644 test/numbertheory.py delete mode 100644 test/primegen.py delete mode 100644 test/sclog/.gitignore delete mode 100644 test/sclog/CMakeLists.txt delete mode 100644 test/sclog/sclog.c delete mode 100644 test/scocols.txt delete mode 100644 test/ssh.py delete mode 100644 test/test_conf.c delete mode 100644 test/test_lineedit.c delete mode 100644 test/test_terminal.c delete mode 100644 test/testcrypt-enum.h delete mode 100644 test/testcrypt-func.h delete mode 100644 test/testcrypt.c delete mode 100644 test/testcrypt.py delete mode 100644 test/testsc.c delete mode 100644 test/testzlib.c delete mode 100644 test/utf8.txt delete mode 100644 test/vt100.txt delete mode 100644 test/windowchange.py delete mode 100644 timing.c delete mode 100644 tree234.h delete mode 100644 unicode/ambiguous_wide_chars.h delete mode 100644 unicode/bidi_brackets.h delete mode 100644 unicode/bidi_mirror.h delete mode 100644 unicode/bidi_type.h delete mode 100644 unicode/canonical_comp.h delete mode 100644 unicode/canonical_decomp.h delete mode 100644 unicode/combining_classes.h delete mode 100644 unicode/known_chars.h delete mode 100644 unicode/nonspacing_chars.h delete mode 100644 unicode/read_ucd.py delete mode 100644 unicode/version.h delete mode 100644 unicode/wide_chars.h delete mode 100644 unix/CMakeLists.txt delete mode 100644 unix/agent-client.c delete mode 100644 unix/agent-socket.c delete mode 100644 unix/askpass.c delete mode 100644 unix/cliloop.c delete mode 100644 unix/columns.c delete mode 100644 unix/columns.h delete mode 100644 unix/config-gtk.c delete mode 100644 unix/config-unix.c delete mode 100644 unix/console.c delete mode 100644 unix/dialog.c delete mode 100644 unix/fd-socket.c delete mode 100644 unix/gss.c delete mode 100644 unix/gtk-common.c delete mode 100644 unix/gtkcompat.h delete mode 100644 unix/gtkmisc.h delete mode 100644 unix/keygen-noise.c delete mode 100644 unix/local-proxy.c delete mode 100644 unix/main-gtk-application.c delete mode 100644 unix/main-gtk-simple.c delete mode 100644 unix/network.c delete mode 100644 unix/no-gtk.c delete mode 100644 unix/noaskpass.c delete mode 100644 unix/noise.c delete mode 100644 unix/osxlaunch.c delete mode 100644 unix/pageant.c delete mode 100644 unix/peerinfo.c delete mode 100644 unix/platform.h delete mode 100644 unix/plink.c delete mode 100644 unix/printing.c delete mode 100644 unix/procnet.c delete mode 100644 unix/psocks.c delete mode 100644 unix/psusan.c delete mode 100644 unix/pterm-config-xpm.c delete mode 100644 unix/pterm-xpm.c delete mode 100644 unix/pterm.bundle delete mode 100644 unix/pterm.c delete mode 100644 unix/pterm.plist delete mode 100644 unix/pty.c delete mode 100644 unix/putty-config-xpm.c delete mode 100644 unix/putty-xpm.c delete mode 100644 unix/putty.bundle delete mode 100644 unix/putty.c delete mode 100644 unix/putty.plist delete mode 100644 unix/serial.c delete mode 100644 unix/sftp.c delete mode 100644 unix/sftpserver.c delete mode 100644 unix/sharing.c delete mode 100644 unix/storage.c delete mode 100644 unix/stubs/no-uxsel.c delete mode 100644 unix/unicode.c delete mode 100644 unix/unifont.c delete mode 100644 unix/unifont.h delete mode 100644 unix/uppity.c delete mode 100644 unix/utils/align_label_left.c delete mode 100644 unix/utils/arm_arch_queries.c delete mode 100644 unix/utils/arm_arch_queries.h delete mode 100644 unix/utils/block_signal.c delete mode 100644 unix/utils/buildinfo_gtk_version.c delete mode 100644 unix/utils/cloexec.c delete mode 100644 unix/utils/dputs.c delete mode 100644 unix/utils/filename.c delete mode 100644 unix/utils/fontspec.c delete mode 100644 unix/utils/get_label_text_dimensions.c delete mode 100644 unix/utils/get_username.c delete mode 100644 unix/utils/get_x11_display.c delete mode 100644 unix/utils/getticks.c delete mode 100644 unix/utils/keysym_to_unicode.c delete mode 100644 unix/utils/make_dir_and_check_ours.c delete mode 100644 unix/utils/make_dir_path.c delete mode 100644 unix/utils/make_spr_sw_abort_errno.c delete mode 100644 unix/utils/nonblock.c delete mode 100644 unix/utils/open_for_write_would_lose_data.c delete mode 100644 unix/utils/our_dialog.c delete mode 100644 unix/utils/pgp_fingerprints.c delete mode 100644 unix/utils/pollwrap.c delete mode 100644 unix/utils/signal.c delete mode 100644 unix/utils/string_width.c delete mode 100644 unix/utils/x11_ignore_error.c delete mode 100644 unix/uxsel.c delete mode 100644 unix/window.c delete mode 100644 unix/x11.c delete mode 100644 unix/x11misc.h delete mode 100644 utils/CMakeLists.txt delete mode 100644 utils/antispoof.c delete mode 100644 utils/backend_socket_log.c delete mode 100644 utils/base64_decode.c delete mode 100644 utils/base64_decode_atom.c delete mode 100644 utils/base64_encode.c delete mode 100644 utils/base64_encode_atom.c delete mode 100644 utils/base64_valid.c delete mode 100644 utils/bufchain.c delete mode 100644 utils/buildinfo.c delete mode 100644 utils/burnstr.c delete mode 100644 utils/burnwcs.c delete mode 100644 utils/cert-expr.c delete mode 100644 utils/chomp.c delete mode 100644 utils/cmdline_get_passwd_input_state_new.c delete mode 100644 utils/conf.c delete mode 100644 utils/conf_data.c delete mode 100644 utils/conf_dest.c delete mode 100644 utils/conf_launchable.c delete mode 100644 utils/ctrlparse.c delete mode 100644 utils/ctrlset_normalise.c delete mode 100644 utils/debug.c delete mode 100644 utils/decode_utf8.c delete mode 100644 utils/decode_utf8_to_wchar.c delete mode 100644 utils/decode_utf8_to_wide_string.c delete mode 100644 utils/default_description.c delete mode 100644 utils/dup_mb_to_wc.c delete mode 100644 utils/dup_wc_to_mb.c delete mode 100644 utils/dupcat.c delete mode 100644 utils/dupprintf.c delete mode 100644 utils/dupstr.c delete mode 100644 utils/dupwcs.c delete mode 100644 utils/encode_utf8.c delete mode 100644 utils/encode_wide_string_as_utf8.c delete mode 100644 utils/fgetline.c delete mode 100644 utils/host_ca_new_free.c delete mode 100644 utils/host_strchr.c delete mode 100644 utils/host_strchr_internal.c delete mode 100644 utils/host_strcspn.c delete mode 100644 utils/host_strduptrim.c delete mode 100644 utils/host_strrchr.c delete mode 100644 utils/key_components.c delete mode 100644 utils/log_proxy_stderr.c delete mode 100644 utils/logeventf.c delete mode 100644 utils/ltime.c delete mode 100644 utils/make_spr_sw_abort_static.c delete mode 100644 utils/marshal.c delete mode 100644 utils/memory.c delete mode 100644 utils/memxor.c delete mode 100644 utils/nullstrcmp.c delete mode 100644 utils/out_of_memory.c delete mode 100644 utils/parse_blocksize.c delete mode 100644 utils/percent_decode.c delete mode 100644 utils/percent_encode.c delete mode 100644 utils/prompts.c delete mode 100644 utils/ptrlen.c delete mode 100644 utils/read_file_into.c delete mode 100644 utils/seat_connection_fatal.c delete mode 100644 utils/seat_dialog_text.c delete mode 100644 utils/seat_nonfatal.c delete mode 100644 utils/sessprep.c delete mode 100644 utils/sk_free_peer_info.c delete mode 100644 utils/smemclr.c delete mode 100644 utils/smemeq.c delete mode 100644 utils/spr_get_error_message.c delete mode 100644 utils/ssh2_pick_fingerprint.c delete mode 100644 utils/ssh_key_clone.c delete mode 100644 utils/sshutils.c delete mode 100644 utils/strbuf.c delete mode 100644 utils/string_length_for_printf.c delete mode 100644 utils/stripctrl.c delete mode 100644 utils/tempseat.c delete mode 100644 utils/tree234.c delete mode 100644 utils/unicode-known.c delete mode 100644 utils/unicode-norm.c delete mode 100644 utils/utils.h delete mode 100644 utils/validate_manual_hostkey.c delete mode 100644 utils/version.c delete mode 100644 utils/wcwidth.c delete mode 100644 utils/wildcard.c delete mode 100644 utils/wordwrap.c delete mode 100644 utils/write_c_string_literal.c delete mode 100644 utils/x11_dehexify.c delete mode 100644 utils/x11_identify_auth_proto.c delete mode 100644 utils/x11_make_greeting.c delete mode 100644 utils/x11_parse_ip.c delete mode 100644 utils/x11authfile.c delete mode 100644 utils/x11authnames.c delete mode 100644 version.h delete mode 100644 x11disp.c diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 7ef7ce286..000000000 --- a/.gitignore +++ /dev/null @@ -1,567 +0,0 @@ -*.o -*.pyc -*.vcxproj -*.vcxproj.filters -.ninja_deps -.ninja_log -build.ninja -/CMakeCache.txt -CMakeFiles/ -Debug/ -Win32/ -cmake_install.cmake -/shipped.txt -*.dir/ -*.lib -.dirstamp -.deps -.DS_Store -/*.pdb -/*.ilk -/*.res -/*.RES -/*.pch -/*.rsp -/*.obj -/*.exe -/*.ncb -/*.plg -/*.dsw -/*.opt -/*.dsp -/*.sln -/*.tds -/*.td2 -/*.map -/MSVC -/*.log -/*.GID -/local -/Output -/pageant -/plink -/pscp -/psftp -/putty -/puttytel -/puttygen -/pterm -/puttyapp -/ptermapp -/psusan -/osxlaunch -/uppity -/psocks -/unix/PuTTY.app -/unix/Pterm.app -/fuzzterm -/testcrypt -/testsc -/testzlib -/cgtest -/scctest -/test_host_strfoo -/test_tree234 -/test_wildcard -/*.DSA -/*.RSA -/*.cnt -/*.hlp -/.bmake -/build.log -/build.out -/empty.h -Makefile -/compile -*.a -/charset/sbcsdat.c -/contrib/cygtermd/cygtermd.exe -/doc/*.html -/doc/*.txt -/doc/*.cnt -/doc/*.hlp -/doc/*.gid -/doc/*.GID -/doc/*.chm -/doc/*.log -/doc/*.1 -/doc/*.info -/doc/vstr.but -/doc/*.hhp -/doc/*.hhc -/doc/*.hhk -/doc/licence.but -/doc/copy.but -/icons/*.pam -/icons/*.png -/icons/*.ico -/icons/*.icns -/icons/*.xpm -/icons/*.c -/unix/empty.h -/unix/plink -/unix/pterm -/unix/putty -/unix/puttytel -/unix/psftp -/unix/pscp -/unix/puttygen -/unix/stamp-h1 -/unix/*.log -/unix/.deps -/windows/*.pdb -/windows/*.ilk -/windows/*.res -/windows/*.RES -/windows/*.pch -/windows/*.rsp -/windows/*.obj -/windows/*.exe -/windows/*.ncb -/windows/*.plg -/windows/*.dsw -/windows/*.opt -/windows/*.dsp -/windows/*.tds -/windows/*.td2 -/windows/*.map -/windows/*.rcpp -/windows/*.log -/windows/*.GID -/windows/local -/windows/Output -/windows/*.DSA -/windows/*.RSA -/windows/*.cnt -/windows/*.hlp -/windows/.bmake -/windows/*.sln -/windows/*.suo -/windows/*.msi -/windows/*.wixobj -/windows/*.wixpdb - - - -## VS Code entries to be on the safe side -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets - -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - -## I think a VS Code CMake extension added some out dir... -/out - -# -# -# I opened the project is VS and now we need this... -# -# - -# I think VS Code generated an out directory... -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.tlog -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - -# Visual Studio 6 workspace and project file (working project files containing files to include in project) -*.dsw -*.dsp - -# Visual Studio 6 technical files -*.ncb -*.aps - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# Visual Studio History (VSHistory) files -.vshistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -# Fody - auto-generated XML schema -FodyWeavers.xsd - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - -# JetBrains Rider -*.sln.iml \ No newline at end of file diff --git a/Buildscr b/Buildscr deleted file mode 100644 index 02de6432a..000000000 --- a/Buildscr +++ /dev/null @@ -1,331 +0,0 @@ -# -*- sh -*- -# Build script to construct a full distribution directory of PuTTY. - -module putty - -# Start by figuring out what our version information looks like. -# -# There are four classes of PuTTY build: -# - a release, which just has an X.YY version number -# - a prerelease, which has an X.YY version number, plus a date and -# version control commit id (and is considered to be 'almost' -# version X.YY) -# - a development snapshot, which just has a date and commit id -# - a custom build, which also has a date and commit id (but is -# labelled differently, to stress that development snapshots are -# built from the checked-in code by the automated nightly script -# whereas custom builds are made manually, perhaps from uncommitted -# changes, e.g. to send to a user for diagnostic or testing -# purposes). -# -# The four classes of build are triggered by invoking bob with -# different command-line variable definitions: -# -# - RELEASE=X.YY makes a release build -# - PRERELEASE=X.YY makes a prerelease build (combined with the build -# date and VCS info) -# - setting SNAPSHOT to any non-empty string makes a development -# snapshot -# - setting none of these makes a custom build. - -# If we need a date for our build, start by computing it in the -# various forms we need. $(Ndate) is the date in purely numeric form -# (YYYYMMDD); $(Date) is separated as YYYY-MM-DD; $(Days) is the -# number of days since the epoch. -ifeq "$(RELEASE)" "" set Ndate $(!builddate) -ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -pe 's/(....)(..)(..)/$$1-$$2-$$3/' > date -ifneq "$(Ndate)" "" read Date date -set Epoch 18293 # update this at every release -ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -ne 'use Time::Local; /(....)(..)(..)/ and print timegm(0,0,0,$$3,$$2-1,$$1) / 86400 - $(Epoch)' > days -ifneq "$(Ndate)" "" read Days days - -# For any non-release, we're going to need the number of the prior -# release, for putting in various places so as to get monotonic -# comparisons with the surrounding actual releases. -ifeq "$(RELEASE)" "" read Lastver putty/LATEST.VER - -# Set up the textual version strings for the docs build and installers. -# We have one of these including the word 'PuTTY', and one without, -# which are inconveniently capitalised differently. -ifneq "$(RELEASE)" "" set Puttytextver PuTTY release $(RELEASE) -ifneq "$(RELEASE)" "" set Textver Release $(RELEASE) -ifneq "$(PRERELEASE)" "" set Puttytextver PuTTY pre-release $(PRERELEASE):$(Date).$(vcsid) -ifneq "$(PRERELEASE)" "" set Textver Pre-release $(PRERELEASE):$(Date).$(vcsid) -ifneq "$(SNAPSHOT)" "" set Puttytextver PuTTY development snapshot $(Date).$(vcsid) -ifneq "$(SNAPSHOT)" "" set Textver Development snapshot $(Date).$(vcsid) -ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Puttytextver PuTTY custom build $(Date).$(vcsid) -ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Textver Custom build $(Date).$(vcsid) -in putty/doc do echo "\\\\versionid $(Puttytextver)" > version.but - -# Set up the version string for use in the SSH connection greeting. -# -# We use $(Ndate) rather than $(Date) in the pre-release string to -# make sure it's under 40 characters, which is a hard limit in the SSH -# protocol spec (and enforced by a compile-time assertion in -# version.c). -ifneq "$(RELEASE)" "" set Sshver -Release-$(RELEASE) -ifneq "$(PRERELEASE)" "" set Sshver -Prerelease-$(PRERELEASE):$(Ndate).$(vcsid) -ifneq "$(SNAPSHOT)" "" set Sshver -Snapshot-$(Date).$(vcsid) -ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Sshver -Custom-$(Date).$(vcsid) - -# Set up the filename suffix for the Unix source archive. -ifneq "$(RELEASE)" "" set Uxarcsuffix -$(RELEASE) -ifneq "$(PRERELEASE)" "" set Uxarcsuffix -$(PRERELEASE)~pre$(Ndate).$(vcsid) -ifneq "$(SNAPSHOT)" "" set Uxarcsuffix -$(Lastver)-$(Date).$(vcsid) -ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Uxarcsuffix -custom-$(Date).$(vcsid) - -# Set up the filenames for the Windows installers (minus extension, -# which goes on later). -ifneq "$(RELEASE)" "" set Isuffix $(RELEASE)-installer -ifneq "$(PRERELEASE)" "" set Isuffix $(PRERELEASE)-pre$(Ndate)-installer -ifneq "$(SNAPSHOT)" "" set Isuffix $(Date)-installer -ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" set Isuffix custom-$(Date)-installer -set Ifilename32 putty-$(Isuffix) -set Ifilename64 putty-64bit-$(Isuffix) -set Ifilenamea32 putty-arm32-$(Isuffix) -set Ifilenamea64 putty-arm64-$(Isuffix) - -# Set up the Windows version resource info, for both the installers and -# the individual programs. This must be a sequence of four 16-bit -# integers compared lexicographically, and we define it as follows: -# -# For release X.YY: X.YY.0.0 -# For a prerelease before the X.YY release: (X.YY-1).(DDDDD + 0x8000).0 -# For a devel snapshot after the X.YY release: X.YY.DDDDD.0 -# For a custom build: X.YY.DDDDD.1 -# -# where DDDDD is a representation of the build date, in the form of a -# number of days since an epoch date. The epoch is reset at every -# release (which, with 15 bits, gives us a comfortable 80-odd years -# before it becomes vital to make another release to reset the count -# :-). - -ifneq "$(RELEASE)" "" in . do echo $(RELEASE).0.0 > winver -ifneq "$(PRERELEASE)" "" in . do perl -e 'printf "%s.%d.0", $$ARGV[0], 0x8000+$$ARGV[1]' $(Lastver) $(Days) > winver -ifneq "$(SNAPSHOT)" "" in . do perl -e 'printf "%s.%d.0", $$ARGV[0], $$ARGV[1]' $(Lastver) $(Days) > winver -ifeq "$(RELEASE)$(PRERELEASE)$(SNAPSHOT)" "" in . do perl -e 'printf "%s.%d.1", $$ARGV[0], $$ARGV[1]' $(Lastver) $(Days) > winver -in . do perl -pe 'y!.!,!' winver > winvercommas -read Winver winver -read Winvercommas winvercommas - -# Write out a version.h that contains the real version number. -in putty do echo '/* Generated by automated build script */' > version.h -ifneq "$(RELEASE)" "" in putty do echo '$#define RELEASE $(RELEASE)' >> version.h -ifneq "$(PRERELEASE)" "" in putty do echo '$#define PRERELEASE $(PRERELEASE)' >> version.h -ifneq "$(SNAPSHOT)" "" in putty do echo '$#define SNAPSHOT' >> version.h -in putty do echo '$#define TEXTVER "$(Textver)"' >> version.h -in putty do echo '$#define SSHVER "$(Sshver)"' >> version.h -in putty do echo '$#define BINARY_VERSION $(Winvercommas)' >> version.h - -# In cmake/gitcommit.cmake, replace the default output "unavailable" -# with the commit string generated by bob, so that people rebuilding -# the source archive will still get a useful value. -in putty do sed -i '/set(DEFAULT_COMMIT/s/unavailable/$(vcsfullid)/' cmake/gitcommit.cmake - -in . do mkdir docbuild -in docbuild do cmake ../putty/doc -in docbuild do make -j$(nproc) VERBOSE=1 -in putty/doc do cp ../../docbuild/*.1 . -in putty/doc do cp ../../docbuild/puttydoc.txt . -in putty/doc do cp ../../docbuild/putty.chm . -in putty/doc do cp -r ../../docbuild/html . - -in putty do ./mksrcarc.sh -in putty do ./mkunxarc.sh '$(Uxarcsuffix)' - -delegate - -# Run the test suite, under self-delegation so that we don't leave any -# cruft lying around. This involves doing a build of the Unix tools -# (which is a useful double-check anyway to pick up any build failures) -in putty do cmake . -DCMAKE_C_COMPILER=clang -DCMAKE_C_FLAGS="-fsanitize=address -fsanitize=leak" -DSTRICT=ON -in putty do make -j$(nproc) VERBOSE=1 -in putty do python3 test/cryptsuite.py -enddelegate - -delegate - -# Also, test-build the Windows tools using MinGW. This is important if -# we want the MinGW build to carry on working, partly because of the -# chance of compiler compatibility issues, but mostly because MinGW's -# linker uses Unix-style library search semantics (once down the -# library list), and no other Windows toolchain we build with is that -# picky. So this ensures the Windows library structure continues to -# work in the most difficult circumstance we expect it to encounter. -in putty do cmake . -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-mingw.cmake -DSTRICT=ON -in putty do make -j$(nproc) VERBOSE=1 -enddelegate - -# Windowsify LICENCE, since it's going in the Windows installers. -in putty do perl -i~ -pe 'y/\015//d;s/$$/\015/' LICENCE - -# Some gratuitous theming for the MSI installer UI. -in putty/icons do make -j$(nproc) -in putty do ./windows/make_install_images.sh - -mkdir putty/windows/build32 -mkdir putty/windows/build64 -mkdir putty/windows/buildold -mkdir putty/windows/abuild32 -mkdir putty/windows/abuild64 - -# Build the binaries to go in the installers, in both 32- and 64-bit -# flavours. -# -# For the 32-bit ones, we set a subsystem version of 5.01, which -# allows the resulting files to still run on Windows XP. -in putty/windows/build32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DPUTTY_SUBSYSTEM_VERSION=5.01 -DSTRICT=ON -in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/build64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON -in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 - -# The cmake mechanism used to set the subsystem version is a bit of a -# bodge (it depends on knowing how cmake set up all its build command -# variables), so just in case it breaks in future, double-check we -# really did get the subsystem version we wanted. -in putty/windows/build32 do objdump -x putty.exe > exe-headers.txt -in putty/windows/build32 do grep -Ex 'MajorOSystemVersion[[:space:]]+5' exe-headers.txt -in putty/windows/build32 do grep -Ex 'MinorOSystemVersion[[:space:]]+1' exe-headers.txt -in putty/windows/build32 do grep -Ex 'MajorSubsystemVersion[[:space:]]+5' exe-headers.txt -in putty/windows/build32 do grep -Ex 'MinorSubsystemVersion[[:space:]]+1' exe-headers.txt - -# Build experimental Arm Windows binaries. -in putty/windows/abuild32 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a32) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON -in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/abuild64 with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl_a64) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DCMAKE_C_FLAGS_RELEASE="/MT /O2" -DSTRICT=ON -in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 - -# Make a list of the Windows binaries we're going to ship, so that we -# can sign them. -in putty/windows do for subdir in build32 abuild32 build64 abuild64; do sed "s!^!$$subdir/!" $$subdir/shipped.txt; done > to-sign.txt - -# Code-sign the Windows binaries, if the local bob config provides a -# script to do so in a cross-compiling way. We assume here that the -# script accepts an -i option to provide a 'more info' URL, an -# optional -n option to provide a program name, and an -N option to -# take the program name from an .exe's version resource, and that it -# can accept multiple .exe or .msi filename arguments and sign them -# all in place. -ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ $$(cat to-sign.txt) - -# Make a preliminary set of cryptographic checksums giving the hashes -# of these versions of the binaries. We'll make the rest below. -in putty do for hash in md5 sha1 sha256 sha512; do for dir_plat in "build32 w32" "build64 w64" "abuild32 wa32" "abuild64 wa64"; do set -- $$dir_plat; (cd windows/$$1 && $${hash}sum *.exe | sed 's!\( \+\)!\1'$$2'/!;s!$$! (installer version)!') >> $${hash}sums.installer; done; done - -# Build a WiX MSI installer, for each build flavour. -in putty/windows with wixonlinux do candle -arch x86 -dRealPlatform=x86 -dDllOk=yes -dBuilddir=build32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer32.msi -spdb -in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=x64 -dDllOk=yes -dBuilddir=build64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installer64.msi -spdb -in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm -dDllOk=no -dBuilddir=abuild32/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera32.msi -spdb -in putty/windows with wixonlinux do candle -arch x64 -dRealPlatform=Arm64 -dDllOk=no -dBuilddir=abuild64/ -dWinver="$(Winver)" -dPuttytextver="$(Puttytextver)" -dHelpFilePath="../../docbuild/putty.chm" installer.wxs && light -ext WixUIExtension -ext WixUtilExtension -sval installer.wixobj -o installera64.msi -spdb - -# Change the width field for our dialog background image so that it -# doesn't stretch across the whole dialog. (WiX's default one does; we -# replace it with a narrow one so that the text to the right of it -# shows up on system default background colour, meaning that -# high-contrast mode doesn't make the text white on white. But that -# means we also have to modify the width field, and there's nothing in -# WiX's source syntax to make that happen.) -# -# Also bodge the platform fields for the Windows on Arm installers, -# since WiX 3 doesn't understand Arm platform names itself. -in putty/windows do ./msifixup.py installer32.msi --dialog-bmp-width=123 -in putty/windows do ./msifixup.py installer64.msi --dialog-bmp-width=123 -in putty/windows do ./msifixup.py installera32.msi --dialog-bmp-width=123 --platform=Arm -in putty/windows do ./msifixup.py installera64.msi --dialog-bmp-width=123 --platform=Arm64 - -# Sign the Windows installers. -ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ -n "PuTTY Installer" installer32.msi installer64.msi installera32.msi installera64.msi - -# Build the standalone binaries, in both 32- and 64-bit flavours. -# These differ from the previous set in that they embed the help file. -in putty/windows/build32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm) -in putty/windows/build32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/build64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm) -in putty/windows/build64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/abuild32 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm) -in putty/windows/abuild32 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 -in putty/windows/abuild64 with cmake_at_least_3.20 do cmake . -DPUTTY_EMBEDDED_CHM_FILE=$$(realpath ../../../docbuild/putty.chm) -in putty/windows/abuild64 with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 - -# Build the 'old' binaries, which should still run on all 32-bit -# versions of Windows back to Win95 (but not Win32s). These link -# against Visual Studio 2003 libraries (the more modern versions -# assume excessively modern Win32 API calls to be available), specify -# a subsystem version of 4.0, and compile with /arch:IA32 to prevent -# the use of modern CPU features like MMX which older machines also -# might not have. -# -# There's no installer to go with these, so they must also embed the -# help file. -in putty/windows/buildold with cmake_at_least_3.20 do cmake ../.. -DCMAKE_TOOLCHAIN_FILE=$(cmake_toolchain_clangcl32_2003) -DCMAKE_BUILD_TYPE=Release -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DPUTTY_LINK_MAPS=ON -DSTRICT=ON -in putty/windows/buildold with cmake_at_least_3.20 do make -j$(nproc) VERBOSE=1 - -# Regenerate to-sign.txt with the 'old' binaries included. -in putty/windows do for subdir in build32 abuild32 build64 abuild64 buildold; do sed "s!^!$$subdir/!" $$subdir/shipped.txt; done > to-sign.txt - -# Code-sign the standalone versions of the binaries. -ifneq "$(cross_winsigncode)" "" in putty/windows do $(cross_winsigncode) -N -i https://www.chiark.greenend.org.uk/~sgtatham/putty/ $$(cat to-sign.txt) - -# Move the shipped (and signed) binaries into another directory to -# deliver them from, so that we omit testcrypt and its ilk. -in putty/windows do mkdir deliver -in putty/windows do for subdir in build32 abuild32 build64 abuild64 buildold; do mkdir deliver/$$subdir; done -in putty/windows do while read x; do mv $$x deliver/$$x; mv $$x.map deliver/$$x.map; done < to-sign.txt - -in putty/windows/deliver/buildold do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm -in putty/windows/deliver/build32 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm -in putty/windows/deliver/build64 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm -in putty/windows/deliver/abuild32 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm -in putty/windows/deliver/abuild64 do zip -k -j putty.zip `ls *.exe | grep -vxE '^(puttytel|pterm).exe'` ../../../docbuild/putty.chm -in docbuild/html do zip puttydoc.zip *.html - -# Deliver the actual PuTTY release directory into a subdir `putty'. -deliver putty/windows/deliver/buildold/*.exe putty/w32old/$@ -deliver putty/windows/deliver/buildold/putty.zip putty/w32old/$@ -deliver putty/windows/deliver/build32/*.exe putty/w32/$@ -deliver putty/windows/deliver/build32/putty.zip putty/w32/$@ -deliver putty/windows/deliver/build64/*.exe putty/w64/$@ -deliver putty/windows/deliver/build64/putty.zip putty/w64/$@ -deliver putty/windows/installer32.msi putty/w32/$(Ifilename32).msi -deliver putty/windows/installer64.msi putty/w64/$(Ifilename64).msi -deliver putty/windows/installera32.msi putty/wa32/$(Ifilenamea32).msi -deliver putty/windows/installera64.msi putty/wa64/$(Ifilenamea64).msi -deliver putty/windows/deliver/abuild32/*.exe putty/wa32/$@ -deliver putty/windows/deliver/abuild32/putty.zip putty/wa32/$@ -deliver putty/windows/deliver/abuild64/*.exe putty/wa64/$@ -deliver putty/windows/deliver/abuild64/putty.zip putty/wa64/$@ -deliver docbuild/html/puttydoc.zip putty/$@ -deliver docbuild/putty.chm putty/$@ -deliver docbuild/puttydoc.txt putty/$@ -deliver docbuild/html/*.html putty/htmldoc/$@ -deliver putty/putty-src.zip putty/$@ -deliver putty/*.tar.gz putty/$@ - -# Deliver the map files alongside the `proper' release deliverables. -deliver putty/windows/deliver/buildold/*.map maps/w32old/$@ -deliver putty/windows/deliver/build32/*.map maps/w32/$@ -deliver putty/windows/deliver/build64/*.map maps/w64/$@ -deliver putty/windows/deliver/abuild32/*.map maps/wa32/$@ -deliver putty/windows/deliver/abuild64/*.map maps/wa64/$@ - -# Deliver sign.sh, so that whoever has just built PuTTY (the -# snapshot scripts or me, depending) can conveniently sign it with -# whatever key they want. -deliver putty/sign.sh $@ - -# Create files of cryptographic checksums, which will be signed along -# with the files they verify. We've provided MD5 checksums for a -# while, but now MD5 is looking iffy, we're expanding our selection. -# -# Creating these files is most easily done in the destination -# directory, where all the files we're delivering are already in their -# final relative layout. -in . do pwd > builddir -read Builddir builddir -in-dest putty do a=`\find * -type f -print`; for hash in md5 sha1 sha256 sha512; do ($${hash}sum $$a; echo; cat $(Builddir)/putty/$${hash}sums.installer) > $${hash}sums; done - -# And construct .htaccess files. One in the top-level directory, -# setting the MIME types for Windows help files and providing an -# appropriate link to the source archive: -in-dest putty do echo "AddType application/octet-stream .chm" >> .htaccess -in-dest putty do set -- putty*.tar.gz; for k in '' .gpg; do echo RedirectMatch temp '(.*/)'putty.tar.gz$$k\$$ '$$1'"$$1$$k" >> .htaccess; done -# And one in each binary directory, providing links for the installers. -in-dest putty do for params in "w32 putty-installer" "w64 putty-64bit-installer" "wa32 putty-arm32-installer" "wa64 putty-arm64-installer"; do (set -- $$params; subdir=$$1; installername=$$2; cd $$subdir && for ext in msi exe; do set -- putty*installer.$$ext; if test -f $$1; then for k in '' .gpg; do echo RedirectMatch temp '(.*/)'$${installername}.$$ext$$k\$$ '$$1'"$$1$$k" >> .htaccess; done; fi; done); done diff --git a/Buildscr.cv b/Buildscr.cv deleted file mode 100644 index ab3107f2c..000000000 --- a/Buildscr.cv +++ /dev/null @@ -1,37 +0,0 @@ -# -*- sh -*- - -# Build script to scan PuTTY with the downloadable Coverity scanner -# and generate a tar file to upload to their open-source scanning -# service. - -module putty - -# Scan the Unix build, on a 64-bit system to differentiate as much as -# possible from the other scan of the cross-platform files. -delegate covscan64 - in putty do mkdir linbuild - in putty/linbuild do cmake .. - in putty/linbuild do cov-build --dir ../cov-int make -j$(nproc) VERBOSE=1 - in putty do tar czvf cov-int.tar.gz cov-int - return putty/cov-int.tar.gz -enddelegate - -# Scan the Windows build, by means of building with Winelib (since as -# of 2013-07-22, the Coverity Scan website doesn't offer a 32-bit -# Windows scanner for download). -delegate covscan32wine - in putty do tar xzvf cov-int.tar.gz - in putty do mkdir winbuild - in putty/winbuild do cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchain-winegcc.cmake - in putty/winbuild do cov-build --dir ../cov-int make -j$(nproc) VERBOSE=1 - in putty do tar czvf cov-int.tar.gz cov-int - return putty/cov-int.tar.gz -enddelegate - -# Provide the revision number as one of the build outputs, to make it -# easy to construct a curl upload command which will annotate it -# appropriately when uploaded. -in putty do echo $(vcsfullid) > revision.txt - -deliver putty/revision.txt $@ -deliver putty/cov-int.tar.gz $@ diff --git a/CHECKLST.txt b/CHECKLST.txt deleted file mode 100644 index 3d618cbd6..000000000 --- a/CHECKLST.txt +++ /dev/null @@ -1,263 +0,0 @@ -Checklists for PuTTY administrative procedures -============================================== - -Going into pre-release stabilisation ------------------------------------- - -When we begin to work towards a release and want to enable -pre-releases on the website: - - - Make a branch whose tip will be the current state of the - pre-release. Regardless of whether the branch is from main or - from a prior release branch, the name of the branch must now be in - the form 'pre-X.YZ', or else the website will fail to link to it - properly in gitweb and the build script will check out the wrong - thing. - - - Edit ~/adm/puttysnap.sh on my build machine to set $prerelver correctly. - - - Edit ~/adm/puttysnap.sh on the master machine to enable pre-release - builds, by changing the 'if false' to 'if true'. - - - Put the website into pre-release mode, by defining prerel_version() - in components/Base.mc to return the upcoming version number. Also - add a news announcement in components/news. (Previous naming - convention has been to name it in the form 'X.YZ-pre.mi'.) - -Things to do during the branch-stabilisation period: - - - Go through the source (including the documentation), and the - website, and review anything tagged with a comment containing the - word XXX-REVIEW-BEFORE-RELEASE. (Any such comments should state - clearly what needs to be done.) - - - Test the Unix build with Address Sanitiser. In particular, any - headline features for the release should get a workout with memory - checking enabled! - - - Test the Windows build with Address Sanitiser too (as of VS 2022). - + In the course of that, give a recent Windows pterm a try, to - make sure that still works. - - - Test building and running on old platforms: - + build on Debian stretch (containing CMake 3.7, the earliest - CMake we claim support for) - + build with all three major versions of GTK - + build the old-Windows binaries and test-run them on Win95 (PuTTY - proper even without WinSock2) - - - Check Coverity is happy. - - - Check the side-channel tester is happy. - - - Check all the non-SSH network backends still basically work. - -Making a release candidate build --------------------------------- - - - Make a directory to hold all the release paraphernalia. I usually - call it ~/src/putty/X.YZ (where X.YZ will stand throughout for the - version number). - - - Inside that directory, clone the PuTTY git repository to a - subdirectory ~/src/putty/X.YZ/putty. Here you can make release- - related commits and tags tentatively, and keep them out of the way - of any 'git push' you might still be doing in other checkouts. - - - Double-check that we have removed anything tagged with a comment - containing the words XXX-REMOVE-BEFORE-RELEASE or - XXX-REVIEW-BEFORE-RELEASE. ('git grep XXX-RE' should only show up - hits in this file itself.) - - - Now update the version numbers and the transcripts in the docs, by - checking out the release branch in the release-specific checkout - and running - - ./release.pl --version=X.YZ --setver - - Then check that the resulting automated git commit has updated the - version number in the following places: - - * putty/LATEST.VER - * putty/doc/plink.but - * putty/doc/pscp.but - - and also check that it has reset the definition of 'Epoch' in - Buildscr. - - - Make the release tag, pointing at the version-update commit we just - generated. - - - Make a release-candidate build from the release tag, and put the - build.out and build.log files somewhere safe. Normally I store - these inside the ~/src/putty/X.YZ directory, alongside the git - checkout at ~/src/putty/X.YZ/putty, so I'll sit in that checkout - directory and run a command like - - bob -o ../build-X.YZ-rcN.out -l ../build-X.YZ-rcN.log -c X.YZ . RELEASE=X.YZ - - This should generate a basically valid release directory as - `build-X.YZ-rcN.out/putty', and provide link maps and sign.sh - alongside that. - - - Double-check in build-X.YZ-rcN.log that the release was built from - the right git commit. - - - Make a preliminary gpg signature, but don't run the full release- - signing procedure. (We use the presence of a full set of GPG - signatures to distinguish _abandoned_ release candidates from the - one that ended up being the release.) In the 'build.X.YZ-rcN.out' - directory, run - sh sign.sh -r -p putty - which will generate a clearsigned file called - sha512sums-preliminary.gpg _outside_ the 'putty' subdirectory. - - - For my own safety, make the release candidate build read-only. - chmod -R a-w build-X.YZ-rcN.{out,log} - - - Now do some checking of the release binaries, and pass them to the - rest of the team to do some as well. Do at least these things: - * make sure they basically work - * check they report the right version number - * if there's any easily observable behaviour difference between - the release branch and main, arrange to observe it - * test that the Windows installer installs successfully - + on x86 and Arm, and test that putty.exe runs in both cases - * test that the Unix source tarball unpacks and builds - + on at least a reasonably current stable Linux distro, and - also try Debian sid - + test-build with all of GTK 1, 2 and 3 - + test-build with -DNOT_X_WINDOWS - * test that the Windows source builds with Visual Studio (just in - case there's an unguarded clangism that would prevent it) - * quick check of the outlying network protocols (Telnet, SUPDUP - etc) - * feed the release-candidate source to Coverity and make sure it - didn't turn up any last-minute problems - * make sure we have a clean run of testsc - * do some testing on a system with a completely clean slate (no - prior saved session data) - -Preparing to make the release ------------------------------ - - - Write a release announcement (basically a summary of the changes - since the last release). Check the draft version into the putty-aux - repository, so the whole team can help wordsmith it if they want to. - - - Update the website, in a local checkout: - * Write a release file in components/releases which identifies the - new version, a section for the Changes page, and a news - announcement for the front page. - + The one thing this can't yet contain is the release date; - that has to be put in at the last minute, when the release - goes live. Fill in 'FIXME', for the moment. - * Disable the pre-release sections of the website (if previously - enabled), by editing prerel_version() in components/Base.mc to - return undef. - - - Update the wishlist, in a local checkout: - * If there are any last-minute wishlist entries (e.g. security - vulnerabilities fixed in the new release), write entries for - them. - * If any other bug fixes have been cherry-picked to the release - branch (so that the wishlist mechanism can't automatically mark - them as fixed in the new release), add appropriate Fixed-in - headers for those. - - - Sign the release in full. In the `build-X.YZ-rcN.out' directory, - re-verify that the preliminary signed checksums file has a correct - signature on it and also matches the files you're about to sign for real: - - gpg -d sha512sums-preliminary.gpg | (cd putty; grep -vF ' (installer version)' | grep . | sha512sum -c) - - If the combined output of that pipeline reports both a good - signature (from the release key) and a successful verification of - all the sha512sums, then all is well and you can do the full - signing (not forgetting that the directory will have been readonly - during the last-minute testing period): - - chmod -R u+w putty - sh sign.sh -r putty # and enter the release key passphrase - chmod -R a-w putty - -The actual release procedure ----------------------------- - -Once all the above preparation is done and the release has been built -locally, this is the procedure for putting it up on the web. - - - Make a final adjustment to your local website changes, filling in - the release date in components/releases/X.YZ.mi. - - - Upload the release itself and its link maps to everywhere it needs - to be, by running this in the build-X.YZ-rcN.out directory: - ../putty/release.pl --version=X.YZ --upload - - - Check that downloads via version-numbered URLs all work: - ../putty/release.pl --version=X.YZ --precheck - - - Switch the 'latest' links over to the new release: - * Update the HTTP redirect at the:www/putty/htaccess . - - - Now verify that downloads via the 'latest' URLs are all redirected - correctly and work: - ../putty/release.pl --version=X.YZ --postcheck - - - If the release is on a branch (which I expect it generally will - be), merge that branch to main, so that the 'update version number' - change appears on main and the snapshots start announcing - themselves as post-X.YZ. - - - Push all the git repositories: - * run 'git push' in the website checkout - * run 'git push' in the wishlist checkout - * push from the main PuTTY checkout. Typically this one will be - pushing both the release tag and the merge we just made to the - main branch, plus removing the pre-release branch, so you'll - want some - commands along these lines: - git push origin main # update the main branch - git push origin --tags # should push the new release tag - git push origin :pre-X.YZ # delete the pre-release branch - - - Run ~/adm/puttyweb.sh on thyestes to update the website after all - those git pushes. - - - Check that the unpublished website on thyestes looks sensible. - - - Run webupdate, so that all the changes on thyestes propagate to - chiark. Important to do this _before_ announcing that the release - is available. - - - After running webupdate, run update-rsync on chiark and verify that - the rsync mirror package (~/ftp/putty-website-mirror) contains a - subdirectory for the new version and that the links from its - latest.html point into that subdirectory. - - - Start the process of updating our Windows Store entry: - + log into partner.microsoft.com and go to Partner Center - + start editing the existing app submission, which should - automatically create a new submission - * provide a new set of installer URLs, then click "save all" - which actually uploads them - * change the "what's new in this release" text in the store - listing - * upload revised screenshots, if necessary - + press Publish to submit that to the actual upload process - - - Announce the release! - + Construct a release announcement email whose message body is the - announcement written above, and which includes the following - headers: - * Reply-To: - * Subject: PuTTY X.YZ is released - + Mail that release announcement to - . - + Post it to comp.security.ssh. - + Mention it in on mono. - - - Edit the master ~/adm/puttysnap.sh to disable pre-release builds, - if they were previously enabled. - - - Relax (slightly). diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 8203e1cb1..000000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,158 +0,0 @@ -cmake_minimum_required(VERSION 3.7) -project(putty LANGUAGES C) - -include(cmake/setup.cmake) - -# Scan the docs directory first, so that when we start calling -# installed_program(), we'll know if we have man pages available -# -# PuTTYNG - AppVeyor has a bad time right here... -#add_subdirectory(doc) - -add_compile_definitions(HAVE_CMAKE_H) - -include_directories(terminal) - -add_library(utils STATIC - ${GENERATED_COMMIT_C}) -add_dependencies(utils cmake_commit_c) -add_subdirectory(utils) -add_subdirectory(stubs) - -add_library(logging OBJECT - logging.c) - -add_library(eventloop STATIC - callback.c timing.c) - -add_library(console STATIC - clicons.c console.c) - -add_library(settings STATIC - cmdline.c settings.c) - -add_library(crypto STATIC - proxy/cproxy.c proxy/sshproxy.c) -add_subdirectory(crypto) - -add_library(network STATIC - errsock.c logging.c x11disp.c - proxy/proxy.c - proxy/http.c - proxy/socks4.c - proxy/socks5.c - proxy/telnet.c - proxy/local.c - proxy/interactor.c) - -add_library(keygen STATIC - import.c) -add_subdirectory(keygen) - -add_library(agent STATIC - sshpubk.c pageant.c aqsync.c) - -add_library(guiterminal STATIC - terminal/terminal.c terminal/bidi.c - ldisc.c config.c dialog.c - $) - -add_library(noterminal STATIC - stubs/no-term.c ldisc.c) - -add_library(all-backends OBJECT - pinger.c) - -add_library(sftpclient STATIC - psftpcommon.c) -add_subdirectory(ssh) - -add_library(otherbackends STATIC - $ - $) -add_subdirectory(otherbackends) - -add_executable(testcrypt - test/testcrypt.c sshpubk.c ssh/crc-attack-detector.c) -target_link_libraries(testcrypt - keygen crypto utils ${platform_libraries}) - -add_executable(test_host_strfoo - utils/host_strchr_internal.c) -target_compile_definitions(test_host_strfoo PRIVATE TEST) -target_link_libraries(test_host_strfoo utils ${platform_libraries}) - -add_executable(test_decode_utf8 - utils/decode_utf8.c) -target_compile_definitions(test_decode_utf8 PRIVATE TEST) -target_link_libraries(test_decode_utf8 utils ${platform_libraries}) - -add_executable(test_tree234 - utils/tree234.c) -target_compile_definitions(test_tree234 PRIVATE TEST) -target_link_libraries(test_tree234 utils ${platform_libraries}) - -add_executable(test_wildcard - utils/wildcard.c) -target_compile_definitions(test_wildcard PRIVATE TEST) -target_link_libraries(test_wildcard utils ${platform_libraries}) - -add_executable(test_cert_expr - utils/cert-expr.c) -target_compile_definitions(test_cert_expr PRIVATE TEST) -target_link_libraries(test_cert_expr utils ${platform_libraries}) - -add_executable(bidi_gettype - terminal/bidi_gettype.c) -target_link_libraries(bidi_gettype guiterminal utils ${platform_libraries}) - -add_executable(bidi_test - terminal/bidi_test.c) -target_link_libraries(bidi_test guiterminal utils ${platform_libraries}) - -add_executable(plink - ${platform}/plink.c) -# Note: if we ever port Plink to a platform where we can't implement a -# serial backend, this be_list command will need to become platform- -# dependent, so that it only sets the SERIAL option on platforms where -# that backend exists. For the moment, though, we have serial port -# backends for both our platforms, so we can do this unconditionally. -be_list(plink Plink SSH SERIAL OTHERBACKENDS) -target_link_libraries(plink - eventloop noterminal console sshclient otherbackends settings network crypto - utils - ${platform_libraries}) -installed_program(plink) - -add_executable(pscp - pscp.c) -be_list(pscp PSCP SSH) -target_link_libraries(pscp - sftpclient eventloop console sshclient settings network crypto utils - ${platform_libraries}) -installed_program(pscp) - -add_executable(psftp - psftp.c) -be_list(psftp PSFTP SSH) -target_link_libraries(psftp - sftpclient eventloop console sshclient settings network crypto utils - ${platform_libraries}) -installed_program(psftp) - -add_executable(psocks - ${platform}/psocks.c - psocks.c - stubs/no-rand.c - proxy/nocproxy.c - proxy/nosshproxy.c - ssh/portfwd.c) -target_link_libraries(psocks - eventloop console network utils - ${platform_libraries}) - -foreach(subdir ${platform} ${extra_dirs}) - add_subdirectory(${subdir}) -endforeach() - -configure_file(cmake/cmake.h.in ${GENERATED_SOURCES_DIR}/cmake.h) diff --git a/LATEST.VER b/LATEST.VER deleted file mode 100644 index 95d2ff574..000000000 --- a/LATEST.VER +++ /dev/null @@ -1 +0,0 @@ -0.78 diff --git a/LICENCE b/LICENCE deleted file mode 100644 index 57ea292be..000000000 --- a/LICENCE +++ /dev/null @@ -1,28 +0,0 @@ -PuTTY is copyright 1997-2022 Simon Tatham. - -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, Christopher Staite, Lorenz Diener, Christian -Brabandt, Jeff Smith, Pavel Kryukov, Maxim Kuznetsov, Svyatoslav -Kuzmich, Nico Williams, Viktor Dukhovni, Josh Dersch, Lars Brinkhoff, -and CORE SDI S.A. - -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 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. diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index faa2af43f..000000000 --- a/Makefile.am +++ /dev/null @@ -1,459 +0,0 @@ -# Makefile.am for putty under Unix with Autoconf/Automake. -# -# This file was created by `mkfiles.pl' from the `Recipe' file. -# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead. - -AUTOMAKE_OPTIONS = subdir-objects - -allsources = agentf.c aqsync.c be_all_s.c be_misc.c be_none.c be_nos_s.c \ - be_ssh.c callback.c cgtest.c charset/charset.h \ - charset/enum.c charset/fromucs.c charset/internal.h \ - charset/localenc.c charset/macenc.c charset/mimeenc.c \ - charset/sbcs.c charset/sbcsdat.c charset/slookup.c \ - charset/toucs.c charset/utf8.c charset/xenc.c clicons.c \ - cmdgen.c cmdline.c conf.c config.c console.c console.h \ - cproxy.c defs.h dialog.c dialog.h ecc.c ecc.h errsock.c \ - fuzzterm.c import.c ldisc.c ldisc.h licence.h logging.c \ - mainchan.c marshal.c marshal.h memory.c millerrabin.c \ - minibidi.c misc.c misc.h miscucs.c mpint.c mpint.h mpint_i.h \ - mpunsafe.c mpunsafe.h network.h nocmdline.c nocproxy.c \ - nogss.c norand.c noshare.c noterm.c notiming.c nullplug.c \ - pageant.c pageant.h pgssapi.c pgssapi.h pinger.c pockle.c \ - portfwd.c primecandidate.c proxy.c proxy.h pscp.c psftp.c \ - psftp.h psftpcommon.c psocks.c psocks.h putty.h puttymem.h \ - puttyps.h raw.c rlogin.c scpserver.c sesschan.c sessprep.c \ - settings.c sftp.c sftp.h sftpcommon.c sftpserver.c \ - smallprimes.c ssh.c ssh.h ssh1bpp.c ssh1censor.c \ - ssh1connection-client.c ssh1connection-server.c \ - ssh1connection.c ssh1connection.h ssh1login-server.c \ - ssh1login.c ssh2bpp-bare.c ssh2bpp.c ssh2censor.c \ - ssh2connection-client.c ssh2connection-server.c \ - ssh2connection.c ssh2connection.h ssh2kex-client.c \ - ssh2kex-server.c ssh2transhk.c ssh2transport.c \ - ssh2transport.h ssh2userauth-server.c ssh2userauth.c \ - sshaes.c ssharcf.c sshargon2.c sshauxcrypt.c sshbcrypt.c \ - sshblake2.c sshblowf.c sshblowf.h sshbpp.h sshccp.c \ - sshchan.h sshcommon.c sshcr.h sshcrc.c sshcrcda.c sshdes.c \ - sshdh.c sshdss.c sshdssg.c sshecc.c sshecdsag.c sshgss.h \ - sshgssc.c sshgssc.h sshhmac.c sshkeygen.h sshmac.c sshmd5.c \ - sshppl.h sshprime.c sshprng.c sshpubk.c sshrand.c sshrsa.c \ - sshrsag.c sshserver.c sshserver.h sshsh256.c sshsh512.c \ - sshsha.c sshsha3.c sshshare.c sshsignals.h sshttymodes.h \ - sshutils.c sshverstring.c sshzlib.c storage.h stripctrl.c \ - supdup.c telnet.c terminal.c terminal.h testcrypt.c \ - testcrypt.h testsc.c testzlib.c time.c timing.c tree234.c \ - tree234.h unix/gtkapp.c unix/gtkask.c unix/gtkcfg.c \ - unix/gtkcols.c unix/gtkcols.h unix/gtkcomm.c \ - unix/gtkcompat.h unix/gtkdlg.c unix/gtkfont.c unix/gtkfont.h \ - unix/gtkmain.c unix/gtkmisc.c unix/gtkmisc.h unix/gtkwin.c \ - unix/osxlaunch.c unix/procnet.c unix/unix.h unix/ux_x11.c \ - unix/uxagentc.c unix/uxagentsock.c unix/uxcfg.c \ - unix/uxcliloop.c unix/uxcons.c unix/uxfdsock.c unix/uxgen.c \ - unix/uxgss.c unix/uxmisc.c unix/uxnet.c unix/uxnogtk.c \ - unix/uxnoise.c unix/uxpeer.c unix/uxpgnt.c unix/uxplink.c \ - unix/uxpoll.c unix/uxprint.c unix/uxproxy.c unix/uxpsusan.c \ - unix/uxpterm.c unix/uxpty.c unix/uxputty.c unix/uxsel.c \ - unix/uxser.c unix/uxserver.c unix/uxsftp.c \ - unix/uxsftpserver.c unix/uxshare.c unix/uxsignal.c \ - unix/uxsocks.c unix/uxstore.c unix/uxucs.c unix/uxutils.c \ - unix/uxutils.h unix/x11misc.c unix/x11misc.h unix/xkeysym.c \ - unix/xpmptcfg.c unix/xpmpterm.c unix/xpmpucfg.c \ - unix/xpmputty.c utils.c version.c version.h wcwidth.c \ - wildcard.c windows/pageant-rc.h windows/pageant.rc \ - windows/plink.rc windows/pscp.rc windows/psftp.rc \ - windows/putty.rc windows/puttygen-rc.h windows/puttygen.rc \ - windows/puttytel.rc windows/rcstuff.h windows/sizetip.c \ - windows/version.rc2 windows/win_res.h windows/win_res.rc2 \ - windows/wincapi.c windows/wincapi.h windows/wincfg.c \ - windows/wincliloop.c windows/wincons.c windows/winctrls.c \ - windows/windefs.c windows/windlg.c windows/window.c \ - windows/wingss.c windows/winhandl.c windows/winhelp.c \ - windows/winhelp.h windows/winhelp.rc2 windows/winhsock.c \ - windows/winjump.c windows/winmisc.c windows/winmiscs.c \ - windows/winnet.c windows/winnohlp.c windows/winnoise.c \ - windows/winnojmp.c windows/winnpc.c windows/winnps.c \ - windows/winpgen.c windows/winpgnt.c windows/winpgntc.c \ - windows/winplink.c windows/winprint.c windows/winproxy.c \ - windows/winseat.h windows/winsecur.c windows/winsecur.h \ - windows/winselcli.c windows/winselgui.c windows/winser.c \ - windows/winsftp.c windows/winshare.c windows/winsocks.c \ - windows/winstore.c windows/winstuff.h windows/wintime.c \ - windows/winucs.c windows/winutils.c windows/winx11.c \ - x11fwd.c - -if HAVE_GTK -bin_PROGRAMS = plink pscp psftp psusan puttygen pageant pterm putty puttytel -else -bin_PROGRAMS = plink pscp psftp psusan puttygen -endif - -if HAVE_GTK -noinst_PROGRAMS = cgtest fuzzterm osxlaunch psocks testcrypt testsc testzlib \ - uppity ptermapp puttyapp -else -noinst_PROGRAMS = cgtest fuzzterm osxlaunch psocks testcrypt testsc testzlib \ - uppity -endif - -AM_CPPFLAGS = -I$(srcdir)/./ -I$(srcdir)/charset/ -I$(srcdir)/windows/ \ - -I$(srcdir)/unix/ -if HAVE_GTK -AM_CFLAGS = $(GTK_CFLAGS) $(COMPAT) $(XFLAGS) $(WARNINGOPTS) -else -AM_CFLAGS = $(COMPAT) $(XFLAGS) $(WARNINGOPTS) -endif - -libversion_a_SOURCES = version.c -libversion_a_CFLAGS = $(COMPAT) $(XFLAGS) $(WARNINGOPTS) -noinst_LIBRARIES = libversion.a - -cgtest_SOURCES = cgtest.c conf.c console.c ecc.c import.c marshal.c memory.c \ - millerrabin.c misc.c mpint.c mpunsafe.c notiming.c pockle.c \ - primecandidate.c smallprimes.c sshaes.c sshargon2.c \ - sshauxcrypt.c sshbcrypt.c sshblake2.c sshblowf.c sshdes.c \ - sshdss.c sshdssg.c sshecc.c sshecdsag.c sshhmac.c sshmd5.c \ - sshprime.c sshprng.c sshpubk.c sshrand.c sshrsa.c sshrsag.c \ - sshsh256.c sshsh512.c sshsha.c sshsha3.c stripctrl.c time.c \ - tree234.c unix/uxcons.c unix/uxgen.c unix/uxmisc.c \ - unix/uxnogtk.c unix/uxnoise.c unix/uxpoll.c unix/uxstore.c \ - unix/uxutils.c utils.c wcwidth.c -cgtest_LDADD = libversion.a - -fuzzterm_SOURCES = be_none.c callback.c charset/fromucs.c charset/localenc.c \ - charset/macenc.c charset/mimeenc.c charset/sbcs.c \ - charset/sbcsdat.c charset/slookup.c charset/toucs.c \ - charset/utf8.c charset/xenc.c conf.c config.c dialog.c \ - fuzzterm.c logging.c marshal.c memory.c minibidi.c misc.c \ - miscucs.c settings.c stripctrl.c terminal.c time.c timing.c \ - tree234.c unix/uxcfg.c unix/uxmisc.c unix/uxnogtk.c \ - unix/uxprint.c unix/uxstore.c unix/uxucs.c utils.c wcwidth.c -fuzzterm_LDADD = libversion.a - -osxlaunch_SOURCES = unix/osxlaunch.c - -if HAVE_GTK -pageant_SOURCES = aqsync.c be_misc.c be_none.c callback.c conf.c console.c \ - ecc.c errsock.c logging.c marshal.c memory.c misc.c mpint.c \ - nocproxy.c nogss.c nullplug.c pageant.c proxy.c settings.c \ - sshaes.c sshargon2.c sshauxcrypt.c sshblake2.c sshdes.c \ - sshdss.c sshecc.c sshhmac.c sshmd5.c sshprng.c sshpubk.c \ - sshrsa.c sshsh256.c sshsh512.c sshsha.c sshsha3.c \ - stripctrl.c time.c timing.c tree234.c unix/gtkask.c \ - unix/gtkmisc.c unix/ux_x11.c unix/uxagentc.c \ - unix/uxagentsock.c unix/uxcliloop.c unix/uxcons.c \ - unix/uxfdsock.c unix/uxmisc.c unix/uxnet.c unix/uxnoise.c \ - unix/uxpeer.c unix/uxpgnt.c unix/uxpoll.c unix/uxproxy.c \ - unix/uxsel.c unix/uxsignal.c unix/uxstore.c unix/uxutils.c \ - utils.c wcwidth.c x11fwd.c -pageant_LDADD = libversion.a $(GTK_LIBS) -endif - -plink_SOURCES = agentf.c aqsync.c be_all_s.c be_misc.c callback.c clicons.c \ - cmdline.c conf.c console.c cproxy.c ecc.c errsock.c ldisc.c \ - logging.c mainchan.c marshal.c memory.c misc.c mpint.c \ - noterm.c nullplug.c pgssapi.c pinger.c portfwd.c proxy.c \ - raw.c rlogin.c sessprep.c settings.c ssh.c ssh1bpp.c \ - ssh1censor.c ssh1connection-client.c ssh1connection.c \ - ssh1login.c ssh2bpp-bare.c ssh2bpp.c ssh2censor.c \ - ssh2connection-client.c ssh2connection.c ssh2kex-client.c \ - ssh2transhk.c ssh2transport.c ssh2userauth.c sshaes.c \ - ssharcf.c sshargon2.c sshauxcrypt.c sshblake2.c sshblowf.c \ - sshccp.c sshcommon.c sshcrc.c sshcrcda.c sshdes.c sshdh.c \ - sshdss.c sshecc.c sshgssc.c sshhmac.c sshmac.c sshmd5.c \ - sshprng.c sshpubk.c sshrand.c sshrsa.c sshsh256.c sshsh512.c \ - sshsha.c sshsha3.c sshshare.c sshutils.c sshverstring.c \ - sshzlib.c stripctrl.c supdup.c telnet.c time.c timing.c \ - tree234.c unix/ux_x11.c unix/uxagentc.c unix/uxcliloop.c \ - unix/uxcons.c unix/uxfdsock.c unix/uxgss.c unix/uxmisc.c \ - unix/uxnet.c unix/uxnogtk.c unix/uxnoise.c unix/uxpeer.c \ - unix/uxplink.c unix/uxpoll.c unix/uxproxy.c unix/uxsel.c \ - unix/uxser.c unix/uxshare.c unix/uxsignal.c unix/uxstore.c \ - unix/uxutils.c utils.c wcwidth.c wildcard.c x11fwd.c -plink_LDADD = libversion.a - -pscp_SOURCES = agentf.c aqsync.c be_misc.c be_ssh.c callback.c clicons.c \ - cmdline.c conf.c console.c cproxy.c ecc.c errsock.c \ - logging.c mainchan.c marshal.c memory.c misc.c mpint.c \ - nullplug.c pgssapi.c pinger.c portfwd.c proxy.c pscp.c \ - psftpcommon.c settings.c sftp.c sftpcommon.c ssh.c ssh1bpp.c \ - ssh1censor.c ssh1connection-client.c ssh1connection.c \ - ssh1login.c ssh2bpp-bare.c ssh2bpp.c ssh2censor.c \ - ssh2connection-client.c ssh2connection.c ssh2kex-client.c \ - ssh2transhk.c ssh2transport.c ssh2userauth.c sshaes.c \ - ssharcf.c sshargon2.c sshauxcrypt.c sshblake2.c sshblowf.c \ - sshccp.c sshcommon.c sshcrc.c sshcrcda.c sshdes.c sshdh.c \ - sshdss.c sshecc.c sshgssc.c sshhmac.c sshmac.c sshmd5.c \ - sshprng.c sshpubk.c sshrand.c sshrsa.c sshsh256.c sshsh512.c \ - sshsha.c sshsha3.c sshshare.c sshutils.c sshverstring.c \ - sshzlib.c stripctrl.c time.c timing.c tree234.c \ - unix/uxagentc.c unix/uxcliloop.c unix/uxcons.c \ - unix/uxfdsock.c unix/uxgss.c unix/uxmisc.c unix/uxnet.c \ - unix/uxnogtk.c unix/uxnoise.c unix/uxpeer.c unix/uxpoll.c \ - unix/uxproxy.c unix/uxsel.c unix/uxsftp.c unix/uxshare.c \ - unix/uxstore.c unix/uxutils.c utils.c wcwidth.c wildcard.c \ - x11fwd.c -pscp_LDADD = libversion.a - -psftp_SOURCES = agentf.c aqsync.c be_misc.c be_ssh.c callback.c clicons.c \ - cmdline.c conf.c console.c cproxy.c ecc.c errsock.c \ - logging.c mainchan.c marshal.c memory.c misc.c mpint.c \ - nullplug.c pgssapi.c pinger.c portfwd.c proxy.c psftp.c \ - psftpcommon.c settings.c sftp.c sftpcommon.c ssh.c ssh1bpp.c \ - ssh1censor.c ssh1connection-client.c ssh1connection.c \ - ssh1login.c ssh2bpp-bare.c ssh2bpp.c ssh2censor.c \ - ssh2connection-client.c ssh2connection.c ssh2kex-client.c \ - ssh2transhk.c ssh2transport.c ssh2userauth.c sshaes.c \ - ssharcf.c sshargon2.c sshauxcrypt.c sshblake2.c sshblowf.c \ - sshccp.c sshcommon.c sshcrc.c sshcrcda.c sshdes.c sshdh.c \ - sshdss.c sshecc.c sshgssc.c sshhmac.c sshmac.c sshmd5.c \ - sshprng.c sshpubk.c sshrand.c sshrsa.c sshsh256.c sshsh512.c \ - sshsha.c sshsha3.c sshshare.c sshutils.c sshverstring.c \ - sshzlib.c stripctrl.c time.c timing.c tree234.c \ - unix/uxagentc.c unix/uxcliloop.c unix/uxcons.c \ - unix/uxfdsock.c unix/uxgss.c unix/uxmisc.c unix/uxnet.c \ - unix/uxnogtk.c unix/uxnoise.c unix/uxpeer.c unix/uxpoll.c \ - unix/uxproxy.c unix/uxsel.c unix/uxsftp.c unix/uxshare.c \ - unix/uxstore.c unix/uxutils.c utils.c wcwidth.c wildcard.c \ - x11fwd.c -psftp_LDADD = libversion.a - -psocks_SOURCES = be_misc.c callback.c conf.c console.c errsock.c logging.c \ - marshal.c memory.c misc.c nocproxy.c norand.c portfwd.c \ - proxy.c psocks.c sshutils.c stripctrl.c time.c timing.c \ - tree234.c unix/uxcliloop.c unix/uxcons.c unix/uxfdsock.c \ - unix/uxmisc.c unix/uxnet.c unix/uxnogtk.c unix/uxpeer.c \ - unix/uxpoll.c unix/uxproxy.c unix/uxsel.c unix/uxsignal.c \ - unix/uxsocks.c utils.c wcwidth.c -psocks_LDADD = libversion.a - -psusan_SOURCES = be_misc.c be_none.c callback.c conf.c cproxy.c ecc.c \ - errsock.c logging.c marshal.c memory.c millerrabin.c misc.c \ - mpint.c mpunsafe.c nogss.c nullplug.c pgssapi.c pockle.c \ - portfwd.c primecandidate.c proxy.c scpserver.c sesschan.c \ - settings.c sftpcommon.c sftpserver.c smallprimes.c ssh1bpp.c \ - ssh1censor.c ssh1connection-server.c ssh1connection.c \ - ssh1login-server.c ssh2bpp-bare.c ssh2bpp.c ssh2censor.c \ - ssh2connection-server.c ssh2connection.c ssh2kex-server.c \ - ssh2transhk.c ssh2transport.c ssh2userauth-server.c sshaes.c \ - ssharcf.c sshargon2.c sshauxcrypt.c sshblake2.c sshblowf.c \ - sshccp.c sshcommon.c sshcrc.c sshcrcda.c sshdes.c sshdh.c \ - sshdss.c sshecc.c sshgssc.c sshhmac.c sshmac.c sshmd5.c \ - sshprime.c sshprng.c sshpubk.c sshrand.c sshrsa.c sshrsag.c \ - sshserver.c sshsh256.c sshsh512.c sshsha.c sshsha3.c \ - sshutils.c sshverstring.c sshzlib.c stripctrl.c time.c \ - timing.c tree234.c unix/procnet.c unix/ux_x11.c \ - unix/uxagentsock.c unix/uxcliloop.c unix/uxfdsock.c \ - unix/uxmisc.c unix/uxnet.c unix/uxnogtk.c unix/uxnoise.c \ - unix/uxpeer.c unix/uxpoll.c unix/uxproxy.c unix/uxpsusan.c \ - unix/uxpty.c unix/uxsel.c unix/uxsftpserver.c \ - unix/uxsignal.c unix/uxstore.c unix/uxutils.c utils.c \ - wcwidth.c wildcard.c x11fwd.c -psusan_LDADD = libversion.a - -if HAVE_GTK -pterm_SOURCES = be_none.c callback.c charset/fromucs.c charset/localenc.c \ - charset/macenc.c charset/mimeenc.c charset/sbcs.c \ - charset/sbcsdat.c charset/slookup.c charset/toucs.c \ - charset/utf8.c charset/xenc.c cmdline.c conf.c config.c \ - dialog.c ldisc.c logging.c marshal.c memory.c minibidi.c \ - misc.c miscucs.c nocproxy.c nogss.c sessprep.c settings.c \ - stripctrl.c terminal.c time.c timing.c tree234.c \ - unix/gtkcfg.c unix/gtkcols.c unix/gtkcomm.c unix/gtkdlg.c \ - unix/gtkfont.c unix/gtkmain.c unix/gtkmisc.c unix/gtkwin.c \ - unix/uxcfg.c unix/uxmisc.c unix/uxprint.c unix/uxpterm.c \ - unix/uxpty.c unix/uxsel.c unix/uxsignal.c unix/uxstore.c \ - unix/uxucs.c unix/x11misc.c unix/xkeysym.c unix/xpmptcfg.c \ - unix/xpmpterm.c utils.c wcwidth.c -pterm_LDADD = libversion.a $(GTK_LIBS) -endif - -if HAVE_GTK -ptermapp_SOURCES = be_none.c callback.c charset/fromucs.c charset/localenc.c \ - charset/macenc.c charset/mimeenc.c charset/sbcs.c \ - charset/sbcsdat.c charset/slookup.c charset/toucs.c \ - charset/utf8.c charset/xenc.c conf.c config.c dialog.c \ - ldisc.c logging.c marshal.c memory.c minibidi.c misc.c \ - miscucs.c nocmdline.c nocproxy.c nogss.c sessprep.c \ - settings.c stripctrl.c terminal.c time.c timing.c tree234.c \ - unix/gtkapp.c unix/gtkcfg.c unix/gtkcols.c unix/gtkcomm.c \ - unix/gtkdlg.c unix/gtkfont.c unix/gtkmisc.c unix/gtkwin.c \ - unix/uxcfg.c unix/uxmisc.c unix/uxprint.c unix/uxpterm.c \ - unix/uxpty.c unix/uxsel.c unix/uxsignal.c unix/uxstore.c \ - unix/uxucs.c unix/x11misc.c unix/xkeysym.c unix/xpmptcfg.c \ - unix/xpmpterm.c utils.c wcwidth.c -ptermapp_LDADD = libversion.a $(GTK_LIBS) -endif - -if HAVE_GTK -putty_SOURCES = agentf.c aqsync.c be_all_s.c be_misc.c callback.c \ - charset/fromucs.c charset/localenc.c charset/macenc.c \ - charset/mimeenc.c charset/sbcs.c charset/sbcsdat.c \ - charset/slookup.c charset/toucs.c charset/utf8.c \ - charset/xenc.c cmdline.c conf.c config.c cproxy.c dialog.c \ - ecc.c errsock.c ldisc.c logging.c mainchan.c marshal.c \ - memory.c minibidi.c misc.c miscucs.c mpint.c nullplug.c \ - pgssapi.c pinger.c portfwd.c proxy.c raw.c rlogin.c \ - sessprep.c settings.c ssh.c ssh1bpp.c ssh1censor.c \ - ssh1connection-client.c ssh1connection.c ssh1login.c \ - ssh2bpp-bare.c ssh2bpp.c ssh2censor.c \ - ssh2connection-client.c ssh2connection.c ssh2kex-client.c \ - ssh2transhk.c ssh2transport.c ssh2userauth.c sshaes.c \ - ssharcf.c sshargon2.c sshauxcrypt.c sshblake2.c sshblowf.c \ - sshccp.c sshcommon.c sshcrc.c sshcrcda.c sshdes.c sshdh.c \ - sshdss.c sshecc.c sshgssc.c sshhmac.c sshmac.c sshmd5.c \ - sshprng.c sshpubk.c sshrand.c sshrsa.c sshsh256.c sshsh512.c \ - sshsha.c sshsha3.c sshshare.c sshutils.c sshverstring.c \ - sshzlib.c stripctrl.c supdup.c telnet.c terminal.c time.c \ - timing.c tree234.c unix/gtkcfg.c unix/gtkcols.c \ - unix/gtkcomm.c unix/gtkdlg.c unix/gtkfont.c unix/gtkmain.c \ - unix/gtkmisc.c unix/gtkwin.c unix/ux_x11.c unix/uxagentc.c \ - unix/uxcfg.c unix/uxfdsock.c unix/uxgss.c unix/uxmisc.c \ - unix/uxnet.c unix/uxnoise.c unix/uxpeer.c unix/uxpoll.c \ - unix/uxprint.c unix/uxproxy.c unix/uxputty.c unix/uxsel.c \ - unix/uxser.c unix/uxshare.c unix/uxsignal.c unix/uxstore.c \ - unix/uxucs.c unix/uxutils.c unix/x11misc.c unix/xkeysym.c \ - unix/xpmpucfg.c unix/xpmputty.c utils.c wcwidth.c wildcard.c \ - x11fwd.c -putty_LDADD = libversion.a $(GTK_LIBS) -endif - -if HAVE_GTK -puttyapp_SOURCES = agentf.c aqsync.c be_all_s.c be_misc.c callback.c \ - charset/fromucs.c charset/localenc.c charset/macenc.c \ - charset/mimeenc.c charset/sbcs.c charset/sbcsdat.c \ - charset/slookup.c charset/toucs.c charset/utf8.c \ - charset/xenc.c conf.c config.c cproxy.c dialog.c ecc.c \ - errsock.c ldisc.c logging.c mainchan.c marshal.c memory.c \ - minibidi.c misc.c miscucs.c mpint.c nocmdline.c nullplug.c \ - pgssapi.c pinger.c portfwd.c proxy.c raw.c rlogin.c \ - sessprep.c settings.c ssh.c ssh1bpp.c ssh1censor.c \ - ssh1connection-client.c ssh1connection.c ssh1login.c \ - ssh2bpp-bare.c ssh2bpp.c ssh2censor.c \ - ssh2connection-client.c ssh2connection.c ssh2kex-client.c \ - ssh2transhk.c ssh2transport.c ssh2userauth.c sshaes.c \ - ssharcf.c sshargon2.c sshauxcrypt.c sshblake2.c sshblowf.c \ - sshccp.c sshcommon.c sshcrc.c sshcrcda.c sshdes.c sshdh.c \ - sshdss.c sshecc.c sshgssc.c sshhmac.c sshmac.c sshmd5.c \ - sshprng.c sshpubk.c sshrand.c sshrsa.c sshsh256.c sshsh512.c \ - sshsha.c sshsha3.c sshshare.c sshutils.c sshverstring.c \ - sshzlib.c stripctrl.c supdup.c telnet.c terminal.c time.c \ - timing.c tree234.c unix/gtkapp.c unix/gtkcfg.c \ - unix/gtkcols.c unix/gtkcomm.c unix/gtkdlg.c unix/gtkfont.c \ - unix/gtkmisc.c unix/gtkwin.c unix/ux_x11.c unix/uxagentc.c \ - unix/uxcfg.c unix/uxfdsock.c unix/uxgss.c unix/uxmisc.c \ - unix/uxnet.c unix/uxnoise.c unix/uxpeer.c unix/uxpoll.c \ - unix/uxprint.c unix/uxproxy.c unix/uxputty.c unix/uxsel.c \ - unix/uxser.c unix/uxshare.c unix/uxsignal.c unix/uxstore.c \ - unix/uxucs.c unix/uxutils.c unix/x11misc.c unix/xkeysym.c \ - unix/xpmpucfg.c unix/xpmputty.c utils.c wcwidth.c wildcard.c \ - x11fwd.c -puttyapp_LDADD = libversion.a $(GTK_LIBS) -endif - -puttygen_SOURCES = cmdgen.c conf.c console.c ecc.c import.c marshal.c \ - memory.c millerrabin.c misc.c mpint.c mpunsafe.c notiming.c \ - pockle.c primecandidate.c smallprimes.c sshaes.c sshargon2.c \ - sshauxcrypt.c sshbcrypt.c sshblake2.c sshblowf.c sshdes.c \ - sshdss.c sshdssg.c sshecc.c sshecdsag.c sshhmac.c sshmd5.c \ - sshprime.c sshprng.c sshpubk.c sshrand.c sshrsa.c sshrsag.c \ - sshsh256.c sshsh512.c sshsha.c sshsha3.c stripctrl.c time.c \ - tree234.c unix/uxcons.c unix/uxgen.c unix/uxmisc.c \ - unix/uxnogtk.c unix/uxnoise.c unix/uxpoll.c unix/uxstore.c \ - unix/uxutils.c utils.c wcwidth.c -puttygen_LDADD = libversion.a - -if HAVE_GTK -puttytel_SOURCES = be_misc.c be_nos_s.c callback.c charset/fromucs.c \ - charset/localenc.c charset/macenc.c charset/mimeenc.c \ - charset/sbcs.c charset/sbcsdat.c charset/slookup.c \ - charset/toucs.c charset/utf8.c charset/xenc.c cmdline.c \ - conf.c config.c dialog.c errsock.c ldisc.c logging.c \ - marshal.c memory.c minibidi.c misc.c miscucs.c nocproxy.c \ - nogss.c norand.c pinger.c proxy.c raw.c rlogin.c sessprep.c \ - settings.c stripctrl.c supdup.c telnet.c terminal.c time.c \ - timing.c tree234.c unix/gtkcfg.c unix/gtkcols.c \ - unix/gtkcomm.c unix/gtkdlg.c unix/gtkfont.c unix/gtkmain.c \ - unix/gtkmisc.c unix/gtkwin.c unix/uxcfg.c unix/uxfdsock.c \ - unix/uxmisc.c unix/uxnet.c unix/uxpeer.c unix/uxpoll.c \ - unix/uxprint.c unix/uxproxy.c unix/uxputty.c unix/uxsel.c \ - unix/uxser.c unix/uxsignal.c unix/uxstore.c unix/uxucs.c \ - unix/uxutils.c unix/x11misc.c unix/xkeysym.c unix/xpmpucfg.c \ - unix/xpmputty.c utils.c wcwidth.c -puttytel_LDADD = libversion.a $(GTK_LIBS) -endif - -testcrypt_SOURCES = ecc.c marshal.c memory.c millerrabin.c mpint.c \ - mpunsafe.c pockle.c primecandidate.c smallprimes.c sshaes.c \ - ssharcf.c sshargon2.c sshauxcrypt.c sshblake2.c sshblowf.c \ - sshccp.c sshcrc.c sshcrcda.c sshdes.c sshdh.c sshdss.c \ - sshdssg.c sshecc.c sshecdsag.c sshhmac.c sshmd5.c sshprime.c \ - sshprng.c sshpubk.c sshrsa.c sshrsag.c sshsh256.c sshsh512.c \ - sshsha.c sshsha3.c testcrypt.c tree234.c unix/uxutils.c \ - utils.c - -testsc_SOURCES = ecc.c marshal.c memory.c mpint.c sshaes.c ssharcf.c \ - sshargon2.c sshauxcrypt.c sshblake2.c sshblowf.c sshccp.c \ - sshcrc.c sshcrcda.c sshdes.c sshdh.c sshdss.c sshecc.c \ - sshhmac.c sshmac.c sshmd5.c sshpubk.c sshrsa.c sshsh256.c \ - sshsh512.c sshsha.c sshsha3.c testsc.c tree234.c \ - unix/uxutils.c utils.c wildcard.c - -testzlib_SOURCES = marshal.c memory.c sshzlib.c testzlib.c utils.c - -uppity_SOURCES = be_misc.c be_none.c callback.c conf.c cproxy.c ecc.c \ - errsock.c logging.c marshal.c memory.c millerrabin.c misc.c \ - mpint.c mpunsafe.c nullplug.c pgssapi.c pockle.c portfwd.c \ - primecandidate.c proxy.c scpserver.c sesschan.c settings.c \ - sftpcommon.c sftpserver.c smallprimes.c ssh1bpp.c \ - ssh1censor.c ssh1connection-server.c ssh1connection.c \ - ssh1login-server.c ssh2bpp-bare.c ssh2bpp.c ssh2censor.c \ - ssh2connection-server.c ssh2connection.c ssh2kex-server.c \ - ssh2transhk.c ssh2transport.c ssh2userauth-server.c sshaes.c \ - ssharcf.c sshargon2.c sshauxcrypt.c sshblake2.c sshblowf.c \ - sshccp.c sshcommon.c sshcrc.c sshcrcda.c sshdes.c sshdh.c \ - sshdss.c sshecc.c sshgssc.c sshhmac.c sshmac.c sshmd5.c \ - sshprime.c sshprng.c sshpubk.c sshrand.c sshrsa.c sshrsag.c \ - sshserver.c sshsh256.c sshsh512.c sshsha.c sshsha3.c \ - sshutils.c sshverstring.c sshzlib.c stripctrl.c time.c \ - timing.c tree234.c unix/procnet.c unix/ux_x11.c \ - unix/uxagentsock.c unix/uxcliloop.c unix/uxfdsock.c \ - unix/uxgss.c unix/uxmisc.c unix/uxnet.c unix/uxnogtk.c \ - unix/uxnoise.c unix/uxpeer.c unix/uxpoll.c unix/uxproxy.c \ - unix/uxpty.c unix/uxsel.c unix/uxserver.c \ - unix/uxsftpserver.c unix/uxsignal.c unix/uxstore.c \ - unix/uxutils.c utils.c wcwidth.c wildcard.c x11fwd.c -uppity_LDADD = libversion.a - -if AUTO_GIT_COMMIT -BUILT_SOURCES = empty.h -CLEANFILES = empty.h -libversion_a_CFLAGS += -DSOURCE_COMMIT=\"`git --git-dir=$(srcdir)/.git rev-parse HEAD 2>/dev/null`\" -empty.h: $(allsources) - echo '/* Empty file touched by automake makefile to force rebuild of version.o */' >$@ -endif - -# Run the cryptsuite tests as part of 'make check'. Override -# PUTTY_TESTCRYPT so that cryptsuite will take the testcrypt binary -# from the build directory instead of the source directory, in case -# this is an out-of-tree build. -check-local: testcrypt - PUTTY_TESTCRYPT=./testcrypt $(srcdir)/test/cryptsuite.py - -if HAVE_GTK -man1_MANS = doc/plink.1 doc/pscp.1 doc/psftp.1 doc/puttygen.1 doc/psusan.1 \ - doc/pageant.1 doc/pterm.1 doc/putty.1 doc/puttytel.1 -else -man1_MANS = doc/plink.1 doc/pscp.1 doc/psftp.1 doc/puttygen.1 doc/psusan.1 -endif -if HAVE_SETID_CMD -install-exec-local: - @SETID_CMD@ $(bindir)/pterm - chmod @SETID_MODE@ $(bindir)/pterm -endif -if HAVE_QUARTZ -noinst_SCRIPTS = unix/PuTTY.app unix/Pterm.app -unix/PuTTY.app: unix/putty.bundle puttyapp osxlaunch - rm -rf $@ && PUTTY_GTK_PREFIX_FROM_MAKEFILE=$$(pkg-config --variable=prefix gtk+-3.0) gtk-mac-bundler $< -unix/Pterm.app: unix/pterm.bundle ptermapp osxlaunch - rm -rf $@ && PUTTY_GTK_PREFIX_FROM_MAKEFILE=$$(pkg-config --variable=prefix gtk+-3.0) gtk-mac-bundler $< -endif diff --git a/PuTTYNG.ps1 b/PuTTYNG.ps1 new file mode 100644 index 000000000..4d6c5b83f --- /dev/null +++ b/PuTTYNG.ps1 @@ -0,0 +1,116 @@ +Function Write-CustomError() +{ +<# +.Synopsis + Displays error information to the console +.DESCRIPTION + Writes property information from the current [ErrorRecord] object + in the pipeline to the console +.EXAMPLE + Write-CustomError -UserMessage "Exception occurred at memory location $x" -ErrorObject $_ +.EXAMPLE + Write-CustomError -UserMessage "Exception occurred at memory location $x" -ErrorObject $_ -FullDetail +.INPUTS + $Error[0] +.OUTPUTS + [String] +.COMPONENT + adminkitMiscTools +.FUNCTIONALITY + General Utility +#> + [cmdletBinding()] + param( + [Parameter(Mandatory=$False)] + [String]$UserMessage, + + [Parameter(Mandatory=$True)] + [Object]$ErrorObject, + + [Parameter(Mandatory=$false)] + [Switch]$FullDetail + ) + + BEGIN + {} + PROCESS + { + if($UserMessage) { + Write-Host "`nERROR: $UserMessage" -ForegroundColor Red + } + + if($FullDetail) + { + $ErrorData = $ErrorData + [PSCustomObject]@{AccountUsed=$ENV:USERNAME; + ExceptionMessage=$ErrorObject.ToString(); + CategoryInfo=$ErrorObject.CategoryInfo; + ExceptionType=$ErrorObject.Exception.GetType(); + ErrorDetails=$ErrorObject.ErrorDetails; + FullyQualifiedErrorId=$ErrorObject.FullyQualifiedErrorId; + InvocationInfo=$ErrorObject.InvocationInfo; + PipelineIterationInfo=$ErrorObject.PipelineIterationInfo; + ScriptStackTrace=$ErrorObject.ScriptStackTrace + TargetObject=$ErrorObject.TargetObject; + } + } + return $ErrorData + } + END + {} +} + +clear +write-host "/============================================================================================/" +write-host "/ This will make a some changes in original putty files to make it compatible with mRemoteNG /" +write-host "/============================================================================================/" +write-host "" + +#clone putty into current directory +try { + Start-Process -FilePath "git.exe" -ArgumentList "clone https://git.tartarus.org/simon/putty.git" -Wait + } +catch { + Write-CustomError -UserMessage 'There was an error' -ErrorObject $_ -FullDetail +} + +$workFolder = "$PSScriptRoot\putty" +$workFile = "$workFolder\version.h" +#Change version data +(Get-Content $workFile).Replace('Unidentified build', 'Release 0.80 mRemoteNG') | Set-Content $workFile +(Get-Content $workFile).Replace('-Unidentified-Local-Build', '-Release-mRemoteNG-Build') | Set-Content $workFile +(Get-Content $workFile).Replace('0,0,0,0', '0,80,0,0') | Set-Content $workFile + +#Add mRemoteNG required changes +$workFile = "$workFolder\cmdline.c" +$newContent = ' +#ifdef PUTTYNG + if (!stricmp(p, "-hwndparent")) { + RETURN(2); + hwnd_parent = atoi(value); + return 2; + } +#endif + +if (!strcmp(p, "-load")) {' +(Get-Content $workFile).Replace('if (!strcmp(p, "-load")) {', $newContent) | Set-Content $workFile + +#Add mRemoteNG required changes +$workFile = "$workFolder\putty.h" +$newContent = 'extern const char *const appname; + +#ifdef PUTTYNG +int hwnd_parent; +#define IsZoomed(hWnd) TRUE +#endif // PUTTYNG' +(Get-Content $workFile).Replace('extern const char *const appname;', $newContent) | Set-Content $workFile + +# run cmake +try { + Start-Process -FilePath "make22.cmd" -Wait + } +catch { + Write-CustomError -UserMessage 'There was an error' -ErrorObject $_ -FullDetail +} + +Write-host "Build has been completed" +write-host "" \ No newline at end of file diff --git a/README b/README deleted file mode 100644 index 979c02bad..000000000 --- a/README +++ /dev/null @@ -1,40 +0,0 @@ -This is the README for PuTTY, a free Windows and Unix Telnet and SSH -client. - -PuTTY is built using CMake . To compile in the -simplest way (on any of Linux, Windows or Mac), run these commands in -the source directory: - - cmake . - cmake --build . - -Then, to install in the simplest way on Linux or Mac: - - cmake --build . --target install - -On Unix, pterm would like to be setuid or setgid, as appropriate, to -permit it to write records of user logins to /var/run/utmp and -/var/log/wtmp. (Of course it will not use this privilege for -anything else, and in particular it will drop all privileges before -starting up complex subsystems like GTK.) The cmake install step -doesn't attempt to add these privileges, so if you want user login -recording to work, you should manually ch{own,grp} and chmod the -pterm binary yourself after installation. If you don't do this, -pterm will still work, but not update the user login databases. - -Documentation (in various formats including Windows Help and Unix -`man' pages) is built from the Halibut (`.but') files in the `doc' -subdirectory. If you aren't using one of our source snapshots, -you'll need to do this yourself. Halibut can be found at -. - -The PuTTY home web site is - - https://www.chiark.greenend.org.uk/~sgtatham/putty/ - -If you want to send bug reports or feature requests, please read the -Feedback section of the web site before doing so. Sending one-line -reports saying `it doesn't work' will waste your time as much as -ours. - -See the file LICENCE for the licence conditions. diff --git a/README.MD b/README.MD index 3333ae27c..e36ca5bca 100644 --- a/README.MD +++ b/README.MD @@ -6,38 +6,27 @@ This is a modified version of [PuTTY](https://www.chiark.greenend.org.uk/~sgtath |:-----------------: |:-------------------:| | `Master` | [![Build status](https://ci.appveyor.com/api/projects/status/cv5of42aqanpr7l8?svg=true)](https://ci.appveyor.com/project/mremoteng/puttyng-publish) | -**The following files within the project were modified:** -- BE_ALL_S.C -- CMakeLists.txt +After reviewing changes I decide to dismiss putty files from our repository as almost no changes in them are done +some small changes now are made by script, in feature will try to avoid even such... + +So just run PuTTYNG.ps1 and it will do all for you! + +Script will clone https://git.tartarus.org/simon/putty.git and then do changes mentioned below: + +**The following files within the project will be modified:** - CMDLINE.C - PUTTY.H - version.h -- cmake/platforms/windows.cmake -- WINDOWS/PUTTY.RC -- WINDOWS/VERSION.RC2 -- WINDOWS/WINDOW.C **New PuTTY Build env** - Install cmake - open command prompt -- cd to local copy +- cd to local copy of putty - cmake . - cmake --build . --config Release --target putty OR -- cd to local copy\WINDOWS -- make22.cmd - -Build output: Release\PuTTYNG.exe - - -**To update PuTTYNG from the original repository:** -- git remote add putty https://git.tartarus.org/simon/putty.git -- git fetch --tags -- git remote update putty -- git checkout master -- git merge tags/0.{version} -- git push master +- run make22.cmd -After that create a new tag from within GitHub to trigger the AppVeyor release build. +Build output will be in: putty\Release\PuTTYNG.exe diff --git a/WINDOWS/CMakeLists.txt b/WINDOWS/CMakeLists.txt deleted file mode 100644 index 18f8b4424..000000000 --- a/WINDOWS/CMakeLists.txt +++ /dev/null @@ -1,213 +0,0 @@ -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) - -add_sources_from_current_dir(utils - utils/agent_mutex_name.c - utils/agent_named_pipe_name.c - utils/arm_arch_queries.c - utils/aux_match_opt.c - utils/centre_window.c - utils/cryptoapi.c - utils/defaults.c - utils/dll_hijacking_protection.c - utils/dputs.c - utils/escape_registry_key.c - utils/filename.c - utils/fontspec.c - utils/getdlgitemtext_alloc.c - utils/get_system_dir.c - utils/get_username.c - utils/gui-timing.c - utils/interprocess_mutex.c - utils/is_console_handle.c - utils/load_system32_dll.c - utils/ltime.c - utils/makedlgitemborderless.c - utils/make_spr_sw_abort_winerror.c - utils/message_box.c - utils/minefield.c - utils/open_for_write_would_lose_data.c - utils/pgp_fingerprints_msgbox.c - utils/platform_get_x_display.c - utils/registry.c - utils/request_file.c - utils/screenshot.c - utils/security.c - utils/shinydialogbox.c - utils/split_into_argv.c - utils/split_into_argv_w.c - utils/version.c - utils/win_strerror.c - unicode.c) -if(NOT HAVE_STRTOUMAX) - add_sources_from_current_dir(utils utils/strtoumax.c) -endif() -add_sources_from_current_dir(eventloop - cliloop.c handle-wait.c) -add_sources_from_current_dir(console - select-cli.c nohelp.c console.c) -add_sources_from_current_dir(settings - storage.c) -add_sources_from_current_dir(network - network.c handle-socket.c named-pipe-client.c named-pipe-server.c local-proxy.c x11.c) -add_sources_from_current_dir(sshcommon - noise.c) -add_sources_from_current_dir(sshclient - agent-client.c gss.c sharing.c) -add_sources_from_current_dir(sftpclient - sftp.c) -add_sources_from_current_dir(otherbackends - serial.c) -add_sources_from_current_dir(agent - agent-client.c) -add_sources_from_current_dir(guiterminal - dialog.c controls.c config.c printing.c jump-list.c sizetip.c) -add_dependencies(guiterminal generated_licence_h) # dialog.c uses licence.h - -# This object awkwardly needs to live in the network library as well -# as the eventloop library, in case it didn't get pulled in from the -# latter before handle-socket.c needed it. -add_library(handle-io OBJECT - handle-io.c) -target_sources(eventloop PRIVATE $) -target_sources(network PRIVATE $) - -add_library(guimisc STATIC - select-gui.c) - -add_executable(pageant - pageant.c - help.c - pageant.rc) -add_dependencies(pageant generated_licence_h) -target_link_libraries(pageant - guimisc eventloop agent network crypto utils - ${platform_libraries}) -set_target_properties(pageant PROPERTIES - WIN32_EXECUTABLE ON - LINK_FLAGS "${LFLAG_MANIFEST_NO}") -installed_program(pageant) - -add_sources_from_current_dir(plink no-jump-list.c nohelp.c plink.rc) -add_dependencies(plink generated_licence_h) - -add_sources_from_current_dir(pscp no-jump-list.c nohelp.c pscp.rc) -add_dependencies(pscp generated_licence_h) - -add_sources_from_current_dir(psftp no-jump-list.c nohelp.c psftp.rc) -add_dependencies(psftp generated_licence_h) - -add_sources_from_current_dir(psocks nohelp.c) - -add_executable(putty - window.c - putty.c - help.c - ${CMAKE_SOURCE_DIR}/stubs/no-console.c - putty.rc) -be_list(putty PuTTY SSH SERIAL OTHERBACKENDS) -add_dependencies(putty generated_licence_h) -target_link_libraries(putty - guiterminal guimisc eventloop sshclient otherbackends settings network crypto - utils - ${platform_libraries}) -set_target_properties(putty PROPERTIES - WIN32_EXECUTABLE ON - LINK_FLAGS "${LFLAG_MANIFEST_NO}") -installed_program(putty) - -add_executable(puttytel - window.c - putty.c - help.c - ${CMAKE_SOURCE_DIR}/stubs/no-gss.c - ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c - ${CMAKE_SOURCE_DIR}/stubs/no-console.c - ${CMAKE_SOURCE_DIR}/stubs/no-rand.c - ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c - ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c - puttytel.rc) -be_list(puttytel PuTTYtel SERIAL OTHERBACKENDS) -add_dependencies(puttytel generated_licence_h) -target_link_libraries(puttytel - guiterminal guimisc eventloop otherbackends settings network utils - ${platform_libraries}) -set_target_properties(puttytel PROPERTIES - WIN32_EXECUTABLE ON - LINK_FLAGS "${LFLAG_MANIFEST_NO}") -installed_program(puttytel) - -add_executable(puttygen - puttygen.c - ${CMAKE_SOURCE_DIR}/stubs/no-timing.c - noise.c - no-jump-list.c - storage.c - help.c - ${CMAKE_SOURCE_DIR}/sshpubk.c - ${CMAKE_SOURCE_DIR}/sshrand.c - controls.c - puttygen.rc) -add_dependencies(puttygen generated_licence_h) -target_link_libraries(puttygen - keygen guimisc crypto utils - ${platform_libraries}) -set_target_properties(puttygen PROPERTIES - WIN32_EXECUTABLE ON - LINK_FLAGS "${LFLAG_MANIFEST_NO}") -installed_program(puttygen) - -if(HAVE_CONPTY) - add_executable(pterm - window.c - pterm.c - help.c - conpty.c - ${CMAKE_SOURCE_DIR}/stubs/no-gss.c - ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c - ${CMAKE_SOURCE_DIR}/stubs/no-console.c - ${CMAKE_SOURCE_DIR}/stubs/no-rand.c - ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c - pterm.rc) - be_list(pterm pterm) - add_dependencies(pterm generated_licence_h) - target_link_libraries(pterm - guiterminal guimisc eventloop settings network utils - ${platform_libraries}) - set_target_properties(pterm PROPERTIES - WIN32_EXECUTABLE ON - LINK_FLAGS "${LFLAG_MANIFEST_NO}") - installed_program(pterm) -else() - message("ConPTY not available; cannot build Windows pterm") -endif() - -add_executable(test_split_into_argv - test/test_split_into_argv.c) -target_compile_definitions(test_split_into_argv PRIVATE TEST) -target_link_libraries(test_split_into_argv utils ${platform_libraries}) - -add_executable(test_screenshot - test/test_screenshot.c) -target_link_libraries(test_screenshot utils ${platform_libraries}) - -add_executable(test_lineedit - ${CMAKE_SOURCE_DIR}/test/test_lineedit.c - ${CMAKE_SOURCE_DIR}/stubs/no-gss.c - ${CMAKE_SOURCE_DIR}/stubs/no-logging.c - ${CMAKE_SOURCE_DIR}/stubs/no-printing.c - ${CMAKE_SOURCE_DIR}/stubs/no-storage.c - ${CMAKE_SOURCE_DIR}/stubs/no-timing.c - no-jump-list.c) -target_link_libraries(test_lineedit - guiterminal settings eventloop utils ${platform_libraries}) - -add_executable(test_terminal - ${CMAKE_SOURCE_DIR}/test/test_terminal.c - ${CMAKE_SOURCE_DIR}/stubs/no-gss.c - ${CMAKE_SOURCE_DIR}/stubs/no-storage.c - ${CMAKE_SOURCE_DIR}/stubs/no-timing.c - no-jump-list.c) -target_link_libraries(test_terminal - guiterminal settings eventloop utils ${platform_libraries}) - -add_sources_from_current_dir(test_conf no-jump-list.c handle-wait.c) diff --git a/WINDOWS/DEVCPP/PAGEANT/PAGEANT.DEV b/WINDOWS/DEVCPP/PAGEANT/PAGEANT.DEV deleted file mode 100644 index 8bd7ed1ce..000000000 --- a/WINDOWS/DEVCPP/PAGEANT/PAGEANT.DEV +++ /dev/null @@ -1,831 +0,0 @@ -# DEV-C++ 5 Project File - pageant.dev -# ** DO NOT EDIT ** - -[Project] -FileName=pageant.dev -Name=pageant -Ver=1 -IsCpp=1 -Type=0 -Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix -Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ -Libs= -UnitCount=78 -Folders="Header Files","Resource Files","Source Files" -ObjFiles= -PrivateResource=pageant_private.rc -ResourceIncludes=..\..\..\WINDOWS -MakeIncludes= -Icon= -ExeOutput= -ObjectOutput= -OverrideOutput=0 -OverrideOutputName=pageant.exe -HostApplication= -CommandLine= -UseCustomMakefile=0 -CustomMakefile= -IncludeVersionInfo=0 -SupportXPThemes=0 -CompilerSet=0 -CompilerSettings=0000000000000000000000 - -[Unit1] -FileName=..\..\..\aqsync.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit2] -FileName=..\..\..\be_misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit3] -FileName=..\..\..\callback.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit4] -FileName=..\..\..\conf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit5] -FileName=..\..\..\ecc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit6] -FileName=..\..\..\errsock.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit7] -FileName=..\..\..\marshal.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit8] -FileName=..\..\..\memory.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit9] -FileName=..\..\..\misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit10] -FileName=..\..\..\mpint.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit11] -FileName=..\..\..\pageant.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit12] -FileName=..\..\..\sshaes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit13] -FileName=..\..\..\sshargon2.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit14] -FileName=..\..\..\sshauxcrypt.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit15] -FileName=..\..\..\sshblake2.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit16] -FileName=..\..\..\sshdes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit17] -FileName=..\..\..\sshdss.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit18] -FileName=..\..\..\sshecc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit19] -FileName=..\..\..\sshhmac.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit20] -FileName=..\..\..\sshmd5.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit21] -FileName=..\..\..\sshpubk.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit22] -FileName=..\..\..\sshrsa.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit23] -FileName=..\..\..\sshsh256.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit24] -FileName=..\..\..\sshsh512.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit25] -FileName=..\..\..\sshsha.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit26] -FileName=..\..\..\sshsha3.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit27] -FileName=..\..\..\stripctrl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit28] -FileName=..\..\..\tree234.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit29] -FileName=..\..\..\utils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit30] -FileName=..\..\..\version.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit31] -FileName=..\..\..\wcwidth.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit32] -FileName=..\..\..\windows\wincapi.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit33] -FileName=..\..\..\windows\winhandl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit34] -FileName=..\..\..\windows\winhelp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit35] -FileName=..\..\..\windows\winhsock.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit36] -FileName=..\..\..\windows\winmisc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit37] -FileName=..\..\..\windows\winmiscs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit38] -FileName=..\..\..\windows\winnet.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit39] -FileName=..\..\..\windows\winnpc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit40] -FileName=..\..\..\windows\winnps.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit41] -FileName=..\..\..\windows\winpgnt.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit42] -FileName=..\..\..\windows\winpgntc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit43] -FileName=..\..\..\windows\winsecur.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit44] -FileName=..\..\..\windows\winselgui.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit45] -FileName=..\..\..\windows\winutils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit46] -FileName=..\..\..\charset\charset.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit47] -FileName=..\..\..\defs.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit48] -FileName=..\..\..\ecc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit49] -FileName=..\..\..\empty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit50] -FileName=..\..\..\licence.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit51] -FileName=..\..\..\marshal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit52] -FileName=..\..\..\misc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit53] -FileName=..\..\..\mpint.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit54] -FileName=..\..\..\mpint_i.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit55] -FileName=..\..\..\network.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit56] -FileName=..\..\..\pageant.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit57] -FileName=..\..\..\proxy.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit58] -FileName=..\..\..\putty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit59] -FileName=..\..\..\puttymem.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit60] -FileName=..\..\..\puttyps.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit61] -FileName=..\..\..\ssh.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit62] -FileName=..\..\..\sshcr.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit63] -FileName=..\..\..\sshsignals.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit64] -FileName=..\..\..\sshttymodes.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit65] -FileName=..\..\..\terminal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit66] -FileName=..\..\..\tree234.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit67] -FileName=..\..\..\unix\unix.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit68] -FileName=..\..\..\version.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit69] -FileName=..\..\..\windows\pageant-rc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit70] -FileName=..\..\..\windows\rcstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit71] -FileName=..\..\..\windows\win_res.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit72] -FileName=..\..\..\windows\wincapi.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit73] -FileName=..\..\..\windows\winhelp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit74] -FileName=..\..\..\windows\winsecur.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit75] -FileName=..\..\..\windows\winstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit76] -FileName=..\..\..\windows\pageant.ico -Folder=Resource Files -Compile=0 -CompileCpp=0 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit77] -FileName=..\..\..\windows\pageant.rc -Folder=Resource Files -Compile=1 -CompileCpp=1 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit78] -FileName=..\..\..\windows\pageants.ico -Folder=Resource Files -Compile=0 -CompileCpp=0 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[VersionInfo] -Major=0 -Minor=0 -Release=1 -Build=1 -LanguageID=1033 -CharsetID=1252 -CompanyName= -FileVersion=0.1 -FileDescription= -InternalName= -LegalCopyright= -LegalTrademarks= -OriginalFilename=pageant.exe -ProductName=pageant -ProductVersion=0.1 -AutoIncBuildNr=0 diff --git a/WINDOWS/DEVCPP/PLINK/PLINK.DEV b/WINDOWS/DEVCPP/PLINK/PLINK.DEV deleted file mode 100644 index 8ddbb050a..000000000 --- a/WINDOWS/DEVCPP/PLINK/PLINK.DEV +++ /dev/null @@ -1,1611 +0,0 @@ -# DEV-C++ 5 Project File - plink.dev -# ** DO NOT EDIT ** - -[Project] -FileName=plink.dev -Name=plink -Ver=1 -IsCpp=1 -Type=1 -Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix -Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ -Libs= -UnitCount=156 -Folders="Header Files","Resource Files","Source Files" -ObjFiles= -PrivateResource=plink_private.rc -ResourceIncludes=..\..\..\WINDOWS -MakeIncludes= -Icon= -ExeOutput= -ObjectOutput= -OverrideOutput=0 -OverrideOutputName=plink.exe -HostApplication= -CommandLine= -UseCustomMakefile=0 -CustomMakefile= -IncludeVersionInfo=0 -SupportXPThemes=0 -CompilerSet=0 -CompilerSettings=0000000000000000000000 - -[Unit1] -FileName=..\..\..\agentf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit2] -FileName=..\..\..\aqsync.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit3] -FileName=..\..\..\be_all_s.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit4] -FileName=..\..\..\be_misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit5] -FileName=..\..\..\callback.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit6] -FileName=..\..\..\clicons.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit7] -FileName=..\..\..\cmdline.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit8] -FileName=..\..\..\conf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit9] -FileName=..\..\..\console.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit10] -FileName=..\..\..\cproxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit11] -FileName=..\..\..\ecc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit12] -FileName=..\..\..\errsock.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit13] -FileName=..\..\..\ldisc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit14] -FileName=..\..\..\logging.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit15] -FileName=..\..\..\mainchan.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit16] -FileName=..\..\..\marshal.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit17] -FileName=..\..\..\memory.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit18] -FileName=..\..\..\misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit19] -FileName=..\..\..\miscucs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit20] -FileName=..\..\..\mpint.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit21] -FileName=..\..\..\noshare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit22] -FileName=..\..\..\noterm.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit23] -FileName=..\..\..\nullplug.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit24] -FileName=..\..\..\pgssapi.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit25] -FileName=..\..\..\pinger.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit26] -FileName=..\..\..\portfwd.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit27] -FileName=..\..\..\proxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit28] -FileName=..\..\..\raw.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit29] -FileName=..\..\..\rlogin.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit30] -FileName=..\..\..\sessprep.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit31] -FileName=..\..\..\settings.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit32] -FileName=..\..\..\ssh.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit33] -FileName=..\..\..\ssh1bpp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit34] -FileName=..\..\..\ssh1censor.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit35] -FileName=..\..\..\ssh1connection-client.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit36] -FileName=..\..\..\ssh1connection.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit37] -FileName=..\..\..\ssh1login.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit38] -FileName=..\..\..\ssh2bpp-bare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit39] -FileName=..\..\..\ssh2bpp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit40] -FileName=..\..\..\ssh2censor.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit41] -FileName=..\..\..\ssh2connection-client.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit42] -FileName=..\..\..\ssh2connection.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit43] -FileName=..\..\..\ssh2kex-client.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit44] -FileName=..\..\..\ssh2transhk.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit45] -FileName=..\..\..\ssh2transport.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit46] -FileName=..\..\..\ssh2userauth.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit47] -FileName=..\..\..\sshaes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit48] -FileName=..\..\..\ssharcf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit49] -FileName=..\..\..\sshargon2.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit50] -FileName=..\..\..\sshauxcrypt.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit51] -FileName=..\..\..\sshblake2.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit52] -FileName=..\..\..\sshblowf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit53] -FileName=..\..\..\sshccp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit54] -FileName=..\..\..\sshcommon.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit55] -FileName=..\..\..\sshcrc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit56] -FileName=..\..\..\sshcrcda.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit57] -FileName=..\..\..\sshdes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit58] -FileName=..\..\..\sshdh.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit59] -FileName=..\..\..\sshdss.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit60] -FileName=..\..\..\sshecc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit61] -FileName=..\..\..\sshgssc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit62] -FileName=..\..\..\sshhmac.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit63] -FileName=..\..\..\sshmac.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit64] -FileName=..\..\..\sshmd5.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit65] -FileName=..\..\..\sshprng.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit66] -FileName=..\..\..\sshpubk.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit67] -FileName=..\..\..\sshrand.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit68] -FileName=..\..\..\sshrsa.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit69] -FileName=..\..\..\sshsh256.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit70] -FileName=..\..\..\sshsh512.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit71] -FileName=..\..\..\sshsha.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit72] -FileName=..\..\..\sshsha3.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit73] -FileName=..\..\..\sshshare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit74] -FileName=..\..\..\sshutils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit75] -FileName=..\..\..\sshverstring.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit76] -FileName=..\..\..\sshzlib.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit77] -FileName=..\..\..\stripctrl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit78] -FileName=..\..\..\supdup.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit79] -FileName=..\..\..\telnet.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit80] -FileName=..\..\..\timing.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit81] -FileName=..\..\..\tree234.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit82] -FileName=..\..\..\utils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit83] -FileName=..\..\..\version.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit84] -FileName=..\..\..\wcwidth.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit85] -FileName=..\..\..\wildcard.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit86] -FileName=..\..\..\windows\wincapi.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit87] -FileName=..\..\..\windows\wincliloop.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit88] -FileName=..\..\..\windows\wincons.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit89] -FileName=..\..\..\windows\windefs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit90] -FileName=..\..\..\windows\wingss.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit91] -FileName=..\..\..\windows\winhandl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit92] -FileName=..\..\..\windows\winhsock.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit93] -FileName=..\..\..\windows\winmisc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit94] -FileName=..\..\..\windows\winmiscs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit95] -FileName=..\..\..\windows\winnet.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit96] -FileName=..\..\..\windows\winnohlp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit97] -FileName=..\..\..\windows\winnoise.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit98] -FileName=..\..\..\windows\winnojmp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit99] -FileName=..\..\..\windows\winnpc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit100] -FileName=..\..\..\windows\winnps.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit101] -FileName=..\..\..\windows\winpgntc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit102] -FileName=..\..\..\windows\winplink.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit103] -FileName=..\..\..\windows\winproxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit104] -FileName=..\..\..\windows\winsecur.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit105] -FileName=..\..\..\windows\winselcli.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit106] -FileName=..\..\..\windows\winser.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit107] -FileName=..\..\..\windows\winshare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit108] -FileName=..\..\..\windows\winstore.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit109] -FileName=..\..\..\windows\wintime.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit110] -FileName=..\..\..\windows\winucs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit111] -FileName=..\..\..\windows\winx11.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit112] -FileName=..\..\..\x11fwd.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit113] -FileName=..\..\..\charset\charset.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit114] -FileName=..\..\..\console.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit115] -FileName=..\..\..\defs.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit116] -FileName=..\..\..\ecc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit117] -FileName=..\..\..\empty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit118] -FileName=..\..\..\ldisc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit119] -FileName=..\..\..\licence.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit120] -FileName=..\..\..\marshal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit121] -FileName=..\..\..\misc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit122] -FileName=..\..\..\mpint.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit123] -FileName=..\..\..\mpint_i.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit124] -FileName=..\..\..\network.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit125] -FileName=..\..\..\pageant.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit126] -FileName=..\..\..\pgssapi.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit127] -FileName=..\..\..\proxy.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit128] -FileName=..\..\..\putty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit129] -FileName=..\..\..\puttymem.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit130] -FileName=..\..\..\puttyps.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit131] -FileName=..\..\..\ssh.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit132] -FileName=..\..\..\ssh1connection.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit133] -FileName=..\..\..\ssh2connection.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit134] -FileName=..\..\..\ssh2transport.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit135] -FileName=..\..\..\sshblowf.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit136] -FileName=..\..\..\sshbpp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit137] -FileName=..\..\..\sshchan.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit138] -FileName=..\..\..\sshcr.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit139] -FileName=..\..\..\sshgss.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit140] -FileName=..\..\..\sshgssc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit141] -FileName=..\..\..\sshppl.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit142] -FileName=..\..\..\sshserver.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit143] -FileName=..\..\..\sshsignals.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit144] -FileName=..\..\..\sshttymodes.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit145] -FileName=..\..\..\storage.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit146] -FileName=..\..\..\terminal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit147] -FileName=..\..\..\tree234.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit148] -FileName=..\..\..\unix\unix.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit149] -FileName=..\..\..\version.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit150] -FileName=..\..\..\windows\rcstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit151] -FileName=..\..\..\windows\wincapi.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit152] -FileName=..\..\..\windows\winhelp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit153] -FileName=..\..\..\windows\winsecur.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit154] -FileName=..\..\..\windows\winstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit155] -FileName=..\..\..\windows\plink.rc -Folder=Resource Files -Compile=1 -CompileCpp=1 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit156] -FileName=..\..\..\windows\putty.ico -Folder=Resource Files -Compile=0 -CompileCpp=0 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[VersionInfo] -Major=0 -Minor=0 -Release=1 -Build=1 -LanguageID=1033 -CharsetID=1252 -CompanyName= -FileVersion=0.1 -FileDescription= -InternalName= -LegalCopyright= -LegalTrademarks= -OriginalFilename=plink.exe -ProductName=plink -ProductVersion=0.1 -AutoIncBuildNr=0 diff --git a/WINDOWS/DEVCPP/PSCP/PSCP.DEV b/WINDOWS/DEVCPP/PSCP/PSCP.DEV deleted file mode 100644 index 0afbc8a49..000000000 --- a/WINDOWS/DEVCPP/PSCP/PSCP.DEV +++ /dev/null @@ -1,1571 +0,0 @@ -# DEV-C++ 5 Project File - pscp.dev -# ** DO NOT EDIT ** - -[Project] -FileName=pscp.dev -Name=pscp -Ver=1 -IsCpp=1 -Type=1 -Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix -Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ -Libs= -UnitCount=152 -Folders="Header Files","Resource Files","Source Files" -ObjFiles= -PrivateResource=pscp_private.rc -ResourceIncludes=..\..\..\WINDOWS -MakeIncludes= -Icon= -ExeOutput= -ObjectOutput= -OverrideOutput=0 -OverrideOutputName=pscp.exe -HostApplication= -CommandLine= -UseCustomMakefile=0 -CustomMakefile= -IncludeVersionInfo=0 -SupportXPThemes=0 -CompilerSet=0 -CompilerSettings=0000000000000000000000 - -[Unit1] -FileName=..\..\..\agentf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit2] -FileName=..\..\..\aqsync.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit3] -FileName=..\..\..\be_misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit4] -FileName=..\..\..\be_ssh.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit5] -FileName=..\..\..\callback.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit6] -FileName=..\..\..\clicons.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit7] -FileName=..\..\..\cmdline.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit8] -FileName=..\..\..\conf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit9] -FileName=..\..\..\console.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit10] -FileName=..\..\..\cproxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit11] -FileName=..\..\..\ecc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit12] -FileName=..\..\..\errsock.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit13] -FileName=..\..\..\logging.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit14] -FileName=..\..\..\mainchan.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit15] -FileName=..\..\..\marshal.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit16] -FileName=..\..\..\memory.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit17] -FileName=..\..\..\misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit18] -FileName=..\..\..\miscucs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit19] -FileName=..\..\..\mpint.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit20] -FileName=..\..\..\noshare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit21] -FileName=..\..\..\nullplug.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit22] -FileName=..\..\..\pgssapi.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit23] -FileName=..\..\..\pinger.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit24] -FileName=..\..\..\portfwd.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit25] -FileName=..\..\..\proxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit26] -FileName=..\..\..\pscp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit27] -FileName=..\..\..\psftpcommon.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit28] -FileName=..\..\..\settings.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit29] -FileName=..\..\..\sftp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit30] -FileName=..\..\..\sftpcommon.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit31] -FileName=..\..\..\ssh.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit32] -FileName=..\..\..\ssh1bpp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit33] -FileName=..\..\..\ssh1censor.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit34] -FileName=..\..\..\ssh1connection-client.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit35] -FileName=..\..\..\ssh1connection.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit36] -FileName=..\..\..\ssh1login.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit37] -FileName=..\..\..\ssh2bpp-bare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit38] -FileName=..\..\..\ssh2bpp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit39] -FileName=..\..\..\ssh2censor.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit40] -FileName=..\..\..\ssh2connection-client.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit41] -FileName=..\..\..\ssh2connection.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit42] -FileName=..\..\..\ssh2kex-client.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit43] -FileName=..\..\..\ssh2transhk.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit44] -FileName=..\..\..\ssh2transport.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit45] -FileName=..\..\..\ssh2userauth.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit46] -FileName=..\..\..\sshaes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit47] -FileName=..\..\..\ssharcf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit48] -FileName=..\..\..\sshargon2.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit49] -FileName=..\..\..\sshauxcrypt.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit50] -FileName=..\..\..\sshblake2.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit51] -FileName=..\..\..\sshblowf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit52] -FileName=..\..\..\sshccp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit53] -FileName=..\..\..\sshcommon.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit54] -FileName=..\..\..\sshcrc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit55] -FileName=..\..\..\sshcrcda.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit56] -FileName=..\..\..\sshdes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit57] -FileName=..\..\..\sshdh.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit58] -FileName=..\..\..\sshdss.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit59] -FileName=..\..\..\sshecc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit60] -FileName=..\..\..\sshgssc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit61] -FileName=..\..\..\sshhmac.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit62] -FileName=..\..\..\sshmac.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit63] -FileName=..\..\..\sshmd5.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit64] -FileName=..\..\..\sshprng.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit65] -FileName=..\..\..\sshpubk.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit66] -FileName=..\..\..\sshrand.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit67] -FileName=..\..\..\sshrsa.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit68] -FileName=..\..\..\sshsh256.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit69] -FileName=..\..\..\sshsh512.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit70] -FileName=..\..\..\sshsha.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit71] -FileName=..\..\..\sshsha3.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit72] -FileName=..\..\..\sshshare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit73] -FileName=..\..\..\sshutils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit74] -FileName=..\..\..\sshverstring.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit75] -FileName=..\..\..\sshzlib.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit76] -FileName=..\..\..\stripctrl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit77] -FileName=..\..\..\timing.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit78] -FileName=..\..\..\tree234.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit79] -FileName=..\..\..\utils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit80] -FileName=..\..\..\version.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit81] -FileName=..\..\..\wcwidth.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit82] -FileName=..\..\..\wildcard.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit83] -FileName=..\..\..\windows\wincapi.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit84] -FileName=..\..\..\windows\wincliloop.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit85] -FileName=..\..\..\windows\wincons.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit86] -FileName=..\..\..\windows\windefs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit87] -FileName=..\..\..\windows\wingss.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit88] -FileName=..\..\..\windows\winhandl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit89] -FileName=..\..\..\windows\winhsock.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit90] -FileName=..\..\..\windows\winmisc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit91] -FileName=..\..\..\windows\winmiscs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit92] -FileName=..\..\..\windows\winnet.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit93] -FileName=..\..\..\windows\winnohlp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit94] -FileName=..\..\..\windows\winnoise.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit95] -FileName=..\..\..\windows\winnojmp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit96] -FileName=..\..\..\windows\winnpc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit97] -FileName=..\..\..\windows\winnps.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit98] -FileName=..\..\..\windows\winpgntc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit99] -FileName=..\..\..\windows\winproxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit100] -FileName=..\..\..\windows\winsecur.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit101] -FileName=..\..\..\windows\winselcli.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit102] -FileName=..\..\..\windows\winsftp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit103] -FileName=..\..\..\windows\winshare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit104] -FileName=..\..\..\windows\winstore.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit105] -FileName=..\..\..\windows\wintime.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit106] -FileName=..\..\..\windows\winucs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit107] -FileName=..\..\..\x11fwd.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit108] -FileName=..\..\..\charset\charset.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit109] -FileName=..\..\..\console.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit110] -FileName=..\..\..\defs.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit111] -FileName=..\..\..\ecc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit112] -FileName=..\..\..\empty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit113] -FileName=..\..\..\licence.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit114] -FileName=..\..\..\marshal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit115] -FileName=..\..\..\misc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit116] -FileName=..\..\..\mpint.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit117] -FileName=..\..\..\mpint_i.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit118] -FileName=..\..\..\network.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit119] -FileName=..\..\..\pageant.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit120] -FileName=..\..\..\pgssapi.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit121] -FileName=..\..\..\proxy.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit122] -FileName=..\..\..\psftp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit123] -FileName=..\..\..\putty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit124] -FileName=..\..\..\puttymem.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit125] -FileName=..\..\..\puttyps.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit126] -FileName=..\..\..\sftp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit127] -FileName=..\..\..\ssh.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit128] -FileName=..\..\..\ssh1connection.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit129] -FileName=..\..\..\ssh2connection.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit130] -FileName=..\..\..\ssh2transport.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit131] -FileName=..\..\..\sshblowf.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit132] -FileName=..\..\..\sshbpp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit133] -FileName=..\..\..\sshchan.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit134] -FileName=..\..\..\sshcr.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit135] -FileName=..\..\..\sshgss.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit136] -FileName=..\..\..\sshgssc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit137] -FileName=..\..\..\sshppl.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit138] -FileName=..\..\..\sshserver.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit139] -FileName=..\..\..\sshsignals.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit140] -FileName=..\..\..\sshttymodes.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit141] -FileName=..\..\..\storage.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit142] -FileName=..\..\..\terminal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit143] -FileName=..\..\..\tree234.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit144] -FileName=..\..\..\unix\unix.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit145] -FileName=..\..\..\version.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit146] -FileName=..\..\..\windows\rcstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit147] -FileName=..\..\..\windows\wincapi.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit148] -FileName=..\..\..\windows\winhelp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit149] -FileName=..\..\..\windows\winsecur.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit150] -FileName=..\..\..\windows\winstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit151] -FileName=..\..\..\windows\pscp.ico -Folder=Resource Files -Compile=0 -CompileCpp=0 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit152] -FileName=..\..\..\windows\pscp.rc -Folder=Resource Files -Compile=1 -CompileCpp=1 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[VersionInfo] -Major=0 -Minor=0 -Release=1 -Build=1 -LanguageID=1033 -CharsetID=1252 -CompanyName= -FileVersion=0.1 -FileDescription= -InternalName= -LegalCopyright= -LegalTrademarks= -OriginalFilename=pscp.exe -ProductName=pscp -ProductVersion=0.1 -AutoIncBuildNr=0 diff --git a/WINDOWS/DEVCPP/PSFTP/PSFTP.DEV b/WINDOWS/DEVCPP/PSFTP/PSFTP.DEV deleted file mode 100644 index ca80126c2..000000000 --- a/WINDOWS/DEVCPP/PSFTP/PSFTP.DEV +++ /dev/null @@ -1,1571 +0,0 @@ -# DEV-C++ 5 Project File - psftp.dev -# ** DO NOT EDIT ** - -[Project] -FileName=psftp.dev -Name=psftp -Ver=1 -IsCpp=1 -Type=1 -Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix -Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ -Libs= -UnitCount=152 -Folders="Header Files","Resource Files","Source Files" -ObjFiles= -PrivateResource=psftp_private.rc -ResourceIncludes=..\..\..\WINDOWS -MakeIncludes= -Icon= -ExeOutput= -ObjectOutput= -OverrideOutput=0 -OverrideOutputName=psftp.exe -HostApplication= -CommandLine= -UseCustomMakefile=0 -CustomMakefile= -IncludeVersionInfo=0 -SupportXPThemes=0 -CompilerSet=0 -CompilerSettings=0000000000000000000000 - -[Unit1] -FileName=..\..\..\agentf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit2] -FileName=..\..\..\aqsync.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit3] -FileName=..\..\..\be_misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit4] -FileName=..\..\..\be_ssh.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit5] -FileName=..\..\..\callback.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit6] -FileName=..\..\..\clicons.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit7] -FileName=..\..\..\cmdline.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit8] -FileName=..\..\..\conf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit9] -FileName=..\..\..\console.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit10] -FileName=..\..\..\cproxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit11] -FileName=..\..\..\ecc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit12] -FileName=..\..\..\errsock.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit13] -FileName=..\..\..\logging.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit14] -FileName=..\..\..\mainchan.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit15] -FileName=..\..\..\marshal.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit16] -FileName=..\..\..\memory.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit17] -FileName=..\..\..\misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit18] -FileName=..\..\..\miscucs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit19] -FileName=..\..\..\mpint.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit20] -FileName=..\..\..\noshare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit21] -FileName=..\..\..\nullplug.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit22] -FileName=..\..\..\pgssapi.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit23] -FileName=..\..\..\pinger.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit24] -FileName=..\..\..\portfwd.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit25] -FileName=..\..\..\proxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit26] -FileName=..\..\..\psftp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit27] -FileName=..\..\..\psftpcommon.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit28] -FileName=..\..\..\settings.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit29] -FileName=..\..\..\sftp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit30] -FileName=..\..\..\sftpcommon.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit31] -FileName=..\..\..\ssh.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit32] -FileName=..\..\..\ssh1bpp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit33] -FileName=..\..\..\ssh1censor.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit34] -FileName=..\..\..\ssh1connection-client.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit35] -FileName=..\..\..\ssh1connection.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit36] -FileName=..\..\..\ssh1login.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit37] -FileName=..\..\..\ssh2bpp-bare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit38] -FileName=..\..\..\ssh2bpp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit39] -FileName=..\..\..\ssh2censor.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit40] -FileName=..\..\..\ssh2connection-client.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit41] -FileName=..\..\..\ssh2connection.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit42] -FileName=..\..\..\ssh2kex-client.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit43] -FileName=..\..\..\ssh2transhk.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit44] -FileName=..\..\..\ssh2transport.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit45] -FileName=..\..\..\ssh2userauth.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit46] -FileName=..\..\..\sshaes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit47] -FileName=..\..\..\ssharcf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit48] -FileName=..\..\..\sshargon2.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit49] -FileName=..\..\..\sshauxcrypt.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit50] -FileName=..\..\..\sshblake2.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit51] -FileName=..\..\..\sshblowf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit52] -FileName=..\..\..\sshccp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit53] -FileName=..\..\..\sshcommon.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit54] -FileName=..\..\..\sshcrc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit55] -FileName=..\..\..\sshcrcda.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit56] -FileName=..\..\..\sshdes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit57] -FileName=..\..\..\sshdh.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit58] -FileName=..\..\..\sshdss.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit59] -FileName=..\..\..\sshecc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit60] -FileName=..\..\..\sshgssc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit61] -FileName=..\..\..\sshhmac.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit62] -FileName=..\..\..\sshmac.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit63] -FileName=..\..\..\sshmd5.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit64] -FileName=..\..\..\sshprng.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit65] -FileName=..\..\..\sshpubk.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit66] -FileName=..\..\..\sshrand.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit67] -FileName=..\..\..\sshrsa.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit68] -FileName=..\..\..\sshsh256.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit69] -FileName=..\..\..\sshsh512.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit70] -FileName=..\..\..\sshsha.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit71] -FileName=..\..\..\sshsha3.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit72] -FileName=..\..\..\sshshare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit73] -FileName=..\..\..\sshutils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit74] -FileName=..\..\..\sshverstring.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit75] -FileName=..\..\..\sshzlib.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit76] -FileName=..\..\..\stripctrl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit77] -FileName=..\..\..\timing.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit78] -FileName=..\..\..\tree234.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit79] -FileName=..\..\..\utils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit80] -FileName=..\..\..\version.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit81] -FileName=..\..\..\wcwidth.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit82] -FileName=..\..\..\wildcard.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit83] -FileName=..\..\..\windows\wincapi.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit84] -FileName=..\..\..\windows\wincliloop.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit85] -FileName=..\..\..\windows\wincons.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit86] -FileName=..\..\..\windows\windefs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit87] -FileName=..\..\..\windows\wingss.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit88] -FileName=..\..\..\windows\winhandl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit89] -FileName=..\..\..\windows\winhsock.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit90] -FileName=..\..\..\windows\winmisc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit91] -FileName=..\..\..\windows\winmiscs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit92] -FileName=..\..\..\windows\winnet.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit93] -FileName=..\..\..\windows\winnohlp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit94] -FileName=..\..\..\windows\winnoise.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit95] -FileName=..\..\..\windows\winnojmp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit96] -FileName=..\..\..\windows\winnpc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit97] -FileName=..\..\..\windows\winnps.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit98] -FileName=..\..\..\windows\winpgntc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit99] -FileName=..\..\..\windows\winproxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit100] -FileName=..\..\..\windows\winsecur.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit101] -FileName=..\..\..\windows\winselcli.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit102] -FileName=..\..\..\windows\winsftp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit103] -FileName=..\..\..\windows\winshare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit104] -FileName=..\..\..\windows\winstore.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit105] -FileName=..\..\..\windows\wintime.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit106] -FileName=..\..\..\windows\winucs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit107] -FileName=..\..\..\x11fwd.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit108] -FileName=..\..\..\charset\charset.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit109] -FileName=..\..\..\console.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit110] -FileName=..\..\..\defs.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit111] -FileName=..\..\..\ecc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit112] -FileName=..\..\..\empty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit113] -FileName=..\..\..\licence.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit114] -FileName=..\..\..\marshal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit115] -FileName=..\..\..\misc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit116] -FileName=..\..\..\mpint.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit117] -FileName=..\..\..\mpint_i.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit118] -FileName=..\..\..\network.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit119] -FileName=..\..\..\pageant.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit120] -FileName=..\..\..\pgssapi.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit121] -FileName=..\..\..\proxy.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit122] -FileName=..\..\..\psftp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit123] -FileName=..\..\..\putty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit124] -FileName=..\..\..\puttymem.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit125] -FileName=..\..\..\puttyps.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit126] -FileName=..\..\..\sftp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit127] -FileName=..\..\..\ssh.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit128] -FileName=..\..\..\ssh1connection.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit129] -FileName=..\..\..\ssh2connection.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit130] -FileName=..\..\..\ssh2transport.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit131] -FileName=..\..\..\sshblowf.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit132] -FileName=..\..\..\sshbpp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit133] -FileName=..\..\..\sshchan.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit134] -FileName=..\..\..\sshcr.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit135] -FileName=..\..\..\sshgss.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit136] -FileName=..\..\..\sshgssc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit137] -FileName=..\..\..\sshppl.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit138] -FileName=..\..\..\sshserver.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit139] -FileName=..\..\..\sshsignals.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit140] -FileName=..\..\..\sshttymodes.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit141] -FileName=..\..\..\storage.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit142] -FileName=..\..\..\terminal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit143] -FileName=..\..\..\tree234.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit144] -FileName=..\..\..\unix\unix.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit145] -FileName=..\..\..\version.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit146] -FileName=..\..\..\windows\rcstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit147] -FileName=..\..\..\windows\wincapi.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit148] -FileName=..\..\..\windows\winhelp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit149] -FileName=..\..\..\windows\winsecur.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit150] -FileName=..\..\..\windows\winstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit151] -FileName=..\..\..\windows\pscp.ico -Folder=Resource Files -Compile=0 -CompileCpp=0 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit152] -FileName=..\..\..\windows\psftp.rc -Folder=Resource Files -Compile=1 -CompileCpp=1 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[VersionInfo] -Major=0 -Minor=0 -Release=1 -Build=1 -LanguageID=1033 -CharsetID=1252 -CompanyName= -FileVersion=0.1 -FileDescription= -InternalName= -LegalCopyright= -LegalTrademarks= -OriginalFilename=psftp.exe -ProductName=psftp -ProductVersion=0.1 -AutoIncBuildNr=0 diff --git a/WINDOWS/DEVCPP/PUTTY/PUTTY.DEV b/WINDOWS/DEVCPP/PUTTY/PUTTY.DEV deleted file mode 100644 index 63dab1098..000000000 --- a/WINDOWS/DEVCPP/PUTTY/PUTTY.DEV +++ /dev/null @@ -1,1691 +0,0 @@ -# DEV-C++ 5 Project File - putty.dev -# ** DO NOT EDIT ** - -[Project] -FileName=putty.dev -Name=putty -Ver=1 -IsCpp=1 -Type=0 -Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix -Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ -Libs= -UnitCount=164 -Folders="Header Files","Resource Files","Source Files" -ObjFiles= -PrivateResource=putty_private.rc -ResourceIncludes=..\..\..\WINDOWS -MakeIncludes= -Icon= -ExeOutput= -ObjectOutput= -OverrideOutput=0 -OverrideOutputName=putty.exe -HostApplication= -CommandLine= -UseCustomMakefile=0 -CustomMakefile= -IncludeVersionInfo=0 -SupportXPThemes=0 -CompilerSet=0 -CompilerSettings=0000000000000000000000 - -[Unit1] -FileName=..\..\..\agentf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit2] -FileName=..\..\..\aqsync.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit3] -FileName=..\..\..\be_all_s.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit4] -FileName=..\..\..\be_misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit5] -FileName=..\..\..\callback.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit6] -FileName=..\..\..\cmdline.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit7] -FileName=..\..\..\conf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit8] -FileName=..\..\..\config.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit9] -FileName=..\..\..\cproxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit10] -FileName=..\..\..\dialog.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit11] -FileName=..\..\..\ecc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit12] -FileName=..\..\..\errsock.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit13] -FileName=..\..\..\ldisc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit14] -FileName=..\..\..\logging.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit15] -FileName=..\..\..\mainchan.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit16] -FileName=..\..\..\marshal.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit17] -FileName=..\..\..\memory.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit18] -FileName=..\..\..\minibidi.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit19] -FileName=..\..\..\misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit20] -FileName=..\..\..\miscucs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit21] -FileName=..\..\..\mpint.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit22] -FileName=..\..\..\noshare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit23] -FileName=..\..\..\nullplug.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit24] -FileName=..\..\..\pgssapi.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit25] -FileName=..\..\..\pinger.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit26] -FileName=..\..\..\portfwd.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit27] -FileName=..\..\..\proxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit28] -FileName=..\..\..\raw.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit29] -FileName=..\..\..\rlogin.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit30] -FileName=..\..\..\sessprep.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit31] -FileName=..\..\..\settings.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit32] -FileName=..\..\..\ssh.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit33] -FileName=..\..\..\ssh1bpp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit34] -FileName=..\..\..\ssh1censor.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit35] -FileName=..\..\..\ssh1connection-client.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit36] -FileName=..\..\..\ssh1connection.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit37] -FileName=..\..\..\ssh1login.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit38] -FileName=..\..\..\ssh2bpp-bare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit39] -FileName=..\..\..\ssh2bpp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit40] -FileName=..\..\..\ssh2censor.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit41] -FileName=..\..\..\ssh2connection-client.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit42] -FileName=..\..\..\ssh2connection.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit43] -FileName=..\..\..\ssh2kex-client.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit44] -FileName=..\..\..\ssh2transhk.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit45] -FileName=..\..\..\ssh2transport.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit46] -FileName=..\..\..\ssh2userauth.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit47] -FileName=..\..\..\sshaes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit48] -FileName=..\..\..\ssharcf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit49] -FileName=..\..\..\sshargon2.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit50] -FileName=..\..\..\sshauxcrypt.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit51] -FileName=..\..\..\sshblake2.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit52] -FileName=..\..\..\sshblowf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit53] -FileName=..\..\..\sshccp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit54] -FileName=..\..\..\sshcommon.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit55] -FileName=..\..\..\sshcrc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit56] -FileName=..\..\..\sshcrcda.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit57] -FileName=..\..\..\sshdes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit58] -FileName=..\..\..\sshdh.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit59] -FileName=..\..\..\sshdss.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit60] -FileName=..\..\..\sshecc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit61] -FileName=..\..\..\sshgssc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit62] -FileName=..\..\..\sshhmac.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit63] -FileName=..\..\..\sshmac.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit64] -FileName=..\..\..\sshmd5.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit65] -FileName=..\..\..\sshprng.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit66] -FileName=..\..\..\sshpubk.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit67] -FileName=..\..\..\sshrand.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit68] -FileName=..\..\..\sshrsa.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit69] -FileName=..\..\..\sshsh256.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit70] -FileName=..\..\..\sshsh512.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit71] -FileName=..\..\..\sshsha.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit72] -FileName=..\..\..\sshsha3.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit73] -FileName=..\..\..\sshshare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit74] -FileName=..\..\..\sshutils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit75] -FileName=..\..\..\sshverstring.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit76] -FileName=..\..\..\sshzlib.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit77] -FileName=..\..\..\stripctrl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit78] -FileName=..\..\..\supdup.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit79] -FileName=..\..\..\telnet.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit80] -FileName=..\..\..\terminal.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit81] -FileName=..\..\..\timing.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit82] -FileName=..\..\..\tree234.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit83] -FileName=..\..\..\utils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit84] -FileName=..\..\..\version.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit85] -FileName=..\..\..\wcwidth.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit86] -FileName=..\..\..\wildcard.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit87] -FileName=..\..\..\windows\sizetip.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit88] -FileName=..\..\..\windows\wincapi.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit89] -FileName=..\..\..\windows\wincfg.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit90] -FileName=..\..\..\windows\winctrls.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit91] -FileName=..\..\..\windows\windefs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit92] -FileName=..\..\..\windows\windlg.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit93] -FileName=..\..\..\windows\window.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit94] -FileName=..\..\..\windows\wingss.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit95] -FileName=..\..\..\windows\winhandl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit96] -FileName=..\..\..\windows\winhelp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit97] -FileName=..\..\..\windows\winhsock.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit98] -FileName=..\..\..\windows\winjump.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit99] -FileName=..\..\..\windows\winmisc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit100] -FileName=..\..\..\windows\winmiscs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit101] -FileName=..\..\..\windows\winnet.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit102] -FileName=..\..\..\windows\winnoise.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit103] -FileName=..\..\..\windows\winnpc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit104] -FileName=..\..\..\windows\winnps.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit105] -FileName=..\..\..\windows\winpgntc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit106] -FileName=..\..\..\windows\winprint.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit107] -FileName=..\..\..\windows\winproxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit108] -FileName=..\..\..\windows\winsecur.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit109] -FileName=..\..\..\windows\winselgui.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit110] -FileName=..\..\..\windows\winser.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit111] -FileName=..\..\..\windows\winshare.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit112] -FileName=..\..\..\windows\winstore.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit113] -FileName=..\..\..\windows\wintime.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit114] -FileName=..\..\..\windows\winucs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit115] -FileName=..\..\..\windows\winutils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit116] -FileName=..\..\..\windows\winx11.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit117] -FileName=..\..\..\x11fwd.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit118] -FileName=..\..\..\charset\charset.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit119] -FileName=..\..\..\defs.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit120] -FileName=..\..\..\dialog.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit121] -FileName=..\..\..\ecc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit122] -FileName=..\..\..\empty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit123] -FileName=..\..\..\ldisc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit124] -FileName=..\..\..\licence.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit125] -FileName=..\..\..\marshal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit126] -FileName=..\..\..\misc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit127] -FileName=..\..\..\mpint.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit128] -FileName=..\..\..\mpint_i.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit129] -FileName=..\..\..\network.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit130] -FileName=..\..\..\pageant.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit131] -FileName=..\..\..\pgssapi.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit132] -FileName=..\..\..\proxy.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit133] -FileName=..\..\..\putty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit134] -FileName=..\..\..\puttymem.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit135] -FileName=..\..\..\puttyps.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit136] -FileName=..\..\..\ssh.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit137] -FileName=..\..\..\ssh1connection.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit138] -FileName=..\..\..\ssh2connection.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit139] -FileName=..\..\..\ssh2transport.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit140] -FileName=..\..\..\sshblowf.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit141] -FileName=..\..\..\sshbpp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit142] -FileName=..\..\..\sshchan.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit143] -FileName=..\..\..\sshcr.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit144] -FileName=..\..\..\sshgss.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit145] -FileName=..\..\..\sshgssc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit146] -FileName=..\..\..\sshppl.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit147] -FileName=..\..\..\sshserver.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit148] -FileName=..\..\..\sshsignals.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit149] -FileName=..\..\..\sshttymodes.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit150] -FileName=..\..\..\storage.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit151] -FileName=..\..\..\terminal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit152] -FileName=..\..\..\tree234.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit153] -FileName=..\..\..\unix\unix.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit154] -FileName=..\..\..\version.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit155] -FileName=..\..\..\windows\rcstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit156] -FileName=..\..\..\windows\win_res.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit157] -FileName=..\..\..\windows\wincapi.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit158] -FileName=..\..\..\windows\winhelp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit159] -FileName=..\..\..\windows\winseat.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit160] -FileName=..\..\..\windows\winsecur.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit161] -FileName=..\..\..\windows\winstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit162] -FileName=..\..\..\windows\putty.ico -Folder=Resource Files -Compile=0 -CompileCpp=0 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit163] -FileName=..\..\..\windows\putty.rc -Folder=Resource Files -Compile=1 -CompileCpp=1 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit164] -FileName=..\..\..\windows\puttycfg.ico -Folder=Resource Files -Compile=0 -CompileCpp=0 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[VersionInfo] -Major=0 -Minor=0 -Release=1 -Build=1 -LanguageID=1033 -CharsetID=1252 -CompanyName= -FileVersion=0.1 -FileDescription= -InternalName= -LegalCopyright= -LegalTrademarks= -OriginalFilename=putty.exe -ProductName=putty -ProductVersion=0.1 -AutoIncBuildNr=0 diff --git a/WINDOWS/DEVCPP/PUTTYGEN/PUTTYGEN.DEV b/WINDOWS/DEVCPP/PUTTYGEN/PUTTYGEN.DEV deleted file mode 100644 index 9070fc089..000000000 --- a/WINDOWS/DEVCPP/PUTTYGEN/PUTTYGEN.DEV +++ /dev/null @@ -1,901 +0,0 @@ -# DEV-C++ 5 Project File - puttygen.dev -# ** DO NOT EDIT ** - -[Project] -FileName=puttygen.dev -Name=puttygen -Ver=1 -IsCpp=1 -Type=0 -Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix -Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ -Libs= -UnitCount=85 -Folders="Header Files","Resource Files","Source Files" -ObjFiles= -PrivateResource=puttygen_private.rc -ResourceIncludes=..\..\..\WINDOWS -MakeIncludes= -Icon= -ExeOutput= -ObjectOutput= -OverrideOutput=0 -OverrideOutputName=puttygen.exe -HostApplication= -CommandLine= -UseCustomMakefile=0 -CustomMakefile= -IncludeVersionInfo=0 -SupportXPThemes=0 -CompilerSet=0 -CompilerSettings=0000000000000000000000 - -[Unit1] -FileName=..\..\..\conf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit2] -FileName=..\..\..\ecc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit3] -FileName=..\..\..\import.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit4] -FileName=..\..\..\marshal.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit5] -FileName=..\..\..\memory.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit6] -FileName=..\..\..\millerrabin.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit7] -FileName=..\..\..\misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit8] -FileName=..\..\..\mpint.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit9] -FileName=..\..\..\mpunsafe.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit10] -FileName=..\..\..\notiming.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit11] -FileName=..\..\..\pockle.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit12] -FileName=..\..\..\primecandidate.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit13] -FileName=..\..\..\smallprimes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit14] -FileName=..\..\..\sshaes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit15] -FileName=..\..\..\sshargon2.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit16] -FileName=..\..\..\sshauxcrypt.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit17] -FileName=..\..\..\sshbcrypt.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit18] -FileName=..\..\..\sshblake2.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit19] -FileName=..\..\..\sshblowf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit20] -FileName=..\..\..\sshdes.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit21] -FileName=..\..\..\sshdss.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit22] -FileName=..\..\..\sshdssg.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit23] -FileName=..\..\..\sshecc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit24] -FileName=..\..\..\sshecdsag.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit25] -FileName=..\..\..\sshhmac.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit26] -FileName=..\..\..\sshmd5.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit27] -FileName=..\..\..\sshprime.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit28] -FileName=..\..\..\sshprng.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit29] -FileName=..\..\..\sshpubk.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit30] -FileName=..\..\..\sshrand.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit31] -FileName=..\..\..\sshrsa.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit32] -FileName=..\..\..\sshrsag.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit33] -FileName=..\..\..\sshsh256.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit34] -FileName=..\..\..\sshsh512.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit35] -FileName=..\..\..\sshsha.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit36] -FileName=..\..\..\sshsha3.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit37] -FileName=..\..\..\stripctrl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit38] -FileName=..\..\..\tree234.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit39] -FileName=..\..\..\utils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit40] -FileName=..\..\..\version.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit41] -FileName=..\..\..\wcwidth.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit42] -FileName=..\..\..\windows\winctrls.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit43] -FileName=..\..\..\windows\winhelp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit44] -FileName=..\..\..\windows\winmisc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit45] -FileName=..\..\..\windows\winmiscs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit46] -FileName=..\..\..\windows\winnoise.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit47] -FileName=..\..\..\windows\winnojmp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit48] -FileName=..\..\..\windows\winpgen.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit49] -FileName=..\..\..\windows\winsecur.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit50] -FileName=..\..\..\windows\winstore.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit51] -FileName=..\..\..\windows\wintime.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit52] -FileName=..\..\..\windows\winutils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit53] -FileName=..\..\..\charset\charset.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit54] -FileName=..\..\..\defs.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit55] -FileName=..\..\..\dialog.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit56] -FileName=..\..\..\ecc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit57] -FileName=..\..\..\empty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit58] -FileName=..\..\..\licence.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit59] -FileName=..\..\..\marshal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit60] -FileName=..\..\..\misc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit61] -FileName=..\..\..\mpint.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit62] -FileName=..\..\..\mpint_i.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit63] -FileName=..\..\..\mpunsafe.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit64] -FileName=..\..\..\network.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit65] -FileName=..\..\..\putty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit66] -FileName=..\..\..\puttymem.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit67] -FileName=..\..\..\puttyps.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit68] -FileName=..\..\..\ssh.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit69] -FileName=..\..\..\sshblowf.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit70] -FileName=..\..\..\sshkeygen.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit71] -FileName=..\..\..\sshsignals.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit72] -FileName=..\..\..\sshttymodes.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit73] -FileName=..\..\..\storage.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit74] -FileName=..\..\..\terminal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit75] -FileName=..\..\..\tree234.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit76] -FileName=..\..\..\unix\unix.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit77] -FileName=..\..\..\version.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit78] -FileName=..\..\..\windows\puttygen-rc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit79] -FileName=..\..\..\windows\rcstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit80] -FileName=..\..\..\windows\win_res.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit81] -FileName=..\..\..\windows\winhelp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit82] -FileName=..\..\..\windows\winsecur.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit83] -FileName=..\..\..\windows\winstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit84] -FileName=..\..\..\windows\puttygen.ico -Folder=Resource Files -Compile=0 -CompileCpp=0 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit85] -FileName=..\..\..\windows\puttygen.rc -Folder=Resource Files -Compile=1 -CompileCpp=1 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[VersionInfo] -Major=0 -Minor=0 -Release=1 -Build=1 -LanguageID=1033 -CharsetID=1252 -CompanyName= -FileVersion=0.1 -FileDescription= -InternalName= -LegalCopyright= -LegalTrademarks= -OriginalFilename=puttygen.exe -ProductName=puttygen -ProductVersion=0.1 -AutoIncBuildNr=0 diff --git a/WINDOWS/DEVCPP/PUTTYTEL/PUTTYTEL.DEV b/WINDOWS/DEVCPP/PUTTYTEL/PUTTYTEL.DEV deleted file mode 100644 index fc2c94b1f..000000000 --- a/WINDOWS/DEVCPP/PUTTYTEL/PUTTYTEL.DEV +++ /dev/null @@ -1,931 +0,0 @@ -# DEV-C++ 5 Project File - puttytel.dev -# ** DO NOT EDIT ** - -[Project] -FileName=puttytel.dev -Name=puttytel -Ver=1 -IsCpp=1 -Type=0 -Compiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -CppCompiler=-W -D__GNUWIN32__ -DWIN32 -DNDEBUG -D_WINDOWS -DNO_MULTIMON -D_MBCS_@@_ -Includes=..\..\..\.;..\..\..\charset;..\..\..\windows;..\..\..\unix -Linker=-ladvapi32 -lcomctl32 -lcomdlg32 -lgdi32 -limm32 -lshell32 -luser32 -lwinmm -lwinspool_@@_ -Libs= -UnitCount=88 -Folders="Header Files","Resource Files","Source Files" -ObjFiles= -PrivateResource=puttytel_private.rc -ResourceIncludes=..\..\..\WINDOWS -MakeIncludes= -Icon= -ExeOutput= -ObjectOutput= -OverrideOutput=0 -OverrideOutputName=puttytel.exe -HostApplication= -CommandLine= -UseCustomMakefile=0 -CustomMakefile= -IncludeVersionInfo=0 -SupportXPThemes=0 -CompilerSet=0 -CompilerSettings=0000000000000000000000 - -[Unit1] -FileName=..\..\..\be_misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit2] -FileName=..\..\..\be_nos_s.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit3] -FileName=..\..\..\callback.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit4] -FileName=..\..\..\cmdline.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit5] -FileName=..\..\..\conf.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit6] -FileName=..\..\..\config.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit7] -FileName=..\..\..\dialog.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit8] -FileName=..\..\..\errsock.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit9] -FileName=..\..\..\ldisc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit10] -FileName=..\..\..\logging.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit11] -FileName=..\..\..\marshal.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit12] -FileName=..\..\..\memory.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit13] -FileName=..\..\..\minibidi.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit14] -FileName=..\..\..\misc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit15] -FileName=..\..\..\miscucs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit16] -FileName=..\..\..\nocproxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit17] -FileName=..\..\..\nogss.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit18] -FileName=..\..\..\norand.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit19] -FileName=..\..\..\pinger.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit20] -FileName=..\..\..\proxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit21] -FileName=..\..\..\raw.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit22] -FileName=..\..\..\rlogin.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit23] -FileName=..\..\..\sessprep.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit24] -FileName=..\..\..\settings.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit25] -FileName=..\..\..\stripctrl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit26] -FileName=..\..\..\supdup.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit27] -FileName=..\..\..\telnet.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit28] -FileName=..\..\..\terminal.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit29] -FileName=..\..\..\timing.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit30] -FileName=..\..\..\tree234.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit31] -FileName=..\..\..\utils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit32] -FileName=..\..\..\version.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit33] -FileName=..\..\..\wcwidth.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit34] -FileName=..\..\..\windows\sizetip.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit35] -FileName=..\..\..\windows\wincfg.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit36] -FileName=..\..\..\windows\winctrls.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit37] -FileName=..\..\..\windows\windefs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit38] -FileName=..\..\..\windows\windlg.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit39] -FileName=..\..\..\windows\window.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit40] -FileName=..\..\..\windows\winhandl.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit41] -FileName=..\..\..\windows\winhelp.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit42] -FileName=..\..\..\windows\winhsock.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit43] -FileName=..\..\..\windows\winjump.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit44] -FileName=..\..\..\windows\winmisc.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit45] -FileName=..\..\..\windows\winmiscs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit46] -FileName=..\..\..\windows\winnet.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit47] -FileName=..\..\..\windows\winprint.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit48] -FileName=..\..\..\windows\winproxy.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit49] -FileName=..\..\..\windows\winsecur.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit50] -FileName=..\..\..\windows\winselgui.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit51] -FileName=..\..\..\windows\winser.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit52] -FileName=..\..\..\windows\winstore.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit53] -FileName=..\..\..\windows\wintime.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit54] -FileName=..\..\..\windows\winucs.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit55] -FileName=..\..\..\windows\winutils.c -Folder=Source Files -Compile=1 -CompileCpp=0 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit56] -FileName=..\..\..\charset\charset.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit57] -FileName=..\..\..\defs.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit58] -FileName=..\..\..\dialog.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit59] -FileName=..\..\..\empty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit60] -FileName=..\..\..\ldisc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit61] -FileName=..\..\..\licence.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit62] -FileName=..\..\..\marshal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit63] -FileName=..\..\..\misc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit64] -FileName=..\..\..\network.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit65] -FileName=..\..\..\pgssapi.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit66] -FileName=..\..\..\proxy.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit67] -FileName=..\..\..\putty.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit68] -FileName=..\..\..\puttymem.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit69] -FileName=..\..\..\puttyps.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit70] -FileName=..\..\..\ssh.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit71] -FileName=..\..\..\sshgss.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit72] -FileName=..\..\..\sshgssc.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit73] -FileName=..\..\..\sshsignals.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit74] -FileName=..\..\..\sshttymodes.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit75] -FileName=..\..\..\storage.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit76] -FileName=..\..\..\terminal.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit77] -FileName=..\..\..\tree234.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit78] -FileName=..\..\..\unix\unix.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit79] -FileName=..\..\..\version.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit80] -FileName=..\..\..\windows\rcstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit81] -FileName=..\..\..\windows\win_res.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit82] -FileName=..\..\..\windows\winhelp.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit83] -FileName=..\..\..\windows\winseat.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit84] -FileName=..\..\..\windows\winsecur.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit85] -FileName=..\..\..\windows\winstuff.h -Folder=Header Files -Compile=1 -CompileCpp=1 -Link=1 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit86] -FileName=..\..\..\windows\putty.ico -Folder=Resource Files -Compile=0 -CompileCpp=0 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit87] -FileName=..\..\..\windows\puttycfg.ico -Folder=Resource Files -Compile=0 -CompileCpp=0 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[Unit88] -FileName=..\..\..\windows\puttytel.rc -Folder=Resource Files -Compile=1 -CompileCpp=1 -Link=0 -Priority=1000 -OverrideBuildCmd=0 -BuildCmd= - -[VersionInfo] -Major=0 -Minor=0 -Release=1 -Build=1 -LanguageID=1033 -CharsetID=1252 -CompanyName= -FileVersion=0.1 -FileDescription= -InternalName= -LegalCopyright= -LegalTrademarks= -OriginalFilename=puttytel.exe -ProductName=puttytel -ProductVersion=0.1 -AutoIncBuildNr=0 diff --git a/WINDOWS/MAKEFILE.LCC b/WINDOWS/MAKEFILE.LCC deleted file mode 100644 index 0d24da26f..000000000 --- a/WINDOWS/MAKEFILE.LCC +++ /dev/null @@ -1,1658 +0,0 @@ -# Makefile for putty under lcc. -# -# This file was created by `mkfiles.pl' from the `Recipe' file. -# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead. -# -# Extra options you can set: -# -# - COMPAT=-DAUTO_WINSOCK (Windows only) -# Causes PuTTY to assume that includes its own WinSock -# header file, so that it won't try to include . -# -# - COMPAT=-DWINSOCK_TWO (Windows only) -# Causes the PuTTY utilities to include instead of -# , except Plink which _needs_ WinSock 2 so it already -# does this. -# -# - COMPAT=-DNO_SECURITY (Windows only) -# Disables use of , which is not available with some -# development environments (such as very old versions of the -# mingw/Cygwin GNU toolchain). This has the following effects: -# - Pageant won't care about the local user ID of processes -# accessing it; a version of Pageant built with this option -# will therefore refuse to run under NT-series OSes on -# security grounds (although it will run fine on Win95-series -# OSes where there is no access control anyway). -# - SSH connection sharing is disabled. -# - There is no support for restriction of the process ACLs. -# -# - COMPAT=-DNO_MULTIMON (Windows only) -# Disables PuTTY's use of , which is not available -# with some development environments. This means that PuTTY's -# full-screen mode (configurable to work on Alt-Enter) will -# not behave usefully in a multi-monitor environment. -# -# - COMPAT=-DNO_HTMLHELP (Windows only) -# Disables PuTTY's use of , which is not available -# with some development environments. -# -# If you don't have this header, you may be able to use the copy -# supplied with HTML Help Workshop. -# -# - RCFL=-DNO_MANIFESTS (Windows only) -# Disables inclusion of XML application manifests in the PuTTY -# binaries. This may be necessary to build for 64-bit Windows; -# the manifests are only included to use the XP GUI style on -# Windows XP, and the architecture tags are a lie on 64-bit. -# -# - COMPAT=-DNO_IPV6 -# Disables PuTTY's ability to make IPv6 connections, enabling -# it to compile under development environments which do not -# support IPv6 in their header files. -# -# - COMPAT=-DNO_GSSAPI -# Disables PuTTY's ability to use GSSAPI functions for -# authentication and key exchange. -# -# - COMPAT=-DSTATIC_GSSAPI -# Causes PuTTY to try to link statically against the GSSAPI -# library instead of the default of doing it at run time. -# -# - COMPAT=-DMSVC4 (Windows only) -# - RCFL=-DMSVC4 -# Makes a couple of minor changes so that PuTTY compiles using -# MSVC 4. You will also need -DNO_SECURITY and -DNO_MULTIMON. -# -# - COMPAT=-DNO_SECUREZEROMEMORY (Windows only) -# Disables PuTTY's use of SecureZeroMemory(), which is missing -# from some environments' header files. -# -# - XFLAGS=-DDEBUG -# Causes PuTTY to enable internal debugging. -# -# - XFLAGS=-DMALLOC_LOG -# Causes PuTTY to emit a file called putty_mem.log, logging every -# memory allocation and free, so you can track memory leaks. -# -# - XFLAGS=-DMINEFIELD (Windows only) -# Causes PuTTY to use a custom memory allocator, similar in -# concept to Electric Fence, in place of regular malloc(). Wastes -# huge amounts of RAM, but should cause heap-corruption bugs to -# show up as GPFs at the point of failure rather than appearing -# later on as second-level damage. -# -# - XFLAGS=-DFUZZING -# Builds a version of PuTTY with some tweaks to make fuzz testing -# easier: the SSH random number generator is replaced by one that -# always returns the same thing. Note that this makes SSH -# completely insecure -- a FUZZING build should never be used to -# connect to a real server. - -# If you rename this file to `Makefile', you should change this line, -# so that the .rsp files still depend on the correct makefile. -MAKEFILE = Makefile.lcc - -# C compilation flags -CFLAGS = -D_WINDOWS -I..\./ -I..\charset/ -I..\windows/ -I..\unix/ -# Resource compilation flags -RCFLAGS = -I..\./ -I..\charset/ -I..\windows/ -I..\unix/ - -# Get include directory for resource compiler - - -all: pageant.exe plink.exe pscp.exe psftp.exe psocks.exe putty.exe \ - puttygen.exe puttytel.exe testcrypt.exe - -pageant.exe: aqsync.obj be_misc.obj callback.obj conf.obj ecc.obj \ - errsock.obj marshal.obj memory.obj misc.obj mpint.obj \ - pageant.obj pageant.res sshaes.obj sshargon2.obj \ - sshauxcrypt.obj sshblake2.obj sshdes.obj sshdss.obj \ - sshecc.obj sshhmac.obj sshmd5.obj sshpubk.obj sshrsa.obj \ - sshsh256.obj sshsh512.obj sshsha.obj sshsha3.obj \ - stripctrl.obj tree234.obj utils.obj version.obj wcwidth.obj \ - wincapi.obj winhandl.obj winhelp.obj winhsock.obj \ - winmisc.obj winmiscs.obj winnet.obj winnpc.obj winnps.obj \ - winpgnt.obj winpgntc.obj winsecur.obj winselgui.obj \ - winutils.obj - lcclnk -subsystem windows -o pageant.exe aqsync.obj be_misc.obj \ - callback.obj conf.obj ecc.obj errsock.obj marshal.obj \ - memory.obj misc.obj mpint.obj pageant.obj pageant.res \ - sshaes.obj sshargon2.obj sshauxcrypt.obj sshblake2.obj \ - sshdes.obj sshdss.obj sshecc.obj sshhmac.obj sshmd5.obj \ - sshpubk.obj sshrsa.obj sshsh256.obj sshsh512.obj sshsha.obj \ - sshsha3.obj stripctrl.obj tree234.obj utils.obj version.obj \ - wcwidth.obj wincapi.obj winhandl.obj winhelp.obj \ - winhsock.obj winmisc.obj winmiscs.obj winnet.obj winnpc.obj \ - winnps.obj winpgnt.obj winpgntc.obj winsecur.obj \ - winselgui.obj winutils.obj shell32.lib wsock32.lib \ - ws2_32.lib winspool.lib winmm.lib imm32.lib - -plink.exe: agentf.obj aqsync.obj be_all_s.obj be_misc.obj callback.obj \ - clicons.obj cmdline.obj conf.obj console.obj cproxy.obj \ - ecc.obj errsock.obj ldisc.obj logging.obj mainchan.obj \ - marshal.obj memory.obj misc.obj miscucs.obj mpint.obj \ - noterm.obj nullplug.obj pgssapi.obj pinger.obj plink.res \ - portfwd.obj proxy.obj raw.obj rlogin.obj sessprep.obj \ - settings.obj ssh.obj ssh1bpp.obj ssh1censor.obj \ - ssh1connection.obj ssh1connection-client.obj ssh1login.obj \ - ssh2bpp.obj ssh2bpp-bare.obj ssh2censor.obj \ - ssh2connection.obj ssh2connection-client.obj \ - ssh2kex-client.obj ssh2transhk.obj ssh2transport.obj \ - ssh2userauth.obj sshaes.obj ssharcf.obj sshargon2.obj \ - sshauxcrypt.obj sshblake2.obj sshblowf.obj sshccp.obj \ - sshcommon.obj sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj \ - sshdss.obj sshecc.obj sshgssc.obj sshhmac.obj sshmac.obj \ - sshmd5.obj sshprng.obj sshpubk.obj sshrand.obj sshrsa.obj \ - sshsh256.obj sshsh512.obj sshsha.obj sshsha3.obj \ - sshshare.obj sshutils.obj sshverstring.obj sshzlib.obj \ - stripctrl.obj supdup.obj telnet.obj timing.obj tree234.obj \ - utils.obj version.obj wcwidth.obj wildcard.obj wincapi.obj \ - wincliloop.obj wincons.obj windefs.obj wingss.obj \ - winhandl.obj winhsock.obj winmisc.obj winmiscs.obj \ - winnet.obj winnohlp.obj winnoise.obj winnojmp.obj winnpc.obj \ - winnps.obj winpgntc.obj winplink.obj winproxy.obj \ - winsecur.obj winselcli.obj winser.obj winshare.obj \ - winstore.obj wintime.obj winucs.obj winx11.obj x11fwd.obj - lcclnk -o plink.exe agentf.obj aqsync.obj be_all_s.obj be_misc.obj \ - callback.obj clicons.obj cmdline.obj conf.obj console.obj \ - cproxy.obj ecc.obj errsock.obj ldisc.obj logging.obj \ - mainchan.obj marshal.obj memory.obj misc.obj miscucs.obj \ - mpint.obj noterm.obj nullplug.obj pgssapi.obj pinger.obj \ - plink.res portfwd.obj proxy.obj raw.obj rlogin.obj \ - sessprep.obj settings.obj ssh.obj ssh1bpp.obj ssh1censor.obj \ - ssh1connection.obj ssh1connection-client.obj ssh1login.obj \ - ssh2bpp.obj ssh2bpp-bare.obj ssh2censor.obj \ - ssh2connection.obj ssh2connection-client.obj \ - ssh2kex-client.obj ssh2transhk.obj ssh2transport.obj \ - ssh2userauth.obj sshaes.obj ssharcf.obj sshargon2.obj \ - sshauxcrypt.obj sshblake2.obj sshblowf.obj sshccp.obj \ - sshcommon.obj sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj \ - sshdss.obj sshecc.obj sshgssc.obj sshhmac.obj sshmac.obj \ - sshmd5.obj sshprng.obj sshpubk.obj sshrand.obj sshrsa.obj \ - sshsh256.obj sshsh512.obj sshsha.obj sshsha3.obj \ - sshshare.obj sshutils.obj sshverstring.obj sshzlib.obj \ - stripctrl.obj supdup.obj telnet.obj timing.obj tree234.obj \ - utils.obj version.obj wcwidth.obj wildcard.obj wincapi.obj \ - wincliloop.obj wincons.obj windefs.obj wingss.obj \ - winhandl.obj winhsock.obj winmisc.obj winmiscs.obj \ - winnet.obj winnohlp.obj winnoise.obj winnojmp.obj winnpc.obj \ - winnps.obj winpgntc.obj winplink.obj winproxy.obj \ - winsecur.obj winselcli.obj winser.obj winshare.obj \ - winstore.obj wintime.obj winucs.obj winx11.obj x11fwd.obj \ - shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib \ - imm32.lib - -pscp.exe: agentf.obj aqsync.obj be_misc.obj be_ssh.obj callback.obj \ - clicons.obj cmdline.obj conf.obj console.obj cproxy.obj \ - ecc.obj errsock.obj logging.obj mainchan.obj marshal.obj \ - memory.obj misc.obj miscucs.obj mpint.obj nullplug.obj \ - pgssapi.obj pinger.obj portfwd.obj proxy.obj pscp.obj \ - pscp.res psftpcommon.obj settings.obj sftp.obj \ - sftpcommon.obj ssh.obj ssh1bpp.obj ssh1censor.obj \ - ssh1connection.obj ssh1connection-client.obj ssh1login.obj \ - ssh2bpp.obj ssh2bpp-bare.obj ssh2censor.obj \ - ssh2connection.obj ssh2connection-client.obj \ - ssh2kex-client.obj ssh2transhk.obj ssh2transport.obj \ - ssh2userauth.obj sshaes.obj ssharcf.obj sshargon2.obj \ - sshauxcrypt.obj sshblake2.obj sshblowf.obj sshccp.obj \ - sshcommon.obj sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj \ - sshdss.obj sshecc.obj sshgssc.obj sshhmac.obj sshmac.obj \ - sshmd5.obj sshprng.obj sshpubk.obj sshrand.obj sshrsa.obj \ - sshsh256.obj sshsh512.obj sshsha.obj sshsha3.obj \ - sshshare.obj sshutils.obj sshverstring.obj sshzlib.obj \ - stripctrl.obj timing.obj tree234.obj utils.obj version.obj \ - wcwidth.obj wildcard.obj wincapi.obj wincliloop.obj \ - wincons.obj windefs.obj wingss.obj winhandl.obj winhsock.obj \ - winmisc.obj winmiscs.obj winnet.obj winnohlp.obj \ - winnoise.obj winnojmp.obj winnpc.obj winnps.obj winpgntc.obj \ - winproxy.obj winsecur.obj winselcli.obj winsftp.obj \ - winshare.obj winstore.obj wintime.obj winucs.obj x11fwd.obj - lcclnk -o pscp.exe agentf.obj aqsync.obj be_misc.obj be_ssh.obj \ - callback.obj clicons.obj cmdline.obj conf.obj console.obj \ - cproxy.obj ecc.obj errsock.obj logging.obj mainchan.obj \ - marshal.obj memory.obj misc.obj miscucs.obj mpint.obj \ - nullplug.obj pgssapi.obj pinger.obj portfwd.obj proxy.obj \ - pscp.obj pscp.res psftpcommon.obj settings.obj sftp.obj \ - sftpcommon.obj ssh.obj ssh1bpp.obj ssh1censor.obj \ - ssh1connection.obj ssh1connection-client.obj ssh1login.obj \ - ssh2bpp.obj ssh2bpp-bare.obj ssh2censor.obj \ - ssh2connection.obj ssh2connection-client.obj \ - ssh2kex-client.obj ssh2transhk.obj ssh2transport.obj \ - ssh2userauth.obj sshaes.obj ssharcf.obj sshargon2.obj \ - sshauxcrypt.obj sshblake2.obj sshblowf.obj sshccp.obj \ - sshcommon.obj sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj \ - sshdss.obj sshecc.obj sshgssc.obj sshhmac.obj sshmac.obj \ - sshmd5.obj sshprng.obj sshpubk.obj sshrand.obj sshrsa.obj \ - sshsh256.obj sshsh512.obj sshsha.obj sshsha3.obj \ - sshshare.obj sshutils.obj sshverstring.obj sshzlib.obj \ - stripctrl.obj timing.obj tree234.obj utils.obj version.obj \ - wcwidth.obj wildcard.obj wincapi.obj wincliloop.obj \ - wincons.obj windefs.obj wingss.obj winhandl.obj winhsock.obj \ - winmisc.obj winmiscs.obj winnet.obj winnohlp.obj \ - winnoise.obj winnojmp.obj winnpc.obj winnps.obj winpgntc.obj \ - winproxy.obj winsecur.obj winselcli.obj winsftp.obj \ - winshare.obj winstore.obj wintime.obj winucs.obj x11fwd.obj \ - shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib \ - imm32.lib - -psftp.exe: agentf.obj aqsync.obj be_misc.obj be_ssh.obj callback.obj \ - clicons.obj cmdline.obj conf.obj console.obj cproxy.obj \ - ecc.obj errsock.obj logging.obj mainchan.obj marshal.obj \ - memory.obj misc.obj miscucs.obj mpint.obj nullplug.obj \ - pgssapi.obj pinger.obj portfwd.obj proxy.obj psftp.obj \ - psftp.res psftpcommon.obj settings.obj sftp.obj \ - sftpcommon.obj ssh.obj ssh1bpp.obj ssh1censor.obj \ - ssh1connection.obj ssh1connection-client.obj ssh1login.obj \ - ssh2bpp.obj ssh2bpp-bare.obj ssh2censor.obj \ - ssh2connection.obj ssh2connection-client.obj \ - ssh2kex-client.obj ssh2transhk.obj ssh2transport.obj \ - ssh2userauth.obj sshaes.obj ssharcf.obj sshargon2.obj \ - sshauxcrypt.obj sshblake2.obj sshblowf.obj sshccp.obj \ - sshcommon.obj sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj \ - sshdss.obj sshecc.obj sshgssc.obj sshhmac.obj sshmac.obj \ - sshmd5.obj sshprng.obj sshpubk.obj sshrand.obj sshrsa.obj \ - sshsh256.obj sshsh512.obj sshsha.obj sshsha3.obj \ - sshshare.obj sshutils.obj sshverstring.obj sshzlib.obj \ - stripctrl.obj timing.obj tree234.obj utils.obj version.obj \ - wcwidth.obj wildcard.obj wincapi.obj wincliloop.obj \ - wincons.obj windefs.obj wingss.obj winhandl.obj winhsock.obj \ - winmisc.obj winmiscs.obj winnet.obj winnohlp.obj \ - winnoise.obj winnojmp.obj winnpc.obj winnps.obj winpgntc.obj \ - winproxy.obj winsecur.obj winselcli.obj winsftp.obj \ - winshare.obj winstore.obj wintime.obj winucs.obj x11fwd.obj - lcclnk -o psftp.exe agentf.obj aqsync.obj be_misc.obj be_ssh.obj \ - callback.obj clicons.obj cmdline.obj conf.obj console.obj \ - cproxy.obj ecc.obj errsock.obj logging.obj mainchan.obj \ - marshal.obj memory.obj misc.obj miscucs.obj mpint.obj \ - nullplug.obj pgssapi.obj pinger.obj portfwd.obj proxy.obj \ - psftp.obj psftp.res psftpcommon.obj settings.obj sftp.obj \ - sftpcommon.obj ssh.obj ssh1bpp.obj ssh1censor.obj \ - ssh1connection.obj ssh1connection-client.obj ssh1login.obj \ - ssh2bpp.obj ssh2bpp-bare.obj ssh2censor.obj \ - ssh2connection.obj ssh2connection-client.obj \ - ssh2kex-client.obj ssh2transhk.obj ssh2transport.obj \ - ssh2userauth.obj sshaes.obj ssharcf.obj sshargon2.obj \ - sshauxcrypt.obj sshblake2.obj sshblowf.obj sshccp.obj \ - sshcommon.obj sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj \ - sshdss.obj sshecc.obj sshgssc.obj sshhmac.obj sshmac.obj \ - sshmd5.obj sshprng.obj sshpubk.obj sshrand.obj sshrsa.obj \ - sshsh256.obj sshsh512.obj sshsha.obj sshsha3.obj \ - sshshare.obj sshutils.obj sshverstring.obj sshzlib.obj \ - stripctrl.obj timing.obj tree234.obj utils.obj version.obj \ - wcwidth.obj wildcard.obj wincapi.obj wincliloop.obj \ - wincons.obj windefs.obj wingss.obj winhandl.obj winhsock.obj \ - winmisc.obj winmiscs.obj winnet.obj winnohlp.obj \ - winnoise.obj winnojmp.obj winnpc.obj winnps.obj winpgntc.obj \ - winproxy.obj winsecur.obj winselcli.obj winsftp.obj \ - winshare.obj winstore.obj wintime.obj winucs.obj x11fwd.obj \ - shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib \ - imm32.lib - -psocks.exe: be_misc.obj callback.obj conf.obj console.obj errsock.obj \ - logging.obj marshal.obj memory.obj misc.obj nocproxy.obj \ - norand.obj portfwd.obj proxy.obj psocks.obj sshutils.obj \ - stripctrl.obj time.obj timing.obj tree234.obj utils.obj \ - version.obj wcwidth.obj wincliloop.obj wincons.obj \ - winhandl.obj winhsock.obj winmisc.obj winmiscs.obj \ - winnet.obj winnohlp.obj winproxy.obj winselcli.obj \ - winsocks.obj - lcclnk -o psocks.exe be_misc.obj callback.obj conf.obj console.obj \ - errsock.obj logging.obj marshal.obj memory.obj misc.obj \ - nocproxy.obj norand.obj portfwd.obj proxy.obj psocks.obj \ - sshutils.obj stripctrl.obj time.obj timing.obj tree234.obj \ - utils.obj version.obj wcwidth.obj wincliloop.obj wincons.obj \ - winhandl.obj winhsock.obj winmisc.obj winmiscs.obj \ - winnet.obj winnohlp.obj winproxy.obj winselcli.obj \ - winsocks.obj shell32.lib wsock32.lib ws2_32.lib winspool.lib \ - winmm.lib imm32.lib - -putty.exe: agentf.obj aqsync.obj be_all_s.obj be_misc.obj callback.obj \ - cmdline.obj conf.obj config.obj cproxy.obj dialog.obj \ - ecc.obj errsock.obj ldisc.obj logging.obj mainchan.obj \ - marshal.obj memory.obj minibidi.obj misc.obj miscucs.obj \ - mpint.obj nullplug.obj pgssapi.obj pinger.obj portfwd.obj \ - proxy.obj putty.res raw.obj rlogin.obj sessprep.obj \ - settings.obj sizetip.obj ssh.obj ssh1bpp.obj ssh1censor.obj \ - ssh1connection.obj ssh1connection-client.obj ssh1login.obj \ - ssh2bpp.obj ssh2bpp-bare.obj ssh2censor.obj \ - ssh2connection.obj ssh2connection-client.obj \ - ssh2kex-client.obj ssh2transhk.obj ssh2transport.obj \ - ssh2userauth.obj sshaes.obj ssharcf.obj sshargon2.obj \ - sshauxcrypt.obj sshblake2.obj sshblowf.obj sshccp.obj \ - sshcommon.obj sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj \ - sshdss.obj sshecc.obj sshgssc.obj sshhmac.obj sshmac.obj \ - sshmd5.obj sshprng.obj sshpubk.obj sshrand.obj sshrsa.obj \ - sshsh256.obj sshsh512.obj sshsha.obj sshsha3.obj \ - sshshare.obj sshutils.obj sshverstring.obj sshzlib.obj \ - stripctrl.obj supdup.obj telnet.obj terminal.obj timing.obj \ - tree234.obj utils.obj version.obj wcwidth.obj wildcard.obj \ - wincapi.obj wincfg.obj winctrls.obj windefs.obj windlg.obj \ - window.obj wingss.obj winhandl.obj winhelp.obj winhsock.obj \ - winjump.obj winmisc.obj winmiscs.obj winnet.obj winnoise.obj \ - winnpc.obj winnps.obj winpgntc.obj winprint.obj winproxy.obj \ - winsecur.obj winselgui.obj winser.obj winshare.obj \ - winstore.obj wintime.obj winucs.obj winutils.obj winx11.obj \ - x11fwd.obj - lcclnk -subsystem windows -o putty.exe agentf.obj aqsync.obj be_all_s.obj \ - be_misc.obj callback.obj cmdline.obj conf.obj config.obj \ - cproxy.obj dialog.obj ecc.obj errsock.obj ldisc.obj \ - logging.obj mainchan.obj marshal.obj memory.obj minibidi.obj \ - misc.obj miscucs.obj mpint.obj nullplug.obj pgssapi.obj \ - pinger.obj portfwd.obj proxy.obj putty.res raw.obj \ - rlogin.obj sessprep.obj settings.obj sizetip.obj ssh.obj \ - ssh1bpp.obj ssh1censor.obj ssh1connection.obj \ - ssh1connection-client.obj ssh1login.obj ssh2bpp.obj \ - ssh2bpp-bare.obj ssh2censor.obj ssh2connection.obj \ - ssh2connection-client.obj ssh2kex-client.obj ssh2transhk.obj \ - ssh2transport.obj ssh2userauth.obj sshaes.obj ssharcf.obj \ - sshargon2.obj sshauxcrypt.obj sshblake2.obj sshblowf.obj \ - sshccp.obj sshcommon.obj sshcrc.obj sshcrcda.obj sshdes.obj \ - sshdh.obj sshdss.obj sshecc.obj sshgssc.obj sshhmac.obj \ - sshmac.obj sshmd5.obj sshprng.obj sshpubk.obj sshrand.obj \ - sshrsa.obj sshsh256.obj sshsh512.obj sshsha.obj sshsha3.obj \ - sshshare.obj sshutils.obj sshverstring.obj sshzlib.obj \ - stripctrl.obj supdup.obj telnet.obj terminal.obj timing.obj \ - tree234.obj utils.obj version.obj wcwidth.obj wildcard.obj \ - wincapi.obj wincfg.obj winctrls.obj windefs.obj windlg.obj \ - window.obj wingss.obj winhandl.obj winhelp.obj winhsock.obj \ - winjump.obj winmisc.obj winmiscs.obj winnet.obj winnoise.obj \ - winnpc.obj winnps.obj winpgntc.obj winprint.obj winproxy.obj \ - winsecur.obj winselgui.obj winser.obj winshare.obj \ - winstore.obj wintime.obj winucs.obj winutils.obj winx11.obj \ - x11fwd.obj shell32.lib wsock32.lib ws2_32.lib winspool.lib \ - winmm.lib imm32.lib - -puttygen.exe: conf.obj ecc.obj import.obj marshal.obj memory.obj \ - millerrabin.obj misc.obj mpint.obj mpunsafe.obj notiming.obj \ - pockle.obj primecandidate.obj puttygen.res smallprimes.obj \ - sshaes.obj sshargon2.obj sshauxcrypt.obj sshbcrypt.obj \ - sshblake2.obj sshblowf.obj sshdes.obj sshdss.obj sshdssg.obj \ - sshecc.obj sshecdsag.obj sshhmac.obj sshmd5.obj sshprime.obj \ - sshprng.obj sshpubk.obj sshrand.obj sshrsa.obj sshrsag.obj \ - sshsh256.obj sshsh512.obj sshsha.obj sshsha3.obj \ - stripctrl.obj tree234.obj utils.obj version.obj wcwidth.obj \ - winctrls.obj winhelp.obj winmisc.obj winmiscs.obj \ - winnoise.obj winnojmp.obj winpgen.obj winsecur.obj \ - winstore.obj wintime.obj winutils.obj - lcclnk -subsystem windows -o puttygen.exe conf.obj ecc.obj import.obj \ - marshal.obj memory.obj millerrabin.obj misc.obj mpint.obj \ - mpunsafe.obj notiming.obj pockle.obj primecandidate.obj \ - puttygen.res smallprimes.obj sshaes.obj sshargon2.obj \ - sshauxcrypt.obj sshbcrypt.obj sshblake2.obj sshblowf.obj \ - sshdes.obj sshdss.obj sshdssg.obj sshecc.obj sshecdsag.obj \ - sshhmac.obj sshmd5.obj sshprime.obj sshprng.obj sshpubk.obj \ - sshrand.obj sshrsa.obj sshrsag.obj sshsh256.obj sshsh512.obj \ - sshsha.obj sshsha3.obj stripctrl.obj tree234.obj utils.obj \ - version.obj wcwidth.obj winctrls.obj winhelp.obj winmisc.obj \ - winmiscs.obj winnoise.obj winnojmp.obj winpgen.obj \ - winsecur.obj winstore.obj wintime.obj winutils.obj \ - shell32.lib wsock32.lib ws2_32.lib winspool.lib winmm.lib \ - imm32.lib - -puttytel.exe: be_misc.obj be_nos_s.obj callback.obj cmdline.obj conf.obj \ - config.obj dialog.obj errsock.obj ldisc.obj logging.obj \ - marshal.obj memory.obj minibidi.obj misc.obj miscucs.obj \ - nocproxy.obj nogss.obj norand.obj pinger.obj proxy.obj \ - puttytel.res raw.obj rlogin.obj sessprep.obj settings.obj \ - sizetip.obj stripctrl.obj supdup.obj telnet.obj terminal.obj \ - timing.obj tree234.obj utils.obj version.obj wcwidth.obj \ - wincfg.obj winctrls.obj windefs.obj windlg.obj window.obj \ - winhandl.obj winhelp.obj winhsock.obj winjump.obj \ - winmisc.obj winmiscs.obj winnet.obj winprint.obj \ - winproxy.obj winsecur.obj winselgui.obj winser.obj \ - winstore.obj wintime.obj winucs.obj winutils.obj - lcclnk -subsystem windows -o puttytel.exe be_misc.obj be_nos_s.obj \ - callback.obj cmdline.obj conf.obj config.obj dialog.obj \ - errsock.obj ldisc.obj logging.obj marshal.obj memory.obj \ - minibidi.obj misc.obj miscucs.obj nocproxy.obj nogss.obj \ - norand.obj pinger.obj proxy.obj puttytel.res raw.obj \ - rlogin.obj sessprep.obj settings.obj sizetip.obj \ - stripctrl.obj supdup.obj telnet.obj terminal.obj timing.obj \ - tree234.obj utils.obj version.obj wcwidth.obj wincfg.obj \ - winctrls.obj windefs.obj windlg.obj window.obj winhandl.obj \ - winhelp.obj winhsock.obj winjump.obj winmisc.obj \ - winmiscs.obj winnet.obj winprint.obj winproxy.obj \ - winsecur.obj winselgui.obj winser.obj winstore.obj \ - wintime.obj winucs.obj winutils.obj shell32.lib wsock32.lib \ - ws2_32.lib winspool.lib winmm.lib imm32.lib - -testcrypt.exe: ecc.obj marshal.obj memory.obj millerrabin.obj mpint.obj \ - mpunsafe.obj pockle.obj primecandidate.obj smallprimes.obj \ - sshaes.obj ssharcf.obj sshargon2.obj sshauxcrypt.obj \ - sshblake2.obj sshblowf.obj sshccp.obj sshcrc.obj \ - sshcrcda.obj sshdes.obj sshdh.obj sshdss.obj sshdssg.obj \ - sshecc.obj sshecdsag.obj sshhmac.obj sshmd5.obj sshprime.obj \ - sshprng.obj sshpubk.obj sshrsa.obj sshrsag.obj sshsh256.obj \ - sshsh512.obj sshsha.obj sshsha3.obj testcrypt.obj \ - tree234.obj utils.obj winmiscs.obj - lcclnk -o testcrypt.exe ecc.obj marshal.obj memory.obj millerrabin.obj \ - mpint.obj mpunsafe.obj pockle.obj primecandidate.obj \ - smallprimes.obj sshaes.obj ssharcf.obj sshargon2.obj \ - sshauxcrypt.obj sshblake2.obj sshblowf.obj sshccp.obj \ - sshcrc.obj sshcrcda.obj sshdes.obj sshdh.obj sshdss.obj \ - sshdssg.obj sshecc.obj sshecdsag.obj sshhmac.obj sshmd5.obj \ - sshprime.obj sshprng.obj sshpubk.obj sshrsa.obj sshrsag.obj \ - sshsh256.obj sshsh512.obj sshsha.obj sshsha3.obj \ - testcrypt.obj tree234.obj utils.obj winmiscs.obj shell32.lib \ - wsock32.lib ws2_32.lib winspool.lib winmm.lib imm32.lib - -agentf.obj: ..\agentf.c ..\putty.h ..\ssh.h ..\pageant.h ..\sshchan.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\agentf.c -aqsync.obj: ..\aqsync.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\aqsync.c -be_all_s.obj: ..\be_all_s.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\be_all_s.c -be_misc.obj: ..\be_misc.c ..\putty.h ..\network.h ..\defs.h ..\puttyps.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\be_misc.c -be_none.obj: ..\be_none.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\be_none.c -be_nos_s.obj: ..\be_nos_s.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\be_nos_s.c -be_ssh.obj: ..\be_ssh.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\be_ssh.c -callback.obj: ..\callback.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\callback.c -cgtest.obj: ..\cgtest.c ..\cmdgen.c ..\putty.h ..\ssh.h ..\sshkeygen.h \ - ..\mpint.h ..\defs.h ..\puttyps.h ..\network.h ..\misc.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h ..\tree234.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\cgtest.c -clicons.obj: ..\clicons.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\clicons.c -cmdgen.obj: ..\cmdgen.c ..\putty.h ..\ssh.h ..\sshkeygen.h ..\mpint.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\cmdgen.c -cmdline.obj: ..\cmdline.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\cmdline.c -conf.obj: ..\conf.c ..\tree234.h ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\conf.c -config.obj: ..\config.c ..\putty.h ..\dialog.h ..\storage.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\config.c -console.obj: ..\console.c ..\putty.h ..\misc.h ..\console.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\console.c -cproxy.obj: ..\cproxy.c ..\putty.h ..\ssh.h ..\network.h ..\proxy.h \ - ..\marshal.h ..\defs.h ..\puttyps.h ..\misc.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\cproxy.c -dialog.obj: ..\dialog.c ..\putty.h ..\dialog.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\dialog.c -ecc.obj: ..\ecc.c ..\ssh.h ..\mpint.h ..\ecc.h ..\puttymem.h ..\tree234.h \ - ..\network.h ..\misc.h ..\sshttymodes.h ..\defs.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ecc.c -errsock.obj: ..\errsock.c ..\tree234.h ..\putty.h ..\network.h ..\defs.h \ - ..\puttyps.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\errsock.c -fromucs.obj: ..\charset\fromucs.c ..\charset\charset.h ..\charset\internal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\fromucs.c -fuzzterm.obj: ..\fuzzterm.c ..\putty.h ..\dialog.h ..\terminal.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\tree234.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\fuzzterm.c -gtkapp.obj: ..\unix\gtkapp.c ..\putty.h ..\unix\gtkmisc.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkapp.c -gtkask.obj: ..\unix\gtkask.c ..\defs.h ..\unix\gtkfont.h ..\unix\gtkcompat.h \ - ..\unix\gtkmisc.h ..\putty.h ..\ssh.h ..\misc.h ..\puttyps.h \ - ..\network.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\tree234.h ..\sshttymodes.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkask.c -gtkcfg.obj: ..\unix\gtkcfg.c ..\putty.h ..\dialog.h ..\storage.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkcfg.c -gtkcols.obj: ..\unix\gtkcols.c ..\defs.h ..\unix\gtkcompat.h \ - ..\unix\gtkcols.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkcols.c -gtkcomm.obj: ..\unix\gtkcomm.c ..\putty.h ..\terminal.h ..\unix\gtkcompat.h \ - ..\unix\gtkfont.h ..\unix\gtkmisc.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\tree234.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkcomm.c -gtkdlg.obj: ..\unix\gtkdlg.c ..\putty.h ..\unix\gtkcompat.h \ - ..\unix\gtkcols.h ..\unix\gtkfont.h ..\unix\gtkmisc.h \ - ..\unix\x11misc.h ..\storage.h ..\dialog.h ..\tree234.h \ - ..\licence.h ..\ssh.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkdlg.c -gtkfont.obj: ..\unix\gtkfont.c ..\putty.h ..\unix\gtkfont.h \ - ..\unix\gtkcompat.h ..\unix\gtkmisc.h ..\tree234.h \ - ..\unix\x11misc.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkfont.c -gtkmain.obj: ..\unix\gtkmain.c ..\putty.h ..\terminal.h ..\unix\gtkcompat.h \ - ..\unix\gtkfont.h ..\unix\gtkmisc.h ..\unix\x11misc.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\tree234.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkmain.c -gtkmisc.obj: ..\unix\gtkmisc.c ..\putty.h ..\unix\gtkcompat.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkmisc.c -gtkwin.obj: ..\unix\gtkwin.c ..\putty.h ..\terminal.h ..\unix\gtkcompat.h \ - ..\unix\gtkfont.h ..\unix\gtkmisc.h ..\unix\x11misc.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\tree234.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\gtkwin.c -import.obj: ..\import.c ..\putty.h ..\ssh.h ..\mpint.h ..\misc.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\import.c -ldisc.obj: ..\ldisc.c ..\putty.h ..\terminal.h ..\ldisc.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\tree234.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ldisc.c -localenc.obj: ..\charset\localenc.c ..\charset\charset.h \ - ..\charset\internal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\localenc.c -logging.obj: ..\logging.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\logging.c -macenc.obj: ..\charset\macenc.c ..\charset\charset.h ..\charset\internal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\macenc.c -mainchan.obj: ..\mainchan.c ..\putty.h ..\ssh.h ..\sshppl.h ..\sshchan.h \ - ..\sshsignals.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\puttymem.h ..\tree234.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\mainchan.c -marshal.obj: ..\marshal.c ..\marshal.h ..\misc.h ..\defs.h ..\puttymem.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\marshal.c -memory.obj: ..\memory.c ..\defs.h ..\puttymem.h ..\misc.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\memory.c -millerrabin.obj: ..\millerrabin.c ..\ssh.h ..\sshkeygen.h ..\mpint.h \ - ..\mpunsafe.h ..\puttymem.h ..\tree234.h ..\network.h \ - ..\misc.h ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\millerrabin.c -mimeenc.obj: ..\charset\mimeenc.c ..\charset\charset.h ..\charset\internal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\mimeenc.c -minibidi.obj: ..\minibidi.c ..\putty.h ..\misc.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\minibidi.c -misc.obj: ..\misc.c ..\defs.h ..\putty.h ..\misc.h ..\puttyps.h ..\network.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\misc.c -miscucs.obj: ..\miscucs.c ..\putty.h ..\misc.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\miscucs.c -mpint.obj: ..\mpint.c ..\defs.h ..\misc.h ..\puttymem.h ..\mpint.h \ - ..\mpint_i.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\mpint.c -mpunsafe.obj: ..\mpunsafe.c ..\defs.h ..\misc.h ..\puttymem.h ..\mpint.h \ - ..\mpint_i.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\mpunsafe.c -nocmdline.obj: ..\nocmdline.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\nocmdline.c -nocproxy.obj: ..\nocproxy.c ..\putty.h ..\network.h ..\proxy.h ..\defs.h \ - ..\puttyps.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\nocproxy.c -nogss.obj: ..\nogss.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\nogss.c -norand.obj: ..\norand.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\norand.c -noterm.obj: ..\noterm.c ..\putty.h ..\terminal.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\tree234.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\noterm.c -notiming.obj: ..\notiming.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\notiming.c -nullplug.obj: ..\nullplug.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\nullplug.c -osxlaunch.obj: ..\unix\osxlaunch.c - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\osxlaunch.c -pageant.obj: ..\pageant.c ..\putty.h ..\mpint.h ..\ssh.h ..\sshcr.h \ - ..\pageant.h ..\defs.h ..\puttyps.h ..\network.h ..\misc.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h ..\tree234.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\pageant.c -pageant.res: ..\windows\pageant.rc ..\windows\rcstuff.h \ - ..\windows\pageant-rc.h ..\windows\winhelp.rc2 \ - ..\windows\pageant.ico ..\windows\pageants.ico \ - ..\windows\version.rc2 ..\windows\pageant.mft \ - ..\windows\win_res.h ..\version.h ..\licence.h - lrc $(RCFL) -r $(RCFLAGS) ..\windows\pageant.rc -pgssapi.obj: ..\pgssapi.c ..\putty.h ..\pgssapi.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\pgssapi.c -pinger.obj: ..\pinger.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\pinger.c -plink.res: ..\windows\plink.rc ..\windows\rcstuff.h ..\windows\putty.ico \ - ..\windows\version.rc2 ..\version.h ..\licence.h - lrc $(RCFL) -r $(RCFLAGS) ..\windows\plink.rc -pockle.obj: ..\pockle.c ..\ssh.h ..\sshkeygen.h ..\mpint.h ..\mpunsafe.h \ - ..\tree234.h ..\puttymem.h ..\network.h ..\misc.h \ - ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\pockle.c -portfwd.obj: ..\portfwd.c ..\putty.h ..\ssh.h ..\sshchan.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\portfwd.c -primecandidate.obj: ..\primecandidate.c ..\ssh.h ..\mpint.h ..\mpunsafe.h \ - ..\sshkeygen.h ..\puttymem.h ..\tree234.h ..\network.h \ - ..\misc.h ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\primecandidate.c -procnet.obj: ..\unix\procnet.c ..\misc.h ..\defs.h ..\puttymem.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\procnet.c -proxy.obj: ..\proxy.c ..\putty.h ..\network.h ..\proxy.h ..\defs.h \ - ..\puttyps.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\proxy.c -pscp.obj: ..\pscp.c ..\putty.h ..\psftp.h ..\ssh.h ..\sftp.h ..\storage.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\pscp.c -pscp.res: ..\windows\pscp.rc ..\windows\rcstuff.h ..\windows\pscp.ico \ - ..\windows\version.rc2 ..\version.h ..\licence.h - lrc $(RCFL) -r $(RCFLAGS) ..\windows\pscp.rc -psftp.obj: ..\psftp.c ..\putty.h ..\psftp.h ..\storage.h ..\ssh.h ..\sftp.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\psftp.c -psftp.res: ..\windows\psftp.rc ..\windows\rcstuff.h ..\windows\pscp.ico \ - ..\windows\version.rc2 ..\version.h ..\licence.h - lrc $(RCFL) -r $(RCFLAGS) ..\windows\psftp.rc -psftpcommon.obj: ..\psftpcommon.c ..\putty.h ..\sftp.h ..\psftp.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\psftpcommon.c -psocks.obj: ..\psocks.c ..\putty.h ..\misc.h ..\ssh.h ..\sshchan.h \ - ..\psocks.h ..\defs.h ..\puttyps.h ..\network.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\psocks.c -putty.res: ..\windows\putty.rc ..\windows\rcstuff.h ..\windows\winhelp.rc2 \ - ..\windows\win_res.rc2 ..\windows\putty.mft \ - ..\windows\win_res.h ..\windows\putty.ico \ - ..\windows\puttycfg.ico ..\windows\version.rc2 ..\version.h \ - ..\licence.h - lrc $(RCFL) -r $(RCFLAGS) ..\windows\putty.rc -puttygen.res: ..\windows\puttygen.rc ..\windows\rcstuff.h \ - ..\windows\winhelp.rc2 ..\windows\puttygen-rc.h \ - ..\windows\puttygen.ico ..\windows\version.rc2 \ - ..\windows\puttygen.mft ..\windows\win_res.h ..\version.h \ - ..\licence.h - lrc $(RCFL) -r $(RCFLAGS) ..\windows\puttygen.rc -puttytel.res: ..\windows\puttytel.rc ..\windows\rcstuff.h \ - ..\windows\winhelp.rc2 ..\windows\win_res.rc2 \ - ..\windows\puttytel.mft ..\windows\win_res.h \ - ..\windows\putty.ico ..\windows\puttycfg.ico \ - ..\windows\version.rc2 ..\version.h ..\licence.h - lrc $(RCFL) -r $(RCFLAGS) ..\windows\puttytel.rc -raw.obj: ..\raw.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h ..\misc.h \ - ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\raw.c -rlogin.obj: ..\rlogin.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\rlogin.c -sbcs.obj: ..\charset\sbcs.c ..\charset\charset.h ..\charset\internal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\sbcs.c -sbcsdat.obj: ..\charset\sbcsdat.c ..\charset\charset.h ..\charset\internal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\sbcsdat.c -scpserver.obj: ..\scpserver.c ..\putty.h ..\ssh.h ..\sshcr.h ..\sshchan.h \ - ..\sftp.h ..\defs.h ..\puttyps.h ..\network.h ..\misc.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h ..\tree234.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\scpserver.c -sesschan.obj: ..\sesschan.c ..\putty.h ..\ssh.h ..\sshchan.h ..\sshserver.h \ - ..\sftp.h ..\sshsignals.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\puttymem.h \ - ..\tree234.h ..\sshttymodes.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sesschan.c -sessprep.obj: ..\sessprep.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sessprep.c -settings.obj: ..\settings.c ..\putty.h ..\storage.h ..\sshgssc.h ..\sshgss.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\pgssapi.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\settings.c -sftp.obj: ..\sftp.c ..\misc.h ..\tree234.h ..\sftp.h ..\defs.h ..\puttymem.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sftp.c -sftpcommon.obj: ..\sftpcommon.c ..\misc.h ..\sftp.h ..\defs.h ..\puttymem.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sftpcommon.c -sftpserver.obj: ..\sftpserver.c ..\putty.h ..\ssh.h ..\sftp.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sftpserver.c -sizetip.obj: ..\windows\sizetip.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\sizetip.c -slookup.obj: ..\charset\slookup.c ..\charset\charset.h ..\charset\internal.h \ - ..\charset\enum.c ..\charset\sbcsdat.c ..\charset\utf8.c - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\slookup.c -smallprimes.obj: ..\smallprimes.c ..\ssh.h ..\sshkeygen.h ..\puttymem.h \ - ..\tree234.h ..\network.h ..\misc.h ..\sshttymodes.h \ - ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\smallprimes.c -ssh.obj: ..\ssh.c ..\putty.h ..\pageant.h ..\tree234.h ..\storage.h \ - ..\marshal.h ..\ssh.h ..\sshcr.h ..\sshbpp.h ..\sshppl.h \ - ..\sshchan.h ..\sshgssc.h ..\sshgss.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\sshsignals.h ..\puttymem.h \ - ..\sshttymodes.h ..\pgssapi.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh.c -ssh1bpp.obj: ..\ssh1bpp.c ..\putty.h ..\ssh.h ..\sshbpp.h ..\sshcr.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh1bpp.c -ssh1censor.obj: ..\ssh1censor.c ..\putty.h ..\ssh.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh1censor.c -ssh1connection.obj: ..\ssh1connection.c ..\putty.h ..\ssh.h ..\sshbpp.h \ - ..\sshppl.h ..\sshchan.h ..\sshcr.h ..\ssh1connection.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh1connection.c -ssh1connection-client.obj: ..\ssh1connection-client.c ..\putty.h ..\ssh.h \ - ..\sshbpp.h ..\sshppl.h ..\sshchan.h ..\sshcr.h \ - ..\ssh1connection.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\tree234.h ..\sshttymodes.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh1connection-client.c -ssh1connection-server.obj: ..\ssh1connection-server.c ..\putty.h ..\ssh.h \ - ..\sshbpp.h ..\sshppl.h ..\sshchan.h ..\sshcr.h \ - ..\ssh1connection.h ..\sshserver.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh1connection-server.c -ssh1login.obj: ..\ssh1login.c ..\putty.h ..\ssh.h ..\mpint.h ..\sshbpp.h \ - ..\sshppl.h ..\sshcr.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\tree234.h ..\sshttymodes.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh1login.c -ssh1login-server.obj: ..\ssh1login-server.c ..\putty.h ..\mpint.h ..\ssh.h \ - ..\sshbpp.h ..\sshppl.h ..\sshcr.h ..\sshserver.h \ - ..\sshkeygen.h ..\defs.h ..\puttyps.h ..\network.h ..\misc.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h ..\tree234.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh1login-server.c -ssh2bpp.obj: ..\ssh2bpp.c ..\putty.h ..\ssh.h ..\sshbpp.h ..\sshcr.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh2bpp.c -ssh2bpp-bare.obj: ..\ssh2bpp-bare.c ..\putty.h ..\ssh.h ..\sshbpp.h \ - ..\sshcr.h ..\defs.h ..\puttyps.h ..\network.h ..\misc.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h ..\tree234.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh2bpp-bare.c -ssh2censor.obj: ..\ssh2censor.c ..\putty.h ..\ssh.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh2censor.c -ssh2connection.obj: ..\ssh2connection.c ..\putty.h ..\ssh.h ..\sshbpp.h \ - ..\sshppl.h ..\sshchan.h ..\sshcr.h ..\ssh2connection.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh2connection.c -ssh2connection-client.obj: ..\ssh2connection-client.c ..\putty.h ..\ssh.h \ - ..\sshbpp.h ..\sshppl.h ..\sshchan.h ..\sshcr.h \ - ..\ssh2connection.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\tree234.h ..\sshttymodes.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh2connection-client.c -ssh2connection-server.obj: ..\ssh2connection-server.c ..\putty.h ..\ssh.h \ - ..\sshbpp.h ..\sshppl.h ..\sshchan.h ..\sshcr.h \ - ..\ssh2connection.h ..\sshserver.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh2connection-server.c -ssh2kex-client.obj: ..\ssh2kex-client.c ..\putty.h ..\ssh.h ..\sshbpp.h \ - ..\sshppl.h ..\sshcr.h ..\storage.h ..\ssh2transport.h \ - ..\mpint.h ..\defs.h ..\puttyps.h ..\network.h ..\misc.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h ..\tree234.h \ - ..\sshttymodes.h ..\sshgssc.h ..\sshgss.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\pgssapi.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh2kex-client.c -ssh2kex-server.obj: ..\ssh2kex-server.c ..\putty.h ..\ssh.h ..\sshbpp.h \ - ..\sshppl.h ..\sshcr.h ..\sshserver.h ..\sshkeygen.h \ - ..\storage.h ..\ssh2transport.h ..\mpint.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\sshgssc.h ..\sshgss.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\pgssapi.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh2kex-server.c -ssh2transhk.obj: ..\ssh2transhk.c ..\putty.h ..\ssh.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh2transhk.c -ssh2transport.obj: ..\ssh2transport.c ..\putty.h ..\ssh.h ..\sshbpp.h \ - ..\sshppl.h ..\sshcr.h ..\sshserver.h ..\storage.h \ - ..\ssh2transport.h ..\mpint.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\tree234.h ..\sshttymodes.h ..\sshgssc.h \ - ..\sshgss.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\pgssapi.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh2transport.c -ssh2userauth.obj: ..\ssh2userauth.c ..\putty.h ..\ssh.h ..\sshbpp.h \ - ..\sshppl.h ..\sshcr.h ..\sshgssc.h ..\sshgss.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\pgssapi.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh2userauth.c -ssh2userauth-server.obj: ..\ssh2userauth-server.c ..\putty.h ..\ssh.h \ - ..\sshbpp.h ..\sshppl.h ..\sshcr.h ..\sshserver.h \ - ..\sshgssc.h ..\sshgss.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\tree234.h ..\sshttymodes.h ..\pgssapi.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssh2userauth-server.c -sshaes.obj: ..\sshaes.c ..\ssh.h ..\mpint_i.h ..\puttymem.h ..\tree234.h \ - ..\network.h ..\misc.h ..\sshttymodes.h ..\defs.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshaes.c -ssharcf.obj: ..\ssharcf.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ - ..\misc.h ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\ssharcf.c -sshargon2.obj: ..\sshargon2.c ..\putty.h ..\ssh.h ..\marshal.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\sshsignals.h \ - ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshargon2.c -sshauxcrypt.obj: ..\sshauxcrypt.c ..\ssh.h ..\puttymem.h ..\tree234.h \ - ..\network.h ..\misc.h ..\sshttymodes.h ..\defs.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshauxcrypt.c -sshbcrypt.obj: ..\sshbcrypt.c ..\ssh.h ..\sshblowf.h ..\puttymem.h \ - ..\tree234.h ..\network.h ..\misc.h ..\sshttymodes.h \ - ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshbcrypt.c -sshblake2.obj: ..\sshblake2.c ..\ssh.h ..\puttymem.h ..\tree234.h \ - ..\network.h ..\misc.h ..\sshttymodes.h ..\defs.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshblake2.c -sshblowf.obj: ..\sshblowf.c ..\ssh.h ..\sshblowf.h ..\puttymem.h \ - ..\tree234.h ..\network.h ..\misc.h ..\sshttymodes.h \ - ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshblowf.c -sshccp.obj: ..\sshccp.c ..\ssh.h ..\mpint_i.h ..\puttymem.h ..\tree234.h \ - ..\network.h ..\misc.h ..\sshttymodes.h ..\defs.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshccp.c -sshcommon.obj: ..\sshcommon.c ..\putty.h ..\mpint.h ..\ssh.h ..\sshbpp.h \ - ..\sshppl.h ..\sshchan.h ..\sshttymodes.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshcommon.c -sshcrc.obj: ..\sshcrc.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ - ..\misc.h ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshcrc.c -sshcrcda.obj: ..\sshcrcda.c ..\misc.h ..\ssh.h ..\defs.h ..\puttymem.h \ - ..\marshal.h ..\tree234.h ..\network.h ..\sshttymodes.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshcrcda.c -sshdes.obj: ..\sshdes.c ..\ssh.h ..\mpint_i.h ..\puttymem.h ..\tree234.h \ - ..\network.h ..\misc.h ..\sshttymodes.h ..\defs.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshdes.c -sshdh.obj: ..\sshdh.c ..\ssh.h ..\misc.h ..\mpint.h ..\puttymem.h \ - ..\tree234.h ..\network.h ..\sshttymodes.h ..\defs.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshdh.c -sshdss.obj: ..\sshdss.c ..\ssh.h ..\mpint.h ..\misc.h ..\puttymem.h \ - ..\tree234.h ..\network.h ..\sshttymodes.h ..\defs.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshdss.c -sshdssg.obj: ..\sshdssg.c ..\misc.h ..\ssh.h ..\sshkeygen.h ..\mpint.h \ - ..\defs.h ..\puttymem.h ..\marshal.h ..\tree234.h \ - ..\network.h ..\sshttymodes.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshdssg.c -sshecc.obj: ..\sshecc.c ..\ssh.h ..\mpint.h ..\ecc.h ..\puttymem.h \ - ..\tree234.h ..\network.h ..\misc.h ..\sshttymodes.h \ - ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshecc.c -sshecdsag.obj: ..\sshecdsag.c ..\ssh.h ..\sshkeygen.h ..\mpint.h \ - ..\puttymem.h ..\tree234.h ..\network.h ..\misc.h \ - ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshecdsag.c -sshgssc.obj: ..\sshgssc.c ..\putty.h ..\sshgssc.h ..\misc.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\marshal.h ..\sshsignals.h \ - ..\pgssapi.h ..\sshgss.h ..\puttymem.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshgssc.c -sshhmac.obj: ..\sshhmac.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ - ..\misc.h ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshhmac.c -sshmac.obj: ..\sshmac.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ - ..\misc.h ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshmac.c -sshmd5.obj: ..\sshmd5.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ - ..\misc.h ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshmd5.c -sshprime.obj: ..\sshprime.c ..\ssh.h ..\mpint.h ..\mpunsafe.h ..\sshkeygen.h \ - ..\puttymem.h ..\tree234.h ..\network.h ..\misc.h \ - ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshprime.c -sshprng.obj: ..\sshprng.c ..\putty.h ..\ssh.h ..\mpint_i.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshprng.c -sshpubk.obj: ..\sshpubk.c ..\putty.h ..\mpint.h ..\ssh.h ..\misc.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshpubk.c -sshrand.obj: ..\sshrand.c ..\putty.h ..\ssh.h ..\storage.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshrand.c -sshrsa.obj: ..\sshrsa.c ..\ssh.h ..\mpint.h ..\misc.h ..\puttymem.h \ - ..\tree234.h ..\network.h ..\sshttymodes.h ..\defs.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshrsa.c -sshrsag.obj: ..\sshrsag.c ..\ssh.h ..\sshkeygen.h ..\mpint.h ..\puttymem.h \ - ..\tree234.h ..\network.h ..\misc.h ..\sshttymodes.h \ - ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshrsag.c -sshserver.obj: ..\sshserver.c ..\putty.h ..\ssh.h ..\sshbpp.h ..\sshppl.h \ - ..\sshchan.h ..\sshserver.h ..\sshgssc.h ..\sshgss.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\pgssapi.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshserver.c -sshsh256.obj: ..\sshsh256.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ - ..\misc.h ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshsh256.c -sshsh512.obj: ..\sshsh512.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ - ..\misc.h ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshsh512.c -sshsha.obj: ..\sshsha.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ - ..\misc.h ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshsha.c -sshsha3.obj: ..\sshsha3.c ..\ssh.h ..\puttymem.h ..\tree234.h ..\network.h \ - ..\misc.h ..\sshttymodes.h ..\defs.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshsha3.c -sshshare.obj: ..\sshshare.c ..\putty.h ..\tree234.h ..\ssh.h ..\sshcr.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshshare.c -sshutils.obj: ..\sshutils.c ..\putty.h ..\ssh.h ..\sshchan.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshutils.c -sshverstring.obj: ..\sshverstring.c ..\putty.h ..\ssh.h ..\sshbpp.h \ - ..\sshcr.h ..\defs.h ..\puttyps.h ..\network.h ..\misc.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h ..\tree234.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshverstring.c -sshzlib.obj: ..\sshzlib.c ..\defs.h ..\ssh.h ..\puttymem.h ..\tree234.h \ - ..\network.h ..\misc.h ..\sshttymodes.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\sshzlib.c -stripctrl.obj: ..\stripctrl.c ..\putty.h ..\terminal.h ..\misc.h \ - ..\marshal.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\sshsignals.h ..\tree234.h ..\puttymem.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\stripctrl.c -supdup.obj: ..\supdup.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\supdup.c -telnet.obj: ..\telnet.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\telnet.c -terminal.obj: ..\terminal.c ..\putty.h ..\terminal.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\tree234.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\terminal.c -testcrypt.obj: ..\testcrypt.c ..\defs.h ..\ssh.h ..\sshkeygen.h ..\misc.h \ - ..\mpint.h ..\ecc.h ..\testcrypt.h ..\puttymem.h \ - ..\tree234.h ..\network.h ..\sshttymodes.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\testcrypt.c -testsc.obj: ..\testsc.c ..\defs.h ..\putty.h ..\ssh.h ..\misc.h ..\mpint.h \ - ..\ecc.h ..\puttyps.h ..\network.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\testsc.c -testzlib.obj: ..\testzlib.c ..\defs.h ..\ssh.h ..\puttymem.h ..\tree234.h \ - ..\network.h ..\misc.h ..\sshttymodes.h ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\testzlib.c -time.obj: ..\time.c - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\time.c -timing.obj: ..\timing.c ..\putty.h ..\tree234.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\timing.c -toucs.obj: ..\charset\toucs.c ..\charset\charset.h ..\charset\internal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\toucs.c -tree234.obj: ..\tree234.c ..\defs.h ..\tree234.h ..\puttymem.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\tree234.c -utf8.obj: ..\charset\utf8.c ..\charset\charset.h ..\charset\internal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\utf8.c -utils.obj: ..\utils.c ..\defs.h ..\misc.h ..\ssh.h ..\puttymem.h \ - ..\marshal.h ..\tree234.h ..\network.h ..\sshttymodes.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\utils.c -ux_x11.obj: ..\unix\ux_x11.c ..\putty.h ..\ssh.h ..\network.h ..\defs.h \ - ..\puttyps.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\ux_x11.c -uxagentc.obj: ..\unix\uxagentc.c ..\putty.h ..\misc.h ..\tree234.h \ - ..\puttymem.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxagentc.c -uxagentsock.obj: ..\unix\uxagentsock.c ..\putty.h ..\ssh.h ..\misc.h \ - ..\pageant.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h ..\tree234.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxagentsock.c -uxcfg.obj: ..\unix\uxcfg.c ..\putty.h ..\dialog.h ..\storage.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxcfg.c -uxcliloop.obj: ..\unix\uxcliloop.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxcliloop.c -uxcons.obj: ..\unix\uxcons.c ..\putty.h ..\storage.h ..\ssh.h ..\console.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxcons.c -uxfdsock.obj: ..\unix\uxfdsock.c ..\tree234.h ..\putty.h ..\network.h \ - ..\defs.h ..\puttyps.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxfdsock.c -uxgen.obj: ..\unix\uxgen.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxgen.c -uxgss.obj: ..\unix\uxgss.c ..\putty.h ..\pgssapi.h ..\sshgss.h ..\sshgssc.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxgss.c -uxmisc.obj: ..\unix\uxmisc.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxmisc.c -uxnet.obj: ..\unix\uxnet.c ..\putty.h ..\network.h ..\tree234.h ..\defs.h \ - ..\puttyps.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxnet.c -uxnogtk.obj: ..\unix\uxnogtk.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxnogtk.c -uxnoise.obj: ..\unix\uxnoise.c ..\putty.h ..\ssh.h ..\storage.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxnoise.c -uxpeer.obj: ..\unix\uxpeer.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxpeer.c -uxpgnt.obj: ..\unix\uxpgnt.c ..\putty.h ..\ssh.h ..\misc.h ..\pageant.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxpgnt.c -uxplink.obj: ..\unix\uxplink.c ..\putty.h ..\ssh.h ..\storage.h ..\tree234.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxplink.c -uxpoll.obj: ..\unix\uxpoll.c ..\putty.h ..\tree234.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxpoll.c -uxprint.obj: ..\unix\uxprint.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxprint.c -uxproxy.obj: ..\unix\uxproxy.c ..\tree234.h ..\putty.h ..\network.h \ - ..\proxy.h ..\defs.h ..\puttyps.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxproxy.c -uxpsusan.obj: ..\unix\uxpsusan.c ..\putty.h ..\mpint.h ..\ssh.h \ - ..\sshserver.h ..\defs.h ..\puttyps.h ..\network.h ..\misc.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h ..\tree234.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxpsusan.c -uxpterm.obj: ..\unix\uxpterm.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxpterm.c -uxpty.obj: ..\unix\uxpty.c ..\putty.h ..\ssh.h ..\sshserver.h ..\tree234.h \ - ..\sshttymodes.h ..\sshsignals.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\puttymem.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxpty.c -uxputty.obj: ..\unix\uxputty.c ..\putty.h ..\ssh.h ..\storage.h \ - ..\unix\gtkcompat.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\tree234.h ..\sshttymodes.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxputty.c -uxsel.obj: ..\unix\uxsel.c ..\putty.h ..\tree234.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxsel.c -uxser.obj: ..\unix\uxser.c ..\putty.h ..\tree234.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxser.c -uxserver.obj: ..\unix\uxserver.c ..\putty.h ..\mpint.h ..\ssh.h \ - ..\sshserver.h ..\defs.h ..\puttyps.h ..\network.h ..\misc.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h ..\tree234.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxserver.c -uxsftp.obj: ..\unix\uxsftp.c ..\putty.h ..\ssh.h ..\psftp.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxsftp.c -uxsftpserver.obj: ..\unix\uxsftpserver.c ..\putty.h ..\ssh.h ..\sshserver.h \ - ..\sftp.h ..\tree234.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxsftpserver.c -uxshare.obj: ..\unix\uxshare.c ..\tree234.h ..\putty.h ..\network.h \ - ..\proxy.h ..\ssh.h ..\defs.h ..\puttyps.h ..\misc.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxshare.c -uxsignal.obj: ..\unix\uxsignal.c ..\defs.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxsignal.c -uxsocks.obj: ..\unix\uxsocks.c ..\putty.h ..\ssh.h ..\psocks.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxsocks.c -uxstore.obj: ..\unix\uxstore.c ..\putty.h ..\storage.h ..\tree234.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxstore.c -uxucs.obj: ..\unix\uxucs.c ..\putty.h ..\charset\charset.h ..\terminal.h \ - ..\misc.h ..\defs.h ..\puttyps.h ..\network.h ..\marshal.h \ - ..\sshsignals.h ..\tree234.h ..\puttymem.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxucs.c -uxutils.obj: ..\unix\uxutils.c ..\putty.h ..\ssh.h ..\unix\uxutils.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\uxutils.c -version.obj: ..\version.c ..\putty.h ..\ssh.h ..\empty.h ..\version.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\version.c -wcwidth.obj: ..\wcwidth.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\wcwidth.c -wildcard.obj: ..\wildcard.c ..\putty.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\wildcard.c -wincapi.obj: ..\windows\wincapi.c ..\putty.h ..\ssh.h ..\windows\wincapi.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\wincapi.c -wincfg.obj: ..\windows\wincfg.c ..\putty.h ..\dialog.h ..\storage.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\wincfg.c -wincliloop.obj: ..\windows\wincliloop.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\wincliloop.c -wincons.obj: ..\windows\wincons.c ..\putty.h ..\storage.h ..\ssh.h \ - ..\console.h ..\defs.h ..\puttyps.h ..\network.h ..\misc.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h ..\tree234.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\wincons.c -winctrls.obj: ..\windows\winctrls.c ..\putty.h ..\misc.h ..\dialog.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winctrls.c -windefs.obj: ..\windows\windefs.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\windefs.c -windlg.obj: ..\windows\windlg.c ..\putty.h ..\ssh.h ..\windows\win_res.h \ - ..\windows\winseat.h ..\storage.h ..\dialog.h ..\licence.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\windlg.c -window.obj: ..\windows\window.c ..\putty.h ..\terminal.h ..\storage.h \ - ..\windows\win_res.h ..\windows\winsecur.h \ - ..\windows\winseat.h ..\tree234.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\window.c -wingss.obj: ..\windows\wingss.c ..\putty.h ..\pgssapi.h ..\sshgss.h \ - ..\sshgssc.h ..\misc.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\tree234.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\wingss.c -winhandl.obj: ..\windows\winhandl.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winhandl.c -winhelp.obj: ..\windows\winhelp.c ..\putty.h ..\windows\win_res.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winhelp.c -winhsock.obj: ..\windows\winhsock.c ..\tree234.h ..\putty.h ..\network.h \ - ..\defs.h ..\puttyps.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winhsock.c -winjump.obj: ..\windows\winjump.c ..\putty.h ..\storage.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winjump.c -winmisc.obj: ..\windows\winmisc.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winmisc.c -winmiscs.obj: ..\windows\winmiscs.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winmiscs.c -winnet.obj: ..\windows\winnet.c ..\putty.h ..\network.h ..\tree234.h \ - ..\ssh.h ..\defs.h ..\puttyps.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winnet.c -winnohlp.obj: ..\windows\winnohlp.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winnohlp.c -winnoise.obj: ..\windows\winnoise.c ..\putty.h ..\ssh.h ..\storage.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winnoise.c -winnojmp.obj: ..\windows\winnojmp.c - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winnojmp.c -winnpc.obj: ..\windows\winnpc.c ..\tree234.h ..\putty.h ..\network.h \ - ..\proxy.h ..\ssh.h ..\windows\winsecur.h ..\defs.h \ - ..\puttyps.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\sshttymodes.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winnpc.c -winnps.obj: ..\windows\winnps.c ..\tree234.h ..\putty.h ..\network.h \ - ..\proxy.h ..\ssh.h ..\windows\winsecur.h ..\defs.h \ - ..\puttyps.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\sshttymodes.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winnps.c -winpgen.obj: ..\windows\winpgen.c ..\putty.h ..\ssh.h ..\sshkeygen.h \ - ..\licence.h ..\windows\winsecur.h ..\windows\puttygen-rc.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winpgen.c -winpgnt.obj: ..\windows\winpgnt.c ..\putty.h ..\ssh.h ..\misc.h ..\tree234.h \ - ..\windows\winsecur.h ..\windows\wincapi.h ..\pageant.h \ - ..\licence.h ..\windows\pageant-rc.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winpgnt.c -winpgntc.obj: ..\windows\winpgntc.c ..\putty.h ..\pageant.h \ - ..\windows\winsecur.h ..\windows\wincapi.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winpgntc.c -winplink.obj: ..\windows\winplink.c ..\putty.h ..\storage.h ..\tree234.h \ - ..\windows\winsecur.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\puttymem.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winplink.c -winprint.obj: ..\windows\winprint.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winprint.c -winproxy.obj: ..\windows\winproxy.c ..\tree234.h ..\putty.h ..\network.h \ - ..\proxy.h ..\defs.h ..\puttyps.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winproxy.c -winsecur.obj: ..\windows\winsecur.c ..\putty.h ..\windows\winsecur.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winsecur.c -winselcli.obj: ..\windows\winselcli.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winselcli.c -winselgui.obj: ..\windows\winselgui.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winselgui.c -winser.obj: ..\windows\winser.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winser.c -winsftp.obj: ..\windows\winsftp.c ..\putty.h ..\psftp.h ..\ssh.h \ - ..\windows\winsecur.h ..\defs.h ..\puttyps.h ..\network.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\tree234.h ..\sshttymodes.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winsftp.c -winshare.obj: ..\windows\winshare.c ..\tree234.h ..\putty.h ..\network.h \ - ..\proxy.h ..\ssh.h ..\windows\wincapi.h \ - ..\windows\winsecur.h ..\noshare.c ..\defs.h ..\puttyps.h \ - ..\misc.h ..\marshal.h ..\sshsignals.h ..\puttymem.h \ - ..\sshttymodes.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winshare.c -winsocks.obj: ..\windows\winsocks.c ..\putty.h ..\ssh.h ..\psocks.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winsocks.c -winstore.obj: ..\windows\winstore.c ..\putty.h ..\storage.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winstore.c -wintime.obj: ..\windows\wintime.c ..\putty.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\puttymem.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\wintime.c -winucs.obj: ..\windows\winucs.c ..\putty.h ..\terminal.h ..\misc.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\marshal.h ..\sshsignals.h \ - ..\tree234.h ..\puttymem.h ..\windows\winstuff.h \ - ..\unix\unix.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winucs.c -winutils.obj: ..\windows\winutils.c ..\putty.h ..\misc.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\tree234.h ..\windows\winhelp.h ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winutils.c -winx11.obj: ..\windows\winx11.c ..\putty.h ..\ssh.h ..\defs.h ..\puttyps.h \ - ..\network.h ..\misc.h ..\marshal.h ..\sshsignals.h \ - ..\puttymem.h ..\tree234.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\windows\winx11.c -x11fwd.obj: ..\x11fwd.c ..\putty.h ..\ssh.h ..\sshchan.h ..\tree234.h \ - ..\defs.h ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\puttymem.h ..\sshttymodes.h \ - ..\windows\winstuff.h ..\unix\unix.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\x11fwd.c -x11misc.obj: ..\unix\x11misc.c ..\putty.h ..\unix\x11misc.h ..\defs.h \ - ..\puttyps.h ..\network.h ..\misc.h ..\marshal.h \ - ..\sshsignals.h ..\windows\winstuff.h ..\unix\unix.h \ - ..\puttymem.h ..\tree234.h ..\windows\winhelp.h \ - ..\charset\charset.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\x11misc.c -xenc.obj: ..\charset\xenc.c ..\charset\charset.h ..\charset\internal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\charset\xenc.c -xkeysym.obj: ..\unix\xkeysym.c ..\misc.h ..\defs.h ..\puttymem.h \ - ..\marshal.h - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\xkeysym.c -xpmptcfg.obj: ..\unix\xpmptcfg.c - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\xpmptcfg.c -xpmpterm.obj: ..\unix\xpmpterm.c - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\xpmpterm.c -xpmpucfg.obj: ..\unix\xpmpucfg.c - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\xpmpucfg.c -xpmputty.obj: ..\unix\xpmputty.c - lcc -O -p6 $(COMPAT) $(CFLAGS) $(XFLAGS) ..\unix\xpmputty.c - - -clean: - -del *.obj - -del *.exe - -del *.res - -FORCE: diff --git a/WINDOWS/MAKEFILE.NG b/WINDOWS/MAKEFILE.NG deleted file mode 100644 index 9fa55149c..000000000 --- a/WINDOWS/MAKEFILE.NG +++ /dev/null @@ -1,1834 +0,0 @@ -# Makefile for putty under Visual C. -# -# This file was created by `mkfiles.pl' from the `Recipe' file. -# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead. -# -# Extra options you can set: -# -# - COMPAT=/DAUTO_WINSOCK (Windows only) -# Causes PuTTY to assume that includes its own WinSock -# header file, so that it won't try to include . -# -# - COMPAT=/DWINSOCK_TWO (Windows only) -# Causes the PuTTY utilities to include instead of -# , except Plink which _needs_ WinSock 2 so it already -# does this. -# -# - COMPAT=/DNO_SECURITY (Windows only) -# Disables use of , which is not available with some -# development environments (such as very old versions of the -# mingw/Cygwin GNU toolchain). This has the following effects: -# - Pageant won't care about the local user ID of processes -# accessing it; a version of Pageant built with this option -# will therefore refuse to run under NT-series OSes on -# security grounds (although it will run fine on Win95-series -# OSes where there is no access control anyway). -# - SSH connection sharing is disabled. -# - There is no support for restriction of the process ACLs. -# -# - COMPAT=/DNO_MULTIMON (Windows only) -# Disables PuTTY's use of , which is not available -# with some development environments. This means that PuTTY's -# full-screen mode (configurable to work on Alt-Enter) will -# not behave usefully in a multi-monitor environment. -# -# - COMPAT=/DNO_HTMLHELP (Windows only) -# Disables PuTTY's use of , which is not available -# with some development environments. -# -# If you don't have this header, you may be able to use the copy -# supplied with HTML Help Workshop. -# -# - RCFL=/DNO_MANIFESTS (Windows only) -# Disables inclusion of XML application manifests in the PuTTY -# binaries. This may be necessary to build for 64-bit Windows; -# the manifests are only included to use the XP GUI style on -# Windows XP, and the architecture tags are a lie on 64-bit. -# -# - COMPAT=/DNO_IPV6 -# Disables PuTTY's ability to make IPv6 connections, enabling -# it to compile under development environments which do not -# support IPv6 in their header files. -# -# - COMPAT=/DNO_GSSAPI -# Disables PuTTY's ability to use GSSAPI functions for -# authentication and key exchange. -# -# - COMPAT=/DSTATIC_GSSAPI -# Causes PuTTY to try to link statically against the GSSAPI -# library instead of the default of doing it at run time. -# -# - COMPAT=/DMSVC4 (Windows only) -# - RCFL=/DMSVC4 -# Makes a couple of minor changes so that PuTTY compiles using -# MSVC 4. You will also need /DNO_SECURITY and /DNO_MULTIMON. -# -# - COMPAT=/DNO_SECUREZEROMEMORY (Windows only) -# Disables PuTTY's use of SecureZeroMemory(), which is missing -# from some environments' header files. -# -# - XFLAGS=/DDEBUG -# Causes PuTTY to enable internal debugging. -# -# - XFLAGS=/DMALLOC_LOG -# Causes PuTTY to emit a file called putty_mem.log, logging every -# memory allocation and free, so you can track memory leaks. -# -# - XFLAGS=/DMINEFIELD (Windows only) -# Causes PuTTY to use a custom memory allocator, similar in -# concept to Electric Fence, in place of regular malloc(). Wastes -# huge amounts of RAM, but should cause heap-corruption bugs to -# show up as GPFs at the point of failure rather than appearing -# later on as second-level damage. -# -# - XFLAGS=/DFUZZING -# Builds a version of PuTTY with some tweaks to make fuzz testing -# easier: the SSH random number generator is replaced by one that -# always returns the same thing. Note that this makes SSH -# completely insecure -- a FUZZING build should never be used to -# connect to a real server. - -# If you rename this file to `Makefile', you should change this line, -# so that the .rsp files still depend on the correct makefile. -MAKEFILE = MAKEFILE.NG - -# C compilation flags -# Add debugging options (just in case): /Zi /Fd".\Debug" -CFLAGS = /nologo /W3 /O1 -I..\./ -I..\charset/ -I..\windows/ -I..\unix/ /Zi /Fd".\Debug" /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500 /D_CRT_SECURE_NO_WARNINGS /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE - -# Add debugging options (just in case): /PDB:PuTTYNG.pdb /DEBUG /MAP -LFLAGS = /incremental:no /dynamicbase /nxcompat /PDB:PuTTYNG.pdb /DEBUG /MAP - -# Add: -DPUTTYNG -RCFLAGS = -I..\./ -I..\charset/ -I..\windows/ -I..\unix/ -DWIN32 -D_WIN32 -DWINVER=0x0400 -DPUTTYNG - -# Add: /DPUTTYNG -CFLAGS = $(CFLAGS) /DHAS_GSSAPI /DPUTTYNG - -# mRemoteNG only needs PuTTYNG.exe - so only build that target. -all: $(BUILDDIR)PuTTYNG.exe - -$(BUILDDIR)pageant.exe: $(BUILDDIR)aqsync.obj $(BUILDDIR)be_misc.obj \ - $(BUILDDIR)callback.obj $(BUILDDIR)conf.obj \ - $(BUILDDIR)ecc.obj $(BUILDDIR)errsock.obj \ - $(BUILDDIR)marshal.obj $(BUILDDIR)memory.obj \ - $(BUILDDIR)misc.obj $(BUILDDIR)mpint.obj \ - $(BUILDDIR)pageant.obj $(BUILDDIR)pageant.res \ - $(BUILDDIR)sshaes.obj $(BUILDDIR)sshargon2.obj \ - $(BUILDDIR)sshauxcrypt.obj $(BUILDDIR)sshblake2.obj \ - $(BUILDDIR)sshdes.obj $(BUILDDIR)sshdss.obj \ - $(BUILDDIR)sshecc.obj $(BUILDDIR)sshhmac.obj \ - $(BUILDDIR)sshmd5.obj $(BUILDDIR)sshpubk.obj \ - $(BUILDDIR)sshrsa.obj $(BUILDDIR)sshsh256.obj \ - $(BUILDDIR)sshsh512.obj $(BUILDDIR)sshsha.obj \ - $(BUILDDIR)sshsha3.obj $(BUILDDIR)stripctrl.obj \ - $(BUILDDIR)tree234.obj $(BUILDDIR)utils.obj \ - $(BUILDDIR)version.obj $(BUILDDIR)wcwidth.obj \ - $(BUILDDIR)wincapi.obj $(BUILDDIR)winhandl.obj \ - $(BUILDDIR)winhelp.obj $(BUILDDIR)winhsock.obj \ - $(BUILDDIR)winmisc.obj $(BUILDDIR)winmiscs.obj \ - $(BUILDDIR)winnet.obj $(BUILDDIR)winnpc.obj \ - $(BUILDDIR)winnps.obj $(BUILDDIR)winpgnt.obj \ - $(BUILDDIR)winpgntc.obj $(BUILDDIR)winsecur.obj \ - $(BUILDDIR)winselgui.obj $(BUILDDIR)winutils.obj - type < includes its own WinSock -# header file, so that it won't try to include . -# -# - COMPAT=/DWINSOCK_TWO (Windows only) -# Causes the PuTTY utilities to include instead of -# , except Plink which _needs_ WinSock 2 so it already -# does this. -# -# - COMPAT=/DNO_SECURITY (Windows only) -# Disables use of , which is not available with some -# development environments (such as very old versions of the -# mingw/Cygwin GNU toolchain). This has the following effects: -# - Pageant won't care about the local user ID of processes -# accessing it; a version of Pageant built with this option -# will therefore refuse to run under NT-series OSes on -# security grounds (although it will run fine on Win95-series -# OSes where there is no access control anyway). -# - SSH connection sharing is disabled. -# - There is no support for restriction of the process ACLs. -# -# - COMPAT=/DNO_MULTIMON (Windows only) -# Disables PuTTY's use of , which is not available -# with some development environments. This means that PuTTY's -# full-screen mode (configurable to work on Alt-Enter) will -# not behave usefully in a multi-monitor environment. -# -# - COMPAT=/DNO_HTMLHELP (Windows only) -# Disables PuTTY's use of , which is not available -# with some development environments. -# -# If you don't have this header, you may be able to use the copy -# supplied with HTML Help Workshop. -# -# - RCFL=/DNO_MANIFESTS (Windows only) -# Disables inclusion of XML application manifests in the PuTTY -# binaries. This may be necessary to build for 64-bit Windows; -# the manifests are only included to use the XP GUI style on -# Windows XP, and the architecture tags are a lie on 64-bit. -# -# - COMPAT=/DNO_IPV6 -# Disables PuTTY's ability to make IPv6 connections, enabling -# it to compile under development environments which do not -# support IPv6 in their header files. -# -# - COMPAT=/DNO_GSSAPI -# Disables PuTTY's ability to use GSSAPI functions for -# authentication and key exchange. -# -# - COMPAT=/DSTATIC_GSSAPI -# Causes PuTTY to try to link statically against the GSSAPI -# library instead of the default of doing it at run time. -# -# - COMPAT=/DMSVC4 (Windows only) -# - RCFL=/DMSVC4 -# Makes a couple of minor changes so that PuTTY compiles using -# MSVC 4. You will also need /DNO_SECURITY and /DNO_MULTIMON. -# -# - COMPAT=/DNO_SECUREZEROMEMORY (Windows only) -# Disables PuTTY's use of SecureZeroMemory(), which is missing -# from some environments' header files. -# -# - XFLAGS=/DDEBUG -# Causes PuTTY to enable internal debugging. -# -# - XFLAGS=/DMALLOC_LOG -# Causes PuTTY to emit a file called putty_mem.log, logging every -# memory allocation and free, so you can track memory leaks. -# -# - XFLAGS=/DMINEFIELD (Windows only) -# Causes PuTTY to use a custom memory allocator, similar in -# concept to Electric Fence, in place of regular malloc(). Wastes -# huge amounts of RAM, but should cause heap-corruption bugs to -# show up as GPFs at the point of failure rather than appearing -# later on as second-level damage. -# -# - XFLAGS=/DFUZZING -# Builds a version of PuTTY with some tweaks to make fuzz testing -# easier: the SSH random number generator is replaced by one that -# always returns the same thing. Note that this makes SSH -# completely insecure -- a FUZZING build should never be used to -# connect to a real server. - -# If you rename this file to `Makefile', you should change this line, -# so that the .rsp files still depend on the correct makefile. -MAKEFILE = Makefile.vc - -# C compilation flags -CFLAGS = /nologo /W3 /O1 -I..\./ -I..\charset/ -I..\windows/ -I..\unix/ /D_WINDOWS /D_WIN32_WINDOWS=0x500 /DWINVER=0x500 /D_CRT_SECURE_NO_WARNINGS /D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE -LFLAGS = /incremental:no /dynamicbase /nxcompat -RCFLAGS = -I..\./ -I..\charset/ -I..\windows/ -I..\unix/ -DWIN32 -D_WIN32 -DWINVER=0x0400 - -CFLAGS = $(CFLAGS) /DHAS_GSSAPI - - -all: $(BUILDDIR)pageant.exe $(BUILDDIR)plink.exe $(BUILDDIR)pscp.exe \ - $(BUILDDIR)psftp.exe $(BUILDDIR)psocks.exe \ - $(BUILDDIR)putty.exe $(BUILDDIR)puttygen.exe \ - $(BUILDDIR)puttytel.exe $(BUILDDIR)testcrypt.exe - -$(BUILDDIR)pageant.exe: $(BUILDDIR)aqsync.obj $(BUILDDIR)be_misc.obj \ - $(BUILDDIR)callback.obj $(BUILDDIR)conf.obj \ - $(BUILDDIR)ecc.obj $(BUILDDIR)errsock.obj \ - $(BUILDDIR)marshal.obj $(BUILDDIR)memory.obj \ - $(BUILDDIR)misc.obj $(BUILDDIR)mpint.obj \ - $(BUILDDIR)pageant.obj $(BUILDDIR)pageant.res \ - $(BUILDDIR)sshaes.obj $(BUILDDIR)sshargon2.obj \ - $(BUILDDIR)sshauxcrypt.obj $(BUILDDIR)sshblake2.obj \ - $(BUILDDIR)sshdes.obj $(BUILDDIR)sshdss.obj \ - $(BUILDDIR)sshecc.obj $(BUILDDIR)sshhmac.obj \ - $(BUILDDIR)sshmd5.obj $(BUILDDIR)sshpubk.obj \ - $(BUILDDIR)sshrsa.obj $(BUILDDIR)sshsh256.obj \ - $(BUILDDIR)sshsh512.obj $(BUILDDIR)sshsha.obj \ - $(BUILDDIR)sshsha3.obj $(BUILDDIR)stripctrl.obj \ - $(BUILDDIR)tree234.obj $(BUILDDIR)utils.obj \ - $(BUILDDIR)version.obj $(BUILDDIR)wcwidth.obj \ - $(BUILDDIR)wincapi.obj $(BUILDDIR)winhandl.obj \ - $(BUILDDIR)winhelp.obj $(BUILDDIR)winhsock.obj \ - $(BUILDDIR)winmisc.obj $(BUILDDIR)winmiscs.obj \ - $(BUILDDIR)winnet.obj $(BUILDDIR)winnpc.obj \ - $(BUILDDIR)winnps.obj $(BUILDDIR)winpgnt.obj \ - $(BUILDDIR)winpgntc.obj $(BUILDDIR)winsecur.obj \ - $(BUILDDIR)winselgui.obj $(BUILDDIR)winutils.obj - type < -#include -#include - -#include "putty.h" -#include "pageant.h" /* for AGENT_MAX_MSGLEN */ - -#include "security-api.h" -#include "cryptoapi.h" - -static bool wm_copydata_agent_exists(void) -{ - HWND hwnd; - hwnd = FindWindow("Pageant", "Pageant"); - if (!hwnd) - return false; - else - return true; -} - -static void wm_copydata_agent_query(strbuf *query, void **out, int *outlen) -{ - 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; - - if (query->len > AGENT_MAX_MSGLEN) - return; /* query too large */ - - hwnd = FindWindow("Pageant", "Pageant"); - if (!hwnd) - return; /* *out == NULL, so failure */ - mapname = dupprintf("PageantRequest%08x", (unsigned)GetCurrentThreadId()); - - psa = NULL; - 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; - } - } - } - } - - filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE, - 0, AGENT_MAX_MSGLEN, mapname); - if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) { - sfree(mapname); - return; /* *out == NULL, so failure */ - } - p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0); - strbuf_finalise_agent_query(query); - memcpy(p, query->s, query->len); - cds.dwData = AGENT_COPYDATA_ID; - cds.cbData = 1 + strlen(mapname); - cds.lpData = mapname; - - /* - * 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) { - uint32_t length_field = GET_32BIT_MSB_FIRST(p); - if (length_field > 0 && length_field <= AGENT_MAX_MSGLEN - 4) { - retlen = length_field + 4; - ret = snewn(retlen, unsigned char); - memcpy(ret, p, retlen); - *out = ret; - *outlen = retlen; - } else { - /* - * If we get here, we received an out-of-range length - * field, either without space for a message type code or - * overflowing the FileMapping. - * - * Treat this as if Pageant didn't answer at all - which - * actually means we do nothing, and just don't fill in - * out and outlen. - */ - } - } - UnmapViewOfFile(p); - CloseHandle(filemap); - sfree(mapname); - if (psd) - LocalFree(psd); -} - -Socket *agent_connect(Plug *plug) -{ - char *pipename = agent_named_pipe_name(); - Socket *s = new_named_pipe_client(pipename, plug); - sfree(pipename); - return s; -} - -static bool named_pipe_agent_exists(void) -{ - char *pipename = agent_named_pipe_name(); - WIN32_FIND_DATA data; - HANDLE ffh = FindFirstFile(pipename, &data); - sfree(pipename); - if (ffh == INVALID_HANDLE_VALUE) - return false; - FindClose(ffh); - return true; -} - -bool agent_exists(void) -{ - return named_pipe_agent_exists() || wm_copydata_agent_exists(); -} - -struct agent_pending_query { - struct handle *handle; - HANDLE os_handle; - strbuf *response; - void (*callback)(void *, void *, int); - void *callback_ctx; -}; - -static int named_pipe_agent_accumulate_response( - strbuf *sb, const void *data, size_t len) -{ - put_data(sb, data, len); - if (sb->len >= 4) { - uint32_t length_field = GET_32BIT_MSB_FIRST(sb->u); - if (length_field > AGENT_MAX_MSGLEN) - return -1; /* badly formatted message */ - - int overall_length = length_field + 4; - if (sb->len >= overall_length) - return overall_length; - } - - return 0; /* not done yet */ -} - -static size_t named_pipe_agent_gotdata( - struct handle *h, const void *data, size_t len, int err) -{ - agent_pending_query *pq = handle_get_privdata(h); - - if (err || len == 0) { - pq->callback(pq->callback_ctx, NULL, 0); - agent_cancel_query(pq); - return 0; - } - - int status = named_pipe_agent_accumulate_response(pq->response, data, len); - if (status == -1) { - pq->callback(pq->callback_ctx, NULL, 0); - agent_cancel_query(pq); - } else if (status > 0) { - void *response_buf = strbuf_to_str(pq->response); - pq->response = NULL; - pq->callback(pq->callback_ctx, response_buf, status); - agent_cancel_query(pq); - } - return 0; -} - -static agent_pending_query *named_pipe_agent_query( - strbuf *query, void **out, int *outlen, - void (*callback)(void *, void *, int), void *callback_ctx) -{ - agent_pending_query *pq = NULL; - char *err = NULL, *pipename = NULL; - strbuf *sb = NULL; - HANDLE pipehandle; - - pipename = agent_named_pipe_name(); - pipehandle = connect_to_named_pipe(pipename, &err); - if (pipehandle == INVALID_HANDLE_VALUE) - goto failure; - - strbuf_finalise_agent_query(query); - - for (DWORD done = 0; done < query->len ;) { - DWORD nwritten; - bool ret = WriteFile(pipehandle, query->s + done, query->len - done, - &nwritten, NULL); - if (!ret) - goto failure; - - done += nwritten; - } - - if (!callback) { - int status; - - sb = strbuf_new_nm(); - do { - char buf[1024]; - DWORD nread; - bool ret = ReadFile(pipehandle, buf, sizeof(buf), &nread, NULL); - if (!ret) - goto failure; - status = named_pipe_agent_accumulate_response(sb, buf, nread); - } while (status == 0); - - if (status == -1) - goto failure; - - *out = strbuf_to_str(sb); - *outlen = status; - sb = NULL; - pq = NULL; - goto out; - } - - pq = snew(agent_pending_query); - pq->handle = handle_input_new(pipehandle, named_pipe_agent_gotdata, pq, 0); - pq->os_handle = pipehandle; - pipehandle = INVALID_HANDLE_VALUE; /* prevent it being closed below */ - pq->response = strbuf_new_nm(); - pq->callback = callback; - pq->callback_ctx = callback_ctx; - goto out; - - failure: - *out = NULL; - *outlen = 0; - pq = NULL; - - out: - sfree(err); - sfree(pipename); - if (pipehandle != INVALID_HANDLE_VALUE) - CloseHandle(pipehandle); - if (sb) - strbuf_free(sb); - return pq; -} - -void agent_cancel_query(agent_pending_query *pq) -{ - handle_free(pq->handle); - CloseHandle(pq->os_handle); - if (pq->response) - strbuf_free(pq->response); - sfree(pq); -} - -agent_pending_query *agent_query( - strbuf *query, void **out, int *outlen, - void (*callback)(void *, void *, int), void *callback_ctx) -{ - agent_pending_query *pq = named_pipe_agent_query( - query, out, outlen, callback, callback_ctx); - if (pq || *out) - return pq; - - wm_copydata_agent_query(query, out, outlen); - return NULL; -} diff --git a/WINDOWS/cliloop.c b/WINDOWS/cliloop.c deleted file mode 100644 index eced54caa..000000000 --- a/WINDOWS/cliloop.c +++ /dev/null @@ -1,134 +0,0 @@ -#include "putty.h" - -void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx) -{ - SOCKET *sklist = NULL; - size_t skcount = 0, sksize = 0; - unsigned long now, next, then; - now = GETTICKCOUNT(); - - while (true) { - DWORD n; - DWORD ticks; - - const HANDLE *extra_handles = NULL; - size_t n_extra_handles = 0; - if (!pre(ctx, &extra_handles, &n_extra_handles)) - break; - - if (toplevel_callback_pending()) { - ticks = 0; - next = now; - } else if (run_timers(now, &next)) { - then = now; - now = GETTICKCOUNT(); - if (now - then > next - then) - ticks = 0; - else - ticks = next - now; - } else { - ticks = INFINITE; - /* no need to initialise next here because we can never - * get WAIT_TIMEOUT */ - } - - HandleWaitList *hwl = get_handle_wait_list(); - size_t winselcli_index = -(size_t)1; - size_t extra_base = hwl->nhandles; - if (winselcli_event != INVALID_HANDLE_VALUE) { - assert(extra_base < MAXIMUM_WAIT_OBJECTS); - winselcli_index = extra_base++; - hwl->handles[winselcli_index] = winselcli_event; - } - size_t total_handles = extra_base + n_extra_handles; - assert(total_handles < MAXIMUM_WAIT_OBJECTS); - for (size_t i = 0; i < n_extra_handles; i++) - hwl->handles[extra_base + i] = extra_handles[i]; - - n = WaitForMultipleObjects(total_handles, hwl->handles, false, ticks); - - size_t extra_handle_index = n_extra_handles; - - if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)hwl->nhandles) { - handle_wait_activate(hwl, n - WAIT_OBJECT_0); - } else if (winselcli_event != INVALID_HANDLE_VALUE && - n == WAIT_OBJECT_0 + winselcli_index) { - WSANETWORKEVENTS things; - SOCKET socket; - int i, socketstate; - - /* - * We must not call select_result() for any socket - * until we have finished enumerating within the tree. - * This is because select_result() may close the socket - * and modify the tree. - */ - /* Count the active sockets. */ - i = 0; - for (socket = first_socket(&socketstate); - socket != INVALID_SOCKET; - socket = next_socket(&socketstate)) i++; - - /* Expand the buffer if necessary. */ - sgrowarray(sklist, sksize, i); - - /* Retrieve the sockets into sklist. */ - skcount = 0; - for (socket = first_socket(&socketstate); - socket != INVALID_SOCKET; - socket = next_socket(&socketstate)) { - sklist[skcount++] = socket; - } - - /* Now we're done enumerating; go through the list. */ - for (i = 0; i < skcount; i++) { - WPARAM wp; - socket = sklist[i]; - wp = (WPARAM) socket; - if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) { - static const struct { int bit, mask; } eventtypes[] = { - {FD_CONNECT_BIT, FD_CONNECT}, - {FD_READ_BIT, FD_READ}, - {FD_CLOSE_BIT, FD_CLOSE}, - {FD_OOB_BIT, FD_OOB}, - {FD_WRITE_BIT, FD_WRITE}, - {FD_ACCEPT_BIT, FD_ACCEPT}, - }; - int e; - - noise_ultralight(NOISE_SOURCE_IOID, socket); - - for (e = 0; e < lenof(eventtypes); e++) - if (things.lNetworkEvents & eventtypes[e].mask) { - LPARAM lp; - int err = things.iErrorCode[eventtypes[e].bit]; - lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err); - select_result(wp, lp); - } - } - } - } else if (n >= WAIT_OBJECT_0 + extra_base && - n < WAIT_OBJECT_0 + extra_base + n_extra_handles) { - extra_handle_index = n - (WAIT_OBJECT_0 + extra_base); - } - - run_toplevel_callbacks(); - - if (n == WAIT_TIMEOUT) { - now = next; - } else { - now = GETTICKCOUNT(); - } - - handle_wait_list_free(hwl); - - if (!post(ctx, extra_handle_index)) - break; - } - - sfree(sklist); -} - -bool cliloop_null_pre(void *vctx, const HANDLE **eh, size_t *neh) -{ return true; } -bool cliloop_null_post(void *vctx, size_t ehi) { return true; } diff --git a/WINDOWS/config.c b/WINDOWS/config.c deleted file mode 100644 index fc9070bf6..000000000 --- a/WINDOWS/config.c +++ /dev/null @@ -1,384 +0,0 @@ -/* - * config.c - the Windows-specific parts of the PuTTY configuration - * box. - */ - -#include -#include - -#include "putty.h" -#include "dialog.h" -#include "storage.h" - -static void about_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - HWND *hwndp = (HWND *)ctrl->context.p; - - if (event == EVENT_ACTION) { - modal_about_box(*hwndp); - } -} - -static void help_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - HWND *hwndp = (HWND *)ctrl->context.p; - - if (event == EVENT_ACTION) { - show_help(*hwndp); - } -} - -static void variable_pitch_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - if (event == EVENT_REFRESH) { - dlg_checkbox_set(ctrl, dlg, !dlg_get_fixed_pitch_flag(dlg)); - } else if (event == EVENT_VALCHANGE) { - dlg_set_fixed_pitch_flag(dlg, !dlg_checkbox_get(ctrl, dlg)); - } -} - -void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, - bool midsession, int protocol) -{ - const struct BackendVtable *backvt; - bool resize_forbidden = false; - struct controlset *s; - dlgcontrol *c; - char *str; - - if (!midsession) { - /* - * Add the About and Help buttons to the standard panel. - */ - s = ctrl_getset(b, "", "", ""); - c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help), - about_handler, P(hwndp)); - c->column = 0; - if (has_help) { - c = ctrl_pushbutton(s, "Help", 'h', HELPCTX(no_help), - help_handler, P(hwndp)); - c->column = 1; - } - } - - /* - * Full-screen mode is a Windows peculiarity; hence - * scrollbar_in_fullscreen is as well. - */ - s = ctrl_getset(b, "Window", "scrollback", - "Control the scrollback in the window"); - ctrl_checkbox(s, "Display scrollbar in full screen mode", 'i', - HELPCTX(window_scrollback), - conf_checkbox_handler, - I(CONF_scrollbar_in_fullscreen)); - /* - * Really this wants to go just after `Display scrollbar'. See - * if we can find that control, and do some shuffling. - */ - { - int i; - for (i = 0; i < s->ncontrols; i++) { - c = s->ctrls[i]; - if (c->type == CTRL_CHECKBOX && - c->context.i == CONF_scrollbar) { - /* - * Control i is the scrollbar checkbox. - * Control s->ncontrols-1 is the scrollbar-in-FS 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(dlgcontrol *)); - s->ctrls[i+1] = c; - } - break; - } - } - } - - /* - * Windows has the AltGr key, which has various Windows- - * specific options. - */ - s = ctrl_getset(b, "Terminal/Keyboard", "features", - "Enable extra keyboard features:"); - ctrl_checkbox(s, "AltGr acts as Compose key", 't', - HELPCTX(keyboard_compose), - conf_checkbox_handler, I(CONF_compose_key)); - ctrl_checkbox(s, "Control-Alt is different from AltGr", 'd', - HELPCTX(keyboard_ctrlalt), - conf_checkbox_handler, I(CONF_ctrlaltkeys)); - - /* - * Windows allows an arbitrary .WAV to be played as a bell, and - * also the use of the PC speaker. For this we must search the - * existing controlset for the radio-button set controlling the - * `beep' option, and add extra buttons to it. - * - * Note that although this _looks_ like a hideous hack, it's - * actually all above board. The well-defined interface to the - * per-platform dialog box code is the _data structures_ `union - * control', `struct controlset' and so on; so code like this - * that reaches into those data structures and changes bits of - * them is perfectly legitimate and crosses no boundaries. All - * the ctrl_* routines that create most of the controls are - * convenient shortcuts provided on the cross-platform side of - * the interface, and template creation code is under no actual - * obligation to use them. - */ - s = ctrl_getset(b, "Terminal/Bell", "style", "Set the style of bell"); - { - int i; - for (i = 0; i < s->ncontrols; i++) { - c = s->ctrls[i]; - if (c->type == CTRL_RADIO && - c->context.i == CONF_beep) { - assert(c->handler == conf_radiobutton_handler); - c->radio.nbuttons += 2; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-1] = - dupstr("Play a custom sound file"); - c->radio.buttons[c->radio.nbuttons-2] = - dupstr("Beep using the PC speaker"); - c->radio.buttondata = - sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); - c->radio.buttondata[c->radio.nbuttons-1] = I(BELL_WAVEFILE); - c->radio.buttondata[c->radio.nbuttons-2] = I(BELL_PCSPEAKER); - if (c->radio.shortcuts) { - c->radio.shortcuts = - sresize(c->radio.shortcuts, c->radio.nbuttons, char); - c->radio.shortcuts[c->radio.nbuttons-1] = NO_SHORTCUT; - c->radio.shortcuts[c->radio.nbuttons-2] = NO_SHORTCUT; - } - break; - } - } - } - ctrl_filesel(s, "Custom sound file to play as a bell:", NO_SHORTCUT, - FILTER_WAVE_FILES, false, "Select bell sound file", - HELPCTX(bell_style), - conf_filesel_handler, I(CONF_bell_wavefile)); - - /* - * While we've got this box open, taskbar flashing on a bell is - * also Windows-specific. - */ - ctrl_radiobuttons(s, "Taskbar/caption indication on bell:", 'i', 3, - HELPCTX(bell_taskbar), - conf_radiobutton_handler, - I(CONF_beep_ind), - "Disabled", I(B_IND_DISABLED), - "Flashing", I(B_IND_FLASH), - "Steady", I(B_IND_STEADY)); - - /* - * The sunken-edge border is a Windows GUI feature. - */ - s = ctrl_getset(b, "Window/Appearance", "border", - "Adjust the window border"); - ctrl_checkbox(s, "Sunken-edge border (slightly thicker)", 's', - HELPCTX(appearance_border), - conf_checkbox_handler, I(CONF_sunken_edge)); - - /* - * Configurable font quality settings for Windows. - */ - s = ctrl_getset(b, "Window/Appearance", "font", - "Font settings"); - ctrl_checkbox(s, "Allow selection of variable-pitch fonts", NO_SHORTCUT, - HELPCTX(appearance_font), variable_pitch_handler, I(0)); - ctrl_radiobuttons(s, "Font quality:", 'q', 2, - HELPCTX(appearance_font), - conf_radiobutton_handler, - I(CONF_font_quality), - "Antialiased", I(FQ_ANTIALIASED), - "Non-Antialiased", I(FQ_NONANTIALIASED), - "ClearType", I(FQ_CLEARTYPE), - "Default", I(FQ_DEFAULT)); - - /* - * Cyrillic Lock is a horrid misfeature even on Windows, and - * the least we can do is ensure it never makes it to any other - * platform (at least unless someone fixes it!). - */ - s = ctrl_getset(b, "Window/Translation", "tweaks", NULL); - ctrl_checkbox(s, "Caps Lock acts as Cyrillic switch", 's', - HELPCTX(translation_cyrillic), - conf_checkbox_handler, - I(CONF_xlat_capslockcyr)); - - /* - * On Windows we can use but not enumerate translation tables - * from the operating system. Briefly document this. - */ - s = ctrl_getset(b, "Window/Translation", "trans", - "Character set translation on received data"); - ctrl_text(s, "(Codepages supported by Windows but not listed here, " - "such as CP866 on many systems, can be entered manually)", - HELPCTX(translation_codepage)); - - /* - * Windows has the weird OEM font mode, which gives us some - * additional options when working with line-drawing - * characters. - */ - str = dupprintf("Adjust how %s displays line drawing characters", appname); - s = ctrl_getset(b, "Window/Translation", "linedraw", str); - sfree(str); - { - int i; - for (i = 0; i < s->ncontrols; i++) { - c = s->ctrls[i]; - if (c->type == CTRL_RADIO && - c->context.i == CONF_vtmode) { - assert(c->handler == conf_radiobutton_handler); - c->radio.nbuttons += 3; - c->radio.buttons = - sresize(c->radio.buttons, c->radio.nbuttons, char *); - c->radio.buttons[c->radio.nbuttons-3] = - dupstr("Font has XWindows encoding"); - c->radio.buttons[c->radio.nbuttons-2] = - dupstr("Use font in both ANSI and OEM modes"); - c->radio.buttons[c->radio.nbuttons-1] = - dupstr("Use font in OEM mode only"); - c->radio.buttondata = - sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); - c->radio.buttondata[c->radio.nbuttons-3] = I(VT_XWINDOWS); - c->radio.buttondata[c->radio.nbuttons-2] = I(VT_OEMANSI); - c->radio.buttondata[c->radio.nbuttons-1] = I(VT_OEMONLY); - if (!c->radio.shortcuts) { - int j; - c->radio.shortcuts = snewn(c->radio.nbuttons, char); - for (j = 0; j < c->radio.nbuttons; j++) - c->radio.shortcuts[j] = NO_SHORTCUT; - } else { - c->radio.shortcuts = sresize(c->radio.shortcuts, - c->radio.nbuttons, char); - } - c->radio.shortcuts[c->radio.nbuttons-3] = 'x'; - c->radio.shortcuts[c->radio.nbuttons-2] = 'b'; - c->radio.shortcuts[c->radio.nbuttons-1] = 'e'; - break; - } - } - } - - /* - * RTF paste is Windows-specific. - */ - s = ctrl_getset(b, "Window/Selection/Copy", "format", - "Formatting of copied characters"); - ctrl_checkbox(s, "Copy to clipboard in RTF as well as plain text", 'f', - HELPCTX(copy_rtf), - conf_checkbox_handler, I(CONF_rtf_paste)); - - /* - * Windows often has no middle button, so we supply a selection - * mode in which the more critical Paste action is available on - * the right button instead. - */ - s = ctrl_getset(b, "Window/Selection", "mouse", - "Control use of mouse"); - ctrl_radiobuttons(s, "Action of mouse buttons:", 'm', 1, - HELPCTX(selection_buttons), - conf_radiobutton_handler, - I(CONF_mouse_is_xterm), - "Windows (Middle extends, Right brings up menu)", I(2), - "Compromise (Middle extends, Right pastes)", I(0), - "xterm (Right extends, Middle pastes)", I(1)); - /* - * This really ought to go at the _top_ of its box, not the - * bottom, so we'll just do some shuffling now we've set it - * up... - */ - c = s->ctrls[s->ncontrols-1]; /* this should be the new control */ - memmove(s->ctrls+1, s->ctrls, (s->ncontrols-1)*sizeof(dlgcontrol *)); - s->ctrls[0] = c; - - /* - * Logical palettes don't even make sense anywhere except Windows. - */ - s = ctrl_getset(b, "Window/Colours", "general", - "General options for colour usage"); - ctrl_checkbox(s, "Attempt to use logical palettes", 'l', - HELPCTX(colours_logpal), - conf_checkbox_handler, I(CONF_try_palette)); - ctrl_checkbox(s, "Use system colours", 's', - HELPCTX(colours_system), - conf_checkbox_handler, I(CONF_system_colour)); - - - /* - * Resize-by-changing-font is a Windows insanity. - */ - - backvt = backend_vt_from_proto(protocol); - if (backvt) - resize_forbidden = (backvt->flags & BACKEND_RESIZE_FORBIDDEN); - if (!midsession || !resize_forbidden) { - s = ctrl_getset(b, "Window", "size", "Set the size of the window"); - ctrl_radiobuttons(s, "When window is resized:", 'z', 1, - HELPCTX(window_resize), - conf_radiobutton_handler, - I(CONF_resize_action), - "Change the number of rows and columns", I(RESIZE_TERM), - "Change the size of the font", I(RESIZE_FONT), - "Change font size only when maximised", I(RESIZE_EITHER), - "Forbid resizing completely", I(RESIZE_DISABLED)); - } - - /* - * Most of the Window/Behaviour stuff is there to mimic Windows - * conventions which PuTTY can optionally disregard. Hence, - * most of these options are Windows-specific. - */ - s = ctrl_getset(b, "Window/Behaviour", "main", NULL); - ctrl_checkbox(s, "Window closes on ALT-F4", '4', - HELPCTX(behaviour_altf4), - conf_checkbox_handler, I(CONF_alt_f4)); - ctrl_checkbox(s, "System menu appears on ALT-Space", 'y', - HELPCTX(behaviour_altspace), - conf_checkbox_handler, I(CONF_alt_space)); - ctrl_checkbox(s, "System menu appears on ALT alone", 'l', - HELPCTX(behaviour_altonly), - conf_checkbox_handler, I(CONF_alt_only)); - ctrl_checkbox(s, "Ensure window is always on top", 'e', - HELPCTX(behaviour_alwaysontop), - conf_checkbox_handler, I(CONF_alwaysontop)); - ctrl_checkbox(s, "Full screen on Alt-Enter", 'f', - HELPCTX(behaviour_altenter), - conf_checkbox_handler, - I(CONF_fullscreenonaltenter)); - - /* - * Windows supports a local-command proxy. - */ - 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->type == CTRL_LISTBOX && - c->handler == proxy_type_handler) { - c->context.i |= PROXY_UI_FLAG_LOCAL; - break; - } - } - } - - /* - * $XAUTHORITY is not reliable on Windows, so we provide a - * means to override it. - */ - if (!midsession && backend_vt_from_proto(PROT_SSH)) { - s = ctrl_getset(b, "Connection/SSH/X11", "x11", "X11 forwarding"); - ctrl_filesel(s, "X authority file for local display", 't', - NULL, false, "Select X authority file", - HELPCTX(ssh_tunnels_xauthority), - conf_filesel_handler, I(CONF_xauthfile)); - } -} diff --git a/WINDOWS/conpty.c b/WINDOWS/conpty.c deleted file mode 100644 index c23c81e19..000000000 --- a/WINDOWS/conpty.c +++ /dev/null @@ -1,433 +0,0 @@ -/* - * Backend to run a Windows console session using ConPTY. - */ - -#include -#include -#include - -#include "putty.h" - -#include -#include - -typedef struct ConPTY ConPTY; -struct ConPTY { - HPCON pseudoconsole; - HANDLE outpipe, inpipe, hprocess; - struct handle *out, *in; - HandleWait *subprocess; - bool exited; - DWORD exitstatus; - Seat *seat; - LogContext *logctx; - int bufsize; - Backend backend; -}; - -DECL_WINDOWS_FUNCTION(static, HRESULT, CreatePseudoConsole, - (COORD, HANDLE, HANDLE, DWORD, HPCON *)); -DECL_WINDOWS_FUNCTION(static, void, ClosePseudoConsole, (HPCON)); -DECL_WINDOWS_FUNCTION(static, HRESULT, ResizePseudoConsole, (HPCON, COORD)); - -static bool init_conpty_api(void) -{ - static bool tried = false; - if (!tried) { - tried = true; - HMODULE kernel32_module = load_system32_dll("kernel32.dll"); - GET_WINDOWS_FUNCTION(kernel32_module, CreatePseudoConsole); - GET_WINDOWS_FUNCTION(kernel32_module, ClosePseudoConsole); - GET_WINDOWS_FUNCTION(kernel32_module, ResizePseudoConsole); - } - - return (p_CreatePseudoConsole != NULL && - p_ClosePseudoConsole != NULL && - p_ResizePseudoConsole != NULL); -} - -static void conpty_terminate(ConPTY *conpty) -{ - if (conpty->out) { - handle_free(conpty->out); - conpty->out = NULL; - } - if (conpty->outpipe != INVALID_HANDLE_VALUE) { - CloseHandle(conpty->outpipe); - conpty->outpipe = INVALID_HANDLE_VALUE; - } - if (conpty->in) { - handle_free(conpty->in); - conpty->in = NULL; - } - if (conpty->inpipe != INVALID_HANDLE_VALUE) { - CloseHandle(conpty->inpipe); - conpty->inpipe = INVALID_HANDLE_VALUE; - } - if (conpty->subprocess) { - delete_handle_wait(conpty->subprocess); - conpty->subprocess = NULL; - conpty->hprocess = INVALID_HANDLE_VALUE; - } - if (conpty->pseudoconsole != INVALID_HANDLE_VALUE) { - p_ClosePseudoConsole(conpty->pseudoconsole); - conpty->pseudoconsole = INVALID_HANDLE_VALUE; - } -} - -static void conpty_process_wait_callback(void *vctx) -{ - ConPTY *conpty = (ConPTY *)vctx; - - if (!GetExitCodeProcess(conpty->hprocess, &conpty->exitstatus)) - return; - conpty->exited = true; - - /* - * We can stop waiting for the process now. - */ - if (conpty->subprocess) { - delete_handle_wait(conpty->subprocess); - conpty->subprocess = NULL; - conpty->hprocess = INVALID_HANDLE_VALUE; - } - - /* - * Once the contained process exits, close the pseudo-console as - * well. But don't close the pipes yet, since apparently - * ClosePseudoConsole can trigger a final bout of terminal output - * as things clean themselves up. - */ - if (conpty->pseudoconsole != INVALID_HANDLE_VALUE) { - p_ClosePseudoConsole(conpty->pseudoconsole); - conpty->pseudoconsole = INVALID_HANDLE_VALUE; - } -} - -static size_t conpty_gotdata( - struct handle *h, const void *data, size_t len, int err) -{ - ConPTY *conpty = (ConPTY *)handle_get_privdata(h); - if (err || len == 0) { - char *error_msg; - - conpty_terminate(conpty); - - seat_notify_remote_exit(conpty->seat); - - if (!err && conpty->exited) { - /* - * The clean-exit case: our subprocess terminated, we - * deleted the PseudoConsole ourself, and now we got the - * expected EOF on the pipe. - */ - return 0; - } - - if (err) - error_msg = dupprintf("Error reading from console pty: %s", - win_strerror(err)); - else - error_msg = dupprintf( - "Unexpected end of file reading from console pty"); - - logevent(conpty->logctx, error_msg); - seat_connection_fatal(conpty->seat, "%s", error_msg); - sfree(error_msg); - - return 0; - } else { - return seat_stdout(conpty->seat, data, len); - } -} - -static void conpty_sentdata(struct handle *h, size_t new_backlog, int err, - bool close) -{ - ConPTY *conpty = (ConPTY *)handle_get_privdata(h); - if (err) { - const char *error_msg = "Error writing to conpty device"; - - conpty_terminate(conpty); - - seat_notify_remote_exit(conpty->seat); - - logevent(conpty->logctx, error_msg); - - seat_connection_fatal(conpty->seat, "%s", error_msg); - } else { - conpty->bufsize = new_backlog; - } -} - -static char *conpty_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - ConPTY *conpty; - char *err = NULL; - - HANDLE in_r = INVALID_HANDLE_VALUE; - HANDLE in_w = INVALID_HANDLE_VALUE; - HANDLE out_r = INVALID_HANDLE_VALUE; - HANDLE out_w = INVALID_HANDLE_VALUE; - - HPCON pcon; - bool pcon_needs_cleanup = false; - - STARTUPINFOEX si; - memset(&si, 0, sizeof(si)); - - if (!init_conpty_api()) { - err = dupprintf("Pseudo-console API is not available on this " - "Windows system"); - goto out; - } - - if (!CreatePipe(&in_r, &in_w, NULL, 0)) { - err = dupprintf("CreatePipe: %s", win_strerror(GetLastError())); - goto out; - } - if (!CreatePipe(&out_r, &out_w, NULL, 0)) { - err = dupprintf("CreatePipe: %s", win_strerror(GetLastError())); - goto out; - } - - COORD size; - size.X = conf_get_int(conf, CONF_width); - size.Y = conf_get_int(conf, CONF_height); - - HRESULT result = p_CreatePseudoConsole(size, in_r, out_w, 0, &pcon); - if (FAILED(result)) { - if (HRESULT_FACILITY(result) == FACILITY_WIN32) - err = dupprintf("CreatePseudoConsole: %s", - win_strerror(HRESULT_CODE(result))); - else - err = dupprintf("CreatePseudoConsole failed: HRESULT=0x%08x", - (unsigned)result); - goto out; - } - pcon_needs_cleanup = true; - - CloseHandle(in_r); - in_r = INVALID_HANDLE_VALUE; - CloseHandle(out_w); - out_w = INVALID_HANDLE_VALUE; - - si.StartupInfo.cb = sizeof(si); - - SIZE_T attrsize = 0; - InitializeProcThreadAttributeList(NULL, 1, 0, &attrsize); - si.lpAttributeList = smalloc(attrsize); - if (!InitializeProcThreadAttributeList( - si.lpAttributeList, 1, 0, &attrsize)) { - err = dupprintf("InitializeProcThreadAttributeList: %s", - win_strerror(GetLastError())); - goto out; - } - if (!UpdateProcThreadAttribute( - si.lpAttributeList, - 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, - pcon, sizeof(pcon), NULL, NULL)) { - err = dupprintf("UpdateProcThreadAttribute: %s", - win_strerror(GetLastError())); - goto out; - } - - PROCESS_INFORMATION pi; - memset(&pi, 0, sizeof(pi)); - - char *command; - const char *conf_cmd = conf_get_str(conf, CONF_remote_cmd); - if (*conf_cmd) { - command = dupstr(conf_cmd); - } else { - command = dupcat(get_system_dir(), "\\cmd.exe"); - } - bool created_ok = CreateProcess(NULL, command, NULL, NULL, - false, EXTENDED_STARTUPINFO_PRESENT, - NULL, NULL, &si.StartupInfo, &pi); - sfree(command); - if (!created_ok) { - err = dupprintf("CreateProcess: %s", - win_strerror(GetLastError())); - goto out; - } - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - conpty = snew(ConPTY); - memset(conpty, 0, sizeof(ConPTY)); - conpty->pseudoconsole = pcon; - pcon_needs_cleanup = false; - conpty->outpipe = in_w; - conpty->out = handle_output_new(in_w, conpty_sentdata, conpty, 0); - in_w = INVALID_HANDLE_VALUE; - conpty->inpipe = out_r; - conpty->in = handle_input_new(out_r, conpty_gotdata, conpty, 0); - out_r = INVALID_HANDLE_VALUE; - conpty->subprocess = add_handle_wait( - pi.hProcess, conpty_process_wait_callback, conpty); - conpty->hprocess = pi.hProcess; - CloseHandle(pi.hThread); - conpty->exited = false; - conpty->exitstatus = 0; - conpty->bufsize = 0; - conpty->backend.vt = vt; - *backend_handle = &conpty->backend; - - conpty->seat = seat; - conpty->logctx = logctx; - - *realhost = dupstr(""); - - /* - * Specials are always available. - */ - seat_update_specials_menu(conpty->seat); - - out: - if (in_r != INVALID_HANDLE_VALUE) - CloseHandle(in_r); - if (in_w != INVALID_HANDLE_VALUE) - CloseHandle(in_w); - if (out_r != INVALID_HANDLE_VALUE) - CloseHandle(out_r); - if (out_w != INVALID_HANDLE_VALUE) - CloseHandle(out_w); - if (pcon_needs_cleanup) - p_ClosePseudoConsole(pcon); - sfree(si.lpAttributeList); - return err; -} - -static void conpty_free(Backend *be) -{ - ConPTY *conpty = container_of(be, ConPTY, backend); - - conpty_terminate(conpty); - expire_timer_context(conpty); - sfree(conpty); -} - -static void conpty_reconfig(Backend *be, Conf *conf) -{ -} - -static void conpty_send(Backend *be, const char *buf, size_t len) -{ - ConPTY *conpty = container_of(be, ConPTY, backend); - - if (conpty->out == NULL) - return; - - conpty->bufsize = handle_write(conpty->out, buf, len); -} - -static size_t conpty_sendbuffer(Backend *be) -{ - ConPTY *conpty = container_of(be, ConPTY, backend); - return conpty->bufsize; -} - -static void conpty_size(Backend *be, int width, int height) -{ - ConPTY *conpty = container_of(be, ConPTY, backend); - COORD size; - size.X = width; - size.Y = height; - p_ResizePseudoConsole(conpty->pseudoconsole, size); -} - -static void conpty_special(Backend *be, SessionSpecialCode code, int arg) -{ -} - -static const SessionSpecial *conpty_get_specials(Backend *be) -{ - static const SessionSpecial specials[] = { - {NULL, SS_EXITMENU} - }; - return specials; -} - -static bool conpty_connected(Backend *be) -{ - return true; /* always connected */ -} - -static bool conpty_sendok(Backend *be) -{ - return true; -} - -static void conpty_unthrottle(Backend *be, size_t backlog) -{ - ConPTY *conpty = container_of(be, ConPTY, backend); - if (conpty->in) - handle_unthrottle(conpty->in, backlog); -} - -static bool conpty_ldisc(Backend *be, int option) -{ - return false; -} - -static void conpty_provide_ldisc(Backend *be, Ldisc *ldisc) -{ -} - -static int conpty_exitcode(Backend *be) -{ - ConPTY *conpty = container_of(be, ConPTY, backend); - - if (conpty->exited) { - /* - * PuTTY's representation of exit statuses expects them to be - * non-negative 'int' values. But Windows exit statuses can - * include all those exception codes like 0xC000001D which - * convert to negative 32-bit ints. - * - * I don't think there's a great deal of use for returning - * those in full detail, right now. (Though if we ever - * connected this system up to a Windows version of psusan or - * Uppity, perhaps there might be?) - * - * So we clip them at INT_MAX-1, since INT_MAX is reserved for - * 'exit so unclean as to inhibit Close On Clean Exit'. - */ - return (0 <= conpty->exitstatus && conpty->exitstatus < INT_MAX) ? - conpty->exitstatus : INT_MAX-1; - } else { - return -1; - } -} - -static int conpty_cfg_info(Backend *be) -{ - return 0; -} - -const BackendVtable conpty_backend = { - .init = conpty_init, - .free = conpty_free, - .reconfig = conpty_reconfig, - .send = conpty_send, - .sendbuffer = conpty_sendbuffer, - .size = conpty_size, - .special = conpty_special, - .get_specials = conpty_get_specials, - .connected = conpty_connected, - .exitcode = conpty_exitcode, - .sendok = conpty_sendok, - .ldisc_option_state = conpty_ldisc, - .provide_ldisc = conpty_provide_ldisc, - .unthrottle = conpty_unthrottle, - .cfg_info = conpty_cfg_info, - .id = "conpty", - .displayname_tc = "ConPTY", - .displayname_lc = "ConPTY", /* proper name, so capitalise it anyway */ - .protocol = -1, -}; diff --git a/WINDOWS/console.c b/WINDOWS/console.c deleted file mode 100644 index 6db3dd14d..000000000 --- a/WINDOWS/console.c +++ /dev/null @@ -1,728 +0,0 @@ -/* - * console.c - various interactive-prompt routines shared between - * the Windows console PuTTY tools - */ - -#include -#include - -#include "putty.h" -#include "storage.h" -#include "ssh.h" -#include "console.h" - -void cleanup_exit(int code) -{ - /* - * Clean up. - */ - sk_cleanup(); - - random_save_seed(); - - exit(code); -} - -void console_print_error_msg(const char *prefix, const char *msg) -{ - fputs(prefix, stderr); - fputs(": ", stderr); - fputs(msg, stderr); - fputc('\n', stderr); - fflush(stderr); -} - -/* - * System for getting I/O handles to talk to the console for - * interactive prompts. - * - * In PuTTY 0.78 and before, these prompts used the standard I/O - * handles. But this means you can't redirect Plink's actual stdin - * from a sensible data channel without the responses to login prompts - * unwantedly being read from it too. Also, if you have a real - * console handle then you can read from it in Unicode mode, which is - * an option not available for any old file handle. - * - * However, many versions of PuTTY have worked the old way, so we need - * a method of falling back to it for the sake of whoever's workflow - * it turns out to break. So this structure equivocates between the - * two systems. - */ -static bool conio_use_standard_handles = false; -bool console_set_stdio_prompts(bool newvalue) -{ - conio_use_standard_handles = newvalue; - return true; -} - -static bool conio_use_utf8 = true; -bool set_legacy_charset_handling(bool newvalue) -{ - conio_use_utf8 = !newvalue; - return true; -} - -typedef struct ConsoleIO { - HANDLE hin, hout; - bool need_close_hin, need_close_hout; - bool hin_is_console, hout_is_console; - bool utf8; - BinarySink_IMPLEMENTATION; -} ConsoleIO; - -static void console_write(BinarySink *bs, const void *data, size_t len); - -static ConsoleIO *conio_setup(bool utf8) -{ - ConsoleIO *conio = snew(ConsoleIO); - - conio->hin = conio->hout = INVALID_HANDLE_VALUE; - conio->need_close_hin = conio->need_close_hout = false; - conio->utf8 = utf8 && conio_use_utf8; - - /* - * First try opening the console itself, so that prompts will go - * there regardless of I/O redirection. We don't do this if the - * user has deliberately requested a fallback to the old - * behaviour. We also don't do it in batch mode, because in that - * situation, any need for an interactive prompt will instead - * noninteractively abort the connection, and in that situation, - * the 'prompt' becomes more in the nature of an error message, so - * it should go to standard error like everything else. - */ - if (!conio_use_standard_handles && !console_batch_mode) { - /* - * If we do open the console, it has to be done separately for - * input and output, with different magic file names. - * - * We need both read and write permission for both handles, - * because read permission is needed to read the console mode - * (in particular, to test if a file handle _is_ a console), - * and write permission to change it. - */ - conio->hin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, - 0, NULL, OPEN_EXISTING, 0, NULL); - if (conio->hin != INVALID_HANDLE_VALUE) - conio->need_close_hin = true; - - conio->hout = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, - 0, NULL, OPEN_EXISTING, 0, NULL); - if (conio->hout != INVALID_HANDLE_VALUE) - conio->need_close_hout = true; - } - - /* - * Fall back from that to using the standard handles. We use - * standard error rather than standard output for our prompts, - * because that has a better chance of separating them from - */ - if (conio->hin == INVALID_HANDLE_VALUE) - conio->hin = GetStdHandle(STD_INPUT_HANDLE); - if (conio->hout == INVALID_HANDLE_VALUE) - conio->hout = GetStdHandle(STD_INPUT_HANDLE); - - DWORD dummy; - conio->hin_is_console = GetConsoleMode(conio->hin, &dummy); - conio->hout_is_console = GetConsoleMode(conio->hout, &dummy); - - BinarySink_INIT(conio, console_write); - - return conio; -} - -static void conio_free(ConsoleIO *conio) -{ - if (conio->need_close_hin) - CloseHandle(conio->hin); - if (conio->need_close_hout) - CloseHandle(conio->hout); - sfree(conio); -} - -static void console_write(BinarySink *bs, const void *data, size_t len) -{ - ConsoleIO *conio = BinarySink_DOWNCAST(bs, ConsoleIO); - - if (conio->utf8) { - /* - * Convert the UTF-8 input into a wide string. - */ - size_t wlen; - wchar_t *wide = dup_mb_to_wc_c(CP_UTF8, 0, data, len, &wlen); - if (conio->hout_is_console) { - /* - * To write UTF-8 to a console, use WriteConsoleW on the - * wide string we've just made. - */ - size_t pos = 0; - DWORD nwritten; - - while (pos < wlen && WriteConsoleW(conio->hout, wide+pos, wlen-pos, - &nwritten, NULL)) - pos += nwritten; - } else { - /* - * To write a string encoded in UTF-8 to any other file - * handle, the best we can do is to convert it into the - * system code page. This will lose some characters, but - * what else can you do? - */ - size_t clen; - char *sys_cp = dup_wc_to_mb_c(CP_ACP, 0, wide, wlen, "?", &clen); - size_t pos = 0; - DWORD nwritten; - - while (pos < clen && WriteFile(conio->hout, sys_cp+pos, clen-pos, - &nwritten, NULL)) - pos += nwritten; - - burnstr(sys_cp); - } - - burnwcs(wide); - } else { - /* - * If we're in legacy non-UTF-8 mode, just send the bytes - * we're given to the file handle without trying to be clever. - */ - const char *cdata = (const char *)data; - size_t pos = 0; - DWORD nwritten; - - while (pos < len && WriteFile(conio->hout, cdata+pos, len-pos, - &nwritten, NULL)) - pos += nwritten; - } -} - -static bool console_read_line_to_strbuf(ConsoleIO *conio, bool echo, - strbuf *sb) -{ - DWORD savemode; - - if (conio->hin_is_console) { - GetConsoleMode(conio->hin, &savemode); - DWORD newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT; - if (!echo) - newmode &= ~ENABLE_ECHO_INPUT; - else - newmode |= ENABLE_ECHO_INPUT; - - SetConsoleMode(conio->hin, newmode); - } - - bool toret = false; - - while (true) { - if (ptrlen_endswith(ptrlen_from_strbuf(sb), - PTRLEN_LITERAL("\n"), NULL)) { - toret = true; - goto out; - } - - if (conio->utf8) { - wchar_t wbuf[4096]; - size_t wlen; - - if (conio->hin_is_console) { - /* - * To read UTF-8 from a console, read wide character data - * via ReadConsoleW, and convert it to UTF-8. - */ - DWORD nread; - if (!ReadConsoleW(conio->hin, wbuf, lenof(wbuf), &nread, NULL)) - goto out; - wlen = nread; - } else { - /* - * To read UTF-8 from an ordinary file handle, read it - * as normal bytes and then convert from CP_ACP to - * UTF-8, in the reverse of what we did above for - * output. - */ - char buf[4096]; - DWORD nread; - if (!ReadFile(conio->hin, buf, lenof(buf), &nread, NULL)) - goto out; - - wlen = mb_to_wc(CP_ACP, 0, buf, nread, wbuf, lenof(wbuf)); - smemclr(buf, sizeof(buf)); - } - - /* Allocate the maximum space in the strbuf that might be - * needed for this data */ - size_t oldlen = sb->len, maxout = wlen * 4; - void *outptr = strbuf_append(sb, maxout); - size_t newlen = oldlen + wc_to_mb(CP_UTF8, 0, wbuf, wlen, - outptr, maxout, NULL); - strbuf_shrink_to(sb, newlen); - smemclr(wbuf, sizeof(wbuf)); - } else { - /* - * If we're in legacy non-UTF-8 mode, just read bytes - * directly from the file handle into the output strbuf. - */ - char buf[4096]; - DWORD nread; - if (!ReadFile(conio->hin, buf, lenof(buf), &nread, NULL)) - goto out; - - put_data(sb, buf, nread); - smemclr(buf, sizeof(buf)); - } - } - - out: - if (!echo) - put_datalit(conio, "\r\n"); - if (conio->hin_is_console) - SetConsoleMode(conio->hin, savemode); - return toret; -} - -static char *console_read_line(ConsoleIO *conio, bool echo) -{ - strbuf *sb = strbuf_new_nm(); - if (!console_read_line_to_strbuf(conio, echo, sb)) { - strbuf_free(sb); - return NULL; - } else { - return strbuf_to_str(sb); - } -} - -typedef enum { - RESPONSE_ABANDON, - RESPONSE_YES, - RESPONSE_NO, - RESPONSE_INFO, - RESPONSE_UNRECOGNISED -} ResponseType; - -static ResponseType parse_and_free_response(char *line) -{ - if (!line) - return RESPONSE_ABANDON; - - ResponseType toret; - switch (line[0]) { - /* In case of misplaced reflexes from another program, - * recognise 'q' as 'abandon connection' as well as the - * advertised 'just press Return' */ - case 'q': - case 'Q': - case '\n': - case '\r': - case '\0': - toret = RESPONSE_ABANDON; - break; - case 'y': - case 'Y': - toret = RESPONSE_YES; - break; - case 'n': - case 'N': - toret = RESPONSE_NO; - break; - case 'i': - case 'I': - toret = RESPONSE_INFO; - break; - default: - toret = RESPONSE_UNRECOGNISED; - break; - } - - burnstr(line); - return toret; -} - -/* - * Helper function to print the message from a SeatDialogText. Returns - * the final prompt to print on the input line, or NULL if a - * batch-mode abort is needed. In the latter case it will have printed - * the abort text already. - */ -static const char *console_print_seatdialogtext( - ConsoleIO *conio, SeatDialogText *text) -{ - const char *prompt = NULL; - - for (SeatDialogTextItem *item = text->items, - *end = item+text->nitems; item < end; item++) { - switch (item->type) { - case SDT_PARA: - wordwrap(BinarySink_UPCAST(conio), - ptrlen_from_asciz(item->text), 60); - put_byte(conio, '\n'); - break; - case SDT_DISPLAY: - put_fmt(conio, " %s\n", item->text); - break; - case SDT_SCARY_HEADING: - /* Can't change font size or weight in this context */ - put_fmt(conio, "%s\n", item->text); - break; - case SDT_BATCH_ABORT: - if (console_batch_mode) { - put_fmt(conio, "%s\n", item->text); - return NULL; - } - break; - case SDT_PROMPT: - prompt = item->text; - break; - default: - break; - } - } - assert(prompt); /* something in the SeatDialogText should have set this */ - return prompt; -} - -SeatPromptResult console_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - ConsoleIO *conio = conio_setup(false); - SeatPromptResult result; - - const char *prompt = console_print_seatdialogtext(conio, text); - if (!prompt) { - result = SPR_SW_ABORT("Cannot confirm a host key in batch mode"); - goto out; - } - - ResponseType response; - - while (true) { - put_fmt(conio, "%s (y/n, Return cancels connection, i for more info) ", - prompt); - - response = parse_and_free_response(console_read_line(conio, true)); - - if (response == RESPONSE_INFO) { - for (SeatDialogTextItem *item = text->items, - *end = item+text->nitems; item < end; item++) { - switch (item->type) { - case SDT_MORE_INFO_KEY: - put_dataz(conio, item->text); - break; - case SDT_MORE_INFO_VALUE_SHORT: - put_fmt(conio, ": %s\n", item->text); - break; - case SDT_MORE_INFO_VALUE_BLOB: - put_fmt(conio, ":\n%s\n", item->text); - break; - default: - break; - } - } - } else { - break; - } - } - - if (response == RESPONSE_YES || response == RESPONSE_NO) { - if (response == RESPONSE_YES) - store_host_key(seat, host, port, keytype, keystr); - result = SPR_OK; - } else { - put_dataz(conio, console_abandoned_msg); - result = SPR_USER_ABORT; - } - out: - conio_free(conio); - return result; -} - -SeatPromptResult console_confirm_weak_crypto_primitive( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - ConsoleIO *conio = conio_setup(false); - SeatPromptResult result; - - const char *prompt = console_print_seatdialogtext(conio, text); - if (!prompt) { - result = SPR_SW_ABORT("Cannot confirm a weak crypto primitive " - "in batch mode"); - goto out; - } - - put_fmt(conio, "%s (y/n) ", prompt); - - ResponseType response = parse_and_free_response( - console_read_line(conio, true)); - - if (response == RESPONSE_YES) { - result = SPR_OK; - } else { - put_dataz(conio, console_abandoned_msg); - result = SPR_USER_ABORT; - } - out: - conio_free(conio); - return result; -} - -SeatPromptResult console_confirm_weak_cached_hostkey( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - ConsoleIO *conio = conio_setup(false); - SeatPromptResult result; - - const char *prompt = console_print_seatdialogtext(conio, text); - if (!prompt) - return SPR_SW_ABORT("Cannot confirm a weak cached host key " - "in batch mode"); - - put_fmt(conio, "%s (y/n) ", prompt); - - ResponseType response = parse_and_free_response( - console_read_line(conio, true)); - - if (response == RESPONSE_YES) { - result = SPR_OK; - } else { - put_dataz(conio, console_abandoned_msg); - result = SPR_USER_ABORT; - } - - conio_free(conio); - return result; -} - -bool is_interactive(void) -{ - ConsoleIO *conio = conio_setup(false); - bool toret = conio->hin_is_console; - conio_free(conio); - return toret; -} - -bool console_antispoof_prompt = true; - -void console_set_trust_status(Seat *seat, bool trusted) -{ - /* Do nothing in response to a change of trust status, because - * there's nothing we can do in a console environment. However, - * the query function below will make a fiddly decision about - * whether to tell the backend to enable fallback handling. */ -} - -bool console_can_set_trust_status(Seat *seat) -{ - if (console_batch_mode) { - /* - * In batch mode, we don't need to worry about the server - * mimicking our interactive authentication, because the user - * already knows not to expect any. - */ - return true; - } - - return false; -} - -bool console_has_mixed_input_stream(Seat *seat) -{ - if (!is_interactive() || !console_antispoof_prompt) { - /* - * If standard input isn't connected to a terminal, then even - * if the server did send a spoof authentication prompt, the - * user couldn't respond to it via the terminal anyway. - * - * We also pretend this is true if the user has purposely - * disabled the antispoof prompt. - */ - return false; - } - - return true; -} - -/* - * 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 console_askappend(LogPolicy *lp, 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"; - - ConsoleIO *conio = conio_setup(true); - int result; - - if (console_batch_mode) { - put_fmt(conio, msgtemplate_batch, FILENAME_MAX, filename->utf8path); - result = 0; - goto out; - } - put_fmt(conio, msgtemplate, FILENAME_MAX, filename->utf8path); - - ResponseType response = parse_and_free_response( - console_read_line(conio, true)); - - if (response == RESPONSE_YES) - result = 2; - else if (response == RESPONSE_NO) - result = 1; - else - result = 0; - out: - conio_free(conio); - return result; -} - -/* - * 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"; - - fputs(message, stderr); -} - -/* - * Display the fingerprints of the PGP Master Keys to the user. - */ -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 " PGP_MASTER_KEY_YEAR - " (" PGP_MASTER_KEY_DETAILS "):\n" - " " PGP_MASTER_KEY_FP "\n\n" - "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR - ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" - " " PGP_PREV_MASTER_KEY_FP "\n", stdout); -} - -void console_logging_error(LogPolicy *lp, const char *string) -{ - /* Ordinary Event Log entries are displayed in the same way as - * logging errors, but only in verbose mode */ - fprintf(stderr, "%s\n", string); - fflush(stderr); -} - -void console_eventlog(LogPolicy *lp, const char *string) -{ - /* Ordinary Event Log entries are displayed in the same way as - * logging errors, but only in verbose mode */ - if (lp_verbose(lp)) - console_logging_error(lp, string); -} - -StripCtrlChars *console_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) -{ - return stripctrl_new(bs_out, false, 0); -} - -SeatPromptResult console_get_userpass_input(prompts_t *p) -{ - ConsoleIO *conio = conio_setup(p->utf8); - SeatPromptResult result; - size_t curr_prompt; - - /* - * 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], ""); - } - - /* - * The prompts_t might contain a message to be displayed but no - * actual prompt. More usually, though, it will contain - * questions that the user needs to answer, in which case we - * need to ensure that we're able to get the answers. - */ - if (p->n_prompts) { - if (console_batch_mode) { - result = SPR_SW_ABORT("Cannot answer interactive prompts " - "in batch mode"); - goto out; - } - } - - /* - * Preamble. - */ - /* We only print the `name' caption if we have to... */ - if (p->name_reqd && p->name) { - ptrlen plname = ptrlen_from_asciz(p->name); - put_datapl(conio, plname); - if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) - put_datalit(conio, "\n"); - } - /* ...but we always print any `instruction'. */ - if (p->instruction) { - ptrlen plinst = ptrlen_from_asciz(p->instruction); - put_datapl(conio, plinst); - if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) - put_datalit(conio, "\n"); - } - - for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { - prompt_t *pr = p->prompts[curr_prompt]; - - put_dataz(conio, pr->prompt); - - if (!console_read_line_to_strbuf(conio, pr->echo, pr->result)) { - result = make_spr_sw_abort_winerror( - "Error reading from console", GetLastError()); - goto out; - } else if (!pr->result->len) { - /* Regard EOF on the terminal as a deliberate user-abort */ - result = SPR_USER_ABORT; - goto out; - } else { - if (strbuf_chomp(pr->result, '\n')) { - strbuf_chomp(pr->result, '\r'); - } - } - } - - result = SPR_OK; - out: - conio_free(conio); - return result; -} diff --git a/WINDOWS/controls.c b/WINDOWS/controls.c deleted file mode 100644 index 84708168a..000000000 --- a/WINDOWS/controls.c +++ /dev/null @@ -1,2653 +0,0 @@ -/* - * controls.c: routines to self-manage the controls in a dialog - * box. - */ - -/* - * Possible TODO in new cross-platform config box stuff: - * - * - When lining up two controls alongside each other, I wonder if - * we could conveniently arrange to centre them vertically? - * Particularly ugly in the current setup is the `Add new - * forwarded port:' static next to the rather taller `Remove' - * button. - */ - -#include -#include - -#include "putty.h" -#include "misc.h" -#include "dialog.h" - -#include - -#define GAPBETWEEN 3 -#define GAPWITHIN 1 -#define GAPXBOX 7 -#define GAPYBOX 4 -#define DLGWIDTH 168 -#define STATICHEIGHT 8 -#define TITLEHEIGHT 12 -#define CHECKBOXHEIGHT 8 -#define RADIOHEIGHT 8 -#define EDITHEIGHT 12 -#define LISTHEIGHT 11 -#define LISTINCREMENT 8 -#define COMBOHEIGHT 12 -#define PUSHBTNHEIGHT 14 -#define PROGBARHEIGHT 14 - -DECL_WINDOWS_FUNCTION(static, void, InitCommonControls, (void)); -DECL_WINDOWS_FUNCTION(static, BOOL, MakeDragList, (HWND)); -DECL_WINDOWS_FUNCTION(static, int, LBItemFromPt, (HWND, POINT, BOOL)); -DECL_WINDOWS_FUNCTION(static, void, DrawInsert, (HWND, HWND, int)); - -void init_common_controls(void) -{ - HMODULE comctl32_module = load_system32_dll("comctl32.dll"); - GET_WINDOWS_FUNCTION(comctl32_module, InitCommonControls); - GET_WINDOWS_FUNCTION(comctl32_module, MakeDragList); - GET_WINDOWS_FUNCTION(comctl32_module, LBItemFromPt); - GET_WINDOWS_FUNCTION(comctl32_module, DrawInsert); - p_InitCommonControls(); -} - -void ctlposinit(struct ctlpos *cp, HWND hwnd, - int leftborder, int rightborder, int topborder) -{ - RECT r, r2; - cp->hwnd = hwnd; - cp->font = SendMessage(hwnd, WM_GETFONT, 0, 0); - cp->ypos = topborder; - GetClientRect(hwnd, &r); - r2.left = r2.top = 0; - r2.right = 4; - r2.bottom = 8; - MapDialogRect(hwnd, &r2); - cp->dlu4inpix = r2.right; - cp->width = (r.right * 4) / (r2.right) - 2 * GAPBETWEEN; - cp->xoff = leftborder; - cp->width -= leftborder + rightborder; -} - -HWND doctl(struct ctlpos *cp, RECT r, const char *wclass, int wstyle, - int exstyle, const char *wtext, int wid) -{ - HWND ctl; - /* - * Note nonstandard use of RECT. This is deliberate: by - * transforming the width and height directly we arrange to - * have all supposedly same-sized controls really same-sized. - */ - - r.left += cp->xoff; - MapDialogRect(cp->hwnd, &r); - - /* - * We can pass in cp->hwnd == NULL, to indicate a dry run - * without creating any actual controls. - */ - if (cp->hwnd) { - ctl = CreateWindowEx(exstyle, wclass, wtext, wstyle, - r.left, r.top, r.right, r.bottom, - cp->hwnd, (HMENU)(ULONG_PTR)wid, hinst, NULL); - SendMessage(ctl, WM_SETFONT, cp->font, MAKELPARAM(true, 0)); - - if (!strcmp(wclass, "LISTBOX")) { - /* - * Bizarre Windows bug: the list box calculates its - * number of lines based on the font it has at creation - * time, but sending it WM_SETFONT doesn't cause it to - * recalculate. So now, _after_ we've sent it - * WM_SETFONT, we explicitly resize it (to the same - * size it was already!) to force it to reconsider. - */ - SetWindowPos(ctl, NULL, 0, 0, r.right, r.bottom, - SWP_NOACTIVATE | SWP_NOCOPYBITS | - SWP_NOMOVE | SWP_NOZORDER); - } - } else - ctl = NULL; - return ctl; -} - -/* - * A title bar across the top of a sub-dialog. - */ -void bartitle(struct ctlpos *cp, const char *name, int id) -{ - RECT r; - - r.left = GAPBETWEEN; - r.right = cp->width; - r.top = cp->ypos; - r.bottom = STATICHEIGHT; - cp->ypos += r.bottom + GAPBETWEEN; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, name, id); -} - -/* - * Begin a grouping box, with or without a group title. - */ -void beginbox(struct ctlpos *cp, const char *name, int idbox) -{ - cp->boxystart = cp->ypos; - if (!name) - cp->boxystart -= STATICHEIGHT / 2; - if (name) - cp->ypos += STATICHEIGHT; - cp->ypos += GAPYBOX; - cp->width -= 2 * GAPXBOX; - cp->xoff += GAPXBOX; - cp->boxid = idbox; - cp->boxtext = name; -} - -/* - * End a grouping box. - */ -void endbox(struct ctlpos *cp) -{ - RECT r; - cp->xoff -= GAPXBOX; - cp->width += 2 * GAPXBOX; - cp->ypos += GAPYBOX - GAPBETWEEN; - r.left = GAPBETWEEN; - r.right = cp->width; - r.top = cp->boxystart; - r.bottom = cp->ypos - cp->boxystart; - doctl(cp, r, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 0, - cp->boxtext ? cp->boxtext : "", cp->boxid); - cp->ypos += GAPYBOX; -} - -/* - * A static line, followed by a full-width edit box. - */ -void editboxfw(struct ctlpos *cp, bool password, bool readonly, - const char *text, int staticid, int editid) -{ - RECT r; - - r.left = GAPBETWEEN; - r.right = cp->width; - - if (text) { - r.top = cp->ypos; - r.bottom = STATICHEIGHT; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid); - cp->ypos += STATICHEIGHT + GAPWITHIN; - } - r.top = cp->ypos; - r.bottom = EDITHEIGHT; - doctl(cp, r, "EDIT", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | - (password ? ES_PASSWORD : 0) | - (readonly ? ES_READONLY : 0), - WS_EX_CLIENTEDGE, "", editid); - cp->ypos += EDITHEIGHT + GAPBETWEEN; -} - -/* - * A static line, followed by a full-width combo box. - */ -void combobox(struct ctlpos *cp, const char *text, int staticid, int listid) -{ - RECT r; - - r.left = GAPBETWEEN; - r.right = cp->width; - - if (text) { - r.top = cp->ypos; - r.bottom = STATICHEIGHT; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid); - cp->ypos += STATICHEIGHT + GAPWITHIN; - } - r.top = cp->ypos; - r.bottom = COMBOHEIGHT * 10; - doctl(cp, r, "COMBOBOX", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | - CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", listid); - cp->ypos += COMBOHEIGHT + GAPBETWEEN; -} - -struct radio { const char *text; int id; }; - -static void radioline_common(struct ctlpos *cp, const char *text, int id, - int nacross, struct radio *buttons, int nbuttons) -{ - RECT r; - int group; - int i; - int j; - - r.left = GAPBETWEEN; - r.top = cp->ypos; - if (text) { - r.right = cp->width; - r.bottom = STATICHEIGHT; - cp->ypos += r.bottom + GAPWITHIN; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id); - } else { - r.right = r.bottom = 0; - } - - group = WS_GROUP; - i = 0; - for (j = 0; j < nbuttons; j++) { - const char *btext = buttons[j].text; - int bid = buttons[j].id; - - if (i == nacross) { - cp->ypos += r.bottom + (nacross > 1 ? GAPBETWEEN : GAPWITHIN); - i = 0; - } - r.left = GAPBETWEEN + i * (cp->width + GAPBETWEEN) / nacross; - if (j < nbuttons-1) - r.right = - (i + 1) * (cp->width + GAPBETWEEN) / nacross - r.left; - else - r.right = cp->width - r.left; - r.top = cp->ypos; - r.bottom = RADIOHEIGHT; - doctl(cp, r, "BUTTON", - BS_NOTIFY | BS_AUTORADIOBUTTON | WS_CHILD | - WS_VISIBLE | WS_TABSTOP | group, 0, btext, bid); - group = 0; - i++; - } - cp->ypos += r.bottom + GAPBETWEEN; -} - -/* - * A set of radio buttons on the same line, with a static above - * them. `nacross' dictates how many parts the line is divided into - * (you might want this not to equal the number of buttons if you - * needed to line up some 2s and some 3s to look good in the same - * panel). - * - * There's a bit of a hack in here to ensure that if nacross - * exceeds the actual number of buttons, the rightmost button - * really does get all the space right to the edge of the line, so - * you can do things like - * - * (*) Button1 (*) Button2 (*) ButtonWithReallyLongTitle - */ -void radioline(struct ctlpos *cp, const char *text, int id, int nacross, ...) -{ - va_list ap; - struct radio *buttons; - int i, nbuttons; - - va_start(ap, nacross); - nbuttons = 0; - while (1) { - const char *btext = va_arg(ap, const char *); - if (!btext) - break; - (void) va_arg(ap, int); /* id */ - nbuttons++; - } - va_end(ap); - buttons = snewn(nbuttons, struct radio); - va_start(ap, nacross); - for (i = 0; i < nbuttons; i++) { - buttons[i].text = va_arg(ap, const char *); - buttons[i].id = va_arg(ap, int); - } - va_end(ap); - radioline_common(cp, text, id, nacross, buttons, nbuttons); - sfree(buttons); -} - -/* - * A set of radio buttons on the same line, without a static above - * them. Otherwise just like radioline. - */ -void bareradioline(struct ctlpos *cp, int nacross, ...) -{ - va_list ap; - struct radio *buttons; - int i, nbuttons; - - va_start(ap, nacross); - nbuttons = 0; - while (1) { - const char *btext = va_arg(ap, const char *); - if (!btext) - break; - (void) va_arg(ap, int); /* id */ - nbuttons++; - } - va_end(ap); - buttons = snewn(nbuttons, struct radio); - va_start(ap, nacross); - for (i = 0; i < nbuttons; i++) { - buttons[i].text = va_arg(ap, const char *); - buttons[i].id = va_arg(ap, int); - } - va_end(ap); - radioline_common(cp, NULL, 0, nacross, buttons, nbuttons); - sfree(buttons); -} - -/* - * A set of radio buttons on multiple lines, with a static above - * them. - */ -void radiobig(struct ctlpos *cp, const char *text, int id, ...) -{ - va_list ap; - struct radio *buttons; - int i, nbuttons; - - va_start(ap, id); - nbuttons = 0; - while (1) { - const char *btext = va_arg(ap, const char *); - if (!btext) - break; - (void) va_arg(ap, int); /* id */ - nbuttons++; - } - va_end(ap); - buttons = snewn(nbuttons, struct radio); - va_start(ap, id); - for (i = 0; i < nbuttons; i++) { - buttons[i].text = va_arg(ap, const char *); - buttons[i].id = va_arg(ap, int); - } - va_end(ap); - radioline_common(cp, text, id, 1, buttons, nbuttons); - sfree(buttons); -} - -/* - * A single standalone checkbox. - */ -void checkbox(struct ctlpos *cp, const char *text, int id) -{ - RECT r; - - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = CHECKBOXHEIGHT; - cp->ypos += r.bottom + GAPBETWEEN; - doctl(cp, r, "BUTTON", - BS_NOTIFY | BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0, - text, id); -} - -/* - * Wrap a piece of text for a static text control. Returns the - * wrapped text (a malloc'ed string containing \ns), and also - * returns the number of lines required. - */ -char *staticwrap(struct ctlpos *cp, HWND hwnd, const char *text, int *lines) -{ - HDC hdc = GetDC(hwnd); - int width, nlines, j; - INT *pwidths, nfit; - SIZE size; - const char *p; - RECT r; - HFONT oldfont, newfont; - - strbuf *sb = strbuf_new(); - p = text; - pwidths = snewn(1+strlen(text), INT); - - /* - * Work out the width the text will need to fit in, by doing - * the same adjustment that the `statictext' function itself - * will perform. - */ - SetMapMode(hdc, MM_TEXT); /* ensure logical units == pixels */ - r.left = r.top = r.bottom = 0; - r.right = cp->width; - MapDialogRect(hwnd, &r); - width = r.right; - - nlines = 1; - - /* - * We must select the correct font into the HDC before calling - * GetTextExtent*, or silly things will happen. - */ - newfont = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0); - oldfont = SelectObject(hdc, newfont); - - while (*p) { - if (!GetTextExtentExPoint(hdc, p, strlen(p), width, - &nfit, pwidths, &size) || - (size_t)nfit >= strlen(p)) { - /* - * Either GetTextExtentExPoint returned failure, or the - * whole of the rest of the text fits on this line. - * Either way, we stop wrapping, copy the remainder of - * the input string unchanged to the output, and leave. - */ - put_datapl(sb, ptrlen_from_asciz(p)); - break; - } - - /* - * Now we search backwards along the string from `nfit', - * looking for a space at which to break the line. If we - * don't find one at all, that's fine - we'll just break - * the line at `nfit'. - */ - for (j = nfit; j > 0; j--) { - if (isspace((unsigned char)p[j])) { - nfit = j; - break; - } - } - - put_data(sb, p, nfit); - put_byte(sb, '\n'); - - p += nfit; - while (*p && isspace((unsigned char)*p)) - p++; - - nlines++; - } - - SelectObject(hdc, oldfont); - ReleaseDC(cp->hwnd, hdc); - - if (lines) *lines = nlines; - - sfree(pwidths); - - return strbuf_to_str(sb); -} - -/* - * A single standalone static text control. - */ -void statictext(struct ctlpos *cp, const char *text, int lines, int id) -{ - RECT r; - - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = STATICHEIGHT * lines; - cp->ypos += r.bottom + GAPBETWEEN; - doctl(cp, r, "STATIC", - WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, - 0, text, id); -} - -/* - * An owner-drawn static text control for a panel title. - */ -void paneltitle(struct ctlpos *cp, int id) -{ - RECT r; - - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = TITLEHEIGHT; - cp->ypos += r.bottom + GAPBETWEEN; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_OWNERDRAW, - 0, NULL, id); -} - -/* - * A button on the right hand side, with a static to its left. - */ -void staticbtn(struct ctlpos *cp, const char *stext, int sid, - const char *btext, int bid) -{ - const int height = (PUSHBTNHEIGHT > STATICHEIGHT ? - PUSHBTNHEIGHT : STATICHEIGHT); - RECT r; - int lwid, rwid, rpos; - - rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4; - lwid = rpos - 2 * GAPBETWEEN; - rwid = cp->width + GAPBETWEEN - rpos; - - r.left = GAPBETWEEN; - r.top = cp->ypos + (height - STATICHEIGHT) / 2; - r.right = lwid; - r.bottom = STATICHEIGHT; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid); - - r.left = rpos; - r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2; - r.right = rwid; - r.bottom = PUSHBTNHEIGHT; - doctl(cp, r, "BUTTON", - BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, - 0, btext, bid); - - cp->ypos += height + GAPBETWEEN; -} - -/* - * A simple push button. - */ -void button(struct ctlpos *cp, const char *btext, int bid, bool defbtn) -{ - RECT r; - - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = PUSHBTNHEIGHT; - - /* Q67655: the _dialog box_ must know which button is default - * as well as the button itself knowing */ - if (defbtn && cp->hwnd) - SendMessage(cp->hwnd, DM_SETDEFID, bid, 0); - - doctl(cp, r, "BUTTON", - BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | - (defbtn ? BS_DEFPUSHBUTTON : 0) | BS_PUSHBUTTON, - 0, btext, bid); - - cp->ypos += PUSHBTNHEIGHT + GAPBETWEEN; -} - -/* - * Like staticbtn, but two buttons. - */ -void static2btn(struct ctlpos *cp, const char *stext, int sid, - const char *btext1, int bid1, const char *btext2, int bid2) -{ - const int height = (PUSHBTNHEIGHT > STATICHEIGHT ? - PUSHBTNHEIGHT : STATICHEIGHT); - RECT r; - int lwid, rwid1, rwid2, rpos1, rpos2; - - rpos1 = GAPBETWEEN + (cp->width + GAPBETWEEN) / 2; - rpos2 = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4; - lwid = rpos1 - 2 * GAPBETWEEN; - rwid1 = rpos2 - rpos1 - GAPBETWEEN; - rwid2 = cp->width + GAPBETWEEN - rpos2; - - r.left = GAPBETWEEN; - r.top = cp->ypos + (height - STATICHEIGHT) / 2; - r.right = lwid; - r.bottom = STATICHEIGHT; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid); - - r.left = rpos1; - r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2; - r.right = rwid1; - r.bottom = PUSHBTNHEIGHT; - doctl(cp, r, "BUTTON", - BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, - 0, btext1, bid1); - - r.left = rpos2; - r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2; - r.right = rwid2; - r.bottom = PUSHBTNHEIGHT; - doctl(cp, r, "BUTTON", - BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, - 0, btext2, bid2); - - cp->ypos += height + GAPBETWEEN; -} - -/* - * An edit control on the right hand side, with a static to its left. - */ -static void staticedit_internal(struct ctlpos *cp, const char *stext, - int sid, int eid, int percentedit, - int style) -{ - const int height = (EDITHEIGHT > STATICHEIGHT ? - EDITHEIGHT : STATICHEIGHT); - RECT r; - int lwid, rwid, rpos; - - rpos = - GAPBETWEEN + (100 - percentedit) * (cp->width + GAPBETWEEN) / 100; - lwid = rpos - 2 * GAPBETWEEN; - rwid = cp->width + GAPBETWEEN - rpos; - - r.left = GAPBETWEEN; - r.top = cp->ypos + (height - STATICHEIGHT) / 2; - r.right = lwid; - r.bottom = STATICHEIGHT; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid); - - r.left = rpos; - r.top = cp->ypos + (height - EDITHEIGHT) / 2; - r.right = rwid; - r.bottom = EDITHEIGHT; - doctl(cp, r, "EDIT", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | style, - WS_EX_CLIENTEDGE, "", eid); - - cp->ypos += height + GAPBETWEEN; -} - -void staticedit(struct ctlpos *cp, const char *stext, - int sid, int eid, int percentedit) -{ - staticedit_internal(cp, stext, sid, eid, percentedit, 0); -} - -void staticpassedit(struct ctlpos *cp, const char *stext, - int sid, int eid, int percentedit) -{ - staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD); -} - -/* - * A drop-down list box on the right hand side, with a static to - * its left. - */ -void staticddl(struct ctlpos *cp, const char *stext, - int sid, int lid, int percentlist) -{ - const int height = (COMBOHEIGHT > STATICHEIGHT ? - COMBOHEIGHT : STATICHEIGHT); - RECT r; - int lwid, rwid, rpos; - - rpos = - GAPBETWEEN + (100 - percentlist) * (cp->width + GAPBETWEEN) / 100; - lwid = rpos - 2 * GAPBETWEEN; - rwid = cp->width + GAPBETWEEN - rpos; - - r.left = GAPBETWEEN; - r.top = cp->ypos + (height - STATICHEIGHT) / 2; - r.right = lwid; - r.bottom = STATICHEIGHT; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid); - - r.left = rpos; - r.top = cp->ypos + (height - EDITHEIGHT) / 2; - r.right = rwid; - r.bottom = COMBOHEIGHT*4; - doctl(cp, r, "COMBOBOX", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | - CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid); - - cp->ypos += height + GAPBETWEEN; -} - -/* - * A combo box on the right hand side, with a static to its left. - */ -void staticcombo(struct ctlpos *cp, const char *stext, - int sid, int lid, int percentlist) -{ - const int height = (COMBOHEIGHT > STATICHEIGHT ? - COMBOHEIGHT : STATICHEIGHT); - RECT r; - int lwid, rwid, rpos; - - rpos = - GAPBETWEEN + (100 - percentlist) * (cp->width + GAPBETWEEN) / 100; - lwid = rpos - 2 * GAPBETWEEN; - rwid = cp->width + GAPBETWEEN - rpos; - - r.left = GAPBETWEEN; - r.top = cp->ypos + (height - STATICHEIGHT) / 2; - r.right = lwid; - r.bottom = STATICHEIGHT; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid); - - r.left = rpos; - r.top = cp->ypos + (height - EDITHEIGHT) / 2; - r.right = rwid; - r.bottom = COMBOHEIGHT*10; - doctl(cp, r, "COMBOBOX", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | - CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid); - - cp->ypos += height + GAPBETWEEN; -} - -/* - * A static, with a full-width drop-down list box below it. - */ -void staticddlbig(struct ctlpos *cp, const char *stext, - int sid, int lid) -{ - RECT r; - - if (stext) { - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = STATICHEIGHT; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid); - cp->ypos += STATICHEIGHT; - } - - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = COMBOHEIGHT*4; - doctl(cp, r, "COMBOBOX", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | - CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid); - cp->ypos += COMBOHEIGHT + GAPBETWEEN; -} - -/* - * A big multiline edit control with a static labelling it. - */ -void bigeditctrl(struct ctlpos *cp, const char *stext, - int sid, int eid, int lines) -{ - RECT r; - - if (stext) { - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = STATICHEIGHT; - cp->ypos += r.bottom + GAPWITHIN; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid); - } - - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = EDITHEIGHT + (lines - 1) * STATICHEIGHT; - cp->ypos += r.bottom + GAPBETWEEN; - doctl(cp, r, "EDIT", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | ES_MULTILINE, - WS_EX_CLIENTEDGE, "", eid); -} - -/* - * A list box with a static labelling it. - */ -void listbox(struct ctlpos *cp, const char *stext, - int sid, int lid, int lines, bool multi) -{ - RECT r; - - if (stext != NULL) { - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = STATICHEIGHT; - cp->ypos += r.bottom + GAPWITHIN; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid); - } - - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = LISTHEIGHT + (lines - 1) * LISTINCREMENT; - cp->ypos += r.bottom + GAPBETWEEN; - doctl(cp, r, "LISTBOX", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | - LBS_NOTIFY | LBS_HASSTRINGS | LBS_USETABSTOPS | - (multi ? LBS_MULTIPLESEL : 0), - WS_EX_CLIENTEDGE, "", lid); -} - -/* - * A tab-control substitute when a real tab control is unavailable. - */ -void ersatztab(struct ctlpos *cp, const char *stext, int sid, int lid, - int s2id) -{ - const int height = (COMBOHEIGHT > STATICHEIGHT ? - COMBOHEIGHT : STATICHEIGHT); - RECT r; - int bigwid, lwid, rwid, rpos; - static const int BIGGAP = 15; - static const int MEDGAP = 3; - - bigwid = cp->width + 2 * GAPBETWEEN - 2 * BIGGAP; - cp->ypos += MEDGAP; - rpos = BIGGAP + (bigwid + BIGGAP) / 2; - lwid = rpos - 2 * BIGGAP; - rwid = bigwid + BIGGAP - rpos; - - r.left = BIGGAP; - r.top = cp->ypos + (height - STATICHEIGHT) / 2; - r.right = lwid; - r.bottom = STATICHEIGHT; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid); - - r.left = rpos; - r.top = cp->ypos + (height - COMBOHEIGHT) / 2; - r.right = rwid; - r.bottom = COMBOHEIGHT * 10; - doctl(cp, r, "COMBOBOX", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | - CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid); - - cp->ypos += height + MEDGAP + GAPBETWEEN; - - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = 2; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ, - 0, "", s2id); -} - -/* - * A static line, followed by an edit control on the left hand side - * and a button on the right. - */ -void editbutton(struct ctlpos *cp, const char *stext, int sid, - int eid, const char *btext, int bid) -{ - const int height = (EDITHEIGHT > PUSHBTNHEIGHT ? - EDITHEIGHT : PUSHBTNHEIGHT); - RECT r; - int lwid, rwid, rpos; - - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = STATICHEIGHT; - cp->ypos += r.bottom + GAPWITHIN; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid); - - rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4; - lwid = rpos - 2 * GAPBETWEEN; - rwid = cp->width + GAPBETWEEN - rpos; - - r.left = GAPBETWEEN; - r.top = cp->ypos + (height - EDITHEIGHT) / 2; - r.right = lwid; - r.bottom = EDITHEIGHT; - doctl(cp, r, "EDIT", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL, - WS_EX_CLIENTEDGE, "", eid); - - r.left = rpos; - r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2; - r.right = rwid; - r.bottom = PUSHBTNHEIGHT; - doctl(cp, r, "BUTTON", - BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, - 0, btext, bid); - - cp->ypos += height + GAPBETWEEN; -} - -/* - * A special control for manipulating an ordered preference list - * (eg. for cipher selection). - * XXX: this is a rough hack and could be improved. - */ -void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines, - const char *stext, int sid, int listid, int upbid, int dnbid) -{ - const static int percents[] = { 5, 75, 20 }; - RECT r; - int xpos, percent = 0, i; - int listheight = LISTHEIGHT + (lines - 1) * LISTINCREMENT; - const int BTNSHEIGHT = 2*PUSHBTNHEIGHT + GAPBETWEEN; - int totalheight, buttonpos; - - /* Squirrel away IDs. */ - hdl->listid = listid; - hdl->upbid = upbid; - hdl->dnbid = dnbid; - - /* The static label. */ - if (stext != NULL) { - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = STATICHEIGHT; - cp->ypos += r.bottom + GAPWITHIN; - doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid); - } - - if (listheight > BTNSHEIGHT) { - totalheight = listheight; - buttonpos = (listheight - BTNSHEIGHT) / 2; - } else { - totalheight = BTNSHEIGHT; - buttonpos = 0; - } - - for (i=0; i<3; i++) { - int left, wid; - xpos = (cp->width + GAPBETWEEN) * percent / 100; - left = xpos + GAPBETWEEN; - percent += percents[i]; - xpos = (cp->width + GAPBETWEEN) * percent / 100; - wid = xpos - left; - - switch (i) { - case 1: { - /* The drag list box. */ - r.left = left; r.right = wid; - r.top = cp->ypos; r.bottom = listheight; - HWND ctl = doctl(cp, r, "LISTBOX", - WS_CHILD | WS_VISIBLE | WS_TABSTOP | - WS_VSCROLL | LBS_HASSTRINGS | LBS_USETABSTOPS, - WS_EX_CLIENTEDGE, - "", listid); - p_MakeDragList(ctl); - break; - } - - case 2: - /* The "Up" and "Down" buttons. */ - /* XXX worry about accelerators if we have more than one - * prefslist on a panel */ - r.left = left; r.right = wid; - r.top = cp->ypos + buttonpos; r.bottom = PUSHBTNHEIGHT; - doctl(cp, r, "BUTTON", - BS_NOTIFY | WS_CHILD | WS_VISIBLE | - WS_TABSTOP | BS_PUSHBUTTON, - 0, "&Up", upbid); - - r.left = left; r.right = wid; - r.top = cp->ypos + buttonpos + PUSHBTNHEIGHT + GAPBETWEEN; - r.bottom = PUSHBTNHEIGHT; - doctl(cp, r, "BUTTON", - BS_NOTIFY | WS_CHILD | WS_VISIBLE | - WS_TABSTOP | BS_PUSHBUTTON, - 0, "&Down", dnbid); - - break; - - } - } - - cp->ypos += totalheight + GAPBETWEEN; - -} - -/* - * Helper function for prefslist: move item in list box. - */ -static void pl_moveitem(HWND hwnd, int listid, int src, int dst) -{ - int tlen, val; - char *txt; - /* Get the item's data. */ - tlen = SendDlgItemMessage (hwnd, listid, LB_GETTEXTLEN, src, 0); - txt = snewn(tlen+1, char); - SendDlgItemMessage (hwnd, listid, LB_GETTEXT, src, (LPARAM) txt); - val = SendDlgItemMessage (hwnd, listid, LB_GETITEMDATA, src, 0); - /* Deselect old location. */ - SendDlgItemMessage (hwnd, listid, LB_SETSEL, false, src); - /* Delete it at the old location. */ - SendDlgItemMessage (hwnd, listid, LB_DELETESTRING, src, 0); - /* Insert it at new location. */ - SendDlgItemMessage (hwnd, listid, LB_INSERTSTRING, dst, - (LPARAM) txt); - SendDlgItemMessage (hwnd, listid, LB_SETITEMDATA, dst, - (LPARAM) val); - /* Set selection. */ - SendDlgItemMessage (hwnd, listid, LB_SETCURSEL, dst, 0); - sfree (txt); -} - -int pl_itemfrompt(HWND hwnd, POINT cursor, bool scroll) -{ - int ret; - POINT uppoint, downpoint; - int updist, downdist, upitem, downitem, i; - - /* - * Ghastly hackery to try to figure out not which - * _item_, but which _gap between items_, the user - * is pointing at. We do this by first working out - * which list item is under the cursor, and then - * working out how far the cursor would have to - * move up or down before the answer was different. - * Then we put the insertion point _above_ the - * current item if the upper edge is closer than - * the lower edge, or _below_ it if vice versa. - */ - ret = p_LBItemFromPt(hwnd, cursor, scroll); - if (ret == -1) - return ret; - ret = p_LBItemFromPt(hwnd, cursor, false); - updist = downdist = 0; - for (i = 1; i < 4096 && (!updist || !downdist); i++) { - uppoint = downpoint = cursor; - uppoint.y -= i; - downpoint.y += i; - upitem = p_LBItemFromPt(hwnd, uppoint, false); - downitem = p_LBItemFromPt(hwnd, downpoint, false); - if (!updist && upitem != ret) - updist = i; - if (!downdist && downitem != ret) - downdist = i; - } - if (downdist < updist) - ret++; - return ret; -} - -/* - * Handler for prefslist above. - * - * Return value has bit 0 set if the dialog box procedure needs to - * return true from handling this message; it has bit 1 set if a - * change may have been made in the contents of the list. - */ -int handle_prefslist(struct prefslist *hdl, - int *array, int maxmemb, - bool is_dlmsg, HWND hwnd, - WPARAM wParam, LPARAM lParam) -{ - int i; - int ret = 0; - - if (is_dlmsg) { - - if ((int)wParam == hdl->listid) { - DRAGLISTINFO *dlm = (DRAGLISTINFO *)lParam; - int dest = 0; /* initialise to placate gcc */ - switch (dlm->uNotification) { - case DL_BEGINDRAG: - /* Add a dummy item to make pl_itemfrompt() work - * better. - * FIXME: this causes scrollbar glitches if the count of - * listbox contains >= its height. */ - hdl->dummyitem = - SendDlgItemMessage(hwnd, hdl->listid, - LB_ADDSTRING, 0, (LPARAM) ""); - - hdl->srcitem = p_LBItemFromPt(dlm->hWnd, dlm->ptCursor, true); - hdl->dragging = false; - /* XXX hack Q183115 */ - SetWindowLongPtr(hwnd, DWLP_MSGRESULT, true); - ret |= 1; break; - case DL_CANCELDRAG: - p_DrawInsert(hwnd, dlm->hWnd, -1); /* Clear arrow */ - SendDlgItemMessage(hwnd, hdl->listid, - LB_DELETESTRING, hdl->dummyitem, 0); - hdl->dragging = false; - ret |= 1; break; - case DL_DRAGGING: - hdl->dragging = true; - dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, true); - if (dest > hdl->dummyitem) dest = hdl->dummyitem; - p_DrawInsert (hwnd, dlm->hWnd, dest); - if (dest >= 0) - SetWindowLongPtr(hwnd, DWLP_MSGRESULT, DL_MOVECURSOR); - else - SetWindowLongPtr(hwnd, DWLP_MSGRESULT, DL_STOPCURSOR); - ret |= 1; break; - case DL_DROPPED: - if (hdl->dragging) { - dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, true); - if (dest > hdl->dummyitem) dest = hdl->dummyitem; - p_DrawInsert (hwnd, dlm->hWnd, -1); - } - SendDlgItemMessage(hwnd, hdl->listid, - LB_DELETESTRING, hdl->dummyitem, 0); - if (hdl->dragging) { - hdl->dragging = false; - if (dest >= 0) { - /* Correct for "missing" item. */ - if (dest > hdl->srcitem) dest--; - pl_moveitem(hwnd, hdl->listid, hdl->srcitem, dest); - } - ret |= 2; - } - ret |= 1; break; - } - } - - } else { - - if (((LOWORD(wParam) == hdl->upbid) || - (LOWORD(wParam) == hdl->dnbid)) && - ((HIWORD(wParam) == BN_CLICKED) || - (HIWORD(wParam) == BN_DOUBLECLICKED))) { - /* Move an item up or down the list. */ - /* Get the current selection, if any. */ - int selection = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCURSEL, 0, 0); - if (selection == LB_ERR) { - MessageBeep(0); - } else { - int nitems; - /* Get the total number of items. */ - nitems = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCOUNT, 0, 0); - /* Should we do anything? */ - if (LOWORD(wParam) == hdl->upbid && (selection > 0)) - pl_moveitem(hwnd, hdl->listid, selection, selection - 1); - else if (LOWORD(wParam) == hdl->dnbid && (selection < nitems - 1)) - pl_moveitem(hwnd, hdl->listid, selection, selection + 1); - ret |= 2; - } - - } - - } - - if (array) { - /* Update array to match the list box. */ - for (i=0; i < maxmemb; i++) - array[i] = SendDlgItemMessage (hwnd, hdl->listid, LB_GETITEMDATA, - i, 0); - } - - return ret; -} - -/* - * A progress bar (from Common Controls). We like our progress bars - * to be smooth and unbroken, without those ugly divisions; some - * older compilers may not support that, but that's life. - */ -void progressbar(struct ctlpos *cp, int id) -{ - RECT r; - - r.left = GAPBETWEEN; - r.top = cp->ypos; - r.right = cp->width; - r.bottom = PROGBARHEIGHT; - cp->ypos += r.bottom + GAPBETWEEN; - - doctl(cp, r, PROGRESS_CLASS, WS_CHILD | WS_VISIBLE -#ifdef PBS_SMOOTH - | PBS_SMOOTH -#endif - , WS_EX_CLIENTEDGE, "", id); -} - -/* ---------------------------------------------------------------------- - * Platform-specific side of portable dialog-box mechanism. - */ - -/* - * This function takes a string, escapes all the ampersands, and - * places a single (unescaped) ampersand in front of the first - * occurrence of the given shortcut character (which may be - * NO_SHORTCUT). - * - * Return value is a malloc'ed copy of the processed version of the - * string. - */ -static char *shortcut_escape(const char *text, char shortcut) -{ - if (!text) - return NULL; /* sfree won't choke on this */ - - strbuf *sb = strbuf_new(); - shortcut = tolower((unsigned char)shortcut); - - const char *p = text; - while (*p) { - if (shortcut != NO_SHORTCUT && - tolower((unsigned char)*p) == shortcut) { - put_byte(sb, '&'); - shortcut = NO_SHORTCUT; /* stop it happening twice */ - } else if (*p == '&') { - put_byte(sb, '&'); - } - put_byte(sb, *p++); - } - return strbuf_to_str(sb); -} - -void winctrl_add_shortcuts(struct dlgparam *dp, struct winctrl *c) -{ - int i; - for (i = 0; i < lenof(c->shortcuts); i++) - if (c->shortcuts[i] != NO_SHORTCUT) { - unsigned char s = tolower((unsigned char)c->shortcuts[i]); - assert(!dp->shortcuts[s]); - dp->shortcuts[s] = true; - } -} - -void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c) -{ - int i; - for (i = 0; i < lenof(c->shortcuts); i++) - if (c->shortcuts[i] != NO_SHORTCUT) { - unsigned char s = tolower((unsigned char)c->shortcuts[i]); - assert(dp->shortcuts[s]); - dp->shortcuts[s] = false; - } -} - -static int winctrl_cmp_byctrl(void *av, void *bv) -{ - struct winctrl *a = (struct winctrl *)av; - struct winctrl *b = (struct winctrl *)bv; - if (a->ctrl < b->ctrl) - return -1; - else if (a->ctrl > b->ctrl) - return +1; - else - return 0; -} -static int winctrl_cmp_byid(void *av, void *bv) -{ - struct winctrl *a = (struct winctrl *)av; - struct winctrl *b = (struct winctrl *)bv; - if (a->base_id < b->base_id) - return -1; - else if (a->base_id > b->base_id) - return +1; - else - return 0; -} -static int winctrl_cmp_byctrl_find(void *av, void *bv) -{ - dlgcontrol *a = (dlgcontrol *)av; - struct winctrl *b = (struct winctrl *)bv; - if (a < b->ctrl) - return -1; - else if (a > b->ctrl) - return +1; - else - return 0; -} -static int winctrl_cmp_byid_find(void *av, void *bv) -{ - int *a = (int *)av; - struct winctrl *b = (struct winctrl *)bv; - if (*a < b->base_id) - return -1; - else if (*a >= b->base_id + b->num_ids) - return +1; - else - return 0; -} - -void winctrl_init(struct winctrls *wc) -{ - wc->byctrl = newtree234(winctrl_cmp_byctrl); - wc->byid = newtree234(winctrl_cmp_byid); -} -void winctrl_cleanup(struct winctrls *wc) -{ - struct winctrl *c; - - while ((c = index234(wc->byid, 0)) != NULL) { - winctrl_remove(wc, c); - sfree(c->data); - sfree(c); - } - - freetree234(wc->byctrl); - freetree234(wc->byid); - wc->byctrl = wc->byid = NULL; -} - -void winctrl_add(struct winctrls *wc, struct winctrl *c) -{ - struct winctrl *ret; - if (c->ctrl) { - ret = add234(wc->byctrl, c); - assert(ret == c); - } - ret = add234(wc->byid, c); - assert(ret == c); -} - -void winctrl_remove(struct winctrls *wc, struct winctrl *c) -{ - struct winctrl *ret; - ret = del234(wc->byctrl, c); - ret = del234(wc->byid, c); - assert(ret == c); -} - -struct winctrl *winctrl_findbyctrl(struct winctrls *wc, dlgcontrol *ctrl) -{ - return find234(wc->byctrl, ctrl, winctrl_cmp_byctrl_find); -} - -struct winctrl *winctrl_findbyid(struct winctrls *wc, int id) -{ - return find234(wc->byid, &id, winctrl_cmp_byid_find); -} - -struct winctrl *winctrl_findbyindex(struct winctrls *wc, int index) -{ - return index234(wc->byid, index); -} - -static void move_windows(HWND hwnd, int base_id, int num_ids, LONG dy) -{ - if (!dy) - return; - for (int i = 0; i < num_ids; i++) { - HWND win = GetDlgItem(hwnd, base_id + i); - - RECT rect; - if (!GetWindowRect(win, &rect)) - continue; - - POINT p; - p.x = rect.left; - p.y = rect.top + dy; - if (!ScreenToClient(hwnd, &p)) - continue; - - SetWindowPos(win, NULL, p.x, p.y, 0, 0, - SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); - } -} - -void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, - struct ctlpos *cp, struct controlset *s, int *id) -{ - struct ctlpos columns[16]; - int ncols, colstart, colspan; - - struct ctlpos tabdelays[16]; - dlgcontrol *tabdelayed[16]; - int ntabdelays; - - struct ctlpos pos; - - char shortcuts[MAX_SHORTCUTS_PER_CTRL]; - int nshortcuts; - char *escaped; - int i, actual_base_id, base_id, num_ids, align_id_relative; - void *data; - - base_id = *id; - - ctrlset_normalise_aligns(s); - - /* Start a containing box, if we have a boxname. */ - if (s->boxname && *s->boxname) { - struct winctrl *c = snew(struct winctrl); - c->ctrl = NULL; - c->base_id = c->align_id = base_id; - c->num_ids = 1; - c->data = NULL; - memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts)); - winctrl_add(wc, c); - beginbox(cp, s->boxtitle, base_id); - base_id++; - } - - /* Draw a title, if we have one. */ - if (!s->boxname && s->boxtitle) { - struct winctrl *c = snew(struct winctrl); - c->ctrl = NULL; - c->base_id = c->align_id = base_id; - c->num_ids = 1; - c->data = dupstr(s->boxtitle); - memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts)); - winctrl_add(wc, c); - paneltitle(cp, base_id); - base_id++; - } - - /* Initially we have just one column. */ - ncols = 1; - columns[0] = *cp; /* structure copy */ - - /* And initially, there are no pending tab-delayed controls. */ - ntabdelays = 0; - - /* Loop over each control in the controlset. */ - for (i = 0; i < s->ncontrols; i++) { - dlgcontrol *ctrl = s->ctrls[i]; - - /* - * Generic processing that pertains to all control types. - * At the end of this if statement, we'll have produced - * `ctrl' (a pointer to the control we have to create, or - * think about creating, in this iteration of the loop), - * `pos' (a suitable ctlpos with which to position it), and - * `c' (a winctrl structure to receive details of the - * dialog IDs). Or we'll have done a `continue', if it was - * CTRL_COLUMNS and doesn't require any control creation at - * all. - */ - if (ctrl->type == CTRL_COLUMNS) { - assert((ctrl->columns.ncols == 1) ^ (ncols == 1)); - - if (ncols == 1) { - /* - * We're splitting into multiple columns. - */ - int lpercent, rpercent, lx, rx, i; - - ncols = ctrl->columns.ncols; - assert(ncols <= lenof(columns)); - for (i = 1; i < ncols; i++) - columns[i] = columns[0]; /* structure copy */ - - lpercent = 0; - for (i = 0; i < ncols; i++) { - rpercent = lpercent + ctrl->columns.percentages[i]; - lx = columns[i].xoff + lpercent * - (columns[i].width + GAPBETWEEN) / 100; - rx = columns[i].xoff + rpercent * - (columns[i].width + GAPBETWEEN) / 100; - columns[i].xoff = lx; - columns[i].width = rx - lx - GAPBETWEEN; - lpercent = rpercent; - } - } else { - /* - * We're recombining the various columns into one. - */ - int maxy = columns[0].ypos; - int i; - for (i = 1; i < ncols; i++) - if (maxy < columns[i].ypos) - maxy = columns[i].ypos; - ncols = 1; - columns[0] = *cp; /* structure copy */ - columns[0].ypos = maxy; - } - - continue; - } else if (ctrl->type == CTRL_TABDELAY) { - int i; - - assert(!ctrl->delay_taborder); - ctrl = ctrl->tabdelay.ctrl; - - for (i = 0; i < ntabdelays; i++) - if (tabdelayed[i] == ctrl) - break; - assert(i < ntabdelays); /* we have to have found it */ - - pos = tabdelays[i]; /* structure copy */ - - colstart = colspan = -1; /* indicate this was tab-delayed */ - - } else { - /* - * If it wasn't one of those, it's a genuine control; - * so we'll have to compute a position for it now, by - * checking its column span. - */ - int col; - - colstart = COLUMN_START(ctrl->column); - colspan = COLUMN_SPAN(ctrl->column); - - pos = columns[colstart]; /* structure copy */ - pos.width = columns[colstart+colspan-1].width + - (columns[colstart+colspan-1].xoff - columns[colstart].xoff); - - for (col = colstart; col < colstart+colspan; col++) - if (pos.ypos < columns[col].ypos) - pos.ypos = columns[col].ypos; - - /* - * If this control is to be tabdelayed, add it to the - * tabdelay list, and unset pos.hwnd to inhibit actual - * control creation. - */ - if (ctrl->delay_taborder) { - assert(ntabdelays < lenof(tabdelays)); - tabdelays[ntabdelays] = pos; /* structure copy */ - tabdelayed[ntabdelays] = ctrl; - ntabdelays++; - pos.hwnd = NULL; - } - } - - /* Most controls don't need anything in c->data. */ - data = NULL; - - /* And they all start off with no shortcuts registered. */ - memset(shortcuts, NO_SHORTCUT, lenof(shortcuts)); - nshortcuts = 0; - - /* Almost all controls start at base_id. */ - actual_base_id = base_id; - - /* For vertical alignment purposes, the most relevant control - * in a group is usually the last one. But that can be - * overridden occasionally. */ - align_id_relative = -1; - - /* - * Now we're ready to actually create the control, by - * switching on its type. - */ - switch (ctrl->type) { - case CTRL_TEXT: - if (ctrl->text.wrap) { - char *wrapped, *escaped; - int lines; - num_ids = 1; - wrapped = staticwrap(&pos, cp->hwnd, - ctrl->label, &lines); - escaped = shortcut_escape(wrapped, NO_SHORTCUT); - statictext(&pos, escaped, lines, base_id); - sfree(escaped); - sfree(wrapped); - } else { - num_ids = 1; - editboxfw(&pos, false, true, NULL, 0, base_id); - SetDlgItemText(pos.hwnd, base_id, ctrl->label); - MakeDlgItemBorderless(pos.hwnd, base_id); - } - break; - case CTRL_EDITBOX: - num_ids = 2; /* static, edit */ - escaped = shortcut_escape(ctrl->label, - ctrl->editbox.shortcut); - shortcuts[nshortcuts++] = ctrl->editbox.shortcut; - if (ctrl->editbox.percentwidth == 100) { - if (ctrl->editbox.has_list) - combobox(&pos, escaped, - base_id, base_id+1); - else - editboxfw(&pos, ctrl->editbox.password, false, escaped, - base_id, base_id+1); - } else { - if (ctrl->editbox.has_list) { - staticcombo(&pos, escaped, base_id, base_id+1, - ctrl->editbox.percentwidth); - } else { - (ctrl->editbox.password ? staticpassedit : staticedit) - (&pos, escaped, base_id, base_id+1, - ctrl->editbox.percentwidth); - } - } - sfree(escaped); - break; - case CTRL_RADIO: { - num_ids = ctrl->radio.nbuttons + 1; /* label as well */ - struct radio *buttons; - int i; - - escaped = shortcut_escape(ctrl->label, - ctrl->radio.shortcut); - shortcuts[nshortcuts++] = ctrl->radio.shortcut; - - buttons = snewn(ctrl->radio.nbuttons, struct radio); - - for (i = 0; i < ctrl->radio.nbuttons; i++) { - buttons[i].text = - shortcut_escape(ctrl->radio.buttons[i], - (char)(ctrl->radio.shortcuts ? - ctrl->radio.shortcuts[i] : - NO_SHORTCUT)); - buttons[i].id = base_id + 1 + i; - if (ctrl->radio.shortcuts) { - assert(nshortcuts < MAX_SHORTCUTS_PER_CTRL); - shortcuts[nshortcuts++] = ctrl->radio.shortcuts[i]; - } - } - - radioline_common(&pos, escaped, base_id, - ctrl->radio.ncolumns, - buttons, ctrl->radio.nbuttons); - - for (i = 0; i < ctrl->radio.nbuttons; i++) { - sfree((char *)buttons[i].text); - } - sfree(buttons); - sfree(escaped); - break; - } - case CTRL_CHECKBOX: - num_ids = 1; - escaped = shortcut_escape(ctrl->label, - ctrl->checkbox.shortcut); - shortcuts[nshortcuts++] = ctrl->checkbox.shortcut; - checkbox(&pos, escaped, base_id); - sfree(escaped); - break; - case CTRL_BUTTON: - escaped = shortcut_escape(ctrl->label, - ctrl->button.shortcut); - shortcuts[nshortcuts++] = ctrl->button.shortcut; - if (ctrl->button.iscancel) - actual_base_id = IDCANCEL; - num_ids = 1; - button(&pos, escaped, actual_base_id, ctrl->button.isdefault); - sfree(escaped); - break; - case CTRL_LISTBOX: - num_ids = 2; - escaped = shortcut_escape(ctrl->label, - ctrl->listbox.shortcut); - shortcuts[nshortcuts++] = ctrl->listbox.shortcut; - if (ctrl->listbox.draglist) { - data = snew(struct prefslist); - num_ids = 4; - prefslist(data, &pos, ctrl->listbox.height, escaped, - base_id, base_id+1, base_id+2, base_id+3); - shortcuts[nshortcuts++] = 'u'; /* Up */ - shortcuts[nshortcuts++] = 'd'; /* Down */ - } else if (ctrl->listbox.height == 0) { - /* Drop-down list. */ - if (ctrl->listbox.percentwidth == 100) { - staticddlbig(&pos, escaped, - base_id, base_id+1); - } else { - staticddl(&pos, escaped, base_id, - base_id+1, ctrl->listbox.percentwidth); - } - } else { - /* Ordinary list. */ - listbox(&pos, escaped, base_id, base_id+1, - ctrl->listbox.height, ctrl->listbox.multisel); - } - if (ctrl->listbox.ncols) { - /* - * This method of getting the box width is a bit of - * a hack; we'd do better to try to retrieve the - * actual width in dialog units from doctl() just - * before MapDialogRect. But that's going to be no - * fun, and this should be good enough accuracy. - */ - int width = cp->width * ctrl->listbox.percentwidth; - int *tabarray; - int i, percent; - - tabarray = snewn(ctrl->listbox.ncols-1, int); - percent = 0; - for (i = 0; i < ctrl->listbox.ncols-1; i++) { - percent += ctrl->listbox.percentages[i]; - tabarray[i] = width * percent / 10000; - } - SendDlgItemMessage(cp->hwnd, base_id+1, LB_SETTABSTOPS, - ctrl->listbox.ncols-1, (LPARAM)tabarray); - sfree(tabarray); - } - sfree(escaped); - break; - case CTRL_FILESELECT: - escaped = shortcut_escape(ctrl->label, ctrl->fileselect.shortcut); - shortcuts[nshortcuts++] = ctrl->fileselect.shortcut; - num_ids = 3; - if (!ctrl->fileselect.just_button) { - editbutton(&pos, escaped, base_id, base_id+1, - "Browse...", base_id+2); - } else { - button(&pos, escaped, base_id+2, false); - } - sfree(escaped); - break; - case CTRL_FONTSELECT: - num_ids = 3; - escaped = shortcut_escape(ctrl->label, - ctrl->fontselect.shortcut); - shortcuts[nshortcuts++] = ctrl->fontselect.shortcut; - statictext(&pos, escaped, 1, base_id); - staticbtn(&pos, "", base_id+1, "Change...", base_id+2); - data = fontspec_new_default(); - sfree(escaped); - break; - default: - unreachable("bad control type in winctrl_layout"); - } - - /* Translate the original align_id_relative of -1 into n-1 */ - if (align_id_relative < 0) - align_id_relative += num_ids; - - /* - * Create a `struct winctrl' for this control, and advance - * the dialog ID counter, if it's actually been created - * (and isn't tabdelayed). - */ - if (pos.hwnd) { - struct winctrl *c = snew(struct winctrl); - - c->ctrl = ctrl; - c->base_id = actual_base_id; - c->align_id = c->base_id + align_id_relative; - c->num_ids = num_ids; - c->data = data; - memcpy(c->shortcuts, shortcuts, sizeof(shortcuts)); - winctrl_add(wc, c); - winctrl_add_shortcuts(dp, c); - if (actual_base_id == base_id) - base_id += num_ids; - - if (ctrl->align_next_to) { - /* - * Implement align_next_to by looking at the y extents - * of the two controls now that both are created, and - * moving one or the other downwards so that they're - * centred on a common horizontal line. - */ - LONG mid2 = 0; - for (dlgcontrol *thisctrl = ctrl; thisctrl; - thisctrl = thisctrl->align_next_to) { - struct winctrl *thisc = winctrl_findbyctrl(wc, thisctrl); - assert(thisc); - - HWND win = GetDlgItem(pos.hwnd, thisc->align_id); - assert(win); - - RECT rect; - if (!GetWindowRect(win, &rect)) - continue; - if (mid2 < rect.top + rect.bottom) - mid2 = rect.top + rect.bottom; - } - - for (dlgcontrol *thisctrl = ctrl; thisctrl; - thisctrl = thisctrl->align_next_to) { - struct winctrl *thisc = winctrl_findbyctrl(wc, thisctrl); - assert(thisc); - - HWND win = GetDlgItem(pos.hwnd, thisc->align_id); - assert(win); - - RECT rect; - if (!GetWindowRect(win, &rect)) - continue; - - LONG dy = (mid2 - (rect.top + rect.bottom)) / 2; - move_windows(pos.hwnd, thisc->base_id, thisc->num_ids, dy); - } - } - } else { - sfree(data); - } - - if (colstart >= 0) { - /* - * Update the ypos in all columns crossed by this - * control. - */ - int i; - for (i = colstart; i < colstart+colspan; i++) - columns[i].ypos = pos.ypos; - } - } - - /* - * We've now finished laying out the controls; so now update - * the ctlpos and control ID that were passed in, terminate - * any containing box, and return. - */ - for (i = 0; i < ncols; i++) - if (cp->ypos < columns[i].ypos) - cp->ypos = columns[i].ypos; - *id = base_id; - - if (s->boxname && *s->boxname) - endbox(cp); -} - -static void winctrl_set_focus(dlgcontrol *ctrl, struct dlgparam *dp, - bool has_focus) -{ - if (has_focus) { - if (dp->focused) - dp->lastfocused = dp->focused; - dp->focused = ctrl; - } else if (!has_focus && dp->focused == ctrl) { - dp->lastfocused = dp->focused; - dp->focused = NULL; - } -} - -dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp) -{ - return dp->focused == ctrl ? dp->lastfocused : dp->focused; -} - -/* - * The dialog-box procedure calls this function to handle Windows - * messages on a control we manage. - */ -bool winctrl_handle_command(struct dlgparam *dp, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - struct winctrl *c; - dlgcontrol *ctrl; - int i, id; - bool ret; - static UINT draglistmsg = WM_NULL; - - /* - * Filter out pointless window messages. Our interest is in - * WM_COMMAND and the drag list message, and nothing else. - */ - if (draglistmsg == WM_NULL) - draglistmsg = RegisterWindowMessage (DRAGLISTMSGSTRING); - - if (msg != draglistmsg && msg != WM_COMMAND && msg != WM_DRAWITEM) - return false; - - /* - * Look up the control ID in our data. - */ - c = NULL; - for (i = 0; i < dp->nctrltrees; i++) { - c = winctrl_findbyid(dp->controltrees[i], LOWORD(wParam)); - if (c) - break; - } - if (!c) - return false; /* we have nothing to do */ - - if (msg == WM_DRAWITEM) { - /* - * Owner-draw request for a panel title. - */ - LPDRAWITEMSTRUCT di = (LPDRAWITEMSTRUCT) lParam; - HDC hdc = di->hDC; - RECT r = di->rcItem; - SIZE s; - - SetMapMode(hdc, MM_TEXT); /* ensure logical units == pixels */ - - GetTextExtentPoint32(hdc, (char *)c->data, - strlen((char *)c->data), &s); - DrawEdge(hdc, &r, EDGE_ETCHED, BF_ADJUST | BF_RECT); - TextOut(hdc, - r.left + (r.right-r.left-s.cx)/2, - r.top + (r.bottom-r.top-s.cy)/2, - (char *)c->data, strlen((char *)c->data)); - - return true; - } - - ctrl = c->ctrl; - id = LOWORD(wParam) - c->base_id; - - if (!ctrl || !ctrl->handler) - return false; /* nothing we can do here */ - - /* - * From here on we do not issue `return' statements until the - * very end of the dialog box: any event handler is entitled to - * ask for a colour selector, so we _must_ always allow control - * to reach the end of this switch statement so that the - * subsequent code can test dp->coloursel_wanted(). - */ - ret = false; - dp->coloursel_wanted = false; - - /* - * Now switch on the control type and the message. - */ - switch (ctrl->type) { - case CTRL_EDITBOX: - if (msg == WM_COMMAND && !ctrl->editbox.has_list && - (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS)) - winctrl_set_focus(ctrl, dp, HIWORD(wParam) == EN_SETFOCUS); - if (msg == WM_COMMAND && ctrl->editbox.has_list && - (HIWORD(wParam)==CBN_SETFOCUS || HIWORD(wParam)==CBN_KILLFOCUS)) - winctrl_set_focus(ctrl, dp, HIWORD(wParam) == CBN_SETFOCUS); - - if (msg == WM_COMMAND && !ctrl->editbox.has_list && - HIWORD(wParam) == EN_CHANGE) - ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); - if (msg == WM_COMMAND && - ctrl->editbox.has_list) { - if (HIWORD(wParam) == CBN_SELCHANGE) { - int index, len; - char *text; - - index = SendDlgItemMessage(dp->hwnd, c->base_id+1, - CB_GETCURSEL, 0, 0); - len = SendDlgItemMessage(dp->hwnd, c->base_id+1, - CB_GETLBTEXTLEN, index, 0); - text = snewn(len+1, char); - SendDlgItemMessage(dp->hwnd, c->base_id+1, CB_GETLBTEXT, - index, (LPARAM)text); - SetDlgItemText(dp->hwnd, c->base_id+1, text); - sfree(text); - ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); - } else if (HIWORD(wParam) == CBN_EDITCHANGE) { - ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); - } else if (HIWORD(wParam) == CBN_KILLFOCUS) { - ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH); - } - - } - break; - case CTRL_RADIO: - if (msg == WM_COMMAND && - (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS)) - winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS); - /* - * We sometimes get spurious BN_CLICKED messages for the - * radio button that is just about to _lose_ selection, if - * we're switching using the arrow keys. Therefore we - * double-check that the button in wParam is actually - * checked before generating an event. - */ - if (msg == WM_COMMAND && - (HIWORD(wParam) == BN_CLICKED || - HIWORD(wParam) == BN_DOUBLECLICKED) && - IsDlgButtonChecked(dp->hwnd, LOWORD(wParam))) { - ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); - } - break; - case CTRL_CHECKBOX: - if (msg == WM_COMMAND && - (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS)) - winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS); - if (msg == WM_COMMAND && - (HIWORD(wParam) == BN_CLICKED || - HIWORD(wParam) == BN_DOUBLECLICKED)) { - ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); - } - break; - case CTRL_BUTTON: - if (msg == WM_COMMAND && - (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS)) - winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS); - if (msg == WM_COMMAND && - (HIWORD(wParam) == BN_CLICKED || - HIWORD(wParam) == BN_DOUBLECLICKED)) { - ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); - } - break; - case CTRL_LISTBOX: - if (msg == WM_COMMAND && ctrl->listbox.height != 0 && - (HIWORD(wParam)==LBN_SETFOCUS || HIWORD(wParam)==LBN_KILLFOCUS)) - winctrl_set_focus(ctrl, dp, HIWORD(wParam) == LBN_SETFOCUS); - if (msg == WM_COMMAND && ctrl->listbox.height == 0 && - (HIWORD(wParam)==CBN_SETFOCUS || HIWORD(wParam)==CBN_KILLFOCUS)) - winctrl_set_focus(ctrl, dp, HIWORD(wParam) == CBN_SETFOCUS); - if (msg == WM_COMMAND && id >= 2 && - (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS)) - winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS); - if (ctrl->listbox.draglist) { - int pret; - pret = handle_prefslist(c->data, NULL, 0, (msg != WM_COMMAND), - dp->hwnd, wParam, lParam); - if (pret & 2) - ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); - ret = pret & 1; - } else { - if (msg == WM_COMMAND && HIWORD(wParam) == LBN_DBLCLK) { - SetCapture(dp->hwnd); - ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); - } else if (msg == WM_COMMAND && HIWORD(wParam) == LBN_SELCHANGE) { - ctrl->handler(ctrl, dp, dp->data, EVENT_SELCHANGE); - } - } - break; - case CTRL_FILESELECT: - if (msg == WM_COMMAND && id == 1 && - (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS)) - winctrl_set_focus(ctrl, dp, HIWORD(wParam) == EN_SETFOCUS); - if (msg == WM_COMMAND && id == 2 && - (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS)) - winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS); - if (msg == WM_COMMAND && id == 1 && HIWORD(wParam) == EN_CHANGE) - ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); - if (id == 2 && - (msg == WM_COMMAND && - (HIWORD(wParam) == BN_CLICKED || - HIWORD(wParam) == BN_DOUBLECLICKED))) { - OPENFILENAMEW of; - wchar_t filename[FILENAME_MAX]; - - wchar_t *title_to_free = NULL; - - memset(&of, 0, sizeof(of)); - of.hwndOwner = dp->hwnd; - if (ctrl->fileselect.filter) - of.lpstrFilter = ctrl->fileselect.filter; - else - of.lpstrFilter = L"All Files (*.*)\0*\0\0\0"; - of.lpstrCustomFilter = NULL; - of.nFilterIndex = 1; - of.lpstrFile = filename; - if (!ctrl->fileselect.just_button) { - GetDlgItemTextW(dp->hwnd, c->base_id+1, - filename, lenof(filename)); - filename[lenof(filename)-1] = L'\0'; - } else { - *filename = L'\0'; - } - of.nMaxFile = lenof(filename); - of.lpstrFileTitle = NULL; - of.lpstrTitle = title_to_free = dup_mb_to_wc( - DEFAULT_CODEPAGE, 0, ctrl->fileselect.title); - of.Flags = 0; - if (request_file_w(NULL, &of, false, - ctrl->fileselect.for_writing)) { - if (!ctrl->fileselect.just_button) { - SetDlgItemTextW(dp->hwnd, c->base_id + 1, filename); - ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); - } else { - assert(!c->data); - c->data = filename; - ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); - c->data = NULL; - } - } - - sfree(title_to_free); - } - break; - case CTRL_FONTSELECT: - if (msg == WM_COMMAND && id == 2 && - (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS)) - winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS); - if (id == 2 && - (msg == WM_COMMAND && - (HIWORD(wParam) == BN_CLICKED || - HIWORD(wParam) == BN_DOUBLECLICKED))) { - CHOOSEFONT cf; - LOGFONT lf; - HDC hdc; - FontSpec *fs = (FontSpec *)c->data; - - hdc = GetDC(0); - lf.lfHeight = -MulDiv(fs->height, - GetDeviceCaps(hdc, LOGPIXELSY), 72); - ReleaseDC(0, hdc); - lf.lfWidth = lf.lfEscapement = lf.lfOrientation = 0; - lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = 0; - lf.lfWeight = (fs->isbold ? FW_BOLD : 0); - lf.lfCharSet = fs->charset; - lf.lfOutPrecision = OUT_DEFAULT_PRECIS; - lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; - lf.lfQuality = DEFAULT_QUALITY; - lf.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE; - strncpy(lf.lfFaceName, fs->name, - sizeof(lf.lfFaceName) - 1); - lf.lfFaceName[sizeof(lf.lfFaceName) - 1] = '\0'; - - cf.lStructSize = sizeof(cf); - cf.hwndOwner = dp->hwnd; - cf.lpLogFont = &lf; - cf.Flags = (dp->fixed_pitch_fonts ? CF_FIXEDPITCHONLY : 0) | - CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS; - - if (ChooseFont(&cf)) { - fs = fontspec_new(lf.lfFaceName, (lf.lfWeight == FW_BOLD), - cf.iPointSize / 10, lf.lfCharSet); - dlg_fontsel_set(ctrl, dp, fs); - fontspec_free(fs); - - ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); - } - } - break; - } - - /* - * If the above event handler has asked for a colour selector, - * now is the time to generate one. - */ - if (dp->coloursel_wanted) { - static CHOOSECOLOR cc; - static DWORD custom[16] = { 0 }; /* zero initialisers */ - cc.lStructSize = sizeof(cc); - cc.hwndOwner = dp->hwnd; - cc.hInstance = (HWND) hinst; - cc.lpCustColors = custom; - cc.rgbResult = RGB(dp->coloursel_result.r, - dp->coloursel_result.g, - dp->coloursel_result.b); - cc.Flags = CC_FULLOPEN | CC_RGBINIT; - if (ChooseColor(&cc)) { - dp->coloursel_result.r = - (unsigned char) (cc.rgbResult & 0xFF); - dp->coloursel_result.g = - (unsigned char) (cc.rgbResult >> 8) & 0xFF; - dp->coloursel_result.b = - (unsigned char) (cc.rgbResult >> 16) & 0xFF; - dp->coloursel_result.ok = true; - } else - dp->coloursel_result.ok = false; - ctrl->handler(ctrl, dp, dp->data, EVENT_CALLBACK); - } - - return ret; -} - -/* - * This function can be called to produce context help on a - * control. Returns true if it has actually launched some help. - */ -bool winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id) -{ - int i; - struct winctrl *c; - - /* - * Look up the control ID in our data. - */ - c = NULL; - for (i = 0; i < dp->nctrltrees; i++) { - c = winctrl_findbyid(dp->controltrees[i], id); - if (c) - break; - } - if (!c) - return false; /* we have nothing to do */ - - /* - * This is the Windows front end, so we're allowed to assume - * `helpctx' is a context string. - */ - if (!c->ctrl || !c->ctrl->helpctx) - return false; /* no help available for this ctrl */ - - launch_help(hwnd, c->ctrl->helpctx); - return true; -} - -/* - * Now the various functions that the platform-independent - * mechanism can call to access the dialog box entries. - */ - -static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, dlgcontrol *ctrl) -{ - int i; - - for (i = 0; i < dp->nctrltrees; i++) { - struct winctrl *c = winctrl_findbyctrl(dp->controltrees[i], ctrl); - if (c) - return c; - } - return NULL; -} - -bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp) -{ - /* - * In this implementation of the dialog box, we physically - * uncreate controls that aren't in a visible panel of the config - * box. So we can tell if a control is visible just by checking if - * it _exists_. - */ - return dlg_findbyctrl(dp, ctrl) != NULL; -} - -void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->type == CTRL_RADIO); - CheckRadioButton(dp->hwnd, - c->base_id + 1, - c->base_id + c->ctrl->radio.nbuttons, - c->base_id + 1 + whichbutton); -} - -int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - int i; - assert(c && c->ctrl->type == CTRL_RADIO); - for (i = 0; i < c->ctrl->radio.nbuttons; i++) - if (IsDlgButtonChecked(dp->hwnd, c->base_id + 1 + i)) - return i; - unreachable("no radio button was checked"); -} - -void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->type == CTRL_CHECKBOX); - CheckDlgButton(dp->hwnd, c->base_id, checked); -} - -bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->type == CTRL_CHECKBOX); - return 0 != IsDlgButtonChecked(dp->hwnd, c->base_id); -} - -void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->type == CTRL_EDITBOX); - SetDlgItemText(dp->hwnd, c->base_id+1, text); -} - -char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->type == CTRL_EDITBOX); - return GetDlgItemText_alloc(dp->hwnd, c->base_id+1); -} - -void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, - size_t start, size_t len) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->type == CTRL_EDITBOX); - SendDlgItemMessage(dp->hwnd, c->base_id+1, EM_SETSEL, start, start+len); -} - -/* The `listbox' functions can also apply to combo boxes. */ -void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - int msg; - assert(c && - (c->ctrl->type == CTRL_LISTBOX || - (c->ctrl->type == CTRL_EDITBOX && - c->ctrl->editbox.has_list))); - msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? - LB_RESETCONTENT : CB_RESETCONTENT); - SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0); -} - -void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - int msg; - assert(c && - (c->ctrl->type == CTRL_LISTBOX || - (c->ctrl->type == CTRL_EDITBOX && - c->ctrl->editbox.has_list))); - msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? - LB_DELETESTRING : CB_DELETESTRING); - SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); -} - -void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - int msg; - assert(c && - (c->ctrl->type == CTRL_LISTBOX || - (c->ctrl->type == CTRL_EDITBOX && - c->ctrl->editbox.has_list))); - msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? - LB_ADDSTRING : CB_ADDSTRING); - SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)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(dlgcontrol *ctrl, dlgparam *dp, - char const *text, int id) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - int msg, msg2, index; - assert(c && - (c->ctrl->type == CTRL_LISTBOX || - (c->ctrl->type == CTRL_EDITBOX && - c->ctrl->editbox.has_list))); - msg = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? - LB_ADDSTRING : CB_ADDSTRING); - msg2 = (c->ctrl->type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? - LB_SETITEMDATA : CB_SETITEMDATA); - index = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text); - SendDlgItemMessage(dp->hwnd, c->base_id+1, msg2, index, (LPARAM)id); -} - -int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - int msg; - assert(c && c->ctrl->type == CTRL_LISTBOX); - msg = (c->ctrl->listbox.height != 0 ? LB_GETITEMDATA : CB_GETITEMDATA); - return - SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); -} - -/* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - int msg, ret; - assert(c && c->ctrl->type == CTRL_LISTBOX); - if (c->ctrl->listbox.multisel) { - assert(c->ctrl->listbox.height != 0); /* not combo box */ - ret = SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSELCOUNT, 0, 0); - if (ret == LB_ERR || ret > 1) - return -1; - } - msg = (c->ctrl->listbox.height != 0 ? LB_GETCURSEL : CB_GETCURSEL); - ret = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0); - if (ret == LB_ERR) - return -1; - else - return ret; -} - -bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->type == CTRL_LISTBOX && - c->ctrl->listbox.multisel && - c->ctrl->listbox.height != 0); - return - SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSEL, index, 0); -} - -void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - int msg; - assert(c && c->ctrl->type == CTRL_LISTBOX && - !c->ctrl->listbox.multisel); - msg = (c->ctrl->listbox.height != 0 ? LB_SETCURSEL : CB_SETCURSEL); - SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0); -} - -void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->type == CTRL_TEXT); - SetDlgItemText(dp->hwnd, c->base_id, text); -} - -void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - char *escaped = NULL; - int id = -1; - - assert(c); - switch (c->ctrl->type) { - case CTRL_EDITBOX: - escaped = shortcut_escape(text, c->ctrl->editbox.shortcut); - id = c->base_id; - break; - case CTRL_RADIO: - escaped = shortcut_escape(text, c->ctrl->radio.shortcut); - id = c->base_id; - break; - case CTRL_CHECKBOX: - escaped = shortcut_escape(text, ctrl->checkbox.shortcut); - id = c->base_id; - break; - case CTRL_BUTTON: - escaped = shortcut_escape(text, ctrl->button.shortcut); - id = c->base_id; - break; - case CTRL_LISTBOX: - escaped = shortcut_escape(text, ctrl->listbox.shortcut); - id = c->base_id; - break; - case CTRL_FILESELECT: - escaped = shortcut_escape(text, ctrl->fileselect.shortcut); - if (ctrl->fileselect.just_button) - id = c->base_id + 2; /* the button */ - else - id = c->base_id; /* the label */ - break; - case CTRL_FONTSELECT: - escaped = shortcut_escape(text, ctrl->fontselect.shortcut); - id = c->base_id; - break; - default: - unreachable("bad control type in label_change"); - } - if (escaped) { - SetDlgItemText(dp->hwnd, id, escaped); - sfree(escaped); - } -} - -void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c); - assert(c->ctrl->type == CTRL_FILESELECT); - assert(!c->ctrl->fileselect.just_button); - SetDlgItemTextW(dp->hwnd, c->base_id+1, fn->wpath); -} - -Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c); - assert(c->ctrl->type == CTRL_FILESELECT); - if (!c->ctrl->fileselect.just_button) { - wchar_t *tmp = GetDlgItemTextW_alloc(dp->hwnd, c->base_id+1); - Filename *ret = filename_from_wstr(tmp); - sfree(tmp); - return ret; - } else { - return filename_from_str(c->data); - } -} - -void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) -{ - char *buf, *boldstr; - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->type == CTRL_FONTSELECT); - - fontspec_free((FontSpec *)c->data); - c->data = fontspec_copy(fs); - - boldstr = (fs->isbold ? "bold, " : ""); - if (fs->height == 0) - buf = dupprintf("Font: %s, %sdefault height", fs->name, boldstr); - else - buf = dupprintf("Font: %s, %s%d-%s", fs->name, boldstr, - (fs->height < 0 ? -fs->height : fs->height), - (fs->height < 0 ? "pixel" : "point")); - SetDlgItemText(dp->hwnd, c->base_id+1, buf); - sfree(buf); - - dlg_auto_set_fixed_pitch_flag(dp); -} - -FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - assert(c && c->ctrl->type == CTRL_FONTSELECT); - return fontspec_copy((FontSpec *)c->data); -} - -/* - * 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(dlgcontrol *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - if (c && c->ctrl->type == CTRL_LISTBOX) { - SendDlgItemMessage(dp->hwnd, c->base_id+1, WM_SETREDRAW, false, 0); - } -} - -void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - if (c && c->ctrl->type == CTRL_LISTBOX) { - HWND hw = GetDlgItem(dp->hwnd, c->base_id+1); - SendMessage(hw, WM_SETREDRAW, true, 0); - InvalidateRect(hw, NULL, true); - } -} - -void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) -{ - struct winctrl *c = dlg_findbyctrl(dp, ctrl); - int id; - HWND ctl; - if (!c) - return; - switch (ctrl->type) { - case CTRL_EDITBOX: id = c->base_id + 1; break; - case CTRL_RADIO: - for (id = c->base_id + ctrl->radio.nbuttons; id > 1; id--) - if (IsDlgButtonChecked(dp->hwnd, id)) - break; - /* - * In the theoretically-unlikely case that no button was - * selected, id should come out of this as 1, which is a - * reasonable enough choice. - */ - break; - case CTRL_CHECKBOX: id = c->base_id; break; - case CTRL_BUTTON: id = c->base_id; break; - case CTRL_LISTBOX: id = c->base_id + 1; break; - case CTRL_FILESELECT: id = c->base_id + 1; break; - case CTRL_FONTSELECT: id = c->base_id + 2; break; - default: id = c->base_id; break; - } - ctl = GetDlgItem(dp->hwnd, id); - SetFocus(ctl); -} - -/* - * 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(dlgparam *dp) -{ - MessageBeep(0); -} - -void dlg_error_msg(dlgparam *dp, const char *msg) -{ - MessageBox(dp->hwnd, msg, - dp->errtitle ? dp->errtitle : NULL, - MB_OK | MB_ICONERROR); -} - -/* - * 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(dlgparam *dp, int value) -{ - dp->ended = true; - dp->endresult = value; -} - -void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) -{ - int i, j; - struct winctrl *c; - - if (!ctrl) { - /* - * Send EVENT_REFRESH to absolutely everything. - */ - for (j = 0; j < dp->nctrltrees; j++) { - for (i = 0; - (c = winctrl_findbyindex(dp->controltrees[j], i)) != NULL; - i++) { - if (c->ctrl && c->ctrl->handler != NULL) - c->ctrl->handler(c->ctrl, dp, - dp->data, EVENT_REFRESH); - } - } - } else { - /* - * Send EVENT_REFRESH to a specific control. - */ - if (ctrl->handler != NULL) - ctrl->handler(ctrl, dp, dp->data, EVENT_REFRESH); - } -} - -void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b) -{ - dp->coloursel_wanted = true; - dp->coloursel_result.r = r; - dp->coloursel_result.g = g; - dp->coloursel_result.b = b; -} - -bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, - int *r, int *g, int *b) -{ - if (dp->coloursel_result.ok) { - *r = dp->coloursel_result.r; - *g = dp->coloursel_result.g; - *b = dp->coloursel_result.b; - return true; - } else - return false; -} - -void dlg_auto_set_fixed_pitch_flag(dlgparam *dp) -{ - Conf *conf = (Conf *)dp->data; - FontSpec *fs; - int quality; - HFONT hfont; - HDC hdc; - TEXTMETRIC tm; - bool is_var; - - /* - * Attempt to load the current font, and see if it's - * variable-pitch. If so, start off the fixed-pitch flag for the - * dialog box as false. - * - * We assume here that any client of the dlg_* mechanism which is - * using font selectors at all is also using a normal 'Conf *' - * as dp->data. - */ - - quality = conf_get_int(conf, CONF_font_quality); - fs = conf_get_fontspec(conf, CONF_font); - - hfont = CreateFont(0, 0, 0, 0, FW_DONTCARE, false, false, false, - DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, - CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), - FIXED_PITCH | FF_DONTCARE, fs->name); - hdc = GetDC(NULL); - if (hdc && SelectObject(hdc, hfont) && GetTextMetrics(hdc, &tm)) { - /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */ - is_var = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH); - } else { - is_var = false; /* assume it's basically normal */ - } - if (hdc) - ReleaseDC(NULL, hdc); - if (hfont) - DeleteObject(hfont); - - if (is_var) - dp->fixed_pitch_fonts = false; -} - -bool dlg_get_fixed_pitch_flag(dlgparam *dp) -{ - return dp->fixed_pitch_fonts; -} - -void dlg_set_fixed_pitch_flag(dlgparam *dp, bool flag) -{ - dp->fixed_pitch_fonts = flag; -} - -void dp_init(struct dlgparam *dp) -{ - dp->nctrltrees = 0; - dp->data = NULL; - dp->ended = false; - dp->focused = dp->lastfocused = NULL; - memset(dp->shortcuts, 0, sizeof(dp->shortcuts)); - dp->hwnd = NULL; - dp->wintitle = dp->errtitle = NULL; - dp->fixed_pitch_fonts = true; -} - -void dp_add_tree(struct dlgparam *dp, struct winctrls *wc) -{ - assert(dp->nctrltrees < lenof(dp->controltrees)); - dp->controltrees[dp->nctrltrees++] = wc; -} - -void dp_cleanup(struct dlgparam *dp) -{ - sfree(dp->wintitle); - sfree(dp->errtitle); -} diff --git a/WINDOWS/cryptoapi.h b/WINDOWS/cryptoapi.h deleted file mode 100644 index 4ea7fe497..000000000 --- a/WINDOWS/cryptoapi.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * cryptoapi.h: Windows Crypto API functions defined in PuTTY that - * use the crypt32 library. Also centralises the machinery for - * dynamically loading that library, and our own functions using that - * in turn. - */ - -DECL_WINDOWS_FUNCTION(extern, BOOL, CryptProtectMemory, (LPVOID,DWORD,DWORD)); - -bool got_crypt(void); - -/* - * Function to obfuscate an input string into something usable as a - * pathname for a Windows named pipe. Uses CryptProtectMemory to make - * the obfuscation depend on a key Windows stores for the owning user, - * and then hashes the string as well to make it have a manageable - * length and be composed of filename-legal characters. - * - * Rationale: Windows's named pipes all live in the same namespace, so - * one user can see what pipes another user has open. This is an - * undesirable privacy leak: in particular, if we used unobfuscated - * names for the connection-sharing pipe names, it would permit one - * user to know what username@host another user is SSHing to. - * - * The returned string is dynamically allocated. - */ -char *capi_obfuscate_string(const char *realname); diff --git a/WINDOWS/dialog.c b/WINDOWS/dialog.c deleted file mode 100644 index e7faf6df7..000000000 --- a/WINDOWS/dialog.c +++ /dev/null @@ -1,1329 +0,0 @@ -/* - * dialog.c - dialogs for PuTTY(tel), including the configuration dialog. - */ - -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "putty-rc.h" -#include "win-gui-seat.h" -#include "storage.h" -#include "dialog.h" -#include "licence.h" - -#include -#include -#include - -#ifdef MSVC4 -#define TVINSERTSTRUCT TV_INSERTSTRUCT -#define TVITEM TV_ITEM -#define ICON_BIG 1 -#endif - -typedef struct PortableDialogStuff { - /* - * These are the various bits of data required to handle a dialog - * box that's been built up from the cross-platform dialog.c - * system. - */ - - /* The 'controlbox' that was returned from the portable setup function */ - struct controlbox *ctrlbox; - - /* The dlgparam that's passed to all the runtime dlg_* functions. - * Declared as an array of 1 so it's convenient to pass it as a pointer. */ - struct dlgparam dp[1]; - - /* - * Collections of instantiated controls. There can be more than - * one of these, because sometimes you want to destroy and - * recreate a subset of them - e.g. when switching panes in the - * main PuTTY config box, you delete and recreate _most_ of the - * controls, but not the OK and Cancel buttons at the bottom. - */ - size_t nctrltrees; - struct winctrls *ctrltrees; - - /* - * Flag indicating whether the dialog box has been initialised. - * Used to suppresss spurious firing of message handlers during - * setup. - */ - bool initialised; -} PortableDialogStuff; - -/* - * Initialise a PortableDialogStuff, before launching the dialog box. - */ -static PortableDialogStuff *pds_new(size_t nctrltrees) -{ - PortableDialogStuff *pds = snew(PortableDialogStuff); - memset(pds, 0, sizeof(*pds)); - - pds->ctrlbox = ctrl_new_box(); - - dp_init(pds->dp); - - pds->nctrltrees = nctrltrees; - pds->ctrltrees = snewn(pds->nctrltrees, struct winctrls); - for (size_t i = 0; i < pds->nctrltrees; i++) { - winctrl_init(&pds->ctrltrees[i]); - dp_add_tree(pds->dp, &pds->ctrltrees[i]); - } - - pds->dp->errtitle = dupprintf("%s Error", appname); - - pds->initialised = false; - - return pds; -} - -static void pds_free(PortableDialogStuff *pds) -{ - ctrl_free_box(pds->ctrlbox); - - dp_cleanup(pds->dp); - - for (size_t i = 0; i < pds->nctrltrees; i++) - winctrl_cleanup(&pds->ctrltrees[i]); - sfree(pds->ctrltrees); - - sfree(pds); -} - -static INT_PTR pds_default_dlgproc(PortableDialogStuff *pds, HWND hwnd, - UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_LBUTTONUP: - /* - * Button release should trigger WM_OK if there was a - * previous double click on the host CA list. - */ - ReleaseCapture(); - if (pds->dp->ended) - ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0); - break; - case WM_COMMAND: - case WM_DRAWITEM: - default: { /* also handle drag list msg here */ - /* - * Only process WM_COMMAND once the dialog is fully formed. - */ - int ret; - if (pds->initialised) { - ret = winctrl_handle_command(pds->dp, msg, wParam, lParam); - if (pds->dp->ended && GetCapture() != hwnd) - ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0); - } else - ret = 0; - return ret; - } - case WM_HELP: - if (!winctrl_context_help(pds->dp, - hwnd, ((LPHELPINFO)lParam)->iCtrlId)) - MessageBeep(0); - break; - case WM_CLOSE: - quit_help(hwnd); - ShinyEndDialog(hwnd, 0); - return 0; - - /* Grrr Explorer will maximize Dialogs! */ - case WM_SIZE: - if (wParam == SIZE_MAXIMIZED) - force_normal(hwnd); - return 0; - - } - return 0; -} - -static void pds_initdialog_start(PortableDialogStuff *pds, HWND hwnd) -{ - pds->dp->hwnd = hwnd; - - if (pds->dp->wintitle) /* apply override title, if provided */ - SetWindowText(hwnd, pds->dp->wintitle); - - /* The portable dialog system generally includes the ability to - * handle context help for particular controls. Enable the - * relevant window styles if we have a help file available. */ - if (has_help()) { - LONG_PTR style = GetWindowLongPtr(hwnd, GWL_EXSTYLE); - SetWindowLongPtr(hwnd, GWL_EXSTYLE, style | WS_EX_CONTEXTHELP); - } else { - /* If not, and if the dialog template provided a top-level - * Help button, delete it */ - HWND item = GetDlgItem(hwnd, IDC_HELPBTN); - if (item) - DestroyWindow(item); - } -} - -/* - * Create the panelfuls of controls in the configuration box. - */ -static void pds_create_controls( - PortableDialogStuff *pds, size_t which_tree, int base_id, - int left, int right, int top, char *path) -{ - struct ctlpos cp; - - ctlposinit(&cp, pds->dp->hwnd, left, right, top); - - for (int index = -1; (index = ctrl_find_path( - pds->ctrlbox, path, index)) >= 0 ;) { - struct controlset *s = pds->ctrlbox->ctrlsets[index]; - winctrl_layout(pds->dp, &pds->ctrltrees[which_tree], &cp, s, &base_id); - } -} - -static void pds_initdialog_finish(PortableDialogStuff *pds) -{ - /* - * Set focus into the first available control in ctrltree #0, - * which the caller was expected to set up to be the one - * containing the dialog controls likely to be used first. - */ - struct winctrl *c; - for (int i = 0; (c = winctrl_findbyindex(&pds->ctrltrees[0], i)) != NULL; - i++) { - if (c->ctrl) { - dlg_set_focus(c->ctrl, pds->dp); - break; - } - } - - /* - * Now we've finished creating our initial set of controls, - * it's safe to actually show the window without risking setup - * flicker. - */ - ShowWindow(pds->dp->hwnd, SW_SHOWNORMAL); - - pds->initialised = true; -} - -#define LOGEVENT_INITIAL_MAX 128 -#define LOGEVENT_CIRCULAR_MAX 128 - -static char *events_initial[LOGEVENT_INITIAL_MAX]; -static char *events_circular[LOGEVENT_CIRCULAR_MAX]; -static int ninitial = 0, ncircular = 0, circular_first = 0; - -#define PRINTER_DISABLED_STRING "None (printing disabled)" - -void force_normal(HWND hwnd) -{ - static bool recurse = false; - - WINDOWPLACEMENT wp; - - if (recurse) - return; - recurse = true; - - wp.length = sizeof(wp); - if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) { - wp.showCmd = SW_SHOWNORMAL; - SetWindowPlacement(hwnd, &wp); - } - recurse = false; -} - -static char *getevent(int i) -{ - if (i < ninitial) - return events_initial[i]; - if ((i -= ninitial) < ncircular) - return events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX]; - return NULL; -} - -static HWND logbox; -HWND event_log_window(void) { return logbox; } - -static INT_PTR CALLBACK LogProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - int i; - - switch (msg) { - case WM_INITDIALOG: { - char *str = dupprintf("%s Event Log", appname); - SetWindowText(hwnd, str); - sfree(str); - - static int tabs[4] = { 78, 108 }; - SendDlgItemMessage(hwnd, IDN_LIST, LB_SETTABSTOPS, 2, - (LPARAM) tabs); - - for (i = 0; i < ninitial; i++) - SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING, - 0, (LPARAM) events_initial[i]); - for (i = 0; i < ncircular; i++) - SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING, - 0, (LPARAM) events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX]); - return 1; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - case IDCANCEL: - logbox = NULL; - SetActiveWindow(GetParent(hwnd)); - DestroyWindow(hwnd); - return 0; - case IDN_COPY: - if (HIWORD(wParam) == BN_CLICKED || - HIWORD(wParam) == BN_DOUBLECLICKED) { - int selcount; - int *selitems; - selcount = SendDlgItemMessage(hwnd, IDN_LIST, - LB_GETSELCOUNT, 0, 0); - if (selcount == 0) { /* don't even try to copy zero items */ - MessageBeep(0); - break; - } - - selitems = snewn(selcount, int); - if (selitems) { - int count = SendDlgItemMessage(hwnd, IDN_LIST, - LB_GETSELITEMS, - selcount, - (LPARAM) selitems); - static const unsigned char sel_nl[] = SEL_NL; - - if (count == 0) { /* can't copy zero stuff */ - MessageBeep(0); - break; - } - - strbuf *sb = strbuf_new(); - for (int i = 0; i < count; i++) { - char *q = getevent(selitems[i]); - put_datapl(sb, ptrlen_from_asciz(q)); - put_data(sb, sel_nl, sizeof(sel_nl)); - } - write_aclip(hwnd, CLIP_SYSTEM, sb->s, sb->len); - strbuf_free(sb); - sfree(selitems); - - for (i = 0; i < (ninitial + ncircular); i++) - SendDlgItemMessage(hwnd, IDN_LIST, LB_SETSEL, - false, i); - } - } - return 0; - } - return 0; - case WM_CLOSE: - logbox = NULL; - SetActiveWindow(GetParent(hwnd)); - DestroyWindow(hwnd); - return 0; - } - return 0; -} - -static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: { - char *str = dupprintf("%s Licence", appname); - SetWindowText(hwnd, str); - sfree(str); - SetDlgItemText(hwnd, IDA_TEXT, LICENCE_TEXT("\r\n\r\n")); - return 1; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - case IDCANCEL: - EndDialog(hwnd, 1); - return 0; - } - return 0; - case WM_CLOSE: - EndDialog(hwnd, 1); - return 0; - } - return 0; -} - -static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - char *str; - - switch (msg) { - case WM_INITDIALOG: { - str = dupprintf("About %s", appname); - SetWindowText(hwnd, str); - sfree(str); - char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf( - "%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - appname, ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); - sfree(buildinfo_text); - SetDlgItemText(hwnd, IDA_TEXT, text); - MakeDlgItemBorderless(hwnd, IDA_TEXT); - sfree(text); - return 1; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - case IDCANCEL: - EndDialog(hwnd, true); - return 0; - case IDA_LICENCE: - EnableWindow(hwnd, 0); - DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX), - hwnd, LicenceProc); - EnableWindow(hwnd, 1); - SetActiveWindow(hwnd); - return 0; - - case IDA_WEB: - /* Load web browser */ - ShellExecute(hwnd, "open", - "https://www.chiark.greenend.org.uk/~sgtatham/putty/", - 0, 0, SW_SHOWDEFAULT); - return 0; - } - return 0; - case WM_CLOSE: - EndDialog(hwnd, true); - return 0; - } - return 0; -} - -/* - * Null dialog procedure. - */ -static INT_PTR CALLBACK NullDlgProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - return 0; -} - -enum { - IDCX_ABOUT = IDC_ABOUT, - IDCX_TVSTATIC, - IDCX_TREEVIEW, - IDCX_STDBASE, - IDCX_PANELBASE = IDCX_STDBASE + 32 -}; - -struct treeview_faff { - HWND treeview; - HTREEITEM lastat[4]; -}; - -static HTREEITEM treeview_insert(struct treeview_faff *faff, - int level, char *text, char *path) -{ - TVINSERTSTRUCT ins; - int i; - HTREEITEM newitem; - ins.hParent = (level > 0 ? faff->lastat[level - 1] : TVI_ROOT); - ins.hInsertAfter = faff->lastat[level]; -#if _WIN32_IE >= 0x0400 && defined NONAMELESSUNION -#define INSITEM DUMMYUNIONNAME.item -#else -#define INSITEM item -#endif - ins.INSITEM.mask = TVIF_TEXT | TVIF_PARAM; - ins.INSITEM.pszText = text; - ins.INSITEM.cchTextMax = strlen(text)+1; - ins.INSITEM.lParam = (LPARAM)path; - newitem = TreeView_InsertItem(faff->treeview, &ins); - if (level > 0) - TreeView_Expand(faff->treeview, faff->lastat[level - 1], - (level > 1 ? TVE_COLLAPSE : TVE_EXPAND)); - faff->lastat[level] = newitem; - for (i = level + 1; i < 4; i++) - faff->lastat[i] = NULL; - return newitem; -} - -const char *dialog_box_demo_screenshot_filename = NULL; - -/* ctrltrees indices for the main dialog box */ -enum { - TREE_PANEL, /* things we swap out every time treeview selects a new pane */ - TREE_BASE, /* fixed things at the bottom like OK and Cancel buttons */ -}; - -/* - * This function is the configuration box. - * (Being a dialog procedure, in general it returns 0 if the default - * dialog processing should be performed, and 1 if it should not.) - */ -static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam, - LPARAM lParam, void *ctx) -{ - PortableDialogStuff *pds = (PortableDialogStuff *)ctx; - const int DEMO_SCREENSHOT_TIMER_ID = 1230; - HWND treeview; - struct treeview_faff tvfaff; - - switch (msg) { - case WM_INITDIALOG: - pds_initdialog_start(pds, hwnd); - - pds_create_controls(pds, TREE_BASE, IDCX_STDBASE, 3, 3, 235, ""); - - SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG, - (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON))); - - centre_window(hwnd); - - /* - * Create the tree view. - */ - { - RECT r; - WPARAM font; - HWND tvstatic; - - r.left = 3; - r.right = r.left + 95; - r.top = 3; - r.bottom = r.top + 10; - MapDialogRect(hwnd, &r); - tvstatic = CreateWindowEx(0, "STATIC", "Cate&gory:", - WS_CHILD | WS_VISIBLE, - r.left, r.top, - r.right - r.left, r.bottom - r.top, - hwnd, (HMENU) IDCX_TVSTATIC, hinst, - NULL); - font = SendMessage(hwnd, WM_GETFONT, 0, 0); - SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(true, 0)); - - r.left = 3; - r.right = r.left + 95; - r.top = 13; - r.bottom = r.top + 219; - MapDialogRect(hwnd, &r); - treeview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, "", - WS_CHILD | WS_VISIBLE | - WS_TABSTOP | TVS_HASLINES | - TVS_DISABLEDRAGDROP | TVS_HASBUTTONS - | TVS_LINESATROOT | - TVS_SHOWSELALWAYS, r.left, r.top, - r.right - r.left, r.bottom - r.top, - hwnd, (HMENU) IDCX_TREEVIEW, hinst, - NULL); - font = SendMessage(hwnd, WM_GETFONT, 0, 0); - SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(true, 0)); - tvfaff.treeview = treeview; - memset(tvfaff.lastat, 0, sizeof(tvfaff.lastat)); - } - - /* - * Set up the tree view contents. - */ - { - HTREEITEM hfirst = NULL; - int i; - char *path = NULL; - char *firstpath = NULL; - - for (i = 0; i < pds->ctrlbox->nctrlsets; i++) { - struct controlset *s = pds->ctrlbox->ctrlsets[i]; - HTREEITEM item; - int j; - char *c; - - if (!s->pathname[0]) - continue; - j = path ? ctrl_path_compare(s->pathname, path) : 0; - if (j == INT_MAX) - continue; /* same path, nothing to add to tree */ - - /* - * 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++; - - item = treeview_insert(&tvfaff, j, c, s->pathname); - if (!hfirst) { - hfirst = item; - firstpath = s->pathname; - } - - path = s->pathname; - } - - /* - * Put the treeview selection on to the first panel in the - * ctrlbox. - */ - TreeView_SelectItem(treeview, hfirst); - - /* - * And create the actual control set for that panel, to - * match the initial treeview selection. - */ - assert(firstpath); /* config.c must have given us _something_ */ - pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE, - 100, 3, 13, firstpath); - dlg_refresh(NULL, pds->dp); /* and set up control values */ - } - - if (dialog_box_demo_screenshot_filename) - SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL); - - pds_initdialog_finish(pds); - return 0; - - case WM_TIMER: - if (dialog_box_demo_screenshot_filename && - (UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) { - KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID); - char *err = save_screenshot( - hwnd, dialog_box_demo_screenshot_filename); - if (err) { - MessageBox(hwnd, err, "Demo screenshot failure", - MB_OK | MB_ICONERROR); - sfree(err); - } - ShinyEndDialog(hwnd, 0); - } - return 0; - - case WM_NOTIFY: - if (LOWORD(wParam) == IDCX_TREEVIEW && - ((LPNMHDR) lParam)->code == TVN_SELCHANGED) { - /* - * Selection-change events on the treeview cause us to do - * a flurry of control deletion and creation - but only - * after WM_INITDIALOG has finished. The initial - * selection-change event(s) during treeview setup are - * ignored. - */ - HTREEITEM i; - TVITEM item; - char buffer[64]; - - if (!pds->initialised) - return 0; - - i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom); - - SendMessage (hwnd, WM_SETREDRAW, false, 0); - - item.hItem = i; - item.pszText = buffer; - item.cchTextMax = sizeof(buffer); - item.mask = TVIF_TEXT | TVIF_PARAM; - TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item); - { - /* Destroy all controls in the currently visible panel. */ - int k; - HWND item; - struct winctrl *c; - - while ((c = winctrl_findbyindex( - &pds->ctrltrees[TREE_PANEL], 0)) != NULL) { - for (k = 0; k < c->num_ids; k++) { - item = GetDlgItem(hwnd, c->base_id + k); - if (item) - DestroyWindow(item); - } - winctrl_rem_shortcuts(pds->dp, c); - winctrl_remove(&pds->ctrltrees[TREE_PANEL], c); - sfree(c->data); - sfree(c); - } - } - pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE, - 100, 3, 13, (char *)item.lParam); - - dlg_refresh(NULL, pds->dp); /* set up control values */ - - SendMessage (hwnd, WM_SETREDRAW, true, 0); - InvalidateRect (hwnd, NULL, true); - - SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */ - } - return 0; - - default: - return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam); - } -} - -void modal_about_box(HWND hwnd) -{ - EnableWindow(hwnd, 0); - DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc); - EnableWindow(hwnd, 1); - SetActiveWindow(hwnd); -} - -void show_help(HWND hwnd) -{ - launch_help(hwnd, NULL); -} - -void defuse_showwindow(void) -{ - /* - * Work around the fact that the app's first call to ShowWindow - * will ignore the default in favour of the shell-provided - * setting. - */ - { - HWND hwnd; - hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), - NULL, NullDlgProc); - ShowWindow(hwnd, SW_HIDE); - SetActiveWindow(hwnd); - DestroyWindow(hwnd); - } -} - -bool do_config(Conf *conf) -{ - bool ret; - PortableDialogStuff *pds = pds_new(2); - - setup_config_box(pds->ctrlbox, false, 0, 0); - win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(), false, 0); - - pds->dp->wintitle = dupprintf("%s Configuration", appname); - pds->dp->data = conf; - - dlg_auto_set_fixed_pitch_flag(pds->dp); - - pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */ - - ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox", - NULL, GenericMainDlgProc, pds); - - pds_free(pds); - - return ret; -} - -bool do_reconfig(HWND hwnd, Conf *conf, int protcfginfo) -{ - Conf *backup_conf; - bool ret; - int protocol; - PortableDialogStuff *pds = pds_new(2); - - backup_conf = conf_copy(conf); - - protocol = conf_get_int(conf, CONF_protocol); - setup_config_box(pds->ctrlbox, true, protocol, protcfginfo); - win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(), - true, protocol); - - pds->dp->wintitle = dupprintf("%s Reconfiguration", appname); - pds->dp->data = conf; - - dlg_auto_set_fixed_pitch_flag(pds->dp); - - pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */ - - ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox", - NULL, GenericMainDlgProc, pds); - - pds_free(pds); - - if (!ret) - conf_copy_into(conf, backup_conf); - - conf_free(backup_conf); - - return ret; -} - -static void win_gui_eventlog(LogPolicy *lp, const char *string) -{ - char timebuf[40]; - char **location; - struct tm tm; - - tm=ltime(); - strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm); - - if (ninitial < LOGEVENT_INITIAL_MAX) - location = &events_initial[ninitial]; - else - location = &events_circular[(circular_first + ncircular) % LOGEVENT_CIRCULAR_MAX]; - - if (*location) - sfree(*location); - *location = dupcat(timebuf, string); - if (logbox) { - int count; - SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING, - 0, (LPARAM) *location); - count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0); - SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0); - } - if (ninitial < LOGEVENT_INITIAL_MAX) { - ninitial++; - } else if (ncircular < LOGEVENT_CIRCULAR_MAX) { - ncircular++; - } else if (ncircular == LOGEVENT_CIRCULAR_MAX) { - circular_first = (circular_first + 1) % LOGEVENT_CIRCULAR_MAX; - sfree(events_circular[circular_first]); - events_circular[circular_first] = dupstr(".."); - } -} - -static void win_gui_logging_error(LogPolicy *lp, const char *event) -{ - WinGuiSeat *wgs = container_of(lp, WinGuiSeat, logpolicy); - - /* Send 'can't open log file' errors to the terminal window. - * (Marked as stderr, although terminal.c won't care.) */ - seat_stderr_pl(&wgs->seat, ptrlen_from_asciz(event)); - seat_stderr_pl(&wgs->seat, PTRLEN_LITERAL("\r\n")); -} - -void showeventlog(HWND hwnd) -{ - if (!logbox) { - logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX), - hwnd, LogProc); - ShowWindow(logbox, SW_SHOWNORMAL); - } - SetActiveWindow(logbox); -} - -void showabout(HWND hwnd) -{ - DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc); -} - -struct hostkey_dialog_ctx { - SeatDialogText *text; - bool has_title; - const char *helpctx; -}; - -static INT_PTR HostKeyMoreInfoProc(HWND hwnd, UINT msg, WPARAM wParam, - LPARAM lParam, void *vctx) -{ - struct hostkey_dialog_ctx *ctx = (struct hostkey_dialog_ctx *)vctx; - - switch (msg) { - case WM_INITDIALOG: { - int index = 100, y = 12; - - WPARAM font = SendMessage(hwnd, WM_GETFONT, 0, 0); - - const char *key = NULL; - for (SeatDialogTextItem *item = ctx->text->items, - *end = item + ctx->text->nitems; item < end; item++) { - switch (item->type) { - case SDT_MORE_INFO_KEY: - key = item->text; - break; - case SDT_MORE_INFO_VALUE_SHORT: - case SDT_MORE_INFO_VALUE_BLOB: { - RECT rk, rv; - DWORD editstyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | - ES_AUTOHSCROLL | ES_READONLY; - if (item->type == SDT_MORE_INFO_VALUE_BLOB) { - rk.left = 12; - rk.right = 376; - rk.top = y; - rk.bottom = 8; - y += 10; - - editstyle |= ES_MULTILINE; - rv.left = 12; - rv.right = 376; - rv.top = y; - rv.bottom = 64; - y += 68; - } else { - rk.left = 12; - rk.right = 80; - rk.top = y+2; - rk.bottom = 8; - - rv.left = 100; - rv.right = 288; - rv.top = y; - rv.bottom = 12; - - y += 16; - } - - MapDialogRect(hwnd, &rk); - HWND ctl = CreateWindowEx( - 0, "STATIC", key, WS_CHILD | WS_VISIBLE, - rk.left, rk.top, rk.right, rk.bottom, - hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); - SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); - - MapDialogRect(hwnd, &rv); - ctl = CreateWindowEx( - WS_EX_CLIENTEDGE, "EDIT", item->text, editstyle, - rv.left, rv.top, rv.right, rv.bottom, - hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); - SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); - break; - } - default: - break; - } - } - - /* - * Now resize the overall window, and move the Close button at - * the bottom. - */ - RECT r; - r.left = 176; - r.top = y + 10; - r.right = r.bottom = 0; - MapDialogRect(hwnd, &r); - HWND ctl = GetDlgItem(hwnd, IDOK); - SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, - SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); - - r.left = r.top = r.right = 0; - r.bottom = 300; - MapDialogRect(hwnd, &r); - int oldheight = r.bottom; - - r.left = r.top = r.right = 0; - r.bottom = y + 30; - MapDialogRect(hwnd, &r); - int newheight = r.bottom; - - GetWindowRect(hwnd, &r); - - SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, - r.bottom - r.top + newheight - oldheight, - SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); - - ShowWindow(hwnd, SW_SHOWNORMAL); - return 1; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - ShinyEndDialog(hwnd, 0); - return 0; - } - return 0; - case WM_CLOSE: - ShinyEndDialog(hwnd, 0); - return 0; - } - return 0; -} - -static const char *process_seatdialogtext( - strbuf *dlg_text, const char **scary_heading, SeatDialogText *text) -{ - const char *dlg_title = ""; - - for (SeatDialogTextItem *item = text->items, - *end = item + text->nitems; item < end; item++) { - switch (item->type) { - case SDT_PARA: - put_fmt(dlg_text, "%s\r\n\r\n", item->text); - break; - case SDT_DISPLAY: - put_fmt(dlg_text, "%s\r\n\r\n", item->text); - break; - case SDT_SCARY_HEADING: - assert(scary_heading != NULL && "only expect a scary heading if " - "the dialog has somewhere to put it"); - *scary_heading = item->text; - break; - case SDT_TITLE: - dlg_title = item->text; - break; - default: - break; - } - } - - /* Trim any trailing newlines */ - while (strbuf_chomp(dlg_text, '\r') || strbuf_chomp(dlg_text, '\n')); - - return dlg_title; -} - -static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam, void *vctx) -{ - struct hostkey_dialog_ctx *ctx = (struct hostkey_dialog_ctx *)vctx; - - switch (msg) { - case WM_INITDIALOG: { - strbuf *dlg_text = strbuf_new(); - const char *scary_heading = NULL; - const char *dlg_title = process_seatdialogtext( - dlg_text, &scary_heading, ctx->text); - - LPCTSTR iconid = IDI_QUESTION; - if (scary_heading) { - SetDlgItemText(hwnd, IDC_HK_TITLE, scary_heading); - iconid = IDI_WARNING; - } - - SetDlgItemText(hwnd, IDC_HK_TEXT, dlg_text->s); - MakeDlgItemBorderless(hwnd, IDC_HK_TEXT); - strbuf_free(dlg_text); - - SetWindowText(hwnd, dlg_title); - - if (!ctx->has_title) { - HWND item = GetDlgItem(hwnd, IDC_HK_TITLE); - if (item) - DestroyWindow(item); - } - - /* - * Find out how tall the text in the edit control really ended - * up (after line wrapping), and adjust the height of the - * whole box to match it. - */ - int height = SendDlgItemMessage(hwnd, IDC_HK_TEXT, - EM_GETLINECOUNT, 0, 0); - height *= 8; /* height of a text line, by definition of dialog units */ - - int edittop = ctx->has_title ? 40 : 20; - - RECT r; - r.left = 40; - r.top = edittop; - r.right = 290; - r.bottom = height; - MapDialogRect(hwnd, &r); - SetWindowPos(GetDlgItem(hwnd, IDC_HK_TEXT), NULL, - r.left, r.top, r.right, r.bottom, - SWP_NOREDRAW | SWP_NOZORDER); - - static const struct { - int id, x; - } buttons[] = { - { IDCANCEL, 288 }, - { IDC_HK_ACCEPT, 168 }, - { IDC_HK_ONCE, 216 }, - { IDC_HK_MOREINFO, 60 }, - { IDHELP, 12 }, - }; - for (size_t i = 0; i < lenof(buttons); i++) { - HWND ctl = GetDlgItem(hwnd, buttons[i].id); - r.left = buttons[i].x; - r.top = edittop + height + 20; - r.right = r.bottom = 0; - MapDialogRect(hwnd, &r); - SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, - SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); - } - - r.left = r.top = r.right = 0; - r.bottom = 240; - MapDialogRect(hwnd, &r); - int oldheight = r.bottom; - - r.left = r.top = r.right = 0; - r.bottom = edittop + height + 40; - MapDialogRect(hwnd, &r); - int newheight = r.bottom; - - GetWindowRect(hwnd, &r); - - SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, - r.bottom - r.top + newheight - oldheight, - SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); - - HANDLE icon = LoadImage( - NULL, iconid, IMAGE_ICON, - GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), - LR_SHARED); - SendDlgItemMessage(hwnd, IDC_HK_ICON, STM_SETICON, (WPARAM)icon, 0); - - if (!has_help()) { - HWND item = GetDlgItem(hwnd, IDHELP); - if (item) - DestroyWindow(item); - } - - ShowWindow(hwnd, SW_SHOWNORMAL); - - return 1; - } - case WM_CTLCOLORSTATIC: { - HDC hdc = (HDC)wParam; - HWND control = (HWND)lParam; - - if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE && - ctx->has_title) { - SetBkMode(hdc, TRANSPARENT); - HFONT prev_font = (HFONT)SelectObject( - hdc, (HFONT)GetStockObject(SYSTEM_FONT)); - LOGFONT lf; - if (GetObject(prev_font, sizeof(lf), &lf)) { - lf.lfWeight = FW_BOLD; - lf.lfHeight = lf.lfHeight * 3 / 2; - HFONT bold_font = CreateFontIndirect(&lf); - if (bold_font) - SelectObject(hdc, bold_font); - } - return (INT_PTR)GetSysColorBrush(COLOR_BTNFACE); - } - return 0; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDC_HK_ACCEPT: - case IDC_HK_ONCE: - case IDCANCEL: - ShinyEndDialog(hwnd, LOWORD(wParam)); - return 0; - case IDHELP: { - launch_help(hwnd, ctx->helpctx); - return 0; - } - case IDC_HK_MOREINFO: { - ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO), - "PuTTYHostKeyMoreInfo", hwnd, - HostKeyMoreInfoProc, ctx); - } - } - return 0; - case WM_CLOSE: - ShinyEndDialog(hwnd, IDCANCEL); - return 0; - } - return 0; -} - -const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat) -{ - static const SeatDialogPromptDescriptions descs = { - .hk_accept_action = "press \"Accept\"", - .hk_connect_once_action = "press \"Connect Once\"", - .hk_cancel_action = "press \"Cancel\"", - .hk_cancel_action_Participle = "Pressing \"Cancel\"", - .weak_accept_action = "press \"Yes\"", - .weak_cancel_action = "press \"No\"", - }; - return &descs; -} - -SeatPromptResult win_seat_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *cbctx) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - - struct hostkey_dialog_ctx ctx[1]; - ctx->text = text; - ctx->helpctx = helpctx; - - int mbret = ShinyDialogBox( - hinst, MAKEINTRESOURCE(IDD_HOSTKEY), "PuTTYHostKeyDialog", - wgs->term_hwnd, HostKeyDialogProc, ctx); - assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); - if (mbret == IDC_HK_ACCEPT) { - store_host_key(seat, host, port, keytype, keystr); - return SPR_OK; - } else if (mbret == IDC_HK_ONCE) { - return SPR_OK; - } - - return SPR_USER_ABORT; -} - -/* - * Ask whether the selected algorithm is acceptable (since it was - * below the configured 'warn' threshold). - */ -SeatPromptResult win_seat_confirm_weak_crypto_primitive( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - strbuf *dlg_text = strbuf_new(); - const char *dlg_title = process_seatdialogtext(dlg_text, NULL, text); - - int mbret = MessageBox(NULL, dlg_text->s, dlg_title, - MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); - socket_reselect_all(); - strbuf_free(dlg_text); - - if (mbret == IDYES) - return SPR_OK; - else - return SPR_USER_ABORT; -} - -SeatPromptResult win_seat_confirm_weak_cached_hostkey( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - strbuf *dlg_text = strbuf_new(); - const char *dlg_title = process_seatdialogtext(dlg_text, NULL, text); - - int mbret = MessageBox(NULL, dlg_text->s, dlg_title, - MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); - socket_reselect_all(); - strbuf_free(dlg_text); - - if (mbret == IDYES) - return SPR_OK; - else - return SPR_USER_ABORT; -} - -/* - * 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). - */ -static int win_gui_askappend(LogPolicy *lp, 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" - "Hit Yes to wipe the file, No to append to it,\n" - "or Cancel to disable logging."; - char *message; - char *mbtitle; - int mbret; - - message = dupprintf(msgtemplate, FILENAME_MAX, filename->utf8path); - mbtitle = dupprintf("%s Log to File", appname); - - mbret = message_box(NULL, message, mbtitle, - MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3, - true, 0); - - socket_reselect_all(); - - sfree(message); - sfree(mbtitle); - - if (mbret == IDYES) - return 2; - else if (mbret == IDNO) - return 1; - else - return 0; -} - -const LogPolicyVtable win_gui_logpolicy_vt = { - .eventlog = win_gui_eventlog, - .askappend = win_gui_askappend, - .logging_error = win_gui_logging_error, - .verbose = null_lp_verbose_yes, -}; - -/* - * 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 mbtitle[] = "%s Key File Warning"; - 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" - "%s may stop supporting this private key format,\n" - "so we recommend you convert your key to the new\n" - "format.\n" - "\n" - "You can perform this conversion by loading the key\n" - "into PuTTYgen and then saving it again."; - - char *msg, *title; - msg = dupprintf(message, appname); - title = dupprintf(mbtitle, appname); - - MessageBox(NULL, msg, title, MB_OK); - - socket_reselect_all(); - - sfree(msg); - sfree(title); -} - -static INT_PTR CAConfigProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, - void *ctx) -{ - PortableDialogStuff *pds = (PortableDialogStuff *)ctx; - - switch (msg) { - case WM_INITDIALOG: - pds_initdialog_start(pds, hwnd); - - SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG, - (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON))); - - centre_window(hwnd); - - pds_create_controls(pds, 0, IDCX_PANELBASE, 3, 3, 3, "Main"); - pds_create_controls(pds, 0, IDCX_STDBASE, 3, 3, 243, ""); - dlg_refresh(NULL, pds->dp); /* and set up control values */ - - pds_initdialog_finish(pds); - return 0; - - default: - return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam); - } -} - -void show_ca_config_box(dlgparam *dp) -{ - PortableDialogStuff *pds = pds_new(1); - - setup_ca_config_box(pds->ctrlbox); - - ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_CA_CONFIG), "PuTTYConfigBox", - dp ? dp->hwnd : NULL, CAConfigProc, pds); - - pds_free(pds); -} diff --git a/WINDOWS/gss.c b/WINDOWS/gss.c deleted file mode 100644 index bb1e914a3..000000000 --- a/WINDOWS/gss.c +++ /dev/null @@ -1,694 +0,0 @@ -#ifndef NO_GSSAPI - -#include -#include "putty.h" - -#define SECURITY_WIN32 -#include - -#include "ssh/pgssapi.h" -#include "ssh/gss.h" -#include "ssh/gssc.h" - -#include "misc.h" - -#define UNIX_EPOCH 11644473600ULL /* Seconds from Windows epoch */ -#define CNS_PERSEC 10000000ULL /* # 100ns per second */ - -/* - * Note, as a special case, 0 relative to the Windows epoch (unspecified) maps - * to 0 relative to the POSIX epoch (unspecified)! - */ -#define TIME_WIN_TO_POSIX(ft, t) do { \ - ULARGE_INTEGER uli; \ - uli.LowPart = (ft).dwLowDateTime; \ - uli.HighPart = (ft).dwHighDateTime; \ - if (uli.QuadPart != 0) \ - uli.QuadPart = uli.QuadPart / CNS_PERSEC - UNIX_EPOCH; \ - (t) = (time_t) uli.QuadPart; \ -} while (0) - -/* Windows code to set up the GSSAPI library list. */ - -#ifdef _WIN64 -#define MIT_KERB_SUFFIX "64" -#else -#define MIT_KERB_SUFFIX "32" -#endif - -const int ngsslibs = 3; -const char *const gsslibnames[3] = { - "MIT Kerberos GSSAPI"MIT_KERB_SUFFIX".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, PVOID, - 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)); -DECL_WINDOWS_FUNCTION(static, SECURITY_STATUS, - VerifySignature, - (PCtxtHandle, PSecBufferDesc, ULONG, PULONG)); -DECL_WINDOWS_FUNCTION(static, DLL_DIRECTORY_COOKIE, - AddDllDirectory, - (PCWSTR)); - -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; - - -const Ssh_gss_buf gss_mech_krb5={9,"\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"}; - -static void ssh_sspi_bind_fns(struct ssh_gss_library *lib); - -static tree234 *libraries_to_never_unload; -static int library_to_never_unload_cmp(void *av, void *bv) -{ - uintptr_t a = (uintptr_t)av, b = (uintptr_t)bv; - return a < b ? -1 : a > b ? +1 : 0; -} -static void ensure_library_tree_exists(void) -{ - if (!libraries_to_never_unload) - libraries_to_never_unload = newtree234(library_to_never_unload_cmp); -} -static bool library_is_in_never_unload_tree(HMODULE module) -{ - ensure_library_tree_exists(); - return find234(libraries_to_never_unload, module, NULL); -} -static void add_library_to_never_unload_tree(HMODULE module) -{ - ensure_library_tree_exists(); - add234(libraries_to_never_unload, module); -} - -struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) -{ - HMODULE module; - struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); - static HMODULE kernel32_module; - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); - } -#if !HAVE_ADDDLLDIRECTORY - /* Omit the type-check because older MSVCs don't have this function */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, AddDllDirectory); -#else - GET_WINDOWS_FUNCTION(kernel32_module, AddDllDirectory); -#endif - - list->libraries = snewn(3, struct ssh_gss_library); - list->nlibraries = 0; - - /* MIT Kerberos GSSAPI implementation */ - module = NULL; - HKEY regkey = open_regkey_ro(HKEY_LOCAL_MACHINE, - "SOFTWARE\\MIT\\Kerberos"); - if (regkey) { - char *installdir = get_reg_sz(regkey, "InstallDir"); - if (installdir) { - char *bindir = dupcat(installdir, "\\bin"); - if (p_AddDllDirectory) { - /* Add MIT Kerberos' path to the DLL search path, - * it loads its own DLLs further down the road */ - wchar_t *dllPath = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, bindir); - p_AddDllDirectory(dllPath); - sfree(dllPath); - } - - char *dllfile = dupcat(bindir, "\\gssapi"MIT_KERB_SUFFIX".dll"); - module = LoadLibraryEx(dllfile, NULL, - LOAD_LIBRARY_SEARCH_SYSTEM32 | - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | - LOAD_LIBRARY_SEARCH_USER_DIRS); - - /* - * The MIT Kerberos DLL suffers an internal segfault for - * some reason if you unload and reload one within the - * same process. So, make sure that after we load this - * library, we never free it. - * - * Or rather: after we've loaded it once, if any _further_ - * load returns the same module handle, we immediately - * free it again (to prevent the Windows API's internal - * reference count growing without bound). But on the - * other hand we never free it in ssh_gss_cleanup. - */ - if (library_is_in_never_unload_tree(module)) - FreeLibrary(module); - add_library_to_never_unload_tree(module); - - sfree(dllfile); - sfree(bindir); - sfree(installdir); - } - close_regkey(regkey); - } - if (module) { - struct ssh_gss_library *lib = - &list->libraries[list->nlibraries++]; - - lib->id = 0; - lib->gsslogmsg = "Using GSSAPI from GSSAPI"MIT_KERB_SUFFIX".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(verify_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); - BIND_GSS_FN(acquire_cred); - BIND_GSS_FN(inquire_cred_by_mech); - -#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; - - /* No typecheck because Winelib thinks one PVOID is a PLUID */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(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); - GET_WINDOWS_FUNCTION(module, VerifySignature); - - ssh_sspi_bind_fns(lib); - } - - /* - * Custom GSSAPI DLL. - */ - module = NULL; - Filename *customlib = conf_get_filename(conf, CONF_ssh_gss_custom); - if (!filename_is_null(customlib)) { - const wchar_t *path = customlib->wpath; - if (p_AddDllDirectory) { - - /* Add the custom directory as well in case it chainloads - * some other DLLs (e.g a non-installed MIT Kerberos - * instance) */ - int pathlen = wcslen(path); - - while (pathlen > 0 && path[pathlen-1] != L':' && - path[pathlen-1] != L'\\') - pathlen--; - - if (pathlen > 0 && path[pathlen-1] != L'\\') - pathlen--; - - if (pathlen > 0) { - wchar_t *dirpath = snewn(pathlen + 1, wchar_t); - memcpy(dirpath, path, pathlen * sizeof(wchar_t)); - dirpath[pathlen] = L'\0'; - p_AddDllDirectory(dirpath); - sfree(dirpath); - } - } - - module = LoadLibraryExW(path, NULL, - LOAD_LIBRARY_SEARCH_SYSTEM32 | - LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | - LOAD_LIBRARY_SEARCH_USER_DIRS); - } - if (module) { - struct ssh_gss_library *lib = - &list->libraries[list->nlibraries++]; - - lib->id = 2; - lib->gsslogmsg = dupprintf("Using GSSAPI from user-specified" - " library '%s'", customlib->cpath); - 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(verify_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); - BIND_GSS_FN(acquire_cred); - BIND_GSS_FN(inquire_cred_by_mech); - -#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++) { - if (list->libraries[i].id != 0) { - HMODULE module = (HMODULE)list->libraries[i].handle; - if (!library_is_in_never_unload_tree(module)) - FreeLibrary(module); - } - 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); - - *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, - time_t *expiry) -{ - 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, - NULL); - - if (winctx->maj_stat != SEC_E_OK) { - p_FreeCredentialsHandle(&winctx->cred_handle); - sfree(winctx); - return SSH_GSS_FAILURE; - } - - /* Windows does not return a valid expiration from AcquireCredentials */ - if (expiry) - *expiry = GSS_NO_EXPIRATION; - - *ctx = (Ssh_gss_ctx) winctx; - return SSH_GSS_OK; -} - -static void localexp_to_exp_lifetime(TimeStamp *localexp, - time_t *expiry, unsigned long *lifetime) -{ - FILETIME nowUTC; - FILETIME expUTC; - time_t now; - time_t exp; - time_t delta; - - if (!lifetime && !expiry) - return; - - GetSystemTimeAsFileTime(&nowUTC); - TIME_WIN_TO_POSIX(nowUTC, now); - - if (lifetime) - *lifetime = 0; - if (expiry) - *expiry = GSS_NO_EXPIRATION; - - /* - * Type oddity: localexp is a pointer to 'TimeStamp', whereas - * LocalFileTimeToFileTime expects a pointer to FILETIME. However, - * despite having different formal type names from the compiler's - * point of view, these two structures are specified to be - * isomorphic in the MS documentation, so it's legitimate to copy - * between them: - * - * https://msdn.microsoft.com/en-us/library/windows/desktop/aa380511(v=vs.85).aspx - */ - { - FILETIME localexp_ft; - enum { vorpal_sword = 1 / (sizeof(*localexp) == sizeof(localexp_ft)) }; - memcpy(&localexp_ft, localexp, sizeof(localexp_ft)); - if (!LocalFileTimeToFileTime(&localexp_ft, &expUTC)) - return; - } - - TIME_WIN_TO_POSIX(expUTC, exp); - delta = exp - now; - if (exp == 0 || delta <= 0) - return; - - if (expiry) - *expiry = exp; - if (lifetime) { - if (delta <= ULONG_MAX) - *lifetime = (unsigned long)delta; - else - *lifetime = ULONG_MAX; - } -} - -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, - time_t *expiry, - unsigned long *lifetime) -{ - winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) *ctx; - SecBuffer wsend_tok = {send_tok->length,SECBUFFER_TOKEN,send_tok->value}; - SecBuffer wrecv_tok = {recv_tok->length,SECBUFFER_TOKEN,recv_tok->value}; - SecBufferDesc output_desc = {SECBUFFER_VERSION,1,&wsend_tok}; - SecBufferDesc input_desc = {SECBUFFER_VERSION,1,&wrecv_tok}; - unsigned long flags=ISC_REQ_MUTUAL_AUTH|ISC_REQ_REPLAY_DETECT| - ISC_REQ_CONFIDENTIALITY|ISC_REQ_ALLOCATE_MEMORY; - ULONG ret_flags=0; - TimeStamp localexp; - - /* 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, - &localexp); - - localexp_to_exp_lifetime(&localexp, expiry, lifetime); - - /* 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); - 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_verify_mic(struct ssh_gss_library *lib, - Ssh_gss_ctx ctx, - Ssh_gss_buf *buf, - Ssh_gss_buf *mic) -{ - winSsh_gss_ctx *winctx = (winSsh_gss_ctx *) ctx; - SecBufferDesc InputBufferDescriptor; - SecBuffer InputSecurityToken[2]; - ULONG qop; - - if (winctx == NULL) return SSH_GSS_FAILURE; - - winctx->maj_stat = 0; - - 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 = mic->length; - InputSecurityToken[1].pvBuffer = mic->value; - - winctx->maj_stat = p_VerifySignature(&winctx->context, - &InputBufferDescriptor, - 0, &qop); - 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->verify_mic = ssh_sspi_verify_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/WINDOWS/handle-io.c b/WINDOWS/handle-io.c deleted file mode 100644 index 0f5b7e94a..000000000 --- a/WINDOWS/handle-io.c +++ /dev/null @@ -1,687 +0,0 @@ -/* - * handle-io.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. - */ - -typedef struct handle_list_node handle_list_node; -struct handle_list_node { - handle_list_node *next, *prev; -}; -static void add_to_ready_list(handle_list_node *node); - -/* - * 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_list_node ready_node; /* for linking on to the ready list */ - HANDLE ev_from_main; /* event used to signal back to us */ - bool moribund; /* are we going to kill this soon? */ - bool done; /* request subthread to terminate */ - bool defunct; /* has the subthread already gone? */ - bool busy; /* operation currently in progress? */ - void *privdata; /* for client to remember who they are */ -}; - -typedef enum { HT_INPUT, HT_OUTPUT } HandleType; - -/* ---------------------------------------------------------------------- - * Input threads. - */ - -/* - * Data required by an input thread. - */ -struct handle_input { - /* - * Copy of the handle_generic structure. - */ - HANDLE h; /* the handle itself */ - handle_list_node ready_node; /* for linking on to the ready list */ - HANDLE ev_from_main; /* event used to signal back to us */ - bool moribund; /* are we going to kill this soon? */ - bool done; /* request subthread to terminate */ - bool defunct; /* has the subthread already gone? */ - bool 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 marking the handle ready, - * 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; - bool readret, finished; - int readlen; - - 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); - - add_to_ready_list(&ctx->ready_node); - - 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. - */ - add_to_ready_list(&ctx->ready_node); - break; - } - } - - if (povl) - CloseHandle(oev); - - return 0; -} - -/* - * This is called after a successful 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_list_node ready_node; /* for linking on to the ready list */ - HANDLE ev_from_main; /* event used to signal back to us */ - bool moribund; /* are we going to kill this soon? */ - bool done; /* request subthread to terminate */ - bool defunct; /* has the subthread already gone? */ - bool 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. - */ - const char *buffer; /* the data to write */ - DWORD len; /* how much data there is */ - - /* - * Data set by the input thread before marking this handle as - * ready, 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; - struct handle *sentdata_param; -}; - -static DWORD WINAPI handle_output_threadfunc(void *param) -{ - struct handle_output *ctx = (struct handle_output *) param; - OVERLAPPED ovl, *povl; - HANDLE oev; - bool 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. - */ - add_to_ready_list(&ctx->ready_node); - 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; - } - - add_to_ready_list(&ctx->ready_node); - 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) -{ - if (!ctx->busy && bufchain_size(&ctx->queued_data)) { - ptrlen data = bufchain_prefix(&ctx->queued_data); - ctx->buffer = data.ptr; - ctx->len = min(data.len, ~(DWORD)0); - SetEvent(ctx->ev_from_main); - ctx->busy = true; - } else if (!ctx->busy && bufchain_size(&ctx->queued_data) == 0 && - ctx->outgoingeof == EOF_PENDING) { - ctx->sentdata(ctx->sentdata_param, 0, 0, true); - ctx->h = INVALID_HANDLE_VALUE; - ctx->outgoingeof = EOF_SENT; - } -} - -/* ---------------------------------------------------------------------- - * 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; - } u; -}; - -/* - * Linked list storing the current list of handles ready to have - * something done to them by the main thread. - */ -static handle_list_node ready_head[1]; -static CRITICAL_SECTION ready_critsec[1]; - -/* - * Event object used by all subthreads to signal that they've just put - * something on the ready list, i.e. that the ready list is non-empty. - */ -static HANDLE ready_event = INVALID_HANDLE_VALUE; - -static void add_to_ready_list(handle_list_node *node) -{ - /* - * Called from subthreads, when their handle has done something - * that they need the main thread to respond to. We append the - * given list node to the end of the ready list, and set - * ready_event to signal to the main thread that the ready list is - * now non-empty. - */ - EnterCriticalSection(ready_critsec); - node->next = ready_head; - node->prev = ready_head->prev; - node->next->prev = node->prev->next = node; - SetEvent(ready_event); - LeaveCriticalSection(ready_critsec); -} - -static void remove_from_ready_list(handle_list_node *node) -{ - /* - * Called from the main thread, just before destroying a 'struct - * handle' completely: as a precaution, we make absolutely sure - * it's not linked on the ready list, just in case somehow it - * still was. - */ - EnterCriticalSection(ready_critsec); - node->next->prev = node->prev; - node->prev->next = node->next; - node->next = node->prev = node; - LeaveCriticalSection(ready_critsec); -} - -static void handle_ready(struct handle *h); /* process one handle (below) */ - -static void handle_ready_callback(void *vctx) -{ - /* - * Called when the main thread detects ready_event, indicating - * that at least one handle is on the ready list. We empty the - * whole list and process the handles one by one. - * - * It's possible that other handles may be destroyed, and hence - * taken _off_ the ready list, during this processing. That - * shouldn't cause a deadlock, because according to the API docs, - * it's safe to call EnterCriticalSection twice in the same thread - * - the second call will return immediately because that thread - * already owns the critsec. (And then it takes two calls to - * LeaveCriticalSection to release it again, which is just what we - * want here.) - */ - EnterCriticalSection(ready_critsec); - while (ready_head->next != ready_head) { - handle_list_node *node = ready_head->next; - node->prev->next = node->next; - node->next->prev = node->prev; - node->next = node->prev = node; - handle_ready(container_of(node, struct handle, u.g.ready_node)); - } - LeaveCriticalSection(ready_critsec); -} - -static inline void ensure_ready_event_setup(void) -{ - if (ready_event == INVALID_HANDLE_VALUE) { - ready_head->prev = ready_head->next = ready_head; - InitializeCriticalSection(ready_critsec); - ready_event = CreateEvent(NULL, false, false, NULL); - add_handle_wait(ready_event, handle_ready_callback, NULL); - } -} - -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_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; - - ensure_ready_event_setup(); - HANDLE hThread = CreateThread(NULL, 0, handle_input_threadfunc, - &h->u.i, 0, &in_threadid); - if (hThread) - CloseHandle(hThread); /* we don't need the thread handle */ - 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_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.sentdata_param = h; - h->u.o.flags = flags; - - ensure_ready_event_setup(); - HANDLE hThread = CreateThread(NULL, 0, handle_output_threadfunc, - &h->u.o, 0, &out_threadid); - if (hThread) - CloseHandle(hThread); /* we don't need the thread handle */ - - return h; -} - -size_t handle_write(struct handle *h, const void *data, size_t 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); - } -} - -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); - remove_from_ready_list(&h->u.g.ready_node); - sfree(h); -} - -void handle_free(struct handle *h) -{ - assert(h && !h->u.g.moribund); - if (h->u.g.busy) { - /* - * 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); - } -} - -static void handle_ready(struct handle *h) -{ - 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); - } - return; - } - - 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, 0, h->u.i.readerr); - } else { - backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len, 0); - handle_throttle(&h->u.i, backlog); - } - break; - - 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, 0, h->u.o.writeerr, false); - } else { - bufchain_consume(&h->u.o.queued_data, h->u.o.lenwritten); - noise_ultralight(NOISE_SOURCE_IOLEN, h->u.o.lenwritten); - h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data), 0, false); - handle_try_output(&h->u.o); - } - break; - } -} - -void handle_unthrottle(struct handle *h, size_t backlog) -{ - assert(h->type == HT_INPUT); - handle_throttle(&h->u.i, backlog); -} - -size_t 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; -} - -static void handle_sink_write(BinarySink *bs, const void *data, size_t len) -{ - handle_sink *sink = BinarySink_DOWNCAST(bs, handle_sink); - handle_write(sink->h, data, len); -} - -void handle_sink_init(handle_sink *sink, struct handle *h) -{ - sink->h = h; - BinarySink_INIT(sink, handle_sink_write); -} diff --git a/WINDOWS/handle-socket.c b/WINDOWS/handle-socket.c deleted file mode 100644 index 2820975c1..000000000 --- a/WINDOWS/handle-socket.c +++ /dev/null @@ -1,512 +0,0 @@ -/* - * General mechanism for wrapping up reading/writing of Windows - * HANDLEs into a PuTTY Socket abstraction. - */ - -#include -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.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 handle-io.c to stop - * reading. - */ -typedef enum HandleSocketFreezeState { - 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 */ -} HandleSocketFreezeState; - -typedef struct HandleSocket { - union { - struct { - HANDLE send_H, recv_H, stderr_H; - struct handle *send_h, *recv_h, *stderr_h; - - HandleSocketFreezeState frozen; - /* We buffer data here if we receive it from winhandl - * while frozen. */ - bufchain inputdata; - - /* Handle logging proxy error messages from stderr_H, if - * we have one */ - ProxyStderrBuf psb; - - bool defer_close, deferred_close; /* in case of re-entrance */ - }; - struct { - DeferredSocketOpener *opener; - - /* We buffer data here if we receive it via sk_write - * before the socket is opened. */ - bufchain outputdata; - - bool output_eof_pending; - - bool start_frozen; - }; - }; - - char *error; - - SockAddr *addr; - int port; - Plug *plug; - - Socket sock; -} HandleSocket; - -static size_t handle_gotdata( - struct handle *h, const void *data, size_t len, int err) -{ - HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); - - if (err) { - plug_closing_error(hs->plug, "Read error from handle"); - return 0; - } else if (len == 0) { - plug_closing_normal(hs->plug); - return 0; - } else { - assert(hs->frozen != FROZEN && hs->frozen != THAWING); - if (hs->frozen == FREEZING) { - /* - * If we've received data while this socket is supposed to - * be frozen (because the read handle-io.c started before - * sk_set_frozen was called has now returned) then buffer - * the data for when we unfreeze. - */ - bufchain_add(&hs->inputdata, data, len); - hs->frozen = FROZEN; - - /* - * And return a very large backlog, to prevent further - * data arriving from winhandl until we unfreeze. - */ - return INT_MAX; - } else { - plug_receive(hs->plug, 0, data, len); - return 0; - } - } -} - -static size_t handle_stderr( - struct handle *h, const void *data, size_t len, int err) -{ - HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); - - if (!err && len > 0) - log_proxy_stderr(hs->plug, &hs->psb, data, len); - - return 0; -} - -static void handle_sentdata(struct handle *h, size_t new_backlog, int err, - bool close) -{ - HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); - - if (close) { - if (hs->send_H != INVALID_HANDLE_VALUE) - CloseHandle(hs->send_H); - if (hs->recv_H != INVALID_HANDLE_VALUE && hs->recv_H != hs->send_H) - CloseHandle(hs->recv_H); - hs->send_H = hs->recv_H = INVALID_HANDLE_VALUE; - } - - if (err) { - plug_closing_system_error(hs->plug, err); - return; - } - - plug_sent(hs->plug, new_backlog); -} - -static Plug *sk_handle_plug(Socket *s, Plug *p) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - Plug *ret = hs->plug; - if (p) - hs->plug = p; - return ret; -} - -static void sk_handle_close(Socket *s) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - - if (hs->defer_close) { - hs->deferred_close = true; - return; - } - - handle_free(hs->send_h); - handle_free(hs->recv_h); - if (hs->send_H != INVALID_HANDLE_VALUE) - CloseHandle(hs->send_H); - if (hs->recv_H != INVALID_HANDLE_VALUE && hs->recv_H != hs->send_H) - CloseHandle(hs->recv_H); - bufchain_clear(&hs->inputdata); - - if (hs->addr) - sk_addr_free(hs->addr); - - delete_callbacks_for_context(hs); - - sfree(hs); -} - -static size_t sk_handle_write(Socket *s, const void *data, size_t len) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - - return handle_write(hs->send_h, data, len); -} - -static size_t sk_handle_write_oob(Socket *s, const void *data, size_t 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) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - - handle_write_eof(hs->send_h); -} - -static void handle_socket_unfreeze(void *hsv) -{ - HandleSocket *hs = (HandleSocket *)hsv; - - /* - * If we've been put into a state other than THAWING since the - * last callback, then we're done. - */ - if (hs->frozen != THAWING) - return; - - /* - * Get some of the data we've buffered. - */ - ptrlen data = bufchain_prefix(&hs->inputdata); - assert(data.len > 0); - - /* - * Hand it off to the plug. Be careful of re-entrance - that might - * have the effect of trying to close this socket. - */ - hs->defer_close = true; - plug_receive(hs->plug, 0, data.ptr, data.len); - bufchain_consume(&hs->inputdata, data.len); - hs->defer_close = false; - if (hs->deferred_close) { - sk_handle_close(&hs->sock); - return; - } - - if (bufchain_size(&hs->inputdata) > 0) { - /* - * If there's still data in our buffer, stay in THAWING state, - * and reschedule ourself. - */ - queue_toplevel_callback(handle_socket_unfreeze, hs); - } else { - /* - * Otherwise, we've successfully thawed! - */ - hs->frozen = UNFROZEN; - handle_unthrottle(hs->recv_h, 0); - } -} - -static void sk_handle_set_frozen(Socket *s, bool is_frozen) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - - if (is_frozen) { - switch (hs->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, handle-io.c is already - * throttled, so just return to FROZEN state. The toplevel - * callback will notice and disable itself. - */ - hs->frozen = FROZEN; - break; - - case UNFROZEN: - /* - * The normal case. Go to FREEZING, and expect one more - * load of data from winhandl if we're unlucky. - */ - hs->frozen = FREEZING; - break; - } - } else { - switch (hs->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(&hs->inputdata) == 0); - hs->frozen = UNFROZEN; - break; - - case FROZEN: - /* - * If we have buffered data, go to THAWING and start - * releasing it in top-level callbacks. - */ - hs->frozen = THAWING; - queue_toplevel_callback(handle_socket_unfreeze, hs); - } - } -} - -static const char *sk_handle_socket_error(Socket *s) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - return hs->error; -} - -static SocketPeerInfo *sk_handle_peer_info(Socket *s) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - ULONG pid; - static HMODULE kernel32_module; - DECL_WINDOWS_FUNCTION(static, BOOL, GetNamedPipeClientProcessId, - (HANDLE, PULONG)); - - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); -#if !HAVE_GETNAMEDPIPECLIENTPROCESSID - /* For older Visual Studio, and MinGW too (at least as of - * Ubuntu 16.04), this function isn't available in the header - * files to type-check. Ditto the toolchain I use for - * Coveritying the Windows code. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK( - kernel32_module, GetNamedPipeClientProcessId); -#else - GET_WINDOWS_FUNCTION( - kernel32_module, GetNamedPipeClientProcessId); -#endif - } - - /* - * 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(hs->send_H, &pid)) { - SocketPeerInfo *pi = snew(SocketPeerInfo); - pi->addressfamily = ADDRTYPE_LOCAL; - pi->addr_text = NULL; - pi->port = -1; - pi->log_text = dupprintf("process id %lu", (unsigned long)pid); - return pi; - } - - return NULL; -} - -static const SocketVtable HandleSocket_sockvt = { - .plug = sk_handle_plug, - .close = sk_handle_close, - .write = sk_handle_write, - .write_oob = sk_handle_write_oob, - .write_eof = sk_handle_write_eof, - .set_frozen = sk_handle_set_frozen, - .socket_error = sk_handle_socket_error, - .peer_info = sk_handle_peer_info, -}; - -static void sk_handle_connect_success_callback(void *ctx) -{ - HandleSocket *hs = (HandleSocket *)ctx; - plug_log(hs->plug, PLUGLOG_CONNECT_SUCCESS, hs->addr, hs->port, NULL, 0); -} - -Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, - SockAddr *addr, int port, Plug *plug, - bool overlapped) -{ - HandleSocket *hs; - int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); - - hs = snew(HandleSocket); - hs->sock.vt = &HandleSocket_sockvt; - hs->addr = addr; - hs->port = port; - hs->plug = plug; - hs->error = NULL; - - hs->frozen = UNFROZEN; - bufchain_init(&hs->inputdata); - psb_init(&hs->psb); - - hs->recv_H = recv_H; - hs->recv_h = handle_input_new(hs->recv_H, handle_gotdata, hs, flags); - hs->send_H = send_H; - hs->send_h = handle_output_new(hs->send_H, handle_sentdata, hs, flags); - hs->stderr_H = stderr_H; - if (hs->stderr_H) - hs->stderr_h = handle_input_new(hs->stderr_H, handle_stderr, - hs, flags); - - hs->defer_close = hs->deferred_close = false; - - queue_toplevel_callback(sk_handle_connect_success_callback, hs); - - return &hs->sock; -} - -void handle_socket_set_psb_prefix(Socket *s, const char *prefix) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - assert(hs->sock.vt == &HandleSocket_sockvt); - psb_set_prefix(&hs->psb, prefix); -} - -static void sk_handle_deferred_close(Socket *s) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - - deferred_socket_opener_free(hs->opener); - bufchain_clear(&hs->outputdata); - - if (hs->addr) - sk_addr_free(hs->addr); - - delete_callbacks_for_context(hs); - - sfree(hs); -} - -static size_t sk_handle_deferred_write(Socket *s, const void *data, size_t len) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - assert(!hs->output_eof_pending); - bufchain_add(&hs->outputdata, data, len); - return bufchain_size(&hs->outputdata); -} - -static void sk_handle_deferred_write_eof(Socket *s) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - assert(!hs->output_eof_pending); - hs->output_eof_pending = true; -} - -static void sk_handle_deferred_set_frozen(Socket *s, bool is_frozen) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - hs->frozen = is_frozen; -} - -static SocketPeerInfo *sk_handle_deferred_peer_info(Socket *s) -{ - return NULL; -} - -static const SocketVtable HandleSocket_deferred_sockvt = { - .plug = sk_handle_plug, - .close = sk_handle_deferred_close, - .write = sk_handle_deferred_write, - .write_oob = sk_handle_deferred_write, - .write_eof = sk_handle_deferred_write_eof, - .set_frozen = sk_handle_deferred_set_frozen, - .socket_error = sk_handle_socket_error, - .peer_info = sk_handle_deferred_peer_info, -}; - -Socket *make_deferred_handle_socket(DeferredSocketOpener *opener, - SockAddr *addr, int port, Plug *plug) -{ - HandleSocket *hs = snew(HandleSocket); - hs->sock.vt = &HandleSocket_deferred_sockvt; - hs->addr = addr; - hs->port = port; - hs->plug = plug; - hs->error = NULL; - - hs->opener = opener; - bufchain_init(&hs->outputdata); - hs->output_eof_pending = false; - hs->start_frozen = false; - - return &hs->sock; -} - -void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H, - HANDLE stderr_H, bool overlapped) -{ - HandleSocket *hs = container_of(s, HandleSocket, sock); - assert(hs->sock.vt == &HandleSocket_deferred_sockvt); - - int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); - - struct handle *recv_h = handle_input_new( - recv_H, handle_gotdata, hs, flags); - struct handle *send_h = handle_output_new( - send_H, handle_sentdata, hs, flags); - struct handle *stderr_h = !stderr_H ? NULL : handle_input_new( - stderr_H, handle_stderr, hs, flags); - - while (bufchain_size(&hs->outputdata)) { - ptrlen data = bufchain_prefix(&hs->outputdata); - handle_write(send_h, data.ptr, data.len); - bufchain_consume(&hs->outputdata, data.len); - } - - if (hs->output_eof_pending) - handle_write_eof(send_h); - - bool start_frozen = hs->start_frozen; - - deferred_socket_opener_free(hs->opener); - bufchain_clear(&hs->outputdata); - - hs->sock.vt = &HandleSocket_sockvt; - hs->frozen = start_frozen ? FREEZING : UNFROZEN; - bufchain_init(&hs->inputdata); - psb_init(&hs->psb); - - hs->recv_H = recv_H; - hs->recv_h = recv_h; - hs->send_H = send_H; - hs->send_h = send_h; - hs->stderr_H = stderr_H; - hs->stderr_h = stderr_h; - - hs->defer_close = hs->deferred_close = false; - - queue_toplevel_callback(sk_handle_connect_success_callback, hs); -} diff --git a/WINDOWS/handle-wait.c b/WINDOWS/handle-wait.c deleted file mode 100644 index 9e4e522d4..000000000 --- a/WINDOWS/handle-wait.c +++ /dev/null @@ -1,143 +0,0 @@ -/* - * handle-wait.c: Manage a collection of HANDLEs to wait for (in a - * WaitFor{Single,Multiple}Objects sense), each with a callback to be - * called when it's activated. Tracks the list, and provides an API to - * event loops that let them get a list of things to wait for and a - * way to call back to here when one of them does something. - */ - -/* - * TODO: currently this system can't cope with more than - * MAXIMUM_WAIT_OBJECTS (= 64) handles at a time. It enforces that by - * assertion, so we'll at least find out if that assumption is ever - * violated. - * - * It should be OK for the moment. As of 2021-05-24, the only uses of - * this system are by the ConPTY backend (just once, to watch for its - * subprocess terminating); by Pageant (for the event that the - * WM_COPYDATA subthread uses to signal the main thread); and by - * named-pipe-server.c (once per named-pipe server, of which there is - * one in Pageant and one in connection-sharing upstreams). So the - * total number of handles has a pretty small upper bound. - * - * But sooner or later, I'm sure we'll find a reason why we really - * need to watch a squillion handles at once. When that happens, I - * can't see any alternative to setting up some kind of tree of - * subthreads in this module, each one condensing 64 of our handles - * into one, by doing its own WaitForMultipleObjects and setting an - * event object to indicate that one of them did something. It'll be - * horribly ugly. - */ - -#include "putty.h" - -struct HandleWait { - HANDLE handle; - handle_wait_callback_fn_t callback; - void *callback_ctx; - - int index; /* sort key for tree234 */ -}; - -struct HandleWaitListInner { - HandleWait *hws[MAXIMUM_WAIT_OBJECTS]; - HANDLE handles[MAXIMUM_WAIT_OBJECTS]; - - struct HandleWaitList hwl; -}; - -static int handlewait_cmp(void *av, void *bv) -{ - HandleWait *a = (HandleWait *)av, *b = (HandleWait *)bv; - if (a->index < b->index) - return -1; - if (a->index > b->index) - return +1; - return 0; -} - -static tree234 *handlewaits_tree_real; - -static inline tree234 *ensure_handlewaits_tree_exists(void) -{ - if (!handlewaits_tree_real) - handlewaits_tree_real = newtree234(handlewait_cmp); - return handlewaits_tree_real; -} - -static int allocate_index(void) -{ - tree234 *t = ensure_handlewaits_tree_exists(); - search234_state st[1]; - - search234_start(st, t); - while (st->element) { - HandleWait *hw = (HandleWait *)st->element; - if (st->index < hw->index) { - /* There are unused index slots to the left of this element */ - search234_step(st, -1); - } else { - assert(st->index == hw->index); - search234_step(st, +1); - } - } - - return st->index; -} - -HandleWait *add_handle_wait(HANDLE h, handle_wait_callback_fn_t callback, - void *callback_ctx) -{ - HandleWait *hw = snew(HandleWait); - hw->handle = h; - hw->callback = callback; - hw->callback_ctx = callback_ctx; - - tree234 *t = ensure_handlewaits_tree_exists(); - hw->index = allocate_index(); - HandleWait *added = add234(t, hw); - assert(added == hw); - - return hw; -} - -void delete_handle_wait(HandleWait *hw) -{ - tree234 *t = ensure_handlewaits_tree_exists(); - HandleWait *deleted = del234(t, hw); - assert(deleted == hw); - sfree(hw); -} - -HandleWaitList *get_handle_wait_list(void) -{ - tree234 *t = ensure_handlewaits_tree_exists(); - struct HandleWaitListInner *hwli = snew(struct HandleWaitListInner); - size_t n = 0; - HandleWait *hw; - for (int i = 0; (hw = index234(t, i)) != NULL; i++) { - assert(n < MAXIMUM_WAIT_OBJECTS); - hwli->hws[n] = hw; - hwli->hwl.handles[n] = hw->handle; - n++; - } - hwli->hwl.nhandles = n; - return &hwli->hwl; -} - -void handle_wait_activate(HandleWaitList *hwl, int index) -{ - struct HandleWaitListInner *hwli = - container_of(hwl, struct HandleWaitListInner, hwl); - assert(0 <= index); - assert(index < hwli->hwl.nhandles); - HandleWait *hw = hwli->hws[index]; - hw->callback(hw->callback_ctx); -} - -void handle_wait_list_free(HandleWaitList *hwl) -{ - struct HandleWaitListInner *hwli = - container_of(hwl, struct HandleWaitListInner, hwl); - sfree(hwli); -} diff --git a/WINDOWS/help.c b/WINDOWS/help.c deleted file mode 100644 index 5160b0d49..000000000 --- a/WINDOWS/help.c +++ /dev/null @@ -1,250 +0,0 @@ -/* - * help.c: centralised functions to launch Windows HTML Help files. - */ - -#include -#include -#include -#include - -#include "putty.h" -#include "putty-rc.h" - -#ifdef NO_HTMLHELP - -/* If htmlhelp.h is not available, we can't do any of this at all */ -bool has_help(void) { return false; } -void init_help(void) { } -void shutdown_help(void) { } -void launch_help(HWND hwnd, const char *topic) { } -void quit_help(HWND hwnd) { } - -#else - -#include - -static char *chm_path = NULL; -static bool chm_created_by_us = false; - -static bool requested_help; -DECL_WINDOWS_FUNCTION(static, HWND, HtmlHelpA, (HWND, LPCSTR, UINT, DWORD_PTR)); - -static HRSRC chm_hrsrc; -static DWORD chm_resource_size = 0; -static const void *chm_resource = NULL; - -int has_embedded_chm(void) -{ - static bool checked = false; - if (!checked) { - checked = true; - - chm_hrsrc = FindResource( - NULL, MAKEINTRESOURCE(ID_CUSTOM_CHMFILE), - MAKEINTRESOURCE(TYPE_CUSTOM_CHMFILE)); - } - return chm_hrsrc != NULL ? 1 : 0; -} - -static bool find_chm_resource(void) -{ - static bool checked = false; - if (checked) /* we've been here already */ - goto out; - checked = true; - - /* - * Look for a CHM file embedded in this executable as a custom - * resource. - */ - if (!has_embedded_chm()) /* set up chm_hrsrc and check if it's NULL */ - goto out; - - chm_resource_size = SizeofResource(NULL, chm_hrsrc); - if (chm_resource_size == 0) - goto out; - - HGLOBAL chm_hglobal = LoadResource(NULL, chm_hrsrc); - if (chm_hglobal == NULL) - goto out; - - chm_resource = (const uint8_t *)LockResource(chm_hglobal); - - out: - return chm_resource != NULL; -} - -static bool load_chm_resource(void) -{ - bool toret = false; - char *filename = NULL; - HANDLE filehandle = INVALID_HANDLE_VALUE; - bool created = false; - - static bool tried_to_load = false; - if (tried_to_load) - goto out; - tried_to_load = true; - - /* - * We've found it! Now write it out into a separate file, so that - * htmlhelp.exe can handle it. - */ - - /* GetTempPath is documented as returning a size of up to - * MAX_PATH+1 which does not count the NUL */ - char tempdir[MAX_PATH + 2]; - if (GetTempPath(sizeof(tempdir), tempdir) == 0) - goto out; - - unsigned long pid = GetCurrentProcessId(); - - for (uint64_t counter = 0;; counter++) { - filename = dupprintf( - "%s\\putty_%lu_%"PRIu64".chm", tempdir, pid, counter); - filehandle = CreateFile( - filename, GENERIC_WRITE, FILE_SHARE_READ, - NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); - - if (filehandle != INVALID_HANDLE_VALUE) - break; /* success! */ - - if (GetLastError() != ERROR_FILE_EXISTS) - goto out; /* failed for some other reason! */ - - sfree(filename); - filename = NULL; - } - created = true; - - const uint8_t *p = (const uint8_t *)chm_resource; - for (DWORD pos = 0; pos < chm_resource_size ;) { - DWORD to_write = chm_resource_size - pos; - DWORD written = 0; - - if (!WriteFile(filehandle, p + pos, to_write, &written, NULL)) - goto out; - pos += written; - } - - chm_path = filename; - filename = NULL; - chm_created_by_us = true; - toret = true; - - out: - if (created && !toret) - DeleteFile(filename); - sfree(filename); - if (filehandle != INVALID_HANDLE_VALUE) - CloseHandle(filehandle); - return toret; -} - -static bool find_chm_from_installation(void) -{ - static const char *const reg_paths[] = { - "Software\\SimonTatham\\PuTTY64\\CHMPath", - "Software\\SimonTatham\\PuTTY\\CHMPath", - }; - - for (size_t i = 0; i < lenof(reg_paths); i++) { - char *filename = get_reg_sz_simple( - HKEY_LOCAL_MACHINE, reg_paths[i], NULL); - - if (filename) { - chm_path = filename; - chm_created_by_us = false; - return true; - } - } - - return false; -} - -void init_help(void) -{ - /* Just in case of multiple calls */ - static bool already_called = false; - if (already_called) - return; - already_called = true; - - /* - * Don't even try looking for the CHM file if we can't even find - * the HtmlHelp() API function. - */ - HINSTANCE dllHH = load_system32_dll("hhctrl.ocx"); - GET_WINDOWS_FUNCTION(dllHH, HtmlHelpA); - if (!p_HtmlHelpA) { - FreeLibrary(dllHH); - return; - } - - /* - * If there's a CHM file embedded in this executable, we should - * use that as the first choice. - */ - if (find_chm_resource()) - return; - - /* - * Otherwise, try looking for the CHM in the location that the - * installer marked in the registry. - */ - if (find_chm_from_installation()) - return; -} - -void shutdown_help(void) -{ - if (chm_path && chm_created_by_us) { - p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0); - DeleteFile(chm_path); - } - sfree(chm_path); - chm_path = NULL; - chm_created_by_us = false; -} - -bool has_help(void) -{ - return chm_path != NULL || chm_resource != NULL; -} - -void launch_help(HWND hwnd, const char *topic) -{ - if (!chm_path && chm_resource) { - /* - * If we've been called without already having a file name for - * the CHM file, that might be because we've located it in our - * resource section but not written it to a temp file yet. Do - * so now, on first use. - */ - load_chm_resource(); - } - - /* If we _still_ don't have a CHM pathname, we just can't display help. */ - if (!chm_path) - return; - - if (topic) { - char *fname = dupprintf( - "%s::/%s.html>main", chm_path, topic); - p_HtmlHelpA(hwnd, fname, HH_DISPLAY_TOPIC, 0); - sfree(fname); - } else { - p_HtmlHelpA(hwnd, chm_path, HH_DISPLAY_TOPIC, 0); - } - requested_help = true; -} - -void quit_help(HWND hwnd) -{ - if (requested_help) - p_HtmlHelpA(NULL, NULL, HH_CLOSE_ALL, 0); - if (chm_path && chm_created_by_us) - DeleteFile(chm_path); -} - -#endif /* NO_HTMLHELP */ diff --git a/WINDOWS/help.h b/WINDOWS/help.h deleted file mode 100644 index cfca4e1c2..000000000 --- a/WINDOWS/help.h +++ /dev/null @@ -1,220 +0,0 @@ -/* - * help.h - define Windows Help context names. - * Each definition is simply a string which matches up with the - * section names in the Halibut source, and is used for HTML Help. - */ - -/* Maximum length for WINHELP_CTX_foo strings */ -#define WINHELP_CTX_MAXLEN 80 - -/* These are used in the cross-platform configuration dialog code. */ - -typedef const char *HelpCtx; -#define NULL_HELPCTX NULL -#define HELPCTX(x) WINHELP_CTX_ ## x - -#define WINHELP_CTX_no_help NULL - -#define WINHELP_CTX_session_hostname "config-hostname" -#define WINHELP_CTX_session_saved "config-saving" -#define WINHELP_CTX_session_coe "config-closeonexit" -#define WINHELP_CTX_logging_main "config-logging" -#define WINHELP_CTX_logging_filename "config-logfilename" -#define WINHELP_CTX_logging_exists "config-logfileexists" -#define WINHELP_CTX_logging_flush "config-logflush" -#define WINHELP_CTX_logging_header "config-logheader" -#define WINHELP_CTX_logging_ssh_omit_password "config-logssh" -#define WINHELP_CTX_logging_ssh_omit_data "config-logssh" -#define WINHELP_CTX_keyboard_backspace "config-backspace" -#define WINHELP_CTX_keyboard_homeend "config-homeend" -#define WINHELP_CTX_keyboard_funkeys "config-funkeys" -#define WINHELP_CTX_keyboard_sharrow "config-sharrow" -#define WINHELP_CTX_keyboard_appkeypad "config-appkeypad" -#define WINHELP_CTX_keyboard_appcursor "config-appcursor" -#define WINHELP_CTX_keyboard_nethack "config-nethack" -#define WINHELP_CTX_keyboard_compose "config-compose" -#define WINHELP_CTX_keyboard_ctrlalt "config-ctrlalt" -#define WINHELP_CTX_features_application "config-features-application" -#define WINHELP_CTX_features_mouse "config-features-mouse" -#define WINHELP_CTX_features_resize "config-features-resize" -#define WINHELP_CTX_features_altscreen "config-features-altscreen" -#define WINHELP_CTX_features_retitle "config-features-retitle" -#define WINHELP_CTX_features_qtitle "config-features-qtitle" -#define WINHELP_CTX_features_dbackspace "config-features-dbackspace" -#define WINHELP_CTX_features_charset "config-features-charset" -#define WINHELP_CTX_features_clearscroll "config-features-clearscroll" -#define WINHELP_CTX_features_arabicshaping "config-features-shaping" -#define WINHELP_CTX_features_bidi "config-features-bidi" -#define WINHELP_CTX_terminal_autowrap "config-autowrap" -#define WINHELP_CTX_terminal_decom "config-decom" -#define WINHELP_CTX_terminal_lfhascr "config-crlf" -#define WINHELP_CTX_terminal_crhaslf "config-lfcr" -#define WINHELP_CTX_terminal_bce "config-erase" -#define WINHELP_CTX_terminal_blink "config-blink" -#define WINHELP_CTX_terminal_answerback "config-answerback" -#define WINHELP_CTX_terminal_localecho "config-localecho" -#define WINHELP_CTX_terminal_localedit "config-localedit" -#define WINHELP_CTX_terminal_printing "config-printing" -#define WINHELP_CTX_supdup_location "supdup-location" -#define WINHELP_CTX_supdup_ascii "supdup-ascii" -#define WINHELP_CTX_supdup_more "supdup-more" -#define WINHELP_CTX_supdup_scroll "supdup-scroll" -#define WINHELP_CTX_bell_style "config-bellstyle" -#define WINHELP_CTX_bell_taskbar "config-belltaskbar" -#define WINHELP_CTX_bell_overload "config-bellovl" -#define WINHELP_CTX_window_size "config-winsize" -#define WINHELP_CTX_window_resize "config-winsizelock" -#define WINHELP_CTX_window_scrollback "config-scrollback" -#define WINHELP_CTX_window_erased "config-erasetoscrollback" -#define WINHELP_CTX_behaviour_closewarn "config-warnonclose" -#define WINHELP_CTX_behaviour_altf4 "config-altf4" -#define WINHELP_CTX_behaviour_altspace "config-altspace" -#define WINHELP_CTX_behaviour_altonly "config-altonly" -#define WINHELP_CTX_behaviour_alwaysontop "config-alwaysontop" -#define WINHELP_CTX_behaviour_altenter "config-fullscreen" -#define WINHELP_CTX_appearance_cursor "config-cursor" -#define WINHELP_CTX_appearance_font "config-font" -#define WINHELP_CTX_appearance_title "config-title" -#define WINHELP_CTX_appearance_hidemouse "config-mouseptr" -#define WINHELP_CTX_appearance_border "config-winborder" -#define WINHELP_CTX_connection_termtype "config-termtype" -#define WINHELP_CTX_connection_termspeed "config-termspeed" -#define WINHELP_CTX_connection_username "config-username" -#define WINHELP_CTX_connection_username_from_env "config-username-from-env" -#define WINHELP_CTX_connection_keepalive "config-keepalive" -#define WINHELP_CTX_connection_nodelay "config-nodelay" -#define WINHELP_CTX_connection_ipversion "config-address-family" -#define WINHELP_CTX_connection_tcpkeepalive "config-tcp-keepalives" -#define WINHELP_CTX_connection_loghost "config-loghost" -#define WINHELP_CTX_proxy_type "config-proxy-type" -#define WINHELP_CTX_proxy_main "config-proxy" -#define WINHELP_CTX_proxy_exclude "config-proxy-exclude" -#define WINHELP_CTX_proxy_dns "config-proxy-dns" -#define WINHELP_CTX_proxy_auth "config-proxy-auth" -#define WINHELP_CTX_proxy_command "config-proxy-command" -#define WINHELP_CTX_proxy_logging "config-proxy-logging" -#define WINHELP_CTX_telnet_environ "config-environ" -#define WINHELP_CTX_telnet_oldenviron "config-oldenviron" -#define WINHELP_CTX_telnet_passive "config-ptelnet" -#define WINHELP_CTX_telnet_specialkeys "config-telnetkey" -#define WINHELP_CTX_telnet_newline "config-telnetnl" -#define WINHELP_CTX_rlogin_localuser "config-rlogin-localuser" -#define WINHELP_CTX_ssh_nopty "config-ssh-pty" -#define WINHELP_CTX_ssh_ttymodes "config-ttymodes" -#define WINHELP_CTX_ssh_noshell "config-ssh-noshell" -#define WINHELP_CTX_ssh_ciphers "config-ssh-encryption" -#define WINHELP_CTX_ssh_protocol "config-ssh-prot" -#define WINHELP_CTX_ssh_command "config-command" -#define WINHELP_CTX_ssh_compress "config-ssh-comp" -#define WINHELP_CTX_ssh_share "config-ssh-sharing" -#define WINHELP_CTX_ssh_kexlist "config-ssh-kex-order" -#define WINHELP_CTX_ssh_hklist "config-ssh-hostkey-order" -#define WINHELP_CTX_ssh_hk_known "config-ssh-prefer-known-hostkeys" -#define WINHELP_CTX_ssh_gssapi_kex_delegation "config-ssh-kex-gssapi-delegation" -#define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey" -#define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys" -#define WINHELP_CTX_ssh_kex_cert "config-ssh-kex-cert" -#define WINHELP_CTX_ssh_cert_valid_expr "config-ssh-cert-valid-expr" -#define WINHELP_CTX_ssh_cert_rsa_hash "config-ssh-cert-rsa-hash" -#define WINHELP_CTX_ssh_auth_bypass "config-ssh-noauth" -#define WINHELP_CTX_ssh_no_trivial_userauth "config-ssh-notrivialauth" -#define WINHELP_CTX_ssh_auth_banner "config-ssh-banner" -#define WINHELP_CTX_ssh_auth_privkey "config-ssh-privkey" -#define WINHELP_CTX_ssh_auth_plugin "config-ssh-authplugin" -#define WINHELP_CTX_ssh_auth_cert "config-ssh-cert" -#define WINHELP_CTX_ssh_auth_agentfwd "config-ssh-agentfwd" -#define WINHELP_CTX_ssh_auth_changeuser "config-ssh-changeuser" -#define WINHELP_CTX_ssh_auth_pageant "config-ssh-tryagent" -#define WINHELP_CTX_ssh_auth_tis "config-ssh-tis" -#define WINHELP_CTX_ssh_auth_ki "config-ssh-ki" -#define WINHELP_CTX_ssh_gssapi "config-ssh-auth-gssapi" -#define WINHELP_CTX_ssh_gssapi_delegation "config-ssh-auth-gssapi-delegation" -#define WINHELP_CTX_ssh_gssapi_libraries "config-ssh-auth-gssapi-libraries" -#define WINHELP_CTX_selection_buttons "config-mouse" -#define WINHELP_CTX_selection_shiftdrag "config-mouseshift" -#define WINHELP_CTX_selection_rect "config-rectselect" -#define WINHELP_CTX_selection_linedraw "config-linedrawpaste" -#define WINHELP_CTX_selection_autocopy "config-selection-autocopy" -#define WINHELP_CTX_selection_clipactions "config-selection-clipactions" -#define WINHELP_CTX_selection_pastectrl "config-paste-ctrl-char" -#define WINHELP_CTX_copy_charclasses "config-charclasses" -#define WINHELP_CTX_copy_rtf "config-rtfcopy" -#define WINHELP_CTX_colours_ansi "config-ansicolour" -#define WINHELP_CTX_colours_xterm256 "config-xtermcolour" -#define WINHELP_CTX_colours_truecolour "config-truecolour" -#define WINHELP_CTX_colours_bold "config-boldcolour" -#define WINHELP_CTX_colours_system "config-syscolour" -#define WINHELP_CTX_colours_logpal "config-logpalette" -#define WINHELP_CTX_colours_config "config-colourcfg" -#define WINHELP_CTX_translation_codepage "config-charset" -#define WINHELP_CTX_translation_cjk_ambig_wide "config-cjk-ambig-wide" -#define WINHELP_CTX_translation_cyrillic "config-cyr" -#define WINHELP_CTX_translation_linedraw "config-linedraw" -#define WINHELP_CTX_translation_utf8linedraw "config-utf8linedraw" -#define WINHELP_CTX_ssh_tunnels_x11 "config-ssh-x11" -#define WINHELP_CTX_ssh_tunnels_x11auth "config-ssh-x11auth" -#define WINHELP_CTX_ssh_tunnels_xauthority "config-ssh-xauthority" -#define WINHELP_CTX_ssh_tunnels_portfwd "config-ssh-portfwd" -#define WINHELP_CTX_ssh_tunnels_portfwd_localhost "config-ssh-portfwd-localhost" -#define WINHELP_CTX_ssh_tunnels_portfwd_ipversion "config-ssh-portfwd-address-family" -#define WINHELP_CTX_ssh_bugs_ignore1 "config-ssh-bug-ignore1" -#define WINHELP_CTX_ssh_bugs_plainpw1 "config-ssh-bug-plainpw1" -#define WINHELP_CTX_ssh_bugs_rsa1 "config-ssh-bug-rsa1" -#define WINHELP_CTX_ssh_bugs_ignore2 "config-ssh-bug-ignore2" -#define WINHELP_CTX_ssh_bugs_hmac2 "config-ssh-bug-hmac2" -#define WINHELP_CTX_ssh_bugs_derivekey2 "config-ssh-bug-derivekey2" -#define WINHELP_CTX_ssh_bugs_rsapad2 "config-ssh-bug-sig" -#define WINHELP_CTX_ssh_bugs_pksessid2 "config-ssh-bug-pksessid2" -#define WINHELP_CTX_ssh_bugs_rekey2 "config-ssh-bug-rekey" -#define WINHELP_CTX_ssh_bugs_maxpkt2 "config-ssh-bug-maxpkt2" -#define WINHELP_CTX_ssh_bugs_rsa_sha2_cert_userauth "config-ssh-bug-rsa-sha2-cert-userauth" -#define WINHELP_CTX_ssh_bugs_winadj "config-ssh-bug-winadj" -#define WINHELP_CTX_ssh_bugs_chanreq "config-ssh-bug-chanreq" -#define WINHELP_CTX_ssh_bugs_oldgex2 "config-ssh-bug-oldgex2" -#define WINHELP_CTX_ssh_bugs_dropstart "config-ssh-bug-dropstart" -#define WINHELP_CTX_ssh_bugs_filter_kexinit "config-ssh-bug-filter-kexinit" -#define WINHELP_CTX_serial_line "config-serial-line" -#define WINHELP_CTX_serial_speed "config-serial-speed" -#define WINHELP_CTX_serial_databits "config-serial-databits" -#define WINHELP_CTX_serial_stopbits "config-serial-stopbits" -#define WINHELP_CTX_serial_parity "config-serial-parity" -#define WINHELP_CTX_serial_flow "config-serial-flow" - -#define WINHELP_CTX_pageant_general "pageant" -#define WINHELP_CTX_pageant_keylist "pageant-mainwin-keylist" -#define WINHELP_CTX_pageant_addkey "pageant-mainwin-addkey" -#define WINHELP_CTX_pageant_remkey "pageant-mainwin-remkey" -#define WINHELP_CTX_pageant_deferred "pageant-deferred-decryption" -#define WINHELP_CTX_pgpfingerprints "pgpkeys" -#define WINHELP_CTX_puttygen_general "pubkey-puttygen" -#define WINHELP_CTX_puttygen_keytype "puttygen-keytype" -#define WINHELP_CTX_puttygen_bits "puttygen-strength" -#define WINHELP_CTX_puttygen_generate "puttygen-generate" -#define WINHELP_CTX_puttygen_fingerprint "puttygen-fingerprint" -#define WINHELP_CTX_puttygen_comment "puttygen-comment" -#define WINHELP_CTX_puttygen_passphrase "puttygen-passphrase" -#define WINHELP_CTX_puttygen_savepriv "puttygen-savepriv" -#define WINHELP_CTX_puttygen_savepub "puttygen-savepub" -#define WINHELP_CTX_puttygen_pastekey "puttygen-pastekey" -#define WINHELP_CTX_puttygen_load "puttygen-load" -#define WINHELP_CTX_puttygen_conversions "puttygen-conversions" -#define WINHELP_CTX_puttygen_ppkver "puttygen-save-ppk-version" -#define WINHELP_CTX_puttygen_kdfparam "puttygen-save-passphrase-hashing" -#define WINHELP_CTX_errors_cert_mismatch "errors-cert-mismatch" - -/* These are used in Windows-specific bits of the frontend. - * We (ab)use "help context identifiers" (dwContextId) to identify them. */ - -#define HELPCTXID(x) WINHELP_CTXID_ ## x - -#define WINHELP_CTXID_no_help 0 -#define WINHELP_CTX_errors_hostkey_absent "errors-hostkey-absent" -#define WINHELP_CTXID_errors_hostkey_absent 1 -#define WINHELP_CTX_errors_hostkey_changed "errors-hostkey-wrong" -#define WINHELP_CTXID_errors_hostkey_changed 2 -#define WINHELP_CTX_errors_cantloadkey "errors-cant-load-key" -#define WINHELP_CTXID_errors_cantloadkey 3 -#define WINHELP_CTX_option_cleanup "using-cleanup" -#define WINHELP_CTXID_option_cleanup 4 -#define WINHELP_CTX_pgp_fingerprints "pgpkeys" -#define WINHELP_CTXID_pgp_fingerprints 5 diff --git a/WINDOWS/help.rc2 b/WINDOWS/help.rc2 deleted file mode 100644 index 16bb41f05..000000000 --- a/WINDOWS/help.rc2 +++ /dev/null @@ -1,8 +0,0 @@ -#include "putty-rc.h" - -#ifdef EMBEDDED_CHM_FILE -ID_CUSTOM_CHMFILE TYPE_CUSTOM_CHMFILE EMBEDDED_CHM_FILE -#define HELPVER " (with embedded help)" -#else -#define HELPVER " (without embedded help)" -#endif diff --git a/WINDOWS/installer.wxs b/WINDOWS/installer.wxs deleted file mode 100644 index 25e8008a9..000000000 --- a/WINDOWS/installer.wxs +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - "1"]]> - - - 1 - - NOT Installed - Installed - - 1 - 1 - - NOT WIXUI_DONTVALIDATEPATH - "1"]]> - WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1" - - 1 - - 1 - 1 - - Installed - NOT Installed - 1 - - NOT Installed - Installed AND NOT PATCH - Installed AND PATCH - - 1 - - 1 - 1 - 1 - - - WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WINDOWS/jump-list.c b/WINDOWS/jump-list.c deleted file mode 100644 index 47c41a042..000000000 --- a/WINDOWS/jump-list.c +++ /dev/null @@ -1,746 +0,0 @@ -/* - * jump-list.c: support for Windows 7 jump lists. - * - * The Windows 7 jumplist is a customizable list defined by the - * application. It is persistent across application restarts: the OS - * maintains the list when the app is not running. The list is shown - * when the user right-clicks on the taskbar button of a running app - * or a pinned non-running application. We use the jumplist to - * maintain a list of recently started saved sessions, started either - * by doubleclicking on a saved session, or with the command line - * "-load" parameter. - * - * Since the jumplist is write-only: it can only be replaced and the - * current list cannot be read, we must maintain the contents of the - * list persistently in the registry. The file winstore.h contains - * functions to directly manipulate these registry entries. This file - * contains higher level functions to manipulate the jumplist. - */ - -#include - -#include "putty.h" -#include "storage.h" - -#define MAX_JUMPLIST_ITEMS 30 /* PuTTY will never show more items in - * the jumplist than this, regardless of - * user preferences. */ - -/* - * COM structures and functions. - */ -#ifndef PROPERTYKEY_DEFINED -#define PROPERTYKEY_DEFINED -typedef struct _tagpropertykey { - GUID fmtid; - DWORD pid; -} PROPERTYKEY; -#endif -#ifndef _REFPROPVARIANT_DEFINED -#define _REFPROPVARIANT_DEFINED -typedef PROPVARIANT *REFPROPVARIANT; -#endif -/* MinGW doesn't define this yet: */ -#if !defined _PROPVARIANTINIT_DEFINED_ && !defined _PROPVARIANT_INIT_DEFINED_ -#define _PROPVARIANTINIT_DEFINED_ -#define PropVariantInit(pvar) memset((pvar),0,sizeof(PROPVARIANT)) -#endif - -#define IID_IShellLink IID_IShellLinkA - -typedef struct ICustomDestinationListVtbl { - HRESULT ( __stdcall *QueryInterface ) ( - /* [in] ICustomDestinationList*/ void *This, - /* [in] */ const GUID * const riid, - /* [out] */ void **ppvObject); - - ULONG ( __stdcall *AddRef )( - /* [in] ICustomDestinationList*/ void *This); - - ULONG ( __stdcall *Release )( - /* [in] ICustomDestinationList*/ void *This); - - HRESULT ( __stdcall *SetAppID )( - /* [in] ICustomDestinationList*/ void *This, - /* [string][in] */ LPCWSTR pszAppID); - - HRESULT ( __stdcall *BeginList )( - /* [in] ICustomDestinationList*/ void *This, - /* [out] */ UINT *pcMinSlots, - /* [in] */ const GUID * const riid, - /* [out] */ void **ppv); - - HRESULT ( __stdcall *AppendCategory )( - /* [in] ICustomDestinationList*/ void *This, - /* [string][in] */ LPCWSTR pszCategory, - /* [in] IObjectArray*/ void *poa); - - HRESULT ( __stdcall *AppendKnownCategory )( - /* [in] ICustomDestinationList*/ void *This, - /* [in] KNOWNDESTCATEGORY*/ int category); - - HRESULT ( __stdcall *AddUserTasks )( - /* [in] ICustomDestinationList*/ void *This, - /* [in] IObjectArray*/ void *poa); - - HRESULT ( __stdcall *CommitList )( - /* [in] ICustomDestinationList*/ void *This); - - HRESULT ( __stdcall *GetRemovedDestinations )( - /* [in] ICustomDestinationList*/ void *This, - /* [in] */ const IID * const riid, - /* [out] */ void **ppv); - - HRESULT ( __stdcall *DeleteList )( - /* [in] ICustomDestinationList*/ void *This, - /* [string][unique][in] */ LPCWSTR pszAppID); - - HRESULT ( __stdcall *AbortList )( - /* [in] ICustomDestinationList*/ void *This); - -} ICustomDestinationListVtbl; - -typedef struct ICustomDestinationList -{ - ICustomDestinationListVtbl *lpVtbl; -} ICustomDestinationList; - -typedef struct IObjectArrayVtbl -{ - HRESULT ( __stdcall *QueryInterface )( - /* [in] IObjectArray*/ void *This, - /* [in] */ const GUID * const riid, - /* [out] */ void **ppvObject); - - ULONG ( __stdcall *AddRef )( - /* [in] IObjectArray*/ void *This); - - ULONG ( __stdcall *Release )( - /* [in] IObjectArray*/ void *This); - - HRESULT ( __stdcall *GetCount )( - /* [in] IObjectArray*/ void *This, - /* [out] */ UINT *pcObjects); - - HRESULT ( __stdcall *GetAt )( - /* [in] IObjectArray*/ void *This, - /* [in] */ UINT uiIndex, - /* [in] */ const GUID * const riid, - /* [out] */ void **ppv); - -} IObjectArrayVtbl; - -typedef struct IObjectArray -{ - IObjectArrayVtbl *lpVtbl; -} IObjectArray; - -typedef struct IShellLinkVtbl -{ - HRESULT ( __stdcall *QueryInterface )( - /* [in] IShellLink*/ void *This, - /* [in] */ const GUID * const riid, - /* [out] */ void **ppvObject); - - ULONG ( __stdcall *AddRef )( - /* [in] IShellLink*/ void *This); - - ULONG ( __stdcall *Release )( - /* [in] IShellLink*/ void *This); - - HRESULT ( __stdcall *GetPath )( - /* [in] IShellLink*/ void *This, - /* [string][out] */ LPSTR pszFile, - /* [in] */ int cch, - /* [unique][out][in] */ WIN32_FIND_DATAA *pfd, - /* [in] */ DWORD fFlags); - - HRESULT ( __stdcall *GetIDList )( - /* [in] IShellLink*/ void *This, - /* [out] LPITEMIDLIST*/ void **ppidl); - - HRESULT ( __stdcall *SetIDList )( - /* [in] IShellLink*/ void *This, - /* [in] LPITEMIDLIST*/ void *pidl); - - HRESULT ( __stdcall *GetDescription )( - /* [in] IShellLink*/ void *This, - /* [string][out] */ LPSTR pszName, - /* [in] */ int cch); - - HRESULT ( __stdcall *SetDescription )( - /* [in] IShellLink*/ void *This, - /* [string][in] */ LPCSTR pszName); - - HRESULT ( __stdcall *GetWorkingDirectory )( - /* [in] IShellLink*/ void *This, - /* [string][out] */ LPSTR pszDir, - /* [in] */ int cch); - - HRESULT ( __stdcall *SetWorkingDirectory )( - /* [in] IShellLink*/ void *This, - /* [string][in] */ LPCSTR pszDir); - - HRESULT ( __stdcall *GetArguments )( - /* [in] IShellLink*/ void *This, - /* [string][out] */ LPSTR pszArgs, - /* [in] */ int cch); - - HRESULT ( __stdcall *SetArguments )( - /* [in] IShellLink*/ void *This, - /* [string][in] */ LPCSTR pszArgs); - - HRESULT ( __stdcall *GetHotkey )( - /* [in] IShellLink*/ void *This, - /* [out] */ WORD *pwHotkey); - - HRESULT ( __stdcall *SetHotkey )( - /* [in] IShellLink*/ void *This, - /* [in] */ WORD wHotkey); - - HRESULT ( __stdcall *GetShowCmd )( - /* [in] IShellLink*/ void *This, - /* [out] */ int *piShowCmd); - - HRESULT ( __stdcall *SetShowCmd )( - /* [in] IShellLink*/ void *This, - /* [in] */ int iShowCmd); - - HRESULT ( __stdcall *GetIconLocation )( - /* [in] IShellLink*/ void *This, - /* [string][out] */ LPSTR pszIconPath, - /* [in] */ int cch, - /* [out] */ int *piIcon); - - HRESULT ( __stdcall *SetIconLocation )( - /* [in] IShellLink*/ void *This, - /* [string][in] */ LPCSTR pszIconPath, - /* [in] */ int iIcon); - - HRESULT ( __stdcall *SetRelativePath )( - /* [in] IShellLink*/ void *This, - /* [string][in] */ LPCSTR pszPathRel, - /* [in] */ DWORD dwReserved); - - HRESULT ( __stdcall *Resolve )( - /* [in] IShellLink*/ void *This, - /* [unique][in] */ HWND hwnd, - /* [in] */ DWORD fFlags); - - HRESULT ( __stdcall *SetPath )( - /* [in] IShellLink*/ void *This, - /* [string][in] */ LPCSTR pszFile); - -} IShellLinkVtbl; - -typedef struct IShellLink -{ - IShellLinkVtbl *lpVtbl; -} IShellLink; - -typedef struct IObjectCollectionVtbl -{ - HRESULT ( __stdcall *QueryInterface )( - /* [in] IShellLink*/ void *This, - /* [in] */ const GUID * const riid, - /* [out] */ void **ppvObject); - - ULONG ( __stdcall *AddRef )( - /* [in] IShellLink*/ void *This); - - ULONG ( __stdcall *Release )( - /* [in] IShellLink*/ void *This); - - HRESULT ( __stdcall *GetCount )( - /* [in] IShellLink*/ void *This, - /* [out] */ UINT *pcObjects); - - HRESULT ( __stdcall *GetAt )( - /* [in] IShellLink*/ void *This, - /* [in] */ UINT uiIndex, - /* [in] */ const GUID * const riid, - /* [iid_is][out] */ void **ppv); - - HRESULT ( __stdcall *AddObject )( - /* [in] IShellLink*/ void *This, - /* [in] */ void *punk); - - HRESULT ( __stdcall *AddFromArray )( - /* [in] IShellLink*/ void *This, - /* [in] */ IObjectArray *poaSource); - - HRESULT ( __stdcall *RemoveObjectAt )( - /* [in] IShellLink*/ void *This, - /* [in] */ UINT uiIndex); - - HRESULT ( __stdcall *Clear )( - /* [in] IShellLink*/ void *This); - -} IObjectCollectionVtbl; - -typedef struct IObjectCollection -{ - IObjectCollectionVtbl *lpVtbl; -} IObjectCollection; - -typedef struct IPropertyStoreVtbl -{ - HRESULT ( __stdcall *QueryInterface )( - /* [in] IPropertyStore*/ void *This, - /* [in] */ const GUID * const riid, - /* [iid_is][out] */ void **ppvObject); - - ULONG ( __stdcall *AddRef )( - /* [in] IPropertyStore*/ void *This); - - ULONG ( __stdcall *Release )( - /* [in] IPropertyStore*/ void *This); - - HRESULT ( __stdcall *GetCount )( - /* [in] IPropertyStore*/ void *This, - /* [out] */ DWORD *cProps); - - HRESULT ( __stdcall *GetAt )( - /* [in] IPropertyStore*/ void *This, - /* [in] */ DWORD iProp, - /* [out] */ PROPERTYKEY *pkey); - - HRESULT ( __stdcall *GetValue )( - /* [in] IPropertyStore*/ void *This, - /* [in] */ const PROPERTYKEY * const key, - /* [out] */ PROPVARIANT *pv); - - HRESULT ( __stdcall *SetValue )( - /* [in] IPropertyStore*/ void *This, - /* [in] */ const PROPERTYKEY * const key, - /* [in] */ REFPROPVARIANT propvar); - - HRESULT ( __stdcall *Commit )( - /* [in] IPropertyStore*/ void *This); -} IPropertyStoreVtbl; - -typedef struct IPropertyStore -{ - IPropertyStoreVtbl *lpVtbl; -} IPropertyStore; - -static const CLSID CLSID_DestinationList = { - 0x77f10cf0, 0x3db5, 0x4966, {0xb5,0x20,0xb7,0xc5,0x4f,0xd3,0x5e,0xd6} -}; -static const CLSID CLSID_ShellLink = { - 0x00021401, 0x0000, 0x0000, {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46} -}; -static const CLSID CLSID_EnumerableObjectCollection = { - 0x2d3468c1, 0x36a7, 0x43b6, {0xac,0x24,0xd3,0xf0,0x2f,0xd9,0x60,0x7a} -}; -static const IID IID_IObjectCollection = { - 0x5632b1a4, 0xe38a, 0x400a, {0x92,0x8a,0xd4,0xcd,0x63,0x23,0x02,0x95} -}; -static const IID IID_IShellLink = { - 0x000214ee, 0x0000, 0x0000, {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46} -}; -static const IID IID_ICustomDestinationList = { - 0x6332debf, 0x87b5, 0x4670, {0x90,0xc0,0x5e,0x57,0xb4,0x08,0xa4,0x9e} -}; -static const IID IID_IObjectArray = { - 0x92ca9dcd, 0x5622, 0x4bba, {0xa8,0x05,0x5e,0x9f,0x54,0x1b,0xd8,0xc9} -}; -static const IID IID_IPropertyStore = { - 0x886d8eeb, 0x8cf2, 0x4446, {0x8d,0x02,0xcd,0xba,0x1d,0xbd,0xcf,0x99} -}; -static const PROPERTYKEY PKEY_Title = { - {0xf29f85e0, 0x4ff9, 0x1068, {0xab,0x91,0x08,0x00,0x2b,0x27,0xb3,0xd9}}, - 0x00000002 -}; - -/* Type-checking macro to provide arguments for CoCreateInstance() - * etc, ensuring that 'obj' really is a 'type **'. */ -#define typecheck(checkexpr, result) \ - (sizeof(checkexpr) ? (result) : (result)) -#define COMPTR(type, obj) &IID_##type, \ - typecheck((obj)-(type **)(obj), (void **)(void *)(obj)) - -static char putty_path[2048]; - -/* - * Function to make an IShellLink describing a particular PuTTY - * command. If 'appname' is null, the command run will be the one - * returned by GetModuleFileName, i.e. our own executable; if it's - * non-null then it will be assumed to be a filename in the same - * directory as our own executable, and the return value will be NULL - * if that file doesn't exist. - * - * If 'sessionname' is null then no command line will be passed to the - * program. If it's non-null, the command line will be that text - * prefixed with an @ (to load a PuTTY saved session). - * - * Hence, you can launch a saved session using make_shell_link(NULL, - * sessionname), and launch another app using e.g. - * make_shell_link("puttygen.exe", NULL). - */ -static IShellLink *make_shell_link(const char *appname, - const char *sessionname) -{ - IShellLink *ret; - char *app_path, *param_string, *desc_string; - IPropertyStore *pPS; - PROPVARIANT pv; - - /* Retrieve path to executable. */ - if (!putty_path[0]) - GetModuleFileName(NULL, putty_path, sizeof(putty_path) - 1); - if (appname) { - char *p, *q = putty_path; - FILE *fp; - - if ((p = strrchr(q, '\\')) != NULL) q = p+1; - if ((p = strrchr(q, ':')) != NULL) q = p+1; - app_path = dupprintf("%.*s%s", (int)(q - putty_path), putty_path, - appname); - if ((fp = fopen(app_path, "r")) == NULL) { - sfree(app_path); - return NULL; - } - fclose(fp); - } else { - app_path = dupstr(putty_path); - } - - /* Check if this is a valid session, otherwise don't add. */ - if (sessionname) { - settings_r *psettings_tmp = open_settings_r(sessionname); - if (!psettings_tmp) { - sfree(app_path); - return NULL; - } - close_settings_r(psettings_tmp); - } - - /* Create the new item. */ - if (!SUCCEEDED(CoCreateInstance(&CLSID_ShellLink, NULL, - CLSCTX_INPROC_SERVER, - COMPTR(IShellLink, &ret)))) { - sfree(app_path); - return NULL; - } - - /* Set path, parameters, icon and description. */ - ret->lpVtbl->SetPath(ret, app_path); - - if (sessionname) { - /* The leading space is reported to work around a Windows 10 - * behaviour change in which an argument string starting with - * '@' causes the SetArguments method to silently do the wrong - * thing. */ - param_string = dupcat(" @", sessionname); - } else { - param_string = dupstr(""); - } - ret->lpVtbl->SetArguments(ret, param_string); - sfree(param_string); - - if (sessionname) { - desc_string = dupcat("Connect to PuTTY session '", sessionname, "'"); - } else { - assert(appname); - desc_string = dupprintf("Run %.*s", - (int)strcspn(appname, "."), appname); - } - ret->lpVtbl->SetDescription(ret, desc_string); - sfree(desc_string); - - ret->lpVtbl->SetIconLocation(ret, app_path, 0); - - /* To set the link title, we require the property store of the link. */ - if (SUCCEEDED(ret->lpVtbl->QueryInterface(ret, - COMPTR(IPropertyStore, &pPS)))) { - PropVariantInit(&pv); - pv.vt = VT_LPSTR; - if (sessionname) { - pv.pszVal = dupstr(sessionname); - } else { - assert(appname); - pv.pszVal = dupprintf("Run %.*s", - (int)strcspn(appname, "."), appname); - } - pPS->lpVtbl->SetValue(pPS, &PKEY_Title, &pv); - sfree(pv.pszVal); - pPS->lpVtbl->Commit(pPS); - pPS->lpVtbl->Release(pPS); - } - - sfree(app_path); - - return ret; -} - -/* Updates jumplist from registry. */ -static void update_jumplist_from_registry(void) -{ - const char *piterator; - UINT num_items; - int jumplist_counter; - UINT nremoved; - - /* Variables used by the cleanup code must be initialised to NULL, - * so that we don't try to free or release them if they were never - * set up. */ - ICustomDestinationList *pCDL = NULL; - char *pjumplist_reg_entries = NULL; - IObjectCollection *collection = NULL; - IObjectArray *array = NULL; - IShellLink *link = NULL; - IObjectArray *pRemoved = NULL; - bool need_abort = false; - - /* - * Create an ICustomDestinationList: the top-level object which - * deals with jump list management. - */ - if (!SUCCEEDED(CoCreateInstance(&CLSID_DestinationList, NULL, - CLSCTX_INPROC_SERVER, - COMPTR(ICustomDestinationList, &pCDL)))) - goto cleanup; - - /* - * Call its BeginList method to start compiling a list. This gives - * us back 'num_items' (a hint derived from systemwide - * configuration about how many things to put on the list) and - * 'pRemoved' (user configuration about things to leave off the - * list). - */ - if (!SUCCEEDED(pCDL->lpVtbl->BeginList(pCDL, &num_items, - COMPTR(IObjectArray, &pRemoved)))) - goto cleanup; - need_abort = true; - if (!SUCCEEDED(pRemoved->lpVtbl->GetCount(pRemoved, &nremoved))) - nremoved = 0; - - /* - * Create an object collection to form the 'Recent Sessions' - * category on the jump list. - */ - if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection, - NULL, CLSCTX_INPROC_SERVER, - COMPTR(IObjectCollection, &collection)))) - goto cleanup; - - /* - * Go through the jump list entries from the registry and add each - * one to the collection. - */ - pjumplist_reg_entries = get_jumplist_registry_entries(); - piterator = pjumplist_reg_entries; - jumplist_counter = 0; - while (*piterator != '\0' && - (jumplist_counter < min(MAX_JUMPLIST_ITEMS, (int) num_items))) { - link = make_shell_link(NULL, piterator); - if (link) { - UINT i; - bool found; - - /* - * Check that the link isn't in the user-removed list. - */ - for (i = 0, found = false; i < nremoved && !found; i++) { - IShellLink *rlink; - if (SUCCEEDED(pRemoved->lpVtbl->GetAt( - pRemoved, i, COMPTR(IShellLink, &rlink)))) { - char desc1[2048], desc2[2048]; - if (SUCCEEDED(link->lpVtbl->GetDescription( - link, desc1, sizeof(desc1)-1)) && - SUCCEEDED(rlink->lpVtbl->GetDescription( - rlink, desc2, sizeof(desc2)-1)) && - !strcmp(desc1, desc2)) { - found = true; - } - rlink->lpVtbl->Release(rlink); - } - } - - if (!found) { - collection->lpVtbl->AddObject(collection, link); - jumplist_counter++; - } - - link->lpVtbl->Release(link); - link = NULL; - } - piterator += strlen(piterator) + 1; - } - sfree(pjumplist_reg_entries); - pjumplist_reg_entries = NULL; - - /* - * Get the array form of the collection we've just constructed, - * and put it in the jump list. - */ - if (!SUCCEEDED(collection->lpVtbl->QueryInterface( - collection, COMPTR(IObjectArray, &array)))) - goto cleanup; - - pCDL->lpVtbl->AppendCategory(pCDL, L"Recent Sessions", array); - - /* - * Create an object collection to form the 'Tasks' category on the - * jump list. - */ - if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection, - NULL, CLSCTX_INPROC_SERVER, - COMPTR(IObjectCollection, &collection)))) - goto cleanup; - - /* - * Add task entries for PuTTYgen and Pageant. - */ - piterator = "Pageant.exe\0PuTTYgen.exe\0\0"; - while (*piterator != '\0') { - link = make_shell_link(piterator, NULL); - if (link) { - collection->lpVtbl->AddObject(collection, link); - link->lpVtbl->Release(link); - link = NULL; - } - piterator += strlen(piterator) + 1; - } - - /* - * Get the array form of the collection we've just constructed, - * and put it in the jump list. - */ - if (!SUCCEEDED(collection->lpVtbl->QueryInterface( - collection, COMPTR(IObjectArray, &array)))) - goto cleanup; - - pCDL->lpVtbl->AddUserTasks(pCDL, array); - - /* - * Now we can clean up the array and collection variables, so as - * to be able to reuse them. - */ - array->lpVtbl->Release(array); - array = NULL; - collection->lpVtbl->Release(collection); - collection = NULL; - - /* - * Create another object collection to form the user tasks - * category. - */ - if (!SUCCEEDED(CoCreateInstance(&CLSID_EnumerableObjectCollection, - NULL, CLSCTX_INPROC_SERVER, - COMPTR(IObjectCollection, &collection)))) - goto cleanup; - - /* - * Get the array form of the collection we've just constructed, - * and put it in the jump list. - */ - if (!SUCCEEDED(collection->lpVtbl->QueryInterface( - collection, COMPTR(IObjectArray, &array)))) - goto cleanup; - - pCDL->lpVtbl->AddUserTasks(pCDL, array); - - /* - * Now we can clean up the array and collection variables, so as - * to be able to reuse them. - */ - array->lpVtbl->Release(array); - array = NULL; - collection->lpVtbl->Release(collection); - collection = NULL; - - /* - * Commit the jump list. - */ - pCDL->lpVtbl->CommitList(pCDL); - need_abort = false; - - /* - * Clean up. - */ - cleanup: - if (pRemoved) pRemoved->lpVtbl->Release(pRemoved); - if (pCDL && need_abort) pCDL->lpVtbl->AbortList(pCDL); - if (pCDL) pCDL->lpVtbl->Release(pCDL); - if (collection) collection->lpVtbl->Release(collection); - if (array) array->lpVtbl->Release(array); - if (link) link->lpVtbl->Release(link); - sfree(pjumplist_reg_entries); -} - -/* Clears the entire jumplist. */ -void clear_jumplist(void) -{ - ICustomDestinationList *pCDL; - - if (CoCreateInstance(&CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER, - COMPTR(ICustomDestinationList, &pCDL)) == S_OK) { - pCDL->lpVtbl->DeleteList(pCDL, NULL); - pCDL->lpVtbl->Release(pCDL); - } - -} - -/* Adds a saved session to the Windows 7 jumplist. */ -void add_session_to_jumplist(const char * const sessionname) -{ - if ((osMajorVersion < 6) || (osMajorVersion == 6 && osMinorVersion < 1)) - return; /* do nothing on pre-Win7 systems */ - - if (add_to_jumplist_registry(sessionname) == JUMPLISTREG_OK) { - update_jumplist_from_registry(); - } else { - /* Make sure we don't leave the jumplist dangling. */ - clear_jumplist(); - } -} - -/* Removes a saved session from the Windows jumplist. */ -void remove_session_from_jumplist(const char * const sessionname) -{ - if ((osMajorVersion < 6) || (osMajorVersion == 6 && osMinorVersion < 1)) - return; /* do nothing on pre-Win7 systems */ - - if (remove_from_jumplist_registry(sessionname) == JUMPLISTREG_OK) { - update_jumplist_from_registry(); - } else { - /* Make sure we don't leave the jumplist dangling. */ - clear_jumplist(); - } -} - -/* Set Explicit App User Model Id to fix removable media error with - jump lists */ - -bool set_explicit_app_user_model_id(void) -{ - DECL_WINDOWS_FUNCTION( - static, HRESULT, SetCurrentProcessExplicitAppUserModelID, (PCWSTR)); - - static HMODULE shell32_module = 0; - - if (!shell32_module) { - shell32_module = load_system32_dll("Shell32.dll"); - /* - * We can't typecheck this function here, because it's defined - * in , which we're not including due to clashes - * with all the manual-COM machinery above. - */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK( - shell32_module, SetCurrentProcessExplicitAppUserModelID); - } - - if (p_SetCurrentProcessExplicitAppUserModelID) { - const wchar_t *id = get_app_user_model_id(); - if (p_SetCurrentProcessExplicitAppUserModelID(id) == S_OK) { - return true; - } - return false; - } - /* Function doesn't exist, which is ok for Pre-7 systems */ - - return true; - -} diff --git a/WINDOWS/local-proxy.c b/WINDOWS/local-proxy.c deleted file mode 100644 index 55e9cbf3b..000000000 --- a/WINDOWS/local-proxy.c +++ /dev/null @@ -1,118 +0,0 @@ -/* - * local-proxy.c: Windows implementation of platform_new_connection(), - * supporting an OpenSSH-like proxy command via the handle-io.c - * mechanism. - */ - -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "proxy/proxy.h" - -char *platform_setup_local_proxy(Socket *socket, const 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; - - /* - * 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)) { - return dupprintf("Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); - } - - if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) { - CloseHandle(us_from_cmd); - CloseHandle(cmd_to_us); - return dupprintf("Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); - } - - if (!CreatePipe(&us_from_cmd_err, &cmd_err_to_us, &sa, 0)) { - CloseHandle(us_from_cmd); - CloseHandle(cmd_to_us); - CloseHandle(us_to_cmd); - CloseHandle(cmd_from_us); - return dupprintf("Unable to create pipes for proxy command: %s", - win_strerror(GetLastError())); - } - - 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; - char *cmd_mutable = dupstr(cmd); /* CreateProcess needs non-const char * */ - CreateProcess(NULL, cmd_mutable, NULL, NULL, true, - CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS, - NULL, NULL, &si, &pi); - sfree(cmd_mutable); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - - CloseHandle(cmd_from_us); - CloseHandle(cmd_to_us); - - if (cmd_err_to_us != NULL) - CloseHandle(cmd_err_to_us); - - setup_handle_socket(socket, us_to_cmd, us_from_cmd, us_from_cmd_err, - false); - - return NULL; -} - -Socket *platform_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, Interactor *itr) -{ - if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) - return NULL; - - DeferredSocketOpener *opener = local_proxy_opener( - addr, port, plug, conf, itr); - Socket *socket = make_deferred_handle_socket(opener, addr, port, plug); - local_proxy_opener_set_socket(opener, socket); - return socket; -} - -Socket *platform_start_subprocess(const char *cmd, Plug *plug, - const char *prefix) -{ - Socket *socket = make_deferred_handle_socket( - null_deferred_socket_opener(), - sk_nonamelookup(""), 0, plug); - char *err = platform_setup_local_proxy(socket, cmd); - handle_socket_set_psb_prefix(socket, prefix); - - if (err) { - sk_close(socket); - socket = new_error_socket_fmt(plug, "%s", err); - sfree(err); - } - - return socket; -} diff --git a/WINDOWS/make.cmd b/WINDOWS/make.cmd deleted file mode 100644 index cdd1261fb..000000000 --- a/WINDOWS/make.cmd +++ /dev/null @@ -1,5 +0,0 @@ -call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools\VsDevCmd.bat" -REM powershell -Command "& {gci -Path 'C:\Program File*\' -Filter rc.exe -Recurse}" -set PATH=%PATH%;C:\Program Files (x86)\Windows Kits\10\bin\10.0.15063.0\x86 -nmake -f MAKEFILE.NG clean -nmake -f MAKEFILE.NG VER="/DNG_VER_MAJOR=0 /DNG_VER_MINOR=0 /DNG_VER_BUILD=0 /DNG_VER_REVISION=0" \ No newline at end of file diff --git a/WINDOWS/make17.cmd b/WINDOWS/make17.cmd deleted file mode 100644 index 3d0393860..000000000 --- a/WINDOWS/make17.cmd +++ /dev/null @@ -1,7 +0,0 @@ -if exist "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" ( - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" - ) else ( - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\Tools\VsDevCmd.bat" - ) -nmake -f MAKEFILE.NG clean -nmake -f MAKEFILE.NG VER="/DNG_VER_MAJOR=0 /DNG_VER_MINOR=0 /DNG_VER_BUILD=0 /DNG_VER_REVISION=0" diff --git a/WINDOWS/make19.cmd b/WINDOWS/make19.cmd deleted file mode 100644 index c5b8be168..000000000 --- a/WINDOWS/make19.cmd +++ /dev/null @@ -1,7 +0,0 @@ -if exist "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\VsDevCmd.bat" ( - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\VsDevCmd.bat" - ) else ( - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat" - ) -nmake -f MAKEFILE.NG clean -nmake -f MAKEFILE.NG VER="/DNG_VER_MAJOR=0 /DNG_VER_MINOR=0 /DNG_VER_BUILD=0 /DNG_VER_REVISION=0" diff --git a/WINDOWS/make_install_images.sh b/WINDOWS/make_install_images.sh deleted file mode 100644 index 49041e24f..000000000 --- a/WINDOWS/make_install_images.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -# Script to make the bitmap files that go into the PuTTY MSI installer. - -set -e - -# For convenience, allow this script to be run from the Windows -# subdirectory as well as the top level of the source tree. -if test -f installer.wxs -a ! -f putty.h -a -f ../putty.h; then - cd .. -fi - -convert -size 164x312 'gradient:blue-white' -distort SRT -90 -swirl 180 \ - \( icons/putty-48.png -geometry +28+24 \) -composite \ - \( icons/pscp-48.png -geometry +88+96 \) -composite \ - \( icons/puttygen-48.png -geometry +28+168 \) -composite \ - \( icons/pageant-48.png -geometry +88+240 \) -composite \ - windows/msidialog.bmp - -convert -size 493x58 canvas:white \ - \( icons/putty-48.png -geometry +440+5 \) -composite \ - windows/msibanner.bmp diff --git a/WINDOWS/msifixup.py b/WINDOWS/msifixup.py deleted file mode 100644 index 11606417d..000000000 --- a/WINDOWS/msifixup.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import os -import tempfile -import shutil -import subprocess -import pipes - -def run(command, verbose): - if verbose: - sys.stdout.write("$ {}\n".format(" ".join( - pipes.quote(word) for word in command))) - out = subprocess.check_output(command) - if verbose: - sys.stdout.write("".join( - "> {}\n".format(line) for line in out.splitlines())) - -def make_changes(msi, args): - run(["msidump", "-t", msi], args.verbose) - build_cmd = ["msibuild", msi] - - def change_table(filename): - with open(filename) as fh: - lines = [line.rstrip("\r\n").split("\t") - for line in iter(fh.readline, "")] - - for line in lines[3:]: - yield line - - with open(filename, "w") as fh: - for line in lines: - fh.write("\t".join(line) + "\r\n") - - build_cmd.extend(["-i", filename]) - - if args.platform is not None: - for line in change_table("_SummaryInformation.idt"): - if line[0] == "7": - line[1] = ";".join([args.platform] + line[1].split(";", 1)[1:]) - - if args.dialog_bmp_width is not None: - for line in change_table("Control.idt"): - if line[9] == "WixUI_Bmp_Dialog": - line[5] = args.dialog_bmp_width - - run(build_cmd, args.verbose) - -def main(): - parser = argparse.ArgumentParser( - description='Change the platform field of an MSI installer package.') - parser.add_argument("msi", help="MSI installer file.") - parser.add_argument("--platform", help="Change the platform field.") - parser.add_argument("--dialog-bmp-width", help="Change the width field" - " in all uses of WixUI_Bmp_Dialog.") - parser.add_argument("-v", "--verbose", action="store_true", - help="Log what this script is doing.") - parser.add_argument("-k", "--keep", action="store_true", - help="Don't delete the temporary working directory.") - args = parser.parse_args() - - msi = os.path.abspath(args.msi) - msidir = os.path.dirname(msi) - try: - tempdir = tempfile.mkdtemp(dir=msidir) - os.chdir(tempdir) - make_changes(msi, args) - finally: - if args.keep: - sys.stdout.write( - "Retained temporary directory {}\n".format(tempdir)) - else: - shutil.rmtree(tempdir) - -if __name__ == '__main__': - main() diff --git a/WINDOWS/named-pipe-client.c b/WINDOWS/named-pipe-client.c deleted file mode 100644 index 2ab6a3099..000000000 --- a/WINDOWS/named-pipe-client.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Windows support module which deals with being a named-pipe client. - */ - -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "proxy/proxy.h" -#include "ssh.h" - -#include "security-api.h" - -HANDLE connect_to_named_pipe(const char *pipename, char **err) -{ - HANDLE pipehandle; - PSID usersid, pipeowner; - PSECURITY_DESCRIPTOR psd; - - assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); - assert(strchr(pipename + 9, '\\') == NULL); - - while (1) { - pipehandle = CreateFile(pipename, GENERIC_READ | GENERIC_WRITE, - 0, NULL, OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, NULL); - - if (pipehandle != INVALID_HANDLE_VALUE) - break; - - if (GetLastError() != ERROR_PIPE_BUSY) { - *err = dupprintf( - "Unable to open named pipe '%s': %s", - pipename, win_strerror(GetLastError())); - return INVALID_HANDLE_VALUE; - } - - /* - * If we got ERROR_PIPE_BUSY, wait for the server to create a - * new pipe instance. (Since the server is expected to be - * named-pipe-server.c, which will do that immediately after a - * previous connection is accepted, that shouldn't take - * excessively long.) - */ - if (!WaitNamedPipe(pipename, NMPWAIT_USE_DEFAULT_WAIT)) { - *err = dupprintf( - "Error waiting for named pipe '%s': %s", - pipename, win_strerror(GetLastError())); - return INVALID_HANDLE_VALUE; - } - } - - if ((usersid = get_user_sid()) == NULL) { - CloseHandle(pipehandle); - *err = dupprintf( - "Unable to get user SID: %s", win_strerror(GetLastError())); - return INVALID_HANDLE_VALUE; - } - - if (p_GetSecurityInfo(pipehandle, SE_KERNEL_OBJECT, - OWNER_SECURITY_INFORMATION, - &pipeowner, NULL, NULL, NULL, - &psd) != ERROR_SUCCESS) { - CloseHandle(pipehandle); - *err = dupprintf( - "Unable to get named pipe security information: %s", - win_strerror(GetLastError())); - return INVALID_HANDLE_VALUE; - } - - if (!EqualSid(pipeowner, usersid)) { - CloseHandle(pipehandle); - LocalFree(psd); - *err = dupprintf( - "Owner of named pipe '%s' is not us", pipename); - return INVALID_HANDLE_VALUE; - } - - LocalFree(psd); - - return pipehandle; -} - -Socket *new_named_pipe_client(const char *pipename, Plug *plug) -{ - char *err = NULL; - HANDLE pipehandle = connect_to_named_pipe(pipename, &err); - if (pipehandle == INVALID_HANDLE_VALUE) - return new_error_socket_consume_string(plug, err); - else - return make_handle_socket(pipehandle, pipehandle, NULL, NULL, 0, - plug, true); -} diff --git a/WINDOWS/named-pipe-server.c b/WINDOWS/named-pipe-server.c deleted file mode 100644 index ef7707966..000000000 --- a/WINDOWS/named-pipe-server.c +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Windows support module which deals with being a named-pipe server. - */ - -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "proxy/proxy.h" -#include "ssh.h" - -#include "security-api.h" - -typedef struct NamedPipeServerSocket { - /* Parameters for (repeated) creation of named pipe objects */ - PSECURITY_DESCRIPTOR psd; - PACL acl; - char *pipename; - - /* The current named pipe object + attempt to connect to it */ - HANDLE pipehandle; - OVERLAPPED connect_ovl; - HandleWait *callback_handle; /* handle-wait.c's reference */ - - /* PuTTY Socket machinery */ - Plug *plug; - char *error; - - Socket sock; -} NamedPipeServerSocket; - -static Plug *sk_namedpipeserver_plug(Socket *s, Plug *p) -{ - NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); - Plug *ret = ps->plug; - if (p) - ps->plug = p; - return ret; -} - -static void sk_namedpipeserver_close(Socket *s) -{ - NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); - - if (ps->callback_handle) - delete_handle_wait(ps->callback_handle); - CloseHandle(ps->pipehandle); - CloseHandle(ps->connect_ovl.hEvent); - sfree(ps->error); - sfree(ps->pipename); - if (ps->acl) - LocalFree(ps->acl); - if (ps->psd) - LocalFree(ps->psd); - sfree(ps); -} - -static const char *sk_namedpipeserver_socket_error(Socket *s) -{ - NamedPipeServerSocket *ps = container_of(s, NamedPipeServerSocket, sock); - return ps->error; -} - -static SocketPeerInfo *sk_namedpipeserver_peer_info(Socket *s) -{ - return NULL; -} - -static bool create_named_pipe(NamedPipeServerSocket *ps, bool first_instance) -{ - SECURITY_ATTRIBUTES sa; - - memset(&sa, 0, sizeof(sa)); - sa.nLength = sizeof(sa); - sa.lpSecurityDescriptor = ps->psd; - sa.bInheritHandle = false; - - ps->pipehandle = CreateNamedPipe( - /* lpName */ - ps->pipename, - - /* dwOpenMode */ - PIPE_ACCESS_DUPLEX | - FILE_FLAG_OVERLAPPED | - (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0), - - /* dwPipeMode */ - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT -#ifdef PIPE_REJECT_REMOTE_CLIENTS - | PIPE_REJECT_REMOTE_CLIENTS -#endif - , - - /* nMaxInstances */ - PIPE_UNLIMITED_INSTANCES, - - /* nOutBufferSize, nInBufferSize */ - 4096, 4096, /* FIXME: think harder about buffer sizes? */ - - /* nDefaultTimeOut */ - 0 /* default timeout */, - - /* lpSecurityAttributes */ - &sa); - - return ps->pipehandle != INVALID_HANDLE_VALUE; -} - -static Socket *named_pipe_accept(accept_ctx_t ctx, Plug *plug) -{ - HANDLE conn = (HANDLE)ctx.p; - - return make_handle_socket(conn, conn, NULL, NULL, 0, plug, true); -} - -static void named_pipe_accept_loop(NamedPipeServerSocket *ps, - bool got_one_already) -{ - while (1) { - int error; - char *errmsg; - - if (got_one_already) { - /* If we were called with a connection already waiting, - * skip this step. */ - got_one_already = false; - error = 0; - } else { - /* - * Call ConnectNamedPipe, which might succeed or might - * tell us that an overlapped operation is in progress and - * we should wait for our event object. - */ - if (ConnectNamedPipe(ps->pipehandle, &ps->connect_ovl)) - error = 0; - else - error = GetLastError(); - - if (error == ERROR_IO_PENDING) - return; - } - - if (error == 0 || error == ERROR_PIPE_CONNECTED) { - /* - * We've successfully retrieved an incoming connection, so - * ps->pipehandle now refers to that connection. So - * convert that handle into a separate connection-type - * Socket, and create a fresh one to be the new listening - * pipe. - */ - HANDLE conn = ps->pipehandle; - accept_ctx_t actx; - - actx.p = (void *)conn; - if (plug_accepting(ps->plug, named_pipe_accept, actx)) { - /* - * If the plug didn't want the connection, might as - * well close this handle. - */ - CloseHandle(conn); - } - - if (!create_named_pipe(ps, false)) { - error = GetLastError(); - } else { - /* - * Go round again to see if more connections can be - * got, or to begin waiting on the event object. - */ - continue; - } - } - - errmsg = dupprintf("Error while listening to named pipe: %s", - win_strerror(error)); - plug_log(ps->plug, 1, sk_namedpipe_addr(ps->pipename), 0, - errmsg, error); - sfree(errmsg); - break; - } -} - -static void named_pipe_connect_callback(void *vps) -{ - NamedPipeServerSocket *ps = (NamedPipeServerSocket *)vps; - named_pipe_accept_loop(ps, true); -} - -/* - * This socket type is only used for listening, so it should never - * be asked to write or set_frozen. - */ -static const SocketVtable NamedPipeServerSocket_sockvt = { - .plug = sk_namedpipeserver_plug, - .close = sk_namedpipeserver_close, - .socket_error = sk_namedpipeserver_socket_error, - .peer_info = sk_namedpipeserver_peer_info, -}; - -Socket *new_named_pipe_listener(const char *pipename, Plug *plug) -{ - NamedPipeServerSocket *ps = snew(NamedPipeServerSocket); - ps->sock.vt = &NamedPipeServerSocket_sockvt; - ps->plug = plug; - ps->error = NULL; - ps->psd = NULL; - ps->pipename = dupstr(pipename); - ps->acl = NULL; - ps->callback_handle = NULL; - - assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); - assert(strchr(pipename + 9, '\\') == NULL); - - if (!make_private_security_descriptor(GENERIC_READ | GENERIC_WRITE, - &ps->psd, &ps->acl, &ps->error)) { - goto cleanup; - } - - if (!create_named_pipe(ps, true)) { - ps->error = dupprintf("unable to create named pipe '%s': %s", - pipename, win_strerror(GetLastError())); - goto cleanup; - } - - memset(&ps->connect_ovl, 0, sizeof(ps->connect_ovl)); - ps->connect_ovl.hEvent = CreateEvent(NULL, true, false, NULL); - ps->callback_handle = add_handle_wait( - ps->connect_ovl.hEvent, named_pipe_connect_callback, ps); - named_pipe_accept_loop(ps, false); - - cleanup: - return &ps->sock; -} diff --git a/WINDOWS/network.c b/WINDOWS/network.c deleted file mode 100644 index d08f33774..000000000 --- a/WINDOWS/network.c +++ /dev/null @@ -1,1878 +0,0 @@ -/* - * Windows networking abstraction. - * - * For the IPv6 code in here I am indebted to Jeroen Massar and - * unfix.org. - */ - -#include /* need to put this first, for winelib builds */ - -#include -#include -#include - -#define NEED_DECLARATION_OF_SELECT /* in order to initialise it */ - -#include "putty.h" -#include "network.h" -#include "tree234.h" -#include "ssh.h" - -#include - -#if HAVE_AFUNIX_H -#include -#endif - -#ifndef NO_IPV6 -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmissing-braces" -#endif -const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; -const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif -#endif - -#define ipv4_is_loopback(addr) \ - ((p_ntohl(addr.s_addr) & 0xFF000000L) == 0x7F000000L) - -/* - * 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; -}; - -typedef struct NetSocket NetSocket; -struct NetSocket { - const char *error; - SOCKET s; - Plug *plug; - bufchain output_data; - bool connected; - bool writable; - bool frozen; /* this causes readability notifications to be ignored */ - bool frozen_readable; /* this means we missed at least one readability - * notification while we were frozen */ - bool localhost_only; /* for listening sockets */ - char oobdata[1]; - size_t sending_oob; - bool 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. - */ - NetSocket *parent, *child; - - Socket sock; -}; - -/* - * Top-level discriminator for SockAddr. - * - * UNRESOLVED means a host name not yet put through DNS; IP means a - * resolved IP address (or list of them); UNIX indicates the AF_UNIX - * network family (which Windows also has); NAMEDPIPE indicates that - * this SockAddr is phony, holding a Windows named pipe pathname - * instead of any address WinSock can understand. - */ -typedef enum SuperFamily { - UNRESOLVED, - IP, -#if HAVE_AFUNIX_H - UNIX, -#endif - NAMEDPIPE -} SuperFamily; - -struct SockAddr { - int refcount; - const char *error; - SuperFamily superfamily; -#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_UNIX for Unix-domain sockets; AF_UNSPEC - * indicates that name resolution has not been done and a simple host - * name is held in this SockAddr structure. - */ -static inline int sockaddr_family(SockAddr *addr, SockAddrStep step) -{ - switch (addr->superfamily) { - case IP: -#ifndef NO_IPV6 - if (step.ai) - return step.ai->ai_family; -#endif - return AF_INET; -#if HAVE_AFUNIX_H - case UNIX: - return AF_UNIX; -#endif - default: - return AF_UNSPEC; - } -} - -/* - * 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) -{ - NetSocket *a = (NetSocket *)av, *b = (NetSocket *)bv; - uintptr_t as = (uintptr_t) a->s, bs = (uintptr_t) 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) -{ - NetSocket *b = (NetSocket *)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, ULONG, ntohl, (ULONG)); -DECL_WINDOWS_FUNCTION(static, ULONG, htonl, (ULONG)); -DECL_WINDOWS_FUNCTION(static, USHORT, htons, (USHORT)); -DECL_WINDOWS_FUNCTION(static, USHORT, ntohs, (USHORT)); -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, ULONG, 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)); -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, ULONG 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, DWORD hostlen, char FAR *serv, - DWORD servlen, int flags)); -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 - -static bool 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; - } - - return true; -} - -DEF_WINDOWS_FUNCTION(WSAAsyncSelect); -DEF_WINDOWS_FUNCTION(WSAEventSelect); -DEF_WINDOWS_FUNCTION(WSAGetLastError); -DEF_WINDOWS_FUNCTION(WSAEnumNetworkEvents); -DEF_WINDOWS_FUNCTION(select); - -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) - modalfatalbox("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_NO_TYPECHECK(winsock_module, getnameinfo); - /* This function would fail its type-check if we did one, - * because the VS header file provides an inline definition - * which is __cdecl instead of WINAPI. */ - } else { - /* Fall back to wship6.dll for Windows 2000 */ - wship6_module = load_system32_dll("wship6.dll"); - if (wship6_module) { - GET_WINDOWS_FUNCTION(wship6_module, getaddrinfo); - GET_WINDOWS_FUNCTION(wship6_module, freeaddrinfo); - /* See comment above about type check */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(wship6_module, getnameinfo); - } else { - } - } - GET_WINDOWS_FUNCTION(winsock2_module, WSAAddressToStringA); -#endif - - GET_WINDOWS_FUNCTION(winsock_module, WSAAsyncSelect); - GET_WINDOWS_FUNCTION(winsock_module, WSAEventSelect); - /* We don't type-check select because at least some MinGW versions - * of the Windows API headers seem to disagree with the - * documentation on whether the 'struct timeval *' pointer is - * const or not. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(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); - /* Winelib maps ntohl and friends to things like - * __wine_ulong_swap, which fail these type checks hopelessly */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, ntohl); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, htonl); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, htons); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, ntohs); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(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); - /* Older Visual Studio, and MinGW as of Ubuntu 16.04, don't know - * about this function at all, so can't type-check it. Also there - * seems to be some disagreement in the VS headers about whether - * the second argument is void * or const void *, so I omit the - * type check. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, inet_ntop); - GET_WINDOWS_FUNCTION(winsock_module, connect); - GET_WINDOWS_FUNCTION(winsock_module, bind); - 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)) { - modalfatalbox("Unable to initialise WinSock"); - } - - sktree = newtree234(cmpfortree); -} - -void sk_cleanup(void) -{ - NetSocket *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 -} - -const char *winsock_error_string(int error) -{ - /* - * 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"; - } - - /* - * Handle any other error code by delegating to win_strerror. - */ - return win_strerror(error); -} - -static inline const char *namelookup_strerror(DWORD err) -{ - /* PuTTY has traditionally translated a few of the likely error - * messages into more concise strings than the standard Windows ones */ - return (err == WSAENETDOWN ? "Network is down" : - err == WSAHOST_NOT_FOUND ? "Host does not exist" : - err == WSATRY_AGAIN ? "Host not found" : - win_strerror(err)); -} - -SockAddr *sk_namelookup(const char *host, char **canonicalname, - int address_family) -{ - *canonicalname = NULL; - - SockAddr *addr = snew(SockAddr); - memset(addr, 0, sizeof(SockAddr)); - addr->superfamily = UNRESOLVED; - addr->refcount = 1; - -#ifndef NO_IPV6 - /* - * Use getaddrinfo, as long as it's available. This should handle - * both IPv4 and IPv6 address literals, and hostnames, in one - * unified API. - */ - if (p_getaddrinfo) { - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : - address_family == ADDRTYPE_IPV6 ? AF_INET6 : - AF_UNSPEC); - hints.ai_flags = AI_CANONNAME; - hints.ai_socktype = SOCK_STREAM; - - /* strip [] on IPv6 address literals */ - char *trimmed_host = host_strduptrim(host); - int err = p_getaddrinfo(trimmed_host, NULL, &hints, &addr->ais); - sfree(trimmed_host); - - if (addr->ais) { - addr->superfamily = IP; - if (addr->ais->ai_canonname) - *canonicalname = dupstr(addr->ais->ai_canonname); - else - *canonicalname = dupstr(host); - } else { - addr->error = namelookup_strerror(err); - } - return addr; - } -#endif - - /* - * Failing that (if IPv6 support was not compiled in, or if - * getaddrinfo turned out to be unavailable at run time), try the - * old-fashioned approach, which is to start by manually checking - * for an IPv4 literal and then use gethostbyname. - */ - unsigned long a = p_inet_addr(host); - if (a != (unsigned long) INADDR_NONE) { - addr->addresses = snew(unsigned long); - addr->naddresses = 1; - addr->addresses[0] = p_ntohl(a); - addr->superfamily = IP; - *canonicalname = dupstr(host); - return addr; - } - - struct hostent *h = p_gethostbyname(host); - if (h) { - addr->superfamily = IP; - - size_t n; - for (n = 0; h->h_addr_list[n]; n++); - addr->addresses = snewn(n, unsigned long); - addr->naddresses = n; - for (n = 0; n < addr->naddresses; n++) { - uint32_t a; - memcpy(&a, h->h_addr_list[n], sizeof(a)); - addr->addresses[n] = p_ntohl(a); - } - - *canonicalname = dupstr(h->h_name); - } else { - DWORD err = p_WSAGetLastError(); - addr->error = namelookup_strerror(err); - } - return addr; -} - -static SockAddr *sk_special_addr(SuperFamily superfamily, const char *name) -{ - SockAddr *addr = snew(SockAddr); - addr->error = NULL; - addr->superfamily = superfamily; -#ifndef NO_IPV6 - addr->ais = NULL; -#endif - addr->addresses = NULL; - addr->naddresses = 0; - addr->refcount = 1; - strncpy(addr->hostname, name, lenof(addr->hostname)); - addr->hostname[lenof(addr->hostname)-1] = '\0'; - return addr; -} - -SockAddr *sk_nonamelookup(const char *host) -{ - return sk_special_addr(UNRESOLVED, host); -} - -SockAddr *sk_namedpipe_addr(const char *pipename) -{ - return sk_special_addr(NAMEDPIPE, pipename); -} - -#if HAVE_AFUNIX_H -SockAddr *sk_unix_addr(const char *sockpath) -{ - return sk_special_addr(UNIX, sockpath); -} -#endif - -static bool 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'; - } -} - -/* - * This constructs a SockAddr that points at one specific sub-address - * of a parent SockAddr. The returned SockAddr does not own all its - * own memory: it points into the old one's data structures, so it - * MUST NOT be used after the old one is freed, and it MUST NOT be - * passed to sk_addr_free. (The latter is why it's returned by value - * rather than dynamically allocated - that should clue in anyone - * writing a call to it that something is weird about it.) - */ -static SockAddr sk_extractaddr_tmp( - SockAddr *addr, const SockAddrStep *step) -{ - SockAddr toret; - toret = *addr; /* structure copy */ - toret.refcount = 1; - -#ifndef NO_IPV6 - toret.ais = step->ai; -#endif - if (sockaddr_family(addr, *step) == AF_INET -#ifndef NO_IPV6 - && !toret.ais -#endif - ) - toret.addresses += step->curraddr; - - return toret; -} - -bool sk_addr_needs_port(SockAddr *addr) -{ - return addr->superfamily != NAMEDPIPE -#if HAVE_AFUNIX_H - && addr->superfamily != UNIX -#endif - ; -} - -bool 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 bool ipv4_is_local_addr(struct in_addr addr) -{ - if (ipv4_is_loopback(addr)) - return true; /* loopback addresses are local */ - if (!n_local_interfaces) { - SOCKET s = p_socket(AF_INET, SOCK_DGRAM, 0); - DWORD retbytes; - - SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); - - 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 - n_local_interfaces = -1; - } - 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 true; /* this address is local */ - } - } - return false; /* this address is not local */ -} - -bool 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 false; /* we don't know; assume not */ - } -} - -bool sk_address_is_special_local(SockAddr *addr) -{ - return false; /* 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 - unreachable("bad address family in sk_addrcopy"); - } 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_net_plug(Socket *sock, Plug *p) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - Plug *ret = s->plug; - if (p) - s->plug = p; - return ret; -} - -static void sk_net_close(Socket *s); -static size_t sk_net_write(Socket *s, const void *data, size_t len); -static size_t sk_net_write_oob(Socket *s, const void *data, size_t len); -static void sk_net_write_eof(Socket *s); -static void sk_net_set_frozen(Socket *s, bool is_frozen); -static const char *sk_net_socket_error(Socket *s); -static SocketPeerInfo *sk_net_peer_info(Socket *s); - -static const SocketVtable NetSocket_sockvt = { - .plug = sk_net_plug, - .close = sk_net_close, - .write = sk_net_write, - .write_oob = sk_net_write_oob, - .write_eof = sk_net_write_eof, - .set_frozen = sk_net_set_frozen, - .socket_error = sk_net_socket_error, - .peer_info = sk_net_peer_info, -}; - -static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug) -{ - DWORD err; - const char *errstr; - NetSocket *s; - - /* - * Create NetSocket structure. - */ - s = snew(NetSocket); - s->sock.vt = &NetSocket_sockvt; - s->error = NULL; - s->plug = plug; - bufchain_init(&s->output_data); - s->writable = true; /* to start with */ - s->sending_oob = 0; - s->outgoingeof = EOF_NO; - s->frozen = true; - s->frozen_readable = false; - s->localhost_only = false; /* unused, but best init anyway */ - s->pending_error = 0; - s->parent = s->child = NULL; - s->addr = NULL; - - s->s = (SOCKET)ctx.p; - - if (s->s == INVALID_SOCKET) { - err = p_WSAGetLastError(); - s->error = winsock_error_string(err); - return &s->sock; - } - - s->oobinline = false; - - /* Set up a select mechanism. This could be an AsyncSelect on a - * window, or an EventSelect on an event object. */ - errstr = do_select(s->s, true); - if (errstr) { - s->error = errstr; - return &s->sock; - } - - add234(sktree, s); - - return &s->sock; -} - -static DWORD try_connect(NetSocket *sock) -{ - SOCKET s; -#ifndef NO_IPV6 - SOCKADDR_IN6 a6; -#endif - SOCKADDR_IN a; - DWORD err; - const char *errstr; - short localport; - int family; - - if (sock->s != INVALID_SOCKET) { - do_select(sock->s, false); - p_closesocket(sock->s); - } - - { - SockAddr thisaddr = sk_extractaddr_tmp( - sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_TRYING, - &thisaddr, 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; - } - - SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); - - 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)); - } - - /* - * 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); - } - - /* Set up a select mechanism. This could be an AsyncSelect on a - * window, or an EventSelect on an event object. */ - errstr = do_select(s, true); - if (errstr) { - sock->error = errstr; - err = 1; - goto ret; - } - - 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 ) { - 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 = true; - SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS, - &thisaddr, sock->port, NULL, 0); - } - - err = 0; - - ret: - - /* - * No matter what happened, put the socket back in the tree. - */ - add234(sktree, sock); - - if (err) { - SockAddr thisaddr = sk_extractaddr_tmp( - sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_FAILED, - &thisaddr, sock->port, sock->error, err); - } - return err; -} - -Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, - bool nodelay, bool keepalive, Plug *plug) -{ - NetSocket *s; - DWORD err; - - /* - * Create NetSocket structure. - */ - s = snew(NetSocket); - s->sock.vt = &NetSocket_sockvt; - s->error = NULL; - s->plug = plug; - bufchain_init(&s->output_data); - s->connected = false; /* to start with */ - s->writable = false; /* to start with */ - s->sending_oob = 0; - s->outgoingeof = EOF_NO; - s->frozen = false; - s->frozen_readable = false; - s->localhost_only = false; /* unused, but best init anyway */ - s->pending_error = 0; - s->parent = s->child = NULL; - s->oobinline = oobinline; - s->nodelay = nodelay; - s->keepalive = keepalive; - s->privport = privport; - s->port = port; - s->addr = addr; - START_STEP(s->addr, s->step); - s->s = INVALID_SOCKET; - - err = 0; - do { - err = try_connect(s); - } while (err && sk_nextaddr(s->addr, &s->step)); - - return &s->sock; -} - -static Socket *sk_newlistener_internal( - const char *srcaddr, int port, Plug *plug, - bool local_host_only, int orig_address_family) -{ - SOCKET sk; - SOCKADDR_IN a; -#ifndef NO_IPV6 - SOCKADDR_IN6 a6; -#endif -#if HAVE_AFUNIX_H - SOCKADDR_UN au; -#endif - struct sockaddr *bindaddr; - unsigned bindsize; - - DWORD err; - const char *errstr; - NetSocket *s; - int retcode; - - int address_family = orig_address_family; - - /* - * Create NetSocket structure. - */ - s = snew(NetSocket); - s->sock.vt = &NetSocket_sockvt; - s->error = NULL; - s->plug = plug; - bufchain_init(&s->output_data); - s->writable = false; /* to start with */ - s->sending_oob = 0; - s->outgoingeof = EOF_NO; - s->frozen = false; - s->frozen_readable = false; - s->localhost_only = local_host_only; - s->pending_error = 0; - s->parent = s->child = NULL; - s->addr = NULL; - - /* - * 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. - */ - sk = p_socket(address_family, SOCK_STREAM, 0); - s->s = sk; - - if (sk == INVALID_SOCKET) { - err = p_WSAGetLastError(); - s->error = winsock_error_string(err); - return &s->sock; - } - - SetHandleInformation((HANDLE)sk, HANDLE_FLAG_INHERIT, 0); - - s->oobinline = false; - -#if HAVE_AFUNIX_H - if (address_family != AF_UNIX) -#endif - { - BOOL on = true; - p_setsockopt(sk, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, - (const char *)&on, sizeof(on)); - } - - switch (address_family) { -#ifndef NO_IPV6 - case 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); - bindaddr = (struct sockaddr *)&a6; - bindsize = sizeof(a6); - break; - } -#endif - case AF_INET: { - bool got_addr = false; - 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. */ - s->localhost_only = ipv4_is_loopback(a.sin_addr); - got_addr = true; - } - } - - /* - * ... 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); - bindaddr = (struct sockaddr *)&a; - bindsize = sizeof(a); - break; - } -#if HAVE_AFUNIX_H - case AF_UNIX: { - au.sun_family = AF_UNIX; - strncpy(au.sun_path, srcaddr, sizeof(au.sun_path)); - bindaddr = (struct sockaddr *)&au; - bindsize = sizeof(au); - break; - } -#endif - default: - unreachable("bad address family in sk_newlistener_internal"); - } - - retcode = p_bind(sk, bindaddr, bindsize); - if (retcode != SOCKET_ERROR) { - err = 0; - } else { - err = p_WSAGetLastError(); - } - - if (err) { - p_closesocket(sk); - s->error = winsock_error_string(err); - return &s->sock; - } - - - if (p_listen(sk, SOMAXCONN) == SOCKET_ERROR) { - p_closesocket(sk); - s->error = winsock_error_string(p_WSAGetLastError()); - return &s->sock; - } - - /* Set up a select mechanism. This could be an AsyncSelect on a - * window, or an EventSelect on an event object. */ - errstr = do_select(sk, true); - if (errstr) { - p_closesocket(sk); - s->error = errstr; - return &s->sock; - } - - add234(sktree, s); - -#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 == AF_UNSPEC) { - Socket *other = sk_newlistener_internal(srcaddr, port, plug, - local_host_only, AF_INET6); - - if (other) { - NetSocket *ns = container_of(other, NetSocket, sock); - if (!ns->error) { - ns->parent = s; - s->child = ns; - } else { - sfree(ns); - } - } - } -#endif - - return &s->sock; -} - -Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, int orig_address_family) -{ - /* - * Translate address_family from platform-independent constants - * into local reality. - */ - int address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET : -#ifndef NO_IPV6 - orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 : -#endif - AF_UNSPEC); - - return sk_newlistener_internal(srcaddr, port, plug, local_host_only, - address_family); -} - -Socket *sk_newlistener_unix(const char *path, Plug *plug) -{ -#if HAVE_AFUNIX_H - return sk_newlistener_internal(path, 0, plug, false, AF_UNIX); -#else - return new_error_socket_fmt( - plug, "AF_UNIX support not compiled into this program"); -#endif -} - -static void sk_net_close(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - - if (s->child) - sk_net_close(&s->child->sock); - - bufchain_clear(&s->output_data); - - del234(sktree, s); - do_select(s->s, false); - p_closesocket(s->s); - if (s->addr) - sk_addr_free(s->addr); - delete_callbacks_for_context(s); - sfree(s); -} - -void plug_closing_system_error(Plug *plug, DWORD error) -{ - PlugCloseType type = PLUGCLOSE_ERROR; - if (error == ERROR_BROKEN_PIPE) - type = PLUGCLOSE_BROKEN_PIPE; - plug_closing(plug, type, win_strerror(error)); -} - -void plug_closing_winsock_error(Plug *plug, DWORD error) -{ - plug_closing(plug, PLUGCLOSE_ERROR, winsock_error_string(error)); -} - -/* - * Deal with socket errors detected in try_send(). - */ -static void socket_error_callback(void *vs) -{ - NetSocket *s = (NetSocket *)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_winsock_error(s->plug, s->pending_error); -} - -/* - * The function which tries to send on a socket once it's deemed - * writable. - */ -static void try_send(NetSocket *s) -{ - while (s->sending_oob || bufchain_size(&s->output_data) > 0) { - int nsent; - DWORD err; - const void *data; - size_t len; - int urgentflag; - - if (s->sending_oob) { - urgentflag = MSG_OOB; - len = s->sending_oob; - data = &s->oobdata; - } else { - urgentflag = 0; - ptrlen bufdata = bufchain_prefix(&s->output_data); - data = bufdata.ptr; - len = bufdata.len; - } - len = min(len, INT_MAX); /* WinSock send() takes an int */ - nsent = p_send(s->s, data, len, urgentflag); - noise_ultralight(NOISE_SOURCE_IOLEN, 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 send() returns a socket error, 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 { - 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 size_t sk_net_write(Socket *sock, const void *buf, size_t len) -{ - NetSocket *s = container_of(sock, NetSocket, 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 size_t sk_net_write_oob(Socket *sock, const void *buf, size_t len) -{ - NetSocket *s = container_of(sock, NetSocket, 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_net_write_eof(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, 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); -} - -void select_result(WPARAM wParam, LPARAM lParam) -{ - int ret; - DWORD err; - char buf[20480]; /* nice big buffer for plenty of speed */ - NetSocket *s; - bool atmark; - - /* wParam is the socket itself */ - - if (wParam == 0) - return; /* boggle */ - - s = find234(sktree, (void *) wParam, cmpforsearch); - if (!s) - return; /* boggle */ - - if ((err = WSAGETSELECTERROR(lParam)) != 0) { - /* - * An error has occurred on this socket. Pass it to the - * plug. - */ - if (s->addr) { - SockAddr thisaddr = sk_extractaddr_tmp( - s->addr, &s->step); - plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port, - winsock_error_string(err), err); - while (err && s->addr && sk_nextaddr(s->addr, &s->step)) { - err = try_connect(s); - } - } - if (err != 0) - plug_closing_winsock_error(s->plug, err); - return; - } - - noise_ultralight(NOISE_SOURCE_IOID, wParam); - - switch (WSAGETSELECTEVENT(lParam)) { - case FD_CONNECT: - s->connected = true; - s->writable = true; - - /* - * Once a socket is connected, we can stop falling back - * through the candidate addresses to connect to. But first, - * let the plug know we were successful. - */ - if (s->addr) { - SockAddr thisaddr = sk_extractaddr_tmp( - s->addr, &s->step); - plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS, - &thisaddr, s->port, NULL, 0); - - 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 = true; - 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) { - u_long atmark_from_ioctl = 1; - p_ioctlsocket(s->s, SIOCATMARK, &atmark_from_ioctl); - /* - * 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. - */ - atmark = atmark_from_ioctl; - } else - atmark = true; - - ret = p_recv(s->s, buf, sizeof(buf), 0); - noise_ultralight(NOISE_SOURCE_IOLEN, ret); - if (ret < 0) { - err = p_WSAGetLastError(); - if (err == WSAEWOULDBLOCK) { - break; - } - } - if (ret < 0) { - plug_closing_winsock_error(s->plug, err); - } else if (0 == ret) { - plug_closing_normal(s->plug); - } else { - 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(NOISE_SOURCE_IOLEN, ret); - if (ret <= 0) { - int err = p_WSAGetLastError(); - plug_closing_winsock_error(s->plug, err); - } else { - plug_receive(s->plug, 2, buf, ret); - } - break; - case FD_WRITE: { - int bufsize_before, bufsize_after; - s->writable = true; - 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. */ - do { - ret = p_recv(s->s, buf, sizeof(buf), 0); - if (ret < 0) { - err = p_WSAGetLastError(); - if (err == WSAEWOULDBLOCK) - break; - plug_closing_winsock_error(s->plug, err); - } else { - if (ret) - plug_receive(s->plug, 0, buf, ret); - else - plug_closing_normal(s->plug); - } - } while (ret > 0); - return; - case FD_ACCEPT: { -#ifdef NO_IPV6 - struct sockaddr_in isa; -#else - struct sockaddr_storage isa; // FIXME: also if Unix and no IPv6 -#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_net_accept, actx)) { - p_closesocket(t); /* denied or error */ - } - break; - } - } -} - -/* - * 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_net_socket_error(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - return s->error; -} - -static SocketPeerInfo *sk_net_peer_info(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); -#ifdef NO_IPV6 - struct sockaddr_in addr; -#else - struct sockaddr_storage addr; // FIXME: also if Unix and no IPv6 - char buf[INET6_ADDRSTRLEN]; -#endif - int addrlen = sizeof(addr); - SocketPeerInfo *pi; - - if (p_getpeername(s->s, (struct sockaddr *)&addr, &addrlen) < 0) - return NULL; - - pi = snew(SocketPeerInfo); - pi->addressfamily = ADDRTYPE_UNSPEC; - pi->addr_text = NULL; - pi->port = -1; - pi->log_text = NULL; - - if (((struct sockaddr *)&addr)->sa_family == AF_INET) { - pi->addressfamily = ADDRTYPE_IPV4; - memcpy(pi->addr_bin.ipv4, &((struct sockaddr_in *)&addr)->sin_addr, 4); - pi->port = p_ntohs(((struct sockaddr_in *)&addr)->sin_port); - pi->addr_text = dupstr( - p_inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr)); - pi->log_text = dupprintf("%s:%d", pi->addr_text, pi->port); - -#ifndef NO_IPV6 - } else if (((struct sockaddr *)&addr)->sa_family == AF_INET6) { - pi->addressfamily = ADDRTYPE_IPV6; - memcpy(pi->addr_bin.ipv6, - &((struct sockaddr_in6 *)&addr)->sin6_addr, 16); - pi->port = p_ntohs(((struct sockaddr_in6 *)&addr)->sin6_port); - pi->addr_text = dupstr( - p_inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr, - buf, sizeof(buf))); - pi->log_text = dupprintf("[%s]:%d", pi->addr_text, pi->port); - -#endif - } else { - sfree(pi); - return NULL; - } - - return pi; -} - -static void sk_net_set_frozen(Socket *sock, bool is_frozen) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - if (s->frozen == is_frozen) - return; - s->frozen = is_frozen; - if (!is_frozen) { - do_select(s->s, true); - if (s->frozen_readable) { - char c; - p_recv(s->s, &c, 1, MSG_PEEK); - } - } - s->frozen_readable = false; -} - -void socket_reselect_all(void) -{ - NetSocket *s; - int i; - - for (i = 0; (s = index234(sktree, i)) != NULL; i++) { - if (!s->frozen) - do_select(s->s, true); - } -} - -/* - * For Plink: enumerate all sockets currently active. - */ -SOCKET first_socket(int *state) -{ - NetSocket *s; - *state = 0; - s = index234(sktree, (*state)++); - return s ? s->s : INVALID_SOCKET; -} - -SOCKET next_socket(int *state) -{ - NetSocket *s = index234(sktree, (*state)++); - return s ? s->s : INVALID_SOCKET; -} - -bool socket_writable(SOCKET skt) -{ - NetSocket *s = find234(sktree, (void *)skt, cmpforsearch); - - if (s) - return bufchain_size(&s->output_data) > 0; - else - return false; -} - -int net_service_lookup(const 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) -{ - char hostbuf[256]; /* MSDN docs for gethostname() promise this is enough */ - if (p_gethostname(hostbuf, sizeof(hostbuf)) < 0) - return NULL; - return dupstr(hostbuf); -} - -SockAddr *platform_get_x11_unix_address(const char *display, int displaynum) -{ - SockAddr *addr = snew(SockAddr); - memset(addr, 0, sizeof(SockAddr)); - addr->error = "unix sockets for X11 not supported on this platform"; - addr->refcount = 1; - return addr; -} diff --git a/WINDOWS/no-jump-list.c b/WINDOWS/no-jump-list.c deleted file mode 100644 index beabd1073..000000000 --- a/WINDOWS/no-jump-list.c +++ /dev/null @@ -1,10 +0,0 @@ -/* - * no-jump-list.c: stub jump list functions for Windows executables - * that don't update the jump list. - */ - -#include "putty.h" - -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/WINDOWS/nohelp.c b/WINDOWS/nohelp.c deleted file mode 100644 index 1b7487762..000000000 --- a/WINDOWS/nohelp.c +++ /dev/null @@ -1,15 +0,0 @@ -/* - * nohelp.c: implement the has_embedded_chm() function for - * applications that have no help file at all, so that buildinfo.c's - * buildinfo string knows not to talk meaninglessly about whether the - * nonexistent help file is present. - */ - -#include -#include -#include -#include - -#include "putty.h" - -int has_embedded_chm(void) { return -1; } diff --git a/WINDOWS/noise.c b/WINDOWS/noise.c deleted file mode 100644 index 65c4c92de..000000000 --- a/WINDOWS/noise.c +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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; - -bool win_read_random(void *buf, unsigned wanted) -{ - bool toret = false; - HCRYPTPROV crypt_provider; - - 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)) { - toret = p_CryptGenRandom(crypt_provider, wanted, buf); - p_CryptReleaseContext(crypt_provider, 0); - } - - return toret; -} - -/* - * This function is called once, at PuTTY startup. - */ - -void noise_get_heavy(void (*func) (void *, int)) -{ - HANDLE srch; - WIN32_FIND_DATA finddata; - DWORD pid; - char winpath[MAX_PATH + 3]; - BYTE buf[32]; - - 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 (win_read_random(buf, sizeof(buf))) { - func(buf, sizeof(buf)); - smemclr(buf, sizeof(buf)); - } - - read_random_seed(func); -} - -/* - * 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]; - - w = GetForegroundWindow(); - random_add_noise(NOISE_SOURCE_FGWINDOW, &w, sizeof(w)); - w = GetCapture(); - random_add_noise(NOISE_SOURCE_CAPTURE, &w, sizeof(w)); - w = GetClipboardOwner(); - random_add_noise(NOISE_SOURCE_CLIPBOARD, &w, sizeof(w)); - z = GetQueueStatus(QS_ALLEVENTS); - random_add_noise(NOISE_SOURCE_QUEUE, &z, sizeof(z)); - - GetCursorPos(&pt); - random_add_noise(NOISE_SOURCE_CURSORPOS, &pt, sizeof(pt)); - - GlobalMemoryStatus(&memstat); - random_add_noise(NOISE_SOURCE_MEMINFO, &memstat, sizeof(memstat)); - - GetThreadTimes(GetCurrentThread(), times, times + 1, times + 2, - times + 3); - random_add_noise(NOISE_SOURCE_THREADTIME, ×, sizeof(times)); - GetProcessTimes(GetCurrentProcess(), times, times + 1, times + 2, - times + 3); - random_add_noise(NOISE_SOURCE_PROCTIME, ×, sizeof(times)); -} - -/* - * 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(NoiseSourceId id, unsigned long data) -{ - DWORD wintime; - LARGE_INTEGER perftime; - - random_add_noise(id, &data, sizeof(DWORD)); - - wintime = GetTickCount(); - random_add_noise(NOISE_SOURCE_TIME, &wintime, sizeof(DWORD)); - - if (QueryPerformanceCounter(&perftime)) - random_add_noise(NOISE_SOURCE_PERFCOUNT, &perftime, sizeof(perftime)); -} - -uint64_t prng_reseed_time_ms(void) -{ - FILETIME ft; - GetSystemTimeAsFileTime(&ft); - uint64_t value = ft.dwHighDateTime; - value = (value << 32) + ft.dwLowDateTime; - return value / 10000; /* 1 millisecond / 100ns */ -} diff --git a/WINDOWS/pageant-rc.h b/WINDOWS/pageant-rc.h deleted file mode 100644 index 3133a9ca4..000000000 --- a/WINDOWS/pageant-rc.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Constant definitions for the Pageant resource file. - */ - -#define IDI_MAINICON 200 -#define IDI_TRAYICON 201 - -#define IDD_KEYLIST 211 -#define IDD_LOAD_PASSPHRASE 210 -#define IDD_ONDEMAND_PASSPHRASE 212 -#define IDD_ABOUT 213 -#define IDD_LICENCE 214 - -#define IDC_PASSPHRASE_STATIC1 100 -#define IDC_PASSPHRASE_FINGERPRINT 101 -#define IDC_PASSPHRASE_STATIC2 102 -#define IDC_PASSPHRASE_STATIC3 103 -#define IDC_PASSPHRASE_EDITBOX 104 - -#define IDC_KEYLIST_LISTBOX 100 -#define IDC_KEYLIST_ADDKEY 101 -#define IDC_KEYLIST_ADDKEY_ENC 110 -#define IDC_KEYLIST_REENCRYPT 106 -#define IDC_KEYLIST_REMOVE 102 -#define IDC_KEYLIST_HELP 103 -#define IDC_KEYLIST_FPTYPE_STATIC 104 -#define IDC_KEYLIST_FPTYPE 105 - -#define IDC_ABOUT_LICENCE 101 -#define IDC_ABOUT_WEBSITE 102 -#define IDC_ABOUT_TEXTBOX 1000 - -#define IDC_LICENCE_TEXTBOX 1000 diff --git a/WINDOWS/pageant.c b/WINDOWS/pageant.c deleted file mode 100644 index 314683014..000000000 --- a/WINDOWS/pageant.c +++ /dev/null @@ -1,1968 +0,0 @@ -/* - * Pageant: the PuTTY Authentication Agent. - */ - -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "misc.h" -#include "tree234.h" -#include "security-api.h" -#include "cryptoapi.h" -#include "pageant.h" -#include "licence.h" -#include "pageant-rc.h" - -#include - -#include -#ifdef DEBUG_IPC -#define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */ -#include -#endif - -#define WM_SYSTRAY (WM_APP + 6) -#define WM_SYSTRAY2 (WM_APP + 7) - -#define APPNAME "Pageant" - -/* Titles and class names for invisible windows. IPCWINTITLE and - * IPCCLASSNAME are critical to backwards compatibility: WM_COPYDATA - * based Pageant clients will call FindWindow with those parameters - * and expect to find the Pageant IPC receiver. */ -#define TRAYWINTITLE "Pageant" -#define TRAYCLASSNAME "PageantSysTray" -#define IPCWINTITLE "Pageant" -#define IPCCLASSNAME "Pageant" - -static HWND traywindow; -static HWND keylist; -static HWND aboutbox; -static HMENU systray_menu, session_menu; -static bool already_running; -static FingerprintType fptype = SSH_FPTYPE_DEFAULT; - -static char *putty_path; -static bool restrict_putty_acl = false; - -/* CWD for "add key" file requester. */ -static filereq *keypath = NULL; - -/* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of - * wParam are used by Windows, and should be masked off, so we shouldn't - * attempt to store information in them. Hence all these identifiers have - * the low 4 bits clear. Also, identifiers should < 0xF000. */ - -#define IDM_CLOSE 0x0010 -#define IDM_VIEWKEYS 0x0020 -#define IDM_ADDKEY 0x0030 -#define IDM_ADDKEY_ENCRYPTED 0x0040 -#define IDM_REMOVE_ALL 0x0050 -#define IDM_REENCRYPT_ALL 0x0060 -#define IDM_HELP 0x0070 -#define IDM_ABOUT 0x0080 -#define IDM_PUTTY 0x0090 -#define IDM_SESSIONS_BASE 0x1000 -#define IDM_SESSIONS_MAX 0x2000 -#define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions" -#define PUTTY_DEFAULT "Default%20Settings" -static int initial_menuitems_count; - -/* - * Print a modal (Really Bad) message box and perform a fatal exit. - */ -void modalfatalbox(const char *fmt, ...) -{ - va_list ap; - char *buf; - - va_start(ap, fmt); - buf = dupvprintf(fmt, ap); - va_end(ap); - MessageBox(traywindow, buf, "Pageant Fatal Error", - MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); - sfree(buf); - exit(1); -} - -struct PassphraseProcStruct { - bool modal; - const char *help_topic; - PageantClientDialogId *dlgid; - char *passphrase; - const char *comment; -}; - -/* - * Dialog-box function for the Licence box. - */ -static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: - SetDlgItemText(hwnd, IDC_LICENCE_TEXTBOX, LICENCE_TEXT("\r\n\r\n")); - return 1; - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - case IDCANCEL: - EndDialog(hwnd, 1); - return 0; - } - return 0; - case WM_CLOSE: - EndDialog(hwnd, 1); - return 0; - } - return 0; -} - -/* - * Dialog-box function for the About box. - */ -static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: { - char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf( - "Pageant\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); - sfree(buildinfo_text); - SetDlgItemText(hwnd, IDC_ABOUT_TEXTBOX, text); - MakeDlgItemBorderless(hwnd, IDC_ABOUT_TEXTBOX); - sfree(text); - return 1; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - case IDCANCEL: - aboutbox = NULL; - DestroyWindow(hwnd); - return 0; - case IDC_ABOUT_LICENCE: - EnableWindow(hwnd, 0); - DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCE), hwnd, LicenceProc); - EnableWindow(hwnd, 1); - SetActiveWindow(hwnd); - return 0; - case IDC_ABOUT_WEBSITE: - /* Load web browser */ - ShellExecute(hwnd, "open", - "https://www.chiark.greenend.org.uk/~sgtatham/putty/", - 0, 0, SW_SHOWDEFAULT); - return 0; - } - return 0; - case WM_CLOSE: - aboutbox = NULL; - DestroyWindow(hwnd); - return 0; - } - return 0; -} - -static HWND modal_passphrase_hwnd = NULL; -static HWND nonmodal_passphrase_hwnd = NULL; - -static void end_passphrase_dialog(HWND hwnd, INT_PTR result) -{ - struct PassphraseProcStruct *p = (struct PassphraseProcStruct *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - - if (p->modal) { - EndDialog(hwnd, result); - } else { - /* - * Destroy this passphrase dialog box before passing the - * results back to the main pageant.c, to avoid re-entrancy - * issues. - * - * If we successfully got a passphrase from the user, but it - * was _wrong_, then pageant_passphrase_request_success will - * respond by calling back - synchronously - to our - * ask_passphrase() implementation, which will expect the - * previous value of nonmodal_passphrase_hwnd to have already - * been cleaned up. - */ - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) NULL); - DestroyWindow(hwnd); - nonmodal_passphrase_hwnd = NULL; - - if (result) - pageant_passphrase_request_success( - p->dlgid, ptrlen_from_asciz(p->passphrase)); - else - pageant_passphrase_request_refused(p->dlgid); - - burnstr(p->passphrase); - sfree(p); - } -} - -/* - * Dialog-box function for the passphrase box. - */ -static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - struct PassphraseProcStruct *p; - - if (msg == WM_INITDIALOG) { - p = (struct PassphraseProcStruct *) lParam; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) p); - } else { - p = (struct PassphraseProcStruct *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - } - - switch (msg) { - case WM_INITDIALOG: { - if (p->modal) - modal_passphrase_hwnd = hwnd; - - /* - * Centre the window. - */ - RECT rs, rd; - HWND hw; - - hw = GetDesktopWindow(); - if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd)) - MoveWindow(hwnd, - (rs.right + rs.left + rd.left - rd.right) / 2, - (rs.bottom + rs.top + rd.top - rd.bottom) / 2, - rd.right - rd.left, rd.bottom - rd.top, true); - - SetForegroundWindow(hwnd); - SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); - if (!p->modal) - SetActiveWindow(hwnd); /* this won't have happened automatically */ - if (p->comment) - SetDlgItemText(hwnd, IDC_PASSPHRASE_FINGERPRINT, p->comment); - burnstr(p->passphrase); - p->passphrase = dupstr(""); - SetDlgItemText(hwnd, IDC_PASSPHRASE_EDITBOX, p->passphrase); - if (!p->help_topic || !has_help()) { - HWND item = GetDlgItem(hwnd, IDHELP); - if (item) - DestroyWindow(item); - } - return 0; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - if (p->passphrase) - end_passphrase_dialog(hwnd, 1); - else - MessageBeep(0); - return 0; - case IDCANCEL: - end_passphrase_dialog(hwnd, 0); - return 0; - case IDHELP: - if (p->help_topic) - launch_help(hwnd, p->help_topic); - return 0; - case IDC_PASSPHRASE_EDITBOX: - if ((HIWORD(wParam) == EN_CHANGE) && p->passphrase) { - burnstr(p->passphrase); - p->passphrase = GetDlgItemText_alloc( - hwnd, IDC_PASSPHRASE_EDITBOX); - } - return 0; - } - return 0; - case WM_CLOSE: - end_passphrase_dialog(hwnd, 0); - return 0; - } - return 0; -} - -/* - * Warn about the obsolescent key file format. - */ -void old_keyfile_warning(void) -{ - static const char mbtitle[] = "PuTTY Key File Warning"; - 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" - "You can perform this conversion by loading the key\n" - "into PuTTYgen and then saving it again."; - - MessageBox(NULL, message, mbtitle, MB_OK); -} - -struct keylist_update_ctx { - HDC hdc; - int algbitswidth, algwidth, bitswidth, hashwidth; - bool enable_remove_controls; - bool enable_reencrypt_controls; -}; - -struct keylist_display_data { - strbuf *alg, *bits, *hash, *comment, *info; -}; - -static void keylist_update_callback( - void *vctx, char **fingerprints, const char *comment, uint32_t ext_flags, - struct pageant_pubkey *key) -{ - struct keylist_update_ctx *ctx = (struct keylist_update_ctx *)vctx; - FingerprintType this_type = ssh2_pick_fingerprint(fingerprints, fptype); - ptrlen fingerprint = ptrlen_from_asciz(fingerprints[this_type]); - - struct keylist_display_data *disp = snew(struct keylist_display_data); - disp->alg = strbuf_new(); - disp->bits = strbuf_new(); - disp->hash = strbuf_new(); - disp->comment = strbuf_new(); - disp->info = strbuf_new(); - - /* There is at least one key, so the controls for removing keys - * should be enabled */ - ctx->enable_remove_controls = true; - - switch (key->ssh_version) { - case 1: { - /* - * Expect the fingerprint to contain two words: bit count and - * hash. - */ - put_dataz(disp->alg, "SSH-1"); - put_datapl(disp->bits, ptrlen_get_word(&fingerprint, " ")); - put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " ")); - break; - } - - case 2: { - /* - * Expect the fingerprint to contain three words: algorithm - * name, bit count, hash. - */ - const ssh_keyalg *alg = pubkey_blob_to_alg( - ptrlen_from_strbuf(key->blob)); - - ptrlen keytype_word = ptrlen_get_word(&fingerprint, " "); - if (alg) { - /* Use our own human-legible algorithm names if available, - * because they fit better in the space. (Certificate key - * algorithm names in particular are terribly long.) */ - char *alg_desc = ssh_keyalg_desc(alg); - put_dataz(disp->alg, alg_desc); - sfree(alg_desc); - } else { - put_datapl(disp->alg, keytype_word); - } - - ptrlen bits_word = ptrlen_get_word(&fingerprint, " "); - if (alg && ssh_keyalg_variable_size(alg)) - put_datapl(disp->bits, bits_word); - - put_datapl(disp->hash, ptrlen_get_word(&fingerprint, " ")); - } - } - - put_dataz(disp->comment, comment); - - SIZE sz; - if (disp->bits->len) { - GetTextExtentPoint32(ctx->hdc, disp->alg->s, disp->alg->len, &sz); - if (ctx->algwidth < sz.cx) ctx->algwidth = sz.cx; - GetTextExtentPoint32(ctx->hdc, disp->bits->s, disp->bits->len, &sz); - if (ctx->bitswidth < sz.cx) ctx->bitswidth = sz.cx; - } else { - GetTextExtentPoint32(ctx->hdc, disp->alg->s, disp->alg->len, &sz); - if (ctx->algbitswidth < sz.cx) ctx->algbitswidth = sz.cx; - } - GetTextExtentPoint32(ctx->hdc, disp->hash->s, disp->hash->len, &sz); - if (ctx->hashwidth < sz.cx) ctx->hashwidth = sz.cx; - - if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) { - put_fmt(disp->info, "(encrypted)"); - } else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) { - put_fmt(disp->info, "(re-encryptable)"); - - /* At least one key can be re-encrypted */ - ctx->enable_reencrypt_controls = true; - } - - /* This list box is owner-drawn but doesn't have LBS_HASSTRINGS, - * so we can use LB_ADDSTRING to hand the list box our display - * info pointer */ - SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, - LB_ADDSTRING, 0, (LPARAM)disp); -} - -/* Column start positions for the list box, in pixels (not dialog units). */ -static int colpos_bits, colpos_hash, colpos_comment; - -/* - * Update the visible key list. - */ -void keylist_update(void) -{ - if (keylist) { - /* - * Clear the previous list box content and free their display - * structures. - */ - { - int nitems = SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, - LB_GETCOUNT, 0, 0); - for (int i = 0; i < nitems; i++) { - struct keylist_display_data *disp = - (struct keylist_display_data *)SendDlgItemMessage( - keylist, IDC_KEYLIST_LISTBOX, LB_GETITEMDATA, i, 0); - strbuf_free(disp->alg); - strbuf_free(disp->bits); - strbuf_free(disp->hash); - strbuf_free(disp->comment); - strbuf_free(disp->info); - sfree(disp); - } - } - SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, - LB_RESETCONTENT, 0, 0); - - char *errmsg; - struct keylist_update_ctx ctx[1]; - ctx->enable_remove_controls = false; - ctx->enable_reencrypt_controls = false; - ctx->algbitswidth = ctx->algwidth = 0; - ctx->bitswidth = ctx->hashwidth = 0; - ctx->hdc = GetDC(keylist); - SelectObject(ctx->hdc, (HFONT)SendMessage(keylist, WM_GETFONT, 0, 0)); - int status = pageant_enum_keys(keylist_update_callback, ctx, &errmsg); - - SIZE sz; - GetTextExtentPoint32(ctx->hdc, "MM", 2, &sz); - int gutter = sz.cx; - - DeleteDC(ctx->hdc); - colpos_hash = ctx->algwidth + ctx->bitswidth + 2*gutter; - if (colpos_hash < ctx->algbitswidth + gutter) - colpos_hash = ctx->algbitswidth + gutter; - colpos_bits = colpos_hash - ctx->bitswidth - gutter; - colpos_comment = colpos_hash + ctx->hashwidth + gutter; - assert(status == PAGEANT_ACTION_OK); - assert(!errmsg); - - SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, - LB_SETCURSEL, (WPARAM) - 1, 0); - - EnableWindow(GetDlgItem(keylist, IDC_KEYLIST_REMOVE), - ctx->enable_remove_controls); - EnableWindow(GetDlgItem(keylist, IDC_KEYLIST_REENCRYPT), - ctx->enable_reencrypt_controls); - } -} - -static void win_add_keyfile(Filename *filename, bool encrypted) -{ - char *err; - int ret; - - /* - * Try loading the key without a passphrase. (Or rather, without a - * _new_ passphrase; pageant_add_keyfile will take care of trying - * all the passphrases we've already stored.) - */ - ret = pageant_add_keyfile(filename, NULL, &err, encrypted); - if (ret == PAGEANT_ACTION_OK) { - goto done; - } else if (ret == PAGEANT_ACTION_FAILURE) { - goto error; - } - - /* - * OK, a passphrase is needed, and we've been given the key - * comment to use in the passphrase prompt. - */ - while (1) { - INT_PTR dlgret; - struct PassphraseProcStruct pps; - pps.modal = true; - pps.help_topic = NULL; /* this dialog has no help button */ - pps.dlgid = NULL; - pps.passphrase = NULL; - pps.comment = err; - dlgret = DialogBoxParam( - hinst, MAKEINTRESOURCE(IDD_LOAD_PASSPHRASE), - NULL, PassphraseProc, (LPARAM) &pps); - modal_passphrase_hwnd = NULL; - - if (!dlgret) { - burnstr(pps.passphrase); - goto done; /* operation cancelled */ - } - - sfree(err); - - assert(pps.passphrase != NULL); - - ret = pageant_add_keyfile(filename, pps.passphrase, &err, false); - burnstr(pps.passphrase); - - if (ret == PAGEANT_ACTION_OK) { - goto done; - } else if (ret == PAGEANT_ACTION_FAILURE) { - goto error; - } - } - - error: - message_box(traywindow, err, APPNAME, MB_OK | MB_ICONERROR, false, - HELPCTXID(errors_cantloadkey)); - done: - sfree(err); - return; -} - -/* - * Prompt for a key file to add, and add it. - */ -static void prompt_add_keyfile(bool encrypted) -{ - OPENFILENAME of; - char *filelist = snewn(8192, char); - - if (!keypath) keypath = filereq_new(); - memset(&of, 0, sizeof(of)); - of.hwndOwner = traywindow; - of.lpstrFilter = FILTER_KEY_FILES_C; - of.lpstrCustomFilter = NULL; - of.nFilterIndex = 1; - of.lpstrFile = filelist; - *filelist = '\0'; - of.nMaxFile = 8192; - of.lpstrFileTitle = NULL; - of.lpstrTitle = "Select Private Key File"; - of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER; - if (request_file(keypath, &of, true, false)) { - if (strlen(filelist) > of.nFileOffset) { - /* Only one filename returned? */ - Filename *fn = filename_from_str(filelist); - win_add_keyfile(fn, encrypted); - filename_free(fn); - } else { - /* we are returned a bunch of strings, end to - * end. first string is the directory, the - * rest the filenames. terminated with an - * empty string. - */ - char *dir = filelist; - char *filewalker = filelist + strlen(dir) + 1; - while (*filewalker != '\0') { - char *filename = dupcat(dir, "\\", filewalker); - Filename *fn = filename_from_str(filename); - win_add_keyfile(fn, encrypted); - filename_free(fn); - sfree(filename); - filewalker += strlen(filewalker) + 1; - } - } - - keylist_update(); - pageant_forget_passphrases(); - } - sfree(filelist); -} - -/* - * Dialog-box function for the key list box. - */ -static INT_PTR CALLBACK KeyListProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - static const struct { - const char *name; - FingerprintType value; - } fptypes[] = { - {"SHA256", SSH_FPTYPE_SHA256}, - {"MD5", SSH_FPTYPE_MD5}, - {"SHA256 including certificate", SSH_FPTYPE_SHA256_CERT}, - {"MD5 including certificate", SSH_FPTYPE_MD5_CERT}, - }; - - switch (msg) { - case WM_INITDIALOG: { - /* - * Centre the window. - */ - RECT rs, rd; - HWND hw; - - hw = GetDesktopWindow(); - if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd)) - MoveWindow(hwnd, - (rs.right + rs.left + rd.left - rd.right) / 2, - (rs.bottom + rs.top + rd.top - rd.bottom) / 2, - rd.right - rd.left, rd.bottom - rd.top, true); - - if (has_help()) - SetWindowLongPtr(hwnd, GWL_EXSTYLE, - GetWindowLongPtr(hwnd, GWL_EXSTYLE) | - WS_EX_CONTEXTHELP); - else { - HWND item = GetDlgItem(hwnd, IDC_KEYLIST_HELP); - if (item) - DestroyWindow(item); - } - - keylist = hwnd; - - int selection = 0; - for (size_t i = 0; i < lenof(fptypes); i++) { - SendDlgItemMessage(hwnd, IDC_KEYLIST_FPTYPE, CB_ADDSTRING, - 0, (LPARAM)fptypes[i].name); - if (fptype == fptypes[i].value) - selection = (int)i; - } - SendDlgItemMessage(hwnd, IDC_KEYLIST_FPTYPE, - CB_SETCURSEL, 0, selection); - - keylist_update(); - return 0; - } - case WM_MEASUREITEM: { - assert(wParam == IDC_KEYLIST_LISTBOX); - - MEASUREITEMSTRUCT *mi = (MEASUREITEMSTRUCT *)lParam; - - /* - * Our list box is owner-drawn, but we put normal text in it. - * So the line height is the same as it would normally be, - * which is 8 dialog units. - */ - RECT r; - r.left = r.right = r.top = 0; - r.bottom = 8; - MapDialogRect(hwnd, &r); - mi->itemHeight = r.bottom; - - return 0; - } - case WM_DRAWITEM: { - assert(wParam == IDC_KEYLIST_LISTBOX); - - DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT *)lParam; - - if (di->itemAction == ODA_FOCUS) { - /* Just toggle the focus rectangle either on or off. This - * is an XOR-type function, so it's the same call in - * either case. */ - DrawFocusRect(di->hDC, &di->rcItem); - } else { - /* Draw the full text. */ - bool selected = (di->itemState & ODS_SELECTED); - COLORREF newfg = GetSysColor( - selected ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT); - COLORREF newbg = GetSysColor( - selected ? COLOR_HIGHLIGHT : COLOR_WINDOW); - COLORREF oldfg = SetTextColor(di->hDC, newfg); - COLORREF oldbg = SetBkColor(di->hDC, newbg); - - HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0); - HFONT oldfont = SelectObject(di->hDC, font); - - /* ExtTextOut("") is an easy way to just draw the - * background rectangle */ - ExtTextOut(di->hDC, di->rcItem.left, di->rcItem.top, - ETO_OPAQUE | ETO_CLIPPED, &di->rcItem, "", 0, NULL); - - struct keylist_display_data *disp = - (struct keylist_display_data *)di->itemData; - - RECT r; - - /* Apparently real list boxes start drawing at x=1, not x=0 */ - r.left = r.top = r.bottom = 0; - r.right = 1; - MapDialogRect(hwnd, &r); - ExtTextOut(di->hDC, di->rcItem.left + r.right, di->rcItem.top, - ETO_CLIPPED, &di->rcItem, disp->alg->s, - disp->alg->len, NULL); - - if (disp->bits->len) { - ExtTextOut(di->hDC, di->rcItem.left + r.right + colpos_bits, - di->rcItem.top, ETO_CLIPPED, &di->rcItem, - disp->bits->s, disp->bits->len, NULL); - } - - ExtTextOut(di->hDC, di->rcItem.left + r.right + colpos_hash, - di->rcItem.top, ETO_CLIPPED, &di->rcItem, - disp->hash->s, disp->hash->len, NULL); - - strbuf *sb = strbuf_new(); - put_datapl(sb, ptrlen_from_strbuf(disp->comment)); - if (disp->info->len) { - put_byte(sb, '\t'); - put_datapl(sb, ptrlen_from_strbuf(disp->info)); - } - - TabbedTextOut(di->hDC, di->rcItem.left + r.right + colpos_comment, - di->rcItem.top, sb->s, sb->len, 0, NULL, 0); - - strbuf_free(sb); - - SetTextColor(di->hDC, oldfg); - SetBkColor(di->hDC, oldbg); - SelectObject(di->hDC, oldfont); - - if (di->itemState & ODS_FOCUS) - DrawFocusRect(di->hDC, &di->rcItem); - } - return 0; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - case IDCANCEL: - keylist = NULL; - DestroyWindow(hwnd); - return 0; - case IDC_KEYLIST_ADDKEY: - case IDC_KEYLIST_ADDKEY_ENC: - if (HIWORD(wParam) == BN_CLICKED || - HIWORD(wParam) == BN_DOUBLECLICKED) { - if (modal_passphrase_hwnd) { - MessageBeep(MB_ICONERROR); - SetForegroundWindow(modal_passphrase_hwnd); - break; - } - prompt_add_keyfile(LOWORD(wParam) == IDC_KEYLIST_ADDKEY_ENC); - } - return 0; - case IDC_KEYLIST_REMOVE: - case IDC_KEYLIST_REENCRYPT: - if (HIWORD(wParam) == BN_CLICKED || - HIWORD(wParam) == BN_DOUBLECLICKED) { - int i; - int rCount, sCount; - int *selectedArray; - - /* our counter within the array of selected items */ - int itemNum; - - /* get the number of items selected in the list */ - int numSelected = SendDlgItemMessage( - hwnd, IDC_KEYLIST_LISTBOX, LB_GETSELCOUNT, 0, 0); - - /* none selected? that was silly */ - if (numSelected == 0) { - MessageBeep(0); - break; - } - - /* get item indices in an array */ - selectedArray = snewn(numSelected, int); - SendDlgItemMessage(hwnd, IDC_KEYLIST_LISTBOX, LB_GETSELITEMS, - numSelected, (WPARAM)selectedArray); - - itemNum = numSelected - 1; - rCount = pageant_count_ssh1_keys(); - sCount = pageant_count_ssh2_keys(); - - /* go through the non-rsakeys until we've covered them all, - * and/or we're out of selected items to check. note that - * we go *backwards*, to avoid complications from deleting - * things hence altering the offset of subsequent items - */ - for (i = sCount - 1; (itemNum >= 0) && (i >= 0); i--) { - if (selectedArray[itemNum] == rCount + i) { - switch (LOWORD(wParam)) { - case IDC_KEYLIST_REMOVE: - pageant_delete_nth_ssh2_key(i); - break; - case IDC_KEYLIST_REENCRYPT: - pageant_reencrypt_nth_ssh2_key(i); - break; - } - itemNum--; - } - } - - /* do the same for the rsa keys */ - for (i = rCount - 1; (itemNum >= 0) && (i >= 0); i--) { - if (selectedArray[itemNum] == i) { - switch (LOWORD(wParam)) { - case IDC_KEYLIST_REMOVE: - pageant_delete_nth_ssh1_key(i); - break; - case IDC_KEYLIST_REENCRYPT: - /* SSH-1 keys can't be re-encrypted */ - break; - } - itemNum--; - } - } - - sfree(selectedArray); - keylist_update(); - } - return 0; - case IDC_KEYLIST_HELP: - if (HIWORD(wParam) == BN_CLICKED || - HIWORD(wParam) == BN_DOUBLECLICKED) { - launch_help(hwnd, WINHELP_CTX_pageant_general); - } - return 0; - case IDC_KEYLIST_FPTYPE: - if (HIWORD(wParam) == CBN_SELCHANGE) { - int selection = SendDlgItemMessage( - hwnd, IDC_KEYLIST_FPTYPE, CB_GETCURSEL, 0, 0); - if (selection >= 0 && (size_t)selection < lenof(fptypes)) { - fptype = fptypes[selection].value; - keylist_update(); - } - } - return 0; - } - return 0; - case WM_HELP: { - int id = ((LPHELPINFO)lParam)->iCtrlId; - const char *topic = NULL; - switch (id) { - case IDC_KEYLIST_LISTBOX: - case IDC_KEYLIST_FPTYPE: - case IDC_KEYLIST_FPTYPE_STATIC: - topic = WINHELP_CTX_pageant_keylist; break; - case IDC_KEYLIST_ADDKEY: topic = WINHELP_CTX_pageant_addkey; break; - case IDC_KEYLIST_REMOVE: topic = WINHELP_CTX_pageant_remkey; break; - case IDC_KEYLIST_ADDKEY_ENC: - case IDC_KEYLIST_REENCRYPT: - topic = WINHELP_CTX_pageant_deferred; break; - } - if (topic) { - launch_help(hwnd, topic); - } else { - MessageBeep(0); - } - break; - } - case WM_CLOSE: - keylist = NULL; - DestroyWindow(hwnd); - return 0; - } - return 0; -} - -/* Set up a system tray icon */ -static BOOL AddTrayIcon(HWND hwnd) -{ - BOOL res; - NOTIFYICONDATA tnid; - HICON hicon; - -#ifdef NIM_SETVERSION - tnid.uVersion = 0; - res = Shell_NotifyIcon(NIM_SETVERSION, &tnid); -#endif - - tnid.cbSize = sizeof(NOTIFYICONDATA); - tnid.hWnd = hwnd; - tnid.uID = 1; /* unique within this systray use */ - tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; - tnid.uCallbackMessage = WM_SYSTRAY; - tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201)); - strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)"); - - res = Shell_NotifyIcon(NIM_ADD, &tnid); - - if (hicon) DestroyIcon(hicon); - - return res; -} - -/* Update the saved-sessions menu. */ -static void update_sessions(void) -{ - int num_entries; - HKEY hkey; - TCHAR buf[MAX_PATH + 1]; - MENUITEMINFO mii; - strbuf *sb; - - int index_key, index_menu; - - if (!putty_path) - return; - - if (ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, PUTTY_REGKEY, &hkey)) - return; - - for (num_entries = GetMenuItemCount(session_menu); - num_entries > initial_menuitems_count; - num_entries--) - RemoveMenu(session_menu, 0, MF_BYPOSITION); - - index_key = 0; - index_menu = 0; - - sb = strbuf_new(); - while (ERROR_SUCCESS == RegEnumKey(hkey, index_key, buf, MAX_PATH)) { - if (strcmp(buf, PUTTY_DEFAULT) != 0) { - strbuf_clear(sb); - unescape_registry_key(buf, sb); - - memset(&mii, 0, sizeof(mii)); - mii.cbSize = sizeof(mii); - mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID; - mii.fType = MFT_STRING; - mii.fState = MFS_ENABLED; - mii.wID = (index_menu * 16) + IDM_SESSIONS_BASE; - mii.dwTypeData = sb->s; - InsertMenuItem(session_menu, index_menu, true, &mii); - index_menu++; - } - index_key++; - } - strbuf_free(sb); - - RegCloseKey(hkey); - - if (index_menu == 0) { - mii.cbSize = sizeof(mii); - mii.fMask = MIIM_TYPE | MIIM_STATE; - mii.fType = MFT_STRING; - mii.fState = MFS_GRAYED; - mii.dwTypeData = _T("(No sessions)"); - InsertMenuItem(session_menu, index_menu, true, &mii); - } -} - -/* - * Versions of Pageant prior to 0.61 expected this SID on incoming - * communications. For backwards compatibility, and more particularly - * for compatibility with derived works of PuTTY still using the old - * Pageant client code, we accept it as an alternative to the one - * returned from get_user_sid(). - */ -PSID get_default_sid(void) -{ - HANDLE proc = NULL; - DWORD sidlen; - PSECURITY_DESCRIPTOR psd = NULL; - PSID sid = NULL, copy = NULL, ret = NULL; - - if ((proc = OpenProcess(MAXIMUM_ALLOWED, false, - GetCurrentProcessId())) == NULL) - goto cleanup; - - if (p_GetSecurityInfo(proc, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, - &sid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS) - goto cleanup; - - sidlen = GetLengthSid(sid); - - copy = (PSID)smalloc(sidlen); - - if (!CopySid(sidlen, copy, sid)) - goto cleanup; - - /* Success. Move sid into the return value slot, and null it out - * to stop the cleanup code freeing it. */ - ret = copy; - copy = NULL; - - cleanup: - if (proc != NULL) - CloseHandle(proc); - if (psd != NULL) - LocalFree(psd); - if (copy != NULL) - sfree(copy); - - return ret; -} - -struct WmCopydataTransaction { - char *length, *body; - size_t bodysize, bodylen; - HANDLE ev_msg_ready, ev_reply_ready; -} wmct; - -static struct PageantClient wmcpc; - -static void wm_copydata_got_msg(void *vctx) -{ - pageant_handle_msg(&wmcpc, NULL, make_ptrlen(wmct.body, wmct.bodylen)); -} - -static void wm_copydata_got_response( - PageantClient *pc, PageantClientRequestId *reqid, ptrlen response) -{ - if (response.len > wmct.bodysize) { - /* Output would overflow message buffer. Replace with a - * failure message. */ - static const unsigned char failure[] = { SSH_AGENT_FAILURE }; - response = make_ptrlen(failure, lenof(failure)); - assert(response.len <= wmct.bodysize); - } - - PUT_32BIT_MSB_FIRST(wmct.length, response.len); - memcpy(wmct.body, response.ptr, response.len); - - SetEvent(wmct.ev_reply_ready); -} - -static bool ask_passphrase_common(PageantClientDialogId *dlgid, - const char *comment) -{ - /* Pageant core should be serialising requests, so we never expect - * a passphrase prompt to exist already at this point */ - assert(!nonmodal_passphrase_hwnd); - - struct PassphraseProcStruct *pps = snew(struct PassphraseProcStruct); - pps->modal = false; - pps->help_topic = WINHELP_CTX_pageant_deferred; - pps->dlgid = dlgid; - pps->passphrase = NULL; - pps->comment = comment; - - nonmodal_passphrase_hwnd = CreateDialogParam( - hinst, MAKEINTRESOURCE(IDD_ONDEMAND_PASSPHRASE), - NULL, PassphraseProc, (LPARAM)pps); - - /* - * Try to put this passphrase prompt into the foreground. - * - * This will probably not succeed in giving it the actual keyboard - * focus, because Windows is quite opposed to applications being - * able to suddenly steal the focus on their own initiative. - * - * That makes sense in a lot of situations, as a defensive - * measure. If you were about to type a password or other secret - * data into the window you already had focused, and some - * malicious app stole the focus, it might manage to trick you - * into typing your secrets into _it_ instead. - * - * In this case it's possible to regard the same defensive measure - * as counterproductive, because the effect if we _do_ steal focus - * is that you type something into our passphrase prompt that - * isn't the passphrase, and we fail to decrypt the key, and no - * harm is done. Whereas the effect of the user wrongly _assuming_ - * the new passphrase prompt has the focus is much worse: now you - * type your highly secret passphrase into some other window you - * didn't mean to trust with that information - such as the - * agent-forwarded PuTTY in which you just ran an ssh command, - * which the _whole point_ was to avoid telling your passphrase to! - * - * On the other hand, I'm sure _every_ application author can come - * up with an argument for why they think _they_ should be allowed - * to steal the focus. Probably most of them include the claim - * that no harm is done if their application receives data - * intended for something else, and of course that's not always - * true! - * - * In any case, I don't know of anything I can do about it, or - * anything I _should_ do about it if I could. If anyone thinks - * they can improve on all this, patches are welcome. - */ - SetForegroundWindow(nonmodal_passphrase_hwnd); - - return true; -} - -static bool wm_copydata_ask_passphrase( - PageantClient *pc, PageantClientDialogId *dlgid, const char *comment) -{ - return ask_passphrase_common(dlgid, comment); -} - -static const PageantClientVtable wmcpc_vtable = { - .log = NULL, /* no logging in this client */ - .got_response = wm_copydata_got_response, - .ask_passphrase = wm_copydata_ask_passphrase, -}; - -static char *answer_filemapping_message(const char *mapname) -{ - HANDLE maphandle = INVALID_HANDLE_VALUE; - void *mapaddr = NULL; - char *err = NULL; - size_t mapsize; - unsigned msglen; - - PSID mapsid = NULL; - PSID expectedsid = NULL; - PSID expectedsid_bc = NULL; - PSECURITY_DESCRIPTOR psd = NULL; - - wmct.length = wmct.body = NULL; - -#ifdef DEBUG_IPC - debug("mapname = \"%s\"\n", mapname); -#endif - - maphandle = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, mapname); - if (maphandle == NULL || maphandle == INVALID_HANDLE_VALUE) { - err = dupprintf("OpenFileMapping(\"%s\"): %s", - mapname, win_strerror(GetLastError())); - goto cleanup; - } - -#ifdef DEBUG_IPC - debug("maphandle = %p\n", maphandle); -#endif - - if (should_have_security()) { - DWORD retd; - - if ((expectedsid = get_user_sid()) == NULL) { - err = dupstr("unable to get user SID"); - goto cleanup; - } - - if ((expectedsid_bc = get_default_sid()) == NULL) { - err = dupstr("unable to get default SID"); - goto cleanup; - } - - if ((retd = p_GetSecurityInfo( - maphandle, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, - &mapsid, NULL, NULL, NULL, &psd) != ERROR_SUCCESS)) { - err = dupprintf("unable to get owner of file mapping: " - "GetSecurityInfo returned: %s", - win_strerror(retd)); - goto cleanup; - } - -#ifdef DEBUG_IPC - { - LPTSTR ours, ours2, theirs; - ConvertSidToStringSid(mapsid, &theirs); - ConvertSidToStringSid(expectedsid, &ours); - ConvertSidToStringSid(expectedsid_bc, &ours2); - debug("got sids:\n oursnew=%s\n oursold=%s\n" - " theirs=%s\n", ours, ours2, theirs); - LocalFree(ours); - LocalFree(ours2); - LocalFree(theirs); - } -#endif - - if (!EqualSid(mapsid, expectedsid) && - !EqualSid(mapsid, expectedsid_bc)) { - err = dupstr("wrong owning SID of file mapping"); - goto cleanup; - } - } else { -#ifdef DEBUG_IPC - debug("security APIs not present\n"); -#endif - } - - mapaddr = MapViewOfFile(maphandle, FILE_MAP_WRITE, 0, 0, 0); - if (!mapaddr) { - err = dupprintf("unable to obtain view of file mapping: %s", - win_strerror(GetLastError())); - goto cleanup; - } - -#ifdef DEBUG_IPC - debug("mapped address = %p\n", mapaddr); -#endif - - { - MEMORY_BASIC_INFORMATION mbi; - size_t mbiSize = VirtualQuery(mapaddr, &mbi, sizeof(mbi)); - if (mbiSize == 0) { - err = dupprintf("unable to query view of file mapping: %s", - win_strerror(GetLastError())); - goto cleanup; - } - if (mbiSize < (offsetof(MEMORY_BASIC_INFORMATION, RegionSize) + - sizeof(mbi.RegionSize))) { - err = dupstr("VirtualQuery returned too little data to get " - "region size"); - goto cleanup; - } - - mapsize = mbi.RegionSize; - } -#ifdef DEBUG_IPC - debug("region size = %"SIZEu"\n", mapsize); -#endif - if (mapsize < 5) { - err = dupstr("mapping smaller than smallest possible request"); - goto cleanup; - } - - wmct.length = (char *)mapaddr; - msglen = GET_32BIT_MSB_FIRST(wmct.length); - -#ifdef DEBUG_IPC - debug("msg length=%08x, msg type=%02x\n", - msglen, (unsigned)((unsigned char *) mapaddr)[4]); -#endif - - wmct.body = wmct.length + 4; - wmct.bodysize = mapsize - 4; - - if (msglen > wmct.bodysize) { - /* Incoming length field is too large. Emit a failure response - * without even trying to handle the request. - * - * (We know this must fit, because we checked mapsize >= 5 - * above.) */ - PUT_32BIT_MSB_FIRST(wmct.length, 1); - *wmct.body = SSH_AGENT_FAILURE; - } else { - wmct.bodylen = msglen; - SetEvent(wmct.ev_msg_ready); - WaitForSingleObject(wmct.ev_reply_ready, INFINITE); - } - - cleanup: - /* expectedsid has the lifetime of the program, so we don't free it */ - sfree(expectedsid_bc); - if (psd) - LocalFree(psd); - if (mapaddr) - UnmapViewOfFile(mapaddr); - if (maphandle != NULL && maphandle != INVALID_HANDLE_VALUE) - CloseHandle(maphandle); - return err; -} - -static void create_keylist_window(void) -{ - if (keylist) - return; - - keylist = CreateDialog(hinst, MAKEINTRESOURCE(IDD_KEYLIST), - NULL, KeyListProc); - ShowWindow(keylist, SW_SHOWNORMAL); -} - -static LRESULT CALLBACK TrayWndProc(HWND hwnd, UINT message, - WPARAM wParam, LPARAM lParam) -{ - static bool menuinprogress; - static UINT msgTaskbarCreated = 0; - - switch (message) { - case WM_CREATE: - msgTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated")); - break; - default: - if (message==msgTaskbarCreated) { - /* - * Explorer has been restarted, so the tray icon will - * have been lost. - */ - AddTrayIcon(hwnd); - } - break; - - case WM_SYSTRAY: - if (lParam == WM_RBUTTONUP) { - POINT cursorpos; - GetCursorPos(&cursorpos); - PostMessage(hwnd, WM_SYSTRAY2, cursorpos.x, cursorpos.y); - } else if (lParam == WM_LBUTTONDBLCLK) { - /* Run the default menu item. */ - UINT menuitem = GetMenuDefaultItem(systray_menu, false, 0); - if (menuitem != -1) - PostMessage(hwnd, WM_COMMAND, menuitem, 0); - } - break; - case WM_SYSTRAY2: - if (!menuinprogress) { - menuinprogress = true; - update_sessions(); - SetForegroundWindow(hwnd); - TrackPopupMenu(systray_menu, - TPM_RIGHTALIGN | TPM_BOTTOMALIGN | - TPM_RIGHTBUTTON, - wParam, lParam, 0, hwnd, NULL); - menuinprogress = false; - } - break; - case WM_COMMAND: - case WM_SYSCOMMAND: { - unsigned command = wParam & ~0xF; /* low 4 bits reserved to Windows */ - switch (command) { - case IDM_PUTTY: { - TCHAR cmdline[10]; - cmdline[0] = '\0'; - if (restrict_putty_acl) - strcat(cmdline, "&R"); - - if ((INT_PTR)ShellExecute(hwnd, NULL, putty_path, cmdline, - _T(""), SW_SHOW) <= 32) { - MessageBox(NULL, "Unable to execute PuTTY!", - "Error", MB_OK | MB_ICONERROR); - } - break; - } - case IDM_CLOSE: - if (modal_passphrase_hwnd) - SendMessage(modal_passphrase_hwnd, WM_CLOSE, 0, 0); - SendMessage(hwnd, WM_CLOSE, 0, 0); - break; - case IDM_VIEWKEYS: - create_keylist_window(); - /* - * Sometimes the window comes up minimised / hidden for - * no obvious reason. Prevent this. This also brings it - * to the front if it's already present (the user - * selected View Keys because they wanted to _see_ the - * thing). - */ - SetForegroundWindow(keylist); - SetWindowPos(keylist, HWND_TOP, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); - break; - case IDM_ADDKEY: - case IDM_ADDKEY_ENCRYPTED: - if (modal_passphrase_hwnd) { - MessageBeep(MB_ICONERROR); - SetForegroundWindow(modal_passphrase_hwnd); - break; - } - prompt_add_keyfile(command == IDM_ADDKEY_ENCRYPTED); - break; - case IDM_REMOVE_ALL: - pageant_delete_all(); - keylist_update(); - break; - case IDM_REENCRYPT_ALL: - pageant_reencrypt_all(); - keylist_update(); - break; - case IDM_ABOUT: - if (!aboutbox) { - aboutbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUT), - NULL, AboutProc); - ShowWindow(aboutbox, SW_SHOWNORMAL); - /* - * Sometimes the window comes up minimised / hidden - * for no obvious reason. Prevent this. - */ - SetForegroundWindow(aboutbox); - SetWindowPos(aboutbox, HWND_TOP, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); - } - break; - case IDM_HELP: - launch_help(hwnd, WINHELP_CTX_pageant_general); - break; - default: { - if (wParam >= IDM_SESSIONS_BASE && wParam <= IDM_SESSIONS_MAX) { - MENUITEMINFO mii; - TCHAR buf[MAX_PATH + 1]; - TCHAR param[MAX_PATH + 1]; - memset(&mii, 0, sizeof(mii)); - mii.cbSize = sizeof(mii); - mii.fMask = MIIM_TYPE; - mii.cch = MAX_PATH; - mii.dwTypeData = buf; - GetMenuItemInfo(session_menu, wParam, false, &mii); - param[0] = '\0'; - if (restrict_putty_acl) - strcat(param, "&R"); - strcat(param, "@"); - strcat(param, mii.dwTypeData); - if ((INT_PTR)ShellExecute(hwnd, NULL, putty_path, param, - _T(""), SW_SHOW) <= 32) { - MessageBox(NULL, "Unable to execute PuTTY!", "Error", - MB_OK | MB_ICONERROR); - } - } - break; - } - } - break; - } - case WM_NETEVENT: - winselgui_response(wParam, lParam); - return 0; - case WM_DESTROY: - quit_help(hwnd); - PostQuitMessage(0); - return 0; - } - - return DefWindowProc(hwnd, message, wParam, lParam); -} - -static LRESULT CALLBACK wm_copydata_WndProc(HWND hwnd, UINT message, - WPARAM wParam, LPARAM lParam) -{ - switch (message) { - case WM_COPYDATA: { - COPYDATASTRUCT *cds; - char *mapname, *err; - - cds = (COPYDATASTRUCT *) lParam; - if (cds->dwData != AGENT_COPYDATA_ID) - return 0; /* not our message, mate */ - mapname = (char *) cds->lpData; - if (mapname[cds->cbData - 1] != '\0') - return 0; /* failure to be ASCIZ! */ - err = answer_filemapping_message(mapname); - if (err) { -#ifdef DEBUG_IPC - debug("IPC failed: %s\n", err); -#endif - sfree(err); - return 0; - } - return 1; - } - } - - return DefWindowProc(hwnd, message, wParam, lParam); -} - -static DWORD WINAPI wm_copydata_threadfunc(void *param) -{ - HINSTANCE inst = *(HINSTANCE *)param; - - HWND ipchwnd = CreateWindow(IPCCLASSNAME, IPCWINTITLE, - WS_OVERLAPPEDWINDOW | WS_VSCROLL, - CW_USEDEFAULT, CW_USEDEFAULT, - 100, 100, NULL, NULL, inst, NULL); - ShowWindow(ipchwnd, SW_HIDE); - - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) == 1) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - return 0; -} - -/* - * Fork and Exec the command in cmdline. [DBW] - */ -void spawn_cmd(const char *cmdline, const char *args, int show) -{ - if (ShellExecute(NULL, _T("open"), cmdline, - args, NULL, show) <= (HINSTANCE) 32) { - char *msg; - msg = dupprintf("Failed to run \"%s\": %s", cmdline, - win_strerror(GetLastError())); - MessageBox(NULL, msg, APPNAME, MB_OK | MB_ICONEXCLAMATION); - sfree(msg); - } -} - -void noise_ultralight(NoiseSourceId id, unsigned long data) -{ - /* Pageant doesn't use random numbers, so we ignore this */ -} - -void cleanup_exit(int code) -{ - shutdown_help(); - exit(code); -} - -static bool winpgnt_listener_ask_passphrase( - PageantListenerClient *plc, PageantClientDialogId *dlgid, - const char *comment) -{ - return ask_passphrase_common(dlgid, comment); -} - -struct winpgnt_client { - PageantListenerClient plc; -}; -static const PageantListenerClientVtable winpgnt_vtable = { - .log = NULL, /* no logging */ - .ask_passphrase = winpgnt_listener_ask_passphrase, -}; - -static struct winpgnt_client wpc[1]; - -HINSTANCE hinst; - -static NORETURN void opt_error(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - char *msg = dupvprintf(fmt, ap); - va_end(ap); - - MessageBox(NULL, msg, "Pageant command line error", MB_ICONERROR | MB_OK); - - exit(1); -} - -#ifdef LEGACY_WINDOWS -BOOL sw_PeekMessage(LPMSG msg, HWND hwnd, UINT min, UINT max, UINT remove) -{ - static bool unicode_unavailable = false; - if (!unicode_unavailable) { - BOOL ret = PeekMessageW(msg, hwnd, min, max, remove); - if (!ret && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) - unicode_unavailable = true; /* don't try again */ - else - return ret; - } - return PeekMessageA(msg, hwnd, min, max, remove); -} -#else -#define sw_PeekMessage PeekMessageW -#endif - -int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) -{ - MSG msg; - const char *command = NULL; - const char *unixsocket = NULL; - bool show_keylist_on_startup = false; - int argc; - char **argv, **argstart; - const char *openssh_config_file = NULL; - - typedef struct CommandLineKey { - Filename *fn; - bool add_encrypted; - } CommandLineKey; - - CommandLineKey *clkeys = NULL; - size_t nclkeys = 0, clkeysize = 0; - - dll_hijacking_protection(); - - hinst = inst; - - if (should_have_security()) { - /* - * Attempt to get the security API we need. - */ - if (!got_advapi()) { - MessageBox(NULL, - "Unable to access security APIs. Pageant will\n" - "not run, in case it causes a security breach.", - "Pageant Fatal Error", MB_ICONERROR | MB_OK); - return 1; - } - } - - /* - * See if we can find our Help file. - */ - init_help(); - - /* - * Look for the PuTTY binary (we will enable the saved session - * submenu if we find it). - */ - { - char b[2048], *p, *q, *r; - FILE *fp; - GetModuleFileName(NULL, b, sizeof(b) - 16); - r = b; - p = strrchr(b, '\\'); - if (p && p >= r) r = p+1; - q = strrchr(b, ':'); - if (q && q >= r) r = q+1; - strcpy(r, "putty.exe"); - if ( (fp = fopen(b, "r")) != NULL) { - putty_path = dupstr(b); - fclose(fp); - } else - putty_path = NULL; - } - - /* - * Process the command line, handling anything that can be done - * immediately, but deferring adding keys until after we've - * started up the main agent. Details of keys to be added are - * stored in the 'clkeys' array. - */ - split_into_argv(cmdline, false, &argc, &argv, &argstart); - bool add_keys_encrypted = false; - AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error); - while (!aux_match_done(&amo)) { - char *val; - #define match_opt(...) aux_match_opt( \ - &amo, NULL, __VA_ARGS__, (const char *)NULL) - #define match_optval(...) aux_match_opt( \ - &amo, &val, __VA_ARGS__, (const char *)NULL) - - if (aux_match_arg(&amo, &val)) { - /* - * Non-option arguments are expected to be key files, and - * added to clkeys. - */ - sgrowarray(clkeys, clkeysize, nclkeys); - CommandLineKey *clkey = &clkeys[nclkeys++]; - clkey->fn = filename_from_str(val); - clkey->add_encrypted = add_keys_encrypted; - } else if (match_opt("-pgpfp")) { - pgp_fingerprints_msgbox(NULL); - return 1; - } else if (match_opt("-restrict-acl", "-restrict_acl", - "-restrictacl")) { - restrict_process_acl(); - } else if (match_opt("-restrict-putty-acl", "-restrict_putty_acl")) { - restrict_putty_acl = true; - } else if (match_opt("-no-decrypt", "-no_decrypt", - "-nodecrypt", "-encrypted")) { - add_keys_encrypted = true; - } else if (match_opt("-keylist")) { - show_keylist_on_startup = true; - } else if (match_optval("-openssh-config", "-openssh_config")) { - openssh_config_file = val; - } else if (match_optval("-unix")) { - unixsocket = val; - } else if (match_opt("-c")) { - /* - * If we see `-c', then the rest of the command line - * should be treated as a command to be spawned. - */ - if (amo.index < amo.argc) - command = argstart[amo.index]; - else - command = ""; - break; - } else { - opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); - } - } - - /* - * Create and lock an interprocess mutex while we figure out - * whether we're going to be the Pageant server or a client. That - * way, two Pageant processes started up simultaneously will be - * able to agree on which one becomes the server without a race - * condition. - */ - HANDLE mutex; - { - char *err; - char *mutexname = agent_mutex_name(); - mutex = lock_interprocess_mutex(mutexname, &err); - sfree(mutexname); - if (!mutex) { - MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); - return 1; - } - } - - /* - * Find out if Pageant is already running. - */ - already_running = agent_exists(); - - /* - * If it isn't, we're going to be the primary Pageant that stays - * running, so set up all the machinery to answer requests. - */ - if (!already_running) { - /* - * Set up the window class for the hidden window that receives - * all the messages to do with our presence in the system tray. - */ - - if (!prev) { - WNDCLASS wndclass; - - memset(&wndclass, 0, sizeof(wndclass)); - wndclass.lpfnWndProc = TrayWndProc; - wndclass.hInstance = inst; - wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON)); - wndclass.lpszClassName = TRAYCLASSNAME; - - RegisterClass(&wndclass); - } - - keylist = NULL; - - traywindow = CreateWindow(TRAYCLASSNAME, TRAYWINTITLE, - WS_OVERLAPPEDWINDOW | WS_VSCROLL, - CW_USEDEFAULT, CW_USEDEFAULT, - 100, 100, NULL, NULL, inst, NULL); - winselgui_set_hwnd(traywindow); - - /* - * Initialise the cross-platform Pageant code. - */ - pageant_init(); - - /* - * Set up a named-pipe listener. - */ - wpc->plc.vt = &winpgnt_vtable; - wpc->plc.suppress_logging = true; - if (should_have_security()) { - Plug *pl_plug; - struct pageant_listen_state *pl = - pageant_listener_new(&pl_plug, &wpc->plc); - char *pipename = agent_named_pipe_name(); - Socket *sock = new_named_pipe_listener(pipename, pl_plug); - if (sk_socket_error(sock)) { - char *err = dupprintf("Unable to open named pipe at %s " - "for SSH agent:\n%s", pipename, - sk_socket_error(sock)); - MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); - return 1; - } - pageant_listener_got_socket(pl, sock); - - /* - * If we've been asked to write out an OpenSSH config file - * pointing at the named pipe, do so. - */ - if (openssh_config_file) { - FILE *fp = fopen(openssh_config_file, "w"); - if (!fp) { - char *err = dupprintf("Unable to write OpenSSH config " - "file to %s", openssh_config_file); - MessageBox(NULL, err, "Pageant Error", - MB_ICONERROR | MB_OK); - return 1; - } - fputs("IdentityAgent \"", fp); - /* Some versions of Windows OpenSSH prefer / to \ as the path - * separator; others don't mind, but as far as we know, no - * version _objects_ to /, so we use it unconditionally. */ - for (const char *p = pipename; *p; p++) { - char c = *p; - if (c == '\\') - c = '/'; - fputc(c, fp); - } - fputs("\"\n", fp); - fclose(fp); - } - - sfree(pipename); - } - - /* - * Set up an AF_UNIX listener too, if we were asked to. - */ - if (unixsocket) { - sk_init(); - - /* FIXME: diagnose any error except file-not-found. Also, - * check the file type if possible? */ - remove(unixsocket); - - Plug *pl_plug; - struct pageant_listen_state *pl = - pageant_listener_new(&pl_plug, &wpc->plc); - Socket *sock = sk_newlistener_unix(unixsocket, pl_plug); - if (sk_socket_error(sock)) { - char *err = dupprintf("Unable to open AF_UNIX socket at %s " - "for SSH agent:\n%s", unixsocket, - sk_socket_error(sock)); - MessageBox(NULL, err, "Pageant Error", MB_ICONERROR | MB_OK); - return 1; - } - pageant_listener_got_socket(pl, sock); - } - - /* - * Set up the window class for the hidden window that receives - * the WM_COPYDATA message used by the old-style Pageant IPC - * system. - */ - if (!prev) { - WNDCLASS wndclass; - - memset(&wndclass, 0, sizeof(wndclass)); - wndclass.lpfnWndProc = wm_copydata_WndProc; - wndclass.hInstance = inst; - wndclass.lpszClassName = IPCCLASSNAME; - - RegisterClass(&wndclass); - } - - /* - * And launch the subthread which will open that hidden window and - * handle WM_COPYDATA messages on it. - */ - wmcpc.vt = &wmcpc_vtable; - wmcpc.suppress_logging = true; - pageant_register_client(&wmcpc); - DWORD wm_copydata_threadid; - wmct.ev_msg_ready = CreateEvent(NULL, false, false, NULL); - wmct.ev_reply_ready = CreateEvent(NULL, false, false, NULL); - HANDLE hThread = CreateThread(NULL, 0, wm_copydata_threadfunc, - &inst, 0, &wm_copydata_threadid); - if (hThread) - CloseHandle(hThread); /* we don't need the thread handle */ - add_handle_wait(wmct.ev_msg_ready, wm_copydata_got_msg, NULL); - } - - /* - * Now we're either a fully set up Pageant server, or we know one - * is running somewhere else. Either way, now it's safe to unlock - * the mutex. - */ - unlock_interprocess_mutex(mutex); - - /* - * Add any keys provided on the command line. - */ - for (size_t i = 0; i < nclkeys; i++) { - CommandLineKey *clkey = &clkeys[i]; - win_add_keyfile(clkey->fn, clkey->add_encrypted); - filename_free(clkey->fn); - } - sfree(clkeys); - /* And forget any passphrases we stashed during that loop. */ - pageant_forget_passphrases(); - - /* - * Now our keys are present, spawn a command, if we were asked to. - */ - if (command) { - char *args; - if (command[0] == '"') - args = strchr(++command, '"'); - else - args = strchr(command, ' '); - if (args) { - *args++ = 0; - while (*args && isspace((unsigned char)*args)) args++; - } - spawn_cmd(command, args, show); - } - - /* - * If Pageant was already running, we leave now. If we haven't - * even taken any auxiliary action (spawned a command or added - * keys), complain. - */ - if (already_running) { - if (!command && !nclkeys) { - MessageBox(NULL, "Pageant is already running", "Pageant Error", - MB_ICONERROR | MB_OK); - } - return 0; - } - - /* Set up a system tray icon */ - AddTrayIcon(traywindow); - - /* Accelerators used: nsvkxa */ - systray_menu = CreatePopupMenu(); - if (putty_path) { - session_menu = CreateMenu(); - AppendMenu(systray_menu, MF_ENABLED, IDM_PUTTY, "&New Session"); - AppendMenu(systray_menu, MF_POPUP | MF_ENABLED, - (UINT_PTR) session_menu, "&Saved Sessions"); - AppendMenu(systray_menu, MF_SEPARATOR, 0, 0); - } - AppendMenu(systray_menu, MF_ENABLED, IDM_VIEWKEYS, - "&View Keys"); - AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY, "Add &Key"); - AppendMenu(systray_menu, MF_ENABLED, IDM_ADDKEY_ENCRYPTED, - "Add key (encrypted)"); - AppendMenu(systray_menu, MF_SEPARATOR, 0, 0); - AppendMenu(systray_menu, MF_ENABLED, IDM_REMOVE_ALL, - "Remove All Keys"); - AppendMenu(systray_menu, MF_ENABLED, IDM_REENCRYPT_ALL, - "Re-encrypt All Keys"); - AppendMenu(systray_menu, MF_SEPARATOR, 0, 0); - if (has_help()) - AppendMenu(systray_menu, MF_ENABLED, IDM_HELP, "&Help"); - AppendMenu(systray_menu, MF_ENABLED, IDM_ABOUT, "&About"); - AppendMenu(systray_menu, MF_SEPARATOR, 0, 0); - AppendMenu(systray_menu, MF_ENABLED, IDM_CLOSE, "E&xit"); - initial_menuitems_count = GetMenuItemCount(session_menu); - - /* Set the default menu item. */ - SetMenuDefaultItem(systray_menu, IDM_VIEWKEYS, false); - - ShowWindow(traywindow, SW_HIDE); - - /* Open the visible key list window, if we've been asked to. */ - if (show_keylist_on_startup) - create_keylist_window(); - - /* - * Main message loop. - */ - while (true) { - int n; - - HandleWaitList *hwl = get_handle_wait_list(); - - DWORD timeout = toplevel_callback_pending() ? 0 : INFINITE; - n = MsgWaitForMultipleObjects(hwl->nhandles, hwl->handles, false, - timeout, QS_ALLINPUT); - - if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)hwl->nhandles) - handle_wait_activate(hwl, n - WAIT_OBJECT_0); - handle_wait_list_free(hwl); - - while (sw_PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { - if (msg.message == WM_QUIT) - goto finished; /* two-level break */ - - if (IsWindow(keylist) && IsDialogMessage(keylist, &msg)) - continue; - if (IsWindow(aboutbox) && IsDialogMessage(aboutbox, &msg)) - continue; - if (IsWindow(nonmodal_passphrase_hwnd) && - IsDialogMessage(nonmodal_passphrase_hwnd, &msg)) - continue; - - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - run_toplevel_callbacks(); - } - finished: - - /* Clean up the system tray icon */ - { - NOTIFYICONDATA tnid; - - tnid.cbSize = sizeof(NOTIFYICONDATA); - tnid.hWnd = traywindow; - tnid.uID = 1; - - Shell_NotifyIcon(NIM_DELETE, &tnid); - - DestroyMenu(systray_menu); - } - - if (keypath) filereq_free(keypath); - - if (openssh_config_file) { - /* - * Leave this file around, but empty it, so that it doesn't - * refer to a pipe we aren't listening on any more. - */ - FILE *fp = fopen(openssh_config_file, "w"); - if (fp) - fclose(fp); - } - - cleanup_exit(msg.wParam); - return msg.wParam; /* just in case optimiser complains */ -} diff --git a/WINDOWS/pageant.ico b/WINDOWS/pageant.ico deleted file mode 100644 index 93274fd92449f2d0ba88784627db7bf6ded89aea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4078 zcmeH}zm5}05XQ@R#jH4_aY*J3$?zMP0Eq;NS3p8SWNd^tKuk*)>_j}vJ;7-*5=*=R zOh|?UBu}uS`F;IocE;;P%dwm|ab;J3UDegy)z#JAV>YpyHk-}B1G_shduC=wM^XNG z!|V;Z#UjejZ<@V4Fzjz&Ke4~CeSb^#QC?vC<`3B?d206eZL_=B%``5oc|;&bOhgrbDWGl*iViDXdxy6D5-v5oalZv z3ISCR6;-GtkgFB-(|RCF7Yzt}q7#gu<4~Fvor|@TFk0=ZBuDdY91fGuYth?(PktbJ zn7j%3G)jVEm3BV5f3>pC%MRvsz)QCW_pf#J1R^WJbGDm+@kbGQ_vE6?CB z=-}!Seu{nym!$2|JM*1QeUsR?Q|~v4JFt7|{aK#Xd*%58egfw_cLxZ2$&uqUklX<% zss5xqXP)n)BIR++=5#t3Vn^SnakZ-OH0X2G2Uv{uc+l__;%5_0XN_rxdSyHj%ytrN3}j41RjUzzp%wDW0ho~ha1jOOkrOZdYHtJO#Nq7e)5M>+oS(<&M zXp6A?1->Ivydqq17dJ2Hb`_Jj7IK)+6&gW%9WnHlUJqeLCG-;6UU>gOc+dM!LSF4{ zLSmy_n*0=Go-i(`>8yxiIQ)`Hfy>CnhB9Fajy~r^+D_3jLO4aK`U9ZwDUiGP|9wcr XiAzgtg^E!`m$2EvHpv;Ub{~EPXjSnU diff --git a/WINDOWS/pageant.mft b/WINDOWS/pageant.mft deleted file mode 100644 index fc1bef775..000000000 --- a/WINDOWS/pageant.mft +++ /dev/null @@ -1,31 +0,0 @@ - - - - - PuTTY SSH authentication agent - - - - - - - - - - true - - - diff --git a/WINDOWS/pageant.rc b/WINDOWS/pageant.rc deleted file mode 100644 index 1bea78b4f..000000000 --- a/WINDOWS/pageant.rc +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Windows resources for Pageant. - */ - -#include "rcstuff.h" - -#define APPNAME "Pageant" -#define APPDESC "PuTTY SSH authentication agent" - -#include "pageant-rc.h" - -#include "help.rc2" - -IDI_MAINICON ICON "pageant.ico" -IDI_TRAYICON ICON "pageants.ico" - -IDD_LOAD_PASSPHRASE DIALOG DISCARDABLE 0, 0, 140, 60 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Pageant: Loading Encrypted Key" -FONT 8, "MS Shell Dlg" -BEGIN - CTEXT "Enter passphrase to load key", IDC_PASSPHRASE_STATIC1, 10, 6, 120, 8 - CTEXT "", IDC_PASSPHRASE_FINGERPRINT, 10, 16, 120, 8 - EDITTEXT IDC_PASSPHRASE_EDITBOX, 10, 26, 120, 12, - ES_PASSWORD | ES_AUTOHSCROLL - DEFPUSHBUTTON "O&K", IDOK, 20, 42, 40, 14 - PUSHBUTTON "&Cancel", IDCANCEL, 80, 42, 40, 14 -END - -IDD_ONDEMAND_PASSPHRASE DIALOG DISCARDABLE 0, 0, 250, 78 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Pageant: Decrypting Stored Key" -FONT 8, "MS Shell Dlg" -BEGIN - CTEXT "A client of Pageant wants to use the following encrypted key:", - IDC_PASSPHRASE_STATIC1, 10, 6, 230, 8 - CTEXT "", IDC_PASSPHRASE_FINGERPRINT, 10, 16, 230, 8 - CTEXT "If you intended this, click in this box to make sure it has", - IDC_PASSPHRASE_STATIC2, 10, 26, 230, 8 - CTEXT "input focus, then enter the passphrase to decrypt the key.", - IDC_PASSPHRASE_STATIC3, 10, 34, 230, 8 - EDITTEXT IDC_PASSPHRASE_EDITBOX, 10, 44, 230, 12, - ES_PASSWORD | ES_AUTOHSCROLL - DEFPUSHBUTTON "O&K", IDOK, 45, 60, 40, 14 - PUSHBUTTON "&Cancel", IDCANCEL, 105, 60, 40, 14 - PUSHBUTTON "&Help", IDHELP, 165, 60, 50, 14 -END - -IDD_KEYLIST DIALOG DISCARDABLE 0, 0, 450, 236 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Pageant Key List" -FONT 8, "MS Shell Dlg" -BEGIN - LISTBOX IDC_KEYLIST_LISTBOX, 10, 10, 420, 155, - LBS_EXTENDEDSEL | LBS_OWNERDRAWFIXED | WS_VSCROLL | WS_TABSTOP - PUSHBUTTON "&Add Key", IDC_KEYLIST_ADDKEY, 10, 187, 60, 14 - PUSHBUTTON "Add Key (&encrypted)", IDC_KEYLIST_ADDKEY_ENC, 75, 187, 80, 14 - PUSHBUTTON "Re-e&ncrypt", IDC_KEYLIST_REENCRYPT, 315, 187, 60, 14 - PUSHBUTTON "&Remove", IDC_KEYLIST_REMOVE, 380, 187, 60, 14 - PUSHBUTTON "&Help", IDC_KEYLIST_HELP, 10, 212, 50, 14 - DEFPUSHBUTTON "&Close", IDOK, 390, 212, 50, 14 - LTEXT "&Fingerprint type:", IDC_KEYLIST_FPTYPE_STATIC, 10, 172, 60, 8 - COMBOBOX IDC_KEYLIST_FPTYPE, 70, 170, 100, 12, CBS_DROPDOWNLIST -END - -/* Accelerators used: cl */ -IDD_ABOUT DIALOG DISCARDABLE 140, 40, 270, 136 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "About Pageant" -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "&Close", IDOK, 216, 118, 48, 14 - PUSHBUTTON "View &Licence", IDC_ABOUT_LICENCE, 6, 118, 70, 14 - PUSHBUTTON "Visit &Web Site", IDC_ABOUT_WEBSITE, 140, 118, 70, 14 - EDITTEXT IDC_ABOUT_TEXTBOX, 10, 6, 250, 110, - ES_READONLY | ES_MULTILINE | ES_CENTER, WS_EX_STATICEDGE -END - -/* No accelerators used */ -IDD_LICENCE DIALOG DISCARDABLE 50, 50, 326, 239 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Licence" -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "OK", IDOK, 148, 219, 44, 14 - - EDITTEXT IDC_LICENCE_TEXTBOX, 10, 10, 306, 200, - ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE -END - -#include "version.rc2" - -#ifndef NO_MANIFESTS -1 RT_MANIFEST "pageant.mft" -#endif /* NO_MANIFESTS */ diff --git a/WINDOWS/pageants.ico b/WINDOWS/pageants.ico deleted file mode 100644 index 28a6d01e548e7ce40cf5ecab4900dfff5c50cd96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 510 zcmbu5!3}~y5Jf*~0tXZ508fUaC0K#&;Bk*zjwNgX6hR3bJrLJ7tQd&Kiv$1vnLi7e zSt-*-RaK!oHJNmjs5jJ^ZsZrd#yoHUcFw|0{Eg^X)43!tsX_>-HRALtM#}(?Sw?f@ zo^VnbqxV-_tM6#rwpMEFx}UoInVpx4B)&~rxTGuYbLQLvcJ2-2=DqRq2Rv6NjHuzk m`0IP(FmayvljePE)~ZMPd)6J8^Rz~}_%sWPg4E{vXWl21O;}0* diff --git a/WINDOWS/platform.h b/WINDOWS/platform.h deleted file mode 100644 index f0bb58446..000000000 --- a/WINDOWS/platform.h +++ /dev/null @@ -1,832 +0,0 @@ -/* - * windows/platform.h: Windows-specific inter-module stuff. - */ - -#ifndef PUTTY_WINDOWS_PLATFORM_H -#define PUTTY_WINDOWS_PLATFORM_H - -#include -#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 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 !HAVE_NO_STDINT_H -#include -#endif - -#include "defs.h" -#include "marshal.h" - -#include "tree234.h" - -#include "help.h" - -#if defined _M_IX86 || defined _M_AMD64 -#define BUILDINFO_PLATFORM "x86 Windows" -#elif defined _M_ARM || defined _M_ARM64 -#define BUILDINFO_PLATFORM "Arm Windows" -#else -#define BUILDINFO_PLATFORM "Windows" -#endif - -#if defined __GNUC__ || defined __clang__ -#define THREADLOCAL __thread -#elif defined _MSC_VER -#define THREADLOCAL __declspec(thread) -#else -#error Do not know how to declare thread-local storage with this toolchain -#endif - -/* Randomly-chosen dwData value identifying a WM_COPYDATA message as - * being a Pageant transaction */ -#define AGENT_COPYDATA_ID 0x804e50ba - -struct Filename { - /* - * A Windows Filename stores a path in three formats: - * - * - wchar_t (in Windows UTF-16 encoding). The best format to use - * for actual file API functions, because all legal Windows - * file names are representable. - * - * - char, in the system default codepage. A fallback to use if - * necessary, e.g. in diagnostics written to somewhere that is - * unavoidably encoded _in_ the system codepage. - * - * - char, in UTF-8. An equally general representation to wpath, - * but suitable for keeping in char-typed strings. - */ - wchar_t *wpath; - char *cpath, *utf8path; -}; -Filename *filename_from_wstr(const wchar_t *str); -FILE *f_open(const Filename *filename, const char *mode, bool isprivate); - -#ifndef SUPERSEDE_FONTSPEC_FOR_TESTING -struct FontSpec { - char *name; - bool isbold; - int height; - int charset; -}; -struct FontSpec *fontspec_new( - const char *name, bool bold, int height, int charset); -#endif - -#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 */ - -#define PLATFORM_CLIPBOARDS(X) \ - X(CLIP_SYSTEM, "system clipboard") \ - /* end of list */ - -/* - * 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 - -#if !HAVE_STRTOUMAX -/* Work around lack of strtoumax in older MSVC libraries */ -static inline uintmax_t strtoumax(const char *nptr, char **endptr, int base) -{ return _strtoui64(nptr, endptr, base); } -#endif - -typedef INT_PTR (*ShinyDlgProc)(HWND hwnd, UINT msg, WPARAM wParam, - LPARAM lParam, void *ctx); -int ShinyDialogBox(HINSTANCE hinst, LPCTSTR tmpl, const char *winclass, - HWND hwndparent, ShinyDlgProc proc, void *ctx); -void ShinyEndDialog(HWND hwnd, int ret); - -void centre_window(HWND hwnd); - -#ifndef __WINE__ -/* Up-to-date Windows headers warn that the unprefixed versions of - * these names are deprecated. */ -#define stricmp _stricmp -#define strnicmp _strnicmp -#else -/* Compiling with winegcc, _neither_ version of these functions - * exists. Use the POSIX names. */ -#define stricmp strcasecmp -#define strnicmp strncasecmp -#endif - -/* - * 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 -/* If you DECL_WINDOWS_FUNCTION as extern in a header file, use this to - * define the function pointer in a source file */ -#define DEF_WINDOWS_FUNCTION(name) t_##name p_##name -#define GET_WINDOWS_FUNCTION_PP(module, name) \ - TYPECHECK((t_##name)NULL == name, \ - (p_##name = module ? \ - (t_##name) GetProcAddress(module, STR(name)) : NULL)) -#define GET_WINDOWS_FUNCTION(module, name) \ - TYPECHECK((t_##name)NULL == name, \ - (p_##name = module ? \ - (t_##name) GetProcAddress(module, #name) : NULL)) -#define GET_WINDOWS_FUNCTION_NO_TYPECHECK(module, name) \ - (p_##name = module ? \ - (t_##name) GetProcAddress(module, #name) : NULL) - -#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_CHM_FILE "putty.chm" - -#define GETTICKCOUNT GetTickCount -#define CURSORBLINK GetCaretBlinkTime() -#define TICKSPERSEC 1000 /* GetTickCount returns milliseconds */ - -#define DEFAULT_CODEPAGE CP_ACP -#define USES_VTLINE_HACK -#define CP_UTF8 65001 -#define CP_437 437 /* used for test suites */ -#define CP_ISO8859_1 0x10001 /* used for test suites */ - -#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 - -/* - * The all-important instance handle, saved from WinMain in every GUI - * program and exported for other GUI code to pass back to the Windows - * API. - */ -extern HINSTANCE hinst; - -/* - * Help file stuff in help.c. - */ -void init_help(void); -void shutdown_help(void); -bool has_help(void); -void launch_help(HWND hwnd, const char *topic); -void quit_help(HWND hwnd); -int has_embedded_chm(void); /* 1 = yes, 0 = no, -1 = N/A */ - -/* - * GUI seat methods in dialog.c, so that the vtable definition in - * window.c can refer to them. - */ -SeatPromptResult win_seat_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); -SeatPromptResult win_seat_confirm_weak_crypto_primitive( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); -SeatPromptResult win_seat_confirm_weak_cached_hostkey( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); -const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat); - -/* - * Windows-specific clipboard helper function shared with dialog.c, - * which takes the data string in the system code page instead of - * Unicode. - */ -void write_aclip(HWND hwnd, int clipboard, char *, int); - -#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/platform.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 ssh/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. - */ -typedef const wchar_t *FILESELECT_FILTER_TYPE; -#define FILTER_KEY_FILES (L"PuTTY Private Key Files (*.ppk)\0*.ppk\0" \ - L"All Files (*.*)\0*\0\0\0") -#define FILTER_WAVE_FILES (L"Wave Files (*.wav)\0*.WAV\0" \ - L"All Files (*.*)\0*\0\0\0") -#define FILTER_DYNLIB_FILES (L"Dynamic Library Files (*.dll)\0*.dll\0" \ - L"All Files (*.*)\0*\0\0\0") - -/* char-based versions of the above, for outlying uses of file selectors. */ -#define FILTER_KEY_FILES_C ("PuTTY Private Key Files (*.ppk)\0*.ppk\0" \ - "All Files (*.*)\0*\0\0\0") -#define FILTER_WAVE_FILES_C ("Wave Files (*.wav)\0*.WAV\0" \ - "All Files (*.*)\0*\0\0\0") -#define FILTER_DYNLIB_FILES_C ("Dynamic Library Files (*.dll)\0*.dll\0" \ - "All Files (*.*)\0*\0\0\0") - -/* - * Exports from network.c. - */ -/* Report an event notification from WSA*Select */ -void select_result(WPARAM, LPARAM); -/* Enumerate all currently live OS-level SOCKETs */ -SOCKET first_socket(int *); -SOCKET next_socket(int *); -/* Ask network.c whether we currently want to try to write to a SOCKET */ -bool socket_writable(SOCKET skt); -/* Force a refresh of the SOCKET list by re-calling do_select for each one */ -void socket_reselect_all(void); -/* Make a SockAddr which just holds a named pipe address. */ -SockAddr *sk_namedpipe_addr(const char *pipename); -/* Turn a WinSock error code into a string. */ -const char *winsock_error_string(int error); -Socket *sk_newlistener_unix(const char *socketpath, Plug *plug); - -/* - * network.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(extern, int, WSAAsyncSelect, - (SOCKET, HWND, u_int, LONG)); -DECL_WINDOWS_FUNCTION(extern, int, WSAEventSelect, - (SOCKET, WSAEVENT, LONG)); -DECL_WINDOWS_FUNCTION(extern, int, WSAGetLastError, (void)); -DECL_WINDOWS_FUNCTION(extern, int, WSAEnumNetworkEvents, - (SOCKET, WSAEVENT, LPWSANETWORKEVENTS)); -#ifdef NEED_DECLARATION_OF_SELECT -/* This declaration is protected by an ifdef for the sake of building - * against winelib, in which you have to include winsock2.h before - * stdlib.h so that the right fd_set type gets defined. It would be a - * pain to do that throughout this codebase, so instead I arrange that - * only a modules actually needing to use (or define, or initialise) - * this function pointer will see its declaration, and _those_ modules - * - which will be Windows-specific anyway - can take more care. */ -DECL_WINDOWS_FUNCTION(extern, int, select, - (int, fd_set FAR *, fd_set FAR *, - fd_set FAR *, const struct timeval FAR *)); -#endif - -/* - * Implemented differently depending on the client of network.c, and - * called by network.c to turn on or off WSA*Select for a given socket. - */ -const char *do_select(SOCKET skt, bool enable); - -/* - * Exports from select-{gui,cli}.c, each of which provides an - * implementation of do_select. - */ -void winselgui_set_hwnd(HWND hwnd); -void winselgui_clear_hwnd(void); -void winselgui_response(WPARAM wParam, LPARAM lParam); - -void winselcli_setup(void); -SOCKET winselcli_unique_socket(void); -extern HANDLE winselcli_event; - -/* - * Network-subsystem-related functions provided in other Windows modules. - */ -Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, - SockAddr *addr, int port, Plug *plug, - bool overlapped); /* winhsock */ -Socket *make_deferred_handle_socket(DeferredSocketOpener *opener, - SockAddr *addr, int port, Plug *plug); -void setup_handle_socket(Socket *s, HANDLE send_H, HANDLE recv_H, - HANDLE stderr_H, bool overlapped); -void handle_socket_set_psb_prefix(Socket *s, const char *prefix); -Socket *new_named_pipe_client(const char *pipename, Plug *plug); /* winnpc */ -Socket *new_named_pipe_listener(const char *pipename, Plug *plug); /* winnps */ - -/* A lower-level function in named-pipe-client.c, which does most of - * the work of new_named_pipe_client (including checking the ownership - * of what it's connected to), but returns a plain HANDLE instead of - * wrapping it into a Socket. */ -HANDLE connect_to_named_pipe(const char *pipename, char **err); - -/* - * Exports from controls.c. - */ - -struct ctlpos { - HWND hwnd; - WPARAM font; - int dlu4inpix; - int ypos, width; - int xoff; - int boxystart, boxid; - const char *boxtext; -}; -void init_common_controls(void); /* also does some DLL-loading */ - -/* - * Exports from utils. - */ -typedef struct filereq_tag filereq; /* cwd for file requester */ -bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save); -bool request_file_w(filereq *state, OPENFILENAMEW *of, - bool preserve, bool save); -filereq *filereq_new(void); -void filereq_free(filereq *state); -void pgp_fingerprints_msgbox(HWND owner); -int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, DWORD style, - bool utf8, DWORD helpctxid); -void MakeDlgItemBorderless(HWND parent, int id); -char *GetDlgItemText_alloc(HWND hwnd, int id); -wchar_t *GetDlgItemTextW_alloc(HWND hwnd, int id); -void split_into_argv(char *, bool includes_program_name, - 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; - bool 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 */ - dlgcontrol *focused, *lastfocused; /* which ctrl has focus now/before */ - bool shortcuts[128]; /* track which shortcuts in use */ - bool coloursel_wanted; /* has an event handler asked for - * a colour selector? */ - struct { - unsigned char r, g, b; /* 0-255 */ - bool ok; - } coloursel_result; - tree234 *privdata; /* stores per-control private data */ - bool ended; /* has the dialog been ended? */ - int endresult; /* and if so, what was the result? */ - bool fixed_pitch_fonts; /* are we constrained to fixed fonts? */ -}; - -/* - * Exports from controls.c. - */ -void ctlposinit(struct ctlpos *cp, HWND hwnd, - int leftborder, int rightborder, int topborder); -HWND doctl(struct ctlpos *cp, RECT r, const char *wclass, int wstyle, - int exstyle, const char *wtext, int wid); -void bartitle(struct ctlpos *cp, const char *name, int id); -void beginbox(struct ctlpos *cp, const char *name, int idbox); -void endbox(struct ctlpos *cp); -void editboxfw(struct ctlpos *cp, bool password, bool readonly, - const char *text, int staticid, int editid); -void radioline(struct ctlpos *cp, const char *text, int id, int nacross, ...); -void bareradioline(struct ctlpos *cp, int nacross, ...); -void radiobig(struct ctlpos *cp, const char *text, int id, ...); -void checkbox(struct ctlpos *cp, const char *text, int id); -void button(struct ctlpos *cp, const char *btext, int bid, bool defbtn); -void statictext(struct ctlpos *cp, const char *text, int lines, int id); -void staticbtn(struct ctlpos *cp, const char *stext, int sid, - const char *btext, int bid); -void static2btn(struct ctlpos *cp, const char *stext, int sid, - const char *btext1, int bid1, const char *btext2, int bid2); -void staticedit(struct ctlpos *cp, const char *stext, - int sid, int eid, int percentedit); -void staticddl(struct ctlpos *cp, const char *stext, - int sid, int lid, int percentlist); -void combobox(struct ctlpos *cp, const char *text, int staticid, int listid); -void staticpassedit(struct ctlpos *cp, const char *stext, - int sid, int eid, int percentedit); -void bigeditctrl(struct ctlpos *cp, const char *stext, - int sid, int eid, int lines); -void ersatztab(struct ctlpos *cp, const char *stext, int sid, int lid, - int s2id); -void editbutton(struct ctlpos *cp, const char *stext, int sid, - int eid, const char *btext, int bid); -void sesssaver(struct ctlpos *cp, const char *text, - int staticid, int editid, int listid, ...); -void envsetter(struct ctlpos *cp, const char *stext, int sid, - const char *e1stext, int e1sid, int e1id, - const char *e2stext, int e2sid, int e2id, - int listid, const char *b1text, int b1id, - const char *b2text, int b2id); -void charclass(struct ctlpos *cp, const char *stext, int sid, int listid, - const char *btext, int bid, int eid, const char *s2text, - int s2id); -void colouredit(struct ctlpos *cp, const char *stext, int sid, int listid, - const char *btext, int bid, ...); -void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines, - const char *stext, int sid, int listid, int upbid, int dnbid); -int handle_prefslist(struct prefslist *hdl, - int *array, int maxmemb, - bool is_dlmsg, HWND hwnd, - WPARAM wParam, LPARAM lParam); -void progressbar(struct ctlpos *cp, int id); -void fwdsetter(struct ctlpos *cp, int listid, const char *stext, int sid, - const char *e1stext, int e1sid, int e1id, - const char *e2stext, int e2sid, int e2id, - const char *btext, int bid, - const char *r1text, int r1id, const char *r2text, int r2id); - -void dlg_auto_set_fixed_pitch_flag(dlgparam *dlg); -bool dlg_get_fixed_pitch_flag(dlgparam *dlg); -void dlg_set_fixed_pitch_flag(dlgparam *dlg, bool flag); - -#define MAX_SHORTCUTS_PER_CTRL 16 - -/* - * This structure is what's stored for each `dlgcontrol' in the - * portable-dialog interface. - */ -struct winctrl { - dlgcontrol *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; - /* - * For vertical alignment, the id of a particular representative - * control that has the y-extent of the sensible part of the - * control. - */ - int align_id; - /* - * 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 `dlgcontrol' 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 *, dlgcontrol *); -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); -bool winctrl_handle_command(struct dlgparam *dp, UINT msg, - WPARAM wParam, LPARAM lParam); -void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c); -bool 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 config.c. - */ -void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, - bool midsession, int protocol); - -/* - * Exports from dialog.c. - */ -void defuse_showwindow(void); -bool do_config(Conf *); -bool do_reconfig(HWND, Conf *, int); -void showeventlog(HWND); -void showabout(HWND); -void force_normal(HWND hwnd); -void modal_about_box(HWND hwnd); -void show_help(HWND hwnd); -HWND event_log_window(void); - -/* - * Exports from utils. - */ -extern DWORD osMajorVersion, osMinorVersion, osPlatformId; -void init_winver(void); -void dll_hijacking_protection(void); -const char *get_system_dir(void); -HMODULE load_system32_dll(const char *libname); -const char *win_strerror(int error); -bool should_have_security(void); -void restrict_process_acl(void); -bool restricted_acl(void); -void escape_registry_key(const char *in, strbuf *out); -void unescape_registry_key(const char *in, strbuf *out); - -bool is_console_handle(HANDLE); - -/* A few pieces of up-to-date Windows API definition needed for older - * compilers. */ -#ifndef LOAD_LIBRARY_SEARCH_SYSTEM32 -#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 -#endif -#ifndef LOAD_LIBRARY_SEARCH_USER_DIRS -#define LOAD_LIBRARY_SEARCH_USER_DIRS 0x00000400 -#endif -#ifndef LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR -#define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100 -#endif -#ifndef DLL_DIRECTORY_COOKIE -typedef PVOID DLL_DIRECTORY_COOKIE; -DECLSPEC_IMPORT DLL_DIRECTORY_COOKIE WINAPI AddDllDirectory (PCWSTR NewDirectory); -#endif - -/* - * Exports from sizetip.c. - */ -void UpdateSizeTip(HWND src, int cx, int cy); -void EnableSizeTip(bool bEnable); - -/* - * Exports from unicode.c. - */ -void init_ucs(Conf *, struct unicode_data *); - -/* - * Exports from handle-io.c. - */ -#define HANDLE_FLAG_OVERLAPPED 1 -#define HANDLE_FLAG_IGNOREEOF 2 -#define HANDLE_FLAG_UNITBUFFER 4 -struct handle; -typedef size_t (*handle_inputfn_t)( - struct handle *h, const void *data, size_t len, int err); -typedef void (*handle_outputfn_t)( - struct handle *h, size_t new_backlog, int err, bool close); -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); -size_t handle_write(struct handle *h, const void *data, size_t len); -void handle_write_eof(struct handle *h); -void handle_free(struct handle *h); -void handle_unthrottle(struct handle *h, size_t backlog); -size_t handle_backlog(struct handle *h); -void *handle_get_privdata(struct handle *h); -/* Analogue of stdio_sink in marshal.h, for a Windows handle */ -struct handle_sink { - struct handle *h; - BinarySink_IMPLEMENTATION; -}; -void handle_sink_init(handle_sink *sink, struct handle *h); - -/* - * Exports from handle-wait.c. - */ -typedef struct HandleWait HandleWait; -typedef void (*handle_wait_callback_fn_t)(void *); -HandleWait *add_handle_wait(HANDLE h, handle_wait_callback_fn_t callback, - void *callback_ctx); -void delete_handle_wait(HandleWait *hw); - -typedef struct HandleWaitList { - HANDLE handles[MAXIMUM_WAIT_OBJECTS]; - int nhandles; -} HandleWaitList; -HandleWaitList *get_handle_wait_list(void); -void handle_wait_activate(HandleWaitList *hwl, int index); -void handle_wait_list_free(HandleWaitList *hwl); - -/* - * Pageant-related pathnames. - */ -char *agent_mutex_name(void); -char *agent_named_pipe_name(void); - -/* - * Exports from serial.c. - */ -extern const struct BackendVtable serial_backend; - -/* - * Exports from jump-list.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); -bool set_explicit_app_user_model_id(void); - -/* - * Exports from noise.c. - */ -bool win_read_random(void *buf, unsigned wanted); /* returns true on success */ - -/* - * Extra functions in storage.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); - -/* - * Windows clipboard-UI wording. - */ -#define CLIPNAME_IMPLICIT "Last selected text" -#define CLIPNAME_EXPLICIT "System clipboard" -#define CLIPNAME_EXPLICIT_OBJECT "system clipboard" -/* These defaults are the ones PuTTY has historically had */ -#define CLIPUI_DEFAULT_AUTOCOPY true -#define CLIPUI_DEFAULT_MOUSE CLIPUI_EXPLICIT -#define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT - -/* In utils */ -HKEY open_regkey_fn(bool create, bool write, HKEY base, const char *path, ...); -#define open_regkey_ro(base, ...) \ - open_regkey_fn(false, false, base, __VA_ARGS__, (const char *)NULL) -#define open_regkey_rw(base, ...) \ - open_regkey_fn(false, true, base, __VA_ARGS__, (const char *)NULL) -#define create_regkey(base, ...) \ - open_regkey_fn(true, true, base, __VA_ARGS__, (const char *)NULL) -void close_regkey(HKEY key); -void del_regkey(HKEY key, const char *name); -char *enum_regkey(HKEY key, int index); -bool get_reg_dword(HKEY key, const char *name, DWORD *out); -bool put_reg_dword(HKEY key, const char *name, DWORD value); -char *get_reg_sz(HKEY key, const char *name); -bool put_reg_sz(HKEY key, const char *name, const char *str); -strbuf *get_reg_multi_sz(HKEY key, const char *name); -bool put_reg_multi_sz(HKEY key, const char *name, strbuf *str); - -char *get_reg_sz_simple(HKEY key, const char *name, const char *leaf); - -/* In cliloop.c */ -typedef bool (*cliloop_pre_t)(void *vctx, const HANDLE **extra_handles, - size_t *n_extra_handles); -typedef bool (*cliloop_post_t)(void *vctx, size_t extra_handle_index); -void cli_main_loop(cliloop_pre_t pre, cliloop_post_t post, void *ctx); -bool cliloop_null_pre(void *vctx, const HANDLE **, size_t *); -bool cliloop_null_post(void *vctx, size_t); - -extern const struct BackendVtable conpty_backend; - -/* Functions that parametrise window.c between PuTTY and pterm */ -void gui_term_process_cmdline(Conf *conf, char *cmdline); -const struct BackendVtable *backend_vt_from_conf(Conf *conf); -const wchar_t *get_app_user_model_id(void); -/* And functions in window.c that those files call back to */ -char *handle_restrict_acl_cmdline_prefix(char *cmdline); -bool handle_special_sessionname_cmdline(char *cmdline, Conf *conf); -bool handle_special_filemapping_cmdline(char *cmdline, Conf *conf); - -/* network.c: network error reporting helpers taking OS error code */ -void plug_closing_system_error(Plug *plug, DWORD error); -void plug_closing_winsock_error(Plug *plug, DWORD error); - -SeatPromptResult make_spr_sw_abort_winerror(const char *prefix, DWORD error); - -HANDLE lock_interprocess_mutex(const char *mutexname, char **error); -void unlock_interprocess_mutex(HANDLE mutex); - -typedef void (*aux_opt_error_fn_t)(const char *, ...); -typedef struct AuxMatchOpt { - int index, argc; - char **argv; - bool doing_opts; - aux_opt_error_fn_t error; -} AuxMatchOpt; -AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index, - aux_opt_error_fn_t opt_error); -bool aux_match_arg(AuxMatchOpt *amo, char **val); -bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...); -bool aux_match_done(AuxMatchOpt *amo); - -char *save_screenshot(HWND hwnd, const char *outfile); -void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend); - -void setup_gui_timing(void); - -#endif /* PUTTY_WINDOWS_PLATFORM_H */ diff --git a/WINDOWS/plink.c b/WINDOWS/plink.c deleted file mode 100644 index a84f0e4b0..000000000 --- a/WINDOWS/plink.c +++ /dev/null @@ -1,555 +0,0 @@ -/* - * PLink - a Windows command-line (stdin/stdout) variant of PuTTY. - */ - -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "storage.h" -#include "tree234.h" -#include "security-api.h" - -void cmdline_error(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - console_print_error_msg_fmt_v("plink", fmt, ap); - va_end(ap); - exit(1); -} - -static HANDLE inhandle, outhandle, errhandle; -static struct handle *stdin_handle, *stdout_handle, *stderr_handle; -static handle_sink stdout_hs, stderr_hs; -static StripCtrlChars *stdout_scc, *stderr_scc; -static BinarySink *stdout_bs, *stderr_bs; -static DWORD orig_console_mode; - -static Backend *backend; -static LogContext *logctx; -static Conf *conf; - -static void plink_echoedit_update(Seat *seat, bool echo, bool edit) -{ - /* Update stdin read mode to reflect changes in line discipline. */ - DWORD mode; - - mode = ENABLE_PROCESSED_INPUT; - if (echo) - mode = mode | ENABLE_ECHO_INPUT; - else - mode = mode & ~ENABLE_ECHO_INPUT; - if (edit) - mode = mode | ENABLE_LINE_INPUT; - else - mode = mode & ~ENABLE_LINE_INPUT; - SetConsoleMode(inhandle, mode); -} - -static size_t plink_output( - Seat *seat, SeatOutputType type, const void *data, size_t len) -{ - bool is_stderr = type != SEAT_OUTPUT_STDOUT; - BinarySink *bs = is_stderr ? stderr_bs : stdout_bs; - put_data(bs, data, len); - - return handle_backlog(stdout_handle) + handle_backlog(stderr_handle); -} - -static bool plink_eof(Seat *seat) -{ - handle_write_eof(stdout_handle); - return false; /* do not respond to incoming EOF with outgoing */ -} - -static SeatPromptResult plink_get_userpass_input(Seat *seat, prompts_t *p) -{ - /* Plink doesn't support Restart Session, so we can just have a - * single static cmdline_get_passwd_input_state that's never reset */ - static cmdline_get_passwd_input_state cmdline_state = - CMDLINE_GET_PASSWD_INPUT_STATE_INIT; - - SeatPromptResult spr; - spr = cmdline_get_passwd_input(p, &cmdline_state, false); - if (spr.kind == SPRK_INCOMPLETE) - spr = console_get_userpass_input(p); - return spr; -} - -static bool plink_seat_interactive(Seat *seat) -{ - return (!*conf_get_str(conf, CONF_remote_cmd) && - !*conf_get_str(conf, CONF_remote_cmd2) && - !*conf_get_str(conf, CONF_ssh_nc_host)); -} - -static const SeatVtable plink_seat_vt = { - .output = plink_output, - .eof = plink_eof, - .sent = nullseat_sent, - .banner = nullseat_banner_to_stderr, - .get_userpass_input = plink_get_userpass_input, - .notify_session_started = nullseat_notify_session_started, - .notify_remote_exit = nullseat_notify_remote_exit, - .notify_remote_disconnect = nullseat_notify_remote_disconnect, - .connection_fatal = console_connection_fatal, - .nonfatal = console_nonfatal, - .update_specials_menu = nullseat_update_specials_menu, - .get_ttymode = nullseat_get_ttymode, - .set_busy_status = nullseat_set_busy_status, - .confirm_ssh_host_key = console_confirm_ssh_host_key, - .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, - .prompt_descriptions = console_prompt_descriptions, - .is_utf8 = nullseat_is_never_utf8, - .echoedit_update = plink_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = nullseat_get_window_pixel_size, - .stripctrl_new = console_stripctrl_new, - .set_trust_status = console_set_trust_status, - .can_set_trust_status = console_can_set_trust_status, - .has_mixed_input_stream = console_has_mixed_input_stream, - .verbose = cmdline_seat_verbose, - .interactive = plink_seat_interactive, - .get_cursor_position = nullseat_get_cursor_position, -}; -static Seat plink_seat[1] = {{ &plink_seat_vt }}; - -static DWORD main_thread_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(" -ssh-connection\n"); - printf(" force use of the bare ssh-connection 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(" -proxycmd command\n"); - printf(" use 'command' as local proxy\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(" -pwfile file login with password read from specified file\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 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(" -no-trivial-auth\n"); - printf(" disconnect if SSH authentication succeeds trivially\n"); - printf(" -noshare disable use of connection sharing\n"); - printf(" -share enable use of connection sharing\n"); - printf(" -hostkey keyid\n"); - printf(" manually specify a host key (may be repeated)\n"); - printf(" -sanitise-stderr, -sanitise-stdout, " - "-no-sanitise-stderr, -no-sanitise-stdout\n"); - printf(" do/don't strip control chars from standard " - "output/error\n"); - printf(" -no-antispoof omit anti-spoofing prompt after " - "authentication\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"); - printf(" -logoverwrite\n"); - printf(" -logappend\n"); - printf(" control what happens when a log file already exists\n"); - printf(" -shareexists\n"); - printf(" test whether a connection-sharing upstream exists\n"); - exit(1); -} - -static void version(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("plink: %s\n%s\n", ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); -} - -size_t stdin_gotdata(struct handle *h, const void *data, size_t len, int err) -{ - if (err) { - char buf[4096]; - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, - buf, lenof(buf), NULL); - buf[lenof(buf)-1] = '\0'; - if (buf[strlen(buf)-1] == '\n') - buf[strlen(buf)-1] = '\0'; - fprintf(stderr, "Unable to read from standard input: %s\n", buf); - cleanup_exit(0); - } - - noise_ultralight(NOISE_SOURCE_IOLEN, len); - if (backend_connected(backend)) { - if (len > 0) { - backend_send(backend, data, len); - return backend_sendbuffer(backend); - } else { - backend_special(backend, SS_EOF, 0); - return 0; - } - } else - return 0; -} - -void stdouterr_sent(struct handle *h, size_t new_backlog, int err, bool close) -{ - if (close) { - CloseHandle(outhandle); - CloseHandle(errhandle); - outhandle = errhandle = INVALID_HANDLE_VALUE; - } - - if (err) { - char buf[4096]; - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, - buf, lenof(buf), NULL); - buf[lenof(buf)-1] = '\0'; - if (buf[strlen(buf)-1] == '\n') - buf[strlen(buf)-1] = '\0'; - fprintf(stderr, "Unable to write to standard %s: %s\n", - (h == stdout_handle ? "output" : "error"), buf); - cleanup_exit(0); - } - - if (backend_connected(backend)) { - backend_unthrottle(backend, (handle_backlog(stdout_handle) + - handle_backlog(stderr_handle))); - } -} - -const bool share_can_be_downstream = true; -const bool share_can_be_upstream = true; - -const unsigned cmdline_tooltype = - TOOLTYPE_HOST_ARG | - TOOLTYPE_HOST_ARG_CAN_BE_SESSION | - TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX | - TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD; - -static bool sending; - -static bool plink_mainloop_pre(void *vctx, const HANDLE **extra_handles, - size_t *n_extra_handles) -{ - if (!sending && backend_sendok(backend)) { - stdin_handle = handle_input_new(inhandle, stdin_gotdata, NULL, - 0); - sending = true; - } - - return true; -} - -static bool plink_mainloop_post(void *vctx, size_t extra_handle_index) -{ - if (sending) - handle_unthrottle(stdin_handle, backend_sendbuffer(backend)); - - if (!backend_connected(backend) && - handle_backlog(stdout_handle) + handle_backlog(stderr_handle) == 0) - return false; /* we closed the connection */ - - return true; -} - -int main(int argc, char **argv) -{ - int exitcode; - bool errors; - bool use_subsystem = false; - bool just_test_share_exists = false; - enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO; - const struct BackendVtable *vt; - - dll_hijacking_protection(); - - /* - * Initialise port and protocol to sensible defaults. (These - * will be overridden by more or less anything.) - */ - settings_set_default_protocol(PROT_SSH); - settings_set_default_port(22); - - /* - * Process the command line. - */ - conf = conf_new(); - do_defaults(NULL, conf); - settings_set_default_protocol(conf_get_int(conf, CONF_protocol)); - settings_set_default_port(conf_get_int(conf, CONF_port)); - errors = false; - { - /* - * Override the default protocol if PLINK_PROTOCOL is set. - */ - char *p = getenv("PLINK_PROTOCOL"); - if (p) { - const struct BackendVtable *vt = backend_vt_from_name(p); - if (vt) { - settings_set_default_protocol(vt->protocol); - settings_set_default_port(vt->default_port); - conf_set_int(conf, CONF_protocol, vt->protocol); - conf_set_int(conf, CONF_port, vt->default_port); - } - } - } - while (--argc) { - char *p = *++argv; - 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 = true; - } else if (ret == 2) { - --argc, ++argv; - } else if (ret == 1) { - continue; - } else if (!strcmp(p, "-s")) { - /* Save status to write to conf later. */ - use_subsystem = true; - } else if (!strcmp(p, "-V") || !strcmp(p, "--version")) { - version(); - } else if (!strcmp(p, "--help")) { - usage(); - } else if (!strcmp(p, "-pgpfp")) { - pgp_fingerprints(); - exit(1); - } else if (!strcmp(p, "-shareexists")) { - just_test_share_exists = true; - } else if (!strcmp(p, "-sanitise-stdout") || - !strcmp(p, "-sanitize-stdout")) { - sanitise_stdout = FORCE_ON; - } else if (!strcmp(p, "-no-sanitise-stdout") || - !strcmp(p, "-no-sanitize-stdout")) { - sanitise_stdout = FORCE_OFF; - } else if (!strcmp(p, "-sanitise-stderr") || - !strcmp(p, "-sanitize-stderr")) { - sanitise_stderr = FORCE_ON; - } else if (!strcmp(p, "-no-sanitise-stderr") || - !strcmp(p, "-no-sanitize-stderr")) { - sanitise_stderr = FORCE_OFF; - } else if (!strcmp(p, "-no-antispoof")) { - console_antispoof_prompt = false; - } else if (*p != '-') { - strbuf *cmdbuf = strbuf_new(); - - while (argc > 0) { - if (cmdbuf->len > 0) - put_byte(cmdbuf, ' '); /* add space separator */ - put_dataz(cmdbuf, p); - if (--argc > 0) - p = *++argv; - } - - conf_set_str(conf, CONF_remote_cmd, cmdbuf->s); - conf_set_str(conf, CONF_remote_cmd2, ""); - conf_set_bool(conf, CONF_nopty, true); /* command => no tty */ - - strbuf_free(cmdbuf); - break; /* done with cmdline */ - } else { - fprintf(stderr, "plink: unknown option \"%s\"\n", p); - errors = true; - } - } - - if (errors) - return 1; - - if (!cmdline_host_ok(conf)) { - usage(); - } - - prepare_session(conf); - - /* - * Perform command-line overrides on session configuration. - */ - cmdline_run_saved(conf); - - /* - * Apply subsystem status. - */ - if (use_subsystem) - conf_set_bool(conf, CONF_ssh_subsys, true); - - /* - * Select protocol. This is farmed out into a table in a - * separate file to enable an ssh-free variant. - */ - vt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); - if (vt == NULL) { - fprintf(stderr, - "Internal fault: Unsupported protocol found\n"); - return 1; - } - - if (vt->flags & BACKEND_NEEDS_TERMINAL) { - fprintf(stderr, - "Plink doesn't support %s, which needs terminal emulation\n", - vt->displayname_lc); - return 1; - } - - sk_init(); - if (p_WSAEventSelect == NULL) { - fprintf(stderr, "Plink requires WinSock 2\n"); - return 1; - } - - /* - * 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_bool(conf, CONF_x11_forward) && - !conf_get_bool(conf, CONF_agentfwd) && - !conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) - conf_set_bool(conf, CONF_ssh_simple, true); - - logctx = log_init(console_cli_logpolicy, conf); - - if (just_test_share_exists) { - if (!vt->test_for_upstream) { - fprintf(stderr, "Connection sharing not supported for this " - "connection type (%s)'\n", vt->displayname_lc); - return 1; - } - if (vt->test_for_upstream(conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), conf)) - return 0; - else - return 1; - } - - if (restricted_acl()) { - lp_eventlog(console_cli_logpolicy, - "Running with restricted process ACL"); - } - - inhandle = GetStdHandle(STD_INPUT_HANDLE); - outhandle = GetStdHandle(STD_OUTPUT_HANDLE); - errhandle = GetStdHandle(STD_ERROR_HANDLE); - - /* - * Turn off ECHO and LINE input modes. We don't care if this - * call fails, because we know we aren't necessarily running in - * a console. - */ - GetConsoleMode(inhandle, &orig_console_mode); - SetConsoleMode(inhandle, ENABLE_PROCESSED_INPUT); - - /* - * Pass the output handles to the handle-handling subsystem. - * (The input one we leave until we're through the - * authentication process.) - */ - stdout_handle = handle_output_new(outhandle, stdouterr_sent, NULL, 0); - stderr_handle = handle_output_new(errhandle, stdouterr_sent, NULL, 0); - handle_sink_init(&stdout_hs, stdout_handle); - handle_sink_init(&stderr_hs, stderr_handle); - stdout_bs = BinarySink_UPCAST(&stdout_hs); - stderr_bs = BinarySink_UPCAST(&stderr_hs); - - /* - * Decide whether to sanitise control sequences out of standard - * output and standard error. - * - * If we weren't given a command-line override, we do this if (a) - * the fd in question is pointing at a console, and (b) we aren't - * trying to allocate a terminal as part of the session. - * - * (Rationale: the risk of control sequences is that they cause - * confusion when sent to a local console, so if there isn't one, - * no problem. Also, if we allocate a remote terminal, then we - * sent a terminal type, i.e. we told it what kind of escape - * sequences we _like_, i.e. we were expecting to receive some.) - */ - if (sanitise_stdout == FORCE_ON || - (sanitise_stdout == AUTO && is_console_handle(outhandle) && - conf_get_bool(conf, CONF_nopty))) { - stdout_scc = stripctrl_new(stdout_bs, true, L'\0'); - stdout_bs = BinarySink_UPCAST(stdout_scc); - } - if (sanitise_stderr == FORCE_ON || - (sanitise_stderr == AUTO && is_console_handle(errhandle) && - conf_get_bool(conf, CONF_nopty))) { - stderr_scc = stripctrl_new(stderr_bs, true, L'\0'); - stderr_bs = BinarySink_UPCAST(stderr_scc); - } - - /* - * Start up the connection. - */ - winselcli_setup(); /* ensure event object exists */ - { - char *error, *realhost; - /* nodelay is only useful if stdin is a character device (console) */ - bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) && - (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) == FILE_TYPE_CHAR); - - error = backend_init(vt, plink_seat, &backend, logctx, conf, - conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), - &realhost, nodelay, - conf_get_bool(conf, CONF_tcp_keepalives)); - if (error) { - fprintf(stderr, "Unable to open connection:\n%s", error); - sfree(error); - return 1; - } - ldisc_create(conf, NULL, backend, plink_seat); - sfree(realhost); - } - - main_thread_id = GetCurrentThreadId(); - - sending = false; - - cli_main_loop(plink_mainloop_pre, plink_mainloop_post, NULL); - - exitcode = backend_exitcode(backend); - if (exitcode < 0) { - fprintf(stderr, "Remote process exit code unavailable\n"); - exitcode = 1; /* this is an error condition */ - } - cleanup_exit(exitcode); - return 0; /* placate compiler warning */ -} diff --git a/WINDOWS/plink.rc b/WINDOWS/plink.rc deleted file mode 100644 index 5282907d2..000000000 --- a/WINDOWS/plink.rc +++ /dev/null @@ -1,8 +0,0 @@ -#include "rcstuff.h" - -#define APPNAME "Plink" -#define APPDESC "Command-line SSH, Telnet, and Rlogin client" - -200 ICON "putty.ico" - -#include "version.rc2" diff --git a/WINDOWS/printing.c b/WINDOWS/printing.c deleted file mode 100644 index 965b63841..000000000 --- a/WINDOWS/printing.c +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Printing interface for PuTTY. - */ - -#include "putty.h" -#include - -struct printer_enum_tag { - int nprinters; - DWORD enum_level; - union { - LPPRINTER_INFO_4 i4; - LPPRINTER_INFO_5 i5; - } info; -}; - -struct printer_job_tag { - HANDLE hprinter; -}; - -DECL_WINDOWS_FUNCTION(static, BOOL, EnumPrinters, - (DWORD, LPTSTR, DWORD, LPBYTE, DWORD, LPDWORD, LPDWORD)); -DECL_WINDOWS_FUNCTION(static, BOOL, OpenPrinter, - (LPTSTR, LPHANDLE, LPPRINTER_DEFAULTS)); -DECL_WINDOWS_FUNCTION(static, BOOL, ClosePrinter, (HANDLE)); -DECL_WINDOWS_FUNCTION(static, DWORD, StartDocPrinter, (HANDLE, DWORD, LPBYTE)); -DECL_WINDOWS_FUNCTION(static, BOOL, EndDocPrinter, (HANDLE)); -DECL_WINDOWS_FUNCTION(static, BOOL, StartPagePrinter, (HANDLE)); -DECL_WINDOWS_FUNCTION(static, BOOL, EndPagePrinter, (HANDLE)); -DECL_WINDOWS_FUNCTION(static, BOOL, WritePrinter, - (HANDLE, LPVOID, DWORD, LPDWORD)); - -static void init_winfuncs(void) -{ - static bool initialised = false; - if (initialised) - return; - { - HMODULE winspool_module = load_system32_dll("winspool.drv"); - /* Some MSDN documentation claims that some of the below functions - * should be loaded from spoolss.dll, but this doesn't seem to - * be reliable in practice. - * Nevertheless, we load spoolss.dll ourselves using our safe - * loading method, against the possibility that winspool.drv - * later loads it unsafely. */ - (void) load_system32_dll("spoolss.dll"); - GET_WINDOWS_FUNCTION_PP(winspool_module, EnumPrinters); - GET_WINDOWS_FUNCTION_PP(winspool_module, OpenPrinter); - GET_WINDOWS_FUNCTION_PP(winspool_module, ClosePrinter); - GET_WINDOWS_FUNCTION_PP(winspool_module, StartDocPrinter); - GET_WINDOWS_FUNCTION_PP(winspool_module, EndDocPrinter); - GET_WINDOWS_FUNCTION_PP(winspool_module, StartPagePrinter); - GET_WINDOWS_FUNCTION_PP(winspool_module, EndPagePrinter); - GET_WINDOWS_FUNCTION_PP(winspool_module, WritePrinter); - } - initialised = true; -} - -static bool printer_add_enum(int param, DWORD level, char **buffer, - int offset, int *nprinters_ptr) -{ - DWORD needed = 0, nprinters = 0; - - init_winfuncs(); - - *buffer = sresize(*buffer, offset+512, char); - - /* - * Exploratory call to EnumPrinters to determine how much space - * we'll need for the output. - * - * If we get ERROR_INSUFFICIENT_BUFFER, that's fine, we're - * prepared to deal with it. Any other error, we return failure. - */ - if (p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), 512, - &needed, &nprinters) == 0 && - GetLastError() != ERROR_INSUFFICIENT_BUFFER) - return false; - - if (needed < 512) - needed = 512; - - *buffer = sresize(*buffer, offset+needed, char); - - if (p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), - needed, &needed, &nprinters) == 0) - return false; - - *nprinters_ptr += nprinters; - - return true; -} - -printer_enum *printer_start_enum(int *nprinters_ptr) -{ - printer_enum *pe = snew(printer_enum); - char *buffer = NULL; - - *nprinters_ptr = 0; /* default return value */ - buffer = snewn(512, char); - - /* - * Determine what enumeration level to use. - * When enumerating printers, we need to use PRINTER_INFO_4 on - * NT-class systems to avoid Windows looking too hard for them and - * slowing things down; and we need to avoid PRINTER_INFO_5 as - * we've seen network printers not show up. - * On 9x-class systems, PRINTER_INFO_4 isn't available and - * PRINTER_INFO_5 is recommended. - * Bletch. - */ - if (osPlatformId != VER_PLATFORM_WIN32_NT) { - pe->enum_level = 5; - } else { - pe->enum_level = 4; - } - - if (!printer_add_enum(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, - pe->enum_level, &buffer, 0, nprinters_ptr)) - goto error; - - switch (pe->enum_level) { - case 4: - pe->info.i4 = (LPPRINTER_INFO_4)buffer; - break; - case 5: - pe->info.i5 = (LPPRINTER_INFO_5)buffer; - break; - } - pe->nprinters = *nprinters_ptr; - - return pe; - - error: - sfree(buffer); - sfree(pe); - *nprinters_ptr = 0; - return NULL; -} - -char *printer_get_name(printer_enum *pe, int i) -{ - if (!pe) - return NULL; - if (i < 0 || i >= pe->nprinters) - return NULL; - switch (pe->enum_level) { - case 4: - return pe->info.i4[i].pPrinterName; - case 5: - return pe->info.i5[i].pPrinterName; - default: - return NULL; - } -} - -void printer_finish_enum(printer_enum *pe) -{ - if (!pe) - return; - switch (pe->enum_level) { - case 4: - sfree(pe->info.i4); - break; - case 5: - sfree(pe->info.i5); - break; - } - sfree(pe); -} - -printer_job *printer_start_job(char *printer) -{ - printer_job *pj = snew(printer_job); - DOC_INFO_1 docinfo; - bool jobstarted = false, pagestarted = false; - - init_winfuncs(); - - pj->hprinter = NULL; - if (!p_OpenPrinter(printer, &pj->hprinter, NULL)) - goto error; - - docinfo.pDocName = "PuTTY remote printer output"; - docinfo.pOutputFile = NULL; - docinfo.pDatatype = "RAW"; - - if (!p_StartDocPrinter(pj->hprinter, 1, (LPBYTE)&docinfo)) - goto error; - jobstarted = true; - - if (!p_StartPagePrinter(pj->hprinter)) - goto error; - pagestarted = true; - - return pj; - - error: - if (pagestarted) - p_EndPagePrinter(pj->hprinter); - if (jobstarted) - p_EndDocPrinter(pj->hprinter); - if (pj->hprinter) - p_ClosePrinter(pj->hprinter); - sfree(pj); - return NULL; -} - -void printer_job_data(printer_job *pj, const void *data, size_t len) -{ - DWORD written; - - if (!pj) - return; - - p_WritePrinter(pj->hprinter, (void *)data, len, &written); -} - -void printer_finish_job(printer_job *pj) -{ - if (!pj) - return; - - p_EndPagePrinter(pj->hprinter); - p_EndDocPrinter(pj->hprinter); - p_ClosePrinter(pj->hprinter); - sfree(pj); -} diff --git a/WINDOWS/pscp.ico b/WINDOWS/pscp.ico deleted file mode 100644 index 2b3c1be084362f0cba56511f9dca36986c1940e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4078 zcmds3KZ_Je6o2gw_MW|7Z+6LXZn%NLga*PcIAGv@gbPLvL@-$pZzdS*I!<)pL_dIH zx*c_y^7r1W>gnm}5nWD@do!!a`JWNK~wgwh98l!*~UCQ$u$ObEavX*;b{8+_}qu2>?DK1pWbX%O_$(|zp zG3Ft$9cQa!?q=EEcx+>T)U=L5x}r)aGp`w%5d7~vJhu?L#_ZHeU_!+ zU@-6wqClLX8xZLP;lu~fcqZ{OWteE+g~KFqlorKbHF-N*o0E4aotKMft?s{5o9S)%!qAe;gi{+va-sQ2B{n)U$88pqH>VYTuVFGZgMR`SXg>h9071 zdG_ebFdi;r+#~B_`8c+m)dp{K*oahF8#@BKb ztD;!c7hKia^m&8TkL5;~Pd0rWw9~#Iio8 zxK>U+aQ;AfIsey)1?o%RqoEVc?@s*y+m;nAHh$v?Ouv8`&w?+3d*I6>guaK9e2J~x zg<=PI61)b+c7%9P8_OG0T{tt%Z$Fk7zyEZ-Mu#o5)p{}a@JCDw+#Wf^npi#$PEKR( zKLhW>{sj0B(V2gNA0AC8czT+)UQa3hfHyP!L@STp5Irv`=8UGZr`ET?{2BTN3S1UR{iLTV)9$qPU1fde zv3rPa;D$hbm1@{1X2IfjS()ng! z5Z|sOJGL)_GYJG{vdQYdU9GgMm9&;fCdV?JPQkIvGm$qUGMfeY>ygMMy2T>MZ;wUZ zjRpH7>@)d*?Uxs7ALIqLmoL>m$|I3aCn9s~raCXIew3WGLk&IJ>0AI>KBaA2P&>T+ zi}(5k^QS6pZlSzUaxy8)s<~)ng0c6Ni0ONgyrhT0ldG$%gD?!=br<3w^qd~n&SH%n z*dn*+hq#+){2@;>hMd~4F)hR%43onVJ>fTQd5A9xe{ypV)Dv3-4sP)mRqn|a=(-*XzmW@)Rxsfkk6v)-gsykWmRU^CBu z{1sA@ZpYzBy_JmO+3DI7)=^$YEF$uDFuMWNc?3PR+mO0#NNqM`YAQ4?T=4g3*hc?d zL;~0Kd;QT-G`c&^S>6<$!TMfRI<wba_0R{9l0rCUXJ=!}W<}1Knw1m2+ zyQ@NLGc%XFdD{GJADm diff --git a/WINDOWS/pterm.rc b/WINDOWS/pterm.rc deleted file mode 100644 index 8bd3a0431..000000000 --- a/WINDOWS/pterm.rc +++ /dev/null @@ -1,15 +0,0 @@ -#include "rcstuff.h" -#include "putty-rc.h" - -#define APPNAME "pterm" -#define APPDESC "PuTTY-style wrapper for Windows command prompts" - -IDI_MAINICON ICON "pterm.ico" -IDI_CFGICON ICON "ptermcfg.ico" - -#include "help.rc2" -#include "putty-common.rc2" - -#ifndef NO_MANIFESTS -1 RT_MANIFEST "putty.mft" -#endif /* NO_MANIFESTS */ diff --git a/WINDOWS/ptermcfg.ico b/WINDOWS/ptermcfg.ico deleted file mode 100644 index 53bde87ecd2e98249e03cb47dfc370fb77dce8a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4078 zcmeHKzl$436n^WuctuXh3Jf8EaC=p_QbFnDLW7b z?$J?1^feJ39xDIy7SVm!x?Sbp+$Q>Nj}U(g@exfB`}?~bue^)c`}-UpxFPzbO>~5~ z4)+V?aagRBy9#*NX{`cERLMC9WQUi3mX#?g7Z#>HLZ;;uLybL(lvd+IKEucz+7;mpx_6>mT%CoDRCe*%kq zWZ=BjV#!gMFLtfajJFxYVB6NE$7!v}2CaE^R3*1sJOey{#xsoJuyW8OAagT=VoRF> zx`ZE^ZYumK;!?zmTHsI6$56x$iq!jJ;Qi98@ILQC&!FW#w|gKv#S*qQ)GDAv_3iI- z4EY*@Q@&Gr&E+d&uu;lt1AEvnY{=vgRfX)UZM#Ld#(Ui~C^@PMr?nj}oRbjVv`cu) za?5(WhRdt(jV?( z!Bs%dh<^AiMIdhqc^vMK$7SSsFwSnkU!D3NA%Nqq1A3qV_im*5vz$@zD4+>=5s-oX z0gtHu?&JrVzOAIPQAk6!@p}vJ+|d;JGxRg?Z;3vBin&cNpDxiC&`+QrK;N4aeaP(~ zZ}$DC^R?3Rc2Ui@>B5X?nEK^B^*_#1|Ke%tuP;;oWZ0&I;vN6_Fj{?o87&tsivDns zdE}z_o{JLv)E_|a8W$JFC3EA_nQg7ieOFYBTK_^eJ-i#N@Y2bvfMpkg?&jeqt)c@jd#p}O-v87}IG*|xO zIWW5NgWrI3wHoD&gQfogK;3Ay6f{f4xKK>yis?*o;1owz(Uah(E5$oY#kf$!e@@tM zaIhF-EUV&`XM8^$3APjEt$>uMo$n-(5)T5u8}KaP4S=*k*5|X%|Gy8{_v72T5B~uG C&{6LI diff --git a/WINDOWS/putty-common.rc2 b/WINDOWS/putty-common.rc2 deleted file mode 100644 index a43e3ac10..000000000 --- a/WINDOWS/putty-common.rc2 +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Windows resources shared between PuTTY and PuTTYtel, to be #include'd - * after defining appropriate macros. - * - * Note that many of these strings mention PuTTY. Due to restrictions in - * VC's handling of string concatenation, this can't easily be fixed. - * It's fixed up at runtime. - * - * This file has the more or less arbitrary extension '.rc2' to avoid - * IDEs taking it to be a top-level resource script in its own right - * (which has been known to happen if the extension was '.rc'), and - * also to avoid the resource compiler ignoring everything included - * from it (which happens if the extension is '.h'). - */ - -/* Accelerators used: clw */ -IDD_ABOUTBOX DIALOG DISCARDABLE 140, 40, 270, 136 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "About PuTTY" -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "&Close", IDOK, 216, 118, 48, 14 - PUSHBUTTON "View &Licence", IDA_LICENCE, 6, 118, 70, 14 - PUSHBUTTON "Visit &Web Site", IDA_WEB, 140, 118, 70, 14 - EDITTEXT IDA_TEXT, 10, 6, 250, 110, ES_READONLY | ES_MULTILINE | ES_CENTER, WS_EX_STATICEDGE -END - -/* Accelerators used: aco */ -IDD_MAINBOX DIALOG DISCARDABLE 0, 0, 300, 252 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Configuration" -FONT 8, "MS Shell Dlg" -CLASS "PuTTYConfigBox" -BEGIN -END - -/* Accelerators used: co */ -IDD_LOGBOX DIALOG DISCARDABLE 100, 20, 300, 119 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Event Log" -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "&Close", IDOK, 135, 102, 44, 14 - PUSHBUTTON "C&opy", IDN_COPY, 81, 102, 44, 14 - LISTBOX IDN_LIST, 3, 3, 294, 95, LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | LBS_EXTENDEDSEL -END - -/* No accelerators used */ -IDD_LICENCEBOX DIALOG DISCARDABLE 50, 50, 326, 239 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Licence" -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "OK", IDOK, 148, 219, 44, 14 - - EDITTEXT IDA_TEXT, 10, 10, 306, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE -END - -/* Accelerators used: achio */ -IDD_HOSTKEY DIALOG DISCARDABLE 50, 50, 340, 240 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Security Alert" -FONT 8, "MS Shell Dlg" -CLASS "PuTTYHostKeyDialog" -BEGIN - ICON "", IDC_HK_ICON, 10, 18, 0, 0 - - PUSHBUTTON "&Cancel", IDCANCEL, 288, 220, 40, 14 - PUSHBUTTON "&Accept", IDC_HK_ACCEPT, 168, 220, 40, 14 - PUSHBUTTON "Connect &Once", IDC_HK_ONCE, 216, 220, 64, 14 - PUSHBUTTON "More &info...", IDC_HK_MOREINFO, 60, 220, 64, 14 - PUSHBUTTON "&Help", IDHELP, 12, 220, 40, 14 - - LTEXT "", IDC_HK_TITLE, 40, 20, 300, 12 - - EDITTEXT IDC_HK_TEXT, 40, 20, 290, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE -END - -/* Accelerators used: c */ -IDD_HK_MOREINFO DIALOG DISCARDABLE 140, 40, 400, 300 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY: information about the server's host key" -FONT 8, "MS Shell Dlg" -CLASS "PuTTYHostKeyMoreInfo" -BEGIN - DEFPUSHBUTTON "&Close", IDOK, 176, 130, 48, 14 -END - -/* Accelerators used: aco */ -IDD_CA_CONFIG DIALOG DISCARDABLE 0, 0, 350, 260 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY trusted host certification authorities" -FONT 8, "MS Shell Dlg" -CLASS "PuTTYConfigBox" -BEGIN -END - -#include "version.rc2" diff --git a/WINDOWS/putty-rc.h b/WINDOWS/putty-rc.h deleted file mode 100644 index 35d9bcdab..000000000 --- a/WINDOWS/putty-rc.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * putty-rc.h - constants shared between putty-common.rc2 and the C code. - */ - -#ifndef PUTTY_WIN_RES_H -#define PUTTY_WIN_RES_H - -#define IDI_MAINICON 200 -#define IDI_CFGICON 201 - -#define IDD_MAINBOX 102 -#define IDD_LOGBOX 110 -#define IDD_ABOUTBOX 111 -#define IDD_RECONF 112 -#define IDD_LICENCEBOX 113 -#define IDD_HOSTKEY 114 -#define IDD_HK_MOREINFO 116 -#define IDD_CA_CONFIG 117 - -#define IDN_LIST 1001 -#define IDN_COPY 1002 - -#define IDA_ICON 1001 -#define IDA_TEXT 1002 -#define IDA_LICENCE 1003 -#define IDA_WEB 1004 - -#define IDC_TAB 1001 -#define IDC_TABSTATIC1 1002 -#define IDC_TABSTATIC2 1003 -#define IDC_TABLIST 1004 -#define IDC_HELPBTN 1005 -#define IDC_ABOUT 1006 - -#define IDC_HK_ICON 98 -#define IDC_HK_TITLE 99 -#define IDC_HK_TEXT 100 -#define IDC_HK_ACCEPT 1001 -#define IDC_HK_ONCE 1000 -#define IDC_HK_HOST 1002 -#define IDC_HK_FINGERPRINT 1003 -#define IDC_HK_MOREINFO 1004 - -#define IDC_HKI_SHA256 1000 -#define IDC_HKI_MD5 1001 -#define IDC_HKI_PUBKEY 1002 - -#define ID_CUSTOM_CHMFILE 2000 -#define TYPE_CUSTOM_CHMFILE 2000 - -#endif diff --git a/WINDOWS/putty.c b/WINDOWS/putty.c deleted file mode 100644 index 9c8ba0019..000000000 --- a/WINDOWS/putty.c +++ /dev/null @@ -1,203 +0,0 @@ -#include "putty.h" -#include "storage.h" - -extern bool sesslist_demo_mode; -extern const char *dialog_box_demo_screenshot_filename; -static strbuf *demo_terminal_data = NULL; -static const char *terminal_demo_screenshot_filename; - -const unsigned cmdline_tooltype = - TOOLTYPE_HOST_ARG | - TOOLTYPE_PORT_ARG | - TOOLTYPE_NO_VERBOSE_OPTION; - -void gui_term_process_cmdline(Conf *conf, char *cmdline) -{ - char *p; - bool special_launchable_argument = false; - bool demo_config_box = false; - - settings_set_default_protocol(be_default_protocol); - /* Find the appropriate default port. */ - { - const struct BackendVtable *vt = - backend_vt_from_proto(be_default_protocol); - settings_set_default_port(0); /* illegal */ - if (vt) - settings_set_default_port(vt->default_port); - } - conf_set_int(conf, CONF_logtype, LGTYP_NONE); - - do_defaults(NULL, conf); - - p = handle_restrict_acl_cmdline_prefix(cmdline); - - if (handle_special_sessionname_cmdline(p, conf)) { - if (!conf_launchable(conf) && !do_config(conf)) { - cleanup_exit(0); - } - special_launchable_argument = true; - } else if (handle_special_filemapping_cmdline(p, conf)) { - special_launchable_argument = true; - } else if (!*p) { - /* Do-nothing case for an empty command line - or rather, - * for a command line that's empty _after_ we strip off - * the &R prefix. */ - } else { - /* - * Otherwise, break up the command line and deal with - * it sensibly. - */ - int argc, i; - char **argv; - - split_into_argv(cmdline, false, &argc, &argv, NULL); - - for (i = 0; i < argc; i++) { - char *p = argv[i]; - int ret; - - ret = cmdline_process_param(p, i+1= argc) { - cmdline_error("%s expects an output filename", p); - } else { - demo_config_box = true; - dialog_box_demo_screenshot_filename = argv[++i]; - } - } else if (!strcmp(p, "-demo-terminal")) { - if (i+2 >= argc) { - cmdline_error("%s expects input and output filenames", p); - } else { - const char *infile = argv[++i]; - terminal_demo_screenshot_filename = argv[++i]; - FILE *fp = fopen(infile, "rb"); - if (!fp) - cmdline_error("can't open input file '%s'", infile); - demo_terminal_data = strbuf_new(); - char buf[4096]; - int retd; - while ((retd = fread(buf, 1, sizeof(buf), fp)) > 0) - put_data(demo_terminal_data, buf, retd); - fclose(fp); - } - } else if (*p != '-') { - cmdline_error("unexpected argument \"%s\"", p); - } else { - cmdline_error("unknown option \"%s\"", p); - } - } - } - - cmdline_run_saved(conf); - - if (demo_config_box) { - sesslist_demo_mode = true; - load_open_settings(NULL, conf); - conf_set_str(conf, CONF_host, "demo-server.example.com"); - do_config(conf); - cleanup_exit(0); - } else if (demo_terminal_data) { - /* Ensure conf will cause an immediate session launch */ - load_open_settings(NULL, conf); - conf_set_str(conf, CONF_host, "demo-server.example.com"); - conf_set_int(conf, CONF_close_on_exit, FORCE_OFF); - } else { - /* - * Bring up the config dialog if the command line hasn't - * (explicitly) specified a launchable configuration. - */ - if (!(special_launchable_argument || cmdline_host_ok(conf))) { - if (!do_config(conf)) - cleanup_exit(0); - } - } - - prepare_session(conf); -} - -const struct BackendVtable *backend_vt_from_conf(Conf *conf) -{ - if (demo_terminal_data) { - return &null_backend; - } - - /* - * Select protocol. This is farmed out into a table in a - * separate file to enable an ssh-free variant. - */ - const struct BackendVtable *vt = backend_vt_from_proto( - conf_get_int(conf, CONF_protocol)); - if (!vt) { - char *str = dupprintf("%s Internal Error", appname); - MessageBox(NULL, "Unsupported protocol number found", - str, MB_OK | MB_ICONEXCLAMATION); - sfree(str); - cleanup_exit(1); - } - return vt; -} - -const wchar_t *get_app_user_model_id(void) -{ - return L"SimonTatham.PuTTY"; -} - -static void demo_terminal_screenshot(void *ctx, unsigned long now) -{ - HWND hwnd = (HWND)ctx; - char *err = save_screenshot(hwnd, terminal_demo_screenshot_filename); - if (err) { - MessageBox(hwnd, err, "Demo screenshot failure", MB_OK | MB_ICONERROR); - sfree(err); - } - cleanup_exit(0); -} - -void gui_terminal_ready(HWND hwnd, Seat *seat, Backend *backend) -{ - if (demo_terminal_data) { - ptrlen data = ptrlen_from_strbuf(demo_terminal_data); - seat_stdout(seat, data.ptr, data.len); - schedule_timer(TICKSPERSEC, demo_terminal_screenshot, (void *)hwnd); - } -} diff --git a/WINDOWS/putty.ico b/WINDOWS/putty.ico deleted file mode 100644 index fcd641a0a9b002b453e9f746ceb19b859384657b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4078 zcmeHJL8~K05UzO%Z=w^KciV%o39R4~1oHzH64-#kz8|qC@#Z_39K^J^_;Qf_1B$;P z;2stRc1IBQGO&NZU84u}q&Y0Sh>x`2S6!1glZ@iZ6E9X${dHB>)O1%@*R)6^hjMgu z1Z>N3B=W9^^!q{n`9S0zbb~>V-#Zlfpe?XJfPEwj*gkui_CX%N_Fh7JlUpJWyCTQ1 zJEC7O-$QcRHZ>UGpmPDxqAPix13BRJFW$2U#*ZpCM)7<;cj%|y&*#)ZR-B$qsF&yW zA1hwmekb5KgN?{K%NF5Lb~uzgIGX~bbNfUw{pNnaVjR$WMcRm)p&5dE=#$Y`MjRYs z<1mziFVNrR4Qv>vK6nH!<0ROFW^%zHVuBtRKXhIsPIyd-@lrB5oq(KlrY#PP+B+e9^7h7s;Y7=UJ!Xe>@&1 zOiv@ee#N~7Iq2fa1bdss*t;{EbOu^LKT!kNsG>6MBvWVcnhr?8pPM$WKoxkq+7K=De@h;?hlI`zg_XqRaV|F+{VdedY02frVKrj6r6%% zWe3c`OX!vuZ)S;?BCQqDa}95JKs*Ew!Cy!Oj{lN3826GM!#1 zeiQgZ=bz*`9#@J(dTKUYh7Xab>YW(_((`R3aa1aL2Ha>`rVZ^&CGfnW7bKlg?5p~C zdjj2Tn_jfMJbAHq_NeM-;T7wu7uw(D!77S>Q;(C{=pQGxQJ>=!G|nfH20c=Gl(qNl zb+2&bch{?7SfP4Xqk}-`hE0-cO<>%3@Ij}^!l}5|gfqUUMy=S_X@#(&g85YpSUR#! z$gk?m60Iq$I#Np{^Q4QZE-}^DhIFbW5*A>u+eswhhm5qFNGNt(`EN%utbE3Ci%S_CWfhe5Hs9sy|FHpyM5k-4c#X}?`|2E`aeO7 pWMqgVuFoOV0fpqOuAtkq - - - - A network client and terminal emulator - - - - - - - - - - true - - PerMonitorV2 - - - - diff --git a/WINDOWS/putty.rc b/WINDOWS/putty.rc deleted file mode 100644 index a7d077113..000000000 --- a/WINDOWS/putty.rc +++ /dev/null @@ -1,15 +0,0 @@ -#include "rcstuff.h" -#include "putty-rc.h" - -#define APPNAME "PuTTY" -#define APPDESC "SSH, Telnet, Rlogin, and SUPDUP client" - -IDI_MAINICON ICON "putty.ico" -IDI_CFGICON ICON "puttycfg.ico" - -#include "help.rc2" -#include "putty-common.rc2" - -#ifndef NO_MANIFESTS -1 RT_MANIFEST "putty.mft" -#endif /* NO_MANIFESTS */ diff --git a/WINDOWS/puttycfg.ico b/WINDOWS/puttycfg.ico deleted file mode 100644 index bb32be844b0bb68f748a3406b275a0b8af75bb7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4078 zcmeHJ&x;&I6n>o^rUz%V_a?!R8Bt6G4+C=%c6AoA;34@V77yY{=H!ehnOY;+IY`!{ zpk%#yZ7_#OL+lVSm%$uG2uUx3dNQ*R$O!AU-}hei{K#$&ija%7`}(W*qh3|LdatHL z0;$T%$_lU|X9JOUM5NU+{`V6iS0HP*jeoZ)@_t2Ne**S_B(S~r4DF3?!}dy@_MQ(# zZZt&B!fu6rL7a!p>G!w~G*migfG@g|Wf_o)LjR>G*aP8@B7S^}Mx&9FFo}y|G?LXs zz>mUIWU$?LVCCVp+hpb7{5n}V*yyCSk;tU8n&{9@ijI@j^qcG319++@=Pv*yy#D>Q z0S2tb;|*Y)Q;@gjJ^;3TAF*)Qzl%JQhbjX(^brVI0euC^K)=G4acY6qL1ml-XK;LT z<`6MK4}?E-{z;rLOdI2+>^|%}H(GOrShO1B8hC!=|-80w1nI)-$^y_)f}z4%A&V zg_?2Z=^#+9RLjFL8hEb;FUs3!9MHJlfWB0N&yvQ#m1>T!Cxe-qe3tsTIp4#Ljg9(T zt@BSXn#;l}BwK$h{GnZC37a4+zTmn!Ck_Z$| zke*Y4B17polnwehWLZU`tRayDUIzV${sCxu3bcm)p+qRt_$=((piNMj=NR*Z=D1*x zGr$+!f0gI(qAjxY)Eu?&9V2%p<2QaZizG^AL9S_SpMj+fs@NZd*F7?MhgBhcezZk} z%8@8!qge&Ip`{n|1#Mn=QQ^wGqI_uXr{25f7v-O@u*PGPTN*3 zH9fF?9YyUU>E$f&e!sWuy&S}{S~mBZCzzXeOQXfSQ1g1tE8NVzdQTJHI8xT;xQkNV`e0;<1Y^i?$Mott zttE_3Npl=ThRdoY1E(yq_0h z-hgJ0Tkm-Jd|p^0YNh>>-0o>jB;?Y}KQCO``$L&U*#~m|ljdkPZ9H=Oc#vlJM> zR?q?QGea*{zm!a*!HXWh5vSB&j%CwtE*~l0%#FY0@weRMMTwc`-GQwq@ECgB#R9ob zF3`|1PW+u&0kuGUud=P{_vlB*LOlE!^aDBwOcY7#6@os7h8EPyp+|#ZC5+D zBej>VO6`R;sWk>tt4t*R^T9N_b$3n9e0?bKhlg?+e{X#EM>M@ZiT5{q&AoQ-i`JPZ`W zznolzIGWxmZcx47FYeL*-WNsA`EGt1DsEjSKGzJkh>dv5T>{pC=YSFij`AaT+RN8) ZZBW1Ik)=qUAvRRT{eK^79G=#F_&4lDQ0o8y diff --git a/WINDOWS/puttygen-rc.h b/WINDOWS/puttygen-rc.h deleted file mode 100644 index 582648a67..000000000 --- a/WINDOWS/puttygen-rc.h +++ /dev/null @@ -1,17 +0,0 @@ -#define IDC_PPKVER_STATIC 100 -#define IDC_PPKVER_2 101 -#define IDC_PPKVER_3 102 -#define IDC_KDF_STATIC 103 -#define IDC_KDF_ARGON2ID 104 -#define IDC_KDF_ARGON2I 105 -#define IDC_KDF_ARGON2D 106 -#define IDC_ARGON2_MEM_STATIC 107 -#define IDC_ARGON2_MEM 108 -#define IDC_ARGON2_MEM_STATIC2 109 -#define IDC_PPK_AUTO_STATIC 110 -#define IDC_PPK_AUTO_YES 111 -#define IDC_PPK_AUTO_NO 112 -#define IDC_ARGON2_TIME_STATIC 113 -#define IDC_ARGON2_TIME 114 -#define IDC_ARGON2_PARALLEL_STATIC 115 -#define IDC_ARGON2_PARALLEL 116 diff --git a/WINDOWS/puttygen.c b/WINDOWS/puttygen.c deleted file mode 100644 index bfd732ac0..000000000 --- a/WINDOWS/puttygen.c +++ /dev/null @@ -1,2612 +0,0 @@ -/* - * PuTTY key generation front end (Windows). - */ - -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshkeygen.h" -#include "licence.h" -#include "security-api.h" -#include "puttygen-rc.h" - -#include - -#ifdef MSVC4 -#define ICON_BIG 1 -#endif - -#define WM_DONEKEY (WM_APP + 1) - -#define DEFAULT_KEY_BITS 2048 -#define DEFAULT_ECCURVE_INDEX 0 -#define DEFAULT_EDCURVE_INDEX 0 - -static char *cmdline_keyfile = NULL; -static ptrlen cmdline_demo_keystr; -static const char *demo_screenshot_filename = NULL; - -/* - * Print a modal (Really Bad) message box and perform a fatal exit. - */ -void modalfatalbox(const char *fmt, ...) -{ - va_list ap; - char *stuff; - - va_start(ap, fmt); - stuff = dupvprintf(fmt, ap); - va_end(ap); - MessageBox(NULL, stuff, "PuTTYgen Fatal Error", - MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); - sfree(stuff); - exit(1); -} - -/* - * Print a non-fatal message box and do not exit. - */ -void nonfatal(const char *fmt, ...) -{ - va_list ap; - char *stuff; - - va_start(ap, fmt); - stuff = dupvprintf(fmt, ap); - va_end(ap); - MessageBox(NULL, stuff, "PuTTYgen Error", - MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); - sfree(stuff); -} - -/* ---------------------------------------------------------------------- - * ProgressReceiver implementation. - */ - -#define PROGRESSRANGE 65535 - -struct progressphase { - double startpoint, total; - /* For exponential phases */ - double exp_probability, exp_current_value; -}; - -struct progress { - size_t nphases, phasessize; - struct progressphase *phases, *currphase; - - double scale; - HWND progbar; - - ProgressReceiver rec; -}; - -static ProgressPhase win_progress_add_linear( - ProgressReceiver *prog, double overall_cost) { - struct progress *p = container_of(prog, struct progress, rec); - - sgrowarray(p->phases, p->phasessize, p->nphases); - int phase = p->nphases++; - - p->phases[phase].total = overall_cost; - - ProgressPhase ph = { .n = phase }; - return ph; -} - -static ProgressPhase win_progress_add_probabilistic( - ProgressReceiver *prog, double cost_per_attempt, double probability) { - struct progress *p = container_of(prog, struct progress, rec); - - sgrowarray(p->phases, p->phasessize, p->nphases); - int phase = p->nphases++; - - p->phases[phase].exp_probability = 1.0 - probability; - p->phases[phase].exp_current_value = 1.0; - /* Expected number of attempts = 1 / probability of attempt succeeding */ - p->phases[phase].total = cost_per_attempt / probability; - - ProgressPhase ph = { .n = phase }; - return ph; -} - -static void win_progress_ready(ProgressReceiver *prog) -{ - struct progress *p = container_of(prog, struct progress, rec); - - double total = 0; - for (int i = 0; i < p->nphases; i++) { - p->phases[i].startpoint = total; - total += p->phases[i].total; - } - p->scale = PROGRESSRANGE / total; - - SendMessage(p->progbar, PBM_SETRANGE, 0, MAKELPARAM(0, PROGRESSRANGE)); -} - -static void win_progress_start_phase(ProgressReceiver *prog, - ProgressPhase phase) -{ - struct progress *p = container_of(prog, struct progress, rec); - - assert(phase.n < p->nphases); - p->currphase = &p->phases[phase.n]; -} - -static void win_progress_update(struct progress *p, double phasepos) -{ - double position = (p->currphase->startpoint + - p->currphase->total * phasepos); - position *= p->scale; - if (position < 0) - position = 0; - if (position > PROGRESSRANGE) - position = PROGRESSRANGE; - - SendMessage(p->progbar, PBM_SETPOS, (WPARAM)position, 0); -} - -static void win_progress_report(ProgressReceiver *prog, double progress) -{ - struct progress *p = container_of(prog, struct progress, rec); - - win_progress_update(p, progress); -} - -static void win_progress_report_attempt(ProgressReceiver *prog) -{ - struct progress *p = container_of(prog, struct progress, rec); - - p->currphase->exp_current_value *= p->currphase->exp_probability; - win_progress_update(p, 1.0 - p->currphase->exp_current_value); -} - -static void win_progress_report_phase_complete(ProgressReceiver *prog) -{ - struct progress *p = container_of(prog, struct progress, rec); - - win_progress_update(p, 1.0); -} - -static const ProgressReceiverVtable win_progress_vt = { - .add_linear = win_progress_add_linear, - .add_probabilistic = win_progress_add_probabilistic, - .ready = win_progress_ready, - .start_phase = win_progress_start_phase, - .report = win_progress_report, - .report_attempt = win_progress_report_attempt, - .report_phase_complete = win_progress_report_phase_complete, -}; - -static void win_progress_initialise(struct progress *p) -{ - p->nphases = p->phasessize = 0; - p->phases = p->currphase = NULL; - p->rec.vt = &win_progress_vt; -} - -static void win_progress_cleanup(struct progress *p) -{ - sfree(p->phases); -} - -struct PassphraseProcStruct { - char **passphrase; - char *comment; -}; - -/* - * Dialog-box function for the passphrase box. - */ -static INT_PTR CALLBACK PassphraseProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - static char **passphrase = NULL; - struct PassphraseProcStruct *p; - - switch (msg) { - case WM_INITDIALOG: - SetForegroundWindow(hwnd); - SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); - - /* - * Centre the window. - */ - { /* centre the window */ - RECT rs, rd; - HWND hw; - - hw = GetDesktopWindow(); - if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd)) - MoveWindow(hwnd, - (rs.right + rs.left + rd.left - rd.right) / 2, - (rs.bottom + rs.top + rd.top - rd.bottom) / 2, - rd.right - rd.left, rd.bottom - rd.top, true); - } - - p = (struct PassphraseProcStruct *) lParam; - passphrase = p->passphrase; - if (p->comment) - SetDlgItemText(hwnd, 101, p->comment); - burnstr(*passphrase); - *passphrase = dupstr(""); - SetDlgItemText(hwnd, 102, *passphrase); - return 0; - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - if (*passphrase) - EndDialog(hwnd, 1); - else - MessageBeep(0); - return 0; - case IDCANCEL: - EndDialog(hwnd, 0); - return 0; - case 102: /* edit box */ - if ((HIWORD(wParam) == EN_CHANGE) && passphrase) { - burnstr(*passphrase); - *passphrase = GetDlgItemText_alloc(hwnd, 102); - } - return 0; - } - return 0; - case WM_CLOSE: - EndDialog(hwnd, 0); - return 0; - } - return 0; -} - -static void try_get_dlg_item_uint32(HWND hwnd, int id, uint32_t *out) -{ - char buf[128]; - if (!GetDlgItemText(hwnd, id, buf, sizeof(buf))) - return; - - if (!*buf) - return; - - char *end; - unsigned long val = strtoul(buf, &end, 10); - if (*end) - return; - - if ((val >> 16) >> 16) - return; - - *out = val; -} - -static ppk_save_parameters save_params; - -struct PPKParams { - ppk_save_parameters params; - uint32_t time_passes, time_ms; -}; - -/* - * Dialog-box function for the passphrase box. - */ -static INT_PTR CALLBACK PPKParamsProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - struct PPKParams *pp; - char *buf; - - if (msg == WM_INITDIALOG) { - pp = (struct PPKParams *)lParam; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pp); - } else { - pp = (struct PPKParams *)GetWindowLongPtr(hwnd, GWLP_USERDATA); - } - - switch (msg) { - case WM_INITDIALOG: - SetForegroundWindow(hwnd); - SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); - - if (has_help()) - SetWindowLongPtr(hwnd, GWL_EXSTYLE, - GetWindowLongPtr(hwnd, GWL_EXSTYLE) | - WS_EX_CONTEXTHELP); - - /* - * Centre the window. - */ - { /* centre the window */ - RECT rs, rd; - HWND hw; - - hw = GetDesktopWindow(); - if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd)) - MoveWindow(hwnd, - (rs.right + rs.left + rd.left - rd.right) / 2, - (rs.bottom + rs.top + rd.top - rd.bottom) / 2, - rd.right - rd.left, rd.bottom - rd.top, true); - } - - CheckRadioButton(hwnd, IDC_PPKVER_2, IDC_PPKVER_3, - IDC_PPKVER_2 + (pp->params.fmt_version - 2)); - - CheckRadioButton( - hwnd, IDC_KDF_ARGON2ID, IDC_KDF_ARGON2D, - (pp->params.argon2_flavour == Argon2id ? IDC_KDF_ARGON2ID : - pp->params.argon2_flavour == Argon2i ? IDC_KDF_ARGON2I : - /* pp->params.argon2_flavour == Argon2d ? */ IDC_KDF_ARGON2D)); - - buf = dupprintf("%"PRIu32, pp->params.argon2_mem); - SetDlgItemText(hwnd, IDC_ARGON2_MEM, buf); - sfree(buf); - - if (pp->params.argon2_passes_auto) { - CheckRadioButton(hwnd, IDC_PPK_AUTO_YES, IDC_PPK_AUTO_NO, - IDC_PPK_AUTO_YES); - buf = dupprintf("%"PRIu32, pp->time_ms); - SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf); - sfree(buf); - } else { - CheckRadioButton(hwnd, IDC_PPK_AUTO_YES, IDC_PPK_AUTO_NO, - IDC_PPK_AUTO_NO); - buf = dupprintf("%"PRIu32, pp->time_passes); - SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf); - sfree(buf); - } - - buf = dupprintf("%"PRIu32, pp->params.argon2_parallelism); - SetDlgItemText(hwnd, IDC_ARGON2_PARALLEL, buf); - sfree(buf); - - return 0; - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - EndDialog(hwnd, 1); - return 0; - case IDCANCEL: - EndDialog(hwnd, 0); - return 0; - case IDC_PPKVER_2: - pp->params.fmt_version = 2; - return 0; - case IDC_PPKVER_3: - pp->params.fmt_version = 3; - return 0; - case IDC_KDF_ARGON2ID: - pp->params.argon2_flavour = Argon2id; - return 0; - case IDC_KDF_ARGON2I: - pp->params.argon2_flavour = Argon2i; - return 0; - case IDC_KDF_ARGON2D: - pp->params.argon2_flavour = Argon2d; - return 0; - case IDC_ARGON2_MEM: - try_get_dlg_item_uint32(hwnd, IDC_ARGON2_MEM, - &pp->params.argon2_mem); - return 0; - case IDC_PPK_AUTO_YES: - pp->params.argon2_passes_auto = true; - buf = dupprintf("%"PRIu32, pp->time_ms); - SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf); - sfree(buf); - return 0; - case IDC_PPK_AUTO_NO: - pp->params.argon2_passes_auto = false; - buf = dupprintf("%"PRIu32, pp->time_passes); - SetDlgItemText(hwnd, IDC_ARGON2_TIME, buf); - sfree(buf); - return 0; - case IDC_ARGON2_TIME: - try_get_dlg_item_uint32(hwnd, IDC_ARGON2_TIME, - pp->params.argon2_passes_auto ? - &pp->time_ms : &pp->time_passes); - return 0; - case IDC_ARGON2_PARALLEL: - try_get_dlg_item_uint32(hwnd, IDC_ARGON2_PARALLEL, - &pp->params.argon2_parallelism); - return 0; - } - return 0; - case WM_HELP: { - int id = ((LPHELPINFO)lParam)->iCtrlId; - const char *topic = NULL; - switch (id) { - case IDC_PPKVER_STATIC: - case IDC_PPKVER_2: - case IDC_PPKVER_3: - topic = WINHELP_CTX_puttygen_ppkver; break; - case IDC_KDF_STATIC: - case IDC_KDF_ARGON2ID: - case IDC_KDF_ARGON2I: - case IDC_KDF_ARGON2D: - case IDC_ARGON2_MEM_STATIC: - case IDC_ARGON2_MEM: - case IDC_ARGON2_MEM_STATIC2: - case IDC_ARGON2_TIME_STATIC: - case IDC_ARGON2_TIME: - case IDC_PPK_AUTO_YES: - case IDC_PPK_AUTO_NO: - case IDC_ARGON2_PARALLEL_STATIC: - case IDC_ARGON2_PARALLEL: - topic = WINHELP_CTX_puttygen_kdfparam; break; - } - if (topic) { - launch_help(hwnd, topic); - } else { - MessageBeep(0); - } - break; - } - case WM_CLOSE: - EndDialog(hwnd, 0); - return 0; - } - return 0; -} - -/* - * Prompt for a key file. Assumes the filename buffer is of size - * FILENAME_MAX. - */ -static bool prompt_keyfile(HWND hwnd, char *dlgtitle, - char *filename, bool save, bool ppk) -{ - OPENFILENAME of; - memset(&of, 0, sizeof(of)); - of.hwndOwner = hwnd; - if (ppk) { - of.lpstrFilter = "PuTTY Private Key Files (*.ppk)\0*.ppk\0" - "All Files (*.*)\0*\0\0\0"; - of.lpstrDefExt = ".ppk"; - } else { - of.lpstrFilter = "All Files (*.*)\0*\0\0\0"; - } - of.lpstrCustomFilter = NULL; - of.nFilterIndex = 1; - of.lpstrFile = filename; - *filename = '\0'; - of.nMaxFile = FILENAME_MAX; - of.lpstrFileTitle = NULL; - of.lpstrTitle = dlgtitle; - of.Flags = 0; - return request_file(NULL, &of, false, save); -} - -/* - * Dialog-box function for the Licence box. - */ -static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: { - /* - * Centre the window. - */ - RECT rs, rd; - HWND hw; - - hw = GetDesktopWindow(); - if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd)) - MoveWindow(hwnd, - (rs.right + rs.left + rd.left - rd.right) / 2, - (rs.bottom + rs.top + rd.top - rd.bottom) / 2, - rd.right - rd.left, rd.bottom - rd.top, true); - - SetDlgItemText(hwnd, 1000, LICENCE_TEXT("\r\n\r\n")); - return 1; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - case IDCANCEL: - EndDialog(hwnd, 1); - return 0; - } - return 0; - case WM_CLOSE: - EndDialog(hwnd, 1); - return 0; - } - return 0; -} - -/* - * Dialog-box function for the About box. - */ -static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: - /* - * Centre the window. - */ - { /* centre the window */ - RECT rs, rd; - HWND hw; - - hw = GetDesktopWindow(); - if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd)) - MoveWindow(hwnd, - (rs.right + rs.left + rd.left - rd.right) / 2, - (rs.bottom + rs.top + rd.top - rd.bottom) / 2, - rd.right - rd.left, rd.bottom - rd.top, true); - } - - { - char *buildinfo_text = buildinfo("\r\n"); - char *text = dupprintf( - "PuTTYgen\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s", - ver, buildinfo_text, - "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved."); - sfree(buildinfo_text); - SetDlgItemText(hwnd, 1000, text); - MakeDlgItemBorderless(hwnd, 1000); - sfree(text); - } - return 1; - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - case IDCANCEL: - EndDialog(hwnd, 1); - return 0; - case 101: - EnableWindow(hwnd, 0); - DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc); - EnableWindow(hwnd, 1); - SetActiveWindow(hwnd); - return 0; - case 102: - /* Load web browser */ - ShellExecute(hwnd, "open", - "https://www.chiark.greenend.org.uk/~sgtatham/putty/", - 0, 0, SW_SHOWDEFAULT); - return 0; - } - return 0; - case WM_CLOSE: - EndDialog(hwnd, 1); - return 0; - } - return 0; -} - -typedef enum {RSA, DSA, ECDSA, EDDSA} keytype; - -/* - * Thread to generate a key. - */ -struct rsa_key_thread_params { - HWND progressbar; /* notify this with progress */ - HWND dialog; /* notify this on completion */ - int key_bits; /* bits in key modulus (RSA, DSA) */ - int curve_bits; /* bits in elliptic curve (ECDSA) */ - keytype keytype; - const PrimeGenerationPolicy *primepolicy; - bool rsa_strong; - union { - RSAKey *key; - struct dsa_key *dsakey; - struct ecdsa_key *eckey; - struct eddsa_key *edkey; - }; -}; -static DWORD WINAPI generate_key_thread(void *param) -{ - struct rsa_key_thread_params *params = - (struct rsa_key_thread_params *) param; - struct progress prog; - prog.progbar = params->progressbar; - - win_progress_initialise(&prog); - - PrimeGenerationContext *pgc = primegen_new_context(params->primepolicy); - - if (params->keytype == DSA) - dsa_generate(params->dsakey, params->key_bits, pgc, &prog.rec); - else if (params->keytype == ECDSA) - ecdsa_generate(params->eckey, params->curve_bits); - else if (params->keytype == EDDSA) - eddsa_generate(params->edkey, params->curve_bits); - else - rsa_generate(params->key, params->key_bits, params->rsa_strong, - pgc, &prog.rec); - - primegen_free_context(pgc); - - PostMessage(params->dialog, WM_DONEKEY, 0, 0); - - win_progress_cleanup(&prog); - - sfree(params); - return 0; -} - -struct InitialParams { - int keybutton; - int primepolicybutton; - bool rsa_strong; - FingerprintType fptype; - int keybits; - int eccurve_index, edcurve_index; -}; - -struct MainDlgState { - bool generation_thread_exists; - bool key_exists; - int entropy_got, entropy_required; - strbuf *entropy; - ULONG entropy_prev_msgtime; - int key_bits, curve_bits; - bool ssh2; - keytype keytype; - const PrimeGenerationPolicy *primepolicy; - bool rsa_strong; - FingerprintType fptype; - char **commentptr; /* points to key.comment or ssh2key.comment */ - ssh2_userkey ssh2key; - union { - RSAKey key; - struct dsa_key dsakey; - struct ecdsa_key eckey; - struct eddsa_key edkey; - }; - HMENU filemenu, keymenu, cvtmenu; -}; - -/* - * Rate limit for incrementing the entropy_got counter. - * - * Some pointing devices (e.g. gaming mice) can be set to send - * mouse-movement events at an extremely high sample rate like 1kHz. - * In that situation, there's likely to be a strong correlation - * between the contents of successive movement events, so you have to - * regard the mouse movements as containing less entropy each. - * - * A reasonably simple approach to this is to continue to buffer all - * mouse data, but limit the rate at which we increment the counter - * for how much entropy we think we've collected. That way, the user - * still has to spend time wiggling the mouse back and forth in a way - * that varies with muscle motions and introduces randomness. - */ -#define ENTROPY_RATE_LIMIT 10 /* in units of GetMessageTime(), i.e. ms */ - -static void hidemany(HWND hwnd, const int *ids, bool hideit) -{ - while (*ids) { - ShowWindow(GetDlgItem(hwnd, *ids++), (hideit ? SW_HIDE : SW_SHOW)); - } -} - -enum { - controlidstart = 100, - IDC_QUIT, - IDC_TITLE, - IDC_BOX_KEY, - IDC_NOKEY, - IDC_GENERATING, - IDC_PROGRESS, - IDC_PKSTATIC, IDC_KEYDISPLAY, - IDC_CERTSTATIC, IDC_CERTMOREINFO, - IDC_FPSTATIC, IDC_FINGERPRINT, - IDC_COMMENTSTATIC, IDC_COMMENTEDIT, - IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT, - IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, - IDC_BOX_ACTIONS, - IDC_GENSTATIC, IDC_GENERATE, - IDC_LOADSTATIC, IDC_LOAD, - IDC_SAVESTATIC, IDC_SAVE, IDC_SAVEPUB, - IDC_BOX_PARAMS, - IDC_TYPESTATIC, IDC_KEYSSH1, IDC_KEYSSH2RSA, IDC_KEYSSH2DSA, - IDC_KEYSSH2ECDSA, IDC_KEYSSH2EDDSA, - IDC_PRIMEGEN_PROB, IDC_PRIMEGEN_MAURER_SIMPLE, IDC_PRIMEGEN_MAURER_COMPLEX, - IDC_RSA_STRONG, - IDC_FPTYPE_SHA256, IDC_FPTYPE_MD5, - IDC_PPK_PARAMS, - IDC_BITSSTATIC, IDC_BITS, - IDC_ECCURVESTATIC, IDC_ECCURVE, - IDC_EDCURVESTATIC, IDC_EDCURVE, - IDC_NOTHINGSTATIC, - IDC_ABOUT, - IDC_GIVEHELP, - IDC_IMPORT, - IDC_EXPORT_OPENSSH_AUTO, IDC_EXPORT_OPENSSH_NEW, - IDC_EXPORT_SSHCOM, - IDC_ADDCERT, IDC_REMCERT, -}; - -static void setupbigedit1(HWND hwnd, RSAKey *key) -{ - ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_HIDE); - ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_HIDE); - ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_SHOW); - ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_SHOW); - - SetDlgItemText(hwnd, IDC_PKSTATIC, - "&Public key for pasting into authorized_keys file:"); - - char *buffer = ssh1_pubkey_str(key); - SetDlgItemText(hwnd, IDC_KEYDISPLAY, buffer); - sfree(buffer); -} - -static void setupbigedit2(HWND hwnd, ssh2_userkey *key) -{ - if (ssh_key_alg(key->key)->is_certificate) { - ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_SHOW); - ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_SHOW); - ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_HIDE); - ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_HIDE); - - SetDlgItemText(hwnd, IDC_CERTSTATIC, - "This public key contains an OpenSSH certificate."); - } else { - ShowWindow(GetDlgItem(hwnd, IDC_CERTSTATIC), SW_HIDE); - ShowWindow(GetDlgItem(hwnd, IDC_CERTMOREINFO), SW_HIDE); - ShowWindow(GetDlgItem(hwnd, IDC_PKSTATIC), SW_SHOW); - ShowWindow(GetDlgItem(hwnd, IDC_KEYDISPLAY), SW_SHOW); - - SetDlgItemText(hwnd, IDC_PKSTATIC, "&Public key for pasting into " - "OpenSSH authorized_keys file:"); - - char *buffer = ssh2_pubkey_openssh_str(key); - SetDlgItemText(hwnd, IDC_KEYDISPLAY, buffer); - sfree(buffer); - } -} - -/* - * Warn about the obsolescent key file format. - */ -void old_keyfile_warning(void) -{ - static const char mbtitle[] = "PuTTY Key File Warning"; - 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."; - - MessageBox(NULL, message, mbtitle, MB_OK); -} - -static const int nokey_ids[] = { IDC_NOKEY, 0 }; -static const int generating_ids[] = { IDC_GENERATING, IDC_PROGRESS, 0 }; -static const int gotkey_ids_unconditional[] = { - IDC_FPSTATIC, IDC_FINGERPRINT, - IDC_COMMENTSTATIC, IDC_COMMENTEDIT, - IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT, - IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 0 -}; -static const int gotkey_ids_conditional[] = { - IDC_PKSTATIC, IDC_KEYDISPLAY, - IDC_CERTSTATIC, IDC_CERTMOREINFO, -}; - -/* - * Small UI helper function to switch the state of the main dialog - * by enabling and disabling controls and menu items. - */ -void ui_set_state(HWND hwnd, struct MainDlgState *state, int status) -{ - int type; - - switch (status) { - case 0: /* no key */ - hidemany(hwnd, nokey_ids, false); - hidemany(hwnd, generating_ids, true); - hidemany(hwnd, gotkey_ids_unconditional, true); - hidemany(hwnd, gotkey_ids_conditional, true); - EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1); - EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1); - EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0); - EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ECDSA), 1); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2EDDSA), 1); - EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1); - EnableMenuItem(state->filemenu, IDC_LOAD, MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->filemenu, IDC_SAVE, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->filemenu, IDC_SAVEPUB, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_GENERATE, MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA, MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA, MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2ECDSA, - MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2EDDSA, - MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_AUTO, - MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_NEW, - MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM, - MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_REMCERT, MF_GRAYED|MF_BYCOMMAND); - break; - case 1: /* generating key */ - hidemany(hwnd, nokey_ids, true); - hidemany(hwnd, generating_ids, false); - hidemany(hwnd, gotkey_ids_unconditional, true); - hidemany(hwnd, gotkey_ids_conditional, true); - EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 0); - EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 0); - EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 0); - EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 0); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 0); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 0); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 0); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ECDSA), 0); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2EDDSA), 0); - EnableWindow(GetDlgItem(hwnd, IDC_BITS), 0); - EnableMenuItem(state->filemenu, IDC_LOAD, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->filemenu, IDC_SAVE, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->filemenu, IDC_SAVEPUB, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_GENERATE, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2ECDSA, - MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2EDDSA, - MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_AUTO, - MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_EXPORT_OPENSSH_NEW, - MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_EXPORT_SSHCOM, - MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_GRAYED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_REMCERT, MF_GRAYED|MF_BYCOMMAND); - break; - case 2: - hidemany(hwnd, nokey_ids, true); - hidemany(hwnd, generating_ids, true); - hidemany(hwnd, gotkey_ids_unconditional, false); - // gotkey_ids_conditional will be unhidden by setupbigedit2 - EnableWindow(GetDlgItem(hwnd, IDC_GENERATE), 1); - EnableWindow(GetDlgItem(hwnd, IDC_LOAD), 1); - EnableWindow(GetDlgItem(hwnd, IDC_SAVE), 1); - EnableWindow(GetDlgItem(hwnd, IDC_SAVEPUB), 1); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH1), 1); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2RSA), 1); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2DSA), 1); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2ECDSA), 1); - EnableWindow(GetDlgItem(hwnd, IDC_KEYSSH2EDDSA), 1); - EnableWindow(GetDlgItem(hwnd, IDC_BITS), 1); - EnableMenuItem(state->filemenu, IDC_LOAD, MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->filemenu, IDC_SAVE, MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->filemenu, IDC_SAVEPUB, MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_GENERATE, MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH1, MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2RSA,MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2DSA,MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2ECDSA, - MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->keymenu, IDC_KEYSSH2EDDSA, - MF_ENABLED|MF_BYCOMMAND); - EnableMenuItem(state->cvtmenu, IDC_IMPORT, MF_ENABLED|MF_BYCOMMAND); - /* - * Enable export menu items if and only if the key type - * supports this kind of export. - */ - type = state->ssh2 ? SSH_KEYTYPE_SSH2 : SSH_KEYTYPE_SSH1; -#define do_export_menuitem(x,y) \ - EnableMenuItem(state->cvtmenu, x, MF_BYCOMMAND | \ - (import_target_type(y)==type?MF_ENABLED:MF_GRAYED)) - do_export_menuitem(IDC_EXPORT_OPENSSH_AUTO, SSH_KEYTYPE_OPENSSH_AUTO); - do_export_menuitem(IDC_EXPORT_OPENSSH_NEW, SSH_KEYTYPE_OPENSSH_NEW); - do_export_menuitem(IDC_EXPORT_SSHCOM, SSH_KEYTYPE_SSHCOM); -#undef do_export_menuitem - /* - * Enable certificate menu items similarly. - */ - { - bool add_cert_allowed = false, rem_cert_allowed = false; - - if (state->ssh2 && state->ssh2key.key) { - const ssh_keyalg *alg = ssh_key_alg(state->ssh2key.key); - if (alg->is_certificate) { - /* If there's a certificate, we can remove it */ - rem_cert_allowed = true; - /* And reset to the base algorithm for the next check */ - alg = alg->base_alg; - } - - /* Now, do we have any certified version of this alg? */ - for (size_t i = 0; i < n_keyalgs; i++) { - if (all_keyalgs[i]->base_alg == alg) { - add_cert_allowed = true; - break; - } - } - } - - EnableMenuItem(state->keymenu, IDC_ADDCERT, MF_BYCOMMAND | - (add_cert_allowed ? MF_ENABLED : MF_GRAYED)); - EnableMenuItem(state->keymenu, IDC_REMCERT, MF_BYCOMMAND | - (rem_cert_allowed ? MF_ENABLED : MF_GRAYED)); - } - break; - } -} - -/* - * Helper functions to set the key type, taking care of keeping the - * menu and radio button selections in sync and also showing/hiding - * the appropriate size/curve control for the current key type. - */ -void ui_update_key_type_ctrls(HWND hwnd) -{ - enum { BITS, ECCURVE, EDCURVE, NOTHING } which; - static const int bits_ids[] = { - IDC_BITSSTATIC, IDC_BITS, 0 - }; - static const int eccurve_ids[] = { - IDC_ECCURVESTATIC, IDC_ECCURVE, 0 - }; - static const int edcurve_ids[] = { - IDC_EDCURVESTATIC, IDC_EDCURVE, 0 - }; - static const int nothing_ids[] = { - IDC_NOTHINGSTATIC, 0 - }; - - if (IsDlgButtonChecked(hwnd, IDC_KEYSSH1) || - IsDlgButtonChecked(hwnd, IDC_KEYSSH2RSA) || - IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) { - which = BITS; - } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) { - which = ECCURVE; - } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2EDDSA)) { - which = EDCURVE; - } else { - /* Currently not used since Ed25519 stopped being the only - * thing in its class, but I'll keep it here in case it comes - * in useful again */ - which = NOTHING; - } - - hidemany(hwnd, bits_ids, which != BITS); - hidemany(hwnd, eccurve_ids, which != ECCURVE); - hidemany(hwnd, edcurve_ids, which != EDCURVE); - hidemany(hwnd, nothing_ids, which != NOTHING); -} -void ui_set_key_type(HWND hwnd, struct MainDlgState *state, int button) -{ - CheckRadioButton(hwnd, IDC_KEYSSH1, IDC_KEYSSH2EDDSA, button); - CheckMenuRadioItem(state->keymenu, IDC_KEYSSH1, IDC_KEYSSH2EDDSA, - button, MF_BYCOMMAND); - ui_update_key_type_ctrls(hwnd); -} -void ui_set_primepolicy(HWND hwnd, struct MainDlgState *state, int option) -{ - CheckMenuRadioItem(state->keymenu, IDC_PRIMEGEN_PROB, - IDC_PRIMEGEN_MAURER_COMPLEX, option, MF_BYCOMMAND); - switch (option) { - case IDC_PRIMEGEN_PROB: - state->primepolicy = &primegen_probabilistic; - break; - case IDC_PRIMEGEN_MAURER_SIMPLE: - state->primepolicy = &primegen_provable_maurer_simple; - break; - case IDC_PRIMEGEN_MAURER_COMPLEX: - state->primepolicy = &primegen_provable_maurer_complex; - break; - } -} -void ui_set_rsa_strong(HWND hwnd, struct MainDlgState *state, bool enable) -{ - state->rsa_strong = enable; - CheckMenuItem(state->keymenu, IDC_RSA_STRONG, - (enable ? MF_CHECKED : 0) | MF_BYCOMMAND); -} -static FingerprintType idc_to_fptype(int option) -{ - switch (option) { - case IDC_FPTYPE_SHA256: - return SSH_FPTYPE_SHA256; - case IDC_FPTYPE_MD5: - return SSH_FPTYPE_MD5; - default: - unreachable("bad control id in idc_to_fptype"); - } -} -static int fptype_to_idc(FingerprintType fptype) -{ - switch (fptype) { - case SSH_FPTYPE_SHA256: - return IDC_FPTYPE_SHA256; - case SSH_FPTYPE_MD5: - return IDC_FPTYPE_MD5; - default: - unreachable("bad fptype in fptype_to_idc"); - } -} -void ui_set_fptype(HWND hwnd, struct MainDlgState *state, int option) -{ - CheckMenuRadioItem(state->keymenu, IDC_FPTYPE_SHA256, - IDC_FPTYPE_MD5, option, MF_BYCOMMAND); - - state->fptype = idc_to_fptype(option); - - if (state->key_exists && state->ssh2) { - char *fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); - SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); - sfree(fp); - } -} - -static void update_ui_after_ssh2_pubkey_change( - HWND hwnd, struct MainDlgState *state) -{ - /* Smaller version of update_ui_after_load which doesn't need to - * be told things like the passphrase, which we aren't changing - * anyway */ - char *savecomment = state->ssh2key.comment; - state->ssh2key.comment = NULL; - char *fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); - state->ssh2key.comment = savecomment; - - SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); - sfree(fp); - - setupbigedit2(hwnd, &state->ssh2key); -} - -static void update_ui_after_load(HWND hwnd, struct MainDlgState *state, - const char *passphrase, int type, - RSAKey *newkey1, ssh2_userkey *newkey2) -{ - SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, passphrase); - SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, passphrase); - - if (type == SSH_KEYTYPE_SSH1) { - char *fingerprint, *savecomment; - - state->ssh2 = false; - state->commentptr = &state->key.comment; - state->key = *newkey1; /* structure copy */ - - /* - * Set the key fingerprint. - */ - savecomment = state->key.comment; - state->key.comment = NULL; - fingerprint = rsa_ssh1_fingerprint(&state->key); - state->key.comment = savecomment; - SetDlgItemText(hwnd, IDC_FINGERPRINT, fingerprint); - sfree(fingerprint); - - /* - * Construct a decimal representation of the key, for pasting - * into .ssh/authorized_keys on a Unix box. - */ - setupbigedit1(hwnd, &state->key); - } else { - state->ssh2 = true; - state->commentptr = &state->ssh2key.comment; - state->ssh2key = *newkey2; /* structure copy */ - sfree(newkey2); - - update_ui_after_ssh2_pubkey_change(hwnd, state); - } - SetDlgItemText(hwnd, IDC_COMMENTEDIT, - *state->commentptr); - - /* - * Finally, hide the progress bar and show the key data. - */ - ui_set_state(hwnd, state, 2); - state->key_exists = true; -} - -void load_key_file(HWND hwnd, struct MainDlgState *state, - Filename *filename, bool was_import_cmd) -{ - char *passphrase; - bool needs_pass; - int type, realtype; - int ret; - const char *errmsg = NULL; - char *comment; - RSAKey newkey1; - ssh2_userkey *newkey2 = NULL; - - type = realtype = key_type(filename); - if (type != SSH_KEYTYPE_SSH1 && - type != SSH_KEYTYPE_SSH2 && - !import_possible(type)) { - char *msg = dupprintf("Couldn't load private key (%s)", - key_type_to_str(type)); - message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, - false, HELPCTXID(errors_cantloadkey)); - sfree(msg); - return; - } - - if (type != SSH_KEYTYPE_SSH1 && - type != SSH_KEYTYPE_SSH2) { - realtype = type; - type = import_target_type(type); - } - - comment = NULL; - passphrase = NULL; - if (realtype == SSH_KEYTYPE_SSH1) - needs_pass = rsa1_encrypted_f(filename, &comment); - else if (realtype == SSH_KEYTYPE_SSH2) - needs_pass = ppk_encrypted_f(filename, &comment); - else - needs_pass = import_encrypted(filename, realtype, &comment); - do { - burnstr(passphrase); - passphrase = NULL; - - if (needs_pass) { - int dlgret; - struct PassphraseProcStruct pps; - pps.passphrase = &passphrase; - pps.comment = comment; - dlgret = DialogBoxParam(hinst, - MAKEINTRESOURCE(210), - NULL, PassphraseProc, - (LPARAM) &pps); - if (!dlgret) { - ret = -2; - break; - } - assert(passphrase != NULL); - } else - passphrase = dupstr(""); - if (type == SSH_KEYTYPE_SSH1) { - if (realtype == type) - ret = rsa1_load_f(filename, &newkey1, passphrase, &errmsg); - else - ret = import_ssh1(filename, realtype, &newkey1, - passphrase, &errmsg); - } else { - if (realtype == type) - newkey2 = ppk_load_f(filename, passphrase, &errmsg); - else - newkey2 = import_ssh2(filename, realtype, passphrase, &errmsg); - if (newkey2 == SSH2_WRONG_PASSPHRASE) - ret = -1; - else if (!newkey2) - ret = 0; - else - ret = 1; - } - } while (ret == -1); - if (comment) - sfree(comment); - if (ret == 0) { - char *msg = dupprintf("Couldn't load private key (%s)", errmsg); - message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, - false, HELPCTXID(errors_cantloadkey)); - sfree(msg); - } else if (ret == 1) { - /* - * Now update the key controls with all the - * key data. - */ - update_ui_after_load(hwnd, state, passphrase, type, &newkey1, newkey2); - - /* - * If the user has imported a foreign key - * using the Load command, let them know. - * If they've used the Import command, be - * silent. - */ - if (realtype != type && !was_import_cmd) { - char msg[512]; - sprintf(msg, "Successfully imported foreign key\n" - "(%s).\n" - "To use this key with PuTTY, you need to\n" - "use the \"Save private key\" command to\n" - "save it in PuTTY's own format.", - key_type_to_str(realtype)); - MessageBox(NULL, msg, "PuTTYgen Notice", - MB_OK | MB_ICONINFORMATION); - } - } - burnstr(passphrase); -} - -void add_certificate(HWND hwnd, struct MainDlgState *state, - Filename *filename) -{ - int type = key_type(filename); - if (type != SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 && - type != SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { - char *msg = dupprintf("Couldn't load certificate (%s)", - key_type_to_str(type)); - message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, - false, HELPCTXID(errors_cantloadkey)); - sfree(msg); - return; - } - - char *algname = NULL; - char *comment = NULL; - const char *error = NULL; - strbuf *pub = strbuf_new(); - if (!ppk_loadpub_f(filename, &algname, BinarySink_UPCAST(pub), &comment, - &error)) { - char *msg = dupprintf("Couldn't load certificate (%s)", error); - message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, - false, HELPCTXID(errors_cantloadkey)); - sfree(msg); - strbuf_free(pub); - return; - } - - sfree(comment); - - const ssh_keyalg *alg = find_pubkey_alg(algname); - if (!alg) { - char *msg = dupprintf("Couldn't load certificate (unsupported " - "algorithm name '%s')", algname); - message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, - false, HELPCTXID(errors_cantloadkey)); - sfree(msg); - sfree(algname); - strbuf_free(pub); - return; - } - - sfree(algname); - - /* Check the two public keys match apart from certificates */ - strbuf *old_basepub = strbuf_new(); - ssh_key_public_blob(ssh_key_base_key(state->ssh2key.key), - BinarySink_UPCAST(old_basepub)); - - ssh_key *new_pubkey = ssh_key_new_pub(alg, ptrlen_from_strbuf(pub)); - strbuf *new_basepub = strbuf_new(); - ssh_key_public_blob(ssh_key_base_key(new_pubkey), - BinarySink_UPCAST(new_basepub)); - ssh_key_free(new_pubkey); - - bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(old_basepub), - ptrlen_from_strbuf(new_basepub)); - strbuf_free(old_basepub); - strbuf_free(new_basepub); - - if (!match) { - char *msg = dupprintf("Certificate is for a different public key"); - message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, - false, HELPCTXID(errors_cantloadkey)); - sfree(msg); - strbuf_free(pub); - return; - } - - strbuf *priv = strbuf_new_nm(); - ssh_key_private_blob(state->ssh2key.key, BinarySink_UPCAST(priv)); - ssh_key *newkey = ssh_key_new_priv( - alg, ptrlen_from_strbuf(pub), ptrlen_from_strbuf(priv)); - strbuf_free(pub); - strbuf_free(priv); - - if (!newkey) { - char *msg = dupprintf("Couldn't combine certificate with key"); - message_box(hwnd, msg, "PuTTYgen Error", MB_OK | MB_ICONERROR, - false, HELPCTXID(errors_cantloadkey)); - sfree(msg); - return; - } - - ssh_key_free(state->ssh2key.key); - state->ssh2key.key = newkey; - - update_ui_after_ssh2_pubkey_change(hwnd, state); - ui_set_state(hwnd, state, 2); -} - -void remove_certificate(HWND hwnd, struct MainDlgState *state) -{ - ssh_key *newkey = ssh_key_clone(ssh_key_base_key(state->ssh2key.key)); - ssh_key_free(state->ssh2key.key); - state->ssh2key.key = newkey; - update_ui_after_ssh2_pubkey_change(hwnd, state); - ui_set_state(hwnd, state, 2); -} - -static void start_generating_key(HWND hwnd, struct MainDlgState *state) -{ - static const char generating_msg[] = - "Please wait while a key is generated..."; - - struct rsa_key_thread_params *params; - DWORD threadid; - - SetDlgItemText(hwnd, IDC_GENERATING, generating_msg); - SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0, - MAKELPARAM(0, PROGRESSRANGE)); - SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0); - - params = snew(struct rsa_key_thread_params); - params->progressbar = GetDlgItem(hwnd, IDC_PROGRESS); - params->dialog = hwnd; - params->key_bits = state->key_bits; - params->curve_bits = state->curve_bits; - params->keytype = state->keytype; - params->primepolicy = state->primepolicy; - params->rsa_strong = state->rsa_strong; - params->key = &state->key; - params->dsakey = &state->dsakey; - - HANDLE hThread = CreateThread(NULL, 0, generate_key_thread, - params, 0, &threadid); - if (!hThread) { - MessageBox(hwnd, "Out of thread resources", - "Key generation error", - MB_OK | MB_ICONERROR); - sfree(params); - } else { - CloseHandle(hThread); /* we don't need the thread handle */ - state->generation_thread_exists = true; - } -} - -/* - * Dialog-box function and context structure for the 'Certificate - * info' button. - */ -struct certinfo_dialog_ctx { - SeatDialogText *text; -}; - -static INT_PTR CertInfoProc(HWND hwnd, UINT msg, WPARAM wParam, - LPARAM lParam, void *vctx) -{ - struct certinfo_dialog_ctx *ctx = (struct certinfo_dialog_ctx *)vctx; - - switch (msg) { - case WM_INITDIALOG: { - int index = 100, y = 12; - - WPARAM font = SendMessage(hwnd, WM_GETFONT, 0, 0); - - const char *key = NULL; - for (SeatDialogTextItem *item = ctx->text->items, - *end = item + ctx->text->nitems; item < end; item++) { - switch (item->type) { - case SDT_MORE_INFO_KEY: - key = item->text; - break; - case SDT_MORE_INFO_VALUE_SHORT: - case SDT_MORE_INFO_VALUE_BLOB: { - RECT rk, rv; - DWORD editstyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | - ES_AUTOHSCROLL | ES_READONLY; - if (item->type == SDT_MORE_INFO_VALUE_BLOB) { - rk.left = 12; - rk.right = 286; - rk.top = y; - rk.bottom = 8; - y += 10; - - editstyle |= ES_MULTILINE; - rv.left = 12; - rv.right = 286; - rv.top = y; - rv.bottom = 64; - y += 68; - } else { - rk.left = 12; - rk.right = 130; - rk.top = y+2; - rk.bottom = 8; - - rv.left = 150; - rv.right = 298; - rv.top = y; - rv.bottom = 12; - - y += 16; - } - - MapDialogRect(hwnd, &rk); - HWND ctl = CreateWindowEx( - 0, "STATIC", key, WS_CHILD | WS_VISIBLE, - rk.left, rk.top, rk.right, rk.bottom, - hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); - SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); - - MapDialogRect(hwnd, &rv); - ctl = CreateWindowEx( - WS_EX_CLIENTEDGE, "EDIT", item->text, editstyle, - rv.left, rv.top, rv.right, rv.bottom, - hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL); - SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0)); - break; - } - default: - break; - } - } - - /* - * Now resize the overall window, and move the Close button at - * the bottom. - */ - RECT r; - r.left = 176; - r.top = y + 10; - r.right = r.bottom = 0; - MapDialogRect(hwnd, &r); - HWND ctl = GetDlgItem(hwnd, IDOK); - SetWindowPos(ctl, NULL, r.left, r.top, 0, 0, - SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); - - r.left = r.top = r.right = 0; - r.bottom = 300; - MapDialogRect(hwnd, &r); - int oldheight = r.bottom; - - r.left = r.top = r.right = 0; - r.bottom = y + 30; - MapDialogRect(hwnd, &r); - int newheight = r.bottom; - - GetWindowRect(hwnd, &r); - - SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, - r.bottom - r.top + newheight - oldheight, - SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); - - ShowWindow(hwnd, SW_SHOWNORMAL); - return 1; - } - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - ShinyEndDialog(hwnd, 0); - return 0; - } - return 0; - case WM_CLOSE: - ShinyEndDialog(hwnd, 0); - return 0; - } - return 0; -} - -/* - * Dialog-box function for the main PuTTYgen dialog box. - */ -static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - const int DEMO_SCREENSHOT_TIMER_ID = 1230; - static const char entropy_msg[] = - "Please generate some randomness by moving the mouse over the blank area."; - struct MainDlgState *state; - - switch (msg) { - case WM_INITDIALOG: - if (has_help()) - SetWindowLongPtr(hwnd, GWL_EXSTYLE, - GetWindowLongPtr(hwnd, GWL_EXSTYLE) | - WS_EX_CONTEXTHELP); - else { - /* - * If we add a Help button, this is where we destroy it - * if the help file isn't present. - */ - } - SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG, - (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(200))); - - state = snew(struct MainDlgState); - state->generation_thread_exists = false; - state->entropy = NULL; - state->key_exists = false; - SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) state); - { - HMENU menu, menu1; - - menu = CreateMenu(); - - menu1 = CreateMenu(); - AppendMenu(menu1, MF_ENABLED, IDC_LOAD, "&Load private key"); - AppendMenu(menu1, MF_ENABLED, IDC_SAVEPUB, "Save p&ublic key"); - AppendMenu(menu1, MF_ENABLED, IDC_SAVE, "&Save private key"); - AppendMenu(menu1, MF_SEPARATOR, 0, 0); - AppendMenu(menu1, MF_ENABLED, IDC_QUIT, "E&xit"); - AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) menu1, "&File"); - state->filemenu = menu1; - - menu1 = CreateMenu(); - AppendMenu(menu1, MF_ENABLED, IDC_GENERATE, "&Generate key pair"); - AppendMenu(menu1, MF_SEPARATOR, 0, 0); - AppendMenu(menu1, MF_ENABLED, IDC_ADDCERT, - "Add &certificate to key"); - AppendMenu(menu1, MF_ENABLED, IDC_REMCERT, - "Remove certificate from key"); - AppendMenu(menu1, MF_SEPARATOR, 0, 0); - AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH1, "SSH-&1 key (RSA)"); - AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2RSA, "SSH-2 &RSA key"); - AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2DSA, "SSH-2 &DSA key"); - AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2ECDSA, "SSH-2 &ECDSA key"); - AppendMenu(menu1, MF_ENABLED, IDC_KEYSSH2EDDSA, "SSH-2 EdD&SA key"); - AppendMenu(menu1, MF_SEPARATOR, 0, 0); - AppendMenu(menu1, MF_ENABLED, IDC_PRIMEGEN_PROB, - "Use probable primes (fast)"); - AppendMenu(menu1, MF_ENABLED, IDC_PRIMEGEN_MAURER_SIMPLE, - "Use proven primes (slower)"); - AppendMenu(menu1, MF_ENABLED, IDC_PRIMEGEN_MAURER_COMPLEX, - "Use proven primes with even distribution (slowest)"); - AppendMenu(menu1, MF_SEPARATOR, 0, 0); - AppendMenu(menu1, MF_ENABLED, IDC_RSA_STRONG, - "Use \"strong\" primes as RSA key factors"); - AppendMenu(menu1, MF_SEPARATOR, 0, 0); - AppendMenu(menu1, MF_ENABLED, IDC_PPK_PARAMS, - "Parameters for saving key files..."); - AppendMenu(menu1, MF_SEPARATOR, 0, 0); - AppendMenu(menu1, MF_ENABLED, IDC_FPTYPE_SHA256, - "Show fingerprint as SHA256"); - AppendMenu(menu1, MF_ENABLED, IDC_FPTYPE_MD5, - "Show fingerprint as MD5"); - AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) menu1, "&Key"); - state->keymenu = menu1; - - menu1 = CreateMenu(); - AppendMenu(menu1, MF_ENABLED, IDC_IMPORT, "&Import key"); - AppendMenu(menu1, MF_SEPARATOR, 0, 0); - AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH_AUTO, - "Export &OpenSSH key"); - AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_OPENSSH_NEW, - "Export &OpenSSH key (force new file format)"); - AppendMenu(menu1, MF_ENABLED, IDC_EXPORT_SSHCOM, - "Export &ssh.com key"); - AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) menu1, - "Con&versions"); - state->cvtmenu = menu1; - - menu1 = CreateMenu(); - AppendMenu(menu1, MF_ENABLED, IDC_ABOUT, "&About"); - if (has_help()) - AppendMenu(menu1, MF_ENABLED, IDC_GIVEHELP, "&Help"); - AppendMenu(menu, MF_POPUP | MF_ENABLED, (UINT_PTR) menu1, "&Help"); - - SetMenu(hwnd, menu); - } - - /* - * Centre the window. - */ - { /* centre the window */ - RECT rs, rd; - HWND hw; - - hw = GetDesktopWindow(); - if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd)) - MoveWindow(hwnd, - (rs.right + rs.left + rd.left - rd.right) / 2, - (rs.bottom + rs.top + rd.top - rd.bottom) / 2, - rd.right - rd.left, rd.bottom - rd.top, true); - } - - { - struct ctlpos cp, cp2; - int ymax; - - /* Accelerators used: acglops1rbvde */ - - ctlposinit(&cp, hwnd, 4, 4, 4); - beginbox(&cp, "Key", IDC_BOX_KEY); - cp2 = cp; - statictext(&cp2, "No key.", 1, IDC_NOKEY); - cp2 = cp; - statictext(&cp2, "", 1, IDC_GENERATING); - progressbar(&cp2, IDC_PROGRESS); - cp2 = cp; - bigeditctrl(&cp2, NULL, -1, IDC_CERTSTATIC, 3); - { - HWND child = GetDlgItem(hwnd, IDC_CERTSTATIC); - LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE); - style &= ~WS_VSCROLL; - SetWindowLongPtr(child, GWL_STYLE, style); - SendMessage(child, EM_SETREADONLY, true, 0); - } - MakeDlgItemBorderless(hwnd, IDC_CERTSTATIC); - cp2.xoff = cp2.width = cp2.width / 3; - button(&cp2, "Certificate info...", IDC_CERTMOREINFO, false); - bigeditctrl(&cp, - "&Public key for pasting into authorized_keys file:", - IDC_PKSTATIC, IDC_KEYDISPLAY, 5); - SendDlgItemMessage(hwnd, IDC_KEYDISPLAY, EM_SETREADONLY, 1, 0); - staticedit(&cp, "Key f&ingerprint:", IDC_FPSTATIC, - IDC_FINGERPRINT, 82); - SendDlgItemMessage(hwnd, IDC_FINGERPRINT, EM_SETREADONLY, 1, - 0); - staticedit(&cp, "Key &comment:", IDC_COMMENTSTATIC, - IDC_COMMENTEDIT, 82); - staticpassedit(&cp, "Key p&assphrase:", IDC_PASSPHRASE1STATIC, - IDC_PASSPHRASE1EDIT, 82); - staticpassedit(&cp, "C&onfirm passphrase:", - IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 82); - endbox(&cp); - beginbox(&cp, "Actions", IDC_BOX_ACTIONS); - staticbtn(&cp, "Generate a public/private key pair", - IDC_GENSTATIC, "&Generate", IDC_GENERATE); - staticbtn(&cp, "Load an existing private key file", - IDC_LOADSTATIC, "&Load", IDC_LOAD); - static2btn(&cp, "Save the generated key", IDC_SAVESTATIC, - "Save p&ublic key", IDC_SAVEPUB, - "&Save private key", IDC_SAVE); - endbox(&cp); - beginbox(&cp, "Parameters", IDC_BOX_PARAMS); - radioline(&cp, "Type of key to generate:", IDC_TYPESTATIC, 5, - "&RSA", IDC_KEYSSH2RSA, - "&DSA", IDC_KEYSSH2DSA, - "&ECDSA", IDC_KEYSSH2ECDSA, - "EdD&SA", IDC_KEYSSH2EDDSA, - "SSH-&1 (RSA)", IDC_KEYSSH1, - NULL); - cp2 = cp; - staticedit(&cp2, "Number of &bits in a generated key:", - IDC_BITSSTATIC, IDC_BITS, 20); - ymax = cp2.ypos; - cp2 = cp; - staticddl(&cp2, "Cur&ve to use for generating this key:", - IDC_ECCURVESTATIC, IDC_ECCURVE, 30); - SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_RESETCONTENT, 0, 0); - { - int i, bits; - const struct ec_curve *curve; - const ssh_keyalg *alg; - - for (i = 0; i < n_ec_nist_curve_lengths; i++) { - bits = ec_nist_curve_lengths[i]; - ec_nist_alg_and_curve_by_bits(bits, &curve, &alg); - SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_ADDSTRING, 0, - (LPARAM)curve->textname); - } - } - ymax = ymax > cp2.ypos ? ymax : cp2.ypos; - cp2 = cp; - staticddl(&cp2, "Cur&ve to use for generating this key:", - IDC_EDCURVESTATIC, IDC_EDCURVE, 30); - SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_RESETCONTENT, 0, 0); - { - int i, bits; - const struct ec_curve *curve; - const ssh_keyalg *alg; - - for (i = 0; i < n_ec_ed_curve_lengths; i++) { - bits = ec_ed_curve_lengths[i]; - ec_ed_alg_and_curve_by_bits(bits, &curve, &alg); - char *desc = dupprintf("%s (%d bits)", - curve->textname, bits); - SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_ADDSTRING, 0, - (LPARAM)desc); - sfree(desc); - } - } - ymax = ymax > cp2.ypos ? ymax : cp2.ypos; - cp2 = cp; - statictext(&cp2, "(nothing to configure for this key type)", - 1, IDC_NOTHINGSTATIC); - ymax = ymax > cp2.ypos ? ymax : cp2.ypos; - cp.ypos = ymax; - endbox(&cp); - } - struct InitialParams *params = (struct InitialParams *)lParam; - ui_set_key_type(hwnd, state, params->keybutton); - ui_set_primepolicy(hwnd, state, params->primepolicybutton); - ui_set_rsa_strong(hwnd, state, params->rsa_strong); - ui_set_fptype(hwnd, state, fptype_to_idc(params->fptype)); - SetDlgItemInt(hwnd, IDC_BITS, params->keybits, false); - SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_SETCURSEL, - params->eccurve_index, 0); - SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_SETCURSEL, - params->edcurve_index, 0); - - /* - * Initially, hide the progress bar and the key display, - * and show the no-key display. Also disable the Save - * buttons, because with no key we obviously can't save - * anything. - */ - ui_set_state(hwnd, state, 0); - - /* - * Load a key file if one was provided on the command line. - */ - if (cmdline_keyfile) { - Filename *fn = filename_from_str(cmdline_keyfile); - load_key_file(hwnd, state, fn, false); - filename_free(fn); - } else if (cmdline_demo_keystr.ptr) { - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, cmdline_demo_keystr); - const char *errmsg; - ssh2_userkey *k = ppk_load_s(src, NULL, &errmsg); - assert(!errmsg); - - update_ui_after_load(hwnd, state, "demo passphrase", - SSH_KEYTYPE_SSH2, NULL, k); - - SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL); - } - - return 1; - case WM_TIMER: - if ((UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) { - KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID); - char *err = save_screenshot(hwnd, demo_screenshot_filename); - if (err) { - MessageBox(hwnd, err, "Demo screenshot failure", - MB_OK | MB_ICONERROR); - sfree(err); - } - EndDialog(hwnd, 0); - } - return 0; - case WM_MOUSEMOVE: - state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (state->entropy && state->entropy_got < state->entropy_required) { - ULONG msgtime = GetMessageTime(); - put_uint32(state->entropy, lParam); - put_uint32(state->entropy, msgtime); - if (msgtime - state->entropy_prev_msgtime > ENTROPY_RATE_LIMIT) { - state->entropy_got += 2; - state->entropy_prev_msgtime = msgtime; - } - SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, - state->entropy_got, 0); - if (state->entropy_got >= state->entropy_required) { - /* - * Seed the entropy pool - */ - random_reseed(ptrlen_from_strbuf(state->entropy)); - strbuf_free(state->entropy); - state->entropy = NULL; - - start_generating_key(hwnd, state); - } - } - break; - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDC_KEYSSH1: - case IDC_KEYSSH2RSA: - case IDC_KEYSSH2DSA: - case IDC_KEYSSH2ECDSA: - case IDC_KEYSSH2EDDSA: { - state = (struct MainDlgState *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - ui_set_key_type(hwnd, state, LOWORD(wParam)); - break; - } - case IDC_PRIMEGEN_PROB: - case IDC_PRIMEGEN_MAURER_SIMPLE: - case IDC_PRIMEGEN_MAURER_COMPLEX: { - state = (struct MainDlgState *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - ui_set_primepolicy(hwnd, state, LOWORD(wParam)); - break; - } - case IDC_FPTYPE_SHA256: - case IDC_FPTYPE_MD5: { - state = (struct MainDlgState *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - ui_set_fptype(hwnd, state, LOWORD(wParam)); - break; - } - case IDC_RSA_STRONG: { - state = (struct MainDlgState *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - ui_set_rsa_strong(hwnd, state, !state->rsa_strong); - break; - } - case IDC_PPK_PARAMS: { - struct PPKParams pp[1]; - pp->params = save_params; - if (pp->params.argon2_passes_auto) { - pp->time_ms = pp->params.argon2_milliseconds; - pp->time_passes = 13; - } else { - pp->time_ms = 100; - pp->time_passes = pp->params.argon2_passes; - } - int dlgret = DialogBoxParam(hinst, MAKEINTRESOURCE(215), - NULL, PPKParamsProc, (LPARAM)pp); - if (dlgret) { - if (pp->params.argon2_passes_auto) { - pp->params.argon2_milliseconds = pp->time_ms; - } else { - pp->params.argon2_passes = pp->time_passes; - } - save_params = pp->params; - } - break; - } - case IDC_QUIT: - PostMessage(hwnd, WM_CLOSE, 0, 0); - break; - case IDC_COMMENTEDIT: - if (HIWORD(wParam) == EN_CHANGE) { - state = (struct MainDlgState *) - GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (state->key_exists) { - HWND editctl = GetDlgItem(hwnd, IDC_COMMENTEDIT); - int len = GetWindowTextLength(editctl); - if (*state->commentptr) - sfree(*state->commentptr); - *state->commentptr = snewn(len + 1, char); - GetWindowText(editctl, *state->commentptr, len + 1); - if (state->ssh2) { - setupbigedit2(hwnd, &state->ssh2key); - } else { - setupbigedit1(hwnd, &state->key); - } - } - } - break; - case IDC_ABOUT: - EnableWindow(hwnd, 0); - DialogBox(hinst, MAKEINTRESOURCE(213), hwnd, AboutProc); - EnableWindow(hwnd, 1); - SetActiveWindow(hwnd); - return 0; - case IDC_GIVEHELP: - if (HIWORD(wParam) == BN_CLICKED || - HIWORD(wParam) == BN_DOUBLECLICKED) { - launch_help(hwnd, WINHELP_CTX_puttygen_general); - } - return 0; - case IDC_GENERATE: - if (HIWORD(wParam) != BN_CLICKED && - HIWORD(wParam) != BN_DOUBLECLICKED) - break; - state = - (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (!state->generation_thread_exists) { - unsigned raw_entropy_required; - unsigned char *raw_entropy_buf; - BOOL ok; - - state->key_bits = GetDlgItemInt(hwnd, IDC_BITS, &ok, false); - if (!ok) - state->key_bits = DEFAULT_KEY_BITS; - state->ssh2 = true; - - if (IsDlgButtonChecked(hwnd, IDC_KEYSSH1)) { - state->ssh2 = false; - state->keytype = RSA; - } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2RSA)) { - state->keytype = RSA; - } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2DSA)) { - state->keytype = DSA; - } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2ECDSA)) { - state->keytype = ECDSA; - int curveindex = SendDlgItemMessage(hwnd, IDC_ECCURVE, - CB_GETCURSEL, 0, 0); - assert(curveindex >= 0); - assert(curveindex < n_ec_nist_curve_lengths); - state->curve_bits = ec_nist_curve_lengths[curveindex]; - } else if (IsDlgButtonChecked(hwnd, IDC_KEYSSH2EDDSA)) { - state->keytype = EDDSA; - int curveindex = SendDlgItemMessage(hwnd, IDC_EDCURVE, - CB_GETCURSEL, 0, 0); - assert(curveindex >= 0); - assert(curveindex < n_ec_ed_curve_lengths); - state->curve_bits = ec_ed_curve_lengths[curveindex]; - } else { - /* Somehow, no button was checked */ - break; - } - - if ((state->keytype == RSA || state->keytype == DSA) && - state->key_bits < 256) { - char *message = dupprintf( - "PuTTYgen will not generate a key smaller than 256" - " bits.\nKey length reset to default %d. Continue?", - DEFAULT_KEY_BITS); - int ret = MessageBox(hwnd, message, "PuTTYgen Warning", - MB_ICONWARNING | MB_OKCANCEL); - sfree(message); - if (ret != IDOK) - break; - state->key_bits = DEFAULT_KEY_BITS; - SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false); - } else if ((state->keytype == RSA || state->keytype == DSA) && - state->key_bits < DEFAULT_KEY_BITS) { - char *message = dupprintf( - "Keys shorter than %d bits are not recommended. " - "Really generate this key?", DEFAULT_KEY_BITS); - int ret = MessageBox(hwnd, message, "PuTTYgen Warning", - MB_ICONWARNING | MB_OKCANCEL); - sfree(message); - if (ret != IDOK) - break; - } - - if (state->keytype == RSA || state->keytype == DSA) - raw_entropy_required = (state->key_bits / 2) * 2; - else if (state->keytype == ECDSA || state->keytype == EDDSA) - raw_entropy_required = (state->curve_bits / 2) * 2; - else - unreachable("we must have initialised keytype by now"); - - /* Bound the entropy collection above by the amount of - * data we can actually fit into the PRNG. Any more - * than that and it's doing no more good. */ - if (raw_entropy_required > random_seed_bits()) - raw_entropy_required = random_seed_bits(); - - raw_entropy_buf = snewn(raw_entropy_required, unsigned char); - if (win_read_random(raw_entropy_buf, raw_entropy_required)) { - /* - * If we can get entropy from CryptGenRandom, use - * it. But CryptGenRandom isn't a kernel-level - * CPRNG (according to Wikipedia), and papers have - * been published cryptanalysing it. So we'll - * still do manual entropy collection; we'll just - * do it _as well_ as this. - */ - random_reseed( - make_ptrlen(raw_entropy_buf, raw_entropy_required)); - } - - /* - * Manual entropy input, by making the user wave the - * mouse over the window a lot. - * - * My brief statistical tests on mouse movements - * suggest that there are about 2.5 bits of randomness - * in the x position, 2.5 in the y position, and 1.7 - * in the message time, making 5.7 bits of - * unpredictability per mouse movement. However, other - * people have told me it's far less than that, so I'm - * going to be stupidly cautious and knock that down - * to a nice round 2. With this method, we require two - * words per mouse movement, so with 2 bits per mouse - * movement we expect 2 bits every 2 words, i.e. the - * number of _words_ of mouse data we want to collect - * is just the same as the number of _bits_ of entropy - * we want. - */ - state->entropy_required = raw_entropy_required; - - ui_set_state(hwnd, state, 1); - SetDlgItemText(hwnd, IDC_GENERATING, entropy_msg); - state->key_exists = false; - - state->entropy_got = 0; - state->entropy = strbuf_new_nm(); - state->entropy_prev_msgtime = GetMessageTime(); - - SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0, - MAKELPARAM(0, state->entropy_required)); - SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, 0, 0); - - smemclr(raw_entropy_buf, raw_entropy_required); - sfree(raw_entropy_buf); - } - break; - case IDC_SAVE: - case IDC_EXPORT_OPENSSH_AUTO: - case IDC_EXPORT_OPENSSH_NEW: - case IDC_EXPORT_SSHCOM: - if (HIWORD(wParam) != BN_CLICKED) - break; - state = - (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (state->key_exists) { - char filename[FILENAME_MAX]; - char *passphrase, *passphrase2; - int type, realtype; - - if (state->ssh2) - realtype = SSH_KEYTYPE_SSH2; - else - realtype = SSH_KEYTYPE_SSH1; - - if (LOWORD(wParam) == IDC_EXPORT_OPENSSH_AUTO) - type = SSH_KEYTYPE_OPENSSH_AUTO; - else if (LOWORD(wParam) == IDC_EXPORT_OPENSSH_NEW) - type = SSH_KEYTYPE_OPENSSH_NEW; - else if (LOWORD(wParam) == IDC_EXPORT_SSHCOM) - type = SSH_KEYTYPE_SSHCOM; - else - type = realtype; - - if (type != realtype && - import_target_type(type) != realtype) { - char msg[256]; - sprintf(msg, "Cannot export an SSH-%d key in an SSH-%d" - " format", (state->ssh2 ? 2 : 1), - (state->ssh2 ? 1 : 2)); - MessageBox(hwnd, msg, - "PuTTYgen Error", MB_OK | MB_ICONERROR); - break; - } - - passphrase = GetDlgItemText_alloc(hwnd, IDC_PASSPHRASE1EDIT); - passphrase2 = GetDlgItemText_alloc(hwnd, IDC_PASSPHRASE2EDIT); - if (strcmp(passphrase, passphrase2)) { - MessageBox(hwnd, - "The two passphrases given do not match.", - "PuTTYgen Error", MB_OK | MB_ICONERROR); - burnstr(passphrase); - burnstr(passphrase2); - break; - } - burnstr(passphrase2); - if (!*passphrase) { - int ret; - ret = MessageBox(hwnd, - "Are you sure you want to save this key\n" - "without a passphrase to protect it?", - "PuTTYgen Warning", - MB_YESNO | MB_ICONWARNING); - if (ret != IDYES) { - burnstr(passphrase); - break; - } - } - if (prompt_keyfile(hwnd, "Save private key as:", - filename, true, (type == realtype))) { - int ret; - FILE *fp = fopen(filename, "r"); - if (fp) { - char *buffer; - fclose(fp); - buffer = dupprintf("Overwrite existing file\n%s?", - filename); - ret = MessageBox(hwnd, buffer, "PuTTYgen Warning", - MB_YESNO | MB_ICONWARNING); - sfree(buffer); - if (ret != IDYES) { - burnstr(passphrase); - break; - } - } - - if (state->ssh2) { - Filename *fn = filename_from_str(filename); - if (type != realtype) - ret = export_ssh2(fn, type, &state->ssh2key, - *passphrase ? passphrase : NULL); - else - ret = ppk_save_f(fn, &state->ssh2key, - *passphrase ? passphrase : NULL, - &save_params); - filename_free(fn); - } else { - Filename *fn = filename_from_str(filename); - if (type != realtype) - ret = export_ssh1(fn, type, &state->key, - *passphrase ? passphrase : NULL); - else - ret = rsa1_save_f(fn, &state->key, - *passphrase ? passphrase : NULL); - filename_free(fn); - } - if (ret <= 0) { - MessageBox(hwnd, "Unable to save key file", - "PuTTYgen Error", MB_OK | MB_ICONERROR); - } - } - burnstr(passphrase); - } - break; - case IDC_SAVEPUB: - if (HIWORD(wParam) != BN_CLICKED) - break; - state = - (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (state->key_exists) { - char filename[FILENAME_MAX]; - if (prompt_keyfile(hwnd, "Save public key as:", - filename, true, false)) { - int ret; - FILE *fp = fopen(filename, "r"); - if (fp) { - char *buffer; - fclose(fp); - buffer = dupprintf("Overwrite existing file\n%s?", - filename); - ret = MessageBox(hwnd, buffer, "PuTTYgen Warning", - MB_YESNO | MB_ICONWARNING); - sfree(buffer); - if (ret != IDYES) - break; - } - fp = fopen(filename, "w"); - if (!fp) { - MessageBox(hwnd, "Unable to open key file", - "PuTTYgen Error", MB_OK | MB_ICONERROR); - } else { - if (state->ssh2) { - strbuf *blob = strbuf_new(); - ssh_key_public_blob( - state->ssh2key.key, BinarySink_UPCAST(blob)); - ssh2_write_pubkey(fp, state->ssh2key.comment, - blob->u, blob->len, - SSH_KEYTYPE_SSH2_PUBLIC_RFC4716); - strbuf_free(blob); - } else { - ssh1_write_pubkey(fp, &state->key); - } - if (fclose(fp) < 0) { - MessageBox(hwnd, "Unable to save key file", - "PuTTYgen Error", MB_OK | MB_ICONERROR); - } - } - } - } - break; - case IDC_LOAD: - case IDC_IMPORT: - if (HIWORD(wParam) != BN_CLICKED) - break; - state = - (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (!state->generation_thread_exists) { - char filename[FILENAME_MAX]; - if (prompt_keyfile(hwnd, "Load private key:", filename, false, - LOWORD(wParam) == IDC_LOAD)) { - Filename *fn = filename_from_str(filename); - load_key_file(hwnd, state, fn, LOWORD(wParam) != IDC_LOAD); - filename_free(fn); - } - } - break; - case IDC_ADDCERT: - if (HIWORD(wParam) != BN_CLICKED) - break; - state = - (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (state->key_exists && !state->generation_thread_exists) { - char filename[FILENAME_MAX]; - if (prompt_keyfile(hwnd, "Load certificate:", filename, false, - false)) { - Filename *fn = filename_from_str(filename); - add_certificate(hwnd, state, fn); - filename_free(fn); - } - } - break; - case IDC_REMCERT: - if (HIWORD(wParam) != BN_CLICKED) - break; - state = - (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (state->key_exists && !state->generation_thread_exists) { - remove_certificate(hwnd, state); - } - break; - case IDC_CERTMOREINFO: { - if (HIWORD(wParam) != BN_CLICKED) - break; - state = - (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (!state->key_exists || !state->ssh2 || !state->ssh2key.key) - break; - if (!ssh_key_alg(state->ssh2key.key)->is_certificate) - break; - - struct certinfo_dialog_ctx ctx[1]; - ctx->text = ssh_key_cert_info(state->ssh2key.key); - ShinyDialogBox(hinst, MAKEINTRESOURCE(216), - "PuTTYgenCertInfo", hwnd, CertInfoProc, ctx); - seat_dialog_text_free(ctx->text); - break; - } - } - return 0; - case WM_DONEKEY: - state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - state->generation_thread_exists = false; - state->key_exists = true; - SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETRANGE, 0, - MAKELPARAM(0, PROGRESSRANGE)); - SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, PROGRESSRANGE, 0); - if (state->ssh2) { - if (state->keytype == DSA) { - state->ssh2key.key = &state->dsakey.sshk; - } else if (state->keytype == ECDSA) { - state->ssh2key.key = &state->eckey.sshk; - } else if (state->keytype == EDDSA) { - state->ssh2key.key = &state->edkey.sshk; - } else { - state->ssh2key.key = &state->key.sshk; - } - state->commentptr = &state->ssh2key.comment; - } else { - state->commentptr = &state->key.comment; - } - /* - * Invent a comment for the key. We'll do this by including - * the date in it. This will be so horrifyingly ugly that - * the user will immediately want to change it, which is - * what we want :-) - */ - *state->commentptr = snewn(30, char); - { - struct tm tm; - tm = ltime(); - if (state->keytype == DSA) - strftime(*state->commentptr, 30, "dsa-key-%Y%m%d", &tm); - else if (state->keytype == ECDSA) - strftime(*state->commentptr, 30, "ecdsa-key-%Y%m%d", &tm); - else if (state->keytype == EDDSA) - strftime(*state->commentptr, 30, "eddsa-key-%Y%m%d", &tm); - else - strftime(*state->commentptr, 30, "rsa-key-%Y%m%d", &tm); - } - - /* - * Now update the key controls with all the key data. - */ - { - char *fp, *savecomment; - /* - * Blank passphrase, initially. This isn't dangerous, - * because we will warn (Are You Sure?) before allowing - * the user to save an unprotected private key. - */ - SetDlgItemText(hwnd, IDC_PASSPHRASE1EDIT, ""); - SetDlgItemText(hwnd, IDC_PASSPHRASE2EDIT, ""); - /* - * Set the comment. - */ - SetDlgItemText(hwnd, IDC_COMMENTEDIT, *state->commentptr); - /* - * Set the key fingerprint. - */ - savecomment = *state->commentptr; - *state->commentptr = NULL; - if (state->ssh2) - fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); - else - fp = rsa_ssh1_fingerprint(&state->key); - SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); - sfree(fp); - *state->commentptr = savecomment; - /* - * Construct a decimal representation of the key, for - * pasting into .ssh/authorized_keys or - * .ssh/authorized_keys2 on a Unix box. - */ - if (state->ssh2) { - setupbigedit2(hwnd, &state->ssh2key); - } else { - setupbigedit1(hwnd, &state->key); - } - } - /* - * Finally, hide the progress bar and show the key data. - */ - ui_set_state(hwnd, state, 2); - break; - case WM_HELP: { - int id = ((LPHELPINFO)lParam)->iCtrlId; - const char *topic = NULL; - switch (id) { - case IDC_GENERATING: - case IDC_PROGRESS: - case IDC_GENSTATIC: - case IDC_GENERATE: - topic = WINHELP_CTX_puttygen_generate; break; - case IDC_PKSTATIC: - case IDC_KEYDISPLAY: - topic = WINHELP_CTX_puttygen_pastekey; break; - case IDC_FPSTATIC: - case IDC_FINGERPRINT: - topic = WINHELP_CTX_puttygen_fingerprint; break; - case IDC_COMMENTSTATIC: - case IDC_COMMENTEDIT: - topic = WINHELP_CTX_puttygen_comment; break; - case IDC_PASSPHRASE1STATIC: - case IDC_PASSPHRASE1EDIT: - case IDC_PASSPHRASE2STATIC: - case IDC_PASSPHRASE2EDIT: - topic = WINHELP_CTX_puttygen_passphrase; break; - case IDC_LOADSTATIC: - case IDC_LOAD: - topic = WINHELP_CTX_puttygen_load; break; - case IDC_SAVESTATIC: - case IDC_SAVE: - topic = WINHELP_CTX_puttygen_savepriv; break; - case IDC_SAVEPUB: - topic = WINHELP_CTX_puttygen_savepub; break; - case IDC_TYPESTATIC: - case IDC_KEYSSH1: - case IDC_KEYSSH2RSA: - case IDC_KEYSSH2DSA: - case IDC_KEYSSH2ECDSA: - case IDC_KEYSSH2EDDSA: - topic = WINHELP_CTX_puttygen_keytype; break; - case IDC_BITSSTATIC: - case IDC_BITS: - topic = WINHELP_CTX_puttygen_bits; break; - case IDC_IMPORT: - case IDC_EXPORT_OPENSSH_AUTO: - case IDC_EXPORT_OPENSSH_NEW: - case IDC_EXPORT_SSHCOM: - topic = WINHELP_CTX_puttygen_conversions; break; - } - if (topic) { - launch_help(hwnd, topic); - } else { - MessageBeep(0); - } - break; - } - case WM_CLOSE: - state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); - sfree(state); - quit_help(hwnd); - EndDialog(hwnd, 1); - return 0; - } - return 0; -} - -void cleanup_exit(int code) -{ - shutdown_help(); - exit(code); -} - -HINSTANCE hinst; - -static NORETURN void opt_error(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - char *msg = dupvprintf(fmt, ap); - va_end(ap); - - MessageBox(NULL, msg, "PuTTYgen command line error", MB_ICONERROR | MB_OK); - - exit(1); -} - -int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) -{ - int argc; - char **argv; - int ret; - struct InitialParams params[1]; - - dll_hijacking_protection(); - - init_common_controls(); - hinst = inst; - - /* - * See if we can find our Help file. - */ - init_help(); - - params->keybutton = IDC_KEYSSH2RSA; - params->primepolicybutton = IDC_PRIMEGEN_PROB; - params->rsa_strong = false; - params->fptype = SSH_FPTYPE_DEFAULT; - params->keybits = DEFAULT_KEY_BITS; - params->eccurve_index = DEFAULT_ECCURVE_INDEX; - params->edcurve_index = DEFAULT_EDCURVE_INDEX; - - save_params = ppk_save_default_parameters; - - split_into_argv(cmdline, false, &argc, &argv, NULL); - - int argbits = -1; - AuxMatchOpt amo = aux_match_opt_init(argc, argv, 0, opt_error); - while (!aux_match_done(&amo)) { - char *val; - #define match_opt(...) aux_match_opt( \ - &amo, NULL, __VA_ARGS__, (const char *)NULL) - #define match_optval(...) aux_match_opt( \ - &amo, &val, __VA_ARGS__, (const char *)NULL) - - if (aux_match_arg(&amo, &val)) { - if (!cmdline_keyfile) { - /* - * Assume the first argument to be a private key file, and - * attempt to load it. - */ - cmdline_keyfile = val; - continue; - } else { - opt_error("unexpected extra argument '%s'\n", val); - } - } else if (match_opt("-pgpfp")) { - pgp_fingerprints_msgbox(NULL); - return 1; - } else if (match_opt("-restrict-acl", "-restrict_acl", - "-restrictacl")) { - restrict_process_acl(); - } else if (match_optval("-t")) { - if (!strcmp(val, "rsa") || !strcmp(val, "rsa2")) { - params->keybutton = IDC_KEYSSH2RSA; - } else if (!strcmp(val, "rsa1")) { - params->keybutton = IDC_KEYSSH1; - } else if (!strcmp(val, "dsa") || !strcmp(val, "dss")) { - params->keybutton = IDC_KEYSSH2DSA; - } else if (!strcmp(val, "ecdsa")) { - params->keybutton = IDC_KEYSSH2ECDSA; - } else if (!strcmp(val, "eddsa")) { - params->keybutton = IDC_KEYSSH2EDDSA; - } else if (!strcmp(val, "ed25519")) { - params->keybutton = IDC_KEYSSH2EDDSA; - argbits = 255; - } else if (!strcmp(val, "ed448")) { - params->keybutton = IDC_KEYSSH2EDDSA; - argbits = 448; - } else { - opt_error("unknown key type '%s'\n", val); - } - } else if (match_optval("-b")) { - argbits = atoi(val); - } else if (match_optval("-E")) { - if (!strcmp(val, "md5")) - params->fptype = SSH_FPTYPE_MD5; - else if (!strcmp(val, "sha256")) - params->fptype = SSH_FPTYPE_SHA256; - else - opt_error("unknown fingerprint type '%s'\n", val); - } else if (match_optval("-primes")) { - if (!strcmp(val, "probable") || - !strcmp(val, "probabilistic")) { - params->primepolicybutton = IDC_PRIMEGEN_PROB; - } else if (!strcmp(val, "provable") || - !strcmp(val, "proven") || - !strcmp(val, "simple") || - !strcmp(val, "maurer-simple")) { - params->primepolicybutton = IDC_PRIMEGEN_MAURER_SIMPLE; - } else if (!strcmp(val, "provable-even") || - !strcmp(val, "proven-even") || - !strcmp(val, "even") || - !strcmp(val, "complex") || - !strcmp(val, "maurer-complex")) { - params->primepolicybutton = IDC_PRIMEGEN_MAURER_COMPLEX; - } else { - opt_error("unrecognised prime-generation mode '%s'\n", val); - } - } else if (match_opt("-strong-rsa")) { - params->rsa_strong = true; - } else if (match_optval("-ppk-param", "-ppk-params")) { - char *nextval; - for (; val; val = nextval) { - nextval = strchr(val, ','); - if (nextval) - *nextval++ = '\0'; - - char *optvalue = strchr(val, '='); - if (!optvalue) - opt_error("PPK parameter '%s' expected a value\n", val); - *optvalue++ = '\0'; - - /* Non-numeric options */ - if (!strcmp(val, "kdf")) { - if (!strcmp(optvalue, "Argon2id") || - !strcmp(optvalue, "argon2id")) { - save_params.argon2_flavour = Argon2id; - } else if (!strcmp(optvalue, "Argon2i") || - !strcmp(optvalue, "argon2i")) { - save_params.argon2_flavour = Argon2i; - } else if (!strcmp(optvalue, "Argon2d") || - !strcmp(optvalue, "argon2d")) { - save_params.argon2_flavour = Argon2d; - } else { - opt_error("unrecognised kdf '%s'\n", optvalue); - } - continue; - } - - char *end; - unsigned long n = strtoul(optvalue, &end, 0); - if (!*optvalue || *end) - opt_error("value '%s' for PPK parameter '%s': expected a " - "number\n", optvalue, val); - - if (!strcmp(val, "version")) { - save_params.fmt_version = n; - } else if (!strcmp(val, "memory") || - !strcmp(val, "mem")) { - save_params.argon2_mem = n; - } else if (!strcmp(val, "time")) { - save_params.argon2_passes_auto = true; - save_params.argon2_milliseconds = n; - } else if (!strcmp(val, "passes")) { - save_params.argon2_passes_auto = false; - save_params.argon2_passes = n; - } else if (!strcmp(val, "parallelism") || - !strcmp(val, "parallel")) { - save_params.argon2_parallelism = n; - } else { - opt_error("unrecognised PPK parameter '%s'\n", val); - } - } - } else if (match_optval("-demo-screenshot")) { - demo_screenshot_filename = val; - cmdline_demo_keystr = PTRLEN_LITERAL( - "PuTTY-User-Key-File-3: ssh-ed25519\n" - "Encryption: none\n" - "Comment: ed25519-key-20220402\n" - "Public-Lines: 2\n" - "AAAAC3NzaC1lZDI1NTE5AAAAILzuIFwZ" - "8ZhgOlilcSb+9zPuCf/DmKJiloVlmWGy\n" - "xa/F\n" - "Private-Lines: 1\n" - "AAAAIPca6vLwtB2NJhZUpABQISR0gcQH8jjQLta19VyzA3wc\n" - "Private-MAC: 1159e9628259b35933b397379bbe8a14" - "a1f1d97fe91e446e45a9581a3408b70e\n"); - params->keybutton = IDC_KEYSSH2EDDSA; - argbits = 255; - } else { - opt_error("unrecognised option '%s'\n", amo.argv[amo.index]); - } - } - - /* Translate argbits into eccurve_index and edcurve_index */ - if (argbits > 0) { - switch (params->keybutton) { - case IDC_KEYSSH2RSA: - case IDC_KEYSSH1: - case IDC_KEYSSH2DSA: - params->keybits = argbits; - break; - case IDC_KEYSSH2ECDSA: { - bool found = false; - for (int j = 0; j < n_ec_nist_curve_lengths; j++) - if (argbits == ec_nist_curve_lengths[j]) { - params->eccurve_index = j; - found = true; - break; - } - if (!found) - opt_error("unsupported ECDSA bit length %d", argbits); - break; - } - case IDC_KEYSSH2EDDSA: { - bool found = false; - for (int j = 0; j < n_ec_ed_curve_lengths; j++) - if (argbits == ec_ed_curve_lengths[j]) { - params->edcurve_index = j; - found = true; - break; - } - if (!found) - opt_error("unsupported EDDSA bit length %d", argbits); - break; - } - } - } - - random_setup_special(); - ret = DialogBoxParam(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc, - (LPARAM)params) != IDOK; - - cleanup_exit(ret); - return ret; /* just in case optimiser complains */ -} diff --git a/WINDOWS/puttygen.ico b/WINDOWS/puttygen.ico deleted file mode 100644 index ddc83057b718848a0546b717849d478fa94a84eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4078 zcmeHJ&x<2P6n;H5q(>9&z#d%K!9+nFyx7o-dJr=!g2EoW_y_E1@i-?l;30*G1QvGv z5BwK$6arc+dmKD?@us~9B8U*(!3h{se&4I=q%&y;S61-gOJ05TUcGwt>(%QLNu(v+ zZWp*K2Z_i{5$X4X{Ns+uZRqkm$nUj8-rp72@4!Bh32eW8oc2M^VSBqx`zRZc&pRRq zug5a{?>E~z_i=bsS#t0Or^+8Q4})C6^pjyG?m;0G=+*R>{B1Dhu~_T1ZU7p7S7RvJyAf3>O<`ldMk0il%kvsL00yTZBVET z)v<1(?kl$2`?Dy^QLr0TvK*_sbRHe@biA+JL3w%t+LYy--0p0)yUCud+-d*zWK(uL zx=5#Qg4%kfs?qU}rYl8?)$oujd*T$dGT2n)F(#lzB%sj0z#O50#==kq7igwIVRDt* z6Uuqi(!d~^g3J~~73^wq(ME{CVMPYkG0s%QNaTPkQYR}3)7)&vRCFRW3skKPJc1OQ zfm3A*bl?)Y679|0)Akwc=92hNKauRtzN8;M-7$#w#P(c_MY(K687Dsj&lB$##`|T8 zlgJJ*2QK0UxQcVT3!#xLCk(dHHxTl}rdL9=K0`CZ_S?<7dQ93KAe@E?$vjJ@f)ai)v*g8J+R@hcIAVf7oUWy+Du!XDznYp z5VZJ8fGUd`YQv%DRv&Cy5K1+HL5wO~fJgA7AxJKBk>>`#CXUIFW z1FFAAjq~VtA^0d@oMjx(`%>rQy+i)y8><8p=lTVC;9rydfygh**Co#|zr`ReQ^mUu zAv}9~P+GiqW-^%Z-2+?%)HQQGBFL&2B1}ApewvWuP|tCRGt7gyz`XdK>4N)UzE??y zT5cEUqRNpK897uBak!{I2YwdfGr(7z$hA|EcfW$oi{P)(@H}`Q40^Qhzc62`p0|tD zeDT#D-7jT??<~2G-OT(E7!NoT`4aFkaEr3YUScI%mXgknkV0|#W-0w^ZkD_I^Q*2{ zyzH(l2kwa<2L9JC2mX&YL|Qiy5(o=}5J}J>9GTT9=wajj3-5@yUjeo@tlBR&j1bTf z%Ycc7AoN@t+ZFS$R(Z-VRmLSeW;@UlTax`vyc6x-iTFaqD~hTAIpS|ZGX8g!6Tfhj zK{vD%c{N}U=Ro3_kW2_(yvZD-U9qJ>rIpw`!^(h{dssng?31{Gk`~q|eqfU?C}TO_ zeN6QJrJaD^MEoh@10ZPxI}-ahA;%$cToUI|k&Sr - - - - SSH key generator for PuTTY - - - - - - - - - - true - - - diff --git a/WINDOWS/puttygen.rc b/WINDOWS/puttygen.rc deleted file mode 100644 index fbe14fc38..000000000 --- a/WINDOWS/puttygen.rc +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Windows resources for PuTTYgen. - */ - -#include "rcstuff.h" - -#define APPNAME "PuTTYgen" -#define APPDESC "PuTTY SSH key generation utility" - -#include "help.rc2" -#include "puttygen-rc.h" - -200 ICON "puttygen.ico" - -201 DIALOG DISCARDABLE 0, 0, 400, 270 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Key Generator" -FONT 8, "MS Shell Dlg" -BEGIN -END - -210 DIALOG DISCARDABLE 0, 0, 140, 60 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTYgen: Enter Passphrase" -FONT 8, "MS Shell Dlg" -BEGIN - CTEXT "Enter passphrase for key", 100, 10, 6, 120, 8 - CTEXT "", 101, 10, 16, 120, 8 - EDITTEXT 102, 10, 26, 120, 12, ES_PASSWORD | ES_AUTOHSCROLL - DEFPUSHBUTTON "O&K", IDOK, 20, 42, 40, 14 - PUSHBUTTON "&Cancel", IDCANCEL, 80, 42, 40, 14 -END - -/* Accelerators used: cl */ -213 DIALOG DISCARDABLE 140, 40, 270, 136 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "About PuTTYgen" -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "&Close", IDOK, 216, 118, 48, 14 - PUSHBUTTON "View &Licence", 101, 6, 118, 70, 14 - PUSHBUTTON "Visit &Web Site", 102, 140, 118, 70, 14 - EDITTEXT 1000, 10, 6, 250, 110, ES_READONLY | ES_MULTILINE | ES_CENTER, WS_EX_STATICEDGE -END - -/* No accelerators used */ -214 DIALOG DISCARDABLE 50, 50, 326, 239 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTY Licence" -FONT 8, "MS Shell Dlg" -BEGIN - DEFPUSHBUTTON "OK", IDOK, 148, 219, 44, 14 - - EDITTEXT 1000, 10, 10, 306, 200, ES_READONLY | ES_MULTILINE | ES_LEFT, WS_EX_STATICEDGE -END - -215 DIALOG DISCARDABLE 0, 0, 259, 98 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTYgen: Private Key File Parameters" -FONT 8, "MS Shell Dlg" -BEGIN - LTEXT "PPK file version:", IDC_PPKVER_STATIC, 5, 6, 119, 8 - AUTORADIOBUTTON "2", IDC_PPKVER_2, 124, 5, 30, 10, WS_GROUP - AUTORADIOBUTTON "3", IDC_PPKVER_3, 154, 5, 30, 10 - LTEXT "Key derivation function:", IDC_KDF_STATIC, 5, 22, 119, 8 - AUTORADIOBUTTON "Argon2id", IDC_KDF_ARGON2ID, 124, 21, 45, 10, WS_GROUP - AUTORADIOBUTTON "Argon2i", IDC_KDF_ARGON2I, 169, 21, 45, 10, WS_GROUP - AUTORADIOBUTTON "Argon2d", IDC_KDF_ARGON2D, 214, 21, 45, 10 - LTEXT "Memory to use for passphrase hash:", IDC_ARGON2_MEM_STATIC, - 5, 36, 119, 8 - EDITTEXT IDC_ARGON2_MEM, 124, 34, 40, 12 - LTEXT "Kbyte", IDC_ARGON2_MEM_STATIC2, 174, 36, 34, 8 - LTEXT "Time to use for passphrase hash:", IDC_ARGON2_TIME_STATIC, - 5, 50, 119, 8 - EDITTEXT IDC_ARGON2_TIME, 124, 48, 40, 12 - AUTORADIOBUTTON "ms", IDC_PPK_AUTO_YES, 174, 49, 20, 10, WS_GROUP - AUTORADIOBUTTON "passes", IDC_PPK_AUTO_NO, 204, 49, 40, 10 - LTEXT "Parallelism for passphrase hash:", IDC_ARGON2_PARALLEL_STATIC, - 5, 64, 119, 8 - EDITTEXT IDC_ARGON2_PARALLEL, 124, 62, 60, 12 - DEFPUSHBUTTON "O&K", IDOK, 70, 80, 40, 14 - PUSHBUTTON "&Cancel", IDCANCEL, 134, 80, 40, 14 -END - -/* Accelerators used: clw */ -216 DIALOG DISCARDABLE 140, 40, 450, 300 -STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "PuTTYgen: certificate information" -FONT 8, "MS Shell Dlg" -CLASS "PuTTYgenCertInfo" -BEGIN - DEFPUSHBUTTON "&Close", IDOK, 201, 130, 48, 14 -END - -#include "version.rc2" - -#ifndef NO_MANIFESTS -1 RT_MANIFEST "puttygen.mft" -#endif /* NO_MANIFESTS */ diff --git a/WINDOWS/puttyins.ico b/WINDOWS/puttyins.ico deleted file mode 100644 index 2bd92c6da2425c32bc31fe9aebe53e46d84cf063..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11478 zcmeHNO>A6O6+X{1<7ayu&%_hQjwkUP3nBy(-V&Bw@l!z(pmd&4 zt6EjpT~{osDyugRy7D!02K!7QKEk7kdOCoCg_`Y-ReRJQ>xYJ^2D$P;O zJMY}{opXQQ{W;eYkwE69UKhwYdAuU>f{3)+Mt^xufG=c~=-s81=NJ)oMu;MY6TECD*TChp8*y0_q>&--Gw|_5h9L+jxHq z{0;DwrXw&Nfsf({1eUN7DJ8ls$mk_Y!Hltcy4h}A!3-Q3uw1Fk&Q{80jl+VFRMrBs zPnB&+Fuy=MC=9FB#l@;qMNWiEwff1CzT5~Kr+mG(w34zfgFL$li)YjV*TA7+ffVea z5!xEzbpYN6-+_LR;X~lK&$hwa;AEdILn$JEpT&O3J=N`e+{sowO>p#JN2LBe!efVGCe}nIU55R}u7={7bo(BAH?d}+G#|6#{dt#UlzL#CH0Jvj69#P(o4voy%)zifD zC=Kwz*v$=jg={fMa8?OKAwc0iKq?ia|CZDjRsuvLh*V}%07f@NfXssY2nYnLi)YSG zKL9O00P1Y$RbV_30YNPhV2Vdi0Q3+J!)$Pi0bp)^p}KgY*p5@QEI6VUPL8oJHvniX zomv@VKfSspvf4N^KhD11JlkA9x6vABUtMdi9pS%x8aJEM%jX{$<8OOEw*UMtmU4qP z8phGvKw9xYYTyuD0tX34c*lHG=6i(Wdw?4l_{u(#ADru@djKS7I|^eI#s>f&AmCAa z2zx9w<|!TU7r{xMG7qJQwj-2ooBLoUPhl?MauweFegobIGhg{B_{ZR{gPC`riFzMo z?(zpP^Ov82Z-L(iGmrTT_?uw#PxF{}A>RSN4rb26yoUJ+?hu{ z@#xkB?coMvKYBjkmYby`=TB<#`3mkVyf5+YQnE6zl0S#( zou+H$6)Rgu$Oyq{Cs9TjIj}lq4fcJ=(8>}d?@+uMQu6Y{dy;g`{f9d5MzrG}8VniX z1%J=x1(JWuj`tz)cj}k`oLsR2Ca-p6?+#^+i9Z;P zp$HYigfUTpCtly*IKf>h-~fS~UgE%kICdmwcUe+mw}8ZM0qdw?#x zB*~az=eGbEGrk$V5h)3vA5V+)fi4&Teu=O5dv&%$4GdiE2mHvp5liZQP>&z`*^ zn1asDn+52i;wD4_1`3orl)N-fB(9l{%%#)c11=cZ5+KsnB&kexmDna*AF(& z5yp_4u>Pc{#Z6p)^0VB>yG4-0o5{@LG~RPa#kaFRj~_W458kaWU1L~eNqoD`bS+{r zCLM6=O&vVETX*VEcdK)>fGyrGLUhcppzanU%KZvBwr16i7Pe`T=fFX-#BY{3pwYV0 z@ib#)L3)jnzFwnzJpYeQyZUiHn zaezL@@+dKwNTDvtFy+9lg{v2I%X5%E_8&`k{c?1`QW*Q?=m4t(McrhdQe;0gyTNW~ zlLg~n(@hHrLMjUOy@;FFaF{E9nwwZr`4F2~Vfla#`8dO&j44NPA^!c9b920UHCL}0 zo!wj6{uYfghe$__+h}A4%stI;!jLIVGcuD7i%%0KY^weV6Tva&5g zxkHI{z4Cd%3v&Ha_@F<+DN%<0Jhp&q;6?BObkOnszZ28mNF(PTuDuJlBUwR1i{uW< zFRA@1 - - - - A network client and terminal emulator - - - - - - - - - - true - - PerMonitorV2 - - - - diff --git a/WINDOWS/puttytel.rc b/WINDOWS/puttytel.rc deleted file mode 100644 index 41767a101..000000000 --- a/WINDOWS/puttytel.rc +++ /dev/null @@ -1,15 +0,0 @@ -#include "rcstuff.h" -#include "putty-rc.h" - -#define APPNAME "PuTTYtel" -#define APPDESC "Telnet and Rlogin client" - -IDI_MAINICON ICON "putty.ico" -IDI_CFGICON ICON "puttycfg.ico" - -#include "help.rc2" -#include "putty-common.rc2" - -#ifndef NO_MANIFESTS -1 RT_MANIFEST "puttytel.mft" -#endif /* NO_MANIFESTS */ diff --git a/WINDOWS/rcstuff.h b/WINDOWS/rcstuff.h deleted file mode 100644 index dbace3f51..000000000 --- a/WINDOWS/rcstuff.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Miscellaneous stuff to include in all .rc files. - */ - -#ifndef PUTTY_RCSTUFF_H -#define PUTTY_RCSTUFF_H - -#ifdef HAVE_CMAKE_H -#include "cmake.h" -#endif - -#if HAVE_WINRESRC_H -#include -#elif HAVE_WINRES_H -#include -#endif - -/* Some systems don't define this, so I do it myself if necessary */ -#ifndef TCS_MULTILINE -#define TCS_MULTILINE 0x0200 -#endif - -/* Likewise */ -#ifndef RT_MANIFEST -#define RT_MANIFEST 24 -#endif - -/* LCC is the offender here. */ -#ifndef VS_FF_DEBUG -#define VS_FF_DEBUG 1 -#endif -#ifndef VS_FF_PRERELEASE -#define VS_FF_PRERELEASE 2 -#endif -#ifndef VS_FF_PRIVATEBUILD -#define VS_FF_PRIVATEBUILD 8 -#endif -#ifndef VOS__WINDOWS32 -#define VOS__WINDOWS32 4 -#endif -#ifndef VFT_APP -#define VFT_APP 1 -#endif - -#endif /* PUTTY_RCSTUFF_H */ diff --git a/WINDOWS/security-api.h b/WINDOWS/security-api.h deleted file mode 100644 index 175486ef3..000000000 --- a/WINDOWS/security-api.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * security-api.h: some miscellaneous security-related helper functions, - * defined in utils/security.c, that use the advapi32 library. Also - * centralises the machinery for dynamically loading that library. - */ - -#include - -/* - * Functions loaded from advapi32.dll. - */ -DECL_WINDOWS_FUNCTION(extern, BOOL, OpenProcessToken, - (HANDLE, DWORD, PHANDLE)); -DECL_WINDOWS_FUNCTION(extern, BOOL, GetTokenInformation, - (HANDLE, TOKEN_INFORMATION_CLASS, - LPVOID, DWORD, PDWORD)); -DECL_WINDOWS_FUNCTION(extern, BOOL, InitializeSecurityDescriptor, - (PSECURITY_DESCRIPTOR, DWORD)); -DECL_WINDOWS_FUNCTION(extern, BOOL, SetSecurityDescriptorOwner, - (PSECURITY_DESCRIPTOR, PSID, BOOL)); -DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo, - (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, - PSID *, PSID *, PACL *, PACL *, - PSECURITY_DESCRIPTOR *)); -DECL_WINDOWS_FUNCTION(extern, DWORD, SetSecurityInfo, - (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, - PSID, PSID, PACL, PACL)); -DECL_WINDOWS_FUNCTION(extern, DWORD, SetEntriesInAclA, - (ULONG, PEXPLICIT_ACCESS, PACL, PACL *)); -bool 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. - */ -bool make_private_security_descriptor( - DWORD permissions, PSECURITY_DESCRIPTOR *psd, PACL *acl, char **error); diff --git a/WINDOWS/select-cli.c b/WINDOWS/select-cli.c deleted file mode 100644 index 9bf8411ed..000000000 --- a/WINDOWS/select-cli.c +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Implementation of do_select() for network.c to use, suitable for use - * when there's no GUI window to have network activity reported to. - * - * It uses WSAEventSelect, where available, to convert network - * activity into activity on an event object, for integration into an - * event loop that includes WaitForMultipleObjects. - * - * It also maintains a list of currently active sockets, which can be - * retrieved by a front end that wants to use WinSock's synchronous - * select() function. - */ - -#include "putty.h" - -static tree234 *winselcli_sockets; - -static int socket_cmp(void *av, void *bv) -{ - return memcmp(av, bv, sizeof(SOCKET)); -} - -HANDLE winselcli_event = INVALID_HANDLE_VALUE; - -void winselcli_setup(void) -{ - if (!winselcli_sockets) - winselcli_sockets = newtree234(socket_cmp); - - if (p_WSAEventSelect && winselcli_event == INVALID_HANDLE_VALUE) - winselcli_event = CreateEvent(NULL, false, false, NULL); -} - -SOCKET winselcli_unique_socket(void) -{ - if (!winselcli_sockets) - return INVALID_SOCKET; - - assert(count234(winselcli_sockets) <= 1); - - SOCKET *p = index234(winselcli_sockets, 0); - if (!p) - return INVALID_SOCKET; - - return *p; -} - -const char *do_select(SOCKET skt, bool enable) -{ - /* Check everything's been set up, for convenience of callers. */ - winselcli_setup(); - - if (enable) { - SOCKET *ptr = snew(SOCKET); - *ptr = skt; - if (add234(winselcli_sockets, ptr) != ptr) - sfree(ptr); /* already there */ - } else { - SOCKET *ptr = del234(winselcli_sockets, &skt); - if (ptr) - sfree(ptr); - } - - if (p_WSAEventSelect) { - int events; - if (enable) { - events = (FD_CONNECT | FD_READ | FD_WRITE | - FD_OOB | FD_CLOSE | FD_ACCEPT); - } else { - events = 0; - } - - if (p_WSAEventSelect(skt, winselcli_event, events) == SOCKET_ERROR) - return winsock_error_string(p_WSAGetLastError()); - } - - return NULL; -} diff --git a/WINDOWS/select-gui.c b/WINDOWS/select-gui.c deleted file mode 100644 index 4c37c3457..000000000 --- a/WINDOWS/select-gui.c +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Implementation of do_select() for network.c to use, that uses - * WSAAsyncSelect to convert network activity into window messages, - * for integration into a GUI event loop. - */ - -#include "putty.h" - -static HWND winsel_hwnd = NULL; - -void winselgui_set_hwnd(HWND hwnd) -{ - winsel_hwnd = hwnd; -} - -void winselgui_clear_hwnd(void) -{ - winsel_hwnd = NULL; -} - -const char *do_select(SOCKET skt, bool enable) -{ - int msg, events; - if (enable) { - msg = WM_NETEVENT; - events = (FD_CONNECT | FD_READ | FD_WRITE | - FD_OOB | FD_CLOSE | FD_ACCEPT); - } else { - msg = events = 0; - } - - assert(winsel_hwnd); - - if (p_WSAAsyncSelect(skt, winsel_hwnd, msg, events) == SOCKET_ERROR) - return winsock_error_string(p_WSAGetLastError()); - - return NULL; -} - -struct wm_netevent_params { - /* Used to pass data to wm_netevent_callback */ - WPARAM wParam; - LPARAM lParam; -}; - -static void wm_netevent_callback(void *vctx) -{ - struct wm_netevent_params *params = (struct wm_netevent_params *)vctx; - select_result(params->wParam, params->lParam); - sfree(params); -} - -void winselgui_response(WPARAM wParam, LPARAM lParam) -{ - /* - * To protect against re-entrancy when Windows's recv() - * immediately triggers a new WSAAsyncSelect window message, we - * don't call select_result directly from this handler but instead - * wait until we're back out at the top level of the message loop. - */ - struct wm_netevent_params *params = snew(struct wm_netevent_params); - params->wParam = wParam; - params->lParam = lParam; - queue_toplevel_callback(wm_netevent_callback, params); -} diff --git a/WINDOWS/serial.c b/WINDOWS/serial.c deleted file mode 100644 index 14a898229..000000000 --- a/WINDOWS/serial.c +++ /dev/null @@ -1,468 +0,0 @@ -/* - * Serial back end (Windows-specific). - */ - -#include -#include -#include - -#include "putty.h" - -#define SERIAL_MAX_BACKLOG 4096 - -typedef struct Serial Serial; -struct Serial { - HANDLE port; - struct handle *out, *in; - Seat *seat; - LogContext *logctx; - int bufsize; - long clearbreak_time; - bool break_in_progress; - Backend backend; -}; - -static void serial_terminate(Serial *serial) -{ - if (serial->out) { - handle_free(serial->out); - serial->out = NULL; - } - if (serial->in) { - handle_free(serial->in); - serial->in = NULL; - } - if (serial->port != INVALID_HANDLE_VALUE) { - if (serial->break_in_progress) - ClearCommBreak(serial->port); - CloseHandle(serial->port); - serial->port = INVALID_HANDLE_VALUE; - } -} - -static size_t serial_gotdata( - struct handle *h, const void *data, size_t len, int err) -{ - Serial *serial = (Serial *)handle_get_privdata(h); - if (err || len == 0) { - const char *error_msg; - - /* - * Currently, len==0 should never happen because we're - * ignoring EOFs. However, it seems not totally impossible - * that this same back end might be usable to talk to named - * pipes or some other non-serial device, in which case EOF - * may become meaningful here. - */ - if (!err) - error_msg = "End of file reading from serial device"; - else - error_msg = "Error reading from serial device"; - - serial_terminate(serial); - - seat_notify_remote_exit(serial->seat); - - logevent(serial->logctx, error_msg); - - seat_connection_fatal(serial->seat, "%s", error_msg); - - return 0; - } else { - return seat_stdout(serial->seat, data, len); - } -} - -static void serial_sentdata(struct handle *h, size_t new_backlog, int err, - bool close) -{ - Serial *serial = (Serial *)handle_get_privdata(h); - if (err) { - const char *error_msg = "Error writing to serial device"; - - serial_terminate(serial); - - seat_notify_remote_exit(serial->seat); - - logevent(serial->logctx, error_msg); - - seat_connection_fatal(serial->seat, "%s", error_msg); - } else { - serial->bufsize = new_backlog; - seat_sent(serial->seat, serial->bufsize); - } -} - -static char *serial_configure(Serial *serial, HANDLE serport, Conf *conf) -{ - DCB dcb; - COMMTIMEOUTS timeouts; - - /* - * Set up the serial port parameters. If we can't even - * GetCommState, we ignore the problem on the grounds that the - * user might have pointed us at some other type of two-way - * device instead of a serial port. - */ - if (GetCommState(serport, &dcb)) { - const char *str; - - /* - * Boilerplate. - */ - dcb.fBinary = true; - dcb.fDtrControl = DTR_CONTROL_ENABLE; - dcb.fDsrSensitivity = false; - dcb.fTXContinueOnXoff = false; - dcb.fOutX = false; - dcb.fInX = false; - dcb.fErrorChar = false; - dcb.fNull = false; - dcb.fRtsControl = RTS_CONTROL_ENABLE; - dcb.fAbortOnError = false; - dcb.fOutxCtsFlow = false; - dcb.fOutxDsrFlow = false; - - /* - * Configurable parameters. - */ - dcb.BaudRate = conf_get_int(conf, CONF_serspeed); - logeventf(serial->logctx, "Configuring baud rate %lu", - (unsigned long)dcb.BaudRate); - - dcb.ByteSize = conf_get_int(conf, CONF_serdatabits); - logeventf(serial->logctx, "Configuring %u data bits", - (unsigned)dcb.ByteSize); - - switch (conf_get_int(conf, CONF_serstopbits)) { - case 2: dcb.StopBits = ONESTOPBIT; str = "1 stop bit"; break; - case 3: dcb.StopBits = ONE5STOPBITS; str = "1.5 stop bits"; break; - case 4: dcb.StopBits = TWOSTOPBITS; str = "2 stop bits"; break; - default: return dupstr("Invalid number of stop bits " - "(need 1, 1.5 or 2)"); - } - logeventf(serial->logctx, "Configuring %s", str); - - switch (conf_get_int(conf, CONF_serparity)) { - case SER_PAR_NONE: dcb.Parity = NOPARITY; str = "no"; break; - case SER_PAR_ODD: dcb.Parity = ODDPARITY; str = "odd"; break; - case SER_PAR_EVEN: dcb.Parity = EVENPARITY; str = "even"; break; - case SER_PAR_MARK: dcb.Parity = MARKPARITY; str = "mark"; break; - case SER_PAR_SPACE: dcb.Parity = SPACEPARITY; str = "space"; break; - } - logeventf(serial->logctx, "Configuring %s parity", str); - - switch (conf_get_int(conf, CONF_serflow)) { - case SER_FLOW_NONE: - str = "no"; - break; - case SER_FLOW_XONXOFF: - dcb.fOutX = dcb.fInX = true; - str = "XON/XOFF"; - break; - case SER_FLOW_RTSCTS: - dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; - dcb.fOutxCtsFlow = true; - str = "RTS/CTS"; - break; - case SER_FLOW_DSRDTR: - dcb.fDtrControl = DTR_CONTROL_HANDSHAKE; - dcb.fOutxDsrFlow = true; - str = "DSR/DTR"; - break; - } - logeventf(serial->logctx, "Configuring %s flow control", str); - - if (!SetCommState(serport, &dcb)) - return dupprintf("Configuring serial port: %s", - win_strerror(GetLastError())); - - timeouts.ReadIntervalTimeout = 1; - timeouts.ReadTotalTimeoutMultiplier = 0; - timeouts.ReadTotalTimeoutConstant = 0; - timeouts.WriteTotalTimeoutMultiplier = 0; - timeouts.WriteTotalTimeoutConstant = 0; - if (!SetCommTimeouts(serport, &timeouts)) - return dupprintf("Configuring serial timeouts: %s", - win_strerror(GetLastError())); - } - - 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 char *serial_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - Serial *serial; - HANDLE serport; - char *err; - char *serline; - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - serial = snew(Serial); - memset(serial, 0, sizeof(Serial)); - serial->port = INVALID_HANDLE_VALUE; - serial->out = serial->in = NULL; - serial->bufsize = 0; - serial->break_in_progress = false; - serial->backend.vt = vt; - *backend_handle = &serial->backend; - - serial->seat = seat; - serial->logctx = logctx; - - serline = conf_get_str(conf, CONF_serline); - logeventf(serial->logctx, "Opening serial device %s", serline); - - /* - * Munge the string supplied by the user into a Windows filename. - * - * Windows supports opening a few "legacy" devices (including - * COM1-9) by specifying their names verbatim as a filename to - * open. (Thus, no files can ever have these names. See - * - * ("Naming a File") for the complete list of reserved names.) - * - * However, this doesn't let you get at devices COM10 and above. - * For that, you need to specify a filename like "\\.\COM10". - * This is also necessary for special serial and serial-like - * devices such as \\.\WCEUSBSH001. It also works for the "legacy" - * names, so you can do \\.\COM1 (verified as far back as Win95). - * See - * (CreateFile() docs). - * - * So, we believe that prepending "\\.\" should always be the - * Right Thing. However, just in case someone finds something to - * talk to that doesn't exist under there, if the serial line - * contains a backslash, we use it verbatim. (This also lets - * existing configurations using \\.\ continue working.) - */ - char *serfilename = - dupprintf("%s%s", strchr(serline, '\\') ? "" : "\\\\.\\", serline); - serport = CreateFile(serfilename, GENERIC_READ | GENERIC_WRITE, 0, NULL, - OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); - if (serport == INVALID_HANDLE_VALUE) { - err = dupprintf("Opening '%s': %s", - serfilename, win_strerror(GetLastError())); - sfree(serfilename); - return err; - } - - sfree(serfilename); - - err = serial_configure(serial, serport, conf); - if (err) - return err; - - serial->port = serport; - serial->out = handle_output_new(serport, serial_sentdata, serial, - HANDLE_FLAG_OVERLAPPED); - serial->in = handle_input_new(serport, serial_gotdata, serial, - HANDLE_FLAG_OVERLAPPED | - HANDLE_FLAG_IGNOREEOF | - HANDLE_FLAG_UNITBUFFER); - - *realhost = dupstr(serline); - - /* - * Specials are always available. - */ - seat_update_specials_menu(serial->seat); - - return NULL; -} - -static void serial_free(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - - serial_terminate(serial); - expire_timer_context(serial); - sfree(serial); -} - -static void serial_reconfig(Backend *be, Conf *conf) -{ - Serial *serial = container_of(be, Serial, backend); - - serial_configure(serial, serial->port, conf); - - /* - * FIXME: what should we do if that call returned a non-NULL error - * message? - */ -} - -/* - * Called to send data down the serial connection. - */ -static void serial_send(Backend *be, const char *buf, size_t len) -{ - Serial *serial = container_of(be, Serial, backend); - - if (serial->out == NULL) - return; - - serial->bufsize = handle_write(serial->out, buf, len); -} - -/* - * Called to query the current sendability status. - */ -static size_t serial_sendbuffer(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - return serial->bufsize; -} - -/* - * Called to set the size of the window - */ -static void serial_size(Backend *be, int width, int height) -{ - /* Do nothing! */ - return; -} - -static void serbreak_timer(void *ctx, unsigned long now) -{ - Serial *serial = (Serial *)ctx; - - if (now == serial->clearbreak_time && serial->port) { - ClearCommBreak(serial->port); - serial->break_in_progress = false; - logevent(serial->logctx, "Finished serial break"); - } -} - -/* - * Send serial special codes. - */ -static void serial_special(Backend *be, SessionSpecialCode code, int arg) -{ - Serial *serial = container_of(be, Serial, backend); - - if (serial->port && code == SS_BRK) { - logevent(serial->logctx, "Starting serial break at user request"); - SetCommBreak(serial->port); - /* - * To send a serial break on Windows, we call SetCommBreak - * to begin the break, then wait a bit, and then call - * ClearCommBreak to finish it. Hence, I must use timing.c - * to arrange a callback when it's time to do the latter. - * - * SUS says that a default break length must be between 1/4 - * and 1/2 second. FreeBSD apparently goes with 2/5 second, - * and so will I. - */ - serial->clearbreak_time = - schedule_timer(TICKSPERSEC * 2 / 5, serbreak_timer, serial); - serial->break_in_progress = true; - } - - return; -} - -/* - * Return a list of the special codes that make sense in this - * protocol. - */ -static const SessionSpecial *serial_get_specials(Backend *be) -{ - static const SessionSpecial specials[] = { - {"Break", SS_BRK}, - {NULL, SS_EXITMENU} - }; - return specials; -} - -static bool serial_connected(Backend *be) -{ - return true; /* always connected */ -} - -static bool serial_sendok(Backend *be) -{ - return true; -} - -static void serial_unthrottle(Backend *be, size_t backlog) -{ - Serial *serial = container_of(be, Serial, backend); - if (serial->in) - handle_unthrottle(serial->in, backlog); -} - -static bool serial_ldisc(Backend *be, int option) -{ - /* - * Local editing and local echo are off by default. - */ - return false; -} - -static void serial_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - /* This is a stub. */ -} - -static int serial_exitcode(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - if (serial->port != INVALID_HANDLE_VALUE) - 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(Backend *be) -{ - return 0; -} - -const BackendVtable serial_backend = { - .init = serial_init, - .free = serial_free, - .reconfig = serial_reconfig, - .send = serial_send, - .sendbuffer = serial_sendbuffer, - .size = serial_size, - .special = serial_special, - .get_specials = serial_get_specials, - .connected = serial_connected, - .exitcode = serial_exitcode, - .sendok = serial_sendok, - .ldisc_option_state = serial_ldisc, - .provide_ldisc = serial_provide_ldisc, - .unthrottle = serial_unthrottle, - .cfg_info = serial_cfg_info, - .id = "serial", - .displayname_tc = "Serial", - .displayname_lc = "serial", - .protocol = PROT_SERIAL, - .serial_parity_mask = ((1 << SER_PAR_NONE) | - (1 << SER_PAR_ODD) | - (1 << SER_PAR_EVEN) | - (1 << SER_PAR_MARK) | - (1 << SER_PAR_SPACE)), - .serial_flow_mask = ((1 << SER_FLOW_NONE) | - (1 << SER_FLOW_XONXOFF) | - (1 << SER_FLOW_RTSCTS) | - (1 << SER_FLOW_DSRDTR)), -}; diff --git a/WINDOWS/sftp.c b/WINDOWS/sftp.c deleted file mode 100644 index 9e98bbd02..000000000 --- a/WINDOWS/sftp.c +++ /dev/null @@ -1,656 +0,0 @@ -/* - * sftp.c: the Windows-specific parts of PSFTP and PSCP. - */ - -#include /* need to put this first, for winelib builds */ -#include - -#define NEED_DECLARATION_OF_SELECT - -#include "putty.h" -#include "psftp.h" -#include "ssh.h" -#include "security-api.h" - -SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p) -{ - /* The file transfer tools don't support Restart Session, so we - * can just have a single static cmdline_get_passwd_input_state - * that's never reset */ - static cmdline_get_passwd_input_state cmdline_state = - CMDLINE_GET_PASSWD_INPUT_STATE_INIT; - - SeatPromptResult spr; - spr = cmdline_get_passwd_input(p, &cmdline_state, false); - if (spr.kind == SPRK_INCOMPLETE) - spr = console_get_userpass_input(p); - return spr; -} - -void platform_get_x11_auth(struct X11Display *display, Conf *conf) -{ - /* Do nothing, therefore no auth. */ -} -const bool platform_uses_x11_unix_by_default = true; - -/* ---------------------------------------------------------------------- - * File access abstraction. - */ - -/* - * Set local current directory. Returns NULL on success, or else an - * error message which must be freed after printing. - */ -char *psftp_lcd(char *dir) -{ - char *ret = NULL; - - if (!SetCurrentDirectory(dir)) { - LPVOID message; - int i; - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&message, 0, NULL); - i = strcspn((char *)message, "\n"); - ret = dupprintf("%.*s", i, (LPCTSTR)message); - LocalFree(message); - } - - return ret; -} - -/* - * Get local current directory. Returns a string which must be - * freed. - */ -char *psftp_getcwd(void) -{ - char *ret = snewn(256, char); - size_t len = GetCurrentDirectory(256, ret); - if (len > 256) - ret = sresize(ret, len, char); - GetCurrentDirectory(len, ret); - return ret; -} - -static inline uint64_t uint64_from_words(uint32_t hi, uint32_t lo) -{ - return (((uint64_t)hi) << 32) | lo; -} - -#define TIME_POSIX_TO_WIN(t, ft) do { \ - ULARGE_INTEGER uli; \ - uli.QuadPart = ((ULONGLONG)(t) + 11644473600ull) * 10000000ull; \ - (ft).dwLowDateTime = uli.LowPart; \ - (ft).dwHighDateTime = uli.HighPart; \ -} while (0) -#define TIME_WIN_TO_POSIX(ft, t) do { \ - ULARGE_INTEGER uli; \ - uli.LowPart = (ft).dwLowDateTime; \ - uli.HighPart = (ft).dwHighDateTime; \ - uli.QuadPart = uli.QuadPart / 10000000ull - 11644473600ull; \ - (t) = (unsigned long) uli.QuadPart; \ -} while (0) - -struct RFile { - HANDLE h; -}; - -RFile *open_existing_file(const char *name, uint64_t *size, - unsigned long *mtime, unsigned long *atime, - long *perms) -{ - HANDLE h; - RFile *f; - - h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL, - OPEN_EXISTING, 0, 0); - if (h == INVALID_HANDLE_VALUE) - return NULL; - - f = snew(RFile); - f->h = h; - - if (size) { - DWORD lo, hi; - lo = GetFileSize(h, &hi); - *size = uint64_from_words(hi, lo); - } - - if (mtime || atime) { - FILETIME actime, wrtime; - GetFileTime(h, NULL, &actime, &wrtime); - if (atime) - TIME_WIN_TO_POSIX(actime, *atime); - if (mtime) - TIME_WIN_TO_POSIX(wrtime, *mtime); - } - - if (perms) - *perms = -1; - - return f; -} - -int read_from_file(RFile *f, void *buffer, int length) -{ - DWORD read; - if (!ReadFile(f->h, buffer, length, &read, NULL)) - return -1; /* error */ - else - return read; -} - -void close_rfile(RFile *f) -{ - CloseHandle(f->h); - sfree(f); -} - -struct WFile { - HANDLE h; -}; - -WFile *open_new_file(const char *name, long perms) -{ - HANDLE h; - WFile *f; - - h = CreateFile(name, GENERIC_WRITE, 0, NULL, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); - if (h == INVALID_HANDLE_VALUE) - return NULL; - - f = snew(WFile); - f->h = h; - - return f; -} - -WFile *open_existing_wfile(const char *name, uint64_t *size) -{ - HANDLE h; - WFile *f; - - h = CreateFile(name, GENERIC_WRITE, FILE_SHARE_READ, NULL, - OPEN_EXISTING, 0, 0); - if (h == INVALID_HANDLE_VALUE) - return NULL; - - f = snew(WFile); - f->h = h; - - if (size) { - DWORD lo, hi; - lo = GetFileSize(h, &hi); - *size = uint64_from_words(hi, lo); - } - - return f; -} - -int write_to_file(WFile *f, void *buffer, int length) -{ - DWORD written; - if (!WriteFile(f->h, buffer, length, &written, NULL)) - return -1; /* error */ - else - return written; -} - -void set_file_times(WFile *f, unsigned long mtime, unsigned long atime) -{ - FILETIME actime, wrtime; - TIME_POSIX_TO_WIN(atime, actime); - TIME_POSIX_TO_WIN(mtime, wrtime); - SetFileTime(f->h, NULL, &actime, &wrtime); -} - -void close_wfile(WFile *f) -{ - CloseHandle(f->h); - 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_t offset, int whence) -{ - DWORD movemethod; - - switch (whence) { - case FROM_START: - movemethod = FILE_BEGIN; - break; - case FROM_CURRENT: - movemethod = FILE_CURRENT; - break; - case FROM_END: - movemethod = FILE_END; - break; - default: - return -1; - } - - { - LONG lo = offset & 0xFFFFFFFFU, hi = offset >> 32; - SetFilePointer(f->h, lo, &hi, movemethod); - } - - if (GetLastError() != NO_ERROR) - return -1; - else - return 0; -} - -uint64_t get_file_posn(WFile *f) -{ - LONG lo, hi = 0; - - lo = SetFilePointer(f->h, 0L, &hi, FILE_CURRENT); - return uint64_from_words(hi, lo); -} - -int file_type(const char *name) -{ - DWORD attr; - attr = GetFileAttributes(name); - /* We know of no `weird' files under Windows. */ - if (attr == (DWORD)-1) - return FILE_TYPE_NONEXISTENT; - else if (attr & FILE_ATTRIBUTE_DIRECTORY) - return FILE_TYPE_DIRECTORY; - else - return FILE_TYPE_FILE; -} - -struct DirHandle { - HANDLE h; - char *name; -}; - -DirHandle *open_directory(const char *name, const char **errmsg) -{ - HANDLE h; - WIN32_FIND_DATA fdat; - char *findfile; - DirHandle *dir; - - /* Enumerate files in dir `foo'. */ - findfile = dupcat(name, "/*"); - h = FindFirstFile(findfile, &fdat); - if (h == INVALID_HANDLE_VALUE) { - *errmsg = win_strerror(GetLastError()); - return NULL; - } - sfree(findfile); - - dir = snew(DirHandle); - dir->h = h; - dir->name = dupstr(fdat.cFileName); - return dir; -} - -char *read_filename(DirHandle *dir) -{ - do { - - if (!dir->name) { - WIN32_FIND_DATA fdat; - if (!FindNextFile(dir->h, &fdat)) - return NULL; - else - dir->name = dupstr(fdat.cFileName); - } - - assert(dir->name); - if (dir->name[0] == '.' && - (dir->name[1] == '\0' || - (dir->name[1] == '.' && dir->name[2] == '\0'))) { - sfree(dir->name); - dir->name = NULL; - } - - } while (!dir->name); - - if (dir->name) { - char *ret = dir->name; - dir->name = NULL; - return ret; - } else - return NULL; -} - -void close_directory(DirHandle *dir) -{ - FindClose(dir->h); - if (dir->name) - sfree(dir->name); - sfree(dir); -} - -int test_wildcard(const char *name, bool cmdline) -{ - HANDLE fh; - WIN32_FIND_DATA fdat; - - /* First see if the exact name exists. */ - if (GetFileAttributes(name) != (DWORD)-1) - return WCTYPE_FILENAME; - - /* Otherwise see if a wildcard match finds anything. */ - fh = FindFirstFile(name, &fdat); - if (fh == INVALID_HANDLE_VALUE) - return WCTYPE_NONEXISTENT; - - FindClose(fh); - return WCTYPE_WILDCARD; -} - -struct WildcardMatcher { - HANDLE h; - char *name; - char *srcpath; -}; - -char *stripslashes(const char *str, bool local) -{ - char *p; - - /* - * On Windows, \ / : are all path component separators. - */ - - 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 (char *)str; -} - -WildcardMatcher *begin_wildcard_matching(const char *name) -{ - HANDLE h; - WIN32_FIND_DATA fdat; - WildcardMatcher *dir; - char *last; - - h = FindFirstFile(name, &fdat); - if (h == INVALID_HANDLE_VALUE) - return NULL; - - dir = snew(WildcardMatcher); - dir->h = h; - dir->srcpath = dupstr(name); - last = stripslashes(dir->srcpath, true); - *last = '\0'; - if (fdat.cFileName[0] == '.' && - (fdat.cFileName[1] == '\0' || - (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0'))) - dir->name = NULL; - else - dir->name = dupcat(dir->srcpath, fdat.cFileName); - - return dir; -} - -char *wildcard_get_filename(WildcardMatcher *dir) -{ - while (!dir->name) { - WIN32_FIND_DATA fdat; - - if (!FindNextFile(dir->h, &fdat)) - return NULL; - - if (fdat.cFileName[0] == '.' && - (fdat.cFileName[1] == '\0' || - (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0'))) - dir->name = NULL; - else - dir->name = dupcat(dir->srcpath, fdat.cFileName); - } - - if (dir->name) { - char *ret = dir->name; - dir->name = NULL; - return ret; - } else - return NULL; -} - -void finish_wildcard_matching(WildcardMatcher *dir) -{ - FindClose(dir->h); - if (dir->name) - sfree(dir->name); - sfree(dir->srcpath); - sfree(dir); -} - -bool vet_filename(const char *name) -{ - if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':')) - return false; - - if (!name[strspn(name, ".")]) /* entirely composed of dots */ - return false; - - return true; -} - -bool create_directory(const char *name) -{ - return CreateDirectory(name, NULL) != 0; -} - -char *dir_file_cat(const char *dir, const char *file) -{ - ptrlen dir_pl = ptrlen_from_asciz(dir); - return dupcat( - dir, (ptrlen_endswith(dir_pl, PTRLEN_LITERAL("\\"), NULL) || - ptrlen_endswith(dir_pl, PTRLEN_LITERAL("/"), NULL)) ? "" : "\\", - file); -} - -/* ---------------------------------------------------------------------- - * Platform-specific network handling. - */ -struct winsftp_cliloop_ctx { - HANDLE other_event; - int toret; -}; -static bool winsftp_cliloop_pre(void *vctx, const HANDLE **extra_handles, - size_t *n_extra_handles) -{ - struct winsftp_cliloop_ctx *ctx = (struct winsftp_cliloop_ctx *)vctx; - - if (ctx->other_event != INVALID_HANDLE_VALUE) { - *extra_handles = &ctx->other_event; - *n_extra_handles = 1; - } - - return true; -} -static bool winsftp_cliloop_post(void *vctx, size_t extra_handle_index) -{ - struct winsftp_cliloop_ctx *ctx = (struct winsftp_cliloop_ctx *)vctx; - - if (ctx->other_event != INVALID_HANDLE_VALUE && - extra_handle_index == 0) - ctx->toret = 1; /* other_event was set */ - - return false; /* always run only one loop iteration */ -} -int do_eventsel_loop(HANDLE other_event) -{ - struct winsftp_cliloop_ctx ctx[1]; - ctx->other_event = other_event; - ctx->toret = 0; - cli_main_loop(winsftp_cliloop_pre, winsftp_cliloop_post, ctx); - return ctx->toret; -} - -/* - * Wait for some network data and process it. - * - * We have two variants of this function. One uses select() so that - * it's compatible with WinSock 1. The other uses WSAEventSelect - * and MsgWaitForMultipleObjects, so that we can consistently use - * WSAEventSelect throughout; this enables us to also implement - * ssh_sftp_get_cmdline() using a parallel mechanism. - */ -int ssh_sftp_loop_iteration(void) -{ - if (p_WSAEventSelect == NULL) { - fd_set readfds; - int ret; - unsigned long now = GETTICKCOUNT(), then; - SOCKET skt = winselcli_unique_socket(); - - if (skt == INVALID_SOCKET) - return -1; /* doom */ - - if (socket_writable(skt)) - select_result((WPARAM) skt, (LPARAM) FD_WRITE); - - do { - unsigned long next; - long ticks; - struct timeval tv, *ptv; - - if (run_timers(now, &next)) { - 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; - ptv = &tv; - } else { - ptv = NULL; - } - - FD_ZERO(&readfds); - FD_SET(skt, &readfds); - ret = p_select(1, &readfds, NULL, NULL, ptv); - - if (ret < 0) - return -1; /* doom */ - else if (ret == 0) - now = next; - else - now = GETTICKCOUNT(); - - } while (ret == 0); - - select_result((WPARAM) skt, (LPARAM) FD_READ); - - return 0; - } else { - return do_eventsel_loop(INVALID_HANDLE_VALUE); - } -} - -/* - * Read a command line from standard input. - * - * In the presence of WinSock 2, we can use WSAEventSelect to - * mediate between the socket and stdin, meaning we can send - * keepalives and respond to server events even while waiting at - * the PSFTP command prompt. Without WS2, we fall back to a simple - * fgets. - */ -struct command_read_ctx { - HANDLE event; - char *line; -}; - -static DWORD WINAPI command_read_thread(void *param) -{ - struct command_read_ctx *ctx = (struct command_read_ctx *) param; - - ctx->line = fgetline(stdin); - - SetEvent(ctx->event); - - return 0; -} - -char *ssh_sftp_get_cmdline(const char *prompt, bool no_fds_ok) -{ - int ret; - struct command_read_ctx ctx[1]; - DWORD threadid; - HANDLE hThread; - - fputs(prompt, stdout); - fflush(stdout); - - if ((winselcli_unique_socket() == INVALID_SOCKET && no_fds_ok) || - p_WSAEventSelect == NULL) { - return fgetline(stdin); /* very simple */ - } - - /* - * Create a second thread to read from stdin. Process network - * and timing events until it terminates. - */ - ctx->event = CreateEvent(NULL, false, false, NULL); - ctx->line = NULL; - - hThread = CreateThread(NULL, 0, command_read_thread, ctx, 0, &threadid); - if (!hThread) { - CloseHandle(ctx->event); - fprintf(stderr, "Unable to create command input thread\n"); - cleanup_exit(1); - } - - do { - ret = do_eventsel_loop(ctx->event); - - /* do_eventsel_loop can't return an error (unlike - * ssh_sftp_loop_iteration, which can return -1 if select goes - * wrong or if the socket doesn't exist). */ - assert(ret >= 0); - } while (ret == 0); - - CloseHandle(hThread); - CloseHandle(ctx->event); - - return ctx->line; -} - -void platform_psftp_pre_conn_setup(LogPolicy *lp) -{ - if (restricted_acl()) { - lp_eventlog(lp, "Running with restricted process ACL"); - } -} - -/* ---------------------------------------------------------------------- - * Main program. Parse arguments etc. - */ -int main(int argc, char *argv[]) -{ - int ret; - - dll_hijacking_protection(); - - ret = psftp_main(argc, argv); - - return ret; -} diff --git a/WINDOWS/sharing.c b/WINDOWS/sharing.c deleted file mode 100644 index 15b1da488..000000000 --- a/WINDOWS/sharing.c +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Windows implementation of SSH connection-sharing IPC setup. - */ - -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "proxy/proxy.h" -#include "ssh.h" - -#include "cryptoapi.h" -#include "security-api.h" - -#define CONNSHARE_PIPE_PREFIX "\\\\.\\pipe\\putty-connshare" -#define CONNSHARE_MUTEX_PREFIX "Local\\putty-connshare-mutex" - -static char *make_name(const char *prefix, const char *name) -{ - char *username, *retname; - - username = get_username(); - retname = dupprintf("%s.%s.%s", prefix, username, name); - sfree(username); - - return retname; -} - -int platform_ssh_share(const char *pi_name, Conf *conf, - Plug *downplug, Plug *upplug, Socket **sock, - char **logtext, char **ds_err, char **us_err, - bool can_upstream, bool can_downstream) -{ - char *name, *mutexname, *pipename; - HANDLE mutex; - Socket *retsock; - - /* - * Transform the platform-independent version of the connection - * identifier into the obfuscated version we'll use for our - * Windows named pipe and mutex. A side effect of doing this is - * that it also eliminates any characters illegal in Windows pipe - * names. - */ - name = capi_obfuscate_string(pi_name); - if (!name) { - *logtext = dupprintf("Unable to call CryptProtectMemory: %s", - win_strerror(GetLastError())); - return SHARE_NONE; - } - - /* - * Make a mutex name out of the connection identifier, and lock it - * while we decide whether to be upstream or downstream. - */ - mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name); - mutex = lock_interprocess_mutex(mutexname, logtext); - if (!mutex) { - sfree(mutexname); - sfree(name); - return SHARE_NONE; - } - - pipename = make_name(CONNSHARE_PIPE_PREFIX, name); - - *logtext = NULL; - - if (can_downstream) { - retsock = new_named_pipe_client(pipename, downplug); - if (sk_socket_error(retsock) == NULL) { - sfree(*logtext); - *logtext = pipename; - *sock = retsock; - sfree(name); - unlock_interprocess_mutex(mutex); - return SHARE_DOWNSTREAM; - } - sfree(*ds_err); - *ds_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock)); - sk_close(retsock); - } - - if (can_upstream) { - retsock = new_named_pipe_listener(pipename, upplug); - if (sk_socket_error(retsock) == NULL) { - sfree(*logtext); - *logtext = pipename; - *sock = retsock; - sfree(name); - ReleaseMutex(mutex); - CloseHandle(mutex); - return SHARE_UPSTREAM; - } - sfree(*us_err); - *us_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock)); - sk_close(retsock); - } - - /* One of the above clauses ought to have happened. */ - assert(*logtext || *ds_err || *us_err); - - sfree(pipename); - sfree(name); - ReleaseMutex(mutex); - CloseHandle(mutex); - return SHARE_NONE; -} - -void platform_ssh_share_cleanup(const char *name) -{ -} diff --git a/WINDOWS/sign_binaries.ps1 b/WINDOWS/sign_binaries.ps1 deleted file mode 100644 index 109d936c2..000000000 --- a/WINDOWS/sign_binaries.ps1 +++ /dev/null @@ -1,76 +0,0 @@ -param ( - [string] - [Parameter(Mandatory=$true)] - $TargetDir, - - [string] - [Parameter(Mandatory=$true)] - $ConfigurationName, - - [string[]] - # File names to exclude from signing - $Exclude, - - [string] - # The code signing certificate to use when signing the files. - $CertificatePath, - - [string] - # Password to unlock the code signing certificate. - $CertificatePassword -) - -Write-Output "===== Beginning $($PSCmdlet.MyInvocation.MyCommand) =====" - - -$timeserver = "http://timestamp.verisign.com/scripts/timstamp.dll" - - -if ($ConfigurationName -notmatch "Release") { - Write-Output "This is not a release build - we won't sign files." - return -} - -if ($CertificatePath -eq "" -or !(Test-Path -Path $CertificatePath -PathType Leaf)) { - Write-Output "Certificate is not present - we won't sign files." - return -} - -if ($CertificatePassword -eq "") { - Write-Output "No certificate password was provided - we won't sign files." - return -} - -try { - $certKeyStore = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertificatePath, $CertificatePassword, $certKeyStore) -ErrorAction Stop -} catch { - Write-Output "Error loading certificate file - we won't sign files." - Write-Output $Error[0] - return -} - -# Sign MSI if we are building a release version and the certificate is available -Write-Output "Signing Binaries" -Write-Output "Getting files from path: $TargetDir" -$signableFiles = Get-ChildItem -Path $TargetDir -Recurse | ?{$_.Extension -match "dll|exe|msi"} | ?{$Exclude -notcontains $_.Name} - -$excluded_files = Get-ChildItem -Path $TargetDir -Recurse | ?{$_.Extension -match "dll|exe|msi"} | ?{$Exclude -contains $_.Name} -$excluded_files | ForEach-Object ` - -Begin { Write-Output "The following files were excluded from signing due to being on the exclusion list:" } ` - -Process { Write-Output "-- $($_.FullName)" } - -Write-Output "Signable files count: $($signableFiles.Count)" - - -foreach ($file in $signableFiles) { - Set-AuthenticodeSignature -Certificate $cert -TimestampServer $timeserver -IncludeChain all -FilePath $file.FullName -} - - -# Release certificate -if ($cert -ne $null) { - $cert.Dispose() -} - -Write-Output "" \ No newline at end of file diff --git a/WINDOWS/sizetip.c b/WINDOWS/sizetip.c deleted file mode 100644 index 46b1ec8b2..000000000 --- a/WINDOWS/sizetip.c +++ /dev/null @@ -1,192 +0,0 @@ -/* - * sizetip.c - resize tips for PuTTY(tel) terminal window. - */ - -#include -#include -#include - -#include "putty.h" - -static ATOM tip_class = 0; - -static HFONT tip_font; -static COLORREF tip_bg; -static COLORREF tip_text; - -static LRESULT CALLBACK SizeTipWndProc(HWND hWnd, UINT nMsg, - WPARAM wParam, LPARAM lParam) -{ - - switch (nMsg) { - case WM_ERASEBKGND: - return true; - - case WM_PAINT: { - HBRUSH hbr; - HGDIOBJ holdbr; - RECT cr; - int wtlen; - LPTSTR wt; - HDC hdc; - - PAINTSTRUCT ps; - hdc = BeginPaint(hWnd, &ps); - - SelectObject(hdc, tip_font); - SelectObject(hdc, GetStockObject(BLACK_PEN)); - - hbr = CreateSolidBrush(tip_bg); - holdbr = SelectObject(hdc, hbr); - - GetClientRect(hWnd, &cr); - Rectangle(hdc, cr.left, cr.top, cr.right, cr.bottom); - - wtlen = GetWindowTextLength(hWnd); - wt = (LPTSTR) snewn(wtlen + 1, TCHAR); - GetWindowText(hWnd, wt, wtlen + 1); - - SetTextColor(hdc, tip_text); - SetBkColor(hdc, tip_bg); - - TextOut(hdc, cr.left + 3, cr.top + 3, wt, wtlen); - - sfree(wt); - - SelectObject(hdc, holdbr); - DeleteObject(hbr); - - EndPaint(hWnd, &ps); - return 0; - } - - case WM_NCHITTEST: - return HTTRANSPARENT; - - case WM_DESTROY: - DeleteObject(tip_font); - tip_font = NULL; - break; - - case WM_SETTEXT: { - LPCTSTR str = (LPCTSTR) lParam; - SIZE sz; - HDC hdc = CreateCompatibleDC(NULL); - - SelectObject(hdc, tip_font); - GetTextExtentPoint32(hdc, str, _tcslen(str), &sz); - - SetWindowPos(hWnd, NULL, 0, 0, sz.cx + 6, sz.cy + 6, - SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); - InvalidateRect(hWnd, NULL, false); - - DeleteDC(hdc); - break; - } - } - - return DefWindowProc(hWnd, nMsg, wParam, lParam); -} - -static HWND tip_wnd = NULL; -static bool tip_enabled = false; - -void UpdateSizeTip(HWND src, int cx, int cy) -{ - TCHAR str[32]; - - if (!tip_enabled) - return; - - if (!tip_wnd) { - NONCLIENTMETRICS nci; - - /* First make sure the window class is registered */ - - if (!tip_class) { - WNDCLASS wc; - wc.style = CS_HREDRAW | CS_VREDRAW; - wc.lpfnWndProc = SizeTipWndProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = hinst; - wc.hIcon = NULL; - wc.hCursor = NULL; - wc.hbrBackground = NULL; - wc.lpszMenuName = NULL; - wc.lpszClassName = "SizeTipClass"; - - tip_class = RegisterClass(&wc); - } -#if 0 - /* Default values based on Windows Standard color scheme */ - - tip_font = GetStockObject(SYSTEM_FONT); - tip_bg = RGB(255, 255, 225); - tip_text = RGB(0, 0, 0); -#endif - - /* Prepare other GDI objects and drawing info */ - - tip_bg = GetSysColor(COLOR_INFOBK); - tip_text = GetSysColor(COLOR_INFOTEXT); - - memset(&nci, 0, sizeof(NONCLIENTMETRICS)); - nci.cbSize = sizeof(NONCLIENTMETRICS); - SystemParametersInfo(SPI_GETNONCLIENTMETRICS, - sizeof(NONCLIENTMETRICS), &nci, 0); - tip_font = CreateFontIndirect(&nci.lfStatusFont); - } - - /* Generate the tip text */ - - sprintf(str, "%dx%d", cx, cy); - - if (!tip_wnd) { - HDC hdc; - SIZE sz; - RECT wr; - int ix, iy; - - /* calculate the tip's size */ - - hdc = CreateCompatibleDC(NULL); - GetTextExtentPoint32(hdc, str, _tcslen(str), &sz); - DeleteDC(hdc); - - GetWindowRect(src, &wr); - - ix = wr.left; - if (ix < 16) - ix = 16; - - iy = wr.top - sz.cy; - if (iy < 16) - iy = 16; - - /* Create the tip window */ - - tip_wnd = - CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TOPMOST, - MAKEINTRESOURCE(tip_class), str, WS_POPUP, ix, - iy, sz.cx, sz.cy, NULL, NULL, hinst, NULL); - - ShowWindow(tip_wnd, SW_SHOWNOACTIVATE); - - } else { - - /* Tip already exists, just set the text */ - - SetWindowText(tip_wnd, str); - } -} - -void EnableSizeTip(bool bEnable) -{ - if (tip_wnd && !bEnable) { - DestroyWindow(tip_wnd); - tip_wnd = NULL; - } - - tip_enabled = bEnable; -} diff --git a/WINDOWS/storage.c b/WINDOWS/storage.c deleted file mode 100644 index a1cab2382..000000000 --- a/WINDOWS/storage.c +++ /dev/null @@ -1,868 +0,0 @@ -/* - * storage.c: Windows-specific implementation of the interface - * defined in storage.h. - */ - -#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 *const host_ca_key = PUTTY_REG_POS "\\SshHostCAs"; - -static bool tried_shgetfolderpath = false; -static HMODULE shell32_module = NULL; -DECL_WINDOWS_FUNCTION(static, HRESULT, SHGetFolderPathA, - (HWND, int, HANDLE, DWORD, LPSTR)); - -struct settings_w { - HKEY sesskey; -}; - -settings_w *open_settings_w(const char *sessionname, char **errmsg) -{ - *errmsg = NULL; - - if (!sessionname || !*sessionname) - sessionname = "Default Settings"; - - strbuf *sb = strbuf_new(); - escape_registry_key(sessionname, sb); - - HKEY sesskey = create_regkey(HKEY_CURRENT_USER, puttystr, sb->s); - if (!sesskey) { - *errmsg = dupprintf("Unable to create registry key\n" - "HKEY_CURRENT_USER\\%s\\%s", puttystr, sb->s); - strbuf_free(sb); - return NULL; - } - strbuf_free(sb); - - settings_w *handle = snew(settings_w); - handle->sesskey = sesskey; - return handle; -} - -void write_setting_s(settings_w *handle, const char *key, const char *value) -{ - if (handle) - put_reg_sz(handle->sesskey, key, value); -} - -void write_setting_i(settings_w *handle, const char *key, int value) -{ - if (handle) - put_reg_dword(handle->sesskey, key, value); -} - -void close_settings_w(settings_w *handle) -{ - close_regkey(handle->sesskey); - sfree(handle); -} - -struct settings_r { - HKEY sesskey; -}; - -settings_r *open_settings_r(const char *sessionname) -{ - if (!sessionname || !*sessionname) - sessionname = "Default Settings"; - - strbuf *sb = strbuf_new(); - escape_registry_key(sessionname, sb); - HKEY sesskey = open_regkey_ro(HKEY_CURRENT_USER, puttystr, sb->s); - strbuf_free(sb); - - if (!sesskey) - return NULL; - - settings_r *handle = snew(settings_r); - handle->sesskey = sesskey; - return handle; -} - -char *read_setting_s(settings_r *handle, const char *key) -{ - if (!handle) - return NULL; - return get_reg_sz(handle->sesskey, key); -} - -int read_setting_i(settings_r *handle, const char *key, int defvalue) -{ - DWORD val; - if (!handle || !get_reg_dword(handle->sesskey, key, &val)) - return defvalue; - else - return val; -} - -FontSpec *read_setting_fontspec(settings_r *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"); - isbold = read_setting_i(handle, settingname, -1); - sfree(settingname); - if (isbold == -1) { - sfree(fontname); - return NULL; - } - - settingname = dupcat(name, "CharSet"); - charset = read_setting_i(handle, settingname, -1); - sfree(settingname); - if (charset == -1) { - sfree(fontname); - return NULL; - } - - settingname = dupcat(name, "Height"); - 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(settings_w *handle, - const char *name, FontSpec *font) -{ - char *settingname; - - write_setting_s(handle, name, font->name); - settingname = dupcat(name, "IsBold"); - write_setting_i(handle, settingname, font->isbold); - sfree(settingname); - settingname = dupcat(name, "CharSet"); - write_setting_i(handle, settingname, font->charset); - sfree(settingname); - settingname = dupcat(name, "Height"); - write_setting_i(handle, settingname, font->height); - sfree(settingname); -} - -Filename *read_setting_filename(settings_r *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(settings_w *handle, - const char *name, Filename *result) -{ - /* - * When saving a session involving a Filename, we use the 'cpath' - * member of the Filename structure, because otherwise we break - * backwards compatibility with existing saved sessions. - * - * This means that 'exotic' filenames - those including Unicode - * characters outside the host system's CP_ACP default code page - - * cannot be represented faithfully, and saving and reloading a - * Conf including one will break it. - * - * This can't be fixed without breaking backwards compatibility, - * and if we're going to break compatibility then we should break - * it good and hard (the Nanny Ogg principle), and devise a - * completely fresh storage representation that fixes as many - * other legacy problems as possible at the same time. - */ - write_setting_s(handle, name, result->cpath); /* FIXME */ -} - -void close_settings_r(settings_r *handle) -{ - if (handle) { - close_regkey(handle->sesskey); - sfree(handle); - } -} - -void del_settings(const char *sessionname) -{ - HKEY rkey = open_regkey_rw(HKEY_CURRENT_USER, puttystr); - if (!rkey) - return; - - strbuf *sb = strbuf_new(); - escape_registry_key(sessionname, sb); - del_regkey(rkey, sb->s); - strbuf_free(sb); - - close_regkey(rkey); - - remove_session_from_jumplist(sessionname); -} - -struct settings_e { - HKEY key; - int i; -}; - -settings_e *enum_settings_start(void) -{ - HKEY key = open_regkey_ro(HKEY_CURRENT_USER, puttystr); - if (!key) - return NULL; - - settings_e *e = snew(settings_e); - if (e) { - e->key = key; - e->i = 0; - } - - return e; -} - -bool enum_settings_next(settings_e *e, strbuf *sb) -{ - char *name = enum_regkey(e->key, e->i); - if (!name) - return false; - - unescape_registry_key(name, sb); - sfree(name); - e->i++; - return true; -} - -void enum_settings_finish(settings_e *e) -{ - close_regkey(e->key); - sfree(e); -} - -static void hostkey_regname(strbuf *sb, const char *hostname, - int port, const char *keytype) -{ - put_fmt(sb, "%s@%d:", keytype, port); - escape_registry_key(hostname, sb); -} - -int check_stored_host_key(const char *hostname, int port, - const char *keytype, const char *key) -{ - /* - * Read a saved key in from the registry and see what it says. - */ - strbuf *regname = strbuf_new(); - hostkey_regname(regname, hostname, port, keytype); - - HKEY rkey = open_regkey_ro(HKEY_CURRENT_USER, - PUTTY_REG_POS "\\SshHostKeys"); - if (!rkey) { - strbuf_free(regname); - return 1; /* key does not exist in registry */ - } - - char *otherstr = get_reg_sz(rkey, regname->s); - if (!otherstr && !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->s + 1 + strcspn(regname->s, ":"); - char *oldstyle = get_reg_sz(rkey, justhost); - - if (oldstyle) { - /* - * 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. - */ - strbuf *new = strbuf_new(); - const char *q = oldstyle; - int i, j; - - for (i = 0; i < 2; i++) { - int ndigits, nwords; - put_datapl(new, PTRLEN_LITERAL("0x")); - 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 = ndigits; j-- > 0 ;) - put_byte(new, q[j ^ 3]); - q += nwords * 4; - if (*q) { - q++; /* eat the slash */ - put_byte(new, ','); /* add a comma */ - } - } - - /* - * 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(new->s, key)) { - put_reg_sz(rkey, regname->s, new->s); - otherstr = strbuf_to_str(new); - } else { - strbuf_free(new); - } - } - - sfree(oldstyle); - } - - close_regkey(rkey); - - int compare = otherstr ? strcmp(otherstr, key) : -1; - - sfree(otherstr); - strbuf_free(regname); - - if (!otherstr) - return 1; /* key does not exist in registry */ - else if (compare) - return 2; /* key is different in registry */ - else - return 0; /* key matched OK in registry */ -} - -bool have_ssh_host_key(const char *hostname, int port, - const char *keytype) -{ - /* - * If we have a host key, check_stored_host_key will return 0 or 2. - * If we don't have one, it'll return 1. - */ - return check_stored_host_key(hostname, port, keytype, "") != 1; -} - -void store_host_key(Seat *seat, const char *hostname, int port, - const char *keytype, const char *key) -{ - strbuf *regname = strbuf_new(); - hostkey_regname(regname, hostname, port, keytype); - - HKEY rkey = create_regkey(HKEY_CURRENT_USER, - PUTTY_REG_POS "\\SshHostKeys"); - if (rkey) { - put_reg_sz(rkey, regname->s, key); - close_regkey(rkey); - } /* else key does not exist in registry */ - - strbuf_free(regname); -} - -struct host_ca_enum { - HKEY key; - int i; -}; - -host_ca_enum *enum_host_ca_start(void) -{ - host_ca_enum *e; - HKEY key; - - if (!(key = open_regkey_ro(HKEY_CURRENT_USER, host_ca_key))) - return NULL; - - e = snew(host_ca_enum); - e->key = key; - e->i = 0; - - return e; -} - -bool enum_host_ca_next(host_ca_enum *e, strbuf *sb) -{ - char *regbuf = enum_regkey(e->key, e->i); - if (!regbuf) - return false; - - unescape_registry_key(regbuf, sb); - sfree(regbuf); - e->i++; - return true; -} - -void enum_host_ca_finish(host_ca_enum *e) -{ - close_regkey(e->key); - sfree(e); -} - -host_ca *host_ca_load(const char *name) -{ - strbuf *sb; - const char *s; - - sb = strbuf_new(); - escape_registry_key(name, sb); - HKEY rkey = open_regkey_ro(HKEY_CURRENT_USER, host_ca_key, sb->s); - strbuf_free(sb); - - if (!rkey) - return NULL; - - host_ca *hca = host_ca_new(); - hca->name = dupstr(name); - - DWORD val; - - if ((s = get_reg_sz(rkey, "PublicKey")) != NULL) - hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(s)); - - if ((s = get_reg_sz(rkey, "Validity")) != NULL) { - hca->validity_expression = strbuf_to_str( - percent_decode_sb(ptrlen_from_asciz(s))); - } else if ((sb = get_reg_multi_sz(rkey, "MatchHosts")) != NULL) { - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(sb)); - CertExprBuilder *eb = cert_expr_builder_new(); - - const char *wc; - while (wc = get_asciz(src), !get_err(src)) - cert_expr_builder_add(eb, wc); - - hca->validity_expression = cert_expr_expression(eb); - cert_expr_builder_free(eb); - } - - if (get_reg_dword(rkey, "PermitRSASHA1", &val)) - hca->opts.permit_rsa_sha1 = val; - if (get_reg_dword(rkey, "PermitRSASHA256", &val)) - hca->opts.permit_rsa_sha256 = val; - if (get_reg_dword(rkey, "PermitRSASHA512", &val)) - hca->opts.permit_rsa_sha512 = val; - - close_regkey(rkey); - return hca; -} - -char *host_ca_save(host_ca *hca) -{ - if (!*hca->name) - return dupstr("CA record must have a name"); - - strbuf *sb = strbuf_new(); - escape_registry_key(hca->name, sb); - HKEY rkey = create_regkey(HKEY_CURRENT_USER, host_ca_key, sb->s); - if (!rkey) { - char *err = dupprintf("Unable to create registry key\n" - "HKEY_CURRENT_USER\\%s\\%s", host_ca_key, sb->s); - strbuf_free(sb); - return err; - } - strbuf_free(sb); - - strbuf *base64_pubkey = base64_encode_sb( - ptrlen_from_strbuf(hca->ca_public_key), 0); - put_reg_sz(rkey, "PublicKey", base64_pubkey->s); - strbuf_free(base64_pubkey); - - strbuf *validity = percent_encode_sb( - ptrlen_from_asciz(hca->validity_expression), NULL); - put_reg_sz(rkey, "Validity", validity->s); - strbuf_free(validity); - - put_reg_dword(rkey, "PermitRSASHA1", hca->opts.permit_rsa_sha1); - put_reg_dword(rkey, "PermitRSASHA256", hca->opts.permit_rsa_sha256); - put_reg_dword(rkey, "PermitRSASHA512", hca->opts.permit_rsa_sha512); - - close_regkey(rkey); - return NULL; -} - -char *host_ca_delete(const char *name) -{ - HKEY rkey = open_regkey_rw(HKEY_CURRENT_USER, host_ca_key); - if (!rkey) - return NULL; - - strbuf *sb = strbuf_new(); - escape_registry_key(name, sb); - del_regkey(rkey, sb->s); - strbuf_free(sb); - - return NULL; -} - -/* - * Open (or delete) the random seed file. - */ -enum { DEL, OPEN_R, OPEN_W }; -static bool 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 bool try_random_seed_and_free(char *path, int action, HANDLE *hout) -{ - bool retd = try_random_seed(path, action, hout); - sfree(path); - return retd; -} - -static HANDLE access_random_seed(int action) -{ - HANDLE rethandle; - - /* - * 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. - */ - { - HKEY rkey = open_regkey_ro(HKEY_CURRENT_USER, PUTTY_REG_POS); - if (rkey) { - char *regpath = get_reg_sz(rkey, "RandSeedFile"); - close_regkey(rkey); - if (regpath) { - bool success = try_random_seed(regpath, action, &rethandle); - sfree(regpath); - if (success) - 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) { - char profile[MAX_PATH + 1]; - if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, - NULL, SHGFP_TYPE_CURRENT, profile)) && - try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"), - action, &rethandle)) - return rethandle; - - if (SUCCEEDED(p_SHGetFolderPathA(NULL, CSIDL_APPDATA, - NULL, SHGFP_TYPE_CURRENT, profile)) && - try_random_seed_and_free(dupcat(profile, "\\PUTTY.RND"), - action, &rethandle)) - return rethandle; - } - - /* - * Failing that, try %HOMEDRIVE%%HOMEPATH% as a guess at the - * user's home directory. - */ - { - char drv[MAX_PATH], path[MAX_PATH]; - - DWORD drvlen = GetEnvironmentVariable("HOMEDRIVE", drv, sizeof(drv)); - DWORD pathlen = GetEnvironmentVariable("HOMEPATH", path, sizeof(path)); - - /* We permit %HOMEDRIVE% to expand to an empty string, but if - * %HOMEPATH% does that, we abort the attempt. Same if either - * variable overflows its buffer. */ - if (drvlen == 0) - drv[0] = '\0'; - - if (drvlen < lenof(drv) && pathlen < lenof(path) && pathlen > 0 && - try_random_seed_and_free( - dupcat(drv, path, "\\PUTTY.RND"), action, &rethandle)) - return rethandle; - } - - /* - * And finally, fall back to C:\WINDOWS. - */ - { - char windir[MAX_PATH]; - DWORD len = GetWindowsDirectory(windir, sizeof(windir)); - if (len < lenof(windir) && - try_random_seed_and_free( - dupcat(windir, "\\PUTTY.RND"), 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) -{ - HKEY rkey = create_regkey(HKEY_CURRENT_USER, reg_jumplist_key); - if (!rkey) - return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE; - - /* Get current list of saved sessions in the registry. */ - strbuf *oldlist = get_reg_multi_sz(rkey, reg_jumplist_value); - if (!oldlist) { - /* Start again with the empty list. */ - oldlist = strbuf_new(); - put_data(oldlist, "\0\0", 2); - } - - /* - * Modify the list, if we're modifying. - */ - bool write_failure = false; - if (add || rem) { - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(oldlist)); - strbuf *newlist = strbuf_new(); - - /* First add the new item to the beginning of the list. */ - if (add) - put_asciz(newlist, add); - - /* Now add the existing list, taking care to leave out the removed - * item, if it was already in the existing list. */ - while (true) { - const char *olditem = get_asciz(src); - if (get_err(src)) - break; - - if (!rem || strcmp(olditem, rem) != 0) { - /* Check if this is a valid session, otherwise don't add. */ - settings_r *psettings_tmp = open_settings_r(olditem); - if (psettings_tmp != NULL) { - close_settings_r(psettings_tmp); - put_asciz(newlist, olditem); - } - } - } - - /* Save the new list to the registry. */ - write_failure = !put_reg_multi_sz(rkey, reg_jumplist_value, newlist); - - strbuf_free(oldlist); - oldlist = newlist; - } - - close_regkey(rkey); - - if (out && !write_failure) - *out = strbuf_to_str(oldlist); - else - strbuf_free(oldlist); - - if (write_failure) - 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) -{ - char *name; - - DWORD i = 0; - while ((name = enum_regkey(key, i)) != NULL) { - HKEY subkey = open_regkey_rw(key, name); - if (subkey) { - registry_recursive_remove(subkey); - close_regkey(subkey); - } - del_regkey(key, name); - sfree(name); - } -} - -void cleanup_all(void) -{ - /* ------------------------------------------------------------ - * 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. - */ - HKEY key = open_regkey_rw(HKEY_CURRENT_USER, PUTTY_REG_POS); - if (key) { - registry_recursive_remove(key); - close_regkey(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 ((key = open_regkey_rw(HKEY_CURRENT_USER, PUTTY_REG_PARENT)) != NULL) { - del_regkey(key, PUTTY_REG_PARENT_CHILD); - char *name = enum_regkey(key, 0); - close_regkey(key); - - /* - * If the parent key had no other children, we must delete - * it in its turn. That means opening the _grandparent_ - * key. - */ - if (name) { - sfree(name); - } else { - if ((key = open_regkey_rw(HKEY_CURRENT_USER, - PUTTY_REG_GPARENT)) != NULL) { - del_regkey(key, PUTTY_REG_GPARENT_CHILD); - close_regkey(key); - } - } - } - /* - * Now we're done. - */ -} diff --git a/WINDOWS/test/test_screenshot.c b/WINDOWS/test/test_screenshot.c deleted file mode 100644 index 1e3a20d74..000000000 --- a/WINDOWS/test/test_screenshot.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "putty.h" - -static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "screenshot: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); - exit(1); -} - -void out_of_memory(void) { fatal_error("out of memory"); } - -int main(int argc, char **argv) -{ - const char *outfile = NULL; - - AuxMatchOpt amo = aux_match_opt_init(argc-1, argv+1, 0, fatal_error); - while (!aux_match_done(&amo)) { - char *val; - #define match_opt(...) aux_match_opt( \ - &amo, NULL, __VA_ARGS__, (const char *)NULL) - #define match_optval(...) aux_match_opt( \ - &amo, &val, __VA_ARGS__, (const char *)NULL) - - if (aux_match_arg(&amo, &val)) { - fatal_error("unexpected argument '%s'", val); - } else if (match_optval("-o", "--output")) { - outfile = val; - } else { - fatal_error("unrecognised option '%s'\n", amo.argv[amo.index]); - } - } - - if (!outfile) - fatal_error("expected an output file name"); - - char *err = save_screenshot(NULL, outfile); - if (err) - fatal_error("%s", err); - - return 0; -} diff --git a/WINDOWS/test/test_split_into_argv.c b/WINDOWS/test/test_split_into_argv.c deleted file mode 100644 index 237341542..000000000 --- a/WINDOWS/test/test_split_into_argv.c +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Test program for split_into_argv. - */ - -#include "putty.h" - -const struct argv_test { - const char *cmdline; - const char *argv[10]; - bool include_program_name; -} argv_tests[] = { - /* - * We generate this set of tests by invoking ourself with - * `-generate'. - */ -#if !MOD3 - /* Newer behaviour, with no weird mod-3 glitch. */ - {"ab c\" d", {"ab", "c d", NULL}}, - {"a\"b c\" d", {"ab c", "d", NULL}}, - {"a\"\"b c\" d", {"ab", "c d", NULL}}, - {"a\"\"\"b c\" d", {"a\"b c", "d", NULL}}, - {"a\"\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"a\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"a\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, - {"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"a\\b c\" d", {"a\\b", "c d", NULL}}, - {"a\\\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\\\"\"b c\" d", {"a\"b c", "d", NULL}}, - {"a\\\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\\\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"a\\\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, - {"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"\"b c", "d", NULL}}, - {"a\\\\b c\" d", {"a\\\\b", "c d", NULL}}, - {"a\\\\\"b c\" d", {"a\\b c", "d", NULL}}, - {"a\\\\\"\"b c\" d", {"a\\b", "c d", NULL}}, - {"a\\\\\"\"\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"a\\\\\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, - {"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"a\\\\\\b c\" d", {"a\\\\\\b", "c d", NULL}}, - {"a\\\\\\\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"a\\\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, - {"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"\"b c", "d", NULL}}, - {"a\\\\\\\\b c\" d", {"a\\\\\\\\b", "c d", NULL}}, - {"a\\\\\\\\\"b c\" d", {"a\\\\b c", "d", NULL}}, - {"a\\\\\\\\\"\"b c\" d", {"a\\\\b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, - {"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b c", "d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}}, - {"\"ab c\" d", {"ab c", "d", NULL}}, - {"\"a\"b c\" d", {"ab", "c d", NULL}}, - {"\"a\"\"b c\" d", {"a\"b c", "d", NULL}}, - {"\"a\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"\"a\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"\"a\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"\"a\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, - {"\"a\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"\"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"\"b c", "d", NULL}}, - {"\"a\\b c\" d", {"a\\b c", "d", NULL}}, - {"\"a\\\"b c\" d", {"a\"b c", "d", NULL}}, - {"\"a\\\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"\"a\\\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"\"a\\\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"\"a\\\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, - {"\"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"\"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"\"b c", "d", NULL}}, - {"\"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"\"b", "c d", NULL}}, - {"\"a\\\\b c\" d", {"a\\\\b c", "d", NULL}}, - {"\"a\\\\\"b c\" d", {"a\\b", "c d", NULL}}, - {"\"a\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"\"a\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"\"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, - {"\"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"\"b c", "d", NULL}}, - {"\"a\\\\\\b c\" d", {"a\\\\\\b c", "d", NULL}}, - {"\"a\\\\\\\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"\"b", "c d", NULL}}, - {"\"a\\\\\\\\b c\" d", {"a\\\\\\\\b c", "d", NULL}}, - {"\"a\\\\\\\\\"b c\" d", {"a\\\\b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, - {"\"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b c", "d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"\"b c", "d", NULL}}, -#else /* MOD3 */ - /* VS7 mod-3 behaviour. */ - {"ab c\" d", {"ab", "c d", NULL}}, - {"a\"b c\" d", {"ab c", "d", NULL}}, - {"a\"\"b c\" d", {"ab", "c d", NULL}}, - {"a\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\"\"\"\"b c\" d", {"a\"b c", "d", NULL}}, - {"a\"\"\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"a\\b c\" d", {"a\\b", "c d", NULL}}, - {"a\\\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\\\"\"b c\" d", {"a\"b c", "d", NULL}}, - {"a\\\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"a\\\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"a\\\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"a\\\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, - {"a\\\\b c\" d", {"a\\\\b", "c d", NULL}}, - {"a\\\\\"b c\" d", {"a\\b c", "d", NULL}}, - {"a\\\\\"\"b c\" d", {"a\\b", "c d", NULL}}, - {"a\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\"\"\"\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"a\\\\\"\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"a\\\\\\b c\" d", {"a\\\\\\b", "c d", NULL}}, - {"a\\\\\\\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\\\"\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"a\\\\\\\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, - {"a\\\\\\\\b c\" d", {"a\\\\\\\\b", "c d", NULL}}, - {"a\\\\\\\\\"b c\" d", {"a\\\\b c", "d", NULL}}, - {"a\\\\\\\\\"\"b c\" d", {"a\\\\b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, - {"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, - {"\"ab c\" d", {"ab c", "d", NULL}}, - {"\"a\"b c\" d", {"ab", "c d", NULL}}, - {"\"a\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"\"a\"\"\"b c\" d", {"a\"b c", "d", NULL}}, - {"\"a\"\"\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"\"a\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"\"a\"\"\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"\"a\"\"\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"\"a\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"\"a\\b c\" d", {"a\\b c", "d", NULL}}, - {"\"a\\\"b c\" d", {"a\"b c", "d", NULL}}, - {"\"a\\\"\"b c\" d", {"a\"b", "c d", NULL}}, - {"\"a\\\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"\"a\\\"\"\"\"b c\" d", {"a\"\"b c", "d", NULL}}, - {"\"a\\\"\"\"\"\"b c\" d", {"a\"\"b", "c d", NULL}}, - {"\"a\\\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"\"a\\\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b c", "d", NULL}}, - {"\"a\\\"\"\"\"\"\"\"\"b c\" d", {"a\"\"\"b", "c d", NULL}}, - {"\"a\\\\b c\" d", {"a\\\\b c", "d", NULL}}, - {"\"a\\\\\"b c\" d", {"a\\b", "c d", NULL}}, - {"\"a\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"\"a\\\\\"\"\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"\"a\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"\"a\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"\"a\\\\\\b c\" d", {"a\\\\\\b c", "d", NULL}}, - {"\"a\\\\\\\"b c\" d", {"a\\\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"b c\" d", {"a\\\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"\"b c\" d", {"a\\\"\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"b c\" d", {"a\\\"\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b c", "d", NULL}}, - {"\"a\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\"\"\"b", "c d", NULL}}, - {"\"a\\\\\\\\b c\" d", {"a\\\\\\\\b c", "d", NULL}}, - {"\"a\\\\\\\\\"b c\" d", {"a\\\\b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"b c\" d", {"a\\\\\"b c", "d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"b c\" d", {"a\\\\\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b c", "d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"b", "c d", NULL}}, - {"\"a\\\\\\\\\"\"\"\"\"\"\"\"b c\" d", {"a\\\\\"\"\"b", "c d", NULL}}, -#endif /* MOD3 */ - /* Common tests that check the special program-name rule. */ - {"\"a b\\\"c \"d e\" \"f g\"", {"a b\\c", "d e", "f g", NULL}, true}, - {"\"a b\\\"c \"d e\" \"f g\"", {"a b\"c d", "e f", "g", NULL}, false}, -}; - -void out_of_memory(void) -{ - fprintf(stderr, "out of memory!\n"); - exit(2); -} - -int main(int argc, char **argv) -{ - int i, j; - - if (argc > 1) { - /* - * Generation of tests. - * - * Given `-splat ', we print out a C-style - * representation of each argument (in the form "a", "b", - * NULL), backslash-escaping each backslash and double - * quote. - * - * Given `-split ', we first doctor `string' by - * turning forward slashes into backslashes, single quotes - * into double quotes and underscores into spaces; and then - * we feed the resulting string to ourself with `-splat'. - * - * Given `-generate', we concoct a variety of fun test - * cases, encode them in quote-safe form (mapping \, " and - * space to /, ' and _ respectively) and feed each one to - * `-split'. - */ - if (!strcmp(argv[1], "-splat")) { - int i; - char *p; - for (i = 2; i < argc; i++) { - putchar('"'); - for (p = argv[i]; *p; p++) { - if (*p == '\\' || *p == '"') - putchar('\\'); - putchar(*p); - } - printf("\", "); - } - printf("NULL"); - return 0; - } - - if (!strcmp(argv[1], "-split") && argc > 2) { - strbuf *cmdline = strbuf_new(); - char *p; - - put_fmt(cmdline, "%s -splat ", argv[0]); - printf(" {\""); - size_t args_start = cmdline->len; - for (p = argv[2]; *p; p++) { - char c = (*p == '/' ? '\\' : - *p == '\'' ? '"' : - *p == '_' ? ' ' : - *p); - put_byte(cmdline, c); - } - write_c_string_literal(stdout, ptrlen_from_asciz( - cmdline->s + args_start)); - printf("\", {"); - fflush(stdout); - - system(cmdline->s); - - printf("}},\n"); - - strbuf_free(cmdline); - return 0; - } - - if (!strcmp(argv[1], "-generate")) { - char *teststr, *p; - int i, initialquote, backslashes, quotes; - - teststr = malloc(200 + strlen(argv[0])); - - for (initialquote = 0; initialquote <= 1; initialquote++) { - for (backslashes = 0; backslashes < 5; backslashes++) { - for (quotes = 0; quotes < 9; quotes++) { - p = teststr + sprintf(teststr, "%s -split ", argv[0]); - if (initialquote) *p++ = '\''; - *p++ = 'a'; - for (i = 0; i < backslashes; i++) *p++ = '/'; - for (i = 0; i < quotes; i++) *p++ = '\''; - *p++ = 'b'; - *p++ = '_'; - *p++ = 'c'; - *p++ = '\''; - *p++ = '_'; - *p++ = 'd'; - *p = '\0'; - - system(teststr); - } - } - } - return 0; - } - - if (!strcmp(argv[1], "-tabulate")) { - char table[] = "\ - * backslashes \n\ - * \n\ - * 0 1 2 3 4 \n\ - * \n\ - * 0 | \n\ - * --------+----------------------------- \n\ - * 1 | \n\ - * q 2 | \n\ - * u 3 | \n\ - * o 4 | \n\ - * t 5 | \n\ - * e 6 | \n\ - * s 7 | \n\ - * 8 | \n\ -"; - char *linestarts[14]; - char *p = table; - for (i = 0; i < lenof(linestarts); i++) { - linestarts[i] = p; - p += strcspn(p, "\n"); - if (*p) p++; - } - - for (i = 0; i < lenof(argv_tests); i++) { - const struct argv_test *test = &argv_tests[i]; - const char *q = test->cmdline; - - /* Skip tests that aren't telling us something about - * the behaviour _inside_ a quoted string */ - if (*q != '"') - continue; - - q++; - - assert(*q == 'a'); - q++; - int backslashes_in = 0, quotes_in = 0; - while (*q == '\\') { - q++; - backslashes_in++; - } - while (*q == '"') { - q++; - quotes_in++; - } - - q = test->argv[0]; - assert(*q == 'a'); - q++; - int backslashes_out = 0, quotes_out = 0; - while (*q == '\\') { - q++; - backslashes_out++; - } - while (*q == '"') { - q++; - quotes_out++; - } - assert(*q == 'b'); - q++; - bool in_quoted_string = (*q == ' '); - - int x = (backslashes_in == 0 ? 15 : 18 + 7 * backslashes_in); - int y = (quotes_in == 0 ? 4 : 5 + quotes_in); - char *buf = dupprintf("%d,%d,%c", - backslashes_out, quotes_out, - in_quoted_string ? 'y' : 'n'); - memcpy(linestarts[y] + x, buf, strlen(buf)); - sfree(buf); - } - - fputs(table, stdout); - return 0; - } - - fprintf(stderr, "unrecognised option: \"%s\"\n", argv[1]); - return 1; - } - - /* - * If we get here, we were invoked with no arguments, so just - * run the tests. - */ - int passes = 0, fails = 0; - - for (i = 0; i < lenof(argv_tests); i++) { - int ac; - char **av; - bool failed = false; - - split_into_argv((char *)argv_tests[i].cmdline, - argv_tests[i].include_program_name, &ac, &av, NULL); - - for (j = 0; j < ac && argv_tests[i].argv[j]; j++) { - if (strcmp(av[j], argv_tests[i].argv[j])) { - printf("failed test %d (|%s|) arg %d: |%s| should be |%s|\n", - i, argv_tests[i].cmdline, - j, av[j], argv_tests[i].argv[j]); - failed = true; - } -#ifdef VERBOSE - else { - printf("test %d (|%s|) arg %d: |%s| == |%s|\n", - i, argv_tests[i].cmdline, - j, av[j], argv_tests[i].argv[j]); - } -#endif - } - if (j < ac) { - printf("failed test %d (|%s|): %d args returned, should be %d\n", - i, argv_tests[i].cmdline, ac, j); - failed = true; - } - if (argv_tests[i].argv[j]) { - printf("failed test %d (|%s|): %d args returned, should be more\n", - i, argv_tests[i].cmdline, ac); - failed = true; - } - - if (failed) - fails++; - else - passes++; - } - - printf("passed %d failed %d (%s mode)\n", - passes, fails, -#if MOD3 - "mod 3" -#else - "mod 2" -#endif - ); - - return fails != 0; -} diff --git a/WINDOWS/test_screenshot.c b/WINDOWS/test_screenshot.c deleted file mode 100644 index 1e3a20d74..000000000 --- a/WINDOWS/test_screenshot.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "putty.h" - -static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "screenshot: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); - exit(1); -} - -void out_of_memory(void) { fatal_error("out of memory"); } - -int main(int argc, char **argv) -{ - const char *outfile = NULL; - - AuxMatchOpt amo = aux_match_opt_init(argc-1, argv+1, 0, fatal_error); - while (!aux_match_done(&amo)) { - char *val; - #define match_opt(...) aux_match_opt( \ - &amo, NULL, __VA_ARGS__, (const char *)NULL) - #define match_optval(...) aux_match_opt( \ - &amo, &val, __VA_ARGS__, (const char *)NULL) - - if (aux_match_arg(&amo, &val)) { - fatal_error("unexpected argument '%s'", val); - } else if (match_optval("-o", "--output")) { - outfile = val; - } else { - fatal_error("unrecognised option '%s'\n", amo.argv[amo.index]); - } - } - - if (!outfile) - fatal_error("expected an output file name"); - - char *err = save_screenshot(NULL, outfile); - if (err) - fatal_error("%s", err); - - return 0; -} diff --git a/WINDOWS/unicode.c b/WINDOWS/unicode.c deleted file mode 100644 index 412db6ea0..000000000 --- a/WINDOWS/unicode.c +++ /dev/null @@ -1,1398 +0,0 @@ -#include -#include -#include -#include -#include - -#include "putty.h" -#include "terminal.h" -#include "misc.h" - -/* Character conversion arrays; they are usually taken from windows, - * the xterm one has the four scanlines that have no unicode 2.0 - * equivalents mapped to their unicode 3.0 locations. - */ -static const WCHAR 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 -}; - -/* - * If the codepage is non-zero it's a window codepage, zero means use a - * local codepage. The name is always converted to the first of any - * duplicate definitions. - */ - -/* - * Tables for ISO-8859-{1-10,13-16} derived from those downloaded - * 2001-10-02 from -- jtn - * Table for ISO-8859-11 derived from same on 2002-11-18. -- bjh21 - */ - -/* XXX: This could be done algorithmically, but I'm not sure it's - * worth the hassle -- jtn */ -/* ISO/IEC 8859-1:1998 (Latin-1, "Western", "West European") */ -static const wchar_t iso_8859_1[] = { - 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 -}; - -/* ISO/IEC 8859-2:1999 (Latin-2, "Central European", "East European") */ -static const wchar_t iso_8859_2[] = { - 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 -}; - -/* ISO/IEC 8859-3:1999 (Latin-3, "South European", "Maltese & Esperanto") */ -static const wchar_t iso_8859_3[] = { - 0x00A0, 0x0126, 0x02D8, 0x00A3, 0x00A4, 0xFFFD, 0x0124, 0x00A7, - 0x00A8, 0x0130, 0x015E, 0x011E, 0x0134, 0x00AD, 0xFFFD, 0x017B, - 0x00B0, 0x0127, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x0125, 0x00B7, - 0x00B8, 0x0131, 0x015F, 0x011F, 0x0135, 0x00BD, 0xFFFD, 0x017C, - 0x00C0, 0x00C1, 0x00C2, 0xFFFD, 0x00C4, 0x010A, 0x0108, 0x00C7, - 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, - 0xFFFD, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x0120, 0x00D6, 0x00D7, - 0x011C, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x016C, 0x015C, 0x00DF, - 0x00E0, 0x00E1, 0x00E2, 0xFFFD, 0x00E4, 0x010B, 0x0109, 0x00E7, - 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, - 0xFFFD, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x0121, 0x00F6, 0x00F7, - 0x011D, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x016D, 0x015D, 0x02D9 -}; - -/* ISO/IEC 8859-4:1998 (Latin-4, "North European") */ -static const wchar_t iso_8859_4[] = { - 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 -}; - -/* ISO/IEC 8859-5:1999 (Latin/Cyrillic) */ -static const wchar_t iso_8859_5[] = { - 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 -}; - -/* ISO/IEC 8859-6:1999 (Latin/Arabic) */ -static const wchar_t iso_8859_6[] = { - 0x00A0, 0xFFFD, 0xFFFD, 0xFFFD, 0x00A4, 0xFFFD, 0xFFFD, 0xFFFD, - 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0x060C, 0x00AD, 0xFFFD, 0xFFFD, - 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, - 0xFFFD, 0xFFFD, 0xFFFD, 0x061B, 0xFFFD, 0xFFFD, 0xFFFD, 0x061F, - 0xFFFD, 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, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, - 0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, - 0x0648, 0x0649, 0x064A, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, - 0x0650, 0x0651, 0x0652, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, - 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD -}; - -/* ISO 8859-7:1987 (Latin/Greek) */ -static const wchar_t iso_8859_7[] = { - 0x00A0, 0x2018, 0x2019, 0x00A3, 0xFFFD, 0xFFFD, 0x00A6, 0x00A7, - 0x00A8, 0x00A9, 0xFFFD, 0x00AB, 0x00AC, 0x00AD, 0xFFFD, 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, 0xFFFD, 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, 0xFFFD -}; - -/* ISO/IEC 8859-8:1999 (Latin/Hebrew) */ -static const wchar_t iso_8859_8[] = { - 0x00A0, 0xFFFD, 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, 0xFFFD, - 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, - 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, - 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, - 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 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, 0xFFFD, 0xFFFD, 0x200E, 0x200F, 0xFFFD -}; - -/* ISO/IEC 8859-9:1999 (Latin-5, "Turkish") */ -static const wchar_t iso_8859_9[] = { - 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 -}; - -/* ISO/IEC 8859-10:1998 (Latin-6, "Nordic" [Sami, Inuit, Icelandic]) */ -static const wchar_t iso_8859_10[] = { - 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 -}; - -/* ISO/IEC 8859-11:2001 ("Thai", "TIS620") */ -static const wchar_t iso_8859_11[] = { - 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, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 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, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD -}; - -/* ISO/IEC 8859-13:1998 (Latin-7, "Baltic Rim") */ -static const wchar_t iso_8859_13[] = { - 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 -}; - -/* ISO/IEC 8859-14:1998 (Latin-8, "Celtic", "Gaelic/Welsh") */ -static const wchar_t iso_8859_14[] = { - 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 -}; - -/* ISO/IEC 8859-15:1999 (Latin-9 aka -0, "euro") */ -static const wchar_t iso_8859_15[] = { - 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 -}; - -/* ISO/IEC 8859-16:2001 (Latin-10, "Balkan") */ -static const wchar_t iso_8859_16[] = { - 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 -}; - -static const wchar_t roman8[] = { - 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, 0xFFFD -}; - -static const wchar_t koi8_u[] = { - 0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524, - 0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590, - 0x2591, 0x2592, 0x2593, 0x2320, 0x25A0, 0x2022, 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 -}; - -static const wchar_t vscii[] = { - 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 -}; - -static const wchar_t dec_mcs[] = { - 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0xFFFD, 0x00A5, 0xFFFD, 0x00A7, - 0x00A4, 0x00A9, 0x00AA, 0x00AB, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, - 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0xFFFD, 0x00B5, 0x00B6, 0x00B7, - 0xFFFD, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0xFFFD, 0x00BF, - 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, - 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, - 0xFFFD, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x0152, - 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0178, 0xFFFD, 0x00DF, - 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, - 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, - 0xFFFD, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x0153, - 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FF, 0xFFFD, 0xFFFD -}; - -/* Mazovia (Polish) aka CP620 - * from "Mazowia to Unicode table", 04/24/96, Mikolaj Jedrzejak */ -static const wchar_t mazovia[] = { - /* Code point 0x9B is "zloty" symbol (zŽ), which is not - * widely used and for which there is no Unicode equivalent. - * One reference shows 0xA8 as U+00A7 SECTION SIGN, but we're - * told that's incorrect. */ - 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x0105, 0x00E7, - 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0107, 0x00C4, 0x0104, - 0x0118, 0x0119, 0x0142, 0x00F4, 0x00F6, 0x0106, 0x00FB, 0x00F9, - 0x015a, 0x00D6, 0x00DC, 0xFFFD, 0x0141, 0x00A5, 0x015b, 0x0192, - 0x0179, 0x017b, 0x00F3, 0x00d3, 0x0144, 0x0143, 0x017a, 0x017c, - 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 -}; - -struct cp_list_item { - char *name; - int codepage; - int cp_size; - const wchar_t *cp_table; -}; - -static const struct cp_list_item cp_list[] = { - {"UTF-8", CP_UTF8}, - - {"ISO-8859-1:1998 (Latin-1, West Europe)", 0, 96, iso_8859_1}, - {"ISO-8859-2:1999 (Latin-2, East Europe)", 0, 96, iso_8859_2}, - {"ISO-8859-3:1999 (Latin-3, South Europe)", 0, 96, iso_8859_3}, - {"ISO-8859-4:1998 (Latin-4, North Europe)", 0, 96, iso_8859_4}, - {"ISO-8859-5:1999 (Latin/Cyrillic)", 0, 96, iso_8859_5}, - {"ISO-8859-6:1999 (Latin/Arabic)", 0, 96, iso_8859_6}, - {"ISO-8859-7:1987 (Latin/Greek)", 0, 96, iso_8859_7}, - {"ISO-8859-8:1999 (Latin/Hebrew)", 0, 96, iso_8859_8}, - {"ISO-8859-9:1999 (Latin-5, Turkish)", 0, 96, iso_8859_9}, - {"ISO-8859-10:1998 (Latin-6, Nordic)", 0, 96, iso_8859_10}, - {"ISO-8859-11:2001 (Latin/Thai)", 0, 96, iso_8859_11}, - {"ISO-8859-13:1998 (Latin-7, Baltic)", 0, 96, iso_8859_13}, - {"ISO-8859-14:1998 (Latin-8, Celtic)", 0, 96, iso_8859_14}, - {"ISO-8859-15:1999 (Latin-9, \"euro\")", 0, 96, iso_8859_15}, - {"ISO-8859-16:2001 (Latin-10, Balkan)", 0, 96, iso_8859_16}, - - {"KOI8-U", 0, 128, koi8_u}, - {"KOI8-R", 20866}, - {"HP-ROMAN8", 0, 96, roman8}, - {"VSCII", 0, 256, vscii}, - {"DEC-MCS", 0, 96, dec_mcs}, - - {"Win1250 (Central European)", 1250}, - {"Win1251 (Cyrillic)", 1251}, - {"Win1252 (Western)", 1252}, - {"Win1253 (Greek)", 1253}, - {"Win1254 (Turkish)", 1254}, - {"Win1255 (Hebrew)", 1255}, - {"Win1256 (Arabic)", 1256}, - {"Win1257 (Baltic)", 1257}, - {"Win1258 (Vietnamese)", 1258}, - - {"CP437", 437}, - {"CP620 (Mazovia)", 0, 128, mazovia}, - {"CP819", 28591}, - {"CP852", 852}, - {"CP878", 20866}, - - {"Use font encoding", -1}, - - {0, 0} -}; - -static void link_font(WCHAR *line_tbl, WCHAR *font_tbl, WCHAR attr); - -/* - * We keep a collection of reverse mappings from Unicode back to code pages, - * in the form of array[256] of array[256] of char. These live forever in a - * local tree234, and we just make a new one whenever we find a need. - */ -typedef struct reverse_mapping { - int codepage; - char **blocks; -} reverse_mapping; -static tree234 *reverse_mappings = NULL; - -static int reverse_mapping_cmp(void *av, void *bv) -{ - const reverse_mapping *a = (const reverse_mapping *)av; - const reverse_mapping *b = (const reverse_mapping *)bv; - if (a->codepage < b->codepage) - return -1; - if (a->codepage > b->codepage) - return +1; - return 0; -} - -static int reverse_mapping_find(void *av, void *bv) -{ - const reverse_mapping *a = (const reverse_mapping *)av; - int b_codepage = *(const int *)bv; - if (a->codepage < b_codepage) - return -1; - if (a->codepage > b_codepage) - return +1; - return 0; -} - -static reverse_mapping *get_existing_reverse_mapping(int codepage) -{ - if (!reverse_mappings) - return NULL; - return find234(reverse_mappings, &codepage, reverse_mapping_find); -} - -static reverse_mapping *make_reverse_mapping_inner( - int codepage, const wchar_t *mapping) -{ - if (!reverse_mappings) - reverse_mappings = newtree234(reverse_mapping_cmp); - - reverse_mapping *rmap = snew(reverse_mapping); - rmap->blocks = snewn(256, char *); - memset(rmap->blocks, 0, 256 * sizeof(char *)); - - for (size_t i = 0; i < 256; i++) { - /* These special kinds of value correspond to no Unicode character */ - if (DIRECT_CHAR(mapping[i])) - continue; - if (DIRECT_FONT(mapping[i])) - continue; - - size_t chr = mapping[i]; - size_t block = chr >> 8, index = chr & 0xFF; - - if (!rmap->blocks[block]) { - rmap->blocks[block] = snewn(256, char); - memset(rmap->blocks[block], 0, 256); - } - rmap->blocks[block][index] = i; - } - - rmap->codepage = codepage; - reverse_mapping *added = add234(reverse_mappings, rmap); - assert(added == rmap); /* we already checked it wasn't already in there */ - return added; -} - -static void make_reverse_mapping(int codepage, const wchar_t *mapping) -{ - if (get_existing_reverse_mapping(codepage)) - return; /* we've already got this one */ - make_reverse_mapping_inner(codepage, mapping); -} - -static reverse_mapping *get_reverse_mapping(int codepage) -{ - /* - * Try harder to get a reverse mapping for a codepage we implement - * internally via a translation table, by hastily making it if it doesn't - * already exist. - */ - - reverse_mapping *rmap = get_existing_reverse_mapping(codepage); - if (rmap) - return rmap; - - if (codepage < 65536) - return NULL; - if (codepage >= 65536 + lenof(cp_list)) - return NULL; - const struct cp_list_item *cp = &cp_list[codepage - 65536]; - if (!cp->cp_table) - return NULL; - - wchar_t mapping[256]; - get_unitab(codepage, mapping, 0); - return make_reverse_mapping_inner(codepage, mapping); -} - -void init_ucs(Conf *conf, struct unicode_data *ucsdata) -{ - int i; - bool used_dtf = false; - int vtmode; - - /* Decide on the Line and Font codepages */ - ucsdata->line_codepage = decode_codepage(conf_get_str(conf, - CONF_line_codepage)); - - if (ucsdata->font_codepage <= 0) { - ucsdata->font_codepage=0; - ucsdata->dbcs_screenfont=false; - } - - vtmode = conf_get_int(conf, CONF_vtmode); - if (vtmode == VT_OEMONLY) { - ucsdata->font_codepage = 437; - ucsdata->dbcs_screenfont = false; - if (ucsdata->line_codepage <= 0) - ucsdata->line_codepage = GetACP(); - } else if (ucsdata->line_codepage <= 0) - ucsdata->line_codepage = ucsdata->font_codepage; - - /* Collect screen font ucs table */ - if (ucsdata->dbcs_screenfont || ucsdata->font_codepage == 0) { - get_unitab(ucsdata->font_codepage, ucsdata->unitab_font, 2); - for (i = 128; i < 256; i++) - ucsdata->unitab_font[i] = (WCHAR) (CSET_ACP + i); - } else { - get_unitab(ucsdata->font_codepage, ucsdata->unitab_font, 1); - - /* CP437 fonts are often broken ... */ - if (ucsdata->font_codepage == 437) - ucsdata->unitab_font[0] = ucsdata->unitab_font[255] = 0xFFFF; - } - if (vtmode == VT_XWINDOWS) - memcpy(ucsdata->unitab_font + 1, unitab_xterm_std, - sizeof(unitab_xterm_std)); - - /* Collect OEMCP ucs table */ - get_unitab(CP_OEMCP, ucsdata->unitab_oemcp, 1); - - /* Collect CP437 ucs table for SCO acs */ - if (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) - memcpy(ucsdata->unitab_scoacs, ucsdata->unitab_oemcp, - sizeof(ucsdata->unitab_scoacs)); - else - get_unitab(437, ucsdata->unitab_scoacs, 1); - - /* Collect line set ucs table */ - if (ucsdata->line_codepage == ucsdata->font_codepage && - (ucsdata->dbcs_screenfont || - vtmode == VT_POORMAN || ucsdata->font_codepage==0)) { - - /* For DBCS and POOR fonts force direct to font */ - used_dtf = true; - for (i = 0; i < 32; i++) - ucsdata->unitab_line[i] = (WCHAR) i; - for (i = 32; i < 256; i++) - ucsdata->unitab_line[i] = (WCHAR) (CSET_ACP + i); - ucsdata->unitab_line[127] = (WCHAR) 127; - } else { - get_unitab(ucsdata->line_codepage, ucsdata->unitab_line, 0); - } - -#if 0 - debug("Line cp%d, Font cp%d%s\n", ucsdata->line_codepage, - ucsdata->font_codepage, ucsdata->dbcs_screenfont ? " DBCS" : ""); - - for (i = 0; i < 256; i += 16) { - for (j = 0; j < 16; j++) { - debug("%04x%s", ucsdata->unitab_line[i + j], j == 15 ? "" : ","); - } - debug("\n"); - } -#endif - - /* VT100 graphics - NB: Broken for non-ascii CP's */ - memcpy(ucsdata->unitab_xterm, ucsdata->unitab_line, - sizeof(ucsdata->unitab_xterm)); - memcpy(ucsdata->unitab_xterm + '`', unitab_xterm_std, - sizeof(unitab_xterm_std)); - ucsdata->unitab_xterm['_'] = ' '; - - if (!used_dtf) { - /* Make sure a reverse mapping exists for this code page. */ - make_reverse_mapping(ucsdata->line_codepage, ucsdata->unitab_line); - } - - /* Find the line control characters. */ - for (i = 0; i < 256; i++) - if (ucsdata->unitab_line[i] < ' ' - || (ucsdata->unitab_line[i] >= 0x7F && - ucsdata->unitab_line[i] < 0xA0)) - ucsdata->unitab_ctrl[i] = i; - else - ucsdata->unitab_ctrl[i] = 0xFF; - - /* Generate line->screen direct conversion links. */ - if (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) - link_font(ucsdata->unitab_scoacs, ucsdata->unitab_oemcp, CSET_OEMCP); - - link_font(ucsdata->unitab_line, ucsdata->unitab_font, CSET_ACP); - link_font(ucsdata->unitab_scoacs, ucsdata->unitab_font, CSET_ACP); - link_font(ucsdata->unitab_xterm, ucsdata->unitab_font, CSET_ACP); - - if (vtmode == VT_OEMANSI || vtmode == VT_XWINDOWS) { - link_font(ucsdata->unitab_line, ucsdata->unitab_oemcp, CSET_OEMCP); - link_font(ucsdata->unitab_xterm, ucsdata->unitab_oemcp, CSET_OEMCP); - } - - if (ucsdata->dbcs_screenfont && - ucsdata->font_codepage != ucsdata->line_codepage) { - /* F***ing Microsoft fonts, Japanese and Korean codepage fonts - * have a currency symbol at 0x5C but their unicode value is - * still given as U+005C not the correct U+00A5. */ - ucsdata->unitab_line['\\'] = CSET_OEMCP + '\\'; - } - - /* Last chance, if !unicode then try poorman links. */ - if (vtmode != VT_UNICODE) { - static const char poorman_scoacs[] = - "CueaaaaceeeiiiAAE**ooouuyOUc$YPsaiounNao?++**!<>###||||++||++++++--|-+||++--|-+----++++++++##||#aBTPEsyt******EN=+><++-=... n2* "; - static const char poorman_latin1[] = - " !cL.Y|S\"Ca<--R~o+23'u|.,1o>///?AAAAAAACEEEEIIIIDNOOOOOxOUUUUYPBaaaaaaaceeeeiiiionooooo/ouuuuypy"; - static const char poorman_vt100[] = "*#****o~**+++++-----++++|****L."; - - for (i = 160; i < 256; i++) - if (!DIRECT_FONT(ucsdata->unitab_line[i]) && - ucsdata->unitab_line[i] >= 160 && - ucsdata->unitab_line[i] < 256) { - ucsdata->unitab_line[i] = - (WCHAR) (CSET_ACP + - poorman_latin1[ucsdata->unitab_line[i] - 160]); - } - for (i = 96; i < 127; i++) - if (!DIRECT_FONT(ucsdata->unitab_xterm[i])) - ucsdata->unitab_xterm[i] = - (WCHAR) (CSET_ACP + poorman_vt100[i - 96]); - for (i = 128; i < 256; i++) - if (!DIRECT_FONT(ucsdata->unitab_scoacs[i])) - ucsdata->unitab_scoacs[i] = - (WCHAR) (CSET_ACP + poorman_scoacs[i - 128]); - } -} - -void init_ucs_generic(Conf *conf, struct unicode_data *ucsdata) -{ - init_ucs(conf, ucsdata); -} - -static void link_font(WCHAR *line_tbl, WCHAR *font_tbl, WCHAR attr) -{ - int font_index, line_index, i; - for (line_index = 0; line_index < 256; line_index++) { - if (DIRECT_FONT(line_tbl[line_index])) - continue; - for (i = 0; i < 256; i++) { - font_index = ((32 + i) & 0xFF); - if (line_tbl[line_index] == font_tbl[font_index]) { - line_tbl[line_index] = (WCHAR) (attr + font_index); - break; - } - } - } -} - -wchar_t xlat_uskbd2cyrllic(int ch) -{ - static const wchar_t cyrtab[] = { - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 0x042d, 35, 36, 37, 38, 0x044d, - 40, 41, 42, 0x0406, 0x0431, 0x0454, 0x044e, 0x002e, - 48, 49, 50, 51, 52, 53, 54, 55, - 56, 57, 0x0416, 0x0436, 0x0411, 0x0456, 0x042e, 0x002c, - 64, 0x0424, 0x0418, 0x0421, 0x0412, 0x0423, 0x0410, 0x041f, - 0x0420, 0x0428, 0x041e, 0x041b, 0x0414, 0x042c, 0x0422, 0x0429, - 0x0417, 0x0419, 0x041a, 0x042b, 0x0415, 0x0413, 0x041c, 0x0426, - 0x0427, 0x041d, 0x042f, 0x0445, 0x0457, 0x044a, 94, 0x0404, - 96, 0x0444, 0x0438, 0x0441, 0x0432, 0x0443, 0x0430, 0x043f, - 0x0440, 0x0448, 0x043e, 0x043b, 0x0434, 0x044c, 0x0442, 0x0449, - 0x0437, 0x0439, 0x043a, 0x044b, 0x0435, 0x0433, 0x043c, 0x0446, - 0x0447, 0x043d, 0x044f, 0x0425, 0x0407, 0x042a, 126, 127 - }; - return cyrtab[ch&0x7F]; -} - -static int check_compose_internal(int first, int second, int recurse) -{ - - static const struct { - char first, second; - wchar_t composed; - } composetbl[] = { - {0x2b, 0x2b, 0x0023}, - {0x41, 0x41, 0x0040}, - {0x28, 0x28, 0x005b}, - {0x2f, 0x2f, 0x005c}, - {0x29, 0x29, 0x005d}, - {0x28, 0x2d, 0x007b}, - {0x2d, 0x29, 0x007d}, - {0x2f, 0x5e, 0x007c}, - {0x21, 0x21, 0x00a1}, - {0x43, 0x2f, 0x00a2}, - {0x43, 0x7c, 0x00a2}, - {0x4c, 0x2d, 0x00a3}, - {0x4c, 0x3d, 0x20a4}, - {0x58, 0x4f, 0x00a4}, - {0x58, 0x30, 0x00a4}, - {0x59, 0x2d, 0x00a5}, - {0x59, 0x3d, 0x00a5}, - {0x7c, 0x7c, 0x00a6}, - {0x53, 0x4f, 0x00a7}, - {0x53, 0x21, 0x00a7}, - {0x53, 0x30, 0x00a7}, - {0x22, 0x22, 0x00a8}, - {0x43, 0x4f, 0x00a9}, - {0x43, 0x30, 0x00a9}, - {0x41, 0x5f, 0x00aa}, - {0x3c, 0x3c, 0x00ab}, - {0x2c, 0x2d, 0x00ac}, - {0x2d, 0x2d, 0x00ad}, - {0x52, 0x4f, 0x00ae}, - {0x2d, 0x5e, 0x00af}, - {0x30, 0x5e, 0x00b0}, - {0x2b, 0x2d, 0x00b1}, - {0x32, 0x5e, 0x00b2}, - {0x33, 0x5e, 0x00b3}, - {0x27, 0x27, 0x00b4}, - {0x2f, 0x55, 0x00b5}, - {0x50, 0x21, 0x00b6}, - {0x2e, 0x5e, 0x00b7}, - {0x2c, 0x2c, 0x00b8}, - {0x31, 0x5e, 0x00b9}, - {0x4f, 0x5f, 0x00ba}, - {0x3e, 0x3e, 0x00bb}, - {0x31, 0x34, 0x00bc}, - {0x31, 0x32, 0x00bd}, - {0x33, 0x34, 0x00be}, - {0x3f, 0x3f, 0x00bf}, - {0x60, 0x41, 0x00c0}, - {0x27, 0x41, 0x00c1}, - {0x5e, 0x41, 0x00c2}, - {0x7e, 0x41, 0x00c3}, - {0x22, 0x41, 0x00c4}, - {0x2a, 0x41, 0x00c5}, - {0x41, 0x45, 0x00c6}, - {0x2c, 0x43, 0x00c7}, - {0x60, 0x45, 0x00c8}, - {0x27, 0x45, 0x00c9}, - {0x5e, 0x45, 0x00ca}, - {0x22, 0x45, 0x00cb}, - {0x60, 0x49, 0x00cc}, - {0x27, 0x49, 0x00cd}, - {0x5e, 0x49, 0x00ce}, - {0x22, 0x49, 0x00cf}, - {0x2d, 0x44, 0x00d0}, - {0x7e, 0x4e, 0x00d1}, - {0x60, 0x4f, 0x00d2}, - {0x27, 0x4f, 0x00d3}, - {0x5e, 0x4f, 0x00d4}, - {0x7e, 0x4f, 0x00d5}, - {0x22, 0x4f, 0x00d6}, - {0x58, 0x58, 0x00d7}, - {0x2f, 0x4f, 0x00d8}, - {0x60, 0x55, 0x00d9}, - {0x27, 0x55, 0x00da}, - {0x5e, 0x55, 0x00db}, - {0x22, 0x55, 0x00dc}, - {0x27, 0x59, 0x00dd}, - {0x48, 0x54, 0x00de}, - {0x73, 0x73, 0x00df}, - {0x60, 0x61, 0x00e0}, - {0x27, 0x61, 0x00e1}, - {0x5e, 0x61, 0x00e2}, - {0x7e, 0x61, 0x00e3}, - {0x22, 0x61, 0x00e4}, - {0x2a, 0x61, 0x00e5}, - {0x61, 0x65, 0x00e6}, - {0x2c, 0x63, 0x00e7}, - {0x60, 0x65, 0x00e8}, - {0x27, 0x65, 0x00e9}, - {0x5e, 0x65, 0x00ea}, - {0x22, 0x65, 0x00eb}, - {0x60, 0x69, 0x00ec}, - {0x27, 0x69, 0x00ed}, - {0x5e, 0x69, 0x00ee}, - {0x22, 0x69, 0x00ef}, - {0x2d, 0x64, 0x00f0}, - {0x7e, 0x6e, 0x00f1}, - {0x60, 0x6f, 0x00f2}, - {0x27, 0x6f, 0x00f3}, - {0x5e, 0x6f, 0x00f4}, - {0x7e, 0x6f, 0x00f5}, - {0x22, 0x6f, 0x00f6}, - {0x3a, 0x2d, 0x00f7}, - {0x6f, 0x2f, 0x00f8}, - {0x60, 0x75, 0x00f9}, - {0x27, 0x75, 0x00fa}, - {0x5e, 0x75, 0x00fb}, - {0x22, 0x75, 0x00fc}, - {0x27, 0x79, 0x00fd}, - {0x68, 0x74, 0x00fe}, - {0x22, 0x79, 0x00ff}, - /* Unicode extras. */ - {0x6f, 0x65, 0x0153}, - {0x4f, 0x45, 0x0152}, - /* Compose pairs from UCS */ - {0x41, 0x2D, 0x0100}, - {0x61, 0x2D, 0x0101}, - {0x43, 0x27, 0x0106}, - {0x63, 0x27, 0x0107}, - {0x43, 0x5E, 0x0108}, - {0x63, 0x5E, 0x0109}, - {0x45, 0x2D, 0x0112}, - {0x65, 0x2D, 0x0113}, - {0x47, 0x5E, 0x011C}, - {0x67, 0x5E, 0x011D}, - {0x47, 0x2C, 0x0122}, - {0x67, 0x2C, 0x0123}, - {0x48, 0x5E, 0x0124}, - {0x68, 0x5E, 0x0125}, - {0x49, 0x7E, 0x0128}, - {0x69, 0x7E, 0x0129}, - {0x49, 0x2D, 0x012A}, - {0x69, 0x2D, 0x012B}, - {0x4A, 0x5E, 0x0134}, - {0x6A, 0x5E, 0x0135}, - {0x4B, 0x2C, 0x0136}, - {0x6B, 0x2C, 0x0137}, - {0x4C, 0x27, 0x0139}, - {0x6C, 0x27, 0x013A}, - {0x4C, 0x2C, 0x013B}, - {0x6C, 0x2C, 0x013C}, - {0x4E, 0x27, 0x0143}, - {0x6E, 0x27, 0x0144}, - {0x4E, 0x2C, 0x0145}, - {0x6E, 0x2C, 0x0146}, - {0x4F, 0x2D, 0x014C}, - {0x6F, 0x2D, 0x014D}, - {0x52, 0x27, 0x0154}, - {0x72, 0x27, 0x0155}, - {0x52, 0x2C, 0x0156}, - {0x72, 0x2C, 0x0157}, - {0x53, 0x27, 0x015A}, - {0x73, 0x27, 0x015B}, - {0x53, 0x5E, 0x015C}, - {0x73, 0x5E, 0x015D}, - {0x53, 0x2C, 0x015E}, - {0x73, 0x2C, 0x015F}, - {0x54, 0x2C, 0x0162}, - {0x74, 0x2C, 0x0163}, - {0x55, 0x7E, 0x0168}, - {0x75, 0x7E, 0x0169}, - {0x55, 0x2D, 0x016A}, - {0x75, 0x2D, 0x016B}, - {0x55, 0x2A, 0x016E}, - {0x75, 0x2A, 0x016F}, - {0x57, 0x5E, 0x0174}, - {0x77, 0x5E, 0x0175}, - {0x59, 0x5E, 0x0176}, - {0x79, 0x5E, 0x0177}, - {0x59, 0x22, 0x0178}, - {0x5A, 0x27, 0x0179}, - {0x7A, 0x27, 0x017A}, - {0x47, 0x27, 0x01F4}, - {0x67, 0x27, 0x01F5}, - {0x4E, 0x60, 0x01F8}, - {0x6E, 0x60, 0x01F9}, - {0x45, 0x2C, 0x0228}, - {0x65, 0x2C, 0x0229}, - {0x59, 0x2D, 0x0232}, - {0x79, 0x2D, 0x0233}, - {0x44, 0x2C, 0x1E10}, - {0x64, 0x2C, 0x1E11}, - {0x47, 0x2D, 0x1E20}, - {0x67, 0x2D, 0x1E21}, - {0x48, 0x22, 0x1E26}, - {0x68, 0x22, 0x1E27}, - {0x48, 0x2C, 0x1E28}, - {0x68, 0x2C, 0x1E29}, - {0x4B, 0x27, 0x1E30}, - {0x6B, 0x27, 0x1E31}, - {0x4D, 0x27, 0x1E3E}, - {0x6D, 0x27, 0x1E3F}, - {0x50, 0x27, 0x1E54}, - {0x70, 0x27, 0x1E55}, - {0x56, 0x7E, 0x1E7C}, - {0x76, 0x7E, 0x1E7D}, - {0x57, 0x60, 0x1E80}, - {0x77, 0x60, 0x1E81}, - {0x57, 0x27, 0x1E82}, - {0x77, 0x27, 0x1E83}, - {0x57, 0x22, 0x1E84}, - {0x77, 0x22, 0x1E85}, - {0x58, 0x22, 0x1E8C}, - {0x78, 0x22, 0x1E8D}, - {0x5A, 0x5E, 0x1E90}, - {0x7A, 0x5E, 0x1E91}, - {0x74, 0x22, 0x1E97}, - {0x77, 0x2A, 0x1E98}, - {0x79, 0x2A, 0x1E99}, - {0x45, 0x7E, 0x1EBC}, - {0x65, 0x7E, 0x1EBD}, - {0x59, 0x60, 0x1EF2}, - {0x79, 0x60, 0x1EF3}, - {0x59, 0x7E, 0x1EF8}, - {0x79, 0x7E, 0x1EF9}, - /* Compatible/possibles from UCS */ - {0x49, 0x4A, 0x0132}, - {0x69, 0x6A, 0x0133}, - {0x4C, 0x4A, 0x01C7}, - {0x4C, 0x6A, 0x01C8}, - {0x6C, 0x6A, 0x01C9}, - {0x4E, 0x4A, 0x01CA}, - {0x4E, 0x6A, 0x01CB}, - {0x6E, 0x6A, 0x01CC}, - {0x44, 0x5A, 0x01F1}, - {0x44, 0x7A, 0x01F2}, - {0x64, 0x7A, 0x01F3}, - {0x2E, 0x2E, 0x2025}, - {0x21, 0x21, 0x203C}, - {0x3F, 0x21, 0x2048}, - {0x21, 0x3F, 0x2049}, - {0x52, 0x73, 0x20A8}, - {0x4E, 0x6F, 0x2116}, - {0x53, 0x4D, 0x2120}, - {0x54, 0x4D, 0x2122}, - {0x49, 0x49, 0x2161}, - {0x49, 0x56, 0x2163}, - {0x56, 0x49, 0x2165}, - {0x49, 0x58, 0x2168}, - {0x58, 0x49, 0x216A}, - {0x69, 0x69, 0x2171}, - {0x69, 0x76, 0x2173}, - {0x76, 0x69, 0x2175}, - {0x69, 0x78, 0x2178}, - {0x78, 0x69, 0x217A}, - {0x31, 0x30, 0x2469}, - {0x31, 0x31, 0x246A}, - {0x31, 0x32, 0x246B}, - {0x31, 0x33, 0x246C}, - {0x31, 0x34, 0x246D}, - {0x31, 0x35, 0x246E}, - {0x31, 0x36, 0x246F}, - {0x31, 0x37, 0x2470}, - {0x31, 0x38, 0x2471}, - {0x31, 0x39, 0x2472}, - {0x32, 0x30, 0x2473}, - {0x31, 0x2E, 0x2488}, - {0x32, 0x2E, 0x2489}, - {0x33, 0x2E, 0x248A}, - {0x34, 0x2E, 0x248B}, - {0x35, 0x2E, 0x248C}, - {0x36, 0x2E, 0x248D}, - {0x37, 0x2E, 0x248E}, - {0x38, 0x2E, 0x248F}, - {0x39, 0x2E, 0x2490}, - {0x64, 0x61, 0x3372}, - {0x41, 0x55, 0x3373}, - {0x6F, 0x56, 0x3375}, - {0x70, 0x63, 0x3376}, - {0x70, 0x41, 0x3380}, - {0x6E, 0x41, 0x3381}, - {0x6D, 0x41, 0x3383}, - {0x6B, 0x41, 0x3384}, - {0x4B, 0x42, 0x3385}, - {0x4D, 0x42, 0x3386}, - {0x47, 0x42, 0x3387}, - {0x70, 0x46, 0x338A}, - {0x6E, 0x46, 0x338B}, - {0x6D, 0x67, 0x338E}, - {0x6B, 0x67, 0x338F}, - {0x48, 0x7A, 0x3390}, - {0x66, 0x6D, 0x3399}, - {0x6E, 0x6D, 0x339A}, - {0x6D, 0x6D, 0x339C}, - {0x63, 0x6D, 0x339D}, - {0x6B, 0x6D, 0x339E}, - {0x50, 0x61, 0x33A9}, - {0x70, 0x73, 0x33B0}, - {0x6E, 0x73, 0x33B1}, - {0x6D, 0x73, 0x33B3}, - {0x70, 0x56, 0x33B4}, - {0x6E, 0x56, 0x33B5}, - {0x6D, 0x56, 0x33B7}, - {0x6B, 0x56, 0x33B8}, - {0x4D, 0x56, 0x33B9}, - {0x70, 0x57, 0x33BA}, - {0x6E, 0x57, 0x33BB}, - {0x6D, 0x57, 0x33BD}, - {0x6B, 0x57, 0x33BE}, - {0x4D, 0x57, 0x33BF}, - {0x42, 0x71, 0x33C3}, - {0x63, 0x63, 0x33C4}, - {0x63, 0x64, 0x33C5}, - {0x64, 0x42, 0x33C8}, - {0x47, 0x79, 0x33C9}, - {0x68, 0x61, 0x33CA}, - {0x48, 0x50, 0x33CB}, - {0x69, 0x6E, 0x33CC}, - {0x4B, 0x4B, 0x33CD}, - {0x4B, 0x4D, 0x33CE}, - {0x6B, 0x74, 0x33CF}, - {0x6C, 0x6D, 0x33D0}, - {0x6C, 0x6E, 0x33D1}, - {0x6C, 0x78, 0x33D3}, - {0x6D, 0x62, 0x33D4}, - {0x50, 0x48, 0x33D7}, - {0x50, 0x52, 0x33DA}, - {0x73, 0x72, 0x33DB}, - {0x53, 0x76, 0x33DC}, - {0x57, 0x62, 0x33DD}, - {0x66, 0x66, 0xFB00}, - {0x66, 0x69, 0xFB01}, - {0x66, 0x6C, 0xFB02}, - {0x73, 0x74, 0xFB06}, - {0, 0, 0} - }, *c; - - int nc = -1; - - for (c = composetbl; c->first; c++) { - if (c->first == first && c->second == second) - return c->composed; - } - - if (recurse == 0) { - nc = check_compose_internal(second, first, 1); - if (nc == -1) - nc = check_compose_internal(toupper((unsigned char)first), - toupper((unsigned char)second), 1); - if (nc == -1) - nc = check_compose_internal(toupper((unsigned char)second), - toupper((unsigned char)first), 1); - } - return nc; -} - -int check_compose(int first, int second) -{ - return check_compose_internal(first, second, 0); -} - -int decode_codepage(const char *cp_name) -{ - const char *s, *d; - const struct cp_list_item *cpi; - int codepage = -1; - CPINFO cpinfo; - - if (!cp_name || !*cp_name) - return CP_UTF8; /* default */ - - for (cpi = cp_list; cpi->name; cpi++) { - s = cp_name; - d = cpi->name; - for (;;) { - while (*s && !isalnum((unsigned char)*s) && *s != ':') - s++; - while (*d && !isalnum((unsigned char)*d) && *d != ':') - d++; - if (*s == 0) { - codepage = cpi->codepage; - if (codepage == CP_UTF8) - goto break_break; - if (codepage == -1) - return codepage; - if (codepage == 0) { - codepage = 65536 + (cpi - cp_list); - goto break_break; - } - - if (GetCPInfo(codepage, &cpinfo) != 0) - goto break_break; - } - if (tolower((unsigned char)*s++) != tolower((unsigned char)*d++)) - break; - } - } - - d = cp_name; - if (tolower((unsigned char)d[0]) == 'c' && - tolower((unsigned char)d[1]) == 'p') - d += 2; - if (tolower((unsigned char)d[0]) == 'i' && - tolower((unsigned char)d[1]) == 'b' && - tolower((unsigned char)d[2]) == 'm') - d += 3; - for (s = d; *s >= '0' && *s <= '9'; s++); - if (*s == 0 && s != d) - codepage = atoi(d); /* CP999 or IBM999 */ - - if (codepage == CP_ACP) - codepage = GetACP(); - if (codepage == CP_OEMCP) - codepage = GetOEMCP(); - if (codepage > 65535) - codepage = -2; - - break_break:; - if (codepage != -1) { - if (codepage != CP_UTF8 && codepage < 65536) { - if (GetCPInfo(codepage, &cpinfo) == 0) { - codepage = -2; - } else if (cpinfo.MaxCharSize > 1) - codepage = -3; - } - } - if (codepage == -1 && *cp_name) - codepage = -2; - return codepage; -} - -const char *cp_name(int codepage) -{ - const struct cp_list_item *cpi, *cpno; - static char buf[32]; - - if (codepage == -1) { - sprintf(buf, "Use font encoding"); - return buf; - } - - if (codepage > 0 && codepage < 65536) - sprintf(buf, "CP%03d", codepage); - else - *buf = 0; - - if (codepage >= 65536) { - cpno = 0; - for (cpi = cp_list; cpi->name; cpi++) - if (cpi == cp_list + (codepage - 65536)) { - cpno = cpi; - break; - } - if (cpno) - for (cpi = cp_list; cpi->name; cpi++) { - if (cpno->cp_table == cpi->cp_table) - return cpi->name; - } - } else { - for (cpi = cp_list; cpi->name; cpi++) { - if (codepage == cpi->codepage) - return cpi->name; - } - } - return buf; -} - -/* - * Return the nth code page in the list, for use in the GUI - * configurer. - */ -const char *cp_enumerate(int index) -{ - if (index < 0 || index >= lenof(cp_list)) - return NULL; - return cp_list[index].name; -} - -void get_unitab(int codepage, wchar_t *unitab, int ftype) -{ - char tbuf[4]; - int i, max = 256, flg = MB_ERR_INVALID_CHARS; - - if (ftype) - flg |= MB_USEGLYPHCHARS; - if (ftype == 2) - max = 128; - - if (codepage == CP_UTF8) { - for (i = 0; i < max; i++) - unitab[i] = i; - return; - } - - if (codepage == CP_ACP) - codepage = GetACP(); - else if (codepage == CP_OEMCP) - codepage = GetOEMCP(); - - if (codepage > 0 && codepage < 65536) { - for (i = 0; i < max; i++) { - tbuf[0] = i; - - if (mb_to_wc(codepage, flg, tbuf, 1, unitab + i, 1) - != 1) - unitab[i] = 0xFFFD; - } - } else { - int j = 256 - cp_list[codepage & 0xFFFF].cp_size; - for (i = 0; i < max; i++) - unitab[i] = i; - for (i = j; i < max; i++) - unitab[i] = cp_list[codepage & 0xFFFF].cp_table[i - j]; - } -} - -int wc_to_mb(int codepage, int flags, const wchar_t *wcstr, int wclen, - char *mbstr, int mblen, const char *defchr) -{ - reverse_mapping *rmap = get_reverse_mapping(codepage); - - if (rmap) { - /* Do this by array lookup if we can. */ - if (wclen < 0) { - for (wclen = 0; wcstr[wclen++] ;); /* will include the NUL */ - } - char *p; - int i; - for (p = mbstr, i = 0; i < wclen; i++) { - wchar_t ch = wcstr[i]; - int by; - const char *p1; - - #define WRITECH(chr) do \ - { \ - assert(p - mbstr < mblen); \ - *p++ = (char)(chr); \ - } while (0) - - if ((p1 = rmap->blocks[(ch >> 8) & 0xFF]) != NULL && - (by = p1[ch & 0xFF]) != '\0') - WRITECH(by); - else if (ch < 0x80) - WRITECH(ch); - else if (defchr) - for (const char *q = defchr; *q; q++) - WRITECH(*q); -#if 1 - else - WRITECH('.'); -#endif - - #undef WRITECH - } - return p - mbstr; - } else { - int defused, ret; - ret = WideCharToMultiByte(codepage, flags, wcstr, wclen, - mbstr, mblen, defchr, &defused); - if (ret) - return ret; - -#ifdef LEGACY_WINDOWS - /* - * Fallback for legacy platforms too old to support UTF-8: if - * the codepage is UTF-8, we can do the translation ourselves. - */ - if (codepage == CP_UTF8 && mblen > 0 && wclen > 0) { - buffer_sink bs[1]; - buffer_sink_init(bs, mbstr, mblen); - - while (wclen > 0) { - unsigned long wc = (wclen--, *wcstr++); - if (wclen > 0 && IS_SURROGATE_PAIR(wc, *wcstr)) { - wc = FROM_SURROGATES(wc, *wcstr); - wclen--, wcstr++; - } - - const char *prev_ptr = bs->out; - put_utf8_char(bs, wc); - if (bs->overflowed) - return prev_ptr - mbstr; - } - - return bs->out - mbstr; - } -#endif - - /* No other fallbacks are available */ - return 0; - } -} - -int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, - wchar_t *wcstr, int wclen) -{ - if (codepage >= 65536) { - /* Character set not known to Windows, so we'll have to - * translate it ourself */ - size_t index = codepage - 65536; - if (index >= lenof(cp_list)) - return 0; - const struct cp_list_item *cp = &cp_list[index]; - if (!cp->cp_table) - return 0; - - size_t remaining = wclen; - wchar_t *p = wcstr; - unsigned tablebase = 256 - cp->cp_size; - - while (mblen > 0) { - mblen--; - unsigned c = 0xFF & *mbstr++; - wchar_t wc = (c < tablebase ? c : cp->cp_table[c - tablebase]); - if (remaining > 0) { - remaining--; - *p++ = wc; - } else { - return p - wcstr; - } - } - - return p - wcstr; - } - - int ret = MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); - if (ret) - return ret; - -#ifdef LEGACY_WINDOWS - /* - * Fallback for legacy platforms too old to support UTF-8: if the - * codepage is UTF-8, we can do the translation ourselves. - */ - if (codepage == CP_UTF8 && mblen > 0 && wclen > 0) { - BinarySource src[1]; - BinarySource_BARE_INIT(src, mbstr, mblen); - - size_t remaining = wclen; - wchar_t *p = wcstr; - - while (get_avail(src)) { - wchar_t wcbuf[2]; - size_t nwc = decode_utf8_to_wchar(src, wcbuf, NULL); - - for (size_t i = 0; i < nwc; i++) { - if (remaining > 0) { - remaining--; - *p++ = wcbuf[i]; - } else { - return p - wcstr; - } - } - } - - return p - wcstr; - } -#endif - - /* No other fallbacks are available */ - return 0; -} - -bool is_dbcs_leadbyte(int codepage, char byte) -{ - return IsDBCSLeadByteEx(codepage, byte); -} diff --git a/WINDOWS/utils/agent_mutex_name.c b/WINDOWS/utils/agent_mutex_name.c deleted file mode 100644 index 949e8cfad..000000000 --- a/WINDOWS/utils/agent_mutex_name.c +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Return the full pathname of the global mutex that Pageant uses at - * startup to atomically decide whether to be a server or a client. - */ - -#include "putty.h" - -char *agent_mutex_name(void) -{ - char *username = get_username(); - char *mutexname = dupprintf("Local\\pageant-mutex.%s", username); - sfree(username); - return mutexname; -} diff --git a/WINDOWS/utils/agent_named_pipe_name.c b/WINDOWS/utils/agent_named_pipe_name.c deleted file mode 100644 index aa64b3f60..000000000 --- a/WINDOWS/utils/agent_named_pipe_name.c +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Return the full pathname of the named pipe Pageant will listen on. - * Used by both the Pageant server code and client code. - */ - -#include "putty.h" -#include "cryptoapi.h" - -char *agent_named_pipe_name(void) -{ - char *username = get_username(); - char *suffix = capi_obfuscate_string("Pageant"); - char *pipename = dupprintf("\\\\.\\pipe\\pageant.%s.%s", username, suffix); - sfree(username); - sfree(suffix); - return pipename; -} diff --git a/WINDOWS/utils/arm_arch_queries.c b/WINDOWS/utils/arm_arch_queries.c deleted file mode 100644 index b0193276d..000000000 --- a/WINDOWS/utils/arm_arch_queries.c +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Windows implementation of the OS query functions that detect Arm - * architecture extensions. - */ - -#include "putty.h" -#include "ssh.h" - -#if !(defined _M_ARM || defined _M_ARM64) -/* - * For non-Arm, stub out these functions. This module shouldn't be - * _called_ in that situation anyway, but it will still be compiled - * (because that's easier than getting CMake to identify the - * architecture in all cases). - */ -#define IsProcessorFeaturePresent(...) false -#endif - -bool platform_aes_neon_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_pmull_neon_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_sha256_neon_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_sha1_neon_available(void) -{ - return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); -} - -bool platform_sha512_neon_available(void) -{ - /* As of 2020-12-24, as far as I can tell from docs.microsoft.com, - * Windows on Arm does not yet provide a PF_ARM_V8_* flag for the - * SHA-512 architecture extension. */ - return false; -} diff --git a/WINDOWS/utils/aux_match_opt.c b/WINDOWS/utils/aux_match_opt.c deleted file mode 100644 index 190eddacf..000000000 --- a/WINDOWS/utils/aux_match_opt.c +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Helper function for matching command-line options in the Windows - * auxiliary tools (PuTTYgen and Pageant). - * - * Supports basically the usual kinds of option, including GNUish - * --foo long options and simple -f short options. But historically - * those tools have also supported long options with a single dash, so - * we don't go full GNU and report those as syntax errors. - */ - -#include "putty.h" - -/* - * Call this to initialise the state structure. - */ -AuxMatchOpt aux_match_opt_init(int argc, char **argv, int start_index, - aux_opt_error_fn_t opt_error) -{ - AuxMatchOpt amo[1]; - - amo->argc = argc; - amo->argv = argv; - amo->index = start_index; - amo->doing_opts = true; - amo->error = opt_error; - - return amo[0]; -} - -/* - * Call this with a NULL-terminated list of synonymous option names. - * Point 'val' at a char * to receive the option argument, if one is - * expected. Set 'val' to NULL if no argument is expected. - */ -bool aux_match_opt(AuxMatchOpt *amo, char **val, const char *optname, ...) -{ - assert(amo->index < amo->argc); - - /* Find the end of the option name */ - char *opt = amo->argv[amo->index]; - ptrlen argopt; - argopt.ptr = opt; - argopt.len = strcspn(opt, "="); - - /* Normalise GNU-style --foo long options to the -foo that this - * tool has historically used */ - ptrlen argopt2 = make_ptrlen(NULL, 0); - if (ptrlen_startswith(argopt, PTRLEN_LITERAL("--"), NULL)) - ptrlen_startswith(argopt, PTRLEN_LITERAL("-"), &argopt2); - - /* See if this option matches any of our synonyms */ - va_list ap; - va_start(ap, optname); - bool matched = false; - while (optname) { - if (ptrlen_eq_string(argopt, optname)) { - matched = true; - break; - } - if (argopt2.ptr && strlen(optname) > 2 && - ptrlen_eq_string(argopt2, optname)) { - matched = true; - break; - } - optname = va_arg(ap, const char *); - } - va_end(ap); - if (!matched) - return false; - - /* Check for a value */ - if (opt[argopt.len]) { - if (!val) - amo->error("option '%s' does not expect a value", opt); - *val = opt + argopt.len + 1; - amo->index++; - } else if (!val) { - amo->index++; - } else { - if (amo->index + 1 >= amo->argc) - amo->error("option '%s' expects a value", opt); - *val = amo->argv[amo->index + 1]; - amo->index += 2; - } - - return true; -} - -/* - * Call this to return a non-option argument in *val. - */ -bool aux_match_arg(AuxMatchOpt *amo, char **val) -{ - assert(amo->index < amo->argc); - char *opt = amo->argv[amo->index]; - - if (!amo->doing_opts || opt[0] != '-' || !strcmp(opt, "-")) { - *val = opt; - amo->index++; - return true; - } - - return false; -} - -/* - * And call this to test whether we're done. Also handles '--'. - */ -bool aux_match_done(AuxMatchOpt *amo) -{ - if (amo->index < amo->argc && !strcmp(amo->argv[amo->index], "--")) { - amo->doing_opts = false; - amo->index++; - } - - return amo->index >= amo->argc; -} diff --git a/WINDOWS/utils/centre_window.c b/WINDOWS/utils/centre_window.c deleted file mode 100644 index 651fc2797..000000000 --- a/WINDOWS/utils/centre_window.c +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Centre a window on the screen. Used to position the main config box. - */ - -#include "putty.h" - -void centre_window(HWND win) -{ - RECT rd, rw; - - if (!GetWindowRect(GetDesktopWindow(), &rd)) - return; - if (!GetWindowRect(win, &rw)) - return; - - MoveWindow(win, - (rd.right + rd.left + rw.left - rw.right) / 2, - (rd.bottom + rd.top + rw.top - rw.bottom) / 2, - rw.right - rw.left, rw.bottom - rw.top, true); -} diff --git a/WINDOWS/utils/cryptoapi.c b/WINDOWS/utils/cryptoapi.c deleted file mode 100644 index c3752c59d..000000000 --- a/WINDOWS/utils/cryptoapi.c +++ /dev/null @@ -1,89 +0,0 @@ -/* - * windows/utils/cryptoapi.c: implementation of cryptoapi.h. - */ - -#include "putty.h" - -#include "putty.h" -#include "ssh.h" - -#include "cryptoapi.h" - -DEF_WINDOWS_FUNCTION(CryptProtectMemory); - -bool got_crypt(void) -{ - static bool attempted = false; - static bool successful; - static HMODULE crypt; - - if (!attempted) { - attempted = true; - crypt = load_system32_dll("crypt32.dll"); - successful = crypt && - GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory); - } - return successful; -} - -char *capi_obfuscate_string(const char *realname) -{ - char *cryptdata; - int cryptlen; - unsigned char digest[32]; - char retbuf[65]; - int i; - - cryptlen = strlen(realname) + 1; - cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1; - cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE; - cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE; - - cryptdata = snewn(cryptlen, char); - memset(cryptdata, 0, cryptlen); - strcpy(cryptdata, realname); - - /* - * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to - * use the same key in all processes with this user id, meaning - * that the next PuTTY process calling this function with the same - * input will get the same data. - * - * (Contrast with CryptProtectData, which invents a new session - * key every time since its API permits returning more data than - * was input, so calling _that_ and hashing the output would not - * be stable.) - * - * We don't worry too much if this doesn't work for some reason. - * Omitting this step still has _some_ privacy value (in that - * another user can test-hash things to confirm guesses as to - * where you might be connecting to, but cannot invert SHA-256 in - * the absence of any plausible guess). So we don't abort if we - * can't call CryptProtectMemory at all, or if it fails. - */ - if (got_crypt()) - p_CryptProtectMemory(cryptdata, cryptlen, - CRYPTPROTECTMEMORY_CROSS_PROCESS); - - /* - * We don't want to give away the length of the hostname either, - * so having got it back out of CryptProtectMemory we now hash it. - */ - { - ssh_hash *h = ssh_hash_new(&ssh_sha256); - put_string(h, cryptdata, cryptlen); - ssh_hash_final(h, digest); - } - - sfree(cryptdata); - - /* - * Finally, make printable. - */ - for (i = 0; i < 32; i++) { - sprintf(retbuf + 2*i, "%02x", digest[i]); - /* the last of those will also write the trailing NUL */ - } - - return dupstr(retbuf); -} diff --git a/WINDOWS/utils/defaults.c b/WINDOWS/utils/defaults.c deleted file mode 100644 index 928c02c4a..000000000 --- a/WINDOWS/utils/defaults.c +++ /dev/null @@ -1,40 +0,0 @@ -/* - * windows/utils/defaults.c: default settings that are specific to Windows. - */ - -#include "putty.h" - -#include - -FontSpec *platform_default_fontspec(const char *name) -{ - if (!strcmp(name, "Font")) - return fontspec_new("Courier New", false, 10, ANSI_CHARSET); - else - return fontspec_new_default(); -} - -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("COM1"); - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} diff --git a/WINDOWS/utils/dll_hijacking_protection.c b/WINDOWS/utils/dll_hijacking_protection.c deleted file mode 100644 index fe9ae59c7..000000000 --- a/WINDOWS/utils/dll_hijacking_protection.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * If the OS provides it, call SetDefaultDllDirectories() to prevent - * DLLs from being loaded from the directory containing our own - * binary, and instead only load from system32. - * - * This is a protection against hijacking attacks, if someone runs - * PuTTY directly from their web browser's download directory having - * previously been enticed into clicking on an unwise link that - * downloaded a malicious DLL to the same directory under one of - * various magic names that seem to be things that standard Windows - * DLLs delegate to. - * - * It shouldn't break deliberate loading of user-provided DLLs such as - * GSSAPI providers, because those are specified by their full - * pathname by the user-provided configuration. - */ - -#include "putty.h" - -void dll_hijacking_protection(void) -{ - static HMODULE kernel32_module; - DECL_WINDOWS_FUNCTION(static, BOOL, SetDefaultDllDirectories, (DWORD)); - - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); -#if !HAVE_SETDEFAULTDLLDIRECTORIES - /* For older Visual Studio, this function isn't available in - * the header files to type-check */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK( - kernel32_module, SetDefaultDllDirectories); -#else - GET_WINDOWS_FUNCTION(kernel32_module, SetDefaultDllDirectories); -#endif - } - - if (p_SetDefaultDllDirectories) { - /* LOAD_LIBRARY_SEARCH_SYSTEM32 and explicitly specified - * directories only */ - p_SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32 | - LOAD_LIBRARY_SEARCH_USER_DIRS); - } -} diff --git a/WINDOWS/utils/dputs.c b/WINDOWS/utils/dputs.c deleted file mode 100644 index f582509a7..000000000 --- a/WINDOWS/utils/dputs.c +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Implementation of dputs() for Windows. - * - * The debug messages are written to STD_OUTPUT_HANDLE, except that - * first it has to make sure that handle _exists_, by calling - * AllocConsole first if necessary. - * - * They also go into a file called debug.log. - */ - -#include "putty.h" -#include "utils/utils.h" - -static HANDLE debug_fp = INVALID_HANDLE_VALUE; -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 == INVALID_HANDLE_VALUE) { - debug_fp = CreateFile("debug.log", GENERIC_WRITE, FILE_SHARE_READ, - NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - } - - if (debug_fp != INVALID_HANDLE_VALUE) { - WriteFile(debug_fp, buf, strlen(buf), &dw, NULL); - } - if (debug_hdl != INVALID_HANDLE_VALUE) { - WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL); - } -} diff --git a/WINDOWS/utils/escape_registry_key.c b/WINDOWS/utils/escape_registry_key.c deleted file mode 100644 index f5c9c19e8..000000000 --- a/WINDOWS/utils/escape_registry_key.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Escaping/unescaping functions to translate between a saved session - * name, and the key name used to represent it in the Registry area - * where we store saved sessions. - * - * The basic technique is to %-escape characters we can't use in - * Registry keys. - */ - -#include "putty.h" - -void escape_registry_key(const char *in, strbuf *out) -{ - bool candot = false; - static const char hex[16] = "0123456789ABCDEF"; - - while (*in) { - if (*in == ' ' || *in == '\\' || *in == '*' || *in == '?' || - *in == '%' || *in < ' ' || *in > '~' || (*in == '.' - && !candot)) { - put_byte(out, '%'); - put_byte(out, hex[((unsigned char) *in) >> 4]); - put_byte(out, hex[((unsigned char) *in) & 15]); - } else - put_byte(out, *in); - in++; - candot = true; - } -} - -void unescape_registry_key(const char *in, strbuf *out) -{ - 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); - - put_byte(out, (i << 4) + j); - in += 3; - } else { - put_byte(out, *in++); - } - } -} diff --git a/WINDOWS/utils/filename.c b/WINDOWS/utils/filename.c deleted file mode 100644 index eb358b1f3..000000000 --- a/WINDOWS/utils/filename.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Implementation of Filename for Windows. - */ - -#include - -#include "putty.h" - -Filename *filename_from_str(const char *str) -{ - Filename *fn = snew(Filename); - fn->cpath = dupstr(str); - fn->wpath = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, fn->cpath); - fn->utf8path = encode_wide_string_as_utf8(fn->wpath); - return fn; -} - -Filename *filename_from_wstr(const wchar_t *str) -{ - Filename *fn = snew(Filename); - fn->wpath = dupwcs(str); - fn->cpath = dup_wc_to_mb(DEFAULT_CODEPAGE, 0, fn->wpath, "?"); - fn->utf8path = encode_wide_string_as_utf8(fn->wpath); - return fn; -} - -Filename *filename_from_utf8(const char *ustr) -{ - Filename *fn = snew(Filename); - fn->utf8path = dupstr(ustr); - fn->wpath = decode_utf8_to_wide_string(fn->utf8path); - fn->cpath = dup_wc_to_mb(DEFAULT_CODEPAGE, 0, fn->wpath, "?"); - return fn; -} - -Filename *filename_copy(const Filename *fn) -{ - Filename *newfn = snew(Filename); - newfn->cpath = dupstr(fn->cpath); - newfn->wpath = dupwcs(fn->wpath); - newfn->utf8path = dupstr(fn->utf8path); - return newfn; -} - -const char *filename_to_str(const Filename *fn) -{ - return fn->cpath; /* FIXME */ -} - -bool filename_equal(const Filename *f1, const Filename *f2) -{ - /* wpath is primary: two filenames refer to the same file if they - * have the same wpath */ - return !wcscmp(f1->wpath, f2->wpath); -} - -bool filename_is_null(const Filename *fn) -{ - return !*fn->wpath; -} - -void filename_free(Filename *fn) -{ - sfree(fn->wpath); - sfree(fn->cpath); - sfree(fn->utf8path); - sfree(fn); -} - -void filename_serialise(BinarySink *bs, const Filename *f) -{ - put_asciz(bs, f->utf8path); -} -Filename *filename_deserialise(BinarySource *src) -{ - const char *utf8 = get_asciz(src); - return filename_from_utf8(utf8); -} - -char filename_char_sanitise(char c) -{ - if (strchr("<>:\"/\\|?*", c)) - return '.'; - return c; -} - -FILE *f_open(const Filename *fn, const char *mode, bool isprivate) -{ - wchar_t *wmode = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, mode); - return _wfopen(fn->wpath, wmode); - sfree(wmode); -} diff --git a/WINDOWS/utils/fontspec.c b/WINDOWS/utils/fontspec.c deleted file mode 100644 index 7bdd34233..000000000 --- a/WINDOWS/utils/fontspec.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Implementation of FontSpec for Windows. - */ - -#include "putty.h" - -FontSpec *fontspec_new(const char *name, bool 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_new_default(void) -{ - return fontspec_new("", false, 0, 0); -} - -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); -} - -void fontspec_serialise(BinarySink *bs, FontSpec *f) -{ - put_asciz(bs, f->name); - put_uint32(bs, f->isbold); - put_uint32(bs, f->height); - put_uint32(bs, f->charset); -} - -FontSpec *fontspec_deserialise(BinarySource *src) -{ - const char *name = get_asciz(src); - unsigned isbold = get_uint32(src); - unsigned height = get_uint32(src); - unsigned charset = get_uint32(src); - return fontspec_new(name, isbold, height, charset); -} diff --git a/WINDOWS/utils/get_system_dir.c b/WINDOWS/utils/get_system_dir.c deleted file mode 100644 index 049cd7fca..000000000 --- a/WINDOWS/utils/get_system_dir.c +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Wrapper function around GetSystemDirectory that deals with - * allocating the output buffer, and also caches the result for future - * calls. - */ - -#include "putty.h" - -const char *get_system_dir(void) -{ - static char *sysdir = NULL; - static size_t sysdirsize = 0; - - if (!sysdir) { - size_t len; - while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize) - sgrowarray(sysdir, sysdirsize, len); - } - - return sysdir; -} diff --git a/WINDOWS/utils/get_username.c b/WINDOWS/utils/get_username.c deleted file mode 100644 index bc5780e40..000000000 --- a/WINDOWS/utils/get_username.c +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Implementation of get_username() for Windows. - */ - -#include "putty.h" - -#ifndef SECURITY_WIN32 -#define SECURITY_WIN32 -#endif -#include - -char *get_username(void) -{ - DWORD namelen; - char *user; - bool got_username = false; - DECL_WINDOWS_FUNCTION(static, BOOLEAN, GetUserNameExA, - (EXTENDED_NAME_FORMAT, LPSTR, PULONG)); - - { - static bool tried_usernameex = false; - if (!tried_usernameex) { - /* Not available on Win9x, so load dynamically */ - HMODULE secur32 = load_system32_dll("secur32.dll"); - /* If MIT Kerberos is installed, the following call to - GET_WINDOWS_FUNCTION makes Windows implicitly load - sspicli.dll WITHOUT proper path sanitizing, so better - load it properly before */ - HMODULE sspicli = load_system32_dll("sspicli.dll"); - (void)sspicli; /* squash compiler warning about unused variable */ - 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)) { - /* - * 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; -} diff --git a/WINDOWS/utils/getdlgitemtext_alloc.c b/WINDOWS/utils/getdlgitemtext_alloc.c deleted file mode 100644 index 8db329012..000000000 --- a/WINDOWS/utils/getdlgitemtext_alloc.c +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Handy wrappers around GetDlgItemText (A and W) which don't make you - * invent an arbitrary length limit on the output string. Returned - * string is dynamically allocated; caller must free. - */ - -#include "putty.h" - -char *GetDlgItemText_alloc(HWND hwnd, int id) -{ - char *ret = NULL; - size_t size = 0; - - do { - sgrowarray_nm(ret, size, size); - GetDlgItemText(hwnd, id, ret, size); - } while (!memchr(ret, '\0', size-1)); - - return ret; -} - -wchar_t *GetDlgItemTextW_alloc(HWND hwnd, int id) -{ - wchar_t *ret = NULL; - size_t size = 0; - - do { - sgrowarray_nm(ret, size, size); - GetDlgItemTextW(hwnd, id, ret, size); - } while (!memchr(ret, '\0', size-1)); - - return ret; -} diff --git a/WINDOWS/utils/gui-timing.c b/WINDOWS/utils/gui-timing.c deleted file mode 100644 index b0beec9c9..000000000 --- a/WINDOWS/utils/gui-timing.c +++ /dev/null @@ -1,56 +0,0 @@ -#include "putty.h" - -#define TIMING_CLASS_NAME "PuTTYTimerWindow" -#define TIMING_TIMER_ID 1234 -static long timing_next_time; -static HWND timing_hwnd; - -static LRESULT CALLBACK TimingWndProc(HWND hwnd, UINT message, - WPARAM wParam, LPARAM lParam) -{ - switch (message) { - case WM_TIMER: - if ((UINT_PTR)wParam == TIMING_TIMER_ID) { - unsigned long next; - - KillTimer(hwnd, TIMING_TIMER_ID); - if (run_timers(timing_next_time, &next)) { - timer_change_notify(next); - } else { - } - } - return 0; - } - return DefWindowProc(hwnd, message, wParam, lParam); -} - -void setup_gui_timing(void) -{ - WNDCLASS wndclass; - - memset(&wndclass, 0, sizeof(wndclass)); - wndclass.lpfnWndProc = TimingWndProc; - wndclass.hInstance = hinst; - wndclass.lpszClassName = TIMING_CLASS_NAME; - - RegisterClass(&wndclass); - - timing_hwnd = CreateWindow( - TIMING_CLASS_NAME, "PuTTY: hidden timing window", - WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, - 100, 100, NULL, NULL, hinst, NULL); - ShowWindow(timing_hwnd, SW_HIDE); -} - -void timer_change_notify(unsigned long next) -{ - unsigned long now = GETTICKCOUNT(); - long ticks; - if (now - next < INT_MAX) - ticks = 0; - else - ticks = next - now; - KillTimer(timing_hwnd, TIMING_TIMER_ID); - SetTimer(timing_hwnd, TIMING_TIMER_ID, ticks, NULL); - timing_next_time = next; -} diff --git a/WINDOWS/utils/interprocess_mutex.c b/WINDOWS/utils/interprocess_mutex.c deleted file mode 100644 index 8612a298c..000000000 --- a/WINDOWS/utils/interprocess_mutex.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Lock and unlock a mutex with a globally visible name. Used to - * synchronise between unrelated processes, such as two - * connection-sharing PuTTYs deciding which will be the upstream. - */ - -#include "putty.h" -#include "security-api.h" - -HANDLE lock_interprocess_mutex(const char *mutexname, char **error) -{ - PSECURITY_DESCRIPTOR psd = NULL; - PACL acl = NULL; - HANDLE mutex = NULL; - - if (should_have_security() && !make_private_security_descriptor( - MUTEX_ALL_ACCESS, &psd, &acl, error)) - goto out; - - SECURITY_ATTRIBUTES sa; - memset(&sa, 0, sizeof(sa)); - sa.nLength = sizeof(sa); - sa.lpSecurityDescriptor = psd; - sa.bInheritHandle = false; - - mutex = CreateMutex(&sa, false, mutexname); - if (!mutex) { - *error = dupprintf("CreateMutex(\"%s\") failed: %s", - mutexname, win_strerror(GetLastError())); - goto out; - } - - WaitForSingleObject(mutex, INFINITE); - - out: - if (psd) - LocalFree(psd); - if (acl) - LocalFree(acl); - - return mutex; -} - -void unlock_interprocess_mutex(HANDLE mutex) -{ - ReleaseMutex(mutex); - CloseHandle(mutex); -} diff --git a/WINDOWS/utils/is_console_handle.c b/WINDOWS/utils/is_console_handle.c deleted file mode 100644 index 887069c93..000000000 --- a/WINDOWS/utils/is_console_handle.c +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Determine whether a Windows HANDLE points at a console device. - */ - -#include "putty.h" - -bool is_console_handle(HANDLE handle) -{ - DWORD ignored_output; - if (GetConsoleMode(handle, &ignored_output)) - return true; - return false; -} diff --git a/WINDOWS/utils/load_system32_dll.c b/WINDOWS/utils/load_system32_dll.c deleted file mode 100644 index d227a2648..000000000 --- a/WINDOWS/utils/load_system32_dll.c +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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.) - */ - -#include "putty.h" - -HMODULE load_system32_dll(const char *libname) -{ - char *fullpath; - HMODULE ret; - - fullpath = dupcat(get_system_dir(), "\\", libname); - ret = LoadLibrary(fullpath); - sfree(fullpath); - return ret; -} diff --git a/WINDOWS/utils/ltime.c b/WINDOWS/utils/ltime.c deleted file mode 100644 index d4364509c..000000000 --- a/WINDOWS/utils/ltime.c +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Implementation of ltime() that avoids trouble with time() returning - * (time_t)-1 on Windows. - */ - -#include "putty.h" -#include - -struct tm ltime(void) -{ - SYSTEMTIME st; - struct tm tm; - - memset(&tm, 0, sizeof(tm)); /* in case there are any other fields */ - - 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/WINDOWS/utils/make_spr_sw_abort_winerror.c b/WINDOWS/utils/make_spr_sw_abort_winerror.c deleted file mode 100644 index b05ef61ff..000000000 --- a/WINDOWS/utils/make_spr_sw_abort_winerror.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Constructor function for a SeatPromptResult of the 'software abort' - * category, whose error message includes the translation of an OS - * error code. - */ - -#include "putty.h" - -static void spr_winerror_errfn(SeatPromptResult spr, BinarySink *bs) -{ - put_fmt(bs, "%s: %s", spr.errdata_lit, win_strerror(spr.errdata_u)); -} - -SeatPromptResult make_spr_sw_abort_winerror(const char *prefix, DWORD error) -{ - SeatPromptResult spr; - spr.kind = SPRK_SW_ABORT; - spr.errfn = spr_winerror_errfn; - spr.errdata_lit = prefix; - spr.errdata_u = error; - return spr; -} diff --git a/WINDOWS/utils/makedlgitemborderless.c b/WINDOWS/utils/makedlgitemborderless.c deleted file mode 100644 index 53975d06f..000000000 --- a/WINDOWS/utils/makedlgitemborderless.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Helper function to remove the border around a dialog item such as - * a read-only edit control. - */ - -#include "putty.h" - -void MakeDlgItemBorderless(HWND parent, int id) -{ - HWND child = GetDlgItem(parent, id); - LONG_PTR style = GetWindowLongPtr(child, GWL_STYLE); - LONG_PTR exstyle = GetWindowLongPtr(child, GWL_EXSTYLE); - style &= ~WS_BORDER; - exstyle &= ~(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE); - SetWindowLongPtr(child, GWL_STYLE, style); - SetWindowLongPtr(child, GWL_EXSTYLE, exstyle); - SetWindowPos(child, NULL, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); -} diff --git a/WINDOWS/utils/message_box.c b/WINDOWS/utils/message_box.c deleted file mode 100644 index 11b697943..000000000 --- a/WINDOWS/utils/message_box.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Enhanced version of the MessageBox API function. Permits enabling a - * Help button by setting helpctxid to a context id in the help file - * relevant to this dialog box. Also permits setting the 'utf8' flag - * to indicate that the char strings given as 'text' and 'caption' are - * encoded in UTF-8 rather than the system code page. - */ - -#include "putty.h" - -static HWND message_box_owner; - -/* Callback function to launch context help. */ -static VOID CALLBACK message_box_help_callback(LPHELPINFO lpHelpInfo) -{ - const char *context = NULL; -#define CHECK_CTX(name) \ - do { \ - if (lpHelpInfo->dwContextId == WINHELP_CTXID_ ## name) \ - context = WINHELP_CTX_ ## name; \ - } while (0) - CHECK_CTX(errors_hostkey_absent); - CHECK_CTX(errors_hostkey_changed); - CHECK_CTX(errors_cantloadkey); - CHECK_CTX(option_cleanup); - CHECK_CTX(pgp_fingerprints); -#undef CHECK_CTX - if (context) - launch_help(message_box_owner, context); -} - -int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, DWORD style, - bool utf8, DWORD helpctxid) -{ - MSGBOXPARAMSW mbox; - - /* - * We use MessageBoxIndirect() because it allows us to specify a - * callback function for the Help button. - */ - mbox.cbSize = sizeof(mbox); - /* Assumes the globals `hinst' and `hwnd' have sensible values. */ - mbox.hInstance = hinst; - mbox.dwLanguageId = LANG_NEUTRAL; - - mbox.hwndOwner = message_box_owner = owner; - - wchar_t *wtext, *wcaption; - if (utf8) { - wtext = decode_utf8_to_wide_string(text); - wcaption = decode_utf8_to_wide_string(caption); - } else { - wtext = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, text); - wcaption = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, caption); - } - mbox.lpszText = wtext; - mbox.lpszCaption = wcaption; - - mbox.dwStyle = style; - - mbox.dwContextHelpId = helpctxid; - if (helpctxid != 0 && has_help()) mbox.dwStyle |= MB_HELP; - mbox.lpfnMsgBoxCallback = &message_box_help_callback; - - int toret = MessageBoxIndirectW(&mbox); - - sfree(wtext); - sfree(wcaption); - - return toret; -} diff --git a/WINDOWS/utils/minefield.c b/WINDOWS/utils/minefield.c deleted file mode 100644 index c5262ae6a..000000000 --- a/WINDOWS/utils/minefield.c +++ /dev/null @@ -1,228 +0,0 @@ -/* - * 'Minefield' - a crude Windows memory debugger, similar in concept - * to the old Unix 'Electric Fence'. The main difference is that - * Electric Fence can be imposed on a program from outside, via - * LD_PRELOAD, whereas this has to be included in the program at - * compile time with its own cooperation. - * - * This module provides the Minefield allocator. Actually enabling it - * is done by a #define in force when the main utils/memory.c is - * compiled. - */ - -#include "putty.h" -#include "puttymem.h" - -#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; -} diff --git a/WINDOWS/utils/open_for_write_would_lose_data.c b/WINDOWS/utils/open_for_write_would_lose_data.c deleted file mode 100644 index 0645d7ac9..000000000 --- a/WINDOWS/utils/open_for_write_would_lose_data.c +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Implementation of open_for_write_would_lose_data for Windows. - */ - -#include "putty.h" - -/* - * This is slightly fiddly because we want to be backwards-compatible - * with systems too old to have GetFileAttributesEx. The next best - * thing is FindFirstFile, which will return a different data - * structure, but one that also contains the fields we want. (But it - * will behave more unhelpfully - for this application - in the - * presence of wildcards, so we'd prefer to use GFAE if we can.) - */ - -static inline bool open_for_write_would_lose_data_impl( - DWORD dwFileAttributes, DWORD nFileSizeHigh, DWORD nFileSizeLow) -{ - if (dwFileAttributes & (FILE_ATTRIBUTE_DEVICE|FILE_ATTRIBUTE_DIRECTORY)) { - /* - * File is something other than an ordinary disk file, so - * opening it for writing will not cause truncation. (It may - * not _succeed_ either, but that's not our problem here!) - */ - return false; - } - if (nFileSizeHigh == 0 && nFileSizeLow == 0) { - /* - * File is zero-length (or may be a named pipe, which - * dwFileAttributes can't tell apart from a regular file), so - * opening it for writing won't truncate any data away because - * there's nothing to truncate anyway. - */ - return false; - } - return true; -} - -bool open_for_write_would_lose_data(const Filename *fn) -{ - static HMODULE kernel32_module; - DECL_WINDOWS_FUNCTION(static, BOOL, GetFileAttributesExW, - (LPCWSTR, GET_FILEEX_INFO_LEVELS, LPVOID)); - - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); - GET_WINDOWS_FUNCTION(kernel32_module, GetFileAttributesExW); - } - - if (p_GetFileAttributesExW) { - WIN32_FILE_ATTRIBUTE_DATA attrs; - if (!p_GetFileAttributesExW(fn->wpath, GetFileExInfoStandard, &attrs)) { - /* - * Generally, if we don't identify a specific reason why we - * should return true from this function, we return false, and - * let the subsequent attempt to open the file for real give a - * more useful error message. - */ - return false; - } - return open_for_write_would_lose_data_impl( - attrs.dwFileAttributes, attrs.nFileSizeHigh, attrs.nFileSizeLow); - } else { - WIN32_FIND_DATAW fd; - HANDLE h = FindFirstFileW(fn->wpath, &fd); - if (h == INVALID_HANDLE_VALUE) { - /* - * As above, if we can't find the file at all, return false. - */ - return false; - } - CloseHandle(h); - return open_for_write_would_lose_data_impl( - fd.dwFileAttributes, fd.nFileSizeHigh, fd.nFileSizeLow); - } -} diff --git a/WINDOWS/utils/pgp_fingerprints_msgbox.c b/WINDOWS/utils/pgp_fingerprints_msgbox.c deleted file mode 100644 index b0733008c..000000000 --- a/WINDOWS/utils/pgp_fingerprints_msgbox.c +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Display the fingerprints of the PGP Master Keys to the user as a - * GUI message box. - */ - -#include "putty.h" - -void pgp_fingerprints_msgbox(HWND owner) -{ - message_box( - owner, - "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 " PGP_MASTER_KEY_YEAR - " (" PGP_MASTER_KEY_DETAILS "):\n" - " " PGP_MASTER_KEY_FP "\n\n" - "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR - ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" - " " PGP_PREV_MASTER_KEY_FP, - "PGP fingerprints", MB_ICONINFORMATION | MB_OK, - false, HELPCTXID(pgp_fingerprints)); -} diff --git a/WINDOWS/utils/platform_get_x_display.c b/WINDOWS/utils/platform_get_x_display.c deleted file mode 100644 index e2a9ddc63..000000000 --- a/WINDOWS/utils/platform_get_x_display.c +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Implementation of platform_get_x_display for Windows, common to all - * tools. - */ - -#include "putty.h" -#include "ssh.h" - -char *platform_get_x_display(void) -{ - /* We may as well check for DISPLAY in case it's useful. */ - return dupstr(getenv("DISPLAY")); -} diff --git a/WINDOWS/utils/registry.c b/WINDOWS/utils/registry.c deleted file mode 100644 index 9d65df1c0..000000000 --- a/WINDOWS/utils/registry.c +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Implement convenience wrappers on the awkward low-level functions - * for accessing the Windows registry. - */ - -#include "putty.h" - -HKEY open_regkey_fn(bool create, bool write, HKEY hk, const char *path, ...) -{ - HKEY toret = NULL; - bool hk_needs_close = false; - va_list ap; - va_start(ap, path); - - for (; path; path = va_arg(ap, const char *)) { - HKEY hk_sub = NULL; - - DWORD access = KEY_READ | (write ? KEY_WRITE : 0); - LONG status; - if (create) - status = RegCreateKeyEx( - hk, path, 0, NULL, REG_OPTION_NON_VOLATILE, - access, NULL, &hk_sub, NULL); - else - status = RegOpenKeyEx(hk, path, 0, access, &hk_sub); - - if (status != ERROR_SUCCESS) - goto out; - - if (hk_needs_close) - RegCloseKey(hk); - hk = hk_sub; - hk_needs_close = true; - } - - toret = hk; - hk = NULL; - hk_needs_close = false; - - out: - va_end(ap); - if (hk_needs_close) - RegCloseKey(hk); - return toret; -} - -void close_regkey(HKEY key) -{ - RegCloseKey(key); -} - -void del_regkey(HKEY key, const char *name) -{ - RegDeleteKey(key, name); -} - -char *enum_regkey(HKEY key, int index) -{ - size_t regbuf_size = MAX_PATH + 1; - char *regbuf = snewn(regbuf_size, char); - - while (1) { - LONG status = RegEnumKey(key, index, regbuf, regbuf_size); - if (status == ERROR_SUCCESS) - return regbuf; - if (status != ERROR_MORE_DATA) { - sfree(regbuf); - return NULL; - } - sgrowarray(regbuf, regbuf_size, regbuf_size); - } -} - -bool get_reg_dword(HKEY key, const char *name, DWORD *out) -{ - DWORD type, size; - size = sizeof(*out); - - if (RegQueryValueEx(key, name, 0, &type, - (BYTE *)out, &size) != ERROR_SUCCESS || - size != sizeof(*out) || type != REG_DWORD) - return false; - else - return true; -} - -bool put_reg_dword(HKEY key, const char *name, DWORD value) -{ - return RegSetValueEx(key, name, 0, REG_DWORD, (CONST BYTE *) &value, - sizeof(value)) == ERROR_SUCCESS; -} - -char *get_reg_sz(HKEY key, const char *name) -{ - DWORD type, size; - - if (RegQueryValueEx(key, name, 0, &type, NULL, - &size) != ERROR_SUCCESS || type != REG_SZ) - return NULL; /* not a string */ - - size_t allocsize = size+1; /* allow for an extra NUL if needed */ - char *toret = snewn(allocsize, char); - if (RegQueryValueEx(key, name, 0, &type, (BYTE *)toret, - &size) != ERROR_SUCCESS || type != REG_SZ) { - sfree(toret); - return NULL; - } - assert(size < allocsize); - toret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx - * didn't supply one */ - - return toret; -} - -bool put_reg_sz(HKEY key, const char *name, const char *str) -{ - /* You have to store the trailing NUL as well */ - return RegSetValueEx(key, name, 0, REG_SZ, (CONST BYTE *)str, - 1 + strlen(str)) == ERROR_SUCCESS; -} - -/* - * REG_MULTI_SZ items are stored as a concatenation of NUL-terminated - * strings, terminated in turn with an empty string, i.e. a second - * consecutive NUL. - * - * We represent these in their storage format, as a strbuf - but - * *without* the second consecutive NUL. - * - * So you can build up a new MULTI_SZ value in a strbuf by calling - * put_asciz once per output string and then put_reg_multi_sz; and you - * can consume one by initialising a BinarySource to the result of - * get_reg_multi_sz, and then calling get_asciz on it and assuming - * that !get_err(src) means you have a real output string. - * - * Also, calling strbuf_to_str on one of these will give you back a - * bare 'char *' with the same double-NUL termination, to pass back to - * a caller. - */ -strbuf *get_reg_multi_sz(HKEY key, const char *name) -{ - DWORD type, size; - - if (RegQueryValueEx(key, name, 0, &type, NULL, - &size) != ERROR_SUCCESS || type != REG_MULTI_SZ) - return NULL; /* not a string */ - - strbuf *toret = strbuf_new(); - void *ptr = strbuf_append(toret, (size_t)size + 2); - if (RegQueryValueEx(key, name, 0, &type, (BYTE *)ptr, - &size) != ERROR_SUCCESS || type != REG_MULTI_SZ) { - strbuf_free(toret); - return NULL; - } - strbuf_shrink_to(toret, size); - /* Ensure we end with exactly one \0 */ - while (strbuf_chomp(toret, '\0')); - put_byte(toret, '\0'); - return toret; -} - -bool put_reg_multi_sz(HKEY key, const char *name, strbuf *str) -{ - /* - * Of course, to write our string list into the registry, we _do_ - * have to include both trailing NULs. But this is easy, because a - * strbuf is also designed to hold a single string and make it - * conveniently accessible in NUL-terminated form, so it stores a - * NUL in its buffer just beyond its formal length. So we just - * include that extra byte in the data we write. - */ - return RegSetValueEx(key, name, 0, REG_MULTI_SZ, (CONST BYTE *)str->s, - str->len + 1) == ERROR_SUCCESS; -} - -char *get_reg_sz_simple(HKEY key, const char *name, const char *leaf) -{ - HKEY subkey = open_regkey_ro(key, name); - if (!subkey) - return NULL; - char *toret = get_reg_sz(subkey, leaf); - RegCloseKey(subkey); - return toret; -} diff --git a/WINDOWS/utils/request_file.c b/WINDOWS/utils/request_file.c deleted file mode 100644 index 57363cad1..000000000 --- a/WINDOWS/utils/request_file.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * GetOpenFileName/GetSaveFileName tend to muck around with the process' - * working directory on at least some versions of Windows. - * Here's a wrapper that gives more control over this, and hides a little - * bit of other grottiness. - */ - -#include "putty.h" - -struct filereq_tag { - TCHAR cwd[MAX_PATH]; - WCHAR wcwd[MAX_PATH]; -}; - -/* - * `of' is expected to be initialised with most interesting fields, but - * this function does some administrivia. (assume `of' was memset to 0) - * save==1 -> GetSaveFileName; save==0 -> GetOpenFileName - * `state' is optional. - */ -bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save) -{ - TCHAR cwd[MAX_PATH]; /* process CWD */ - bool ret; - - /* Get process CWD */ - if (preserve) { - DWORD r = GetCurrentDirectory(lenof(cwd), cwd); - if (r == 0 || r >= lenof(cwd)) - /* Didn't work, oh well. Stop trying to be clever. */ - preserve = false; - } - - /* Open the file requester, maybe setting lpstrInitialDir */ - { -#ifdef OPENFILENAME_SIZE_VERSION_400 - of->lStructSize = OPENFILENAME_SIZE_VERSION_400; -#else - of->lStructSize = sizeof(*of); -#endif - of->lpstrInitialDir = (state && state->cwd[0]) ? state->cwd : NULL; - /* Actually put up the requester. */ - ret = save ? GetSaveFileName(of) : GetOpenFileName(of); - } - - /* Get CWD left by requester */ - if (state) { - DWORD r = GetCurrentDirectory(lenof(state->cwd), state->cwd); - if (r == 0 || r >= lenof(state->cwd)) - /* Didn't work, oh well. */ - state->cwd[0] = '\0'; - } - - /* Restore process CWD */ - if (preserve) - /* If it fails, there's not much we can do. */ - (void) SetCurrentDirectory(cwd); - - return ret; -} - -/* - * Here's the same one again, the wide-string version - */ -bool request_file_w(filereq *state, OPENFILENAMEW *of, - bool preserve, bool save) -{ - WCHAR cwd[MAX_PATH]; /* process CWD */ - bool ret; - - /* Get process CWD */ - if (preserve) { - DWORD r = GetCurrentDirectoryW(lenof(cwd), cwd); - if (r == 0 || r >= lenof(cwd)) - /* Didn't work, oh well. Stop trying to be clever. */ - preserve = false; - } - - /* Open the file requester, maybe setting lpstrInitialDir */ - { - of->lStructSize = sizeof(*of); - of->lpstrInitialDir = (state && state->wcwd[0]) ? state->wcwd : NULL; - /* Actually put up the requester. */ - ret = save ? GetSaveFileNameW(of) : GetOpenFileNameW(of); - } - - /* Get CWD left by requester */ - if (state) { - DWORD r = GetCurrentDirectoryW(lenof(state->wcwd), state->wcwd); - if (r == 0 || r >= lenof(state->wcwd)) - /* Didn't work, oh well. */ - state->wcwd[0] = L'\0'; - } - - /* Restore process CWD */ - if (preserve) - /* If it fails, there's not much we can do. */ - (void) SetCurrentDirectoryW(cwd); - - return ret; -} - -filereq *filereq_new(void) -{ - filereq *state = snew(filereq); - state->cwd[0] = '\0'; - state->wcwd[0] = L'\0'; - return state; -} - -void filereq_free(filereq *state) -{ - sfree(state); -} diff --git a/WINDOWS/utils/screenshot.c b/WINDOWS/utils/screenshot.c deleted file mode 100644 index 777520fd4..000000000 --- a/WINDOWS/utils/screenshot.c +++ /dev/null @@ -1,126 +0,0 @@ -#include "putty.h" - -#if HAVE_DWMAPI_H - -#include - -char *save_screenshot(HWND hwnd, const char *outfile) -{ - HDC dcWindow = NULL, dcSave = NULL; - HBITMAP bmSave = NULL; - uint8_t *buffer = NULL; - char *err = NULL; - - static HMODULE dwmapi_module; - DECL_WINDOWS_FUNCTION(static, HRESULT, DwmGetWindowAttribute, - (HWND, DWORD, PVOID, DWORD)); - - if (!dwmapi_module) { - dwmapi_module = load_system32_dll("dwmapi.dll"); - GET_WINDOWS_FUNCTION(dwmapi_module, DwmGetWindowAttribute); - } - - dcWindow = GetDC(NULL); - if (!dcWindow) { - err = dupprintf("GetDC(window): %s", win_strerror(GetLastError())); - goto out; - } - - int x, y, w, h; - RECT wr; - - /* Use DwmGetWindowAttribute in place of GetWindowRect to exclude - * drop shadow, otherwise we get a load of unwanted desktop - * background under the shadow */ - if (p_DwmGetWindowAttribute && - 0 <= p_DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, - &wr, sizeof(wr))) { - x = wr.left; - y = wr.top; - w = wr.right - wr.left; - h = wr.bottom - wr.top; - } else { - BITMAP bmhdr; - memset(&bmhdr, 0, sizeof(bmhdr)); - GetObject(GetCurrentObject(dcWindow, OBJ_BITMAP), - sizeof(bmhdr), &bmhdr); - x = y = 0; - w = bmhdr.bmWidth; - h = bmhdr.bmHeight; - } - - dcSave = CreateCompatibleDC(dcWindow); - if (!dcSave) { - err = dupprintf("CreateCompatibleDC(desktop window dc): %s", - win_strerror(GetLastError())); - goto out; - } - - bmSave = CreateCompatibleBitmap(dcWindow, w, h); - if (!bmSave) { - err = dupprintf("CreateCompatibleBitmap: %s", - win_strerror(GetLastError())); - goto out; - } - - if (!SelectObject(dcSave, bmSave)) { - err = dupprintf("SelectObject: %s", win_strerror(GetLastError())); - goto out; - } - - if (!BitBlt(dcSave, 0, 0, w, h, dcWindow, x, y, SRCCOPY)) { - err = dupprintf("BitBlt: %s", win_strerror(GetLastError())); - goto out; - } - - BITMAPINFO bmInfo; - memset(&bmInfo, 0, sizeof(bmInfo)); - bmInfo.bmiHeader.biSize = sizeof(bmInfo.bmiHeader); - bmInfo.bmiHeader.biWidth = w; - bmInfo.bmiHeader.biHeight = h; - bmInfo.bmiHeader.biPlanes = 1; - bmInfo.bmiHeader.biBitCount = 32; - bmInfo.bmiHeader.biCompression = BI_RGB; - - size_t bmPixels = (size_t)w*h, bmBytes = bmPixels * 4; - buffer = snewn(bmBytes, uint8_t); - - if (!GetDIBits(dcWindow, bmSave, 0, h, buffer, &bmInfo, DIB_RGB_COLORS)) - err = dupprintf("GetDIBits (get data): %s", - win_strerror(GetLastError())); - - FILE *fp = fopen(outfile, "wb"); - if (!fp) { - err = dupprintf("'%s': unable to open file", outfile); - goto out; - } - - BITMAPFILEHEADER bmFileHdr; - bmFileHdr.bfType = 'B' | ('M' << 8); - bmFileHdr.bfSize = sizeof(bmFileHdr) + sizeof(bmInfo.bmiHeader) + bmBytes; - bmFileHdr.bfOffBits = sizeof(bmFileHdr) + sizeof(bmInfo.bmiHeader); - fwrite((void *)&bmFileHdr, 1, sizeof(bmFileHdr), fp); - fwrite((void *)&bmInfo.bmiHeader, 1, sizeof(bmInfo.bmiHeader), fp); - fwrite((void *)buffer, 1, bmBytes, fp); - fclose(fp); - - out: - if (dcWindow) - ReleaseDC(NULL, dcWindow); - if (bmSave) - DeleteObject(bmSave); - if (dcSave) - DeleteObject(dcSave); - sfree(buffer); - return err; -} - -#else /* HAVE_DWMAPI_H */ - -/* Without we can't get the right window rectangle */ -char *save_screenshot(HWND hwnd, const char *outfile) -{ - return dupstr("Demo screenshots not compiled in to this build"); -} - -#endif /* HAVE_DWMAPI_H */ diff --git a/WINDOWS/utils/security.c b/WINDOWS/utils/security.c deleted file mode 100644 index 522994205..000000000 --- a/WINDOWS/utils/security.c +++ /dev/null @@ -1,340 +0,0 @@ -/* - * windows/utils/security.c: implementation of security-api.h. - */ - -#include -#include - -#include "putty.h" - -#include "security-api.h" - -/* Initialised once, then kept around to reuse forever */ -static PSID worldsid, networksid, usersid; - -DEF_WINDOWS_FUNCTION(OpenProcessToken); -DEF_WINDOWS_FUNCTION(GetTokenInformation); -DEF_WINDOWS_FUNCTION(InitializeSecurityDescriptor); -DEF_WINDOWS_FUNCTION(SetSecurityDescriptorOwner); -DEF_WINDOWS_FUNCTION(GetSecurityInfo); -DEF_WINDOWS_FUNCTION(SetSecurityInfo); -DEF_WINDOWS_FUNCTION(SetEntriesInAclA); - -bool should_have_security(void) -{ -#ifdef LEGACY_WINDOWS - /* Legacy pre-NT platforms are not expected to have any of these APIs */ - init_winver(); - return (osPlatformId == VER_PLATFORM_WIN32_NT); -#else - /* In the up-to-date PuTTY builds which do not support those - * platforms, unconditionally return true, to minimise the risk of - * compiling out security checks. */ - return true; -#endif -} - -bool got_advapi(void) -{ - static bool attempted = false; - static bool 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; -} - -static bool getsids(char **error) -{ -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wmissing-braces" -#endif - SID_IDENTIFIER_AUTHORITY world_auth = SECURITY_WORLD_SID_AUTHORITY; - SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - - bool ret = false; - - *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: - return ret; -} - - -bool make_private_security_descriptor(DWORD permissions, - PSECURITY_DESCRIPTOR *psd, - PACL *acl, - char **error) -{ - EXPLICIT_ACCESS ea[3]; - int acl_err; - bool 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; -} - -static bool acl_restricted = false; -bool restricted_acl(void) { return acl_restricted; } - -static bool really_restrict_process_acl(char **error) -{ - EXPLICIT_ACCESS ea[2]; - int acl_err; - bool 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; - } - - acl_restricted = true; - ret=true; - - cleanup: - if (!ret) { - if (acl) { - LocalFree(acl); - acl = NULL; - } - } - return ret; -} - -/* - * Lock down our process's ACL, to present an obstacle to malware - * trying to write into its memory. This can't be a full defence, - * because well timed malware could attack us before this code runs - - * even if it was unconditionally run at the very start of main(), - * which we wouldn't want to do anyway because it turns out in practie - * that interfering with other processes in this way has significant - * non-infringing uses on Windows (e.g. screen reader software). - * - * If we've been requested to do this and are unsuccessful, bomb out - * via modalfatalbox rather than continue in a less protected mode. - * - * This function is intentionally outside the #ifndef NO_SECURITY that - * covers the rest of this file, because when PuTTY is compiled - * without the ability to restrict its ACL, we don't want it to - * silently pretend to honour the instruction to do so. - */ -void restrict_process_acl(void) -{ - char *error = NULL; - bool ret; - - ret = really_restrict_process_acl(&error); - if (!ret) - modalfatalbox("Could not restrict process ACL: %s", error); -} diff --git a/WINDOWS/utils/shinydialogbox.c b/WINDOWS/utils/shinydialogbox.c deleted file mode 100644 index ab3201c1a..000000000 --- a/WINDOWS/utils/shinydialogbox.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * PuTTY's own reimplementation of DialogBox() and EndDialog() which - * provide extra capabilities. - * - * Originally introduced in 2003 to work around a problem with our - * message loops, in which keystrokes pressed in the 'Change Settings' - * dialog in mid-session would _also_ be delivered to the main - * terminal window. - * - * But once we had our own wrapper it was convenient to put further - * things into it. In particular, this system allows you to provide a - * context pointer at setup time that's easy to retrieve from the - * window procedure. - */ - -#include "putty.h" - -struct ShinyDialogBoxState { - bool ended; - int result; - ShinyDlgProc proc; - void *ctx; -}; - -/* - * For use in re-entrant calls to the dialog procedure from - * CreateDialog itself, temporarily store the state pointer that we - * won't store in the usual window-memory slot until later. - * - * I don't _intend_ that this system will need to be used in multiple - * threads at all, let alone concurrently, but just in case, declaring - * sdb_tempstate as thread-local will protect against that possibility. - */ -static THREADLOCAL struct ShinyDialogBoxState *sdb_tempstate; - -static inline struct ShinyDialogBoxState *ShinyDialogGetState(HWND hwnd) -{ - if (sdb_tempstate) - return sdb_tempstate; - return (struct ShinyDialogBoxState *)GetWindowLongPtr( - hwnd, DLGWINDOWEXTRA); -} - -static INT_PTR CALLBACK ShinyRealDlgProc(HWND hwnd, UINT msg, - WPARAM wParam, LPARAM lParam) -{ - struct ShinyDialogBoxState *state = ShinyDialogGetState(hwnd); - return state->proc(hwnd, msg, wParam, lParam, state->ctx); -} - -int ShinyDialogBox(HINSTANCE hinst, LPCTSTR tmpl, const char *winclass, - HWND hwndparent, ShinyDlgProc proc, void *ctx) -{ - /* - * Register the window class ourselves in such a way that we - * allocate an extra word of window memory to store the state - * pointer. - * - * It would be nice in principle to load the dialog template - * resource and dig the class name out of it. But DLGTEMPLATEEX is - * a nasty variable-layout structure not declared conveniently in - * a header file, so I think that's too much effort. Callers of - * this function will just have to provide the right window class - * name to match their template resource via the 'winclass' - * parameter. - */ - WNDCLASS wc; - wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW; - wc.lpfnWndProc = DefDlgProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = DLGWINDOWEXTRA + sizeof(LONG_PTR); - wc.hInstance = hinst; - wc.hIcon = NULL; - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1); - wc.lpszMenuName = NULL; - wc.lpszClassName = winclass; - RegisterClass(&wc); - - struct ShinyDialogBoxState state[1]; - state->ended = false; - state->proc = proc; - state->ctx = ctx; - - sdb_tempstate = state; - HWND hwnd = CreateDialog(hinst, tmpl, hwndparent, ShinyRealDlgProc); - SetWindowLongPtr(hwnd, DLGWINDOWEXTRA, (LONG_PTR)state); - sdb_tempstate = NULL; - - MSG msg; - int gm; - while ((gm = GetMessage(&msg, NULL, 0, 0)) > 0) { - if (!state->ended && !IsDialogMessage(hwnd, &msg)) - DispatchMessage(&msg); - if (state->ended) - break; - } - - if (gm == 0) - PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */ - - DestroyWindow(hwnd); - return state->result; -} - -void ShinyEndDialog(HWND hwnd, int ret) -{ - struct ShinyDialogBoxState *state = ShinyDialogGetState(hwnd); - state->result = ret; - state->ended = true; -} diff --git a/WINDOWS/utils/split_into_argv.c b/WINDOWS/utils/split_into_argv.c deleted file mode 100644 index 11de889bb..000000000 --- a/WINDOWS/utils/split_into_argv.c +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Split a complete command line into argc/argv, attempting to do it - * exactly the same way the Visual Studio C library would do it (so - * that our console utilities, which receive argc and argv already - * broken apart by the C library, will have their command lines - * processed in the same way as the GUI utilities which get a whole - * command line and must call this function). - * - * Does not modify the input command line. - * - * The final parameter (argstart) is used to return a second array - * of char * pointers, the same length as argv, each one pointing - * at the start of the corresponding element of argv in the - * original command line. So if you get half way through processing - * your command line in argc/argv form and then decide you want to - * treat the rest as a raw string, you can. If you don't want to, - * `argstart' can be safely left NULL. - */ - -#include "putty.h" - -/* - * The precise argument-breaking rules vary with compiler version, or - * rather, with the crt0-type startup code that comes with each - * compiler's C library. We do our best to match the compiler version, - * so that we faithfully imitate in our GUI utilities what the - * corresponding set of CLI utilities can't be prevented from doing. - * - * The basic rules are: - * - * - Single quotes are not special characters. - * - * - Double quotes are removed, but within them spaces cease to be - * special. - * - * - Backslashes are _only_ special when a sequence of them appear - * just before a double quote. In this situation, they are treated - * like C backslashes: so \" just gives a literal quote, \\" gives - * a literal backslash and then opens or closes a double-quoted - * segment, \\\" gives a literal backslash and then a literal - * quote, \\\\" gives two literal backslashes and then opens/closes - * a double-quoted segment, and so forth. Note that this behaviour - * is identical inside and outside double quotes. - * - * - Two successive double quotes become one literal double quote, - * but only _inside_ a double-quoted segment. Outside, they just - * form an empty double-quoted segment (which may cause an empty - * argument word). - * - * That only leaves the interesting question of what happens when one - * or more backslashes precedes two or more double quotes, starting - * inside a double-quoted string. - * - * Modern Visual Studio (as of 2021) - * --------------------------------- - * - * I investigated this in an ordinary CLI program, using the - * toolchain's crt0 to split a command line of the form - * - * "a\\\"""b c" d - * - * Here I tabulate number of backslashes (across the top) against - * number of quotes (down the left), and indicate how many backslashes - * are output, how many quotes are output, and whether a quoted - * segment is open at the end of the sequence: - * - * backslashes - * - * 0 1 2 3 4 - * - * 0 0,0,y | 1,0,y 2,0,y 3,0,y 4,0,y - * --------+----------------------------- - * 1 0,0,n | 0,1,y 1,0,n 1,1,y 2,0,n - * q 2 0,1,y | 0,1,n 1,1,y 1,1,n 2,1,y - * u 3 0,1,n | 0,2,y 1,1,n 1,2,y 2,1,n - * o 4 0,2,y | 0,2,n 1,2,y 1,2,n 2,2,y - * t 5 0,2,n | 0,3,y 1,2,n 1,3,y 2,2,n - * e 6 0,3,y | 0,3,n 1,3,y 1,3,n 2,3,y - * s 7 0,3,n | 0,4,y 1,3,n 1,4,y 2,3,n - * 8 0,4,y | 0,4,n 1,4,y 1,4,n 2,4,y - * - * The row at the top of this table, with quotes=0, demonstrates what - * I claimed above, that when a sequence of backslashes are not - * followed by a double quote, they don't act specially at all. The - * rest of the table shows that the backslashes escape each other in - * pairs (so that with 2n or 2n+1 input backslashes you get n output - * ones); if there's an odd number of input backslashes then the last - * one escapes the first double quote (so you get a literal quote and - * enter a quoted string); thereafter, each input quote character - * either opens or closes a quoted string, and if it closes one, it - * generates a literal " as a side effect. - * - * Older Visual Studio - * ------------------- - * - * But here's the corresponding table from the older Visual Studio 7: - * - * backslashes - * - * 0 1 2 3 4 - * - * 0 0,0,y | 1,0,y 2,0,y 3,0,y 4,0,y - * --------+----------------------------- - * 1 0,0,n | 0,1,y 1,0,n 1,1,y 2,0,n - * q 2 0,1,n | 0,1,n 1,1,n 1,1,n 2,1,n - * u 3 0,1,y | 0,2,n 1,1,y 1,2,n 2,1,y - * o 4 0,1,n | 0,2,y 1,1,n 1,2,y 2,1,n - * t 5 0,2,n | 0,2,n 1,2,n 1,2,n 2,2,n - * e 6 0,2,y | 0,3,n 1,2,y 1,3,n 2,2,y - * s 7 0,2,n | 0,3,y 1,2,n 1,3,y 2,2,n - * 8 0,3,n | 0,3,n 1,3,n 1,3,n 2,3,n - * 9 0,3,y | 0,4,n 1,3,y 1,4,n 2,3,y - * 10 0,3,n | 0,4,y 1,3,n 1,4,y 2,3,n - * 11 0,4,n | 0,4,n 1,4,n 1,4,n 2,4,n - * - * There is very weird mod-3 behaviour going on here in the number of - * quotes, and it even applies when there aren't any backslashes! How - * ghastly. - * - * With a bit of thought, this extremely odd diagram suddenly - * coalesced itself into a coherent, if still ghastly, model of how - * things work: - * - * - As before, backslashes are only special when one or more of them - * appear contiguously before at least one double quote. In this - * situation the backslashes do exactly what you'd expect: each one - * quotes the next thing in front of it, so you end up with n/2 - * literal backslashes (if n is even) or (n-1)/2 literal - * backslashes and a literal quote (if n is odd). In the latter - * case the double quote character right after the backslashes is - * used up. - * - * - After that, any remaining double quotes are processed. A string - * of contiguous unescaped double quotes has a mod-3 behaviour: - * - * * inside a quoted segment, a quote ends the segment. - * * _immediately_ after ending a quoted segment, a quote simply - * produces a literal quote. - * * otherwise, outside a quoted segment, a quote begins a quoted - * segment. - * - * So, for example, if we started inside a quoted segment then two - * contiguous quotes would close the segment and produce a literal - * quote; three would close the segment, produce a literal quote, - * and open a new segment. If we started outside a quoted segment, - * then two contiguous quotes would open and then close a segment, - * producing no output (but potentially creating a zero-length - * argument); but three quotes would open and close a segment and - * then produce a literal quote. - * - * I don't know exactly when the bug fix happened, but I know that VS7 - * had the odd mod-3 behaviour. So the #if below will ensure that - * modern (2015 onwards) versions of VS use the new more sensible - * behaviour, and VS7 uses the old one. Things in between may be - * wrong; if anyone cares, patches to change the cutoff version in - * this #if are welcome. - */ -#if _MSC_VER < 1400 -#define MOD3 1 -#else -#define MOD3 0 -#endif - -#ifdef SPLIT_INTO_ARGV_W -#define FUNCTION split_into_argv_w -#define CHAR wchar_t -#define STRLEN wcslen -#else -#define FUNCTION split_into_argv -#define CHAR char -#define STRLEN strlen -#endif - -static inline bool is_word_sep(CHAR c) -{ - return c == ' ' || c == '\t'; -} - -void FUNCTION(CHAR *cmdline, bool includes_program_name, - int *argc, CHAR ***argv, CHAR ***argstart) -{ - CHAR *p; - CHAR *outputline, *q; - CHAR **outputargv, **outputargstart; - int outputargc; - - /* - * First deal with the simplest of all special cases: if there - * aren't any arguments, return 0,NULL,NULL. - */ - while (*cmdline && is_word_sep(*cmdline)) cmdline++; - if (!*cmdline) { - if (argc) *argc = 0; - if (argv) *argv = NULL; - if (argstart) *argstart = NULL; - return; - } - - /* - * This will guaranteeably be big enough; we can realloc it - * down later. - */ - { - size_t len = STRLEN(cmdline); - outputline = snewn(1+len, CHAR); - outputargv = snewn((len+1) / 2, CHAR *); - outputargstart = snewn((len+1) / 2, CHAR *); - } - - p = cmdline; q = outputline; outputargc = 0; - - while (*p) { - bool quote; - - /* Skip whitespace searching for start of argument. */ - while (*p && is_word_sep(*p)) p++; - if (!*p) break; - - /* - * Check if this argument is the program name. If so, - * different rules apply. - * - * In most arguments, the special characters are the double - * quote and the backslash. An exception is the program name - * at the start of the command line, in which backslashes are - * _not_ special - if one appears before a quote, it does not - * make the quote literal. - * - * The C library must implement this special rule, and we must - * follow suit here, in order to match the way CreateProcess - * scans the command line to determine the program name. It - * will consider that all these commands refer to the same - * file equally validly: - * - * "C:\Program Files\Foo"\bar.exe - * "C:\Program Files\Foo\"bar.exe - * "C:\Program Files\Foo\bar.exe" - * - * Each one contains a quoted section that protects the space - * in "Program Files", and the closing quote takes effect the - * same in all cases - even though, in the middle case, it's - * immediately preceded by one of the path separators in the - * name. For CreateProcess, backslashes aren't special. - * - * So, if our caller told us that the input command line - * includes the program name (which it does if it came from - * GetCommandLine, but not if it was passed in to WinMain), - * then we must treat the 0th output argument specially, by - * not considering backslashes to affect the quoting. - */ - bool backslash_special = !(outputargc == 0 && includes_program_name); - - /* We have an argument; start it. */ - outputargv[outputargc] = q; - outputargstart[outputargc] = p; - outputargc++; - quote = false; - - /* Copy data into the argument until it's finished. */ - while (*p) { - if (!quote && is_word_sep(*p)) - break; /* argument is finished */ - - if (*p == '"' || (*p == '\\' && backslash_special)) { - /* - * We have a sequence of zero or more backslashes - * followed by a sequence of zero or more quotes. - * Count up how many of each, and then deal with - * them as appropriate. - */ - int i, slashes = 0, quotes = 0; - while (*p == '\\') slashes++, p++; - while (*p == '"') quotes++, p++; - - if (!quotes) { - /* - * Special case: if there are no quotes, - * slashes are not special at all, so just copy - * n slashes to the output string. - */ - while (slashes--) *q++ = '\\'; - } else { - /* Slashes annihilate in pairs. */ - while (slashes >= 2) slashes -= 2, *q++ = '\\'; - - /* One remaining slash takes out the first quote. */ - if (slashes) quotes--, *q++ = '"'; - - if (quotes > 0) { - /* Outside a quote segment, a quote starts one. */ - if (!quote) quotes--; - -#if !MOD3 - /* New behaviour: produce n/2 literal quotes... */ - for (i = 2; i <= quotes; i += 2) *q++ = '"'; - /* ... and end in a quote segment iff 2 divides n. */ - quote = (quotes % 2 == 0); -#else - /* Old behaviour: produce (n+1)/3 literal quotes... */ - for (i = 3; i <= quotes+1; i += 3) *q++ = '"'; - /* ... and end in a quote segment iff 3 divides n. */ - quote = (quotes % 3 == 0); -#endif - } - } - } else { - *q++ = *p++; - } - } - - /* At the end of an argument, just append a trailing NUL. */ - *q++ = '\0'; - } - - outputargv = sresize(outputargv, outputargc, CHAR *); - outputargstart = sresize(outputargstart, outputargc, CHAR *); - - if (argc) *argc = outputargc; - if (argv) *argv = outputargv; else sfree(outputargv); - if (argstart) *argstart = outputargstart; else sfree(outputargstart); -} diff --git a/WINDOWS/utils/split_into_argv_w.c b/WINDOWS/utils/split_into_argv_w.c deleted file mode 100644 index a125e54d3..000000000 --- a/WINDOWS/utils/split_into_argv_w.c +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Unicode version of split_into_argv. - */ - -#define SPLIT_INTO_ARGV_W -#include "split_into_argv.c" diff --git a/WINDOWS/utils/strtoumax.c b/WINDOWS/utils/strtoumax.c deleted file mode 100644 index 38d00014c..000000000 --- a/WINDOWS/utils/strtoumax.c +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Work around lack of strtoumax in older MSVC libraries. - */ - -#include - -#include "defs.h" - -uintmax_t strtoumax(const char *nptr, char **endptr, int base) -{ - return _strtoui64(nptr, endptr, base); -} diff --git a/WINDOWS/utils/version.c b/WINDOWS/utils/version.c deleted file mode 100644 index b626710e6..000000000 --- a/WINDOWS/utils/version.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "putty.h" - -DWORD osMajorVersion, osMinorVersion, osPlatformId; - -void init_winver(void) -{ - static bool initialised = false; - if (initialised) - return; - initialised = true; - - OSVERSIONINFO osVersion; - static HMODULE kernel32_module; - DECL_WINDOWS_FUNCTION(static, BOOL, GetVersionExA, (LPOSVERSIONINFO)); - - if (!kernel32_module) { - kernel32_module = load_system32_dll("kernel32.dll"); - /* Deliberately don't type-check this function, because that - * would involve using its declaration in a header file which - * triggers a deprecation warning. I know it's deprecated (see - * below) and don't need telling. */ - GET_WINDOWS_FUNCTION_NO_TYPECHECK(kernel32_module, GetVersionExA); - } - - ZeroMemory(&osVersion, sizeof(osVersion)); - osVersion.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); - if (p_GetVersionExA && p_GetVersionExA(&osVersion)) { - osMajorVersion = osVersion.dwMajorVersion; - osMinorVersion = osVersion.dwMinorVersion; - osPlatformId = osVersion.dwPlatformId; - } else { - /* - * GetVersionEx is deprecated, so allow for it perhaps going - * away in future API versions. If it's not there, simply - * assume that's because Windows is too _new_, so fill in the - * variables we care about to a value that will always compare - * higher than any given test threshold. - * - * Normally we should be checking against the presence of a - * specific function if possible in any case. - */ - osMajorVersion = osMinorVersion = UINT_MAX; /* a very high number */ - osPlatformId = VER_PLATFORM_WIN32_NT; /* not Win32s or Win95-like */ - } -} diff --git a/WINDOWS/utils/win_strerror.c b/WINDOWS/utils/win_strerror.c deleted file mode 100644 index 5572bc8b0..000000000 --- a/WINDOWS/utils/win_strerror.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Wrapper around the Windows FormatMessage system for retrieving the - * text of a system error code, with a simple API similar to strerror. - * - * Works by keeping a tree234 containing mappings from system error - * codes to strings. Entries allocated in this tree are simply never - * freed. - * - * Also, the returned string has its trailing newline removed (so it - * can go in places like the Event Log that never want a newline), and - * is prefixed with the error number (so that if a user sends an error - * report containing a translated error message we can't read, we can - * still find out what the error actually was). - */ - -#include "putty.h" - -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) { - char msgtext[65536]; /* maximum size for FormatMessage is 64K */ - - es = snew(struct errstring); - es->error = error; - if (!FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS), NULL, error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - msgtext, lenof(msgtext)-1, NULL)) { - sprintf(msgtext, - "(unable to format: FormatMessage returned %u)", - (unsigned int)GetLastError()); - } else { - int len = strlen(msgtext); - if (len > 0 && msgtext[len-1] == '\n') - msgtext[len-1] = '\0'; - } - es->text = dupprintf("Error %d: %s", error, msgtext); - add234(errstrings, es); - } - - return es->text; -} diff --git a/WINDOWS/version.rc2 b/WINDOWS/version.rc2 deleted file mode 100644 index 971446bb9..000000000 --- a/WINDOWS/version.rc2 +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Standard Windows version information. - * (For inclusion in other .rc files with appropriate macro definitions.) - * - * This file has the more or less arbitrary extension '.rc2' to avoid - * IDEs taking it to be a top-level resource script in its own right - * (which has been known to happen if the extension was '.rc'), and - * also to avoid the resource compiler ignoring everything included - * from it (which happens if the extension is '.h'). - */ - -#include "version.h" -#include "licence.h" - -/* - * The actual VERSIONINFO resource. - */ -VS_VERSION_INFO VERSIONINFO -/* (None of this "fixed" info appears to be trivially user-visible on - * Win98SE. The binary version does show up on Win2K.) */ -FILEVERSION BINARY_VERSION -PRODUCTVERSION BINARY_VERSION /* version of whole suite */ -FILEFLAGSMASK VS_FF_DEBUG | VS_FF_PRERELEASE | VS_FF_PRIVATEBUILD -FILEFLAGS 0x0L -#if defined DEBUG - | VS_FF_DEBUG -#endif -#if defined SNAPSHOT || defined PRERELEASE - | VS_FF_PRERELEASE -#elif !defined RELEASE - | VS_FF_PRIVATEBUILD -#endif -FILEOS VOS__WINDOWS32 -FILETYPE VFT_APP -FILESUBTYPE 0x0L /* n/a for VFT_APP */ -BEGIN - /* (On Win98SE and Win2K, we can see most of this on the Version tab - * in the file properties in Explorer.) */ - BLOCK "StringFileInfo" - BEGIN - /* "lang-charset" LLLLCCCC = (UK English, Unicode) */ - BLOCK "080904B0" - BEGIN - VALUE "CompanyName", "Simon Tatham" /* required :/ */ - VALUE "ProductName", "PuTTYNG" - VALUE "FileDescription", APPDESC - VALUE "InternalName", APPNAME - VALUE "OriginalFilename", APPNAME -#if (defined HELPVER) - /* FIXME: this doesn't seem to be visible in Win7/Win10's UI. - * Oh well. */ - VALUE "FileVersion", TEXTVER HELPVER -#else - VALUE "FileVersion", TEXTVER -#endif - VALUE "ProductVersion", "4 mRemoteNG" - VALUE "LegalCopyright", "Copyright \251 " SHORT_COPYRIGHT_DETAILS "." -#if (!defined SNAPSHOT) && (!defined RELEASE) && (!defined PRERELEASE) - /* Only if VS_FF_PRIVATEBUILD. */ - VALUE "PrivateBuild", TEXTVER /* NBI */ -#endif - END - END - BLOCK "VarFileInfo" - BEGIN - /* Once again -- same meanings -- apparently necessary */ - VALUE "Translation", 0x809, 1200 - END -END diff --git a/WINDOWS/website.url b/WINDOWS/website.url deleted file mode 100644 index 4f6d47d1f7136fed66f8bd3d36f26e7315f5496a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104 zcma#{%qvMP%1bQ?&d4t+NiHpkwgslistnode.prev = wgslisthead.prev; - wgs->wgslistnode.next = &wgslisthead; - wgs->wgslistnode.prev->next = &wgs->wgslistnode; - wgs->wgslistnode.next->prev = &wgs->wgslistnode; -} - -static inline void wgs_unlink(WinGuiSeat *wgs) -{ - wgs->wgslistnode.prev->next = wgs->wgslistnode.next; - wgs->wgslistnode.next->prev = wgs->wgslistnode.prev; -} diff --git a/WINDOWS/window.c b/WINDOWS/window.c deleted file mode 100644 index 69cc3d3dd..000000000 --- a/WINDOWS/window.c +++ /dev/null @@ -1,5914 +0,0 @@ -/* - * window.c - the PuTTY(tel)/pterm main program, which runs a PuTTY - * terminal emulator and backend in a window. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define COMPILE_MULTIMON_STUBS - -#include "putty.h" -#include "ssh.h" -#include "terminal.h" -#include "storage.h" -#include "putty-rc.h" -#include "security-api.h" -#include "win-gui-seat.h" -#include "tree234.h" - -#ifdef NO_MULTIMON -#include -#endif - -#include -#include -#include -#include - -/* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of - * wParam are used by Windows, and should be masked off, so we shouldn't - * attempt to store information in them. Hence all these identifiers have - * the low 4 bits clear. Also, identifiers should < 0xF000. */ - -#define IDM_SHOWLOG 0x0010 -#define IDM_NEWSESS 0x0020 -#define IDM_DUPSESS 0x0030 -#define IDM_RESTART 0x0040 -#define IDM_RECONF 0x0050 -#define IDM_CLRSB 0x0060 -#define IDM_RESET 0x0070 -#define IDM_HELP 0x0140 -#define IDM_ABOUT 0x0150 -#define IDM_SAVEDSESS 0x0160 -#define IDM_COPYALL 0x0170 -#define IDM_FULLSCREEN 0x0180 -#define IDM_COPY 0x0190 -#define IDM_PASTE 0x01A0 -#define IDM_SPECIALSEP 0x0200 - -#define IDM_SPECIAL_MIN 0x0400 -#define IDM_SPECIAL_MAX 0x0800 - -#define IDM_SAVED_MIN 0x1000 -#define IDM_SAVED_MAX 0x5000 -#define MENU_SAVED_STEP 16 -/* Maximum number of sessions on saved-session submenu */ -#define MENU_SAVED_MAX ((IDM_SAVED_MAX-IDM_SAVED_MIN) / MENU_SAVED_STEP) - -#define WM_IGNORE_CLIP (WM_APP + 2) -#define WM_FULLSCR_ON_MAX (WM_APP + 3) -#define WM_GOT_CLIPDATA (WM_APP + 4) - -/* Needed for Chinese support and apparently not always defined. */ -#ifndef VK_PROCESSKEY -#define VK_PROCESSKEY 0xE5 -#endif - -/* Mouse wheel support. */ -#ifndef WM_MOUSEWHEEL -#define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */ -#endif -#ifndef WM_MOUSEHWHEEL -#define WM_MOUSEHWHEEL 0x020E /* not defined in earlier SDKs */ -#endif -#ifndef WHEEL_DELTA -#define WHEEL_DELTA 120 -#endif - -/* DPI awareness support */ -#ifndef WM_DPICHANGED -#define WM_DPICHANGED 0x02E0 -#define WM_DPICHANGED_BEFOREPARENT 0x02E2 -#define WM_DPICHANGED_AFTERPARENT 0x02E3 -#define WM_GETDPISCALEDSIZE 0x02E4 -#endif - -/* VK_PACKET, used to send Unicode characters in WM_KEYDOWNs */ -#ifndef VK_PACKET -#define VK_PACKET 0xE7 -#endif - -static Mouse_Button translate_button(WinGuiSeat *wgs, Mouse_Button button); -static void show_mouseptr(WinGuiSeat *wgs, bool show); -static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); -static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam, - LPARAM lParam, unsigned char *output); -static void init_palette(WinGuiSeat *wgs); -static void init_fonts(WinGuiSeat *wgs, int, int); -static void init_dpi_info(WinGuiSeat *wgs); -static void another_font(WinGuiSeat *wgs, int); -static void deinit_fonts(WinGuiSeat *wgs); -static void set_input_locale(WinGuiSeat *wgs, HKL); -static void update_savedsess_menu(WinGuiSeat *wgs); -static void init_winfuncs(void); - -static bool is_full_screen(WinGuiSeat *wgs); -static void make_full_screen(WinGuiSeat *wgs); -static void clear_full_screen(WinGuiSeat *wgs); -static void flip_full_screen(WinGuiSeat *wgs); -static void process_clipdata(WinGuiSeat *wgs, HGLOBAL clipdata, bool unicode); -static void setup_clipboards(Terminal *, Conf *); - -/* Window layout information */ -static void reset_window(WinGuiSeat *wgs, int reinit); - -static void flash_window(WinGuiSeat *wgs, int mode); -static void sys_cursor_update(WinGuiSeat *wgs); -static bool get_fullscreen_rect(WinGuiSeat *wgs, RECT *ss); - -static void conf_cache_data(WinGuiSeat *wgs); - -static struct sesslist sesslist; /* for saved-session menu */ - -enum MONITOR_DPI_TYPE { MDT_EFFECTIVE_DPI, MDT_ANGULAR_DPI, MDT_RAW_DPI, MDT_DEFAULT }; -DECL_WINDOWS_FUNCTION(static, BOOL, GetMonitorInfoA, (HMONITOR, LPMONITORINFO)); -DECL_WINDOWS_FUNCTION(static, HMONITOR, MonitorFromPoint, (POINT, DWORD)); -DECL_WINDOWS_FUNCTION(static, HMONITOR, MonitorFromWindow, (HWND, DWORD)); -DECL_WINDOWS_FUNCTION(static, HRESULT, GetDpiForMonitor, (HMONITOR hmonitor, enum MONITOR_DPI_TYPE dpiType, UINT *dpiX, UINT *dpiY)); -DECL_WINDOWS_FUNCTION(static, HRESULT, GetSystemMetricsForDpi, (int nIndex, UINT dpi)); -DECL_WINDOWS_FUNCTION(static, HRESULT, AdjustWindowRectExForDpi, (LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi)); - -static UINT wm_mousewheel = WM_MOUSEWHEEL; - -struct WinGuiSeatListNode wgslisthead = { - .next = &wgslisthead, .prev = &wgslisthead, -}; - -#define IS_HIGH_VARSEL(wch1, wch2) \ - ((wch1) == 0xDB40 && ((wch2) >= 0xDD00 && (wch2) <= 0xDDEF)) -#define IS_LOW_VARSEL(wch) \ - (((wch) >= 0x180B && (wch) <= 0x180D) || /* MONGOLIAN FREE VARIATION SELECTOR */ \ - ((wch) >= 0xFE00 && (wch) <= 0xFE0F)) /* VARIATION SELECTOR 1-16 */ - -static bool wintw_setup_draw_ctx(TermWin *); -static void wintw_draw_text(TermWin *, int x, int y, wchar_t *text, int len, - unsigned long attrs, int lattrs, truecolour tc); -static void wintw_draw_cursor(TermWin *, int x, int y, wchar_t *text, int len, - unsigned long attrs, int lattrs, truecolour tc); -static void wintw_draw_trust_sigil(TermWin *, int x, int y); -static int wintw_char_width(TermWin *, int uc); -static void wintw_free_draw_ctx(TermWin *); -static void wintw_set_cursor_pos(TermWin *, int x, int y); -static void wintw_set_raw_mouse_mode(TermWin *, bool enable); -static void wintw_set_raw_mouse_mode_pointer(TermWin *, bool enable); -static void wintw_set_scrollbar(TermWin *, int total, int start, int page); -static void wintw_bell(TermWin *, int mode); -static void wintw_clip_write( - TermWin *, int clipboard, wchar_t *text, int *attrs, - truecolour *colours, int len, bool must_deselect); -static void wintw_clip_request_paste(TermWin *, int clipboard); -static void wintw_refresh(TermWin *); -static void wintw_request_resize(TermWin *, int w, int h); -static void wintw_set_title(TermWin *, const char *title, int codepage); -static void wintw_set_icon_title(TermWin *, const char *icontitle, - int codepage); -static void wintw_set_minimised(TermWin *, bool minimised); -static void wintw_set_maximised(TermWin *, bool maximised); -static void wintw_move(TermWin *, int x, int y); -static void wintw_set_zorder(TermWin *, bool top); -static void wintw_palette_set(TermWin *, unsigned, unsigned, const rgb *); -static void wintw_palette_get_overrides(TermWin *, Terminal *); -static void wintw_unthrottle(TermWin *win, size_t bufsize); - -static const TermWinVtable windows_termwin_vt = { - .setup_draw_ctx = wintw_setup_draw_ctx, - .draw_text = wintw_draw_text, - .draw_cursor = wintw_draw_cursor, - .draw_trust_sigil = wintw_draw_trust_sigil, - .char_width = wintw_char_width, - .free_draw_ctx = wintw_free_draw_ctx, - .set_cursor_pos = wintw_set_cursor_pos, - .set_raw_mouse_mode = wintw_set_raw_mouse_mode, - .set_raw_mouse_mode_pointer = wintw_set_raw_mouse_mode_pointer, - .set_scrollbar = wintw_set_scrollbar, - .bell = wintw_bell, - .clip_write = wintw_clip_write, - .clip_request_paste = wintw_clip_request_paste, - .refresh = wintw_refresh, - .request_resize = wintw_request_resize, - .set_title = wintw_set_title, - .set_icon_title = wintw_set_icon_title, - .set_minimised = wintw_set_minimised, - .set_maximised = wintw_set_maximised, - .move = wintw_move, - .set_zorder = wintw_set_zorder, - .palette_set = wintw_palette_set, - .palette_get_overrides = wintw_palette_get_overrides, - .unthrottle = wintw_unthrottle, -}; - -static HICON trust_icon = INVALID_HANDLE_VALUE; - -const bool share_can_be_downstream = true; -const bool share_can_be_upstream = true; - -static bool is_utf8(WinGuiSeat *wgs) -{ - return wgs->ucsdata.line_codepage == CP_UTF8; -} - -static bool win_seat_is_utf8(Seat *seat) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - return is_utf8(wgs); -} - -static char *win_seat_get_ttymode(Seat *seat, const char *mode) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - return term_get_ttymode(wgs->term, mode); -} - -static StripCtrlChars *win_seat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - return stripctrl_new_term(bs_out, false, 0, wgs->term); -} - -static size_t win_seat_output( - Seat *seat, SeatOutputType type, const void *, size_t); -static bool win_seat_eof(Seat *seat); -static SeatPromptResult win_seat_get_userpass_input(Seat *seat, prompts_t *p); -static void win_seat_notify_remote_exit(Seat *seat); -static void win_seat_connection_fatal(Seat *seat, const char *msg); -static void win_seat_nonfatal(Seat *seat, const char *msg); -static void win_seat_update_specials_menu(Seat *seat); -static void win_seat_set_busy_status(Seat *seat, BusyStatus status); -static void win_seat_set_trust_status(Seat *seat, bool trusted); -static bool win_seat_can_set_trust_status(Seat *seat); -static bool win_seat_get_cursor_position(Seat *seat, int *x, int *y); -static bool win_seat_get_window_pixel_size(Seat *seat, int *x, int *y); - -static const SeatVtable win_seat_vt = { - .output = win_seat_output, - .eof = win_seat_eof, - .sent = nullseat_sent, - .banner = nullseat_banner_to_stderr, - .get_userpass_input = win_seat_get_userpass_input, - .notify_session_started = nullseat_notify_session_started, - .notify_remote_exit = win_seat_notify_remote_exit, - .notify_remote_disconnect = nullseat_notify_remote_disconnect, - .connection_fatal = win_seat_connection_fatal, - .nonfatal = win_seat_nonfatal, - .update_specials_menu = win_seat_update_specials_menu, - .get_ttymode = win_seat_get_ttymode, - .set_busy_status = win_seat_set_busy_status, - .confirm_ssh_host_key = win_seat_confirm_ssh_host_key, - .confirm_weak_crypto_primitive = win_seat_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = win_seat_confirm_weak_cached_hostkey, - .prompt_descriptions = win_seat_prompt_descriptions, - .is_utf8 = win_seat_is_utf8, - .echoedit_update = nullseat_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = win_seat_get_window_pixel_size, - .stripctrl_new = win_seat_stripctrl_new, - .set_trust_status = win_seat_set_trust_status, - .can_set_trust_status = win_seat_can_set_trust_status, - .has_mixed_input_stream = nullseat_has_mixed_input_stream_yes, - .verbose = nullseat_verbose_yes, - .interactive = nullseat_interactive_yes, - .get_cursor_position = win_seat_get_cursor_position, -}; - -static void start_backend(WinGuiSeat *wgs) -{ - const struct BackendVtable *vt; - char *error, *realhost; - int i; - - wgs->cmdline_get_passwd_state = cmdline_get_passwd_input_state_new; - - vt = backend_vt_from_conf(wgs->conf); - - seat_set_trust_status(&wgs->seat, true); - error = backend_init(vt, &wgs->seat, &wgs->backend, wgs->logctx, wgs->conf, - conf_get_str(wgs->conf, CONF_host), - conf_get_int(wgs->conf, CONF_port), - &realhost, - conf_get_bool(wgs->conf, CONF_tcp_nodelay), - conf_get_bool(wgs->conf, CONF_tcp_keepalives)); - if (error) { - char *str = dupprintf("%s Error", appname); - char *msg; - if (cmdline_tooltype & TOOLTYPE_NONNETWORK) { - /* Special case for pterm. */ - msg = dupprintf("Unable to open terminal:\n%s", error); - } else { - msg = dupprintf("Unable to open connection to\n%s\n%s", - conf_dest(wgs->conf), error); - } - sfree(error); - MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK); - sfree(str); - sfree(msg); - exit(0); - } - term_setup_window_titles(wgs->term, realhost); - sfree(realhost); - - /* - * Connect the terminal to the backend for resize purposes. - */ - term_provide_backend(wgs->term, wgs->backend); - - /* - * Set up a line discipline. - */ - wgs->ldisc = ldisc_create(wgs->conf, wgs->term, wgs->backend, &wgs->seat); - - /* - * Destroy the Restart Session menu item. (This will return - * failure if it's already absent, as it will be the very first - * time we call this function. We ignore that, because as long - * as the menu item ends up not being there, we don't care - * whether it was us who removed it or not!) - */ - for (i = 0; i < lenof(wgs->popup_menus); i++) { - DeleteMenu(wgs->popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); - } - - wgs->session_closed = false; -} - -static void close_session(void *vctx) -{ - WinGuiSeat *wgs = (WinGuiSeat *)vctx; - char *newtitle; - int i; - - wgs->session_closed = true; - newtitle = dupprintf("%s (inactive)", appname); - win_set_icon_title(&wgs->termwin, newtitle, DEFAULT_CODEPAGE); - win_set_title(&wgs->termwin, newtitle, DEFAULT_CODEPAGE); - sfree(newtitle); - - if (wgs->ldisc) { - ldisc_free(wgs->ldisc); - wgs->ldisc = NULL; - } - if (wgs->backend) { - backend_free(wgs->backend); - wgs->backend = NULL; - term_provide_backend(wgs->term, NULL); - seat_update_specials_menu(&wgs->seat); - } - - /* - * Show the Restart Session menu item. Do a precautionary - * delete first to ensure we never end up with more than one. - */ - for (i = 0; i < lenof(wgs->popup_menus); i++) { - DeleteMenu(wgs->popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); - InsertMenu(wgs->popup_menus[i].menu, IDM_DUPSESS, - MF_BYCOMMAND | MF_ENABLED, IDM_RESTART, "&Restart Session"); - } -} - -/* - * Some machinery to deal with switching the window type between ANSI - * and Unicode. We prefer Unicode, but some PuTTY builds still try to - * run on machines so old that they don't support that mode. So we're - * prepared to fall back to an ANSI window if we have to. For this - * purpose, we swap out a few Windows API functions, and wrap - * SetWindowText so that if we're not in Unicode mode we first convert - * the wide string we're given. - */ -static bool unicode_window; -static BOOL (WINAPI *sw_PeekMessage)(LPMSG, HWND, UINT, UINT, UINT); -static LRESULT (WINAPI *sw_DispatchMessage)(const MSG *); -static LRESULT (WINAPI *sw_DefWindowProc)(HWND, UINT, WPARAM, LPARAM); -static void sw_SetWindowText(HWND hwnd, wchar_t *text) -{ - if (unicode_window) { - SetWindowTextW(hwnd, text); - } else { - char *mb = dup_wc_to_mb(DEFAULT_CODEPAGE, 0, text, "?"); - SetWindowTextA(hwnd, mb); - sfree(mb); - } -} - -static HINSTANCE hprev; - -/* - * Also, registering window classes has to be done in a fiddly way. - */ -#define SETUP_WNDCLASS(wndclass, classname) do { \ - wndclass.style = 0; \ - wndclass.lpfnWndProc = WndProc; \ - wndclass.cbClsExtra = 0; \ - wndclass.cbWndExtra = 0; \ - wndclass.hInstance = hinst; \ - wndclass.hIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_MAINICON)); \ - wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM); \ - wndclass.hbrBackground = NULL; \ - wndclass.lpszMenuName = NULL; \ - wndclass.lpszClassName = classname; \ - } while (0) -wchar_t *terminal_window_class_w(void) -{ - static wchar_t *classname = NULL; - if (!classname) - classname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); - if (!hprev) { - WNDCLASSW wndclassw; - SETUP_WNDCLASS(wndclassw, classname); - RegisterClassW(&wndclassw); - } - return classname; -} -char *terminal_window_class_a(void) -{ - static char *classname = NULL; - if (!classname) - classname = dupcat(appname, ".ansi"); - if (!hprev) { - WNDCLASSA wndclassa; - SETUP_WNDCLASS(wndclassa, classname); - RegisterClassA(&wndclassa); - } - return classname; -} - -HINSTANCE hinst; - -int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) -{ - MSG msg; - HRESULT hr; - int guess_width, guess_height; - - dll_hijacking_protection(); - - hinst = inst; - hprev = prev; - - sk_init(); - - init_common_controls(); - - /* Set Explicit App User Model Id so that jump lists don't cause - PuTTY to hang on to removable media. */ - - set_explicit_app_user_model_id(); - - /* Ensure a Maximize setting in Explorer doesn't maximise the - * config box. */ - defuse_showwindow(); - - init_winver(); - - /* - * If we're running a version of Windows that doesn't support - * WM_MOUSEWHEEL, find out what message number we should be - * using instead. - */ - if (osMajorVersion < 4 || - (osMajorVersion == 4 && osPlatformId != VER_PLATFORM_WIN32_NT)) - wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG"); - - init_help(); - - init_winfuncs(); - - setup_gui_timing(); - - WinGuiSeat *wgs = snew(WinGuiSeat); - memset(wgs, 0, sizeof(*wgs)); - wgs_link(wgs); - - wgs->seat.vt = &win_seat_vt; - wgs->logpolicy.vt = &win_gui_logpolicy_vt; - wgs->termwin.vt = &windows_termwin_vt; - - wgs->caret_x = wgs->caret_y = -1; - wgs->busy_status = BUSY_NOT; - - wgs->conf = conf_new(); - - /* - * Initialize COM. - */ - hr = CoInitialize(NULL); - if (hr != S_OK && hr != S_FALSE) { - char *str = dupprintf("%s Fatal Error", appname); - MessageBox(NULL, "Failed to initialize COM subsystem", - str, MB_OK | MB_ICONEXCLAMATION); - sfree(str); - return 1; - } - - /* - * Process the command line. - * (If the command line doesn't provide enough info to start a - * session, this will detour via the config box.) - */ - gui_term_process_cmdline(wgs->conf, cmdline); - - memset(&wgs->ucsdata, 0, sizeof(wgs->ucsdata)); - - conf_cache_data(wgs); - - /* - * Guess some defaults for the window size. This all gets - * updated later, so we don't really care too much. However, we - * do want the font width/height guesses to correspond to a - * large font rather than a small one... - */ - - wgs->font_width = 10; - wgs->font_height = 20; - wgs->extra_width = 25; - wgs->extra_height = 28; - guess_width = wgs->extra_width + wgs->font_width * conf_get_int( - wgs->conf, CONF_width); - guess_height = wgs->extra_height + wgs->font_height * conf_get_int( - wgs->conf, CONF_height); - { - RECT r; - get_fullscreen_rect(wgs, &r); - if (guess_width > r.right - r.left) - guess_width = r.right - r.left; - if (guess_height > r.bottom - r.top) - guess_height = r.bottom - r.top; - } - - { - int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL; - int exwinmode = 0; - const struct BackendVtable *vt = - backend_vt_from_proto(be_default_protocol); - bool resize_forbidden = false; - if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) - resize_forbidden = true; - wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); - wgs->window_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); - wgs->icon_name = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); - if (!conf_get_bool(wgs->conf, CONF_scrollbar)) - winmode &= ~(WS_VSCROLL); - if (conf_get_int(wgs->conf, CONF_resize_action) == RESIZE_DISABLED || - resize_forbidden) - winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); - if (conf_get_bool(wgs->conf, CONF_alwaysontop)) - exwinmode |= WS_EX_TOPMOST; - if (conf_get_bool(wgs->conf, CONF_sunken_edge)) - exwinmode |= WS_EX_CLIENTEDGE; - -#ifdef TEST_ANSI_WINDOW - /* For developer testing of ANSI window support, pretend - * CreateWindowExW failed */ - wgs->term_hwnd = NULL; - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); -#else - unicode_window = true; - sw_PeekMessage = PeekMessageW; - sw_DispatchMessage = DispatchMessageW; - sw_DefWindowProc = DefWindowProcW; - wgs->term_hwnd = CreateWindowExW( - exwinmode, terminal_window_class_w(), uappname, - winmode, CW_USEDEFAULT, CW_USEDEFAULT, - guess_width, guess_height, NULL, NULL, inst, NULL); -#endif - -#if defined LEGACY_WINDOWS || defined TEST_ANSI_WINDOW - if (!wgs->term_hwnd && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) { - /* Fall back to an ANSI window, swapping in all the ANSI - * window message handling functions */ - unicode_window = false; - sw_PeekMessage = PeekMessageA; - sw_DispatchMessage = DispatchMessageA; - sw_DefWindowProc = DefWindowProcA; - wgs->term_hwnd = CreateWindowExA( - exwinmode, terminal_window_class_a(), appname, - winmode, CW_USEDEFAULT, CW_USEDEFAULT, - guess_width, guess_height, NULL, NULL, inst, NULL); - } -#endif - - if (!wgs->term_hwnd) { - modalfatalbox("Unable to create terminal window: %s", - win_strerror(GetLastError())); - } - memset(&wgs->dpi_info, 0, sizeof(struct _dpi_info)); - init_dpi_info(wgs); - sfree(uappname); - } - - SetWindowLongPtr(wgs->term_hwnd, GWLP_USERDATA, (LONG_PTR)wgs); - - /* - * Initialise the fonts, simultaneously correcting the guesses - * for font_{width,height}. - */ - init_fonts(wgs, 0, 0); - - /* - * Prepare a logical palette. - */ - init_palette(wgs); - - /* - * Initialise the terminal. (We have to do this _after_ - * creating the window, since the terminal is the first thing - * which will call schedule_timer(), which will in turn call - * timer_change_notify() which will expect hwnd to exist.) - */ - wgs->term = term_init(wgs->conf, &wgs->ucsdata, &wgs->termwin); - setup_clipboards(wgs->term, wgs->conf); - wgs->logctx = log_init(&wgs->logpolicy, wgs->conf); - term_provide_logctx(wgs->term, wgs->logctx); - term_size(wgs->term, conf_get_int(wgs->conf, CONF_height), - conf_get_int(wgs->conf, CONF_width), - conf_get_int(wgs->conf, CONF_savelines)); - - /* - * Correct the guesses for extra_{width,height}. - */ - { - RECT cr, wr; - GetWindowRect(wgs->term_hwnd, &wr); - GetClientRect(wgs->term_hwnd, &cr); - wgs->offset_width = wgs->offset_height = - conf_get_int(wgs->conf, CONF_window_border); - wgs->extra_width = - wr.right - wr.left - cr.right + cr.left + wgs->offset_width*2; - wgs->extra_height = - wr.bottom - wr.top - cr.bottom + cr.top +wgs->offset_height*2; - } - - /* - * Resize the window, now we know what size we _really_ want it - * to be. - */ - guess_width = wgs->extra_width + wgs->font_width * wgs->term->cols; - guess_height = wgs->extra_height + wgs->font_height * wgs->term->rows; - SetWindowPos(wgs->term_hwnd, NULL, 0, 0, guess_width, guess_height, - SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); - - /* - * Set up a caret bitmap, with no content. - */ - { - char *bits; - int size = (wgs->font_width + 15) / 16 * 2 * wgs->font_height; - bits = snewn(size, char); - memset(bits, 0, size); - wgs->caretbm = CreateBitmap(wgs->font_width, wgs->font_height, - 1, 1, bits); - sfree(bits); - } - CreateCaret(wgs->term_hwnd, wgs->caretbm, - wgs->font_width, wgs->font_height); - - /* - * Initialise the scroll bar. - */ - { - SCROLLINFO si; - - si.cbSize = sizeof(si); - si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; - si.nMin = 0; - si.nMax = wgs->term->rows - 1; - si.nPage = wgs->term->rows; - si.nPos = 0; - SetScrollInfo(wgs->term_hwnd, SB_VERT, &si, false); - } - - /* - * Prepare the mouse handler. - */ - wgs->lastact = MA_NOTHING; - wgs->lastbtn = MBT_NOTHING; - wgs->dbltime = GetDoubleClickTime(); - - /* - * Set up the session-control options on the system menu. - */ - { - HMENU m; - int j; - char *str; - - wgs->popup_menus[SYSMENU].menu = GetSystemMenu(wgs->term_hwnd, false); - wgs->popup_menus[CTXMENU].menu = CreatePopupMenu(); - AppendMenu(wgs->popup_menus[CTXMENU].menu, MF_ENABLED, - IDM_COPY, "&Copy"); - AppendMenu(wgs->popup_menus[CTXMENU].menu, MF_ENABLED, - IDM_PASTE, "&Paste"); - - wgs->savedsess_menu = CreateMenu(); - get_sesslist(&sesslist, true); - update_savedsess_menu(wgs); - - for (j = 0; j < lenof(wgs->popup_menus); j++) { - m = wgs->popup_menus[j].menu; - - AppendMenu(m, MF_SEPARATOR, 0, 0); - AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log"); - AppendMenu(m, MF_SEPARATOR, 0, 0); - AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session..."); - AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session"); - AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT_PTR)wgs->savedsess_menu, - "Sa&ved Sessions"); - AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings..."); - AppendMenu(m, MF_SEPARATOR, 0, 0); - AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard"); - AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback"); - AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal"); - AppendMenu(m, MF_SEPARATOR, 0, 0); - AppendMenu(m, (conf_get_int(wgs->conf, CONF_resize_action) - == RESIZE_DISABLED) ? MF_GRAYED : MF_ENABLED, - IDM_FULLSCREEN, "&Full Screen"); - AppendMenu(m, MF_SEPARATOR, 0, 0); - if (has_help()) - AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help"); - str = dupprintf("&About %s", appname); - AppendMenu(m, MF_ENABLED, IDM_ABOUT, str); - sfree(str); - } - } - - if (restricted_acl()) { - lp_eventlog(&wgs->logpolicy, "Running with restricted process ACL"); - } - - winselgui_set_hwnd(wgs->term_hwnd); - start_backend(wgs); - - /* - * Set up the initial input locale. - */ - set_input_locale(wgs, GetKeyboardLayout(0)); - - /* - * Finally show the window! - */ - ShowWindow(wgs->term_hwnd, show); - SetForegroundWindow(wgs->term_hwnd); - - term_set_focus(wgs->term, GetForegroundWindow() == wgs->term_hwnd); - UpdateWindow(wgs->term_hwnd); - - gui_terminal_ready(wgs->term_hwnd, &wgs->seat, wgs->backend); - - while (1) { - int n; - DWORD timeout; - - if (toplevel_callback_pending() || - PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { - /* - * If we have anything we'd like to do immediately, set - * the timeout for MsgWaitForMultipleObjects to zero so - * that we'll only do a quick check of our handles and - * then get on with whatever that was. - * - * One such option is a pending toplevel callback. The - * other is a non-empty Windows message queue, which you'd - * think we could leave to MsgWaitForMultipleObjects to - * check for us along with all the handles, but in fact we - * can't because once PeekMessage in one iteration of this - * loop has removed a message from the queue, the whole - * queue is considered uninteresting by the next - * invocation of MWFMO. So we check ourselves whether the - * message queue is non-empty, and if so, set this timeout - * to zero to ensure MWFMO doesn't block. - */ - timeout = 0; - } else { - timeout = INFINITE; - /* The messages seem unreliable; especially if we're being tricky */ - term_set_focus(wgs->term, GetForegroundWindow() == wgs->term_hwnd); - } - - HandleWaitList *hwl = get_handle_wait_list(); - - n = MsgWaitForMultipleObjects(hwl->nhandles, hwl->handles, false, - timeout, QS_ALLINPUT); - - if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)hwl->nhandles) - handle_wait_activate(hwl, n - WAIT_OBJECT_0); - handle_wait_list_free(hwl); - - while (sw_PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { - if (msg.message == WM_QUIT) - goto finished; /* two-level break */ - - HWND logbox = event_log_window(); - if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg))) - sw_DispatchMessage(&msg); - - /* - * WM_NETEVENT messages seem to jump ahead of others in - * the message queue. I'm not sure why; the docs for - * PeekMessage mention that messages are prioritised in - * some way, but I'm unclear on which priorities go where. - * - * Anyway, in practice I observe that WM_NETEVENT seems to - * jump to the head of the queue, which means that if we - * were to only process one message every time round this - * loop, we'd get nothing but NETEVENTs if the server - * flooded us with data, and stop responding to any other - * kind of window message. So instead, we keep on round - * this loop until we've consumed at least one message - * that _isn't_ a NETEVENT, or run out of messages - * completely (whichever comes first). And we don't go to - * run_toplevel_callbacks (which is where the netevents - * are actually processed, causing fresh NETEVENT messages - * to appear) until we've done this. - */ - if (msg.message != WM_NETEVENT) - break; - } - - run_toplevel_callbacks(); - } - - finished: - cleanup_exit(msg.wParam); /* this doesn't return... */ - return msg.wParam; /* ... but optimiser doesn't know */ -} - -static void wgs_cleanup(WinGuiSeat *wgs) -{ - deinit_fonts(wgs); - sfree(wgs->logpal); - if (wgs->pal) - DeleteObject(wgs->pal); - wgs_unlink(wgs); - sfree(wgs); -} - -char *handle_restrict_acl_cmdline_prefix(char *p) -{ - /* - * Process the &R prefix on a command line, which is equivalent to - * -restrict-acl but lexically easier to prepend when another - * instance of ourself automatically constructs a command line. - * - * If successful, restricts the process ACL and advances the input - * pointer past the prefix. Returns the updated pointer (whether - * it moved or not). - */ - while (*p && isspace((unsigned char)*p)) - p++; - if (*p == '&' && p[1] == 'R' && - (!p[2] || p[2] == '@' || p[2] == '&')) { - /* &R restrict-acl prefix */ - restrict_process_acl(); - p += 2; - } - return p; -} - -bool handle_special_sessionname_cmdline(char *p, Conf *conf) -{ - /* - * Process the special form of command line with an initial @ - * followed by the name of a saved session with _no quoting or - * escaping_. This is a very convenient means of automated - * saved-session launching, via IDM_SAVEDSESS or Windows 7 jump - * lists. - * - * If successful, the whole command line has been interpreted in - * this way, so there's nothing left to parse into other arguments. - */ - if (*p != '@') - return false; - - ptrlen sessionname = ptrlen_from_asciz(p + 1); - while (sessionname.len > 0 && - isspace(((unsigned char *)sessionname.ptr)[sessionname.len-1])) - sessionname.len--; - - char *dup = mkstr(sessionname); - bool loaded = do_defaults(dup, conf); - sfree(dup); - - return loaded; -} - -bool handle_special_filemapping_cmdline(char *p, Conf *conf) -{ - /* - * Process the special form of command line with an initial & - * followed by the hex value of a HANDLE for a file mapping object - * and the size of the data contained in it, which we must - * interpret as a serialised Conf. - * - * If successful, the whole command line has been interpreted in - * this way, so there's nothing left to parse into other arguments. - */ - - if (*p != '&') - return false; - - HANDLE filemap; - unsigned cpsize; - if (sscanf(p + 1, "%p:%u", &filemap, &cpsize) != 2) - return false; - - void *cp = MapViewOfFile(filemap, FILE_MAP_READ, 0, 0, cpsize); - if (!cp) - return false; - - BinarySource src[1]; - BinarySource_BARE_INIT(src, cp, cpsize); - if (!conf_deserialise(conf, src)) - modalfatalbox("Serialised configuration data was invalid"); - UnmapViewOfFile(cp); - CloseHandle(filemap); - return true; -} - -static void setup_clipboards(Terminal *term, Conf *conf) -{ - assert(term->mouse_select_clipboards[0] == CLIP_LOCAL); - - term->n_mouse_select_clipboards = 1; - - if (conf_get_bool(conf, CONF_mouseautocopy)) { - term->mouse_select_clipboards[ - term->n_mouse_select_clipboards++] = CLIP_SYSTEM; - } - - switch (conf_get_int(conf, CONF_mousepaste)) { - case CLIPUI_IMPLICIT: - term->mouse_paste_clipboard = CLIP_LOCAL; - break; - case CLIPUI_EXPLICIT: - term->mouse_paste_clipboard = CLIP_SYSTEM; - break; - default: - term->mouse_paste_clipboard = CLIP_NULL; - break; - } -} - -/* - * Clean up and exit. - */ -void cleanup_exit(int code) -{ - /* - * Clean up. - */ - while (wgslisthead.next != &wgslisthead) { - WinGuiSeat *wgs = container_of( - wgslisthead.next, WinGuiSeat, wgslistnode); - wgs_cleanup(wgs); - } - sk_cleanup(); - - random_save_seed(); - shutdown_help(); - - /* Clean up COM. */ - CoUninitialize(); - - exit(code); -} - -/* - * Refresh the saved-session submenu from `sesslist'. - */ -static void update_savedsess_menu(WinGuiSeat *wgs) -{ - int i; - while (DeleteMenu(wgs->savedsess_menu, 0, MF_BYPOSITION)) ; - /* skip sesslist.sessions[0] == Default Settings */ - for (i = 1; - i < ((sesslist.nsessions <= MENU_SAVED_MAX+1) ? sesslist.nsessions - : MENU_SAVED_MAX+1); - i++) - AppendMenu(wgs->savedsess_menu, MF_ENABLED, - IDM_SAVED_MIN + (i-1)*MENU_SAVED_STEP, - sesslist.sessions[i]); - if (sesslist.nsessions <= 1) - AppendMenu(wgs->savedsess_menu, MF_GRAYED, IDM_SAVED_MIN, - "(No sessions)"); -} - -/* - * Update the Special Commands submenu. - */ -static void win_seat_update_specials_menu(Seat *seat) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - HMENU new_menu; - int i, j; - - if (wgs->backend) - wgs->specials = backend_get_specials(wgs->backend); - else - wgs->specials = NULL; - - if (wgs->specials) { - /* We can't use Windows to provide a stack for submenus, so - * here's a lame "stack" that will do for now. */ - HMENU saved_menu = NULL; - int nesting = 1; - new_menu = CreatePopupMenu(); - for (i = 0; nesting > 0; i++) { - assert(IDM_SPECIAL_MIN + 0x10 * i < IDM_SPECIAL_MAX); - switch (wgs->specials[i].code) { - case SS_SEP: - AppendMenu(new_menu, MF_SEPARATOR, 0, 0); - break; - case SS_SUBMENU: - assert(nesting < 2); - nesting++; - saved_menu = new_menu; /* XXX lame stacking */ - new_menu = CreatePopupMenu(); - AppendMenu(saved_menu, MF_POPUP | MF_ENABLED, - (UINT_PTR) new_menu, wgs->specials[i].name); - break; - case SS_EXITMENU: - nesting--; - if (nesting) { - new_menu = saved_menu; /* XXX lame stacking */ - saved_menu = NULL; - } - break; - default: - AppendMenu(new_menu, MF_ENABLED, IDM_SPECIAL_MIN + 0x10 * i, - wgs->specials[i].name); - break; - } - } - /* Squirrel the highest special. */ - wgs->n_specials = i - 1; - } else { - new_menu = NULL; - wgs->n_specials = 0; - } - - for (j = 0; j < lenof(wgs->popup_menus); j++) { - if (wgs->specials_menu) { - /* XXX does this free up all submenus? */ - DeleteMenu(wgs->popup_menus[j].menu, (UINT_PTR)wgs->specials_menu, - MF_BYCOMMAND); - DeleteMenu(wgs->popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND); - } - if (new_menu) { - InsertMenu(wgs->popup_menus[j].menu, IDM_SHOWLOG, - MF_BYCOMMAND | MF_POPUP | MF_ENABLED, - (UINT_PTR) new_menu, "S&pecial Command"); - InsertMenu(wgs->popup_menus[j].menu, IDM_SHOWLOG, - MF_BYCOMMAND | MF_SEPARATOR, IDM_SPECIALSEP, 0); - } - } - wgs->specials_menu = new_menu; -} - -static void update_mouse_pointer(WinGuiSeat *wgs) -{ - LPTSTR curstype = NULL; - bool force_visible = false; - static bool forced_visible = false; - switch (wgs->busy_status) { - case BUSY_NOT: - if (wgs->pointer_indicates_raw_mouse) - curstype = IDC_ARROW; - else - curstype = IDC_IBEAM; - break; - case BUSY_WAITING: - curstype = IDC_APPSTARTING; /* this may be an abuse */ - force_visible = true; - break; - case BUSY_CPU: - curstype = IDC_WAIT; - force_visible = true; - break; - default: - unreachable("Bad busy_status"); - } - { - HCURSOR cursor = LoadCursor(NULL, curstype); - SetClassLongPtr(wgs->term_hwnd, GCLP_HCURSOR, (LONG_PTR)cursor); - SetCursor(cursor); /* force redraw of cursor at current posn */ - } - if (force_visible != forced_visible) { - /* We want some cursor shapes to be visible always. - * Along with show_mouseptr(), this manages the ShowCursor() - * counter such that if we switch back to a non-force_visible - * cursor, the previous visibility state is restored. */ - ShowCursor(force_visible); - forced_visible = force_visible; - } -} - -static void win_seat_set_busy_status(Seat *seat, BusyStatus status) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - wgs->busy_status = status; - update_mouse_pointer(wgs); -} - -static void wintw_set_raw_mouse_mode(TermWin *tw, bool activate) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - wgs->send_raw_mouse = activate; -} - -static void wintw_set_raw_mouse_mode_pointer(TermWin *tw, bool activate) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - wgs->pointer_indicates_raw_mouse = activate; - update_mouse_pointer(wgs); -} - -/* - * Print a message box and close the connection. - */ -static void win_seat_connection_fatal(Seat *seat, const char *msg) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - char *title = dupprintf("%s Fatal Error", appname); - show_mouseptr(wgs, true); - MessageBox(wgs->term_hwnd, msg, title, MB_ICONERROR | MB_OK); - sfree(title); - - if (conf_get_int(wgs->conf, CONF_close_on_exit) == FORCE_ON) - PostQuitMessage(1); - else { - queue_toplevel_callback(close_session, wgs); - } -} - -/* - * Print a message box and don't close the connection. - */ -static void win_seat_nonfatal(Seat *seat, const char *msg) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - char *title = dupprintf("%s Error", appname); - show_mouseptr(wgs, true); - MessageBox(wgs->term_hwnd, msg, title, MB_ICONERROR | MB_OK); - sfree(title); -} - -static HWND find_window_for_msgbox(void) -{ - if (wgslisthead.next != &wgslisthead) { - WinGuiSeat *wgs = container_of( - wgslisthead.next, WinGuiSeat, wgslistnode); - return wgs->term_hwnd; - } - return NULL; -} - -/* - * Report an error at the command-line parsing stage. - */ -void cmdline_error(const char *fmt, ...) -{ - va_list ap; - char *message, *title; - - va_start(ap, fmt); - message = dupvprintf(fmt, ap); - va_end(ap); - title = dupprintf("%s Command Line Error", appname); - MessageBox(find_window_for_msgbox(), message, title, MB_ICONERROR | MB_OK); - sfree(message); - sfree(title); - exit(1); -} - -static inline rgb rgb_from_colorref(COLORREF cr) -{ - rgb toret; - toret.r = GetRValue(cr); - toret.g = GetGValue(cr); - toret.b = GetBValue(cr); - return toret; -} - -static void wintw_palette_get_overrides(TermWin *tw, Terminal *term) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - if (conf_get_bool(wgs->conf, CONF_system_colour)) { - rgb rgb; - - rgb = rgb_from_colorref(GetSysColor(COLOR_WINDOWTEXT)); - term_palette_override(term, OSC4_COLOUR_fg, rgb); - term_palette_override(term, OSC4_COLOUR_fg_bold, rgb); - - rgb = rgb_from_colorref(GetSysColor(COLOR_WINDOW)); - term_palette_override(term, OSC4_COLOUR_bg, rgb); - term_palette_override(term, OSC4_COLOUR_bg_bold, rgb); - - rgb = rgb_from_colorref(GetSysColor(COLOR_HIGHLIGHTTEXT)); - term_palette_override(term, OSC4_COLOUR_cursor_fg, rgb); - - rgb = rgb_from_colorref(GetSysColor(COLOR_HIGHLIGHT)); - term_palette_override(term, OSC4_COLOUR_cursor_bg, rgb); - } -} - -/* - * This is a wrapper to ExtTextOut() to force Windows to display - * the precise glyphs we give it. Otherwise it would do its own - * bidi and Arabic shaping, and we would end up uncertain which - * characters it had put where. - */ -static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc, - unsigned short *lpString, UINT cbCount, - CONST INT *lpDx, bool opaque) -{ -#if HAVE_GCP_RESULTSW - GCP_RESULTSW gcpr; -#else - /* - * If building against old enough headers that the GCP_RESULTSW - * type isn't available, we can make do with GCP_RESULTS proper: - * the differences aren't important to us (the only variable-width - * string parameter is one we don't use anyway). - */ - GCP_RESULTS gcpr; -#endif - char *buffer = snewn(cbCount*2+2, char); - char *classbuffer = snewn(cbCount, char); - memset(&gcpr, 0, sizeof(gcpr)); - memset(buffer, 0, cbCount*2+2); - memset(classbuffer, GCPCLASS_NEUTRAL, cbCount); - - gcpr.lStructSize = sizeof(gcpr); - gcpr.lpGlyphs = (void *)buffer; - gcpr.lpClass = (void *)classbuffer; - gcpr.nGlyphs = cbCount; - GetCharacterPlacementW(hdc, lpString, cbCount, 0, &gcpr, - FLI_MASK | GCP_CLASSIN | GCP_DIACRITIC); - - ExtTextOut(hdc, x, y, - ETO_GLYPH_INDEX | ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), - lprc, buffer, cbCount, lpDx); -} - -/* - * The exact_textout() wrapper, unfortunately, destroys the useful - * Windows `font linking' behaviour: automatic handling of Unicode - * code points not supported in this font by falling back to a font - * which does contain them. Therefore, we adopt a multi-layered - * approach: for any potentially-bidi text, we use exact_textout(), - * and for everything else we use a simple ExtTextOut as we did - * before exact_textout() was introduced. - */ -static void general_textout( - WinGuiSeat *wgs, HDC hdc, int x, int y, CONST RECT *lprc, - unsigned short *lpString, UINT cbCount, CONST INT *lpDx, bool opaque) -{ - int i, j, xp, xn; - int bkmode = 0; - bool got_bkmode = false; - - xp = xn = x; - - for (i = 0; i < (int)cbCount ;) { - bool rtl = is_rtl(lpString[i]); - - xn += lpDx[i]; - - for (j = i+1; j < (int)cbCount; j++) { - if (rtl != is_rtl(lpString[j])) - break; - xn += lpDx[j]; - } - - /* - * Now [i,j) indicates a maximal substring of lpString - * which should be displayed using the same textout - * function. - */ - if (rtl) { - exact_textout(hdc, xp, y, lprc, lpString+i, j-i, - wgs->font_varpitch ? NULL : lpDx+i, opaque); - } else { - ExtTextOutW(hdc, xp, y, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), - lprc, lpString+i, j-i, - wgs->font_varpitch ? NULL : lpDx+i); - } - - i = j; - xp = xn; - - bkmode = GetBkMode(hdc); - got_bkmode = true; - SetBkMode(hdc, TRANSPARENT); - opaque = false; - } - - if (got_bkmode) - SetBkMode(hdc, bkmode); -} - -static int get_font_width(WinGuiSeat *wgs, HDC hdc, const TEXTMETRIC *tm) -{ - int ret; - /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */ - if (!(tm->tmPitchAndFamily & TMPF_FIXED_PITCH)) { - ret = tm->tmAveCharWidth; - } else { -#define FIRST '0' -#define LAST '9' - ABCFLOAT widths[LAST-FIRST + 1]; - int j; - - wgs->font_varpitch = true; - wgs->font_dualwidth = true; - if (GetCharABCWidthsFloat(hdc, FIRST, LAST, widths)) { - ret = 0; - for (j = 0; j < lenof(widths); j++) { - int width = (int)(0.5 + widths[j].abcfA + - widths[j].abcfB + widths[j].abcfC); - if (ret < width) - ret = width; - } - } else { - ret = tm->tmMaxCharWidth; - } -#undef FIRST -#undef LAST - } - return ret; -} - -static void init_dpi_info(WinGuiSeat *wgs) -{ - if (wgs->dpi_info.cur_dpi.x == 0 || wgs->dpi_info.cur_dpi.y == 0) { - if (p_GetDpiForMonitor && p_MonitorFromWindow) { - UINT dpiX, dpiY; - HMONITOR currentMonitor = p_MonitorFromWindow( - wgs->term_hwnd, MONITOR_DEFAULTTOPRIMARY); - if (p_GetDpiForMonitor(currentMonitor, MDT_EFFECTIVE_DPI, - &dpiX, &dpiY) == S_OK) { - wgs->dpi_info.cur_dpi.x = (int)dpiX; - wgs->dpi_info.cur_dpi.y = (int)dpiY; - } - } - - /* Fall back to system DPI */ - if (wgs->dpi_info.cur_dpi.x == 0 || wgs->dpi_info.cur_dpi.y == 0) { - HDC hdc = GetDC(wgs->term_hwnd); - wgs->dpi_info.cur_dpi.x = GetDeviceCaps(hdc, LOGPIXELSX); - wgs->dpi_info.cur_dpi.y = GetDeviceCaps(hdc, LOGPIXELSY); - ReleaseDC(wgs->term_hwnd, hdc); - } - } -} - -/* - * Initialise all the fonts we will need initially. There may be as many as - * three or as few as one. The other (potentially) twenty-one fonts are done - * if/when they are needed. - * - * We also: - * - * - check the font width and height, correcting our guesses if - * necessary. - * - * - verify that the bold font is the same width as the ordinary - * one, and engage shadow bolding if not. - * - * - verify that the underlined font is the same width as the - * ordinary one (manual underlining by means of line drawing can - * be done in a pinch). - * - * - find a trust sigil icon that will look OK with the chosen font. - */ -static void init_fonts(WinGuiSeat *wgs, int pick_width, int pick_height) -{ - TEXTMETRIC tm; - OUTLINETEXTMETRIC otm; - CPINFO cpinfo; - FontSpec *font; - int fontsize[3]; - int i; - int quality; - HDC hdc; - int fw_dontcare, fw_bold; - - for (i = 0; i < FONT_MAXNO; i++) - wgs->fonts[i] = NULL; - - wgs->bold_font_mode = - conf_get_int(wgs->conf, CONF_bold_style) & BOLD_STYLE_FONT ? - BOLD_FONT : BOLD_NONE; - wgs->bold_colours = - conf_get_int(wgs->conf, CONF_bold_style) & BOLD_STYLE_COLOUR ? - true : false; - wgs->und_mode = UND_FONT; - - font = conf_get_fontspec(wgs->conf, CONF_font); - if (font->isbold) { - fw_dontcare = FW_BOLD; - fw_bold = FW_HEAVY; - } else { - fw_dontcare = FW_DONTCARE; - fw_bold = FW_BOLD; - } - - hdc = GetDC(wgs->term_hwnd); - - if (pick_height) - wgs->font_height = pick_height; - else { - wgs->font_height = font->height; - if (wgs->font_height > 0) { - wgs->font_height = -MulDiv( - wgs->font_height, wgs->dpi_info.cur_dpi.y, 72); - } - } - wgs->font_width = pick_width; - - quality = conf_get_int(wgs->conf, CONF_font_quality); -#define f(i,c,w,u) \ - wgs->fonts[i] = CreateFont( \ - wgs->font_height, wgs->font_width, 0, 0, w, false, u, false, c, \ - OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), \ - FIXED_PITCH | FF_DONTCARE, font->name) - - f(FONT_NORMAL, font->charset, fw_dontcare, false); - - SelectObject(hdc, wgs->fonts[FONT_NORMAL]); - GetTextMetrics(hdc, &tm); - if (GetOutlineTextMetrics(hdc, sizeof(otm), &otm)) - wgs->font_strikethrough_y = tm.tmAscent - otm.otmsStrikeoutPosition; - else - wgs->font_strikethrough_y = tm.tmAscent - (tm.tmAscent * 3 / 8); - - GetObject(wgs->fonts[FONT_NORMAL], sizeof(LOGFONT), &wgs->lfont); - - /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */ - if (!(tm.tmPitchAndFamily & TMPF_FIXED_PITCH)) { - wgs->font_varpitch = false; - wgs->font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth); - } else { - wgs->font_varpitch = true; - wgs->font_dualwidth = true; - } - if (pick_width == 0 || pick_height == 0) { - wgs->font_height = tm.tmHeight; - wgs->font_width = get_font_width(wgs, hdc, &tm); - } - -#ifdef RDB_DEBUG_PATCH - debug("Primary font H=%d, AW=%d, MW=%d\n", - tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth); -#endif - - { - CHARSETINFO info; - DWORD cset = tm.tmCharSet; - memset(&info, 0xFF, sizeof(info)); - - /* !!! Yes the next line is right */ - if (cset == OEM_CHARSET) - wgs->ucsdata.font_codepage = GetOEMCP(); - else if (TranslateCharsetInfo ((DWORD *)(ULONG_PTR)cset, - &info, TCI_SRCCHARSET)) - wgs->ucsdata.font_codepage = info.ciACP; - else - wgs->ucsdata.font_codepage = -1; - - GetCPInfo(wgs->ucsdata.font_codepage, &cpinfo); - wgs->ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1); - } - - f(FONT_UNDERLINE, font->charset, fw_dontcare, true); - - /* - * Some fonts, e.g. 9-pt Courier, draw their underlines - * outside their character cell. We successfully prevent - * screen corruption by clipping the text output, but then - * we lose the underline completely. Here we try to work - * out whether this is such a font, and if it is, we set a - * flag that causes underlines to be drawn by hand. - * - * Having tried other more sophisticated approaches (such - * as examining the TEXTMETRIC structure or requesting the - * height of a string), I think we'll do this the brute - * force way: we create a small bitmap, draw an underlined - * space on it, and test to see whether any pixels are - * foreground-coloured. (Since we expect the underline to - * go all the way across the character cell, we only search - * down a single column of the bitmap, half way across.) - */ - { - HDC und_dc; - HBITMAP und_bm, und_oldbm; - int i; - bool gotit; - COLORREF c; - - und_dc = CreateCompatibleDC(hdc); - und_bm = CreateCompatibleBitmap( - hdc, wgs->font_width, wgs->font_height); - und_oldbm = SelectObject(und_dc, und_bm); - SelectObject(und_dc, wgs->fonts[FONT_UNDERLINE]); - SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP); - SetTextColor(und_dc, RGB(255, 255, 255)); - SetBkColor(und_dc, RGB(0, 0, 0)); - SetBkMode(und_dc, OPAQUE); - ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL); - gotit = false; - for (i = 0; i < wgs->font_height; i++) { - c = GetPixel(und_dc, wgs->font_width / 2, i); - if (c != RGB(0, 0, 0)) - gotit = true; - } - SelectObject(und_dc, und_oldbm); - DeleteObject(und_bm); - DeleteDC(und_dc); - if (!gotit) { - wgs->und_mode = UND_LINE; - DeleteObject(wgs->fonts[FONT_UNDERLINE]); - wgs->fonts[FONT_UNDERLINE] = 0; - } - } - - if (wgs->bold_font_mode == BOLD_FONT) { - f(FONT_BOLD, font->charset, fw_bold, false); - } -#undef f - - wgs->descent = tm.tmAscent + 1; - if (wgs->descent >= wgs->font_height) - wgs->descent = wgs->font_height - 1; - - for (i = 0; i < 3; i++) { - if (wgs->fonts[i]) { - if (SelectObject(hdc, wgs->fonts[i]) && GetTextMetrics(hdc, &tm)) - fontsize[i] = (get_font_width(wgs, hdc, &tm) + - 256 * tm.tmHeight); - else - fontsize[i] = -i; - } else - fontsize[i] = -i; - } - - ReleaseDC(wgs->term_hwnd, hdc); - - if (trust_icon != INVALID_HANDLE_VALUE) { - DestroyIcon(trust_icon); - } - trust_icon = LoadImage(hinst, MAKEINTRESOURCE(IDI_MAINICON), - IMAGE_ICON, wgs->font_width*2, wgs->font_height, - LR_DEFAULTCOLOR); - - if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) { - wgs->und_mode = UND_LINE; - DeleteObject(wgs->fonts[FONT_UNDERLINE]); - wgs->fonts[FONT_UNDERLINE] = 0; - } - - if (wgs->bold_font_mode == BOLD_FONT && - fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) { - wgs->bold_font_mode = BOLD_SHADOW; - DeleteObject(wgs->fonts[FONT_BOLD]); - wgs->fonts[FONT_BOLD] = 0; - } - wgs->fontflag[0] = true; - wgs->fontflag[1] = true; - wgs->fontflag[2] = true; - - init_ucs(wgs->conf, &wgs->ucsdata); -} - -static void another_font(WinGuiSeat *wgs, int fontno) -{ - int basefont; - int fw_dontcare, fw_bold, quality; - int c, w, x; - bool u; - char *s; - FontSpec *font; - - if (fontno < 0 || fontno >= FONT_MAXNO || wgs->fontflag[fontno]) - return; - - basefont = (fontno & ~(FONT_BOLDUND)); - if (basefont != fontno && !wgs->fontflag[basefont]) - another_font(wgs, basefont); - - font = conf_get_fontspec(wgs->conf, CONF_font); - - if (font->isbold) { - fw_dontcare = FW_BOLD; - fw_bold = FW_HEAVY; - } else { - fw_dontcare = FW_DONTCARE; - fw_bold = FW_BOLD; - } - - c = font->charset; - w = fw_dontcare; - u = false; - s = font->name; - x = wgs->font_width; - - if (fontno & FONT_WIDE) - x *= 2; - if (fontno & FONT_NARROW) - x = (x+1)/2; - if (fontno & FONT_OEM) - c = OEM_CHARSET; - if (fontno & FONT_BOLD) - w = fw_bold; - if (fontno & FONT_UNDERLINE) - u = true; - - quality = conf_get_int(wgs->conf, CONF_font_quality); - - wgs->fonts[fontno] = - CreateFont(wgs->font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w, - false, u, false, c, OUT_DEFAULT_PRECIS, - CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), - DEFAULT_PITCH | FF_DONTCARE, s); - - wgs->fontflag[fontno] = true; -} - -static void deinit_fonts(WinGuiSeat *wgs) -{ - int i; - for (i = 0; i < FONT_MAXNO; i++) { - if (wgs->fonts[i]) - DeleteObject(wgs->fonts[i]); - wgs->fonts[i] = 0; - wgs->fontflag[i] = false; - } - - if (trust_icon != INVALID_HANDLE_VALUE) { - DestroyIcon(trust_icon); - } - trust_icon = INVALID_HANDLE_VALUE; -} - -static void wintw_request_resize(TermWin *tw, int w, int h) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - const struct BackendVtable *vt; - int width, height; - int resize_action = conf_get_int(wgs->conf, CONF_resize_action); - bool deny_resize = false; - - /* Suppress server-originated resizing attempts if local resizing - * is disabled entirely, or if it's supposed to change - * rows/columns but the window is maximised. */ - if (resize_action == RESIZE_DISABLED - || (resize_action == RESIZE_TERM && IsZoomed(wgs->term_hwnd))) { - deny_resize = true; - } - - vt = backend_vt_from_proto(be_default_protocol); - if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) - deny_resize = true; - if (h == wgs->term->rows && w == wgs->term->cols) deny_resize = true; - - /* We still need to acknowledge a suppressed resize attempt. */ - if (deny_resize) { - term_resize_request_completed(wgs->term); - return; - } - - /* Sanity checks ... */ - { - RECT ss; - if (get_fullscreen_rect(wgs, &ss)) { - /* Make sure the values aren't too big */ - width = (ss.right - ss.left - wgs->extra_width) / 4; - height = (ss.bottom - ss.top - wgs->extra_height) / 6; - - if (w > width || h > height) { - term_resize_request_completed(wgs->term); - return; - } - if (w < 15) - w = 15; - if (h < 1) - h = 1; - } - } - - if (resize_action != RESIZE_FONT && !IsZoomed(wgs->term_hwnd)) { - width = wgs->extra_width + wgs->font_width * w; - height = wgs->extra_height + wgs->font_height * h; - - SetWindowPos(wgs->term_hwnd, NULL, 0, 0, width, height, - SWP_NOACTIVATE | SWP_NOCOPYBITS | - SWP_NOMOVE | SWP_NOZORDER); - } else { - /* - * If we're resizing by changing the font, we must tell the - * terminal the new size immediately, so that reset_window - * will know what to do. - */ - term_size(wgs->term, h, w, conf_get_int(wgs->conf, CONF_savelines)); - reset_window(wgs, 0); - } - - term_resize_request_completed(wgs->term); - InvalidateRect(wgs->term_hwnd, NULL, true); -} - -static void recompute_window_offset(WinGuiSeat *wgs) -{ - RECT cr; - GetClientRect(wgs->term_hwnd, &cr); - - int win_width = cr.right - cr.left; - int win_height = cr.bottom - cr.top; - - int new_offset_width = (win_width-wgs->font_width*wgs->term->cols)/2; - int new_offset_height = (win_height-wgs->font_height*wgs->term->rows)/2; - - if (wgs->offset_width != new_offset_width || - wgs->offset_height != new_offset_height) { - wgs->offset_width = new_offset_width; - wgs->offset_height = new_offset_height; - InvalidateRect(wgs->term_hwnd, NULL, true); - } -} - -static void reset_window(WinGuiSeat *wgs, int reinit) -{ - /* - * This function decides how to resize or redraw when the - * user changes something. - * - * This function doesn't like to change the terminal size but if the - * font size is locked that may be it's only soluion. - */ - int win_width, win_height, resize_action, window_border; - RECT cr, wr; - -#ifdef RDB_DEBUG_PATCH - debug("reset_window()\n"); -#endif - - /* Current window sizes ... */ - GetWindowRect(wgs->term_hwnd, &wr); - GetClientRect(wgs->term_hwnd, &cr); - - win_width = cr.right - cr.left; - win_height = cr.bottom - cr.top; - - resize_action = conf_get_int(wgs->conf, CONF_resize_action); - window_border = conf_get_int(wgs->conf, CONF_window_border); - - if (resize_action == RESIZE_DISABLED) - reinit = 2; - - /* Are we being forced to reload the fonts ? */ - if (reinit>1) { -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -- Forced deinit\n"); -#endif - deinit_fonts(wgs); - init_fonts(wgs, 0, 0); - } - - /* Oh, looks like we're minimised */ - if (win_width == 0 || win_height == 0) - return; - - /* Is the window out of position ? */ - if (!reinit) { - recompute_window_offset(wgs); -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> Reposition terminal\n"); -#endif - } - - if (IsZoomed(wgs->term_hwnd)) { - /* We're fullscreen, this means we must not change the size of - * the window so it's the font size or the terminal itself. - */ - - wgs->extra_width = wr.right - wr.left - cr.right + cr.left; - wgs->extra_height = wr.bottom - wr.top - cr.bottom + cr.top; - - if (resize_action != RESIZE_TERM) { - if (wgs->font_width != win_width/wgs->term->cols || - wgs->font_height != win_height/wgs->term->rows) { - deinit_fonts(wgs); - init_fonts(wgs, win_width/wgs->term->cols, - win_height/wgs->term->rows); - wgs->offset_width = - (win_width - wgs->font_width*wgs->term->cols) / 2; - wgs->offset_height = - (win_height - wgs->font_height*wgs->term->rows) / 2; - InvalidateRect(wgs->term_hwnd, NULL, true); -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> Z font resize to (%d, %d)\n", - wgs->font_width, wgs->font_height); -#endif - } - } else { - if (wgs->font_width * wgs->term->cols != win_width || - wgs->font_height * wgs->term->rows != win_height) { - /* Our only choice at this point is to change the - * size of the terminal; Oh well. - */ - term_size(wgs->term, win_height / wgs->font_height, - win_width / wgs->font_width, - conf_get_int(wgs->conf, CONF_savelines)); - wgs->offset_width = - (win_width - wgs->font_width*wgs->term->cols) / 2; - wgs->offset_height = - (win_height - wgs->font_height*wgs->term->rows) / 2; - InvalidateRect(wgs->term_hwnd, NULL, true); -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> Zoomed term_size\n"); -#endif - } - } - return; - } - - /* Resize window after DPI change */ - if (reinit == 3 && p_GetSystemMetricsForDpi && p_AdjustWindowRectExForDpi) { - RECT rect; - rect.left = rect.top = 0; - rect.right = (wgs->font_width * wgs->term->cols); - if (conf_get_bool(wgs->conf, CONF_scrollbar)) - rect.right += p_GetSystemMetricsForDpi(SM_CXVSCROLL, - wgs->dpi_info.cur_dpi.x); - rect.bottom = (wgs->font_height * wgs->term->rows); - p_AdjustWindowRectExForDpi( - &rect, GetWindowLongPtr(wgs->term_hwnd, GWL_STYLE), - FALSE, GetWindowLongPtr(wgs->term_hwnd, GWL_EXSTYLE), - wgs->dpi_info.cur_dpi.x); - rect.right += (window_border * 2); - rect.bottom += (window_border * 2); - OffsetRect(&wgs->dpi_info.new_wnd_rect, - ((wgs->dpi_info.new_wnd_rect.right - - wgs->dpi_info.new_wnd_rect.left) - - (rect.right - rect.left)) / 2, - ((wgs->dpi_info.new_wnd_rect.bottom - - wgs->dpi_info.new_wnd_rect.top) - - (rect.bottom - rect.top)) / 2); - SetWindowPos(wgs->term_hwnd, NULL, - wgs->dpi_info.new_wnd_rect.left, - wgs->dpi_info.new_wnd_rect.top, - rect.right - rect.left, rect.bottom - rect.top, - SWP_NOZORDER); - - InvalidateRect(wgs->term_hwnd, NULL, true); - return; - } - - /* Hmm, a force re-init means we should ignore the current window - * so we resize to the default font size. - */ - if (reinit>0) { -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> Forced re-init\n"); -#endif - - wgs->offset_width = wgs->offset_height = window_border; - wgs->extra_width = - wr.right - wr.left - cr.right + cr.left + wgs->offset_width*2; - wgs->extra_height = - wr.bottom - wr.top - cr.bottom + cr.top + wgs->offset_height*2; - - if (win_width != (wgs->font_width*wgs->term->cols + - wgs->offset_width*2) || - win_height != (wgs->font_height*wgs->term->rows + - wgs->offset_height*2)) { - - /* If this is too large windows will resize it to the maximum - * allowed window size, we will then be back in here and resize - * the font or terminal to fit. - */ - SetWindowPos(wgs->term_hwnd, NULL, 0, 0, - wgs->font_width*wgs->term->cols + wgs->extra_width, - wgs->font_height*wgs->term->rows + wgs->extra_height, - SWP_NOMOVE | SWP_NOZORDER); - } - - InvalidateRect(wgs->term_hwnd, NULL, true); - return; - } - - /* Okay the user doesn't want us to change the font so we try the - * window. But that may be too big for the screen which forces us - * to change the terminal. - */ - if ((resize_action == RESIZE_TERM && reinit<=0) || - (resize_action == RESIZE_EITHER && reinit<0) || - reinit>0) { - wgs->offset_width = wgs->offset_height = window_border; - wgs->extra_width = - wr.right - wr.left - cr.right + cr.left + wgs->offset_width*2; - wgs->extra_height = - wr.bottom - wr.top - cr.bottom + cr.top + wgs->offset_height*2; - - if (win_width != (wgs->font_width*wgs->term->cols + - wgs->offset_width*2) || - win_height != (wgs->font_height*wgs->term->rows + - wgs->offset_height*2)) { - - RECT ss; - int width, height; - - get_fullscreen_rect(wgs, &ss); - - width = (ss.right - ss.left - wgs->extra_width) / wgs->font_width; - height = (ss.bottom - ss.top - wgs->extra_height)/wgs->font_height; - - /* Grrr too big */ - if ( wgs->term->rows > height || wgs->term->cols > width ) { - if (resize_action == RESIZE_EITHER) { - /* Make the font the biggest we can */ - if (wgs->term->cols > width) - wgs->font_width = - (ss.right - ss.left - wgs->extra_width) / - wgs->term->cols; - if (wgs->term->rows > height) - wgs->font_height = - (ss.bottom - ss.top - wgs->extra_height) / - wgs->term->rows; - - deinit_fonts(wgs); - init_fonts(wgs, wgs->font_width, wgs->font_height); - - width = (ss.right - ss.left - wgs->extra_width) / - wgs->font_width; - height = (ss.bottom - ss.top - wgs->extra_height) / - wgs->font_height; - } else { - if ( height > wgs->term->rows ) height = wgs->term->rows; - if ( width > wgs->term->cols ) width = wgs->term->cols; - term_size(wgs->term, height, width, - conf_get_int(wgs->conf, CONF_savelines)); -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> term resize to (%d,%d)\n", - height, width); -#endif - } - } - - SetWindowPos(wgs->term_hwnd, NULL, 0, 0, - wgs->font_width*wgs->term->cols + wgs->extra_width, - wgs->font_height*wgs->term->rows + wgs->extra_height, - SWP_NOMOVE | SWP_NOZORDER); - - InvalidateRect(wgs->term_hwnd, NULL, true); -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> window resize to (%d,%d)\n", - wgs->font_width*term->cols + wgs->extra_width, - wgs->font_height*term->rows + wgs->extra_height); -#endif - } - return; - } - - /* We're allowed to or must change the font but do we want to ? */ - - if (wgs->font_width != (win_width-window_border*2)/wgs->term->cols || - wgs->font_height != (win_height-window_border*2)/wgs->term->rows) { - - deinit_fonts(wgs); - init_fonts(wgs, (win_width-window_border*2)/wgs->term->cols, - (win_height-window_border*2)/wgs->term->rows); - wgs->offset_width = (win_width-wgs->font_width*wgs->term->cols)/2; - wgs->offset_height = (win_height-wgs->font_height*wgs->term->rows)/2; - - wgs->extra_width = - wr.right - wr.left - cr.right + cr.left + wgs->offset_width*2; - wgs->extra_height = - wr.bottom - wr.top - cr.bottom + cr.top + wgs->offset_height*2; - - InvalidateRect(wgs->term_hwnd, NULL, true); -#ifdef RDB_DEBUG_PATCH - debug("reset_window() -> font resize to (%d,%d)\n", - wgs->font_width, wgs->font_height); -#endif - } -} - -static void set_input_locale(WinGuiSeat *wgs, HKL kl) -{ - char lbuf[20]; - - GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE, - lbuf, sizeof(lbuf)); - - wgs->kbd_codepage = atoi(lbuf); -} - -static void click(WinGuiSeat *wgs, Mouse_Button b, int x, int y, - bool shift, bool ctrl, bool alt) -{ - int thistime = GetMessageTime(); - - if (wgs->send_raw_mouse && - !(shift && conf_get_bool(wgs->conf, CONF_mouse_override))) { - wgs->lastbtn = MBT_NOTHING; - term_mouse(wgs->term, b, translate_button(wgs, b), MA_CLICK, - x, y, shift, ctrl, alt); - return; - } - - if (wgs->lastbtn == b && thistime - wgs->lasttime < wgs->dbltime) { - wgs->lastact = (wgs->lastact == MA_CLICK ? MA_2CLK : - wgs->lastact == MA_2CLK ? MA_3CLK : - wgs->lastact == MA_3CLK ? MA_CLICK : MA_NOTHING); - } else { - wgs->lastbtn = b; - wgs->lastact = MA_CLICK; - } - if (wgs->lastact != MA_NOTHING) - term_mouse(wgs->term, b, translate_button(wgs, b), wgs->lastact, - x, y, shift, ctrl, alt); - wgs->lasttime = thistime; -} - -/* - * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT) - * into a cooked one (SELECT, EXTEND, PASTE). - */ -static Mouse_Button translate_button(WinGuiSeat *wgs, Mouse_Button button) -{ - if (button == MBT_LEFT) - return MBT_SELECT; - if (button == MBT_MIDDLE) - return conf_get_int(wgs->conf, CONF_mouse_is_xterm) == MOUSE_XTERM ? - MBT_PASTE : MBT_EXTEND; - if (button == MBT_RIGHT) - return conf_get_int(wgs->conf, CONF_mouse_is_xterm) == MOUSE_XTERM ? - MBT_EXTEND : MBT_PASTE; - return 0; /* shouldn't happen */ -} - -static void show_mouseptr(WinGuiSeat *wgs, bool show) -{ - /* NB that the counter in ShowCursor() is also frobbed by - * update_mouse_pointer() */ - static bool cursor_visible = true; - if (wgs) { - if (!conf_get_bool(wgs->conf, CONF_hide_mouseptr)) - show = true; /* hiding mouse pointer disabled in Conf */ - } else { - /* - * You can pass wgs==NULL if you want to _show_ the pointer - * rather than hiding it, because that's never disallowed. - */ - assert(show); - } - if (cursor_visible && !show) - ShowCursor(false); - else if (!cursor_visible && show) - ShowCursor(true); - cursor_visible = show; -} - -static bool is_alt_pressed(void) -{ - BYTE keystate[256]; - int r = GetKeyboardState(keystate); - if (!r) - return false; - if (keystate[VK_MENU] & 0x80) - return true; - if (keystate[VK_RMENU] & 0x80) - return true; - return false; -} - -static void exit_callback(void *vctx) -{ - WinGuiSeat *wgs = (WinGuiSeat *)vctx; - int exitcode, close_on_exit; - - if (!wgs->session_closed && - (exitcode = backend_exitcode(wgs->backend)) >= 0) { - close_on_exit = conf_get_int(wgs->conf, CONF_close_on_exit); - /* Abnormal exits will already have set session_closed and taken - * appropriate action. */ - if (close_on_exit == FORCE_ON || - (close_on_exit == AUTO && exitcode != INT_MAX)) { - PostQuitMessage(0); - } else { - queue_toplevel_callback(close_session, wgs); - wgs->session_closed = true; - /* exitcode == INT_MAX indicates that the connection was closed - * by a fatal error, so an error box will be coming our way and - * we should not generate this informational one. */ - if (exitcode != INT_MAX) { - show_mouseptr(wgs, true); - MessageBox(wgs->term_hwnd, "Connection closed by remote host", - appname, MB_OK | MB_ICONINFORMATION); - } - } - } -} - -static void win_seat_notify_remote_exit(Seat *seat) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - queue_toplevel_callback(exit_callback, wgs); -} - -static void conf_cache_data(WinGuiSeat *wgs) -{ - /* Cache some items from conf to speed lookups in very hot code */ - wgs->cursor_type = conf_get_int(wgs->conf, CONF_cursor_type); - wgs->vtmode = conf_get_int(wgs->conf, CONF_vtmode); -} - -static const int clips_system[] = { CLIP_SYSTEM }; - -static HDC make_hdc(WinGuiSeat *wgs) -{ - HDC hdc; - - if (!wgs->term_hwnd) - return NULL; - - hdc = GetDC(wgs->term_hwnd); - if (!hdc) - return NULL; - - SelectPalette(hdc, wgs->pal, false); - return hdc; -} - -static void free_hdc(WinGuiSeat *wgs, HDC hdc) -{ - assert(wgs->term_hwnd); - SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), false); - ReleaseDC(wgs->term_hwnd, hdc); -} - -static void wm_size_resize_term(WinGuiSeat *wgs, LPARAM lParam, bool border) -{ - int width = LOWORD(lParam); - int height = HIWORD(lParam); - int border_size = border ? conf_get_int(wgs->conf, CONF_window_border) : 0; - - int w = (width - border_size*2) / wgs->font_width; - int h = (height - border_size*2) / wgs->font_height; - - if (w < 1) w = 1; - if (h < 1) h = 1; - - if (wgs->resizing) { - /* - * If we're in the middle of an interactive resize, we don't - * call term_size. This means that, firstly, the user can drag - * the size back and forth indecisively without wiping out any - * actual terminal contents, and secondly, the Terminal - * doesn't call back->size in turn for each increment of the - * resizing drag, so we don't spam the server with huge - * numbers of resize events. - */ - wgs->need_backend_resize = true; - conf_set_int(wgs->conf, CONF_height, h); - conf_set_int(wgs->conf, CONF_width, w); - } else { - term_size(wgs->term, h, w, - conf_get_int(wgs->conf, CONF_savelines)); - } -} - -static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, - WPARAM wParam, LPARAM lParam) -{ - HDC hdc; - int resize_action; - WinGuiSeat *wgs = (WinGuiSeat *)GetWindowLongPtr(hwnd, GWLP_USERDATA); - - switch (message) { - case WM_CREATE: - break; - case WM_CLOSE: { - char *title, *msg, *additional = NULL; - show_mouseptr(wgs, true); - title = dupprintf("%s Exit Confirmation", appname); - if (wgs->backend && wgs->backend->vt->close_warn_text) { - additional = wgs->backend->vt->close_warn_text(wgs->backend); - } - msg = dupprintf("Are you sure you want to close this session?%s%s", - additional ? "\n" : "", - additional ? additional : ""); - if (wgs->session_closed || - !conf_get_bool(wgs->conf, CONF_warn_on_close) || - MessageBox(hwnd, msg, title, - MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1) - == IDOK) - DestroyWindow(hwnd); - sfree(title); - sfree(msg); - sfree(additional); - return 0; - } - case WM_DESTROY: - show_mouseptr(wgs, true); - PostQuitMessage(0); - return 0; - case WM_INITMENUPOPUP: - if ((HMENU)wParam == wgs->savedsess_menu) { - /* About to pop up Saved Sessions sub-menu. - * Refresh the session list. */ - get_sesslist(&sesslist, false); /* free */ - get_sesslist(&sesslist, true); - update_savedsess_menu(wgs); - return 0; - } - break; - case WM_COMMAND: - case WM_SYSCOMMAND: - switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */ - case SC_VSCROLL: - case SC_HSCROLL: - if (message == WM_SYSCOMMAND) { - /* As per the long comment in WM_VSCROLL handler: give - * this message the default handling, which starts a - * subsidiary message loop, but set a flag so that - * when we're re-entered from that loop, scroll events - * within an interactive scrollbar-drag can be handled - * differently. */ - wgs->in_scrollbar_loop = true; - LRESULT result = sw_DefWindowProc( - hwnd, message, wParam, lParam); - wgs->in_scrollbar_loop = false; - return result; - } - break; - case IDM_SHOWLOG: - showeventlog(hwnd); - break; - case IDM_NEWSESS: - case IDM_DUPSESS: - case IDM_SAVEDSESS: { - char b[2048]; - char *cl; - const char *argprefix; - bool inherit_handles; - STARTUPINFO si; - PROCESS_INFORMATION pi; - HANDLE filemap = NULL; - - if (restricted_acl()) - argprefix = "&R"; - else - argprefix = ""; - - if (wParam == IDM_DUPSESS) { - /* - * Allocate a file-mapping memory chunk for the - * config structure. - */ - SECURITY_ATTRIBUTES sa; - strbuf *serbuf; - void *p; - int size; - - serbuf = strbuf_new(); - conf_serialise(BinarySink_UPCAST(serbuf), wgs->conf); - size = serbuf->len; - - sa.nLength = sizeof(sa); - sa.lpSecurityDescriptor = NULL; - sa.bInheritHandle = true; - filemap = CreateFileMapping(INVALID_HANDLE_VALUE, - &sa, - PAGE_READWRITE, - 0, size, NULL); - if (filemap && filemap != INVALID_HANDLE_VALUE) { - p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, size); - if (p) { - memcpy(p, serbuf->s, size); - UnmapViewOfFile(p); - } - } - - strbuf_free(serbuf); - inherit_handles = true; - cl = dupprintf("putty %s&%p:%u", argprefix, - filemap, (unsigned)size); - } else if (wParam == IDM_SAVEDSESS) { - unsigned int sessno = ((lParam - IDM_SAVED_MIN) - / MENU_SAVED_STEP) + 1; - if (sessno < (unsigned)sesslist.nsessions) { - const char *session = sesslist.sessions[sessno]; - cl = dupprintf("putty %s@%s", argprefix, session); - inherit_handles = false; - } else - break; - } else /* IDM_NEWSESS */ { - cl = dupprintf("putty%s%s", - *argprefix ? " " : "", - argprefix); - inherit_handles = false; - } - - GetModuleFileName(NULL, b, sizeof(b) - 1); - si.cb = sizeof(si); - si.lpReserved = NULL; - si.lpDesktop = NULL; - si.lpTitle = NULL; - si.dwFlags = 0; - si.cbReserved2 = 0; - si.lpReserved2 = NULL; - CreateProcess(b, cl, NULL, NULL, inherit_handles, - NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - - if (filemap) - CloseHandle(filemap); - sfree(cl); - break; - } - case IDM_RESTART: - if (!wgs->backend) { - lp_eventlog(&wgs->logpolicy, "----- Session restarted -----"); - term_pwron(wgs->term, false); - start_backend(wgs); - } - - break; - case IDM_RECONF: { - Conf *prev_conf; - int init_lvl = 1; - bool reconfig_result; - - if (wgs->reconfiguring) - break; - else - wgs->reconfiguring = true; - - term_pre_reconfig(wgs->term, wgs->conf); - prev_conf = conf_copy(wgs->conf); - - reconfig_result = do_reconfig( - hwnd, wgs->conf, - wgs->backend ? backend_cfg_info(wgs->backend) : 0); - wgs->reconfiguring = false; - if (!reconfig_result) { - conf_free(prev_conf); - break; - } - - conf_cache_data(wgs); - - resize_action = conf_get_int(wgs->conf, CONF_resize_action); - { - /* Disable full-screen if resizing forbidden */ - int i; - for (i = 0; i < lenof(wgs->popup_menus); i++) - EnableMenuItem(wgs->popup_menus[i].menu, IDM_FULLSCREEN, - MF_BYCOMMAND | - (resize_action == RESIZE_DISABLED - ? MF_GRAYED : MF_ENABLED)); - /* Gracefully unzoom if necessary */ - if (IsZoomed(hwnd) && (resize_action == RESIZE_DISABLED)) - ShowWindow(hwnd, SW_RESTORE); - } - - /* Pass new config data to the logging module */ - log_reconfig(wgs->logctx, wgs->conf); - - sfree(wgs->logpal); - /* - * Flush the line discipline's edit buffer in the - * case where local editing has just been disabled. - */ - if (wgs->ldisc) { - ldisc_configure(wgs->ldisc, wgs->conf); - ldisc_echoedit_update(wgs->ldisc); - } - - if (conf_get_bool(wgs->conf, CONF_system_colour) != - conf_get_bool(prev_conf, CONF_system_colour)) - term_notify_palette_changed(wgs->term); - - /* Pass new config data to the terminal */ - term_reconfig(wgs->term, wgs->conf); - setup_clipboards(wgs->term, wgs->conf); - - /* Reinitialise the colour palette, in case the terminal - * just read new settings out of Conf */ - if (wgs->pal) - DeleteObject(wgs->pal); - wgs->logpal = NULL; - wgs->pal = NULL; - init_palette(wgs); - - /* Pass new config data to the back end */ - if (wgs->backend) - backend_reconfig(wgs->backend, wgs->conf); - - /* Screen size changed ? */ - if (conf_get_int(wgs->conf, CONF_height) != - conf_get_int(prev_conf, CONF_height) || - conf_get_int(wgs->conf, CONF_width) != - conf_get_int(prev_conf, CONF_width) || - conf_get_int(wgs->conf, CONF_savelines) != - conf_get_int(prev_conf, CONF_savelines) || - resize_action == RESIZE_FONT || - (resize_action == RESIZE_EITHER && IsZoomed(hwnd)) || - resize_action == RESIZE_DISABLED) - term_size(wgs->term, conf_get_int(wgs->conf, CONF_height), - conf_get_int(wgs->conf, CONF_width), - conf_get_int(wgs->conf, CONF_savelines)); - - /* Enable or disable the scroll bar, etc */ - { - LONG nflg, flag = GetWindowLongPtr(hwnd, GWL_STYLE); - LONG nexflag, exflag = - GetWindowLongPtr(hwnd, GWL_EXSTYLE); - - nexflag = exflag; - if (conf_get_bool(wgs->conf, CONF_alwaysontop) != - conf_get_bool(prev_conf, CONF_alwaysontop)) { - if (conf_get_bool(wgs->conf, CONF_alwaysontop)) { - nexflag |= WS_EX_TOPMOST; - SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE); - } else { - nexflag &= ~(WS_EX_TOPMOST); - SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE); - } - } - if (conf_get_bool(wgs->conf, CONF_sunken_edge)) - nexflag |= WS_EX_CLIENTEDGE; - else - nexflag &= ~(WS_EX_CLIENTEDGE); - - nflg = flag; - if (conf_get_bool(wgs->conf, is_full_screen(wgs) ? - CONF_scrollbar_in_fullscreen : - CONF_scrollbar)) - nflg |= WS_VSCROLL; - else - nflg &= ~WS_VSCROLL; - - if (resize_action == RESIZE_DISABLED || - is_full_screen(wgs)) - nflg &= ~WS_THICKFRAME; - else - nflg |= WS_THICKFRAME; - - if (resize_action == RESIZE_DISABLED) - nflg &= ~WS_MAXIMIZEBOX; - else - nflg |= WS_MAXIMIZEBOX; - - if (nflg != flag || nexflag != exflag) { - if (nflg != flag) - SetWindowLongPtr(hwnd, GWL_STYLE, nflg); - if (nexflag != exflag) - SetWindowLongPtr(hwnd, GWL_EXSTYLE, nexflag); - - SetWindowPos(hwnd, NULL, 0, 0, 0, 0, - SWP_NOACTIVATE | SWP_NOCOPYBITS | - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | - SWP_FRAMECHANGED); - - init_lvl = 2; - } - } - - /* Oops */ - if (resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) { - force_normal(hwnd); - init_lvl = 2; - } - - { - FontSpec *font = conf_get_fontspec(wgs->conf, CONF_font); - FontSpec *prev_font = conf_get_fontspec(prev_conf, - CONF_font); - - if (!strcmp(font->name, prev_font->name) || - !strcmp(conf_get_str(wgs->conf, CONF_line_codepage), - conf_get_str(prev_conf, CONF_line_codepage)) || - font->isbold != prev_font->isbold || - font->height != prev_font->height || - font->charset != prev_font->charset || - conf_get_int(wgs->conf, CONF_font_quality) != - conf_get_int(prev_conf, CONF_font_quality) || - conf_get_int(wgs->conf, CONF_vtmode) != - conf_get_int(prev_conf, CONF_vtmode) || - conf_get_int(wgs->conf, CONF_bold_style) != - conf_get_int(prev_conf, CONF_bold_style) || - resize_action == RESIZE_DISABLED || - resize_action == RESIZE_EITHER || - resize_action != conf_get_int(prev_conf, - CONF_resize_action)) - init_lvl = 2; - } - - InvalidateRect(hwnd, NULL, true); - reset_window(wgs, init_lvl); - - conf_free(prev_conf); - break; - } - case IDM_COPYALL: - term_copyall(wgs->term, clips_system, lenof(clips_system)); - break; - case IDM_COPY: - term_request_copy(wgs->term, clips_system, lenof(clips_system)); - break; - case IDM_PASTE: - term_request_paste(wgs->term, CLIP_SYSTEM); - break; - case IDM_CLRSB: - term_clrsb(wgs->term); - break; - case IDM_RESET: - term_pwron(wgs->term, true); - if (wgs->ldisc) - ldisc_echoedit_update(wgs->ldisc); - break; - case IDM_ABOUT: - showabout(hwnd); - break; - case IDM_HELP: - launch_help(hwnd, NULL); - break; - case SC_MOUSEMENU: - /* - * We get this if the System menu has been activated - * using the mouse. - */ - show_mouseptr(wgs, true); - break; - case SC_KEYMENU: - /* - * We get this if the System menu has been activated - * using the keyboard. This might happen from within - * TranslateKey, in which case it really wants to be - * followed by a `space' character to actually _bring - * the menu up_ rather than just sitting there in - * `ready to appear' state. - */ - show_mouseptr(wgs, true); /* make sure pointer is visible */ - if (lParam == 0) - PostMessage(hwnd, WM_CHAR, ' ', 0); - break; - case IDM_FULLSCREEN: - flip_full_screen(wgs); - break; - default: - if (wParam >= IDM_SAVED_MIN && wParam < IDM_SAVED_MAX) { - SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam); - } - if (wParam >= IDM_SPECIAL_MIN && wParam <= IDM_SPECIAL_MAX) { - int i = (wParam - IDM_SPECIAL_MIN) / 0x10; - /* - * Ensure we haven't been sent a bogus SYSCOMMAND - * which would cause us to reference invalid memory - * and crash. Perhaps I'm just too paranoid here. - */ - if (i >= wgs->n_specials) - break; - if (wgs->backend) - backend_special(wgs->backend, wgs->specials[i].code, - wgs->specials[i].arg); - } - } - break; - -#define X_POS(l) ((int)(short)LOWORD(l)) -#define Y_POS(l) ((int)(short)HIWORD(l)) - -#define TO_CHR_X(x) ((((x)<0 ? (x)-wgs->font_width+1 : \ - (x))-wgs->offset_width) / wgs->font_width) -#define TO_CHR_Y(y) ((((y)<0 ? (y)-wgs->font_height+1 : \ - (y))-wgs->offset_height) / wgs->font_height) - case WM_LBUTTONDOWN: - case WM_MBUTTONDOWN: - case WM_RBUTTONDOWN: - case WM_LBUTTONUP: - case WM_MBUTTONUP: - case WM_RBUTTONUP: - if (message == WM_RBUTTONDOWN && - ((wParam & MK_CONTROL) || - (conf_get_int(wgs->conf, CONF_mouse_is_xterm) == MOUSE_WINDOWS))) { - POINT cursorpos; - - /* Just in case this happened in mid-select */ - term_cancel_selection_drag(wgs->term); - - show_mouseptr(wgs, true); /* make sure pointer is visible */ - GetCursorPos(&cursorpos); - TrackPopupMenu(wgs->popup_menus[CTXMENU].menu, - TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON, - cursorpos.x, cursorpos.y, - 0, hwnd, NULL); - break; - } - { - int button; - bool press; - - switch (message) { - case WM_LBUTTONDOWN: - button = MBT_LEFT; - wParam |= MK_LBUTTON; - press = true; - break; - case WM_MBUTTONDOWN: - button = MBT_MIDDLE; - wParam |= MK_MBUTTON; - press = true; - break; - case WM_RBUTTONDOWN: - button = MBT_RIGHT; - wParam |= MK_RBUTTON; - press = true; - break; - case WM_LBUTTONUP: - button = MBT_LEFT; - wParam &= ~MK_LBUTTON; - press = false; - break; - case WM_MBUTTONUP: - button = MBT_MIDDLE; - wParam &= ~MK_MBUTTON; - press = false; - break; - case WM_RBUTTONUP: - button = MBT_RIGHT; - wParam &= ~MK_RBUTTON; - press = false; - break; - default: /* shouldn't happen */ - button = 0; - press = false; - } - show_mouseptr(wgs, true); - /* - * Special case: in full-screen mode, if the left - * button is clicked in the very top left corner of the - * window, we put up the System menu instead of doing - * selection. - */ - { - bool mouse_on_hotspot = false; - POINT pt; - - GetCursorPos(&pt); -#ifndef NO_MULTIMON - if (p_GetMonitorInfoA && p_MonitorFromPoint) { - HMONITOR mon; - MONITORINFO mi; - - mon = p_MonitorFromPoint(pt, MONITOR_DEFAULTTONULL); - - if (mon != NULL) { - mi.cbSize = sizeof(MONITORINFO); - p_GetMonitorInfoA(mon, &mi); - - if (mi.rcMonitor.left == pt.x && - mi.rcMonitor.top == pt.y) { - mouse_on_hotspot = true; - } - } - } else -#endif - if (pt.x == 0 && pt.y == 0) { - mouse_on_hotspot = true; - } - if (is_full_screen(wgs) && press && - button == MBT_LEFT && mouse_on_hotspot) { - SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, - MAKELPARAM(pt.x, pt.y)); - return 0; - } - } - - if (press) { - click(wgs, button, - TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)), - wParam & MK_SHIFT, wParam & MK_CONTROL, - is_alt_pressed()); - SetCapture(hwnd); - } else { - term_mouse(wgs->term, button, translate_button(wgs, button), - MA_RELEASE, TO_CHR_X(X_POS(lParam)), - TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT, - wParam & MK_CONTROL, is_alt_pressed()); - if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON))) - ReleaseCapture(); - } - } - return 0; - case WM_MOUSEMOVE: - /* - * Windows seems to like to occasionally send MOUSEMOVE - * events even if the mouse hasn't moved. Don't unhide - * the mouse pointer in this case. - */ - if (wgs->last_mousemove != WM_MOUSEMOVE || - wParam != wgs->last_wm_mousemove_wParam || - lParam != wgs->last_wm_mousemove_lParam) { - show_mouseptr(wgs, true); - wgs->last_mousemove = WM_MOUSEMOVE; - wgs->last_wm_mousemove_wParam = wParam; - wgs->last_wm_mousemove_lParam = lParam; - } - /* - * Add the mouse position and message time to the random - * number noise. - */ - noise_ultralight(NOISE_SOURCE_MOUSEPOS, lParam); - - if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) && - GetCapture() == hwnd) { - Mouse_Button b; - if (wParam & MK_LBUTTON) - b = MBT_LEFT; - else if (wParam & MK_MBUTTON) - b = MBT_MIDDLE; - else - b = MBT_RIGHT; - term_mouse(wgs->term, b, translate_button(wgs, b), MA_DRAG, - TO_CHR_X(X_POS(lParam)), - TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT, - wParam & MK_CONTROL, is_alt_pressed()); - } else { - term_mouse(wgs->term, MBT_NOTHING, MBT_NOTHING, MA_MOVE, - TO_CHR_X(X_POS(lParam)), - TO_CHR_Y(Y_POS(lParam)), false, - false, false); - } - return 0; - case WM_NCMOUSEMOVE: - if (wgs->last_mousemove != WM_NCMOUSEMOVE || - wParam != wgs->last_wm_ncmousemove_wParam || - lParam != wgs->last_wm_ncmousemove_lParam) { - show_mouseptr(wgs, true); - wgs->last_mousemove = WM_NCMOUSEMOVE; - wgs->last_wm_ncmousemove_wParam = wParam; - wgs->last_wm_ncmousemove_lParam = lParam; - } - noise_ultralight(NOISE_SOURCE_MOUSEPOS, lParam); - break; - case WM_IGNORE_CLIP: - wgs->ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */ - break; - case WM_DESTROYCLIPBOARD: - if (!wgs->ignore_clip) - term_lost_clipboard_ownership(wgs->term, CLIP_SYSTEM); - wgs->ignore_clip = false; - return 0; - case WM_PAINT: { - PAINTSTRUCT p; - - HideCaret(hwnd); - hdc = BeginPaint(hwnd, &p); - if (wgs->pal) { - SelectPalette(hdc, wgs->pal, true); - RealizePalette(hdc); - } - - /* - * We have to be careful about term_paint(). It will - * set a bunch of character cells to INVALID and then - * call do_paint(), which will redraw those cells and - * _then mark them as done_. This may not be accurate: - * when painting in WM_PAINT context we are restricted - * to the rectangle which has just been exposed - so if - * that only covers _part_ of a character cell and the - * rest of it was already visible, that remainder will - * not be redrawn at all. Accordingly, we must not - * paint any character cell in a WM_PAINT context which - * already has a pending update due to terminal output. - * The simplest solution to this - and many, many - * thanks to Hung-Te Lin for working all this out - is - * not to do any actual painting at _all_ if there's a - * pending terminal update: just mark the relevant - * character cells as INVALID and wait for the - * scheduled full update to sort it out. - * - * I have a suspicion this isn't the _right_ solution. - * An alternative approach would be to have terminal.c - * separately track what _should_ be on the terminal - * screen and what _is_ on the terminal screen, and - * have two completely different types of redraw (one - * for full updates, which syncs the former with the - * terminal itself, and one for WM_PAINT which syncs - * the latter with the former); yet another possibility - * would be to have the Windows front end do what the - * GTK one already does, and maintain a bitmap of the - * current terminal appearance so that WM_PAINT becomes - * completely trivial. However, this should do for now. - */ - assert(!wgs->wintw_hdc); - wgs->wintw_hdc = hdc; - term_paint(wgs->term, - (p.rcPaint.left-wgs->offset_width)/wgs->font_width, - (p.rcPaint.top-wgs->offset_height)/wgs->font_height, - (p.rcPaint.right-wgs->offset_width-1)/wgs->font_width, - (p.rcPaint.bottom-wgs->offset_height-1)/wgs->font_height, - !wgs->term->window_update_pending); - wgs->wintw_hdc = NULL; - - if (p.fErase || - p.rcPaint.left < wgs->offset_width || - p.rcPaint.top < wgs->offset_height || - p.rcPaint.right >= (wgs->offset_width + - wgs->font_width*wgs->term->cols) || - p.rcPaint.bottom>= (wgs->offset_height + - wgs->font_height*wgs->term->rows)) { - HBRUSH fillcolour, oldbrush; - HPEN edge, oldpen; - fillcolour = CreateSolidBrush ( - wgs->colours[ATTR_DEFBG>>ATTR_BGSHIFT]); - oldbrush = SelectObject(hdc, fillcolour); - edge = CreatePen(PS_SOLID, 0, - wgs->colours[ATTR_DEFBG>>ATTR_BGSHIFT]); - oldpen = SelectObject(hdc, edge); - - /* - * Jordan Russell reports that this apparently - * ineffectual IntersectClipRect() call masks a - * Windows NT/2K bug causing strange display - * problems when the PuTTY window is taller than - * the primary monitor. It seems harmless enough... - */ - IntersectClipRect(hdc, - p.rcPaint.left, p.rcPaint.top, - p.rcPaint.right, p.rcPaint.bottom); - - ExcludeClipRect( - hdc, wgs->offset_width, wgs->offset_height, - wgs->offset_width+wgs->font_width*wgs->term->cols, - wgs->offset_height+wgs->font_height*wgs->term->rows); - - Rectangle(hdc, p.rcPaint.left, p.rcPaint.top, - p.rcPaint.right, p.rcPaint.bottom); - - /* SelectClipRgn(hdc, NULL); */ - - SelectObject(hdc, oldbrush); - DeleteObject(fillcolour); - SelectObject(hdc, oldpen); - DeleteObject(edge); - } - SelectObject(hdc, GetStockObject(SYSTEM_FONT)); - SelectObject(hdc, GetStockObject(WHITE_PEN)); - EndPaint(hwnd, &p); - ShowCaret(hwnd); - return 0; - } - case WM_NETEVENT: - winselgui_response(wParam, lParam); - return 0; - case WM_SETFOCUS: - term_set_focus(wgs->term, true); - CreateCaret(hwnd, wgs->caretbm, wgs->font_width, wgs->font_height); - ShowCaret(hwnd); - flash_window(wgs, 0); /* stop */ - wgs->compose_state = 0; - term_update(wgs->term); - break; - case WM_KILLFOCUS: - show_mouseptr(wgs, true); - term_set_focus(wgs->term, false); - DestroyCaret(); - wgs->caret_x = wgs->caret_y = -1; /* ensure caret replaced next time */ - term_update(wgs->term); - break; - case WM_ENTERSIZEMOVE: -#ifdef RDB_DEBUG_PATCH - debug("WM_ENTERSIZEMOVE\n"); -#endif - EnableSizeTip(true); - wgs->resizing = true; - wgs->need_backend_resize = false; - break; - case WM_EXITSIZEMOVE: - EnableSizeTip(false); - wgs->resizing = false; -#ifdef RDB_DEBUG_PATCH - debug("WM_EXITSIZEMOVE\n"); -#endif - if (wgs->need_backend_resize) { - term_size(wgs->term, conf_get_int(wgs->conf, CONF_height), - conf_get_int(wgs->conf, CONF_width), - conf_get_int(wgs->conf, CONF_savelines)); - InvalidateRect(hwnd, NULL, true); - } - recompute_window_offset(wgs); - break; - case WM_SIZING: - /* - * This does two jobs: - * 1) Keep the sizetip uptodate - * 2) Make sure the window size is _stepped_ in units of the font size. - */ - resize_action = conf_get_int(wgs->conf, CONF_resize_action); - if (resize_action == RESIZE_TERM || - (resize_action == RESIZE_EITHER && !is_alt_pressed())) { - int width, height, w, h, ew, eh; - LPRECT r = (LPRECT) lParam; - - if (!wgs->need_backend_resize && resize_action == RESIZE_EITHER && - (conf_get_int(wgs->conf, CONF_height) != wgs->term->rows || - conf_get_int(wgs->conf, CONF_width) != wgs->term->cols)) { - /* - * Great! It seems that both the terminal size and the - * font size have been changed and the user is now dragging. - * - * It will now be difficult to get back to the configured - * font size! - * - * This would be easier but it seems to be too confusing. - */ - conf_set_int(wgs->conf, CONF_height, wgs->term->rows); - conf_set_int(wgs->conf, CONF_width, wgs->term->cols); - - InvalidateRect(hwnd, NULL, true); - wgs->need_backend_resize = true; - } - - width = r->right - r->left - wgs->extra_width; - height = r->bottom - r->top - wgs->extra_height; - w = (width + wgs->font_width / 2) / wgs->font_width; - if (w < 1) - w = 1; - h = (height + wgs->font_height / 2) / wgs->font_height; - if (h < 1) - h = 1; - UpdateSizeTip(hwnd, w, h); - ew = width - w * wgs->font_width; - eh = height - h * wgs->font_height; - if (ew != 0) { - if (wParam == WMSZ_LEFT || - wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT) - r->left += ew; - else - r->right -= ew; - } - if (eh != 0) { - if (wParam == WMSZ_TOP || - wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT) - r->top += eh; - else - r->bottom -= eh; - } - if (ew || eh) - return 1; - else - return 0; - } else { - int width, height, w, h, rv = 0; - int window_border = conf_get_int(wgs->conf, CONF_window_border); - int ex_width = wgs->extra_width + - (window_border - wgs->offset_width) * 2; - int ex_height = wgs->extra_height + - (window_border - wgs->offset_height) * 2; - LPRECT r = (LPRECT) lParam; - - width = r->right - r->left - ex_width; - height = r->bottom - r->top - ex_height; - - w = (width + wgs->term->cols/2)/wgs->term->cols; - h = (height + wgs->term->rows/2)/wgs->term->rows; - if ( r->right != r->left + w*wgs->term->cols + ex_width) - rv = 1; - - if (wParam == WMSZ_LEFT || - wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT) - r->left = r->right - w*wgs->term->cols - ex_width; - else - r->right = r->left + w*wgs->term->cols + ex_width; - - if (r->bottom != r->top + h*wgs->term->rows + ex_height) - rv = 1; - - if (wParam == WMSZ_TOP || - wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT) - r->top = r->bottom - h*wgs->term->rows - ex_height; - else - r->bottom = r->top + h*wgs->term->rows + ex_height; - - return rv; - } - /* break; (never reached) */ - case WM_FULLSCR_ON_MAX: - wgs->fullscr_on_max = true; - break; - case WM_MOVE: - term_notify_window_pos(wgs->term, LOWORD(lParam), HIWORD(lParam)); - sys_cursor_update(wgs); - break; - case WM_SIZE: - resize_action = conf_get_int(wgs->conf, CONF_resize_action); -#ifdef RDB_DEBUG_PATCH - debug("WM_SIZE %s (%d,%d)\n", - (wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED": - (wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED": - (wParam == SIZE_RESTORED && resizing) ? "to": - (wParam == SIZE_RESTORED) ? "SIZE_RESTORED": - "...", - LOWORD(lParam), HIWORD(lParam)); -#endif - term_notify_minimised(wgs->term, wParam == SIZE_MINIMIZED); - { - /* - * WM_SIZE's lParam tells us the size of the client area. - * But historic PuTTY practice is that we want to tell the - * terminal the size of the overall window. - */ - RECT r; - GetWindowRect(hwnd, &r); - term_notify_window_size_pixels( - wgs->term, r.right - r.left, r.bottom - r.top); - } - if (wParam == SIZE_MINIMIZED) - sw_SetWindowText(hwnd, - conf_get_bool(wgs->conf, CONF_win_name_always) ? - wgs->window_name : wgs->icon_name); - if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) - sw_SetWindowText(hwnd, wgs->window_name); - if (wParam == SIZE_RESTORED) { - wgs->processed_resize = false; - clear_full_screen(wgs); - if (wgs->processed_resize) { - /* - * Inhibit normal processing of this WM_SIZE; a - * secondary one was triggered just now by - * clear_full_screen which contained the correct - * client area size. - */ - return 0; - } - } - if (wParam == SIZE_MAXIMIZED && wgs->fullscr_on_max) { - wgs->fullscr_on_max = false; - wgs->processed_resize = false; - make_full_screen(wgs); - if (wgs->processed_resize) { - /* - * Inhibit normal processing of this WM_SIZE; a - * secondary one was triggered just now by - * make_full_screen which contained the correct client - * area size. - */ - return 0; - } - } - - wgs->processed_resize = true; - - if (resize_action == RESIZE_DISABLED) { - /* A resize, well it better be a minimize. */ - reset_window(wgs, -1); - } else { - if (wParam == SIZE_MAXIMIZED) { - wgs->was_zoomed = true; - wgs->prev_rows = wgs->term->rows; - wgs->prev_cols = wgs->term->cols; - if (resize_action == RESIZE_TERM) - wm_size_resize_term(wgs, lParam, false); - reset_window(wgs, 0); - } else if (wParam == SIZE_RESTORED && wgs->was_zoomed) { - wgs->was_zoomed = false; - if (resize_action == RESIZE_TERM) { - wm_size_resize_term(wgs, lParam, true); - reset_window(wgs, 2); - } else if (resize_action != RESIZE_FONT) - reset_window(wgs, 2); - else - reset_window(wgs, 0); - } else if (wParam == SIZE_MINIMIZED) { - /* do nothing */ - } else if (resize_action == RESIZE_TERM || - (resize_action == RESIZE_EITHER && - !is_alt_pressed())) { - wm_size_resize_term(wgs, lParam, true); - - /* - * Sometimes, we can get a spontaneous resize event - * outside a WM_SIZING interactive drag which wants to - * set us to a new specific SIZE_RESTORED size. An - * example is what happens if you press Windows+Right - * and then Windows+Up: the first operation fits the - * window to the right-hand half of the screen, and - * the second one changes that for the top right - * quadrant. In that situation, if we've responded - * here by resizing the terminal, we may still need to - * recompute the border around the window and do a - * full redraw to clear the new border. - */ - if (!wgs->resizing) - recompute_window_offset(wgs); - } else { - reset_window(wgs, 0); - } - } - sys_cursor_update(wgs); - return 0; - case WM_DPICHANGED: - wgs->dpi_info.cur_dpi.x = LOWORD(wParam); - wgs->dpi_info.cur_dpi.y = HIWORD(wParam); - wgs->dpi_info.new_wnd_rect = *(RECT*)(lParam); - reset_window(wgs, 3); - return 0; - case WM_VSCROLL: - switch (LOWORD(wParam)) { - case SB_BOTTOM: - term_scroll(wgs->term, -1, 0); - break; - case SB_TOP: - term_scroll(wgs->term, +1, 0); - break; - case SB_LINEDOWN: - term_scroll(wgs->term, 0, +1); - break; - case SB_LINEUP: - term_scroll(wgs->term, 0, -1); - break; - case SB_PAGEDOWN: - term_scroll(wgs->term, 0, +wgs->term->rows / 2); - break; - case SB_PAGEUP: - term_scroll(wgs->term, 0, -wgs->term->rows / 2); - break; - case SB_THUMBPOSITION: - case SB_THUMBTRACK: { - /* - * Use GetScrollInfo instead of HIWORD(wParam) to get - * 32-bit scroll position. - */ - SCROLLINFO si; - - si.cbSize = sizeof(si); - si.fMask = SIF_TRACKPOS; - if (GetScrollInfo(hwnd, SB_VERT, &si) == 0) - si.nTrackPos = HIWORD(wParam); - term_scroll(wgs->term, 1, si.nTrackPos); - break; - } - } - - if (wgs->in_scrollbar_loop) { - /* - * Allow window updates to happen during interactive - * scroll. - * - * When the user takes hold of our window's scrollbar and - * wobbles it interactively back and forth, or presses on - * one of the arrow buttons at the ends, the first thing - * that happens is that this window procedure receives - * WM_SYSCOMMAND / SC_VSCROLL. [1] The default handler for - * that window message starts a subsidiary message loop, - * which continues to run until the user lets go of the - * scrollbar again. All WM_VSCROLL / SB_THUMBTRACK - * messages are generated by the handlers within that - * subsidiary message loop. - * - * So, during that time, _our_ message loop is not - * running, which means toplevel callbacks and timers and - * so forth are not happening, which means that when we - * redraw the window and set a timer to clear the cooldown - * flag 20ms later, that timer never fires, and we aren't - * able to keep redrawing the window. - * - * The 'obvious' answer would be to seize that SYSCOMMAND - * ourselves and inhibit the default handler, so that our - * message loop carries on running. But that would mean - * we'd have to reimplement the whole of the scrollbar - * handler! - * - * So instead we apply a bodge: set a static variable that - * indicates that we're _in_ that sub-loop, and if so, - * decide it's OK to manually call term_update() proper, - * bypassing the timer and cooldown and rate-limiting - * systems completely, whenever we see an SB_THUMBTRACK. - * This shouldn't cause a rate overload, because we're - * only doing it once per UI event! - * - * [1] Actually, there's an extra oddity where SC_HSCROLL - * and SC_VSCROLL have their documented values the wrong - * way round. Many people on the Internet have noticed - * this, e.g. https://stackoverflow.com/q/55528397 - */ - term_update(wgs->term); - } - break; - case WM_PALETTECHANGED: - if ((HWND) wParam != hwnd && wgs->pal != NULL) { - HDC hdc = make_hdc(wgs); - if (hdc) { - if (RealizePalette(hdc) > 0) - UpdateColors(hdc); - free_hdc(wgs, hdc); - } - } - break; - case WM_QUERYNEWPALETTE: - if (wgs->pal != NULL) { - HDC hdc = make_hdc(wgs); - if (hdc) { - if (RealizePalette(hdc) > 0) - UpdateColors(hdc); - free_hdc(wgs, hdc); - return true; - } - } - return false; - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - case WM_KEYUP: - case WM_SYSKEYUP: - /* - * Add the scan code and keypress timing to the random - * number noise. - */ - noise_ultralight(NOISE_SOURCE_KEY, lParam); - - /* - * We don't do TranslateMessage since it disassociates the - * resulting CHAR message from the KEYDOWN that sparked it, - * which we occasionally don't want. Instead, we process - * KEYDOWN, and call the Win32 translator functions so that - * we get the translations under _our_ control. - */ - { - unsigned char buf[20]; - int len; - - if (wParam == VK_PROCESSKEY || /* IME PROCESS key */ - wParam == VK_PACKET) { /* 'this key is a Unicode char' */ - if (message == WM_KEYDOWN) { - MSG m; - m.hwnd = hwnd; - m.message = WM_KEYDOWN; - m.wParam = wParam; - m.lParam = lParam & 0xdfff; - TranslateMessage(&m); - } else break; /* pass to Windows for default processing */ - } else { - len = TranslateKey(wgs, message, wParam, lParam, buf); - if (len == -1) - return sw_DefWindowProc(hwnd, message, wParam, lParam); - - if (len != 0) { - /* - * We need not bother about stdin backlogs - * here, because in GUI PuTTY we can't do - * anything about it anyway; there's no means - * of asking Windows to hold off on KEYDOWN - * messages. We _have_ to buffer everything - * we're sent. - */ - term_keyinput(wgs->term, -1, buf, len); - show_mouseptr(wgs, false); - } - } - } - return 0; - case WM_INPUTLANGCHANGE: - /* wParam == Font number */ - /* lParam == Locale */ - set_input_locale(wgs, (HKL)lParam); - sys_cursor_update(wgs); - break; - case WM_IME_STARTCOMPOSITION: { - HIMC hImc = ImmGetContext(hwnd); - ImmSetCompositionFont(hImc, &wgs->lfont); - ImmReleaseContext(hwnd, hImc); - break; - } - case WM_IME_COMPOSITION: { - HIMC hIMC; - int n; - char *buff; - - if (osPlatformId == VER_PLATFORM_WIN32_WINDOWS || - osPlatformId == VER_PLATFORM_WIN32s) - break; /* no Unicode */ - - if ((lParam & GCS_RESULTSTR) == 0) /* Composition unfinished. */ - break; /* fall back to DefWindowProc */ - - hIMC = ImmGetContext(hwnd); - n = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0); - - if (n > 0) { - int i; - buff = snewn(n, char); - ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, buff, n); - /* - * Jaeyoun Chung reports that Korean character - * input doesn't work correctly if we do a single - * term_keyinputw covering the whole of buff. So - * instead we send the characters one by one. - */ - /* don't divide SURROGATE PAIR */ - if (wgs->ldisc) { - for (i = 0; i < n; i += 2) { - WCHAR hs = *(unsigned short *)(buff+i); - if (IS_HIGH_SURROGATE(hs) && i+2 < n) { - WCHAR ls = *(unsigned short *)(buff+i+2); - if (IS_LOW_SURROGATE(ls)) { - term_keyinputw( - wgs->term, (unsigned short *)(buff+i), 2); - i += 2; - continue; - } - } - term_keyinputw( - wgs->term, (unsigned short *)(buff+i), 1); - } - } - free(buff); - } - ImmReleaseContext(hwnd, hIMC); - return 1; - } - - case WM_IME_CHAR: - if (wParam & 0xFF00) { - char buf[2]; - - buf[1] = wParam; - buf[0] = wParam >> 8; - term_keyinput(wgs->term, wgs->kbd_codepage, buf, 2); - } else { - char c = (unsigned char) wParam; - term_seen_key_event(wgs->term); - term_keyinput(wgs->term, wgs->kbd_codepage, &c, 1); - } - return (0); - case WM_CHAR: - case WM_SYSCHAR: - /* - * Nevertheless, we are prepared to deal with WM_CHAR - * messages, should they crop up. So if someone wants to - * post the things to us as part of a macro manoeuvre, - * we're ready to cope. - */ - if (unicode_window) { - wchar_t c = wParam; - - if (IS_HIGH_SURROGATE(c)) { - wgs->pending_surrogate = c; - } else if (IS_SURROGATE_PAIR(wgs->pending_surrogate, c)) { - wchar_t pair[2]; - pair[0] = wgs->pending_surrogate; - pair[1] = c; - term_keyinputw(wgs->term, pair, 2); - } else if (!IS_SURROGATE(c)) { - term_keyinputw(wgs->term, &c, 1); - } - } else { - char c = (unsigned char)wParam; - term_seen_key_event(wgs->term); - if (wgs->ldisc) - term_keyinput(wgs->term, CP_ACP, &c, 1); - } - return 0; - case WM_SYSCOLORCHANGE: - if (conf_get_bool(wgs->conf, CONF_system_colour)) { - /* Refresh palette from system colours. */ - term_notify_palette_changed(wgs->term); - init_palette(wgs); - /* Force a repaint of the terminal window. */ - term_invalidate(wgs->term); - } - break; - case WM_GOT_CLIPDATA: - process_clipdata(wgs, (HGLOBAL)lParam, wParam); - return 0; - default: - if (message == wm_mousewheel || message == WM_MOUSEWHEEL - || message == WM_MOUSEHWHEEL) { - bool shift_pressed = false, control_pressed = false; - - if (message == WM_MOUSEWHEEL || message == WM_MOUSEHWHEEL) { - wgs->wheel_accumulator += (short)HIWORD(wParam); - shift_pressed=LOWORD(wParam) & MK_SHIFT; - control_pressed=LOWORD(wParam) & MK_CONTROL; - } else { - BYTE keys[256]; - wgs->wheel_accumulator += (int)wParam; - if (GetKeyboardState(keys)!=0) { - shift_pressed=keys[VK_SHIFT]&0x80; - control_pressed=keys[VK_CONTROL]&0x80; - } - } - - /* process events when the threshold is reached */ - while (abs(wgs->wheel_accumulator) >= WHEEL_DELTA) { - int b; - - /* reduce amount for next time */ - if (wgs->wheel_accumulator > 0) { - b = message == WM_MOUSEHWHEEL ? MBT_WHEEL_RIGHT : MBT_WHEEL_UP; - wgs->wheel_accumulator -= WHEEL_DELTA; - } else if (wgs->wheel_accumulator < 0) { - b = message == WM_MOUSEHWHEEL ? MBT_WHEEL_LEFT : MBT_WHEEL_DOWN; - wgs->wheel_accumulator += WHEEL_DELTA; - } else - break; - - if (wgs->send_raw_mouse && - !(conf_get_bool(wgs->conf, CONF_mouse_override) && - shift_pressed)) { - /* Mouse wheel position is in screen coordinates for - * some reason */ - POINT p; - p.x = X_POS(lParam); p.y = Y_POS(lParam); - if (ScreenToClient(hwnd, &p)) { - /* send a mouse-down followed by a mouse up */ - term_mouse(wgs->term, b, translate_button(wgs, b), - MA_CLICK, - TO_CHR_X(p.x), - TO_CHR_Y(p.y), shift_pressed, - control_pressed, is_alt_pressed()); - } /* else: not sure when this can fail */ - } else if (message != WM_MOUSEHWHEEL) { - /* trigger a scroll */ - term_scroll(wgs->term, 0, - b == MBT_WHEEL_UP ? - -wgs->term->rows / 2 : wgs->term->rows / 2); - } - } - return 0; - } - } - - /* - * Any messages we don't process completely above are passed through to - * DefWindowProc() for default processing. - */ - return sw_DefWindowProc(hwnd, message, wParam, lParam); -} - -/* - * Move the system caret. (We maintain one, even though it's - * invisible, for the benefit of blind people: apparently some - * helper software tracks the system caret, so we should arrange to - * have one.) - */ -static void wintw_set_cursor_pos(TermWin *tw, int x, int y) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - int cx, cy; - - if (!wgs->term->has_focus) return; - - /* - * Avoid gratuitously re-updating the cursor position and IMM - * window if there's no actual change required. - */ - cx = x * wgs->font_width + wgs->offset_width; - cy = y * wgs->font_height + wgs->offset_height; - if (cx == wgs->caret_x && cy == wgs->caret_y) - return; - wgs->caret_x = cx; - wgs->caret_y = cy; - - sys_cursor_update(wgs); -} - -static void sys_cursor_update(WinGuiSeat *wgs) -{ - COMPOSITIONFORM cf; - HIMC hIMC; - - if (!wgs->term->has_focus) return; - - if (wgs->caret_x < 0 || wgs->caret_y < 0) - return; - - SetCaretPos(wgs->caret_x, wgs->caret_y); - - /* IMM calls on Win98 and beyond only */ - if (osPlatformId == VER_PLATFORM_WIN32s) return; /* 3.11 */ - - if (osPlatformId == VER_PLATFORM_WIN32_WINDOWS && - osMinorVersion == 0) return; /* 95 */ - - /* we should have the IMM functions */ - hIMC = ImmGetContext(wgs->term_hwnd); - cf.dwStyle = CFS_POINT; - cf.ptCurrentPos.x = wgs->caret_x; - cf.ptCurrentPos.y = wgs->caret_y; - ImmSetCompositionWindow(hIMC, &cf); - - ImmReleaseContext(wgs->term_hwnd, hIMC); -} - -static void draw_horizontal_line_on_text( - WinGuiSeat *wgs, int y, int lattr, RECT line_box, COLORREF colour) -{ - if (lattr == LATTR_TOP || lattr == LATTR_BOT) { - y *= 2; - if (lattr == LATTR_BOT) - y -= wgs->font_height; - } - - if (!(0 <= y && y < wgs->font_height)) - return; - - HPEN oldpen = SelectObject(wgs->wintw_hdc, CreatePen(PS_SOLID, 0, colour)); - MoveToEx(wgs->wintw_hdc, line_box.left, line_box.top + y, NULL); - LineTo(wgs->wintw_hdc, line_box.right, line_box.top + y); - oldpen = SelectObject(wgs->wintw_hdc, oldpen); - DeleteObject(oldpen); -} - -/* - * 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'. - */ -static void do_text_internal( - WinGuiSeat *wgs, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour truecolour) -{ - COLORREF fg, bg, t; - int nfg, nbg, nfont; - RECT line_box; - bool force_manual_underline = false; - int fnt_width, char_width; - int text_adjust = 0; - int xoffset = 0; - int maxlen, remaining; - bool opaque; - bool is_cursor = false; - int *lpDx = NULL; - size_t lpDx_len = 0; - bool use_lpDx; - wchar_t *wbuf = NULL; - char *cbuf = NULL; - size_t wbuflen = 0, cbuflen = 0; - int len2; /* for SURROGATE PAIR */ - - lattr &= LATTR_MODE; - - char_width = fnt_width = wgs->font_width * (1 + (lattr != LATTR_NORM)); - - if (attr & ATTR_WIDE) - char_width *= 2; - - /* Only want the left half of double width lines */ - if (lattr != LATTR_NORM && x*2 >= wgs->term->cols) - return; - - x *= fnt_width; - y *= wgs->font_height; - x += wgs->offset_width; - y += wgs->offset_height; - - if ((attr & TATTR_ACTCURS) && - (wgs->cursor_type == CURSOR_BLOCK || wgs->term->big_cursor)) { - truecolour.fg = truecolour.bg = optionalrgb_none; - attr &= ~(ATTR_REVERSE|ATTR_BLINK|ATTR_COLOURS|ATTR_DIM); - /* cursor fg and bg */ - attr |= (260 << ATTR_FGSHIFT) | (261 << ATTR_BGSHIFT); - is_cursor = true; - } - - nfont = 0; - if (wgs->vtmode == VT_POORMAN && lattr != LATTR_NORM) { - /* Assume a poorman font is borken in other ways too. */ - lattr = LATTR_WIDE; - } else - switch (lattr) { - case LATTR_NORM: - break; - case LATTR_WIDE: - nfont |= FONT_WIDE; - break; - default: - nfont |= FONT_WIDE + FONT_HIGH; - break; - } - if (attr & ATTR_NARROW) - nfont |= FONT_NARROW; - -#ifdef USES_VTLINE_HACK - /* Special hack for the VT100 linedraw glyphs. */ - if (text[0] >= 0x23BA && text[0] <= 0x23BD) { - switch ((unsigned char) (text[0])) { - case 0xBA: - text_adjust = -2 * wgs->font_height / 5; - break; - case 0xBB: - text_adjust = -1 * wgs->font_height / 5; - break; - case 0xBC: - text_adjust = wgs->font_height / 5; - break; - case 0xBD: - text_adjust = 2 * wgs->font_height / 5; - break; - } - if (lattr == LATTR_TOP || lattr == LATTR_BOT) - text_adjust *= 2; - text[0] = wgs->ucsdata.unitab_xterm['q']; - if (attr & ATTR_UNDER) { - attr &= ~ATTR_UNDER; - force_manual_underline = true; - } - } -#endif - - /* Anything left as an original character set is unprintable. */ - if (DIRECT_CHAR(text[0]) && - (len < 2 || !IS_SURROGATE_PAIR(text[0], text[1]))) { - int i; - for (i = 0; i < len; i++) - text[i] = 0xFFFD; - } - - /* OEM CP */ - if ((text[0] & CSET_MASK) == CSET_OEMCP) - nfont |= FONT_OEM; - - nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT); - nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT); - if (wgs->bold_font_mode == BOLD_FONT && (attr & ATTR_BOLD)) - nfont |= FONT_BOLD; - if (wgs->und_mode == UND_FONT && (attr & ATTR_UNDER)) - nfont |= FONT_UNDERLINE; - another_font(wgs, nfont); - if (!wgs->fonts[nfont]) { - if (nfont & FONT_UNDERLINE) - force_manual_underline = true; - /* Don't do the same for manual bold, it could be bad news. */ - - nfont &= ~(FONT_BOLD | FONT_UNDERLINE); - } - another_font(wgs, nfont); - if (!wgs->fonts[nfont]) - nfont = FONT_NORMAL; - if (attr & ATTR_REVERSE) { - struct optionalrgb trgb; - - t = nfg; - nfg = nbg; - nbg = t; - - trgb = truecolour.fg; - truecolour.fg = truecolour.bg; - truecolour.bg = trgb; - } - if (wgs->bold_colours && (attr & ATTR_BOLD) && !is_cursor) { - if (nfg < 16) nfg |= 8; - else if (nfg >= 256) nfg |= 1; - } - if (wgs->bold_colours && (attr & ATTR_BLINK)) { - if (nbg < 16) nbg |= 8; - else if (nbg >= 256) nbg |= 1; - } - if (!wgs->pal && truecolour.fg.enabled) - fg = RGB(truecolour.fg.r, truecolour.fg.g, truecolour.fg.b); - else - fg = wgs->colours[nfg]; - - if (!wgs->pal && truecolour.bg.enabled) - bg = RGB(truecolour.bg.r, truecolour.bg.g, truecolour.bg.b); - else - bg = wgs->colours[nbg]; - - if (!wgs->pal && (attr & ATTR_DIM)) { - fg = RGB(GetRValue(fg) * 2 / 3, - GetGValue(fg) * 2 / 3, - GetBValue(fg) * 2 / 3); - } - - SelectObject(wgs->wintw_hdc, wgs->fonts[nfont]); - SetTextColor(wgs->wintw_hdc, fg); - SetBkColor(wgs->wintw_hdc, bg); - if (attr & TATTR_COMBINING) - SetBkMode(wgs->wintw_hdc, TRANSPARENT); - else - SetBkMode(wgs->wintw_hdc, OPAQUE); - line_box.left = x; - line_box.top = y; - line_box.right = x + char_width * len; - line_box.bottom = y + wgs->font_height; - /* adjust line_box.right for SURROGATE PAIR & VARIATION SELECTOR */ - { - int i; - int rc_width = 0; - for (i = 0; i < len ; i++) { - if (i+1 < len && IS_HIGH_VARSEL(text[i], text[i+1])) { - i++; - } else if (i+1 < len && IS_SURROGATE_PAIR(text[i], text[i+1])) { - rc_width += char_width; - i++; - } else if (IS_LOW_VARSEL(text[i])) { - /* do nothing */ - } else { - rc_width += char_width; - } - } - line_box.right = line_box.left + rc_width; - } - - /* Only want the left half of double width lines */ - if (line_box.right > wgs->font_width*wgs->term->cols+wgs->offset_width) - line_box.right = wgs->font_width*wgs->term->cols+wgs->offset_width; - - if (wgs->font_varpitch) { - /* - * If we're using a variable-pitch font, we unconditionally - * draw the glyphs one at a time and centre them in their - * character cells (which means in particular that we must - * disable the lpDx mechanism). This gives slightly odd but - * generally reasonable results. - */ - xoffset = char_width / 2; - SetTextAlign(wgs->wintw_hdc, TA_TOP | TA_CENTER | TA_NOUPDATECP); - use_lpDx = false; - maxlen = 1; - } else { - /* - * In a fixed-pitch font, we draw the whole string in one go - * in the normal way. - */ - xoffset = 0; - SetTextAlign(wgs->wintw_hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP); - use_lpDx = true; - maxlen = len; - } - - opaque = true; /* start by erasing the rectangle */ - for (remaining = len; remaining > 0; - text += len, remaining -= len, x += char_width * len2) { - len = (maxlen < remaining ? maxlen : remaining); - /* don't divide SURROGATE PAIR and VARIATION SELECTOR */ - len2 = len; - if (maxlen == 1) { - if (remaining >= 1 && IS_SURROGATE_PAIR(text[0], text[1])) - len++; - if (remaining-len >= 1 && IS_LOW_VARSEL(text[len])) - len++; - else if (remaining-len >= 2 && - IS_HIGH_VARSEL(text[len], text[len+1])) - len += 2; - } - - if (len > lpDx_len) - sgrowarray(lpDx, lpDx_len, len); - - { - int i; - /* only last char has dx width in SURROGATE PAIR and - * VARIATION sequence */ - for (i = 0; i < len; i++) { - lpDx[i] = char_width; - if (i+1 < len && IS_HIGH_VARSEL(text[i], text[i+1])) { - if (i > 0) lpDx[i-1] = 0; - lpDx[i] = 0; - i++; - lpDx[i] = char_width; - } else if (i+1 < len && IS_SURROGATE_PAIR(text[i],text[i+1])) { - lpDx[i] = 0; - i++; - lpDx[i] = char_width; - } else if (IS_LOW_VARSEL(text[i])) { - if (i > 0) lpDx[i-1] = 0; - lpDx[i] = char_width; - } - } - } - - /* We're using a private area for direct to font. (512 chars.) */ - if (wgs->ucsdata.dbcs_screenfont && - (text[0] & CSET_MASK) == CSET_ACP) { - /* Ho Hum, dbcs fonts are a PITA! */ - /* To display on W9x I have to convert to UCS */ - int nlen, mptr; - - sgrowarray(wbuf, wbuflen, len); - for (nlen = mptr = 0; mptrucsdata.font_codepage, - (BYTE) text[mptr])) { - char dbcstext[2]; - dbcstext[0] = text[mptr] & 0xFF; - dbcstext[1] = text[mptr+1] & 0xFF; - lpDx[nlen] += char_width; - MultiByteToWideChar( - wgs->ucsdata.font_codepage, MB_USEGLYPHCHARS, - dbcstext, 2, wbuf+nlen, 1); - mptr++; - } else { - char dbcstext[1]; - dbcstext[0] = text[mptr] & 0xFF; - MultiByteToWideChar( - wgs->ucsdata.font_codepage, MB_USEGLYPHCHARS, - dbcstext, 1, wbuf+nlen, 1); - } - nlen++; - } - if (nlen <= 0) - goto out; /* Eeek! */ - - ExtTextOutW( - wgs->wintw_hdc, x + xoffset, - y - wgs->font_height * (lattr == LATTR_BOT) + text_adjust, - ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), - &line_box, wbuf, nlen, (use_lpDx ? lpDx : NULL)); - if (wgs->bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { - SetBkMode(wgs->wintw_hdc, TRANSPARENT); - ExtTextOutW( - wgs->wintw_hdc, x + xoffset - 1, - y - wgs->font_height * (lattr == LATTR_BOT) + text_adjust, - ETO_CLIPPED, &line_box, wbuf, nlen, - (use_lpDx ? lpDx : NULL)); - } - - lpDx[0] = -1; - } else if (DIRECT_FONT(text[0])) { - sgrowarray(cbuf, cbuflen, len); - for (size_t i = 0; i < len; i++) - cbuf[i] = text[i] & 0xFF; - - ExtTextOut( - wgs->wintw_hdc, x + xoffset, - y - wgs->font_height * (lattr == LATTR_BOT) + text_adjust, - ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), - &line_box, cbuf, len, (use_lpDx ? lpDx : NULL)); - if (wgs->bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { - SetBkMode(wgs->wintw_hdc, TRANSPARENT); - - /* GRR: This draws the character outside its box and - * can leave 'droppings' even with the clip box! I - * suppose I could loop it one character at a time ... - * yuk. - * - * Or ... I could do a test print with "W", and use +1 - * or -1 for this shift depending on if the leftmost - * column is blank... - */ - ExtTextOut( - wgs->wintw_hdc, x + xoffset - 1, - y - wgs->font_height * (lattr == LATTR_BOT) + text_adjust, - ETO_CLIPPED, &line_box, cbuf, len, - (use_lpDx ? lpDx : NULL)); - } - } else { - /* And 'normal' unicode characters */ - sgrowarray(wbuf, wbuflen, len); - for (int i = 0; i < len; i++) - wbuf[i] = text[i]; - - /* print Glyphs as they are, without Windows' Shaping*/ - general_textout( - wgs, wgs->wintw_hdc, x + xoffset, - y - wgs->font_height * (lattr==LATTR_BOT) + text_adjust, - &line_box, wbuf, len, lpDx, - opaque && !(attr & TATTR_COMBINING)); - - /* And the shadow bold hack. */ - if (wgs->bold_font_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) { - SetBkMode(wgs->wintw_hdc, TRANSPARENT); - ExtTextOutW( - wgs->wintw_hdc, x + xoffset - 1, - y - wgs->font_height * (lattr == LATTR_BOT) + text_adjust, - ETO_CLIPPED, &line_box, wbuf, len, - (use_lpDx ? lpDx : NULL)); - } - } - - /* - * If we're looping round again, stop erasing the background - * rectangle. - */ - SetBkMode(wgs->wintw_hdc, TRANSPARENT); - opaque = false; - } - - if (lattr != LATTR_TOP && - (force_manual_underline || (wgs->und_mode == UND_LINE && - (attr & ATTR_UNDER)))) - draw_horizontal_line_on_text(wgs, wgs->descent, lattr, line_box, fg); - - if (attr & ATTR_STRIKE) - draw_horizontal_line_on_text(wgs, wgs->font_strikethrough_y, lattr, - line_box, fg); - - out: - sfree(lpDx); - sfree(wbuf); - sfree(cbuf); -} - -/* - * Wrapper that handles combining characters. - */ -static void wintw_draw_text( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour truecolour) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - if (attr & TATTR_COMBINING) { - unsigned long a = 0; - int len0 = 1; - /* don't divide SURROGATE PAIR and VARIATION SELECTOR */ - if (len >= 2 && IS_SURROGATE_PAIR(text[0], text[1])) - len0 = 2; - if (len-len0 >= 1 && IS_LOW_VARSEL(text[len0])) { - attr &= ~TATTR_COMBINING; - do_text_internal(wgs, x, y, text, len0+1, attr, lattr, truecolour); - text += len0+1; - len -= len0+1; - a = TATTR_COMBINING; - } else if (len-len0 >= 2 && IS_HIGH_VARSEL(text[len0], text[len0+1])) { - attr &= ~TATTR_COMBINING; - do_text_internal(wgs, x, y, text, len0+2, attr, lattr, truecolour); - text += len0+2; - len -= len0+2; - a = TATTR_COMBINING; - } else { - attr &= ~TATTR_COMBINING; - } - - while (len--) { - if (len >= 1 && IS_SURROGATE_PAIR(text[0], text[1])) { - do_text_internal(wgs, x, y, text, 2, attr | a, lattr, - truecolour); - len--; - text++; - } else - do_text_internal(wgs, x, y, text, 1, attr | a, lattr, - truecolour); - - text++; - a = TATTR_COMBINING; - } - } else - do_text_internal(wgs, x, y, text, len, attr, lattr, truecolour); -} - -static void wintw_draw_cursor( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour truecolour) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - int fnt_width; - int char_width; - int ctype = wgs->cursor_type; - - lattr &= LATTR_MODE; - - if ((attr & TATTR_ACTCURS) && - (ctype == CURSOR_BLOCK || wgs->term->big_cursor)) { - if (*text != UCSWIDE) { - win_draw_text(tw, x, y, text, len, attr, lattr, truecolour); - return; - } - ctype = CURSOR_VERTICAL_LINE; - attr |= TATTR_RIGHTCURS; - } - - fnt_width = char_width = wgs->font_width * (1 + (lattr != LATTR_NORM)); - if (attr & ATTR_WIDE) - char_width *= 2; - x *= fnt_width; - y *= wgs->font_height; - x += wgs->offset_width; - y += wgs->offset_height; - - if ((attr & TATTR_PASCURS) && - (ctype == CURSOR_BLOCK || wgs->term->big_cursor)) { - POINT pts[5]; - HPEN oldpen; - pts[0].x = pts[1].x = pts[4].x = x; - pts[2].x = pts[3].x = x + char_width - 1; - pts[0].y = pts[3].y = pts[4].y = y; - pts[1].y = pts[2].y = y + wgs->font_height - 1; - oldpen = SelectObject(wgs->wintw_hdc, - CreatePen(PS_SOLID, 0, wgs->colours[261])); - Polyline(wgs->wintw_hdc, pts, 5); - oldpen = SelectObject(wgs->wintw_hdc, oldpen); - DeleteObject(oldpen); - } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && - ctype != CURSOR_BLOCK) { - int startx, starty, dx, dy, length, i; - if (ctype == CURSOR_UNDERLINE) { - startx = x; - starty = y + wgs->descent; - dx = 1; - dy = 0; - length = char_width; - } else /* ctype == CURSOR_VERTICAL_LINE */ { - int xadjust = 0; - if (attr & TATTR_RIGHTCURS) - xadjust = char_width - 1; - startx = x + xadjust; - starty = y; - dx = 0; - dy = 1; - length = wgs->font_height; - } - if (attr & TATTR_ACTCURS) { - HPEN oldpen; - oldpen = - SelectObject(wgs->wintw_hdc, - CreatePen(PS_SOLID, 0, wgs->colours[261])); - MoveToEx(wgs->wintw_hdc, startx, starty, NULL); - LineTo(wgs->wintw_hdc, startx + dx * length, starty + dy * length); - oldpen = SelectObject(wgs->wintw_hdc, oldpen); - DeleteObject(oldpen); - } else { - for (i = 0; i < length; i++) { - if (i % 2 == 0) { - SetPixel(wgs->wintw_hdc, startx, starty, - wgs->colours[261]); - } - startx += dx; - starty += dy; - } - } - } -} - -static void wintw_draw_trust_sigil(TermWin *tw, int x, int y) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - - x *= wgs->font_width; - y *= wgs->font_height; - x += wgs->offset_width; - y += wgs->offset_height; - - DrawIconEx(wgs->wintw_hdc, x, y, trust_icon, - wgs->font_width * 2, wgs->font_height, 0, NULL, DI_NORMAL); -} - -/* This function gets the actual width of a character in the normal font. - */ -static int wintw_char_width(TermWin *tw, int uc) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - int ibuf = 0; - - /* If the font max is the same as the font ave width then this - * function is a no-op. - */ - if (!wgs->font_dualwidth) return 1; - - switch (uc & CSET_MASK) { - case CSET_ASCII: - uc = wgs->ucsdata.unitab_line[uc & 0xFF]; - break; - case CSET_LINEDRW: - uc = wgs->ucsdata.unitab_xterm[uc & 0xFF]; - break; - case CSET_SCOACS: - uc = wgs->ucsdata.unitab_scoacs[uc & 0xFF]; - break; - } - if (DIRECT_FONT(uc)) { - if (wgs->ucsdata.dbcs_screenfont) return 1; - - /* Speedup, I know of no font where ascii is the wrong width */ - if ((uc&~CSET_MASK) >= ' ' && (uc&~CSET_MASK)<= '~') - return 1; - - if ( (uc & CSET_MASK) == CSET_ACP ) { - SelectObject(wgs->wintw_hdc, wgs->fonts[FONT_NORMAL]); - } else if ( (uc & CSET_MASK) == CSET_OEMCP ) { - another_font(wgs, FONT_OEM); - if (!wgs->fonts[FONT_OEM]) return 0; - - SelectObject(wgs->wintw_hdc, wgs->fonts[FONT_OEM]); - } else - return 0; - - if (GetCharWidth32(wgs->wintw_hdc, uc & ~CSET_MASK, - uc & ~CSET_MASK, &ibuf) != 1 && - GetCharWidth(wgs->wintw_hdc, uc & ~CSET_MASK, - uc & ~CSET_MASK, &ibuf) != 1) - return 0; - } else { - /* Speedup, I know of no font where ascii is the wrong width */ - if (uc >= ' ' && uc <= '~') return 1; - - SelectObject(wgs->wintw_hdc, wgs->fonts[FONT_NORMAL]); - if (GetCharWidth32W(wgs->wintw_hdc, uc, uc, &ibuf) == 1) - /* Okay that one worked */ ; - else if (GetCharWidthW(wgs->wintw_hdc, uc, uc, &ibuf) == 1) - /* This should work on 9x too, but it's "less accurate" */ ; - else - return 0; - } - - ibuf += wgs->font_width / 2 -1; - ibuf /= wgs->font_width; - - return ibuf; -} - -DECL_WINDOWS_FUNCTION(static, BOOL, FlashWindowEx, (PFLASHWINFO)); -DECL_WINDOWS_FUNCTION(static, BOOL, ToUnicodeEx, - (UINT, UINT, const BYTE *, LPWSTR, int, UINT, HKL)); -DECL_WINDOWS_FUNCTION(static, BOOL, PlaySoundW, (LPCWSTR, HMODULE, DWORD)); -DECL_WINDOWS_FUNCTION(static, BOOL, PlaySoundA, (LPCSTR, HMODULE, DWORD)); - -static void init_winfuncs(void) -{ - HMODULE user32_module = load_system32_dll("user32.dll"); - HMODULE winmm_module = load_system32_dll("winmm.dll"); - HMODULE shcore_module = load_system32_dll("shcore.dll"); - GET_WINDOWS_FUNCTION(user32_module, FlashWindowEx); - GET_WINDOWS_FUNCTION(user32_module, ToUnicodeEx); - GET_WINDOWS_FUNCTION(winmm_module, PlaySoundW); - GET_WINDOWS_FUNCTION(winmm_module, PlaySoundA); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, GetMonitorInfoA); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, MonitorFromPoint); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, MonitorFromWindow); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(shcore_module, GetDpiForMonitor); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, GetSystemMetricsForDpi); - GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, AdjustWindowRectExForDpi); -} - -/* - * Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII - * codes. Returns number of bytes used, zero to drop the message, - * -1 to forward the message to Windows, or another negative number - * to indicate a NUL-terminated "special" string. - */ -static int TranslateKey(WinGuiSeat *wgs, UINT message, WPARAM wParam, - LPARAM lParam, unsigned char *output) -{ - BYTE keystate[256]; - int scan, shift_state; - bool left_alt = false, key_down; - int r, i; - unsigned char *p = output; - int funky_type = conf_get_int(wgs->conf, CONF_funky_type); - bool no_applic_k = conf_get_bool(wgs->conf, CONF_no_applic_k); - bool ctrlaltkeys = conf_get_bool(wgs->conf, CONF_ctrlaltkeys); - bool nethack_keypad = conf_get_bool(wgs->conf, CONF_nethack_keypad); - char keypad_key = '\0'; - - HKL kbd_layout = GetKeyboardLayout(0); - - r = GetKeyboardState(keystate); - if (!r) - memset(keystate, 0, sizeof(keystate)); - else { -#if 0 -#define SHOW_TOASCII_RESULT - { /* Tell us all about key events */ - static BYTE oldstate[256]; - static int first = 1; - static int scan; - int ch; - if (first) - memcpy(oldstate, keystate, sizeof(oldstate)); - first = 0; - - if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) { - debug("+"); - } else if ((HIWORD(lParam) & KF_UP) - && scan == (HIWORD(lParam) & 0xFF)) { - debug(". U"); - } else { - debug(".\n"); - if (wParam >= VK_F1 && wParam <= VK_F20) - debug("K_F%d", wParam + 1 - VK_F1); - else - switch (wParam) { - case VK_SHIFT: - debug("SHIFT"); - break; - case VK_CONTROL: - debug("CTRL"); - break; - case VK_MENU: - debug("ALT"); - break; - default: - debug("VK_%02x", wParam); - } - if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP) - debug("*"); - debug(", S%02x", scan = (HIWORD(lParam) & 0xFF)); - - ch = MapVirtualKeyEx(wParam, 2, kbd_layout); - if (ch >= ' ' && ch <= '~') - debug(", '%c'", ch); - else if (ch) - debug(", $%02x", ch); - - if ((keystate[VK_SHIFT] & 0x80) != 0) - debug(", S"); - if ((keystate[VK_CONTROL] & 0x80) != 0) - debug(", C"); - if ((HIWORD(lParam) & KF_EXTENDED)) - debug(", E"); - if ((HIWORD(lParam) & KF_UP)) - debug(", U"); - } - - if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT); - else if ((HIWORD(lParam) & KF_UP)) - oldstate[wParam & 0xFF] ^= 0x80; - else - oldstate[wParam & 0xFF] ^= 0x81; - - for (ch = 0; ch < 256; ch++) - if (oldstate[ch] != keystate[ch]) - debug(", M%02x=%02x", ch, keystate[ch]); - - memcpy(oldstate, keystate, sizeof(oldstate)); - } -#endif - - if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) { - keystate[VK_RMENU] = keystate[VK_MENU]; - } - - - /* Nastiness with NUMLock - Shift-NUMLock is left alone though */ - if ((funky_type == FUNKY_VT400 || - (funky_type <= FUNKY_LINUX && wgs->term->app_keypad_keys && - !no_applic_k)) - && wParam == VK_NUMLOCK && !(keystate[VK_SHIFT] & 0x80)) { - - wParam = VK_EXECUTE; - - /* UnToggle NUMLock */ - if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) - keystate[VK_NUMLOCK] ^= 1; - } - - /* And write back the 'adjusted' state */ - SetKeyboardState(keystate); - } - - /* Disable Auto repeat if required */ - if (wgs->term->repeat_off && - (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == KF_REPEAT) - return 0; - - if ((HIWORD(lParam) & KF_ALTDOWN) && (keystate[VK_RMENU] & 0x80) == 0) - left_alt = true; - - key_down = ((HIWORD(lParam) & KF_UP) == 0); - - /* Make sure Ctrl-ALT is not the same as AltGr for ToAscii unless told. */ - if (left_alt && (keystate[VK_CONTROL] & 0x80)) { - if (ctrlaltkeys) - keystate[VK_MENU] = 0; - else { - keystate[VK_RMENU] = 0x80; - left_alt = false; - } - } - - scan = (HIWORD(lParam) & (KF_UP | KF_EXTENDED | 0xFF)); - shift_state = ((keystate[VK_SHIFT] & 0x80) != 0) - + ((keystate[VK_CONTROL] & 0x80) != 0) * 2; - - /* Note if AltGr was pressed and if it was used as a compose key */ - if (!wgs->compose_state) { - wgs->compose_keycode = 0x100; - if (conf_get_bool(wgs->conf, CONF_compose_key)) { - if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) - wgs->compose_keycode = wParam; - } - if (wParam == VK_APPS) - wgs->compose_keycode = wParam; - } - - if (wParam == wgs->compose_keycode) { - if (wgs->compose_state == 0 && - (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) - wgs->compose_state = 1; - else if (wgs->compose_state == 1 && (HIWORD(lParam) & KF_UP)) - wgs->compose_state = 2; - else - wgs->compose_state = 0; - } else if (wgs->compose_state == 1 && wParam != VK_CONTROL) - wgs->compose_state = 0; - - if (wgs->compose_state > 1 && left_alt) - wgs->compose_state = 0; - - /* Sanitize the number pad if not using a PC NumPad */ - if (left_alt || (wgs->term->app_keypad_keys && !no_applic_k - && funky_type != FUNKY_XTERM) || - funky_type == FUNKY_VT400 || nethack_keypad || wgs->compose_state) { - if ((HIWORD(lParam) & KF_EXTENDED) == 0) { - int nParam = 0; - switch (wParam) { - case VK_INSERT: - nParam = VK_NUMPAD0; - break; - case VK_END: - nParam = VK_NUMPAD1; - break; - case VK_DOWN: - nParam = VK_NUMPAD2; - break; - case VK_NEXT: - nParam = VK_NUMPAD3; - break; - case VK_LEFT: - nParam = VK_NUMPAD4; - break; - case VK_CLEAR: - nParam = VK_NUMPAD5; - break; - case VK_RIGHT: - nParam = VK_NUMPAD6; - break; - case VK_HOME: - nParam = VK_NUMPAD7; - break; - case VK_UP: - nParam = VK_NUMPAD8; - break; - case VK_PRIOR: - nParam = VK_NUMPAD9; - break; - case VK_DELETE: - nParam = VK_DECIMAL; - break; - } - if (nParam) { - if (keystate[VK_NUMLOCK] & 1) - shift_state |= 1; - wParam = nParam; - } - } - } - - /* If a key is pressed and AltGr is not active */ - if (key_down && (keystate[VK_RMENU] & 0x80) == 0 && !wgs->compose_state) { - /* Okay, prepare for most alts then ... */ - if (left_alt) - *p++ = '\033'; - - /* Lets see if it's a pattern we know all about ... */ - if (wParam == VK_PRIOR && shift_state == 1) { - SendMessage(wgs->term_hwnd, WM_VSCROLL, SB_PAGEUP, 0); - return 0; - } - if (wParam == VK_PRIOR && shift_state == 3) { /* ctrl-shift-pageup */ - SendMessage(wgs->term_hwnd, WM_VSCROLL, SB_TOP, 0); - return 0; - } - if (wParam == VK_NEXT && shift_state == 3) { /* ctrl-shift-pagedown */ - SendMessage(wgs->term_hwnd, WM_VSCROLL, SB_BOTTOM, 0); - return 0; - } - - if (wParam == VK_PRIOR && shift_state == 2) { - SendMessage(wgs->term_hwnd, WM_VSCROLL, SB_LINEUP, 0); - return 0; - } - if (wParam == VK_NEXT && shift_state == 1) { - SendMessage(wgs->term_hwnd, WM_VSCROLL, SB_PAGEDOWN, 0); - return 0; - } - if (wParam == VK_NEXT && shift_state == 2) { - SendMessage(wgs->term_hwnd, WM_VSCROLL, SB_LINEDOWN, 0); - return 0; - } - if ((wParam == VK_PRIOR || wParam == VK_NEXT) && shift_state == 3) { - term_scroll_to_selection(wgs->term, (wParam == VK_PRIOR ? 0 : 1)); - return 0; - } - if (wParam == VK_INSERT && shift_state == 2) { - switch (conf_get_int(wgs->conf, CONF_ctrlshiftins)) { - case CLIPUI_IMPLICIT: - break; /* no need to re-copy to CLIP_LOCAL */ - case CLIPUI_EXPLICIT: - term_request_copy(wgs->term, clips_system, - lenof(clips_system)); - break; - default: - break; - } - return 0; - } - if (wParam == VK_INSERT && shift_state == 1) { - switch (conf_get_int(wgs->conf, CONF_ctrlshiftins)) { - case CLIPUI_IMPLICIT: - term_request_paste(wgs->term, CLIP_LOCAL); - break; - case CLIPUI_EXPLICIT: - term_request_paste(wgs->term, CLIP_SYSTEM); - break; - default: - break; - } - return 0; - } - if (wParam == 'C' && shift_state == 3) { - switch (conf_get_int(wgs->conf, CONF_ctrlshiftcv)) { - case CLIPUI_IMPLICIT: - break; /* no need to re-copy to CLIP_LOCAL */ - case CLIPUI_EXPLICIT: - term_request_copy(wgs->term, clips_system, - lenof(clips_system)); - break; - default: - break; - } - return 0; - } - if (wParam == 'V' && shift_state == 3) { - switch (conf_get_int(wgs->conf, CONF_ctrlshiftcv)) { - case CLIPUI_IMPLICIT: - term_request_paste(wgs->term, CLIP_LOCAL); - break; - case CLIPUI_EXPLICIT: - term_request_paste(wgs->term, CLIP_SYSTEM); - break; - default: - break; - } - return 0; - } - if (left_alt && wParam == VK_F4 && - conf_get_bool(wgs->conf, CONF_alt_f4)) { - return -1; - } - if (left_alt && wParam == VK_SPACE && - conf_get_bool(wgs->conf, CONF_alt_space)) { - SendMessage(wgs->term_hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0); - return -1; - } - if (left_alt && wParam == VK_RETURN && - conf_get_bool(wgs->conf, CONF_fullscreenonaltenter) && - (conf_get_int(wgs->conf, CONF_resize_action) != RESIZE_DISABLED)) { - if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT) - flip_full_screen(wgs); - return -1; - } - /* Control-Numlock for app-keypad mode switch */ - if (wParam == VK_PAUSE && shift_state == 2) { - wgs->term->app_keypad_keys = !wgs->term->app_keypad_keys; - return 0; - } - - if (wParam == VK_BACK && shift_state == 0) { /* Backspace */ - *p++ = (conf_get_bool(wgs->conf, CONF_bksp_is_delete) ? - 0x7F : 0x08); - *p++ = 0; - return -2; - } - if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */ - /* We do the opposite of what is configured */ - *p++ = (conf_get_bool(wgs->conf, CONF_bksp_is_delete) ? - 0x08 : 0x7F); - *p++ = 0; - return -2; - } - if (wParam == VK_TAB && shift_state == 1) { /* Shift tab */ - *p++ = 0x1B; - *p++ = '['; - *p++ = 'Z'; - return p - output; - } - if (wParam == VK_SPACE && shift_state == 2) { /* Ctrl-Space */ - *p++ = 0; - return p - output; - } - if (wParam == VK_SPACE && shift_state == 3) { /* Ctrl-Shift-Space */ - *p++ = 160; - return p - output; - } - if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */ - if (wgs->backend) - backend_special(wgs->backend, SS_BRK, 0); - return 0; - } - if (wParam == VK_PAUSE) { /* Break/Pause */ - *p++ = 26; - *p++ = 0; - return -2; - } - /* Control-2 to Control-8 are special */ - if (shift_state == 2 && wParam >= '2' && wParam <= '8') { - *p++ = "\000\033\034\035\036\037\177"[wParam - '2']; - return p - output; - } - if (shift_state == 2 && (wParam == 0xBD || wParam == 0xBF)) { - *p++ = 0x1F; - return p - output; - } - if (shift_state == 2 && (wParam == 0xDF || wParam == 0xDC)) { - *p++ = 0x1C; - return p - output; - } - if (shift_state == 3 && wParam == 0xDE) { - *p++ = 0x1E; /* Ctrl-~ == Ctrl-^ in xterm at least */ - return p - output; - } - - switch (wParam) { - bool consumed_alt; - - case VK_NUMPAD0: keypad_key = '0'; goto numeric_keypad; - case VK_NUMPAD1: keypad_key = '1'; goto numeric_keypad; - case VK_NUMPAD2: keypad_key = '2'; goto numeric_keypad; - case VK_NUMPAD3: keypad_key = '3'; goto numeric_keypad; - case VK_NUMPAD4: keypad_key = '4'; goto numeric_keypad; - case VK_NUMPAD5: keypad_key = '5'; goto numeric_keypad; - case VK_NUMPAD6: keypad_key = '6'; goto numeric_keypad; - case VK_NUMPAD7: keypad_key = '7'; goto numeric_keypad; - case VK_NUMPAD8: keypad_key = '8'; goto numeric_keypad; - case VK_NUMPAD9: keypad_key = '9'; goto numeric_keypad; - case VK_DECIMAL: keypad_key = '.'; goto numeric_keypad; - case VK_ADD: keypad_key = '+'; goto numeric_keypad; - case VK_SUBTRACT: keypad_key = '-'; goto numeric_keypad; - case VK_MULTIPLY: keypad_key = '*'; goto numeric_keypad; - case VK_DIVIDE: keypad_key = '/'; goto numeric_keypad; - case VK_EXECUTE: keypad_key = 'G'; goto numeric_keypad; - /* also the case for VK_RETURN below can sometimes come here */ - numeric_keypad: - /* Left Alt overrides all numeric keypad usage to act as - * numeric character code input */ - if (left_alt) { - if (keypad_key >= '0' && keypad_key <= '9') - wgs->alt_numberpad_accumulator = - wgs->alt_numberpad_accumulator * 10 + keypad_key - '0'; - else - wgs->alt_numberpad_accumulator = 0; - break; - } - - { - int nchars = format_numeric_keypad_key( - (char *)p, wgs->term, keypad_key, - shift_state & 1, shift_state & 2); - if (!nchars) { - /* - * If we didn't get an escape sequence out of the - * numeric keypad key, then that must be because - * we're in Num Lock mode without application - * keypad enabled. In that situation we leave this - * keypress to the ToUnicode/ToAsciiEx handler - * below, which will translate it according to the - * appropriate keypad layout (e.g. so that what a - * Brit thinks of as keypad '.' can become ',' in - * the German layout). - * - * An exception is the keypad Return key: if we - * didn't get an escape sequence for that, we - * treat it like ordinary Return, taking into - * account Telnet special new line codes and - * config options. - */ - if (keypad_key == '\r') - goto ordinary_return_key; - break; - } - - p += nchars; - return p - output; - } - - int fkey_number; - case VK_F1: fkey_number = 1; goto numbered_function_key; - case VK_F2: fkey_number = 2; goto numbered_function_key; - case VK_F3: fkey_number = 3; goto numbered_function_key; - case VK_F4: fkey_number = 4; goto numbered_function_key; - case VK_F5: fkey_number = 5; goto numbered_function_key; - case VK_F6: fkey_number = 6; goto numbered_function_key; - case VK_F7: fkey_number = 7; goto numbered_function_key; - case VK_F8: fkey_number = 8; goto numbered_function_key; - case VK_F9: fkey_number = 9; goto numbered_function_key; - case VK_F10: fkey_number = 10; goto numbered_function_key; - case VK_F11: fkey_number = 11; goto numbered_function_key; - case VK_F12: fkey_number = 12; goto numbered_function_key; - case VK_F13: fkey_number = 13; goto numbered_function_key; - case VK_F14: fkey_number = 14; goto numbered_function_key; - case VK_F15: fkey_number = 15; goto numbered_function_key; - case VK_F16: fkey_number = 16; goto numbered_function_key; - case VK_F17: fkey_number = 17; goto numbered_function_key; - case VK_F18: fkey_number = 18; goto numbered_function_key; - case VK_F19: fkey_number = 19; goto numbered_function_key; - case VK_F20: fkey_number = 20; goto numbered_function_key; - numbered_function_key: - consumed_alt = false; - p += format_function_key((char *)p, wgs->term, fkey_number, - shift_state & 1, shift_state & 2, - left_alt, &consumed_alt); - if (consumed_alt) - left_alt = false; /* supersedes the usual prefixing of Esc */ - return p - output; - - SmallKeypadKey sk_key; - case VK_HOME: sk_key = SKK_HOME; goto small_keypad_key; - case VK_END: sk_key = SKK_END; goto small_keypad_key; - case VK_INSERT: sk_key = SKK_INSERT; goto small_keypad_key; - case VK_DELETE: sk_key = SKK_DELETE; goto small_keypad_key; - case VK_PRIOR: sk_key = SKK_PGUP; goto small_keypad_key; - case VK_NEXT: sk_key = SKK_PGDN; goto small_keypad_key; - small_keypad_key: - /* These keys don't generate terminal input with Ctrl */ - if (shift_state & 2) - break; - - p += format_small_keypad_key((char *)p, wgs->term, sk_key, - shift_state & 1, shift_state & 2, - left_alt, &consumed_alt); - if (consumed_alt) - left_alt = false; /* supersedes the usual prefixing of Esc */ - return p - output; - - char xkey; - case VK_UP: xkey = 'A'; goto arrow_key; - case VK_DOWN: xkey = 'B'; goto arrow_key; - case VK_RIGHT: xkey = 'C'; goto arrow_key; - case VK_LEFT: xkey = 'D'; goto arrow_key; - case VK_CLEAR: xkey = 'G'; goto arrow_key; /* close enough */ - arrow_key: - consumed_alt = false; - p += format_arrow_key((char *)p, wgs->term, xkey, shift_state & 1, - shift_state & 2, left_alt, &consumed_alt); - if (consumed_alt) - left_alt = false; /* supersedes the usual prefixing of Esc */ - return p - output; - - case VK_RETURN: - if (HIWORD(lParam) & KF_EXTENDED) { - keypad_key = '\r'; - goto numeric_keypad; - } - ordinary_return_key: - if (shift_state == 0 && wgs->term->cr_lf_return) { - *p++ = '\r'; - *p++ = '\n'; - return p - output; - } else { - *p++ = 0x0D; - *p++ = 0; - return -2; - } - } - } - - /* Okay we've done everything interesting; let windows deal with - * the boring stuff */ - { - bool capsOn = false; - - /* helg: clear CAPS LOCK state if caps lock switches to cyrillic */ - if (keystate[VK_CAPITAL] != 0 && - conf_get_bool(wgs->conf, CONF_xlat_capslockcyr)) { - capsOn = !left_alt; - keystate[VK_CAPITAL] = 0; - } - - wchar_t keys_unicode[3]; - - /* XXX how do we know what the max size of the keys array should - * be is? There's indication on MS' website of an Inquire/InquireEx - * functioning returning a KBINFO structure which tells us. */ - if (osPlatformId == VER_PLATFORM_WIN32_NT && p_ToUnicodeEx) { - r = p_ToUnicodeEx(wParam, scan, keystate, keys_unicode, - lenof(keys_unicode), 0, kbd_layout); - } else { - /* XXX 'keys' parameter is declared in MSDN documentation as - * 'LPWORD lpChar'. - * The experience of a French user indicates that on - * Win98, WORD[] should be passed in, but on Win2K, it should - * be BYTE[]. German WinXP and my Win2K with "US International" - * driver corroborate this. - * Experimentally I've conditionalised the behaviour on the - * Win9x/NT split, but I suspect it's worse than that. - * See wishlist item `win-dead-keys' for more horrible detail - * and speculations. */ - int i; - WORD keys[3]; - BYTE keysb[3]; - r = ToAsciiEx(wParam, scan, keystate, keys, 0, kbd_layout); - if (r > 0) { - for (i = 0; i < r; i++) { - keysb[i] = (BYTE)keys[i]; - } - MultiByteToWideChar(CP_ACP, 0, (LPCSTR)keysb, r, - keys_unicode, lenof(keys_unicode)); - } - } -#ifdef SHOW_TOASCII_RESULT - if (r == 1 && !key_down) { - if (wgs->alt_numberpad_accumulator) { - if (in_utf(term) || ucsdata.dbcs_screenfont) - debug(", (U+%04x)", wgs->alt_numberpad_accumulator); - else - debug(", LCH(%d)", wgs->alt_numberpad_accumulator); - } else { - debug(", ACH(%d)", keys_unicode[0]); - } - } else if (r > 0) { - int r1; - debug(", ASC("); - for (r1 = 0; r1 < r; r1++) { - debug("%s%d", r1 ? "," : "", keys_unicode[r1]); - } - debug(")"); - } -#endif - if (r > 0) { - WCHAR keybuf; - - p = output; - for (i = 0; i < r; i++) { - wchar_t wch = keys_unicode[i]; - - if (wgs->compose_state == 2 && wch >= ' ' && wch < 0x80) { - wgs->compose_char = wch; - wgs->compose_state++; - continue; - } - if (wgs->compose_state == 3 && wch >= ' ' && wch < 0x80) { - int nc; - wgs->compose_state = 0; - - if ((nc = check_compose(wgs->compose_char, wch)) == -1) { - MessageBeep(MB_ICONHAND); - return 0; - } - keybuf = nc; - term_keyinputw(wgs->term, &keybuf, 1); - continue; - } - - wgs->compose_state = 0; - - if (!key_down) { - if (wgs->alt_numberpad_accumulator) { - if (in_utf(wgs->term) || - wgs->ucsdata.dbcs_screenfont) { - keybuf = wgs->alt_numberpad_accumulator; - term_keyinputw(wgs->term, &keybuf, 1); - } else { - char ch = (char) wgs->alt_numberpad_accumulator; - /* - * We need not bother about stdin - * backlogs here, because in GUI PuTTY - * we can't do anything about it - * anyway; there's no means of asking - * Windows to hold off on KEYDOWN - * messages. We _have_ to buffer - * everything we're sent. - */ - term_keyinput(wgs->term, -1, &ch, 1); - } - wgs->alt_numberpad_accumulator = 0; - } else { - term_keyinputw(wgs->term, &wch, 1); - } - } else { - if (capsOn && wch < 0x80) { - WCHAR cbuf[2]; - cbuf[0] = 27; - cbuf[1] = xlat_uskbd2cyrllic(wch); - term_keyinputw( - wgs->term, cbuf+!left_alt, 1+!!left_alt); - } else { - WCHAR cbuf[2]; - cbuf[0] = '\033'; - cbuf[1] = wch; - term_keyinputw( - wgs->term, cbuf +!left_alt, 1+!!left_alt); - } - } - show_mouseptr(wgs, false); - } - - return p - output; - } - } - - /* - * ALT alone may or may not want to bring up the System menu. - * If it's not meant to, we return 0 on presses or releases of - * ALT, to show that we've swallowed the keystroke. Otherwise - * we return -1, which means Windows will give the keystroke - * its default handling (i.e. bring up the System menu). - */ - if (wParam == VK_MENU && !conf_get_bool(wgs->conf, CONF_alt_only)) - return 0; - - return -1; -} - -static void wintw_set_title(TermWin *tw, const char *title, int codepage) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - wchar_t *new_window_name = dup_mb_to_wc(codepage, 0, title); - if (!wcscmp(new_window_name, wgs->window_name)) { - sfree(new_window_name); - return; - } - sfree(wgs->window_name); - wgs->window_name = new_window_name; - if (conf_get_bool(wgs->conf, CONF_win_name_always) || - !IsIconic(wgs->term_hwnd)) - sw_SetWindowText(wgs->term_hwnd, wgs->window_name); -} - -static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - wchar_t *new_icon_name = dup_mb_to_wc(codepage, 0, title); - if (!wcscmp(new_icon_name, wgs->icon_name)) { - sfree(new_icon_name); - return; - } - sfree(wgs->icon_name); - wgs->icon_name = new_icon_name; - if (!conf_get_bool(wgs->conf, CONF_win_name_always) && - IsIconic(wgs->term_hwnd)) - sw_SetWindowText(wgs->term_hwnd, wgs->icon_name); -} - -static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - SCROLLINFO si; - - if (!conf_get_bool(wgs->conf, is_full_screen(wgs) ? - CONF_scrollbar_in_fullscreen : CONF_scrollbar)) - return; - - si.cbSize = sizeof(si); - si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; - si.nMin = 0; - si.nMax = total - 1; - si.nPage = page; - si.nPos = start; - if (wgs->term_hwnd) - SetScrollInfo(wgs->term_hwnd, SB_VERT, &si, true); -} - -static bool wintw_setup_draw_ctx(TermWin *tw) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - assert(!wgs->wintw_hdc); - wgs->wintw_hdc = make_hdc(wgs); - return wgs->wintw_hdc != NULL; -} - -static void wintw_free_draw_ctx(TermWin *tw) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - assert(wgs->wintw_hdc); - free_hdc(wgs, wgs->wintw_hdc); - wgs->wintw_hdc = NULL; -} - -/* - * Set up the colour palette. - */ -static void init_palette(WinGuiSeat *wgs) -{ - wgs->pal = NULL; - wgs->logpal = snew_plus( - LOGPALETTE, (OSC4_NCOLOURS - 1) * sizeof(PALETTEENTRY)); - wgs->logpal->palVersion = 0x300; - wgs->logpal->palNumEntries = OSC4_NCOLOURS; - for (unsigned i = 0; i < OSC4_NCOLOURS; i++) - wgs->logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE; -} - -static void wintw_palette_set(TermWin *tw, unsigned start, - unsigned ncolours, const rgb *colours_in) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - assert(start <= OSC4_NCOLOURS); - assert(ncolours <= OSC4_NCOLOURS - start); - - for (unsigned i = 0; i < ncolours; i++) { - const rgb *in = &colours_in[i]; - PALETTEENTRY *out = &wgs->logpal->palPalEntry[i + start]; - out->peRed = in->r; - out->peGreen = in->g; - out->peBlue = in->b; - wgs->colours[i + start] = - RGB(in->r, in->g, in->b) ^ wgs->colorref_modifier; - } - - bool got_new_palette = false; - - if (!wgs->tried_pal && conf_get_bool(wgs->conf, CONF_try_palette)) { - HDC hdc = GetDC(wgs->term_hwnd); - if (GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) { - wgs->pal = CreatePalette(wgs->logpal); - if (wgs->pal) { - SelectPalette(hdc, wgs->pal, false); - RealizePalette(hdc); - SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), false); - - /* Convert all RGB() values in colours[] into PALETTERGB(), - * and ensure we stick to that later */ - wgs->colorref_modifier = PALETTERGB(0, 0, 0) ^ RGB(0, 0, 0); - for (unsigned i = 0; i < OSC4_NCOLOURS; i++) - wgs->colours[i] ^= wgs->colorref_modifier; - - /* Inhibit the SetPaletteEntries call below */ - got_new_palette = true; - } - } - ReleaseDC(wgs->term_hwnd, hdc); - wgs->tried_pal = true; - } - - if (wgs->pal && !got_new_palette) { - /* We already had a palette, so replace the changed colours in the - * existing one. */ - SetPaletteEntries(wgs->pal, start, ncolours, - wgs->logpal->palPalEntry + start); - - HDC hdc = make_hdc(wgs); - UnrealizeObject(wgs->pal); - RealizePalette(hdc); - free_hdc(wgs, hdc); - } - - if (start <= OSC4_COLOUR_bg && OSC4_COLOUR_bg < start + ncolours) { - /* If Default Background changes, we need to ensure any space between - * the text area and the window border is redrawn. */ - InvalidateRect(wgs->term_hwnd, NULL, true); - } -} - -void write_aclip(HWND hwnd, int clipboard, char *data, int len) -{ - HGLOBAL clipdata; - void *lock; - - if (clipboard != CLIP_SYSTEM) - return; - - clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1); - if (!clipdata) - return; - lock = GlobalLock(clipdata); - if (!lock) - return; - memcpy(lock, data, len); - ((unsigned char *) lock)[len] = 0; - GlobalUnlock(clipdata); - - if (OpenClipboard(hwnd)) { - EmptyClipboard(); - SetClipboardData(CF_TEXT, clipdata); - CloseClipboard(); - } else - GlobalFree(clipdata); -} - -typedef struct _rgbindex { - int index; - COLORREF ref; -} rgbindex; - -int cmpCOLORREF(void *va, void *vb) -{ - COLORREF a = ((rgbindex *)va)->ref; - COLORREF b = ((rgbindex *)vb)->ref; - return (a < b) ? -1 : (a > b) ? +1 : 0; -} - -/* - * Note: unlike write_aclip() this will not append a nul. - */ -static void wintw_clip_write( - TermWin *tw, int clipboard, wchar_t *data, int *attr, - truecolour *truecolour, int len, bool must_deselect) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - HGLOBAL clipdata, clipdata2, clipdata3; - int len2; - void *lock, *lock2, *lock3; - - if (clipboard != CLIP_SYSTEM) - return; - - len2 = WideCharToMultiByte(CP_ACP, 0, data, len, 0, 0, NULL, NULL); - - clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, - len * sizeof(wchar_t)); - clipdata2 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len2); - - if (!clipdata || !clipdata2) { - if (clipdata) - GlobalFree(clipdata); - if (clipdata2) - GlobalFree(clipdata2); - return; - } - if (!(lock = GlobalLock(clipdata))) { - GlobalFree(clipdata); - GlobalFree(clipdata2); - return; - } - if (!(lock2 = GlobalLock(clipdata2))) { - GlobalUnlock(clipdata); - GlobalFree(clipdata); - GlobalFree(clipdata2); - return; - } - - memcpy(lock, data, len * sizeof(wchar_t)); - WideCharToMultiByte(CP_ACP, 0, data, len, lock2, len2, NULL, NULL); - - if (conf_get_bool(wgs->conf, CONF_rtf_paste)) { - wchar_t unitab[256]; - strbuf *rtf = strbuf_new(); - unsigned char *tdata = (unsigned char *)lock2; - wchar_t *udata = (wchar_t *)lock; - int uindex = 0, tindex = 0; - int multilen, blen, alen, i; - char before[16], after[4]; - int fgcolour, lastfgcolour = -1; - int bgcolour, lastbgcolour = -1; - COLORREF fg, lastfg = -1; - COLORREF bg, lastbg = -1; - int attrBold, lastAttrBold = 0; - int attrUnder, lastAttrUnder = 0; - int palette[OSC4_NCOLOURS]; - int numcolours; - tree234 *rgbtree = NULL; - FontSpec *font = conf_get_fontspec(wgs->conf, CONF_font); - - get_unitab(CP_ACP, unitab, 0); - - put_fmt( - rtf, "{\\rtf1\\ansi\\deff0{\\fonttbl\\f0\\fmodern %s;}\\f0\\fs%d", - font->name, font->height*2); - - /* - * Add colour palette - * {\colortbl ;\red255\green0\blue0;\red0\green0\blue128;} - */ - - /* - * First - Determine all colours in use - * o Foregound and background colours share the same palette - */ - if (attr) { - memset(palette, 0, sizeof(palette)); - for (i = 0; i < (len-1); i++) { - fgcolour = ((attr[i] & ATTR_FGMASK) >> ATTR_FGSHIFT); - bgcolour = ((attr[i] & ATTR_BGMASK) >> ATTR_BGSHIFT); - - if (attr[i] & ATTR_REVERSE) { - int tmpcolour = fgcolour; /* Swap foreground and background */ - fgcolour = bgcolour; - bgcolour = tmpcolour; - } - - if (wgs->bold_colours && (attr[i] & ATTR_BOLD)) { - if (fgcolour < 8) /* ANSI colours */ - fgcolour += 8; - else if (fgcolour >= 256) /* Default colours */ - fgcolour ++; - } - - if ((attr[i] & ATTR_BLINK)) { - if (bgcolour < 8) /* ANSI colours */ - bgcolour += 8; - else if (bgcolour >= 256) /* Default colours */ - bgcolour ++; - } - - palette[fgcolour]++; - palette[bgcolour]++; - } - - if (truecolour) { - rgbtree = newtree234(cmpCOLORREF); - for (i = 0; i < (len-1); i++) { - if (truecolour[i].fg.enabled) { - rgbindex *rgbp = snew(rgbindex); - rgbp->ref = RGB(truecolour[i].fg.r, - truecolour[i].fg.g, - truecolour[i].fg.b); - if (add234(rgbtree, rgbp) != rgbp) - sfree(rgbp); - } - if (truecolour[i].bg.enabled) { - rgbindex *rgbp = snew(rgbindex); - rgbp->ref = RGB(truecolour[i].bg.r, - truecolour[i].bg.g, - truecolour[i].bg.b); - if (add234(rgbtree, rgbp) != rgbp) - sfree(rgbp); - } - } - } - - /* - * Next - Create a reduced palette - */ - numcolours = 0; - for (i = 0; i < OSC4_NCOLOURS; i++) { - if (palette[i] != 0) - palette[i] = ++numcolours; - } - - if (rgbtree) { - rgbindex *rgbp; - for (i = 0; (rgbp = index234(rgbtree, i)) != NULL; i++) - rgbp->index = ++numcolours; - } - - /* - * Finally - Write the colour table - */ - put_datapl(rtf, PTRLEN_LITERAL("{\\colortbl ;")); - - for (i = 0; i < OSC4_NCOLOURS; i++) { - if (palette[i] != 0) { - const PALETTEENTRY *pe = &wgs->logpal->palPalEntry[i]; - put_fmt(rtf, "\\red%d\\green%d\\blue%d;", - pe->peRed, pe->peGreen, pe->peBlue); - } - } - if (rgbtree) { - rgbindex *rgbp; - for (i = 0; (rgbp = index234(rgbtree, i)) != NULL; i++) - put_fmt(rtf, "\\red%d\\green%d\\blue%d;", - GetRValue(rgbp->ref), GetGValue(rgbp->ref), - GetBValue(rgbp->ref)); - } - put_datapl(rtf, PTRLEN_LITERAL("}")); - } - - /* - * We want to construct a piece of RTF that specifies the - * same Unicode text. To do this we will read back in - * parallel from the Unicode data in `udata' and the - * non-Unicode data in `tdata'. For each character in - * `tdata' which becomes the right thing in `udata' when - * looked up in `unitab', we just copy straight over from - * tdata. For each one that doesn't, we must WCToMB it - * individually and produce a \u escape sequence. - * - * It would probably be more robust to just bite the bullet - * and WCToMB each individual Unicode character one by one, - * then MBToWC each one back to see if it was an accurate - * translation; but that strikes me as a horrifying number - * of Windows API calls so I want to see if this faster way - * will work. If it screws up badly we can always revert to - * the simple and slow way. - */ - while (tindex < len2 && uindex < len && - tdata[tindex] && udata[uindex]) { - if (tindex + 1 < len2 && - tdata[tindex] == '\r' && - tdata[tindex+1] == '\n') { - tindex++; - uindex++; - } - - /* - * Set text attributes - */ - if (attr) { - /* - * Determine foreground and background colours - */ - if (truecolour && truecolour[tindex].fg.enabled) { - fgcolour = -1; - fg = RGB(truecolour[tindex].fg.r, - truecolour[tindex].fg.g, - truecolour[tindex].fg.b); - } else { - fgcolour = ((attr[tindex] & ATTR_FGMASK) >> ATTR_FGSHIFT); - fg = -1; - } - - if (truecolour && truecolour[tindex].bg.enabled) { - bgcolour = -1; - bg = RGB(truecolour[tindex].bg.r, - truecolour[tindex].bg.g, - truecolour[tindex].bg.b); - } else { - bgcolour = ((attr[tindex] & ATTR_BGMASK) >> ATTR_BGSHIFT); - bg = -1; - } - - if (attr[tindex] & ATTR_REVERSE) { - int tmpcolour = fgcolour; /* Swap foreground and background */ - fgcolour = bgcolour; - bgcolour = tmpcolour; - - COLORREF tmpref = fg; - fg = bg; - bg = tmpref; - } - - if (wgs->bold_colours && (attr[tindex] & ATTR_BOLD) && - (fgcolour >= 0)) { - if (fgcolour < 8) /* ANSI colours */ - fgcolour += 8; - else if (fgcolour >= 256) /* Default colours */ - fgcolour ++; - } - - if ((attr[tindex] & ATTR_BLINK) && (bgcolour >= 0)) { - if (bgcolour < 8) /* ANSI colours */ - bgcolour += 8; - else if (bgcolour >= 256) /* Default colours */ - bgcolour ++; - } - - /* - * Collect other attributes - */ - if (wgs->bold_font_mode != BOLD_NONE) - attrBold = attr[tindex] & ATTR_BOLD; - else - attrBold = 0; - - attrUnder = attr[tindex] & ATTR_UNDER; - - /* - * Reverse video - * o If video isn't reversed, ignore colour attributes for default foregound - * or background. - * o Special case where bolded text is displayed using the default foregound - * and background colours - force to bolded RTF. - */ - if (!(attr[tindex] & ATTR_REVERSE)) { - if (bgcolour >= 256) /* Default color */ - bgcolour = -1; /* No coloring */ - - if (fgcolour >= 256) { /* Default colour */ - if (wgs->bold_colours && (fgcolour & 1) && - bgcolour == -1) - attrBold = ATTR_BOLD; /* Emphasize text with bold attribute */ - - fgcolour = -1; /* No coloring */ - } - } - - /* - * Write RTF text attributes - */ - if ((lastfgcolour != fgcolour) || (lastfg != fg)) { - lastfgcolour = fgcolour; - lastfg = fg; - if (fg == -1) { - put_fmt(rtf, "\\cf%d ", - (fgcolour >= 0) ? palette[fgcolour] : 0); - } else { - rgbindex rgb, *rgbp; - rgb.ref = fg; - if ((rgbp = find234(rgbtree, &rgb, NULL)) != NULL) - put_fmt(rtf, "\\cf%d ", rgbp->index); - } - } - - if ((lastbgcolour != bgcolour) || (lastbg != bg)) { - lastbgcolour = bgcolour; - lastbg = bg; - if (bg == -1) - put_fmt(rtf, "\\highlight%d ", - (bgcolour >= 0) ? palette[bgcolour] : 0); - else { - rgbindex rgb, *rgbp; - rgb.ref = bg; - if ((rgbp = find234(rgbtree, &rgb, NULL)) != NULL) - put_fmt(rtf, "\\highlight%d ", rgbp->index); - } - } - - if (lastAttrBold != attrBold) { - lastAttrBold = attrBold; - put_datapl(rtf, attrBold ? - PTRLEN_LITERAL("\\b ") : - PTRLEN_LITERAL("\\b0 ")); - } - - if (lastAttrUnder != attrUnder) { - lastAttrUnder = attrUnder; - put_datapl(rtf, attrUnder ? - PTRLEN_LITERAL("\\ul ") : - PTRLEN_LITERAL("\\ulnone ")); - } - } - - if (unitab[tdata[tindex]] == udata[uindex]) { - multilen = 1; - before[0] = '\0'; - after[0] = '\0'; - blen = alen = 0; - } else { - multilen = WideCharToMultiByte(CP_ACP, 0, unitab+uindex, 1, - NULL, 0, NULL, NULL); - if (multilen != 1) { - blen = sprintf(before, "{\\uc%d\\u%d", (int)multilen, - (int)udata[uindex]); - alen = 1; strcpy(after, "}"); - } else { - blen = sprintf(before, "\\u%d", (int)udata[uindex]); - alen = 0; after[0] = '\0'; - } - } - assert(tindex + multilen <= len2); - - put_data(rtf, before, blen); - for (i = 0; i < multilen; i++) { - if (tdata[tindex+i] == '\\' || - tdata[tindex+i] == '{' || - tdata[tindex+i] == '}') { - put_byte(rtf, '\\'); - put_byte(rtf, tdata[tindex+i]); - } else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) { - put_datapl(rtf, PTRLEN_LITERAL("\\par\r\n")); - } else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) { - put_fmt(rtf, "\\'%02x", tdata[tindex+i]); - } else { - put_byte(rtf, tdata[tindex+i]); - } - } - put_data(rtf, after, alen); - - tindex += multilen; - uindex++; - } - - put_datapl(rtf, PTRLEN_LITERAL("}\0\0")); /* Terminate RTF stream */ - - clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtf->len); - if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) { - memcpy(lock3, rtf->u, rtf->len); - GlobalUnlock(clipdata3); - } - strbuf_free(rtf); - - if (rgbtree) { - rgbindex *rgbp; - while ((rgbp = delpos234(rgbtree, 0)) != NULL) - sfree(rgbp); - freetree234(rgbtree); - } - } else - clipdata3 = NULL; - - GlobalUnlock(clipdata); - GlobalUnlock(clipdata2); - - if (!must_deselect) - SendMessage(wgs->term_hwnd, WM_IGNORE_CLIP, true, 0); - - if (OpenClipboard(wgs->term_hwnd)) { - EmptyClipboard(); - SetClipboardData(CF_UNICODETEXT, clipdata); - SetClipboardData(CF_TEXT, clipdata2); - if (clipdata3) - SetClipboardData(RegisterClipboardFormat(CF_RTF), clipdata3); - CloseClipboard(); - } else { - GlobalFree(clipdata); - GlobalFree(clipdata2); - } - - if (!must_deselect) - SendMessage(wgs->term_hwnd, WM_IGNORE_CLIP, false, 0); -} - -static DWORD WINAPI clipboard_read_threadfunc(void *param) -{ - HWND hwnd = (HWND)param; - HGLOBAL clipdata; - - if (OpenClipboard(NULL)) { - if ((clipdata = GetClipboardData(CF_UNICODETEXT))) { - SendMessage(hwnd, WM_GOT_CLIPDATA, - (WPARAM)true, (LPARAM)clipdata); - } else if ((clipdata = GetClipboardData(CF_TEXT))) { - SendMessage(hwnd, WM_GOT_CLIPDATA, - (WPARAM)false, (LPARAM)clipdata); - } - CloseClipboard(); - } - - return 0; -} - -static void process_clipdata(WinGuiSeat *wgs, HGLOBAL clipdata, bool unicode) -{ - wchar_t *clipboard_contents = NULL; - size_t clipboard_length = 0; - - if (unicode) { - wchar_t *p = GlobalLock(clipdata); - wchar_t *p2; - - if (p) { - /* Unwilling to rely on Windows having wcslen() */ - for (p2 = p; *p2; p2++); - clipboard_length = p2 - p; - clipboard_contents = snewn(clipboard_length + 1, wchar_t); - memcpy(clipboard_contents, p, clipboard_length * sizeof(wchar_t)); - clipboard_contents[clipboard_length] = L'\0'; - term_do_paste(wgs->term, clipboard_contents, clipboard_length); - } - } else { - char *s = GlobalLock(clipdata); - int i; - - if (s) { - i = MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, 0, 0); - clipboard_contents = snewn(i, wchar_t); - MultiByteToWideChar(CP_ACP, 0, s, strlen(s) + 1, - clipboard_contents, i); - clipboard_length = i - 1; - clipboard_contents[clipboard_length] = L'\0'; - term_do_paste(wgs->term, clipboard_contents, clipboard_length); - } - } - - sfree(clipboard_contents); -} - -static void wintw_clip_request_paste(TermWin *tw, int clipboard) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - assert(clipboard == CLIP_SYSTEM); - - /* - * I always thought pasting was synchronous in Windows; the - * clipboard access functions certainly _look_ synchronous, - * unlike the X ones. But in fact it seems that in some - * situations the contents of the clipboard might not be - * immediately available, and the clipboard-reading functions - * may block. This leads to trouble if the application - * delivering the clipboard data has to get hold of it by - - * for example - talking over a network connection which is - * forwarded through this very PuTTY. - * - * Hence, we spawn a subthread to read the clipboard, and do - * our paste when it's finished. The thread will send a - * message back to our main window when it terminates, and - * that tells us it's OK to paste. - */ - DWORD in_threadid; /* required for Win9x */ - HANDLE hThread = CreateThread(NULL, 0, clipboard_read_threadfunc, - wgs->term_hwnd, 0, &in_threadid); - if (hThread) - CloseHandle(hThread); /* we don't need the thread handle */ -} - -/* - * Print a modal (Really Bad) message box and perform a fatal exit. - */ -void modalfatalbox(const char *fmt, ...) -{ - va_list ap; - char *message, *title; - - va_start(ap, fmt); - message = dupvprintf(fmt, ap); - va_end(ap); - show_mouseptr(NULL, true); - title = dupprintf("%s Fatal Error", appname); - MessageBox(find_window_for_msgbox(), message, title, - MB_SYSTEMMODAL | MB_ICONERROR | MB_OK); - sfree(message); - sfree(title); - cleanup_exit(1); -} - -/* - * Print a message box and don't close the connection. - */ -void nonfatal(const char *fmt, ...) -{ - va_list ap; - char *message, *title; - - va_start(ap, fmt); - message = dupvprintf(fmt, ap); - va_end(ap); - show_mouseptr(NULL, true); - title = dupprintf("%s Error", appname); - MessageBox(find_window_for_msgbox(), message, title, MB_ICONERROR | MB_OK); - sfree(message); - sfree(title); -} - -static bool flash_window_ex(WinGuiSeat *wgs, DWORD dwFlags, - UINT uCount, DWORD dwTimeout) -{ - if (p_FlashWindowEx) { - FLASHWINFO fi; - fi.cbSize = sizeof(fi); - fi.hwnd = wgs->term_hwnd; - fi.dwFlags = dwFlags; - fi.uCount = uCount; - fi.dwTimeout = dwTimeout; - return (*p_FlashWindowEx)(&fi); - } - else - return false; /* shrug */ -} - -/* - * Timer for platforms where we must maintain window flashing manually - * (e.g., Win95). - */ -static void flash_window_timer(void *vctx, unsigned long now) -{ - WinGuiSeat *wgs = (WinGuiSeat *)vctx; - if (wgs->flashing && now == wgs->next_flash) { - flash_window(wgs, 1); - } -} - -/* - * Manage window caption / taskbar flashing, if enabled. - * 0 = stop, 1 = maintain, 2 = start - */ -static void flash_window(WinGuiSeat *wgs, int mode) -{ - int beep_ind = conf_get_int(wgs->conf, CONF_beep_ind); - if ((mode == 0) || (beep_ind == B_IND_DISABLED)) { - /* stop */ - if (wgs->flashing) { - wgs->flashing = false; - if (p_FlashWindowEx) - flash_window_ex(wgs, FLASHW_STOP, 0, 0); - else - FlashWindow(wgs->term_hwnd, false); - } - - } else if (mode == 2) { - /* start */ - if (!wgs->flashing) { - wgs->flashing = true; - if (p_FlashWindowEx) { - /* For so-called "steady" mode, we use uCount=2, which - * seems to be the traditional number of flashes used - * by user notifications (e.g., by Explorer). - * uCount=0 appears to enable continuous flashing, per - * "flashing" mode, although I haven't seen this - * documented. */ - flash_window_ex(wgs, FLASHW_ALL | FLASHW_TIMER, - (beep_ind == B_IND_FLASH ? 0 : 2), - 0 /* system cursor blink rate */); - /* No need to schedule timer */ - } else { - FlashWindow(wgs->term_hwnd, true); - wgs->next_flash = schedule_timer(450, flash_window_timer, wgs); - } - } - - } else if ((mode == 1) && (beep_ind == B_IND_FLASH)) { - /* maintain */ - if (wgs->flashing && !p_FlashWindowEx) { - FlashWindow(wgs->term_hwnd, true); /* toggle */ - wgs->next_flash = schedule_timer(450, flash_window_timer, wgs); - } - } -} - -/* - * Beep. - */ -static void wintw_bell(TermWin *tw, int mode) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - if (mode == BELL_DEFAULT) { - /* - * For MessageBeep style bells, we want to be careful of - * timing, because they don't have the nice property of - * PlaySound bells that each one cancels the previous - * active one. So we limit the rate to one per 50ms or so. - */ - long beepdiff; - - beepdiff = GetTickCount() - wgs->last_beep_time; - if (beepdiff >= 0 && beepdiff < 50) - return; - MessageBeep(MB_OK); - /* - * The above MessageBeep call takes time, so we record the - * time _after_ it finishes rather than before it starts. - */ - wgs->last_beep_time = GetTickCount(); - } else if (mode == BELL_WAVEFILE) { - Filename *bell_wavefile = conf_get_filename( - wgs->conf, CONF_bell_wavefile); - bool success = ( - p_PlaySoundW ? p_PlaySoundW(bell_wavefile->wpath, NULL, - SND_ASYNC | SND_FILENAME) : - p_PlaySoundA ? p_PlaySoundA(bell_wavefile->cpath, NULL, - SND_ASYNC | SND_FILENAME) : false); - if (!success) { - char *buf, *otherbuf; - show_mouseptr(wgs, true); - buf = dupprintf( - "Unable to play sound file\n%s\nUsing default sound instead", - bell_wavefile->utf8path); - otherbuf = dupprintf("%s Sound Error", appname); - message_box(wgs->term_hwnd, buf, otherbuf, - MB_OK | MB_ICONEXCLAMATION, true, 0); - sfree(buf); - sfree(otherbuf); - conf_set_int(wgs->conf, CONF_beep, BELL_DEFAULT); - } - } else if (mode == BELL_PCSPEAKER) { - long beepdiff; - - beepdiff = GetTickCount() - wgs->last_beep_time; - if (beepdiff >= 0 && beepdiff < 50) - return; - - /* - * We must beep in different ways depending on whether this - * is a 95-series or NT-series OS. - */ - if (osPlatformId == VER_PLATFORM_WIN32_NT) - Beep(800, 100); - else - MessageBeep(-1); - wgs->last_beep_time = GetTickCount(); - } - /* Otherwise, either visual bell or disabled; do nothing here */ - if (!wgs->term->has_focus) { - flash_window(wgs, 2); /* start */ - } -} - -/* - * Minimise or restore the window in response to a server-side - * request. - */ -static void wintw_set_minimised(TermWin *tw, bool minimised) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - if (IsIconic(wgs->term_hwnd)) { - if (!minimised) - ShowWindow(wgs->term_hwnd, SW_RESTORE); - } else { - if (minimised) - ShowWindow(wgs->term_hwnd, SW_MINIMIZE); - } -} - -/* - * Move the window in response to a server-side request. - */ -static void wintw_move(TermWin *tw, int x, int y) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - int resize_action = conf_get_int(wgs->conf, CONF_resize_action); - if (resize_action == RESIZE_DISABLED || - resize_action == RESIZE_FONT || - IsZoomed(wgs->term_hwnd)) - return; - - SetWindowPos(wgs->term_hwnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); -} - -/* - * Move the window to the top or bottom of the z-order in response - * to a server-side request. - */ -static void wintw_set_zorder(TermWin *tw, bool top) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - if (conf_get_bool(wgs->conf, CONF_alwaysontop)) - return; /* ignore */ - SetWindowPos(wgs->term_hwnd, top ? HWND_TOP : HWND_BOTTOM, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE); -} - -/* - * Refresh the window in response to a server-side request. - */ -static void wintw_refresh(TermWin *tw) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - InvalidateRect(wgs->term_hwnd, NULL, true); -} - -/* - * Maximise or restore the window in response to a server-side - * request. - */ -static void wintw_set_maximised(TermWin *tw, bool maximised) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - if (IsZoomed(wgs->term_hwnd)) { - if (!maximised) - ShowWindow(wgs->term_hwnd, SW_RESTORE); - } else { - if (maximised) - ShowWindow(wgs->term_hwnd, SW_MAXIMIZE); - } -} - -/* - * See if we're in full-screen mode. - */ -static bool is_full_screen(WinGuiSeat *wgs) -{ - if (!IsZoomed(wgs->term_hwnd)) - return false; - if (GetWindowLongPtr(wgs->term_hwnd, GWL_STYLE) & WS_CAPTION) - return false; - return true; -} - -/* Get the rect/size of a full screen window using the nearest available - * monitor in multimon systems; default to something sensible if only - * one monitor is present. */ -static bool get_fullscreen_rect(WinGuiSeat *wgs, RECT *ss) -{ -#if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON) - if (p_GetMonitorInfoA && p_MonitorFromWindow) { - HMONITOR mon; - MONITORINFO mi; - mon = p_MonitorFromWindow(wgs->term_hwnd, MONITOR_DEFAULTTONEAREST); - mi.cbSize = sizeof(mi); - p_GetMonitorInfoA(mon, &mi); - - /* structure copy */ - *ss = mi.rcMonitor; - return true; - } -#endif -/* could also use code like this: - ss->left = ss->top = 0; - ss->right = GetSystemMetrics(SM_CXSCREEN); - ss->bottom = GetSystemMetrics(SM_CYSCREEN); -*/ - return GetClientRect(GetDesktopWindow(), ss); -} - - -/* - * Go full-screen. This should only be called when we are already - * maximised. - */ -static void make_full_screen(WinGuiSeat *wgs) -{ - DWORD style; - RECT ss; - - assert(IsZoomed(wgs->term_hwnd)); - - if (is_full_screen(wgs)) - return; - - /* Remove the window furniture. */ - style = GetWindowLongPtr(wgs->term_hwnd, GWL_STYLE); - style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME); - if (conf_get_bool(wgs->conf, CONF_scrollbar_in_fullscreen)) - style |= WS_VSCROLL; - else - style &= ~WS_VSCROLL; - SetWindowLongPtr(wgs->term_hwnd, GWL_STYLE, style); - - /* Resize ourselves to exactly cover the nearest monitor. */ - get_fullscreen_rect(wgs, &ss); - SetWindowPos(wgs->term_hwnd, HWND_TOP, ss.left, ss.top, - ss.right - ss.left, ss.bottom - ss.top, SWP_FRAMECHANGED); - - /* We may have changed size as a result */ - - reset_window(wgs, 0); - - /* Tick the menu item in the System and context menus. */ - { - int i; - for (i = 0; i < lenof(wgs->popup_menus); i++) - CheckMenuItem(wgs->popup_menus[i].menu, - IDM_FULLSCREEN, MF_CHECKED); - } -} - -/* - * Clear the full-screen attributes. - */ -static void clear_full_screen(WinGuiSeat *wgs) -{ - DWORD oldstyle, style; - - /* Reinstate the window furniture. */ - style = oldstyle = GetWindowLongPtr(wgs->term_hwnd, GWL_STYLE); - style |= WS_CAPTION | WS_BORDER; - if (conf_get_int(wgs->conf, CONF_resize_action) == RESIZE_DISABLED) - style &= ~WS_THICKFRAME; - else - style |= WS_THICKFRAME; - if (conf_get_bool(wgs->conf, CONF_scrollbar)) - style |= WS_VSCROLL; - else - style &= ~WS_VSCROLL; - if (style != oldstyle) { - SetWindowLongPtr(wgs->term_hwnd, GWL_STYLE, style); - SetWindowPos(wgs->term_hwnd, NULL, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | - SWP_FRAMECHANGED); - } - - /* Untick the menu item in the System and context menus. */ - { - int i; - for (i = 0; i < lenof(wgs->popup_menus); i++) - CheckMenuItem(wgs->popup_menus[i].menu, - IDM_FULLSCREEN, MF_UNCHECKED); - } -} - -/* - * Toggle full-screen mode. - */ -static void flip_full_screen(WinGuiSeat *wgs) -{ - if (is_full_screen(wgs)) { - ShowWindow(wgs->term_hwnd, SW_RESTORE); - } else if (IsZoomed(wgs->term_hwnd)) { - make_full_screen(wgs); - } else { - SendMessage(wgs->term_hwnd, WM_FULLSCR_ON_MAX, 0, 0); - ShowWindow(wgs->term_hwnd, SW_MAXIMIZE); - } -} - -static size_t win_seat_output(Seat *seat, SeatOutputType type, - const void *data, size_t len) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - return term_data(wgs->term, data, len); -} - -static void wintw_unthrottle(TermWin *tw, size_t bufsize) -{ - WinGuiSeat *wgs = container_of(tw, WinGuiSeat, termwin); - if (wgs->backend) - backend_unthrottle(wgs->backend, bufsize); -} - -static bool win_seat_eof(Seat *seat) -{ - return true; /* do respond to incoming EOF with outgoing */ -} - -static SeatPromptResult win_seat_get_userpass_input(Seat *seat, prompts_t *p) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - SeatPromptResult spr; - spr = cmdline_get_passwd_input(p, &wgs->cmdline_get_passwd_state, true); - if (spr.kind == SPRK_INCOMPLETE) - spr = term_get_userpass_input(wgs->term, p); - return spr; -} - -static void win_seat_set_trust_status(Seat *seat, bool trusted) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - term_set_trust_status(wgs->term, trusted); -} - -static bool win_seat_can_set_trust_status(Seat *seat) -{ - return true; -} - -static bool win_seat_get_cursor_position(Seat *seat, int *x, int *y) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - term_get_cursor_position(wgs->term, x, y); - return true; -} - -static bool win_seat_get_window_pixel_size(Seat *seat, int *x, int *y) -{ - WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); - RECT r; - GetWindowRect(wgs->term_hwnd, &r); - *x = r.right - r.left; - *y = r.bottom - r.top; - return true; -} diff --git a/WINDOWS/x11.c b/WINDOWS/x11.c deleted file mode 100644 index a6f2676c4..000000000 --- a/WINDOWS/x11.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * x11.c: fetch local auth data for X forwarding. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" - -void platform_get_x11_auth(struct X11Display *disp, Conf *conf) -{ - Filename *xauthfn = conf_get_filename(conf, CONF_xauthfile); - if (!filename_is_null(xauthfn)) - x11_get_auth_from_authfile(disp, xauthfn); -} - -const bool platform_uses_x11_unix_by_default = false; diff --git a/aqsync.c b/aqsync.c deleted file mode 100644 index dee048e75..000000000 --- a/aqsync.c +++ /dev/null @@ -1,21 +0,0 @@ -/* - * aqsync.c: the agent_query_synchronous() wrapper function. - * - * This is a very small thing to have to put in its own module, but it - * wants to be shared between back ends, and exist in any SSH client - * program and also Pageant, and _nowhere else_ (because it pulls in - * the main agent_query). - */ - -#include - -#include "putty.h" - -void agent_query_synchronous(strbuf *query, void **out, int *outlen) -{ - agent_pending_query *pending; - - pending = agent_query(query, out, outlen, NULL, 0); - assert(!pending); -} - diff --git a/be_all.c b/be_all.c deleted file mode 100644 index aa34589ee..000000000 --- a/be_all.c +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 = "PuTTYNG"; - -const int be_default_protocol = PROT_SSH; - -const struct BackendVtable *const backends[] = { - &ssh_backend, - &telnet_backend, - &rlogin_backend, - &supdup_backend, - &raw_backend, - &sshconn_backend, - NULL -}; - -const size_t n_ui_backends = 1; diff --git a/be_all_s.c b/be_all_s.c deleted file mode 100644 index 2afcfa15a..000000000 --- a/be_all_s.c +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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... - */ -#ifdef PUTTYNG -const char *const appname = "PuTTYNG"; -#else -const char *const appname = "PuTTY"; -#endif - -const int be_default_protocol = PROT_SSH; - -const struct BackendVtable *const backends[] = { - &ssh_backend, - &serial_backend, - &telnet_backend, - &rlogin_backend, - &supdup_backend, - &raw_backend, - &sshconn_backend, - NULL -}; - -const size_t n_ui_backends = 2; diff --git a/be_list.c b/be_list.c deleted file mode 100644 index 09437a69a..000000000 --- a/be_list.c +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Source file that is rebuilt per application, and provides the list - * of backends, the default protocol, and the application name. - * - * This file expects the build system to provide some per-application - * definitions on the compiler command line. So you don't just add it - * directly to the sources list for an application. Instead you call - * the be_list() function defined in setup.cmake, e.g. - * - * be_list(target-name AppName [SSH] [SERIAL] [OTHERBACKENDS]) - * - * This translates into the following command-line macro definitions - * used by the code below: - * - * - APPNAME should be defined to the name of the program, in - * user-facing capitalisation (e.g. PuTTY rather than putty). - * Unquoted: it's easier to stringify it in the preprocessor than - * to persuade cmake to put the right quotes on the command line on - * all build platforms. - * - * - The following macros should each be defined to 1 if a given set - * of backends should be added to the backends[] list, or 0 if they - * should not be: - * - * * SSH: the two SSH backends (SSH proper, and bare-ssh-connection) - * - * * SERIAL: the serial port backend - * - * * OTHERBACKENDS: the non-cryptographic network protocol backends - * (Telnet, Rlogin, SUPDUP, Raw) - */ - -#include -#include "putty.h" - -const char *const appname = STR(APPNAME); - -/* - * Define the default protocol for the application. This is always a - * network backend (serial ports come second behind network, in every - * case). Applications that don't have either (such as pterm) don't - * need this variable anyway, so just set it to -1. - */ -#if SSH -const int be_default_protocol = PROT_SSH; -#elif OTHERBACKENDS -const int be_default_protocol = PROT_TELNET; -#else -const int be_default_protocol = -1; -#endif - -/* - * List all the configured backends, in the order they should appear - * in the config box. - */ -const struct BackendVtable *const backends[] = { - /* - * Start with the most-preferred network-remote-login protocol. - * That's SSH if present, otherwise Telnet if present. - */ -#if SSH - &ssh_backend, -#elif OTHERBACKENDS - &telnet_backend, /* Telnet at the top if SSH is absent */ -#endif - - /* - * Second on the list is the serial-port backend, if available. - */ -#if SERIAL - &serial_backend, -#endif - - /* - * After that come the remaining network protocols: Telnet if it - * hasn't already appeared above, and Rlogin, SUPDUP and Raw. - */ -#if OTHERBACKENDS && SSH - &telnet_backend, /* only if SSH displaced it at the top */ -#endif -#if OTHERBACKENDS - &rlogin_backend, - &supdup_backend, - &raw_backend, -#endif - - /* - * Bare ssh-connection / PSUSAN is a niche protocol and goes well - * down the list. - */ -#if SSH - &sshconn_backend, -#endif - - /* - * Done. Null pointer to mark the end of the list. - */ - NULL -}; - -/* - * Number of backends at the start of the above list that should have - * radio buttons in the config UI. - * - * The rule is: the most-preferred network backend, and Serial, each - * get a radio button if present. - * - * The rest will be relegated to a dropdown list. - */ -const size_t n_ui_backends = - 0 -#if SSH || OTHERBACKENDS - + 1 -#endif -#if SERIAL - + 1 -#endif - ; diff --git a/callback.c b/callback.c deleted file mode 100644 index ea647af4d..000000000 --- a/callback.c +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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; -}; - -static struct callback *cbcurr = NULL, *cbhead = NULL, *cbtail = NULL; - -static toplevel_callback_notify_fn_t notify_frontend = NULL; -static void *notify_ctx = NULL; - -void request_callback_notifications(toplevel_callback_notify_fn_t fn, - void *ctx) -{ - notify_frontend = fn; - notify_ctx = ctx; -} - -static void run_idempotent_callback(void *ctx) -{ - struct IdempotentCallback *ic = (struct IdempotentCallback *)ctx; - ic->queued = false; - ic->fn(ic->ctx); -} - -void queue_idempotent_callback(struct IdempotentCallback *ic) -{ - if (ic->queued) - return; - ic->queued = true; - queue_toplevel_callback(run_idempotent_callback, ic); -} - -void delete_callbacks_for_context(void *ctx) -{ - struct callback *newhead, *newtail; - - newhead = newtail = NULL; - while (cbhead) { - struct callback *cb = cbhead; - cbhead = cbhead->next; - if (cb->ctx == ctx || - (cb->fn == run_idempotent_callback && - ((struct IdempotentCallback *)cb->ctx)->ctx == ctx)) { - sfree(cb); - } else { - if (!newhead) - newhead = cb; - else - newtail->next = cb; - - newtail = cb; - } - } - - cbhead = newhead; - cbtail = newtail; - if (newtail) - newtail->next = NULL; -} - -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 cbcurr is non-NULL, i.e. we are actually in the middle of - * executing a callback right now, then we count that as the queue - * already having been non-empty. That saves the front end getting - * a constant stream of needless re-notifications if the last - * callback keeps re-scheduling itself. - */ - if (notify_frontend && !cbhead && !cbcurr) - notify_frontend(notify_ctx); - - if (cbtail) - cbtail->next = cb; - else - cbhead = cb; - cbtail = cb; - cb->next = NULL; -} - -bool run_toplevel_callbacks(void) -{ - bool done_something = false; - - if (cbhead) { - /* - * Transfer the head callback into cbcurr to indicate that - * it's being executed. Then operations which transform the - * queue, like delete_callbacks_for_context, can proceed as if - * it's not there. - */ - cbcurr = cbhead; - cbhead = cbhead->next; - if (!cbhead) - cbtail = NULL; - - /* - * Now run the callback, and then clear it out of cbcurr. - */ - cbcurr->fn(cbcurr->ctx); - sfree(cbcurr); - cbcurr = NULL; - - done_something = true; - } - return done_something; -} - -bool toplevel_callback_pending(void) -{ - return cbcurr != NULL || cbhead != NULL; -} diff --git a/cert/CodeSigning_Cert_mRemoteNG_DigiCert.p12.enc b/cert/CodeSigning_Cert_mRemoteNG_DigiCert.p12.enc deleted file mode 100644 index 3853825984fdedc11b69a12648a9398b8a6a2b0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3120 zcmV-04A1ip1y*jOL&;jr$QMXzU7h_b(_vNX!|6C&1vJLc`9W( zblGKO4tsgD!MWDz{S`4#L-1-{SZF!Ph7ZpPll!nbY+X=v-{p9x3#ctIDh=Vd*4v+LoG%=Dzxo@ zvw_*6Mvhm7TsAZRF{*4Hli#Q0+IaW)53_H20dy}o3~S@#S|)Ha6Q&wh;|A6x=K}t#$+T# zH$>-Lh&efryGXp3YoPsgl(SvX$pSdGH(MB_5vO_r>D9qHUaY85!*L9V{Lf@tuP>M>5jyE!1}Ob;0z%=B{oY z;d-g^d7#S8LRZ%nqu(7H+in5s;@9jzrE_4li84bZ{##c8QI#b4jR5x|Z{vBX;;>?X zD{nQOwbxWRFVK4byBU;VLh3|LOLABIoBnkmvvR8sx((x_YZg=cp3AsJxtEYOR`9V_u9 zXu{|;?&oc8)l)?NBf9$S`BJC~rhm=GrI_u)_;N$>b=1l0Z z5}m+Jjkvr$p>OtBe~(Cp)Zq~|)IEdNzFhmosu1~a3@usaKVqyIhn$(Ip-xLV@yo>&&I}T1~81Oa!+_bos^Ot!M(Ss-l{*}bV(uS=TbhmM!O&23( zCJtHm{}AKSic*872q}!%-zL-X4e1^+1bYuA{rK7{cwuhM(Sd}Lkn$EbP#&~o**mb7 z7KoUtD`l9>fplRr(*hbW!|tW!pW8Ef7TyzJ=Sa|ow}6x{?#^-4qD1dqFBeRsIOnt& zn;&$gtO$7LRFcD{UFrpsh`QO0irD9KpkB~9zhBlY8hx6%LEIb7hl4{*V`v#ymJ&Bb zWW4&7&%J8s|Ixk6fji(|%&Pr4AO_MMk{qQ|jVt{ZMmJKZQosAKFu!#qx45@R#(AxV zil`LX&);!kU5xi#%<5!xDy>!@25ORKK)+aQM-k8>yEZ?0Ui-O=S6uNo_Ue%E1=6^0`m z`!Co%>5KV_SyR;P!JVCIDzrCt(Pw;WG2JZ-$vov_ci=4EO*H3J524K3pt%v5e;fe<6?i+4-iyQ$bbaCJ**yFNI&dPX?-Jk@Hcr7!rVo8)Oa5IthU+QAI zjJ#{kx-zc4&C=~T@>Qq}kF`CqOg}Ho)5#9hpSrzZ$%M7IdWmgW_d!JNx7L=TyLy+8vpaW@fRu2A45cEz77;^S8 z{W-?h(swg-`{dmJXW;N?Ss<%6xu1;0_NW1G<7nsbLHv=Z02<$S-kq*j&>1dkSly`O z?hvZO3xI(~zkrfs6VXpn)jlYF66iN-$EOp-IP$SAyrlMX)VmR{Wp&;4e3C$wZtHOf%}hE59t5VRPA_Vkpj(UQRg_9z&^TJ5VQ(rMV$J<)LF z2OPEzvl9q-BeP}p!GP>C-@N@aig)n+N6+z9Np}G$#Dk=VA zQgYLPVmMrDb^4%_^JD(;A+P09!*I6AoUaM&d11H7@eq5rKKSsiwfbS=N)k1qX<)%f z#fVDd;8!LmKg(HPf~%esQyIhdsb%q8kHyEy@TZMvj=A!>nju%l_vF#1E;Zw?q@uXU zZJF$w7|{#p=5(=IBI11uOzN!C4|U+^Ee#TT33O44t2Xa@zcRAg%XSbv_VE zlqhZ9>odrq$U$mfk@?zvF>#Q3S=ruLiO&K4R2x>z>627c15#pbJ@C1eSqOdSLxEqh zGV}o?ervZjFPLmkQ9BT*myFn4Xrgoyfqb8{Hx{F zIP+-=z?R(f|341(uJ+niun@kG zv|)R8Hq-D|)+Fq{M&izZr9^Y7Xoi@gASoJ=eD!o?oy|_SvWdLg#w`4z-dtuWUqnaY z)>Td_BTNBpV(cZ7^+Bb?K`ofVCf3Z3frsLoRGV$k0CO;2oK9|H5YP`E=3 zxhYv^*#%Bd!g;5T7jlP_lbZM1BIiP9S(^7!M*{KS(_`7=sFbU)C1YD?hM*ifsQ2J4 KkM45s)_H(RMK#_4 diff --git a/cgtest.c b/cgtest.c deleted file mode 100644 index f0655b982..000000000 --- a/cgtest.c +++ /dev/null @@ -1,788 +0,0 @@ -/* - * cgtest.c: stub file to compile cmdgen.c in self-test mode - */ - -/* - * Before we #include cmdgen.c, we override some function names for - * test purposes. We do this via #define, so that when we link against - * modules containing the original versions, we don't get a link-time - * symbol clash: - * - * - Calls to get_random_data() are replaced with the diagnostic - * function below, 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. - * - * - The main() defined by cmdgen.c is renamed to cmdgen_main(); in - * this file I define another main() which calls the former - * repeatedly to run tests. - */ -#define get_random_data get_random_data_diagnostic -#define console_get_userpass_input console_get_userpass_input_diagnostic -#define main cmdgen_main -#define ppk_save_default_parameters ppk_save_cgtest_parameters - -#include "cmdgen.c" - -#undef get_random_data -#undef console_get_userpass_input -#undef main - -static bool cgtest_verbose = false; - -const struct ppk_save_parameters ppk_save_cgtest_parameters = { - /* Replacement set of key derivation parameters that make this - * test suite run a bit faster and also add determinism: we don't - * try to auto-scale the number of passes (in case it gets - * different answers twice in the test suite when we were - * expecting two key files to compare equal), and we specify a - * passphrase salt. */ - .fmt_version = 3, - .argon2_flavour = Argon2id, - .argon2_mem = 16, - .argon2_passes_auto = false, - .argon2_passes = 2, - .argon2_parallelism = 1, - .salt = (const uint8_t *)"SameSaltEachTime", - .saltlen = 16, -}; - -/* - * Define the special versions of get_random_data and - * console_get_userpass_input that we need for this test rig. - */ - -char *get_random_data_diagnostic(int len, const char *device) -{ - char *buf = snewn(len, char); - memset(buf, 'x', len); - return buf; -} - -static int nprompts, promptsgot; -static const char *prompts[3]; -SeatPromptResult console_get_userpass_input_diagnostic(prompts_t *p) -{ - size_t i; - SeatPromptResult ret = SPR_OK; - for (i = 0; i < p->n_prompts; i++) { - if (promptsgot < nprompts) { - prompt_set_result(p->prompts[i], prompts[promptsgot++]); - if (cgtest_verbose) - printf(" prompt \"%s\": response \"%s\"\n", - p->prompts[i]->prompt, p->prompts[i]->result->s); - } else { - promptsgot++; /* track number of requests anyway */ - ret = SPR_SW_ABORT("preloaded prompt unavailable in cgtest"); - if (cgtest_verbose) - printf(" prompt \"%s\": no response preloaded\n", - p->prompts[i]->prompt); - } - } - return ret; -} - -#include - -static 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; - if (cgtest_verbose) { - printf("run:"); - for (int i = 0; i < argc; i++) { - static const char okchars[] = - "0123456789abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ%+,-./:=[]^_"; - const char *arg = argv[i]; - - printf(" "); - if (arg[strspn(arg, okchars)]) { - printf("'"); - for (const char *c = argv[i]; *c; c++) { - if (*c == '\'') { - printf("'\\''"); - } else { - putchar(*c); - } - } - printf("'"); - } else { - fputs(arg, stdout); - } - } - printf("\n"); - } - ret = cmdgen_main(argc, argv); - random_clear(); - - 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++; - } - - sfree(argv); -} - -PRINTF_LIKE(3, 4) 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++; -} - -/* - * General-purpose flags word - */ -#define CGT_FLAGS(X) \ - X(CGT_TYPE_KNOWN_EARLY) \ - X(CGT_OPENSSH) \ - X(CGT_SSHCOM) \ - X(CGT_SSH_KEYGEN) \ - X(CGT_ED25519) \ - /* end of list */ - -#define FLAG_SHIFTS(name) name ## _shift, -enum { CGT_FLAGS(FLAG_SHIFTS) CGT_dummy_shift }; -#define FLAG_VALUES(name) name = 1 << name ## _shift, -enum { CGT_FLAGS(FLAG_VALUES) CGT_dummy_flag }; - -char *cleanup_fp(char *s, unsigned flags) -{ - ptrlen pl = ptrlen_from_asciz(s); - static const char separators[] = " \n\t"; - - /* Skip initial key type word if we find one */ - if (ptrlen_startswith(pl, PTRLEN_LITERAL("ssh-"), NULL) || - ptrlen_startswith(pl, PTRLEN_LITERAL("ecdsa-"), NULL)) - ptrlen_get_word(&pl, separators); - - /* Expect two words giving the key length and the hash */ - ptrlen bits = ptrlen_get_word(&pl, separators); - ptrlen hash = ptrlen_get_word(&pl, separators); - - if (flags & CGT_SSH_KEYGEN) { - /* Strip "MD5:" prefix if it's present, and do nothing if it isn't */ - ptrlen_startswith(hash, PTRLEN_LITERAL("MD5:"), &hash); - - if (flags & CGT_ED25519) { - /* OpenSSH ssh-keygen lists ed25519 keys as 256 bits, not 255 */ - if (ptrlen_eq_string(bits, "256")) - bits = PTRLEN_LITERAL("255"); - } - } - - return dupprintf("%.*s %.*s", PTRLEN_PRINTF(bits), PTRLEN_PRINTF(hash)); -} - -char *get_line(char *filename) -{ - FILE *fp; - char *line; - - fp = fopen(filename, "r"); - if (!fp) - return NULL; - line = fgetline(fp); - fclose(fp); - return line; -} - -char *get_fp(char *filename, unsigned flags) -{ - char *orig = get_line(filename); - if (!orig) - return NULL; - char *toret = cleanup_fp(orig, flags); - sfree(orig); - return toret; -} - -PRINTF_LIKE(3, 4) void check_fp(char *filename, char *fp, char *fmt, ...) -{ - char *newfp; - - if (!fp) - return; - - newfp = get_fp(filename, 0); - - 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); -} - -static const struct cgtest_keytype { - const char *name; - unsigned flags; -} cgtest_keytypes[] = { - { "rsa1", CGT_TYPE_KNOWN_EARLY }, - { "dsa", CGT_OPENSSH | CGT_SSHCOM }, - { "rsa", CGT_OPENSSH | CGT_SSHCOM }, - { "ecdsa", CGT_OPENSSH }, - { "ed25519", CGT_OPENSSH | CGT_ED25519 }, -}; - -int main(int argc, char **argv) -{ - int i; - int active[lenof(cgtest_keytypes)], active_value; - bool remove_files = true; - - active_value = 0; - for (i = 0; i < lenof(cgtest_keytypes); i++) - active[i] = active_value; - - while (--argc > 0) { - ptrlen arg = ptrlen_from_asciz(*++argv); - if (ptrlen_eq_string(arg, "-v") || - ptrlen_eq_string(arg, "--verbose")) { - cgtest_verbose = true; - } else if (ptrlen_eq_string(arg, "--keep")) { - remove_files = false; - } else if (ptrlen_eq_string(arg, "--help")) { - printf("usage: cgtest [options] [key types]\n"); - printf("options: -v, --verbose " - "print more output during tests\n"); - printf(" --keep " - "do not delete the temporary output files\n"); - printf(" --help " - "display this help text\n"); - printf("key types: "); - for (i = 0; i < lenof(cgtest_keytypes); i++) - printf("%s%s", i ? ", " : "", cgtest_keytypes[i].name); - printf("\n"); - return 0; - } else if (!ptrlen_startswith(arg, PTRLEN_LITERAL("-"), NULL)) { - for (i = 0; i < lenof(cgtest_keytypes); i++) - if (ptrlen_eq_string(arg, cgtest_keytypes[i].name)) - break; - if (i == lenof(cgtest_keytypes)) { - fprintf(stderr, "cgtest: unrecognised key type '%.*s'\n", - PTRLEN_PRINTF(arg)); - return 1; - } - active_value = 1; /* disables all keys not explicitly enabled */ - active[i] = active_value; - } else { - fprintf(stderr, "cgtest: unrecognised option '%.*s'\n", - PTRLEN_PRINTF(arg)); - return 1; - } - } - - passes = fails = 0; - - for (i = 0; i < lenof(cgtest_keytypes); i++) { - if (active[i] != active_value) - continue; - - const struct cgtest_keytype *keytype = &cgtest_keytypes[i]; - bool supports_openssh = keytype->flags & CGT_OPENSSH; - bool supports_sshcom = keytype->flags & CGT_SSHCOM; - bool type_known_early = keytype->flags & CGT_TYPE_KNOWN_EARLY; - - char filename[128], osfilename[128], scfilename[128]; - char pubfilename[128], tmpfilename1[128], tmpfilename2[128]; - char *fps[SSH_N_FPTYPES]; - - sprintf(filename, "test-%s.ppk", keytype->name); - sprintf(pubfilename, "test-%s.pub", keytype->name); - sprintf(osfilename, "test-%s.os", keytype->name); - sprintf(scfilename, "test-%s.sc", keytype->name); - sprintf(tmpfilename1, "test-%s.tmp1", keytype->name); - sprintf(tmpfilename2, "test-%s.tmp2", keytype->name); - - /* - * Create an encrypted key. - */ - setup_passphrases("sponge", "sponge", NULL); - test(0, "puttygen", "-t", keytype->name, "-o", filename, NULL); - - /* - * List the public key in OpenSSH format. - */ - setup_passphrases(NULL); - test(0, "puttygen", "-L", filename, "-o", pubfilename, NULL); - for (FingerprintType fptype = 0; fptype < SSH_N_FPTYPES; fptype++) { - const char *fpname = (fptype == SSH_FPTYPE_MD5 ? "md5" : "sha256"); - char *cmdbuf; - char *fp = NULL; - cmdbuf = dupprintf("ssh-keygen -E %s -l -f '%s' > '%s'", - fpname, pubfilename, tmpfilename1); - if (cgtest_verbose) - printf("OpenSSH %s fp check: %s\n", fpname, cmdbuf); - if (system(cmdbuf) || - (fp = get_fp(tmpfilename1, - CGT_SSH_KEYGEN | keytype->flags)) == NULL) { - printf("UNABLE to test fingerprint matching against " - "OpenSSH\n"); - } - sfree(cmdbuf); - if (fp && cgtest_verbose) { - char *line = get_line(tmpfilename1); - printf("OpenSSH %s fp: %s\n", fpname, line); - printf("Cleaned up: %s\n", fp); - sfree(line); - } - fps[fptype] = fp; - } - - /* - * 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); - for (FingerprintType fptype = 0; fptype < SSH_N_FPTYPES; fptype++) { - const char *fpname = (fptype == SSH_FPTYPE_MD5 ? "md5" : "sha256"); - test(0, "puttygen", "-E", fpname, "-l", filename, - "-o", tmpfilename1, NULL); - if (!fps[fptype]) { - /* - * 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. - */ - fps[fptype] = get_fp(tmpfilename1, 0); - } else { - check_fp(tmpfilename1, fps[fptype], "%s initial %s fp", - keytype->name, fpname); - } - } - - /* - * 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. - */ - setup_passphrases(NULL); - test(supports_openssh ? 0 : 1, - "puttygen", "-O", "private-openssh", "-o", osfilename, - filename, NULL); - - if (supports_openssh) { - /* - * List the fingerprint of the OpenSSH-formatted key. - */ - setup_passphrases(NULL); - test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL); - check_fp(tmpfilename1, fps[SSH_FPTYPE_DEFAULT], - "%s openssh clear fp", keytype->name); - - /* - * 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. - */ - setup_passphrases(NULL); - test(supports_sshcom ? 0 : 1, - "puttygen", "-O", "private-sshcom", - "-o", scfilename, filename, NULL); - - if (supports_sshcom) { - /* - * List the fingerprint of the ssh.com-formatted key. - */ - setup_passphrases(NULL); - test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL); - check_fp(tmpfilename1, fps[SSH_FPTYPE_DEFAULT], - "%s ssh.com clear fp", keytype->name); - - /* - * 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 (supports_openssh && supports_sshcom) { - /* - * 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", keytype->name); - - /* - * 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", keytype->name); - - /* - * 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", keytype->name); - } - - /* - * 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. - */ - if (!supports_openssh && type_known_early) { - /* We'll know far enough in advance that this combination - * is going to fail that we never ask for the passphrase */ - setup_passphrases(NULL); - } else { - setup_passphrases("sponge2", NULL); - } - - test(supports_openssh ? 0 : 1, - "puttygen", "-O", "private-openssh", "-o", osfilename, - filename, NULL); - - if (supports_openssh) { - /* - * List the fingerprint of the OpenSSH-formatted key. - */ - setup_passphrases("sponge2", NULL); - test(0, "puttygen", "-l", osfilename, "-o", tmpfilename1, NULL); - check_fp(tmpfilename1, fps[SSH_FPTYPE_DEFAULT], - "%s openssh encrypted fp", keytype->name); - - /* - * 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 (!supports_sshcom && type_known_early) { - /* We'll know far enough in advance that this combination - * is going to fail that we never ask for the passphrase */ - setup_passphrases(NULL); - } else { - setup_passphrases("sponge2", NULL); - } - - test(supports_sshcom ? 0 : 1, - "puttygen", "-O", "private-sshcom", "-o", scfilename, - filename, NULL); - - if (supports_sshcom) { - /* - * List the fingerprint of the ssh.com-formatted key. - */ - setup_passphrases("sponge2", NULL); - test(0, "puttygen", "-l", scfilename, "-o", tmpfilename1, NULL); - check_fp(tmpfilename1, fps[SSH_FPTYPE_DEFAULT], - "%s ssh.com encrypted fp", keytype->name); - - /* - * 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 (supports_openssh && supports_sshcom) { - /* - * 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", keytype->name); - - /* - * 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", keytype->name); - - /* - * 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", keytype->name); - } - - /* - * 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); - - for (FingerprintType fptype = 0; fptype < SSH_N_FPTYPES; fptype++) - sfree(fps[fptype]); - - if (remove_files) { - remove(filename); - remove(pubfilename); - remove(osfilename); - remove(scfilename); - remove(tmpfilename1); - remove(tmpfilename2); - } - } - printf("%d passes, %d fails\n", passes, fails); - return fails == 0 ? 0 : 1; -} diff --git a/charset/CMakeLists.txt b/charset/CMakeLists.txt deleted file mode 100644 index 4ff5bb8a9..000000000 --- a/charset/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -include(FindPerl) -if(NOT PERL_EXECUTABLE) - message(FATAL_ERROR "Perl is required to autogenerate sbcsdat.c") -endif() - -set(GENERATED_SBCSDAT_C ${GENERATED_SOURCES_DIR}/sbcsdat.c) -add_custom_command(OUTPUT ${GENERATED_SBCSDAT_C}.tmp - COMMAND ${PERL_EXECUTABLE} ${CMAKE_SOURCE_DIR}/charset/sbcsgen.pl - -o ${GENERATED_SBCSDAT_C}.tmp - DEPENDS ${CMAKE_SOURCE_DIR}/charset/sbcsgen.pl - ${CMAKE_SOURCE_DIR}/charset/sbcs.dat) -add_custom_target(generated_sbcsdat_c - BYPRODUCTS ${GENERATED_SBCSDAT_C} - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${GENERATED_SBCSDAT_C}.tmp ${GENERATED_SBCSDAT_C} - DEPENDS ${GENERATED_SBCSDAT_C}.tmp - COMMENT "Updating sbcsdat.c") - -add_library(charset STATIC - fromucs.c - localenc.c - macenc.c - mimeenc.c - sbcs.c - ${GENERATED_SBCSDAT_C} - slookup.c - toucs.c - utf8.c - xenc.c) -add_dependencies(charset generated_sbcsdat_c) diff --git a/charset/README b/charset/README deleted file mode 100644 index 16b540a76..000000000 --- a/charset/README +++ /dev/null @@ -1,15 +0,0 @@ -This subdirectory contains a general character-set conversion -library, used in the Unix port of PuTTY, and available for use in -other ports if it should happen to be useful. - -This is a variant of a library that's currently used in some other -programs such as Timber and Halibut. At some future date, we would -like to merge the two libraries, so that all programs use the same -libcharset. - -It is therefore a _strong_ design goal that this library should remain -perfectly general, and not tied to particulars of PuTTY. It must not -reference any code outside its own subdirectory; it should not have -PuTTY-specific helper routines added to it unless they can be -documented in a general manner which might make them useful in other -circumstances as well. diff --git a/charset/charset.h b/charset/charset.h deleted file mode 100644 index a967aab91..000000000 --- a/charset/charset.h +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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/charset/enum.c b/charset/enum.c deleted file mode 100644 index f659e8463..000000000 --- a/charset/enum.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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/charset/fromucs.c b/charset/fromucs.c deleted file mode 100644 index a4100e3f1..000000000 --- a/charset/fromucs.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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/charset/internal.h b/charset/internal.h deleted file mode 100644 index 32d0f6d5b..000000000 --- a/charset/internal.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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/charset/localenc.c b/charset/localenc.c deleted file mode 100644 index 49719fbea..000000000 --- a/charset/localenc.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * localenc.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/charset/macenc.c b/charset/macenc.c deleted file mode 100644 index cde5fb892..000000000 --- a/charset/macenc.c +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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/charset/mimeenc.c b/charset/mimeenc.c deleted file mode 100644 index 8a0203b3e..000000000 --- a/charset/mimeenc.c +++ /dev/null @@ -1,219 +0,0 @@ -/* - * 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/charset/sbcs.c b/charset/sbcs.c deleted file mode 100644 index 9d9a3c94f..000000000 --- a/charset/sbcs.c +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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/charset/sbcs.dat b/charset/sbcs.dat deleted file mode 100644 index 4fe4a4ade..000000000 --- a/charset/sbcs.dat +++ /dev/null @@ -1,1139 +0,0 @@ - Data file defining single-byte character sets. - - All lines which begin with whitespace are considered comments. - - To generate an SBCS table from a unicode.org mapping table: - - gensbcs() { - wget -q -O - "$1" | tr '\r' '\n' | \ - perl -ne '/^(0x.*)\s+(0x.*)\s+/ and $a[hex $1]=sprintf "%04x", hex $2;' \ - -e 'BEGIN{for($i=0;$i<256;$i++){$a[$i]="XXXX";' \ - -e ' if ($i < 32 or $i == 127) {$a[$i]=sprintf "%04x", $i}}}' \ - -e 'END{for($i=0;$i<256;$i++){printf"%s%s",$a[$i],$i%16==15?"\n":" "}}' - } - - (A couple of noteworthy ickinesses here. For a start, any - undefined characters in the control-code regions (00-1F and 7F) - are assumed to be the Unicode code point corresponding to their - index, since the Mac Roman mapping table declines to define them - but realistically you don't want to be messing with that sort of - thing. Secondly, the Mac mapping tables are shipped with Mac line - endings, so note the `tr' to turn them into something legible to - Perl...) - - Here are the ISO-8859-x tables, generated by this piece of Bourne - shell: - - for i in 1 2 3 4 5 6 7 8 9 10 11 13 14 15 16; do - echo charset CS_ISO8859_$i - gensbcs http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-$i.TXT - echo - done - -charset CS_ISO8859_1 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af -00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf -00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf -00d0 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 00dd 00de 00df -00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef -00f0 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 00fd 00fe 00ff - -charset CS_ISO8859_2 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 0104 02d8 0141 00a4 013d 015a 00a7 00a8 0160 015e 0164 0179 00ad 017d 017b -00b0 0105 02db 0142 00b4 013e 015b 02c7 00b8 0161 015f 0165 017a 02dd 017e 017c -0154 00c1 00c2 0102 00c4 0139 0106 00c7 010c 00c9 0118 00cb 011a 00cd 00ce 010e -0110 0143 0147 00d3 00d4 0150 00d6 00d7 0158 016e 00da 0170 00dc 00dd 0162 00df -0155 00e1 00e2 0103 00e4 013a 0107 00e7 010d 00e9 0119 00eb 011b 00ed 00ee 010f -0111 0144 0148 00f3 00f4 0151 00f6 00f7 0159 016f 00fa 0171 00fc 00fd 0163 02d9 - -charset CS_ISO8859_3 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 0126 02d8 00a3 00a4 XXXX 0124 00a7 00a8 0130 015e 011e 0134 00ad XXXX 017b -00b0 0127 00b2 00b3 00b4 00b5 0125 00b7 00b8 0131 015f 011f 0135 00bd XXXX 017c -00c0 00c1 00c2 XXXX 00c4 010a 0108 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf -XXXX 00d1 00d2 00d3 00d4 0120 00d6 00d7 011c 00d9 00da 00db 00dc 016c 015c 00df -00e0 00e1 00e2 XXXX 00e4 010b 0109 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef -XXXX 00f1 00f2 00f3 00f4 0121 00f6 00f7 011d 00f9 00fa 00fb 00fc 016d 015d 02d9 - -charset CS_ISO8859_4 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 0104 0138 0156 00a4 0128 013b 00a7 00a8 0160 0112 0122 0166 00ad 017d 00af -00b0 0105 02db 0157 00b4 0129 013c 02c7 00b8 0161 0113 0123 0167 014a 017e 014b -0100 00c1 00c2 00c3 00c4 00c5 00c6 012e 010c 00c9 0118 00cb 0116 00cd 00ce 012a -0110 0145 014c 0136 00d4 00d5 00d6 00d7 00d8 0172 00da 00db 00dc 0168 016a 00df -0101 00e1 00e2 00e3 00e4 00e5 00e6 012f 010d 00e9 0119 00eb 0117 00ed 00ee 012b -0111 0146 014d 0137 00f4 00f5 00f6 00f7 00f8 0173 00fa 00fb 00fc 0169 016b 02d9 - -charset CS_ISO8859_5 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 0401 0402 0403 0404 0405 0406 0407 0408 0409 040a 040b 040c 00ad 040e 040f -0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f -0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f -0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f -0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 044f -2116 0451 0452 0453 0454 0455 0456 0457 0458 0459 045a 045b 045c 00a7 045e 045f - -charset CS_ISO8859_6 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 XXXX XXXX XXXX 00a4 XXXX XXXX XXXX XXXX XXXX XXXX XXXX 060c 00ad XXXX XXXX -XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX 061b XXXX XXXX XXXX 061f -XXXX 0621 0622 0623 0624 0625 0626 0627 0628 0629 062a 062b 062c 062d 062e 062f -0630 0631 0632 0633 0634 0635 0636 0637 0638 0639 063a XXXX XXXX XXXX XXXX XXXX -0640 0641 0642 0643 0644 0645 0646 0647 0648 0649 064a 064b 064c 064d 064e 064f -0650 0651 0652 XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX - -charset CS_ISO8859_7 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 2018 2019 00a3 XXXX XXXX 00a6 00a7 00a8 00a9 XXXX 00ab 00ac 00ad XXXX 2015 -00b0 00b1 00b2 00b3 0384 0385 0386 00b7 0388 0389 038a 00bb 038c 00bd 038e 038f -0390 0391 0392 0393 0394 0395 0396 0397 0398 0399 039a 039b 039c 039d 039e 039f -03a0 03a1 XXXX 03a3 03a4 03a5 03a6 03a7 03a8 03a9 03aa 03ab 03ac 03ad 03ae 03af -03b0 03b1 03b2 03b3 03b4 03b5 03b6 03b7 03b8 03b9 03ba 03bb 03bc 03bd 03be 03bf -03c0 03c1 03c2 03c3 03c4 03c5 03c6 03c7 03c8 03c9 03ca 03cb 03cc 03cd 03ce XXXX - -charset CS_ISO8859_8 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 XXXX 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00d7 00ab 00ac 00ad 00ae 00af -00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00f7 00bb 00bc 00bd 00be XXXX -XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX -XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX 2017 -05d0 05d1 05d2 05d3 05d4 05d5 05d6 05d7 05d8 05d9 05da 05db 05dc 05dd 05de 05df -05e0 05e1 05e2 05e3 05e4 05e5 05e6 05e7 05e8 05e9 05ea XXXX XXXX 200e 200f XXXX - -charset CS_ISO8859_9 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af -00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf -00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf -011e 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 0130 015e 00df -00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef -011f 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 0131 015f 00ff - -charset CS_ISO8859_10 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 0104 0112 0122 012a 0128 0136 00a7 013b 0110 0160 0166 017d 00ad 016a 014a -00b0 0105 0113 0123 012b 0129 0137 00b7 013c 0111 0161 0167 017e 2015 016b 014b -0100 00c1 00c2 00c3 00c4 00c5 00c6 012e 010c 00c9 0118 00cb 0116 00cd 00ce 00cf -00d0 0145 014c 00d3 00d4 00d5 00d6 0168 00d8 0172 00da 00db 00dc 00dd 00de 00df -0101 00e1 00e2 00e3 00e4 00e5 00e6 012f 010d 00e9 0119 00eb 0117 00ed 00ee 00ef -00f0 0146 014d 00f3 00f4 00f5 00f6 0169 00f8 0173 00fa 00fb 00fc 00fd 00fe 0138 - -charset CS_ISO8859_11 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 0e01 0e02 0e03 0e04 0e05 0e06 0e07 0e08 0e09 0e0a 0e0b 0e0c 0e0d 0e0e 0e0f -0e10 0e11 0e12 0e13 0e14 0e15 0e16 0e17 0e18 0e19 0e1a 0e1b 0e1c 0e1d 0e1e 0e1f -0e20 0e21 0e22 0e23 0e24 0e25 0e26 0e27 0e28 0e29 0e2a 0e2b 0e2c 0e2d 0e2e 0e2f -0e30 0e31 0e32 0e33 0e34 0e35 0e36 0e37 0e38 0e39 0e3a XXXX XXXX XXXX XXXX 0e3f -0e40 0e41 0e42 0e43 0e44 0e45 0e46 0e47 0e48 0e49 0e4a 0e4b 0e4c 0e4d 0e4e 0e4f -0e50 0e51 0e52 0e53 0e54 0e55 0e56 0e57 0e58 0e59 0e5a 0e5b XXXX XXXX XXXX XXXX - -charset CS_ISO8859_13 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 201d 00a2 00a3 00a4 201e 00a6 00a7 00d8 00a9 0156 00ab 00ac 00ad 00ae 00c6 -00b0 00b1 00b2 00b3 201c 00b5 00b6 00b7 00f8 00b9 0157 00bb 00bc 00bd 00be 00e6 -0104 012e 0100 0106 00c4 00c5 0118 0112 010c 00c9 0179 0116 0122 0136 012a 013b -0160 0143 0145 00d3 014c 00d5 00d6 00d7 0172 0141 015a 016a 00dc 017b 017d 00df -0105 012f 0101 0107 00e4 00e5 0119 0113 010d 00e9 017a 0117 0123 0137 012b 013c -0161 0144 0146 00f3 014d 00f5 00f6 00f7 0173 0142 015b 016b 00fc 017c 017e 2019 - -charset CS_ISO8859_14 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 1e02 1e03 00a3 010a 010b 1e0a 00a7 1e80 00a9 1e82 1e0b 1ef2 00ad 00ae 0178 -1e1e 1e1f 0120 0121 1e40 1e41 00b6 1e56 1e81 1e57 1e83 1e60 1ef3 1e84 1e85 1e61 -00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf -0174 00d1 00d2 00d3 00d4 00d5 00d6 1e6a 00d8 00d9 00da 00db 00dc 00dd 0176 00df -00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef -0175 00f1 00f2 00f3 00f4 00f5 00f6 1e6b 00f8 00f9 00fa 00fb 00fc 00fd 0177 00ff - -charset CS_ISO8859_15 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 00a1 00a2 00a3 20ac 00a5 0160 00a7 0161 00a9 00aa 00ab 00ac 00ad 00ae 00af -00b0 00b1 00b2 00b3 017d 00b5 00b6 00b7 017e 00b9 00ba 00bb 0152 0153 0178 00bf -00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf -00d0 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 00dd 00de 00df -00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef -00f0 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 00fd 00fe 00ff - -charset CS_ISO8859_16 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 0104 0105 0141 20ac 201e 0160 00a7 0161 00a9 0218 00ab 0179 00ad 017a 017b -00b0 00b1 010c 0142 017d 201d 00b6 00b7 017e 010d 0219 00bb 0152 0153 0178 017c -00c0 00c1 00c2 0102 00c4 0106 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf -0110 0143 00d2 00d3 00d4 0150 00d6 015a 0170 00d9 00da 00db 00dc 0118 021a 00df -00e0 00e1 00e2 0103 00e4 0107 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef -0111 0144 00f2 00f3 00f4 0151 00f6 015b 0171 00f9 00fa 00fb 00fc 0119 021b 00ff - - Some X fonts are encoded in a variant form of ISO8859-1: - everything above 0x20 (space) is as normal, but the first 32 - characters contain the VT100 line drawing glyphs as they would - appear from positions 0x5F to 0x7E inclusive. Here is the modified - ISO8859-1 code table. - - Since this table contains a few duplicated positions, we use the - `sortpriority' hint to indicate that things in the main part of - the code table (0x20-0xFF) should be generated preferentially when - converting _from_ Unicode. Hence, U+00b0 (for example) will yield - 0xb0 rather than 0x07. - -charset CS_ISO8859_1_X11 -sortpriority 00-1F -1 -0020 2666 2592 2409 240c 240d 240a 00b0 00b1 2424 240b 2518 2510 250c 2514 253c -23ba 23bb 2500 23bc 23bd 251c 2524 2534 252c 2502 2264 2265 03c0 2260 00a3 00b7 -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af -00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf -00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf -00d0 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 00dd 00de 00df -00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef -00f0 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 00fd 00fe 00ff - - Here are some PC (old DOS) code pages, generated by this piece of - Bourne shell: - - for i in 437 850 866; do - echo charset CS_CP$i - gensbcs http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP$i.TXT - echo - done - -charset CS_CP437 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c7 00fc 00e9 00e2 00e4 00e0 00e5 00e7 00ea 00eb 00e8 00ef 00ee 00ec 00c4 00c5 -00c9 00e6 00c6 00f4 00f6 00f2 00fb 00f9 00ff 00d6 00dc 00a2 00a3 00a5 20a7 0192 -00e1 00ed 00f3 00fa 00f1 00d1 00aa 00ba 00bf 2310 00ac 00bd 00bc 00a1 00ab 00bb -2591 2592 2593 2502 2524 2561 2562 2556 2555 2563 2551 2557 255d 255c 255b 2510 -2514 2534 252c 251c 2500 253c 255e 255f 255a 2554 2569 2566 2560 2550 256c 2567 -2568 2564 2565 2559 2558 2552 2553 256b 256a 2518 250c 2588 2584 258c 2590 2580 -03b1 00df 0393 03c0 03a3 03c3 00b5 03c4 03a6 0398 03a9 03b4 221e 03c6 03b5 2229 -2261 00b1 2265 2264 2320 2321 00f7 2248 00b0 2219 00b7 221a 207f 00b2 25a0 00a0 - -charset CS_CP850 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c7 00fc 00e9 00e2 00e4 00e0 00e5 00e7 00ea 00eb 00e8 00ef 00ee 00ec 00c4 00c5 -00c9 00e6 00c6 00f4 00f6 00f2 00fb 00f9 00ff 00d6 00dc 00f8 00a3 00d8 00d7 0192 -00e1 00ed 00f3 00fa 00f1 00d1 00aa 00ba 00bf 00ae 00ac 00bd 00bc 00a1 00ab 00bb -2591 2592 2593 2502 2524 00c1 00c2 00c0 00a9 2563 2551 2557 255d 00a2 00a5 2510 -2514 2534 252c 251c 2500 253c 00e3 00c3 255a 2554 2569 2566 2560 2550 256c 00a4 -00f0 00d0 00ca 00cb 00c8 0131 00cd 00ce 00cf 2518 250c 2588 2584 00a6 00cc 2580 -00d3 00df 00d4 00d2 00f5 00d5 00b5 00fe 00de 00da 00db 00d9 00fd 00dd 00af 00b4 -00ad 00b1 2017 00be 00b6 00a7 00f7 00b8 00b0 00a8 00b7 00b9 00b3 00b2 25a0 00a0 - -charset CS_CP866 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f -0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f -0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f -2591 2592 2593 2502 2524 2561 2562 2556 2555 2563 2551 2557 255d 255c 255b 2510 -2514 2534 252c 251c 2500 253c 255e 255f 255a 2554 2569 2566 2560 2550 256c 2567 -2568 2564 2565 2559 2558 2552 2553 256b 256a 2518 250c 2588 2584 258c 2590 2580 -0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 044f -0401 0451 0404 0454 0407 0457 040e 045e 00b0 2219 00b7 221a 2116 00a4 25a0 00a0 - - Another old DOS code page, submitted by a user and checked against - the translation table at - http://msdn.microsoft.com/en-us/goglobal/cc305161.aspx . - -charset CS_CP852 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c7 00fc 00e9 00e2 00e4 016f 0107 00e7 0142 00eb 0150 0151 00ee 0179 00c4 0106 -00c9 0139 013a 00f4 00f6 013d 013e 015a 015b 00d6 00dc 0164 0165 0141 00d7 010d -00e1 00ed 00f3 00fa 0104 0105 017d 017e 0118 0119 00ac 017a 010c 015f 00ab 00bb -2591 2592 2593 2502 2524 00c1 00c2 011a 015e 2563 2551 2557 255d 017b 017c 2510 -2514 2534 252c 251c 2500 253c 0102 0103 255a 2554 2569 2566 2560 2550 256c 00a4 -0111 0110 010e 00cb 010f 0147 00cd 00ce 011b 2518 250c 2588 2584 0162 016e 2580 -00d3 00df 00d4 0143 0144 0148 0160 0161 0154 00da 0155 0170 00fd 00dd 0163 00b4 -00ad 02dd 02db 02c7 02d8 00a7 00f7 00b8 00b0 00a8 02d9 0171 0158 0159 25a0 00a0 - - Here are some Windows code pages, generated by this piece of - Bourne shell: - - for i in 1250 1251 1252 1253 1254 1255 1256 1257 1258; do - echo charset CS_CP$i - gensbcs http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP$i.TXT - echo - done - -charset CS_CP1250 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -20ac XXXX 201a XXXX 201e 2026 2020 2021 XXXX 2030 0160 2039 015a 0164 017d 0179 -XXXX 2018 2019 201c 201d 2022 2013 2014 XXXX 2122 0161 203a 015b 0165 017e 017a -00a0 02c7 02d8 0141 00a4 0104 00a6 00a7 00a8 00a9 015e 00ab 00ac 00ad 00ae 017b -00b0 00b1 02db 0142 00b4 00b5 00b6 00b7 00b8 0105 015f 00bb 013d 02dd 013e 017c -0154 00c1 00c2 0102 00c4 0139 0106 00c7 010c 00c9 0118 00cb 011a 00cd 00ce 010e -0110 0143 0147 00d3 00d4 0150 00d6 00d7 0158 016e 00da 0170 00dc 00dd 0162 00df -0155 00e1 00e2 0103 00e4 013a 0107 00e7 010d 00e9 0119 00eb 011b 00ed 00ee 010f -0111 0144 0148 00f3 00f4 0151 00f6 00f7 0159 016f 00fa 0171 00fc 00fd 0163 02d9 - -charset CS_CP1251 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0402 0403 201a 0453 201e 2026 2020 2021 20ac 2030 0409 2039 040a 040c 040b 040f -0452 2018 2019 201c 201d 2022 2013 2014 XXXX 2122 0459 203a 045a 045c 045b 045f -00a0 040e 045e 0408 00a4 0490 00a6 00a7 0401 00a9 0404 00ab 00ac 00ad 00ae 0407 -00b0 00b1 0406 0456 0491 00b5 00b6 00b7 0451 2116 0454 00bb 0458 0405 0455 0457 -0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f -0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f -0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f -0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 044f - -charset CS_CP1252 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -20ac XXXX 201a 0192 201e 2026 2020 2021 02c6 2030 0160 2039 0152 XXXX 017d XXXX -XXXX 2018 2019 201c 201d 2022 2013 2014 02dc 2122 0161 203a 0153 XXXX 017e 0178 -00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af -00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf -00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf -00d0 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 00dd 00de 00df -00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef -00f0 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 00fd 00fe 00ff - -charset CS_CP1253 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -20ac XXXX 201a 0192 201e 2026 2020 2021 XXXX 2030 XXXX 2039 XXXX XXXX XXXX XXXX -XXXX 2018 2019 201c 201d 2022 2013 2014 XXXX 2122 XXXX 203a XXXX XXXX XXXX XXXX -00a0 0385 0386 00a3 00a4 00a5 00a6 00a7 00a8 00a9 XXXX 00ab 00ac 00ad 00ae 2015 -00b0 00b1 00b2 00b3 0384 00b5 00b6 00b7 0388 0389 038a 00bb 038c 00bd 038e 038f -0390 0391 0392 0393 0394 0395 0396 0397 0398 0399 039a 039b 039c 039d 039e 039f -03a0 03a1 XXXX 03a3 03a4 03a5 03a6 03a7 03a8 03a9 03aa 03ab 03ac 03ad 03ae 03af -03b0 03b1 03b2 03b3 03b4 03b5 03b6 03b7 03b8 03b9 03ba 03bb 03bc 03bd 03be 03bf -03c0 03c1 03c2 03c3 03c4 03c5 03c6 03c7 03c8 03c9 03ca 03cb 03cc 03cd 03ce XXXX - -charset CS_CP1254 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -20ac XXXX 201a 0192 201e 2026 2020 2021 02c6 2030 0160 2039 0152 XXXX XXXX XXXX -XXXX 2018 2019 201c 201d 2022 2013 2014 02dc 2122 0161 203a 0153 XXXX XXXX 0178 -00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af -00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf -00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf -011e 00d1 00d2 00d3 00d4 00d5 00d6 00d7 00d8 00d9 00da 00db 00dc 0130 015e 00df -00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef -011f 00f1 00f2 00f3 00f4 00f5 00f6 00f7 00f8 00f9 00fa 00fb 00fc 0131 015f 00ff - -charset CS_CP1255 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -20ac XXXX 201a 0192 201e 2026 2020 2021 02c6 2030 XXXX 2039 XXXX XXXX XXXX XXXX -XXXX 2018 2019 201c 201d 2022 2013 2014 02dc 2122 XXXX 203a XXXX XXXX XXXX XXXX -00a0 00a1 00a2 00a3 20aa 00a5 00a6 00a7 00a8 00a9 00d7 00ab 00ac 00ad 00ae 00af -00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00f7 00bb 00bc 00bd 00be 00bf -05b0 05b1 05b2 05b3 05b4 05b5 05b6 05b7 05b8 05b9 XXXX 05bb 05bc 05bd 05be 05bf -05c0 05c1 05c2 05c3 05f0 05f1 05f2 05f3 05f4 XXXX XXXX XXXX XXXX XXXX XXXX XXXX -05d0 05d1 05d2 05d3 05d4 05d5 05d6 05d7 05d8 05d9 05da 05db 05dc 05dd 05de 05df -05e0 05e1 05e2 05e3 05e4 05e5 05e6 05e7 05e8 05e9 05ea XXXX XXXX 200e 200f XXXX - -charset CS_CP1256 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -20ac 067e 201a 0192 201e 2026 2020 2021 02c6 2030 0679 2039 0152 0686 0698 0688 -06af 2018 2019 201c 201d 2022 2013 2014 06a9 2122 0691 203a 0153 200c 200d 06ba -00a0 060c 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 06be 00ab 00ac 00ad 00ae 00af -00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 061b 00bb 00bc 00bd 00be 061f -06c1 0621 0622 0623 0624 0625 0626 0627 0628 0629 062a 062b 062c 062d 062e 062f -0630 0631 0632 0633 0634 0635 0636 00d7 0637 0638 0639 063a 0640 0641 0642 0643 -00e0 0644 00e2 0645 0646 0647 0648 00e7 00e8 00e9 00ea 00eb 0649 064a 00ee 00ef -064b 064c 064d 064e 00f4 064f 0650 00f7 0651 00f9 0652 00fb 00fc 200e 200f 06d2 - -charset CS_CP1257 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -20ac XXXX 201a XXXX 201e 2026 2020 2021 XXXX 2030 XXXX 2039 XXXX 00a8 02c7 00b8 -XXXX 2018 2019 201c 201d 2022 2013 2014 XXXX 2122 XXXX 203a XXXX 00af 02db XXXX -00a0 XXXX 00a2 00a3 00a4 XXXX 00a6 00a7 00d8 00a9 0156 00ab 00ac 00ad 00ae 00c6 -00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00f8 00b9 0157 00bb 00bc 00bd 00be 00e6 -0104 012e 0100 0106 00c4 00c5 0118 0112 010c 00c9 0179 0116 0122 0136 012a 013b -0160 0143 0145 00d3 014c 00d5 00d6 00d7 0172 0141 015a 016a 00dc 017b 017d 00df -0105 012f 0101 0107 00e4 00e5 0119 0113 010d 00e9 017a 0117 0123 0137 012b 013c -0161 0144 0146 00f3 014d 00f5 00f6 00f7 0173 0142 015b 016b 00fc 017c 017e 02d9 - -charset CS_CP1258 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -20ac XXXX 201a 0192 201e 2026 2020 2021 02c6 2030 XXXX 2039 0152 XXXX XXXX XXXX -XXXX 2018 2019 201c 201d 2022 2013 2014 02dc 2122 XXXX 203a 0153 XXXX XXXX 0178 -00a0 00a1 00a2 00a3 00a4 00a5 00a6 00a7 00a8 00a9 00aa 00ab 00ac 00ad 00ae 00af -00b0 00b1 00b2 00b3 00b4 00b5 00b6 00b7 00b8 00b9 00ba 00bb 00bc 00bd 00be 00bf -00c0 00c1 00c2 0102 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 0300 00cd 00ce 00cf -0110 00d1 0309 00d3 00d4 01a0 00d6 00d7 00d8 00d9 00da 00db 00dc 01af 0303 00df -00e0 00e1 00e2 0103 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 0301 00ed 00ee 00ef -0111 00f1 0323 00f3 00f4 01a1 00f6 00f7 00f8 00f9 00fa 00fb 00fc 01b0 20ab 00ff - - KOI8-R, generated by this code: - - { echo charset CS_KOI8_R; - gensbcs http://www.unicode.org/Public/MAPPINGS/VENDORS/MISC/KOI8-R.TXT; } - -charset CS_KOI8_R -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -2500 2502 250c 2510 2514 2518 251c 2524 252c 2534 253c 2580 2584 2588 258c 2590 -2591 2592 2593 2320 25a0 2219 221a 2248 2264 2265 00a0 2321 00b0 00b2 00b7 00f7 -2550 2551 2552 0451 2553 2554 2555 2556 2557 2558 2559 255a 255b 255c 255d 255e -255f 2560 2561 0401 2562 2563 2564 2565 2566 2567 2568 2569 256a 256b 256c 00a9 -044e 0430 0431 0446 0434 0435 0444 0433 0445 0438 0439 043a 043b 043c 043d 043e -043f 044f 0440 0441 0442 0443 0436 0432 044c 044b 0437 0448 044d 0449 0447 044a -042e 0410 0411 0426 0414 0415 0424 0413 0425 0418 0419 041a 041b 041c 041d 041e -041f 042f 0420 0421 0422 0423 0416 0412 042c 042b 0417 0428 042d 0429 0427 042a - - KOI8-U: I can't find an easily machine-processable mapping table - for this one, so I've created it by hand-editing the KOI8-R - mapping table in accordance with the list of differences specified - in RFC2319. Note that RFC2319 has an apparent error: position B4 - is listed as U+0404 in the main character set list, but as U+0403 - in Appendix A (differences from KOI8-R). Both agree that it should - be CYRILLIC CAPITAL LETTER UKRAINIAN IE, however, and the Unicode - character database says that therefore U+0404 is the correct value. - -charset CS_KOI8_U -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -2500 2502 250c 2510 2514 2518 251c 2524 252c 2534 253c 2580 2584 2588 258c 2590 -2591 2592 2593 2320 25a0 2219 221a 2248 2264 2265 00a0 2321 00b0 00b2 00b7 00f7 -2550 2551 2552 0451 0454 2554 0456 0457 2557 2558 2559 255a 255b 0491 255d 255e -255f 2560 2561 0401 0404 2563 0406 0407 2566 2567 2568 2569 256a 0490 256c 00a9 -044e 0430 0431 0446 0434 0435 0444 0433 0445 0438 0439 043a 043b 043c 043d 043e -043f 044f 0440 0441 0442 0443 0436 0432 044c 044b 0437 0448 044d 0449 0447 044a -042e 0410 0411 0426 0414 0415 0424 0413 0425 0418 0419 041a 041b 041c 041d 041e -041f 042f 0420 0421 0422 0423 0416 0412 042c 042b 0417 0428 042d 0429 0427 042a - - Various Mac character sets, generated by: - - for i in ROMAN TURKISH CROATIAN ICELAND ROMANIAN GREEK CYRILLIC THAI \ - CENTEURO SYMBOL DINGBATS; do - echo charset CS_MAC_$i - gensbcs http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/$i.TXT | \ - sed s/f8a0/XXXX/ - echo - done - - The code point F8FF at position F0 in Mac OS Roman an interesting - one. In Unicode, it's the last of the Private Use section. The - mapping table states that it should be an Apple logo. I suppose we - should just leave it as it is; there's bound to be some software out - there that understands U+F8FF to be an Apple logo! - - Code point F8A0 at position F5 in Mac OS Turkish is actually just an - undefined character, so we make it properly undefined. - - Many of the positions 80-9F in Mac OS Thai are for presentation - forms of other characters. When converting from Unicode, we use - `sortpriority' to avoid them. - - Positions E2-E4 in Mac OS Symbol are for sans-serif variants of - other characters. Similarly, we avoid them. - -charset CS_MAC_ROMAN -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 -00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc -2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 -221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8 -00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 -2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 20ac 2039 203a fb01 fb02 -2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 -f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 - -charset CS_MAC_TURKISH -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 -00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc -2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 -221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8 -00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 -2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 011e 011f 0130 0131 015e 015f -2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 -f8ff 00d2 00da 00db 00d9 XXXX 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 - -charset CS_MAC_CROATIAN -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 -00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc -2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 0160 2122 00b4 00a8 2260 017d 00d8 -221e 00b1 2264 2265 2206 00b5 2202 2211 220f 0161 222b 00aa 00ba 03a9 017e 00f8 -00bf 00a1 00ac 221a 0192 2248 0106 00ab 010c 2026 00a0 00c0 00c3 00d5 0152 0153 -0110 2014 201c 201d 2018 2019 00f7 25ca f8ff 00a9 2044 20ac 2039 203a 00c6 00bb -2013 00b7 201a 201e 2030 00c2 0107 00c1 010d 00c8 00cd 00ce 00cf 00cc 00d3 00d4 -0111 00d2 00da 00db 00d9 0131 02c6 02dc 00af 03c0 00cb 02da 00b8 00ca 00e6 02c7 - -charset CS_MAC_ICELAND -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 -00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc -00dd 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 -221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8 -00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 -2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 20ac 00d0 00f0 00de 00fe -00fd 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 -f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 - -charset CS_MAC_ROMANIAN -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 -00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc -2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 0102 0218 -221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 0103 0219 -00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 -2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 20ac 2039 203a 021a 021b -2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 -f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 - -charset CS_MAC_GREEK -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c4 00b9 00b2 00c9 00b3 00d6 00dc 0385 00e0 00e2 00e4 0384 00a8 00e7 00e9 00e8 -00ea 00eb 00a3 2122 00ee 00ef 2022 00bd 2030 00f4 00f6 00a6 20ac 00f9 00fb 00fc -2020 0393 0394 0398 039b 039e 03a0 00df 00ae 00a9 03a3 03aa 00a7 2260 00b0 00b7 -0391 00b1 2264 2265 00a5 0392 0395 0396 0397 0399 039a 039c 03a6 03ab 03a8 03a9 -03ac 039d 00ac 039f 03a1 2248 03a4 00ab 00bb 2026 00a0 03a5 03a7 0386 0388 0153 -2013 2015 201c 201d 2018 2019 00f7 0389 038a 038c 038e 03ad 03ae 03af 03cc 038f -03cd 03b1 03b2 03c8 03b4 03b5 03c6 03b3 03b7 03b9 03be 03ba 03bb 03bc 03bd 03bf -03c0 03ce 03c1 03c3 03c4 03b8 03c9 03c2 03c7 03c5 03b6 03ca 03cb 0390 03b0 00ad - -charset CS_MAC_CYRILLIC -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f -0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f -2020 00b0 0490 00a3 00a7 2022 00b6 0406 00ae 00a9 2122 0402 0452 2260 0403 0453 -221e 00b1 2264 2265 0456 00b5 0491 0408 0404 0454 0407 0457 0409 0459 040a 045a -0458 0405 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 040b 045b 040c 045c 0455 -2013 2014 201c 201d 2018 2019 00f7 201e 040e 045e 040f 045f 2116 0401 0451 044f -0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f -0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 20ac - -charset CS_MAC_THAI -sortpriority 83-8C -1 -sortpriority 8F-8F -1 -sortpriority 92-9C -1 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00ab 00bb 2026 0e48 0e49 0e4a 0e4b 0e4c 0e48 0e49 0e4a 0e4b 0e4c 201c 201d 0e4d -XXXX 2022 0e31 0e47 0e34 0e35 0e36 0e37 0e48 0e49 0e4a 0e4b 0e4c 2018 2019 XXXX -00a0 0e01 0e02 0e03 0e04 0e05 0e06 0e07 0e08 0e09 0e0a 0e0b 0e0c 0e0d 0e0e 0e0f -0e10 0e11 0e12 0e13 0e14 0e15 0e16 0e17 0e18 0e19 0e1a 0e1b 0e1c 0e1d 0e1e 0e1f -0e20 0e21 0e22 0e23 0e24 0e25 0e26 0e27 0e28 0e29 0e2a 0e2b 0e2c 0e2d 0e2e 0e2f -0e30 0e31 0e32 0e33 0e34 0e35 0e36 0e37 0e38 0e39 0e3a 2060 200b 2013 2014 0e3f -0e40 0e41 0e42 0e43 0e44 0e45 0e46 0e47 0e48 0e49 0e4a 0e4b 0e4c 0e4d 2122 0e4f -0e50 0e51 0e52 0e53 0e54 0e55 0e56 0e57 0e58 0e59 00ae 00a9 XXXX XXXX XXXX XXXX - -charset CS_MAC_CENTEURO -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c4 0100 0101 00c9 0104 00d6 00dc 00e1 0105 010c 00e4 010d 0106 0107 00e9 0179 -017a 010e 00ed 010f 0112 0113 0116 00f3 0117 00f4 00f6 00f5 00fa 011a 011b 00fc -2020 00b0 0118 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 0119 00a8 2260 0123 012e -012f 012a 2264 2265 012b 0136 2202 2211 0142 013b 013c 013d 013e 0139 013a 0145 -0146 0143 00ac 221a 0144 0147 2206 00ab 00bb 2026 00a0 0148 0150 00d5 0151 014c -2013 2014 201c 201d 2018 2019 00f7 25ca 014d 0154 0155 0158 2039 203a 0159 0156 -0157 0160 201a 201e 0161 015a 015b 00c1 0164 0165 00cd 017d 017e 016a 00d3 00d4 -016b 016e 00da 016f 0170 0171 0172 0173 00dd 00fd 0137 017b 0141 017c 0122 02c7 - -charset CS_MAC_SYMBOL -sortpriority E2-E4 -1 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 2200 0023 2203 0025 0026 220d 0028 0029 2217 002b 002c 2212 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -2245 0391 0392 03a7 0394 0395 03a6 0393 0397 0399 03d1 039a 039b 039c 039d 039f -03a0 0398 03a1 03a3 03a4 03a5 03c2 03a9 039e 03a8 0396 005b 2234 005d 22a5 005f -f8e5 03b1 03b2 03c7 03b4 03b5 03c6 03b3 03b7 03b9 03d5 03ba 03bb 03bc 03bd 03bf -03c0 03b8 03c1 03c3 03c4 03c5 03d6 03c9 03be 03c8 03b6 007b 007c 007d 223c 007f -XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX -XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX -20ac 03d2 2032 2264 2044 221e 0192 2663 2666 2665 2660 2194 2190 2191 2192 2193 -00b0 00b1 2033 2265 00d7 221d 2202 2022 00f7 2260 2261 2248 2026 f8e6 23af 21b5 -2135 2111 211c 2118 2297 2295 2205 2229 222a 2283 2287 2284 2282 2286 2208 2209 -2220 2207 00ae 00a9 2122 220f 221a 22c5 00ac 2227 2228 21d4 21d0 21d1 21d2 21d3 -22c4 3008 00ae 00a9 2122 2211 239b 239c 239d 23a1 23a2 23a3 23a7 23a8 23a9 23aa -f8ff 3009 222b 2320 23ae 2321 239e 239f 23a0 23a4 23a5 23a6 23ab 23ac 23ad XXXX - -charset CS_MAC_DINGBATS -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 2701 2702 2703 2704 260e 2706 2707 2708 2709 261b 261e 270c 270d 270e 270f -2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 271a 271b 271c 271d 271e 271f -2720 2721 2722 2723 2724 2725 2726 2727 2605 2729 272a 272b 272c 272d 272e 272f -2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 273a 273b 273c 273d 273e 273f -2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 274a 274b 25cf 274d 25a0 274f -2750 2751 2752 25b2 25bc 25c6 2756 25d7 2758 2759 275a 275b 275c 275d 275e 007f -2768 2769 276a 276b 276c 276d 276e 276f 2770 2771 2772 2773 2774 2775 XXXX XXXX -XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX -XXXX 2761 2762 2763 2764 2765 2766 2767 2663 2666 2665 2660 2460 2461 2462 2463 -2464 2465 2466 2467 2468 2469 2776 2777 2778 2779 277a 277b 277c 277d 277e 277f -2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 278a 278b 278c 278d 278e 278f -2790 2791 2792 2793 2794 2192 2194 2195 2798 2799 279a 279b 279c 279d 279e 279f -27a0 27a1 27a2 27a3 27a4 27a5 27a6 27a7 27a8 27a9 27aa 27ab 27ac 27ad 27ae 27af -XXXX 27b1 27b2 27b3 27b4 27b5 27b6 27b7 27b8 27b9 27ba 27bb 27bc 27bd 27be XXXX - - Various Mac character sets have older (usually pre-Euro) variants - which are documented in the comments in their mapping tables. I've - manually applied these changes below. - - Mac OS Roman variants before Mac OS 8.5 (CURRENCY SIGN rather than - EURO SIGN): - -charset CS_MAC_ROMAN_OLD -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 -00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc -2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 -221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8 -00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 -2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 00a4 2039 203a fb01 fb02 -2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 -f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 - -charset CS_MAC_CROATIAN_OLD -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 -00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc -2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 0160 2122 00b4 00a8 2260 017d 00d8 -221e 00b1 2264 2265 2206 00b5 2202 2211 220f 0161 222b 00aa 00ba 03a9 017e 00f8 -00bf 00a1 00ac 221a 0192 2248 0106 00ab 010c 2026 00a0 00c0 00c3 00d5 0152 0153 -0110 2014 201c 201d 2018 2019 00f7 25ca f8ff 00a9 2044 00a4 2039 203a 00c6 00bb -2013 00b7 201a 201e 2030 00c2 0107 00c1 010d 00c8 00cd 00ce 00cf 00cc 00d3 00d4 -0111 00d2 00da 00db 00d9 0131 02c6 02dc 00af 03c0 00cb 02da 00b8 00ca 00e6 02c7 - -charset CS_MAC_ICELAND_OLD -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 -00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc -00dd 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 -221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 00e6 00f8 -00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 -2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 00a4 00d0 00f0 00de 00fe -00fd 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 -f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 - -charset CS_MAC_ROMANIAN_OLD -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 -00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc -2020 00b0 00a2 00a3 00a7 2022 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 0102 0218 -221e 00b1 2264 2265 00a5 00b5 2202 2211 220f 03c0 222b 00aa 00ba 03a9 0103 0219 -00bf 00a1 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 00c0 00c3 00d5 0152 0153 -2013 2014 201c 201d 2018 2019 00f7 25ca 00ff 0178 2044 00a4 2039 203a 021a 021b -2021 00b7 201a 201e 2030 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 -f8ff 00d2 00da 00db 00d9 0131 02c6 02dc 00af 02d8 02d9 02da 00b8 02dd 02db 02c7 - - Mac OS Greek before Mac OS 9.2.2 (SOFT HYPHEN instead of EURO SIGN, - and undefined instead of SOFT HYPHEN). - -charset CS_MAC_GREEK_OLD -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -00c4 00b9 00b2 00c9 00b3 00d6 00dc 0385 00e0 00e2 00e4 0384 00a8 00e7 00e9 00e8 -00ea 00eb 00a3 2122 00ee 00ef 2022 00bd 2030 00f4 00f6 00a6 00ad 00f9 00fb 00fc -2020 0393 0394 0398 039b 039e 03a0 00df 00ae 00a9 03a3 03aa 00a7 2260 00b0 00b7 -0391 00b1 2264 2265 00a5 0392 0395 0396 0397 0399 039a 039c 03a6 03ab 03a8 03a9 -03ac 039d 00ac 039f 03a1 2248 03a4 00ab 00bb 2026 00a0 03a5 03a7 0386 0388 0153 -2013 2015 201c 201d 2018 2019 00f7 0389 038a 038c 038e 03ad 03ae 03af 03cc 038f -03cd 03b1 03b2 03c8 03b4 03b5 03c6 03b3 03b7 03b9 03be 03ba 03bb 03bc 03bd 03bf -03c0 03ce 03c1 03c3 03c4 03b8 03c9 03c2 03c7 03c5 03b6 03ca 03cb 0390 03b0 XXXX - - Mac OS Cyrillic before Mac OS 9.0 (CENT SIGN instead of CYRILLIC - CAPITAL LETTER GHE WITH UPTURN, PARTIAL DIFFERENTIAL instead of - CYRILLIC SMALL LETTER GHE WITH UPTURN, CURRENCY SIGN instead of EURO - SIGN): - -charset CS_MAC_CYRILLIC_OLD -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f -0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f -2020 00b0 00a2 00a3 00a7 2022 00b6 0406 00ae 00a9 2122 0402 0452 2260 0403 0453 -221e 00b1 2264 2265 0456 00b5 2022 0408 0404 0454 0407 0457 0409 0459 040a 045a -0458 0405 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 040b 045b 040c 045c 0455 -2013 2014 201c 201d 2018 2019 00f7 201e 040e 045e 040f 045f 2116 0401 0451 044f -0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f -0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 00a4 - - Mac OS Ukrainian (now Cyrillic) before Mac OS 9.0 (CURRENCY SIGN - instead of EURO SIGN): - -charset CS_MAC_UKRAINE -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0410 0411 0412 0413 0414 0415 0416 0417 0418 0419 041a 041b 041c 041d 041e 041f -0420 0421 0422 0423 0424 0425 0426 0427 0428 0429 042a 042b 042c 042d 042e 042f -2020 00b0 0490 00a3 00a7 2022 00b6 0406 00ae 00a9 2122 0402 0452 2260 0403 0453 -221e 00b1 2264 2265 0456 00b5 0491 0408 0404 0454 0407 0457 0409 0459 040a 045a -0458 0405 00ac 221a 0192 2248 2206 00ab 00bb 2026 00a0 040b 045b 040c 045c 0455 -2013 2014 201c 201d 2018 2019 00f7 201e 040e 045e 040f 045f 2116 0401 0451 044f -0430 0431 0432 0433 0434 0435 0436 0437 0438 0439 043a 043b 043c 043d 043e 043f -0440 0441 0442 0443 0444 0445 0446 0447 0448 0449 044a 044b 044c 044d 044e 00a4 - - Mac OS VT100 character set, as used by the "VT100" font. Basically - Mac OS Roman hacked about to give it an almost-Latin1 repertoire and - most of the VT100 line-drawing set too. - - Point CA is the backward question-mark used for silo overflows. - - This table was derived by pasting the relevant part of 'utom' 140 - from the "Western Language Encodings" file shipped with TEC 1.5 and - then manually fixing up the scan line characters to use the Unicode - 3.2 HORIZONTAL SCAN LINE characters rather than UPPER ONE EIGHTH - BLOCK and LOWER ONE EIGHTH BLOCK with transcoding hints. - -charset CS_MAC_VT100 -2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 240a 240b 240c 240d 240e 240f -2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 241a 241b 241c 241d 241e 241f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 2421 -00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 -00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc -00dd 00b0 00a2 00a3 00a7 00b8 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 -00d7 00b1 2264 2265 00a5 00b5 00b9 00b2 00b3 03c0 00a6 00aa 00ba 2592 00e6 00f8 -00bf 00a1 00ac 00bd 0192 00bc 00be 00ab 00bb 2026 XXXX 00c0 00c3 00d5 0152 0153 -2013 2014 2518 2510 250c 2514 00f7 2022 00ff 0178 253c 20ac 00d0 00f0 00fe 00de -00fd 00b7 23ba 23bb 2500 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 -XXXX 00d2 00da 00db 00d9 23bc 23bd 251c 2524 2534 252c 2502 XXXX XXXX XXXX XXXX - - As with so many others, before Mac OS 8.5 this font had CURRENCY - SIGN rather than EURO SIGN. - -charset CS_MAC_VT100_OLD -2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 240a 240b 240c 240d 240e 240f -2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 241a 241b 241c 241d 241e 241f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 2421 -00c4 00c5 00c7 00c9 00d1 00d6 00dc 00e1 00e0 00e2 00e4 00e3 00e5 00e7 00e9 00e8 -00ea 00eb 00ed 00ec 00ee 00ef 00f1 00f3 00f2 00f4 00f6 00f5 00fa 00f9 00fb 00fc -00dd 00b0 00a2 00a3 00a7 00b8 00b6 00df 00ae 00a9 2122 00b4 00a8 2260 00c6 00d8 -00d7 00b1 2264 2265 00a5 00b5 00b9 00b2 00b3 03c0 00a6 00aa 00ba 2592 00e6 00f8 -00bf 00a1 00ac 00bd 0192 00bc 00be 00ab 00bb 2026 XXXX 00c0 00c3 00d5 0152 0153 -2013 2014 2518 2510 250c 2514 00f7 2022 00ff 0178 253c 00a4 00d0 00f0 00fe 00de -00fd 00b7 23ba 23bb 2500 00c2 00ca 00c1 00cb 00c8 00cd 00ce 00cf 00cc 00d3 00d4 -XXXX 00d2 00da 00db 00d9 23bc 23bd 251c 2524 2534 252c 2502 XXXX XXXX XXXX XXXX - - Roman Czyborra's web site (http://czyborra.com/) has a variety of - other useful mapping tables, in a slightly different format (and - gzipped). Here's a shell/Perl function to generate an SBCS table - from a Czyborra mapping table: - - gensbcs_c() { - wget -q -O - "$1" | gzip -d | \ - perl -ne '/^=(.*)\s+U\+(.*)\s+/ and $a[hex $1]=sprintf "%04x", hex $2;' \ - -e 'BEGIN{for($i=0;$i<256;$i++){$a[$i]="XXXX";' \ - -e 'if ($i < 32 or ($i >=127 and $i < 160)) {$a[$i]=sprintf "%04x", $i}}}' \ - -e 'END{for($i=0;$i<256;$i++){printf"%s%s",$a[$i],$i%16==15?"\n":" "}}' - } - - So here we have some character sets generated from Czyborra - mapping tables: VISCII, HP-Roman8, and the DEC Multinational - Character Set. - - { echo charset CS_VISCII; - gensbcs_c http://czyborra.com/charsets/viscii.txt.gz; echo; - echo charset CS_HP_ROMAN8; - gensbcs_c http://czyborra.com/charsets/hp-roman8.txt.gz; echo; - echo charset CS_DEC_MCS; - gensbcs_c http://czyborra.com/charsets/dec-mcs.txt.gz; echo; } - -charset CS_VISCII -0000 0001 1eb2 0003 0004 1eb4 1eaa 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 1ef6 0015 0016 0017 0018 1ef8 001a 001b 001c 001d 1ef4 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -1ea0 1eae 1eb0 1eb6 1ea4 1ea6 1ea8 1eac 1ebc 1eb8 1ebe 1ec0 1ec2 1ec4 1ec6 1ed0 -1ed2 1ed4 1ed6 1ed8 1ee2 1eda 1edc 1ede 1eca 1ece 1ecc 1ec8 1ee6 0168 1ee4 1ef2 -00d5 1eaf 1eb1 1eb7 1ea5 1ea7 1ea8 1ead 1ebd 1eb9 1ebf 1ec1 1ec3 1ec5 1ec7 1ed1 -1ed3 1ed5 1ed7 1ee0 01a0 1ed9 1edd 1edf 1ecb 1ef0 1ee8 1eea 1eec 01a1 1edb 01af -00c0 00c1 00c2 00c3 1ea2 0102 1eb3 1eb5 00c8 00c9 00ca 1eba 00cc 00cd 0128 1ef3 -0110 1ee9 00d2 00d3 00d4 1ea1 1ef7 1eeb 1eed 00d9 00da 1ef9 1ef5 00dd 1ee1 01b0 -00e0 00e1 00e2 00e3 1ea3 0103 1eef 1eab 00e8 00e9 00ea 1ebb 00ec 00ed 0129 1ec9 -0111 1ef1 00f2 00f3 00f4 00f5 1ecf 1ecd 1ee5 00f9 00fa 0169 1ee7 00fd 1ee3 1eee - -charset CS_HP_ROMAN8 -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -00a0 00c0 00c2 00c8 00ca 00cb 00ce 00cf 00b4 02cb 02c6 00a8 02dc 00d9 00db 20a4 -00af 00dd 00fd 00b0 00c7 00e7 00d1 00f1 00a1 00bf 00a4 00a3 00a5 00a7 0192 00a2 -00e2 00ea 00f4 00fb 00e1 00e9 00f3 00fa 00e0 00e8 00f2 00f9 00e4 00eb 00f6 00fc -00c5 00ee 00d8 00c6 00e5 00ed 00f8 00e6 00c4 00ec 00d6 00dc 00c9 00ef 00df 00d4 -00c1 00c3 00e3 00d0 00f0 00cd 00cc 00d3 00d2 00d5 00f5 0160 0161 00da 0178 00ff -00de 00fe 00b7 00b5 00b6 00be 2014 00bc 00bd 00aa 00ba 00ab 25a0 00bb 00b1 XXXX - -charset CS_DEC_MCS -0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a 000b 000c 000d 000e 000f -0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 001a 001b 001c 001d 001e 001f -0020 0021 0022 0023 0024 0025 0026 0027 0028 0029 002a 002b 002c 002d 002e 002f -0030 0031 0032 0033 0034 0035 0036 0037 0038 0039 003a 003b 003c 003d 003e 003f -0040 0041 0042 0043 0044 0045 0046 0047 0048 0049 004a 004b 004c 004d 004e 004f -0050 0051 0052 0053 0054 0055 0056 0057 0058 0059 005a 005b 005c 005d 005e 005f -0060 0061 0062 0063 0064 0065 0066 0067 0068 0069 006a 006b 006c 006d 006e 006f -0070 0071 0072 0073 0074 0075 0076 0077 0078 0079 007a 007b 007c 007d 007e 007f -0080 0081 0082 0083 0084 0085 0086 0087 0088 0089 008a 008b 008c 008d 008e 008f -0090 0091 0092 0093 0094 0095 0096 0097 0098 0099 009a 009b 009c 009d 009e 009f -XXXX 00a1 00a2 00a3 XXXX 00a5 XXXX 00a7 00a4 00a9 00aa 00ab XXXX XXXX XXXX XXXX -00b0 00b1 00b2 00b3 XXXX 00b5 00b6 00b7 XXXX 00b9 00ba 00bb 00bc 00bd XXXX 00bf -00c0 00c1 00c2 00c3 00c4 00c5 00c6 00c7 00c8 00c9 00ca 00cb 00cc 00cd 00ce 00cf -XXXX 00d1 00d2 00d3 00d4 00d5 00d6 0152 00d8 00d9 00da 00db 00dc 0178 XXXX 00df -00e0 00e1 00e2 00e3 00e4 00e5 00e6 00e7 00e8 00e9 00ea 00eb 00ec 00ed 00ee 00ef -XXXX 00f1 00f2 00f3 00f4 00f5 00f6 0153 00f8 00f9 00fa 00fb 00fc 00ff XXXX XXXX diff --git a/charset/sbcsgen.pl b/charset/sbcsgen.pl deleted file mode 100644 index 6012c9335..000000000 --- a/charset/sbcsgen.pl +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env perl - -# This script generates sbcsdat.c (the data for all the SBCSes) from its -# source form sbcs.dat. - -use warnings; -use Getopt::Long; -use File::Basename; - -$infile = (dirname __FILE__) . "/sbcs.dat"; -$outfile = "sbcsdat.c"; - -my $usage = "usage: sbcsgen.pl [-o OUTFILE]\n"; -GetOptions("o|output=s" => \$outfile) - or die $usage; - -open FOO, $infile; -open BAR, ">$outfile"; -select BAR; - -print "/*\n"; -print " * sbcsdat.c - data definitions for single-byte character sets.\n"; -print " *\n"; -print " * Generated by sbcsgen.pl from sbcs.dat.\n"; -print " * You should edit those files rather than editing this one.\n"; -print " */\n"; -print "\n"; -print "#ifndef ENUM_CHARSETS\n"; -print "\n"; -print "#include \"charset.h\"\n"; -print "#include \"internal.h\"\n"; -print "\n"; - -my $charsetname = undef; -my @vals = (); - -my @charsetnames = (); -my @sortpriority = (); - -while () { - chomp; y/\r//d; - if (/^charset (.*)$/) { - $charsetname = $1; - @vals = (); - @sortpriority = map { 0 } 0..255; - } elsif (/^sortpriority ([^-]*)-([^-]*) (.*)$/) { - for ($i = hex $1; $i <= hex $2; $i++) { - $sortpriority[$i] += $3; - } - } elsif (/^[0-9a-fA-FX]/) { - push @vals, map { $_ eq "XXXX" ? -1 : hex $_ } split / +/, $_; - if (scalar @vals > 256) { - die "$infile:$.: charset $charsetname has more than 256 values\n"; - } elsif (scalar @vals == 256) { - &outcharset($charsetname, \@vals, \@sortpriority); - push @charsetnames, $charsetname; - $charsetname = undef; - @vals = (); - @sortpriority = map { 0 } 0..255; - } - } -} - -print "#else /* ENUM_CHARSETS */\n"; -print "\n"; - -foreach $i (@charsetnames) { - print "ENUM_CHARSET($i)\n"; -} - -print "\n"; -print "#endif /* ENUM_CHARSETS */\n"; - -sub outcharset($$$) { - my ($name, $vals, $sortpriority) = @_; - my ($prefix, $i, @sorted); - - print "static const sbcs_data data_$name = {\n"; - print " {\n"; - $prefix = " "; - @sorted = (); - for ($i = 0; $i < 256; $i++) { - if ($vals->[$i] < 0) { - printf "%sERROR ", $prefix; - } else { - printf "%s0x%04x", $prefix, $vals->[$i]; - die "ooh? $i\n" unless defined $sortpriority->[$i]; - push @sorted, [$i, $vals->[$i], 0+$sortpriority->[$i]]; - } - if ($i % 8 == 7) { - $prefix = ",\n "; - } else { - $prefix = ", "; - } - } - print "\n },\n {\n"; - @sorted = sort { ($a->[1] == $b->[1] ? - $b->[2] <=> $a->[2] : - $a->[1] <=> $b->[1]) || - $a->[0] <=> $b->[0] } @sorted; - $prefix = " "; - $uval = -1; - for ($i = $j = 0; $i < scalar @sorted; $i++) { - next if ($uval == $sorted[$i]->[1]); # low-priority alternative - $uval = $sorted[$i]->[1]; - printf "%s0x%02x", $prefix, $sorted[$i]->[0]; - if ($j % 8 == 7) { - $prefix = ",\n "; - } else { - $prefix = ", "; - } - $j++; - } - printf "\n },\n %d\n", $j; - print "};\n"; - print "const charset_spec charset_$name = {\n" . - " $name, read_sbcs, write_sbcs, &data_$name\n};\n\n"; -} diff --git a/charset/slookup.c b/charset/slookup.c deleted file mode 100644 index 80d7aeff4..000000000 --- a/charset/slookup.c +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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/charset/toucs.c b/charset/toucs.c deleted file mode 100644 index 34d1bf817..000000000 --- a/charset/toucs.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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/charset/utf8.c b/charset/utf8.c deleted file mode 100644 index 2594fec36..000000000 --- a/charset/utf8.c +++ /dev/null @@ -1,877 +0,0 @@ -/* - * utf8.c - routines to handle UTF-8. - */ - -#ifndef ENUM_CHARSETS - -#include "charset.h" -#include "internal.h" - -/* - * UTF-8 has no associated data, so `charset' may be ignored. - */ - -static 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. - */ - -static 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], (unsigned)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], (unsigned)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/charset/xenc.c b/charset/xenc.c deleted file mode 100644 index 24592ad11..000000000 --- a/charset/xenc.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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/clicons.c b/clicons.c deleted file mode 100644 index 2e88544f3..000000000 --- a/clicons.c +++ /dev/null @@ -1,14 +0,0 @@ -/* - * clicons.c: definitions limited to tools that link against both - * console.c and cmdline.c. - */ - -#include "putty.h" - -static const LogPolicyVtable console_cli_logpolicy_vt = { - .eventlog = console_eventlog, - .askappend = console_askappend, - .logging_error = console_logging_error, - .verbose = cmdline_lp_verbose, -}; -LogPolicy console_cli_logpolicy[1] = {{ &console_cli_logpolicy_vt }}; diff --git a/cmake/cmake.h.in b/cmake/cmake.h.in deleted file mode 100644 index 3882148b2..000000000 --- a/cmake/cmake.h.in +++ /dev/null @@ -1,61 +0,0 @@ -#cmakedefine NO_IPV6 -#cmakedefine NO_GSSAPI -#cmakedefine STATIC_GSSAPI -#cmakedefine NO_SCROLLBACK_COMPRESSION - -#cmakedefine NO_MULTIMON - -#cmakedefine01 HAVE_WINRESRC_H -#cmakedefine01 HAVE_WINRES_H -#cmakedefine01 HAVE_WIN_H -#cmakedefine01 HAVE_NO_STDINT_H -#cmakedefine01 HAVE_AFUNIX_H -#cmakedefine01 HAVE_GCP_RESULTSW -#cmakedefine01 HAVE_ADDDLLDIRECTORY -#cmakedefine01 HAVE_GETNAMEDPIPECLIENTPROCESSID -#cmakedefine01 HAVE_SETDEFAULTDLLDIRECTORIES -#cmakedefine01 HAVE_STRTOUMAX -#cmakedefine01 HAVE_DWMAPI_H - -#cmakedefine NOT_X_WINDOWS -#cmakedefine OMIT_UTMP - -#cmakedefine01 HAVE_ASM_HWCAP_H -#cmakedefine01 HAVE_SYS_AUXV_H -#cmakedefine01 HAVE_SYS_SYSCTL_H -#cmakedefine01 HAVE_SYS_TYPES_H -#cmakedefine01 HAVE_GLOB_H -#cmakedefine01 HAVE_UTMP_H -#cmakedefine01 HAVE_FUTIMES -#cmakedefine01 HAVE_GETADDRINFO -#cmakedefine01 HAVE_POSIX_OPENPT -#cmakedefine01 HAVE_PTSNAME -#cmakedefine01 HAVE_SETRESUID -#cmakedefine01 HAVE_SETRESGID -#cmakedefine01 HAVE_STRSIGNAL -#cmakedefine01 HAVE_UPDWTMPX -#cmakedefine01 HAVE_FSTATAT -#cmakedefine01 HAVE_DIRFD -#cmakedefine01 HAVE_SETPWENT -#cmakedefine01 HAVE_ENDPWENT -#cmakedefine01 HAVE_GETAUXVAL -#cmakedefine01 HAVE_ELF_AUX_INFO -#cmakedefine01 HAVE_SYSCTLBYNAME -#cmakedefine01 HAVE_CLOCK_MONOTONIC -#cmakedefine01 HAVE_CLOCK_GETTIME -#cmakedefine01 HAVE_SO_PEERCRED -#cmakedefine01 HAVE_NULLARY_SETPGRP -#cmakedefine01 HAVE_BINARY_SETPGRP -#cmakedefine01 HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE -#cmakedefine01 HAVE_PANGO_FONT_MAP_LIST_FAMILIES - -#cmakedefine01 HAVE_AES_NI -#cmakedefine01 HAVE_SHA_NI -#cmakedefine01 HAVE_SHAINTRIN_H -#cmakedefine01 HAVE_CLMUL -#cmakedefine01 HAVE_NEON_CRYPTO -#cmakedefine01 HAVE_NEON_PMULL -#cmakedefine01 HAVE_NEON_VADDQ_P128 -#cmakedefine01 HAVE_NEON_SHA512 -#cmakedefine01 HAVE_NEON_SHA512_INTRINSICS -#cmakedefine01 USE_ARM64_NEON_H diff --git a/cmake/gitcommit.cmake b/cmake/gitcommit.cmake deleted file mode 100644 index 0aa8a0958..000000000 --- a/cmake/gitcommit.cmake +++ /dev/null @@ -1,63 +0,0 @@ -# Pure cmake script to write out cmake_commit.c and cmake_version.but - -set(DEFAULT_COMMIT "unavailable") -set(commit "${DEFAULT_COMMIT}") - -set(TOPLEVEL_SOURCE_DIR ${CMAKE_SOURCE_DIR}) - -execute_process( - COMMAND ${GIT_EXECUTABLE} rev-parse --show-toplevel - OUTPUT_VARIABLE git_worktree - ERROR_VARIABLE stderr - RESULT_VARIABLE status) -string(REGEX REPLACE "\n$" "" git_worktree "${git_worktree}") - -if(status EQUAL 0) - if(git_worktree STREQUAL CMAKE_SOURCE_DIR) - execute_process( - COMMAND ${GIT_EXECUTABLE} rev-parse HEAD - OUTPUT_VARIABLE git_commit - ERROR_VARIABLE stderr - RESULT_VARIABLE status) - if(status EQUAL 0) - string(REGEX REPLACE "\n$" "" commit "${git_commit}") - else() - if(commit STREQUAL "unavailable") - message("Unable to determine git commit: 'git rev-parse HEAD' returned status ${status} and error output:\n${stderr}\n") - endif() - endif() - else() - if(commit STREQUAL "unavailable") - message("Unable to determine git commit: top-level source dir ${CMAKE_SOURCE_DIR} is not the root of a repository") - endif() - endif() -else() - if(commit STREQUAL "unavailable") - message("Unable to determine git commit: 'git rev-parse --show-toplevel' returned status ${status} and error output:\n${stderr}\n") - endif() -endif() - -if(OUTPUT_TYPE STREQUAL header) - file(WRITE "${OUTPUT_FILE}" "\ -/* - * cmake_commit.c - string literal giving the source git commit, if known. - * - * Generated by cmake/gitcommit.cmake. - */ - -#include \"putty.h\" -const char commitid[] = \"${commit}\"; -") -elseif(OUTPUT_TYPE STREQUAL halibut) - if(commit STREQUAL "unavailable") - file(WRITE "${OUTPUT_FILE}" "\ -\\versionid no version information available -") - else() - file(WRITE "${OUTPUT_FILE}" "\ -\\versionid built from git commit ${commit} -") - endif() -else() - message(FATAL_ERROR "Set OUTPUT_TYPE when running this script") -endif() diff --git a/cmake/gtk.cmake b/cmake/gtk.cmake deleted file mode 100644 index 13ff77059..000000000 --- a/cmake/gtk.cmake +++ /dev/null @@ -1,89 +0,0 @@ -# Look for GTK, of any version. - -set(PUTTY_GTK_VERSION "ANY" - CACHE STRING "Which major version of GTK to build with") -set_property(CACHE PUTTY_GTK_VERSION - PROPERTY STRINGS ANY 3 2 1 NONE) - -set(GTK_FOUND FALSE) - -macro(try_pkg_config_gtk VER PACKAGENAME) - if(NOT GTK_FOUND AND - (PUTTY_GTK_VERSION STREQUAL ANY OR PUTTY_GTK_VERSION STREQUAL ${VER})) - find_package(PkgConfig) - pkg_check_modules(GTK ${PACKAGENAME}) - if(GTK_FOUND) - set(GTK_VERSION ${VER}) - endif() - endif() -endmacro() -try_pkg_config_gtk(3 gtk+-3.0) -try_pkg_config_gtk(2 gtk+-2.0) - -if(NOT GTK_FOUND AND - (PUTTY_GTK_VERSION STREQUAL ANY OR PUTTY_GTK_VERSION STREQUAL 1)) - message("-- Checking for GTK1 (via gtk-config)") - find_program(GTK_CONFIG gtk-config) - if(GTK_CONFIG) - execute_process(COMMAND ${GTK_CONFIG} --cflags - OUTPUT_VARIABLE gtk_config_cflags - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE gtk_config_cflags_result) - execute_process(COMMAND ${GTK_CONFIG} --libs - OUTPUT_VARIABLE gtk_config_libs - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE gtk_config_libs_result) - - if(gtk_config_cflags_result EQUAL 0 AND gtk_config_libs_result EQUAL 0) - - set(GTK_INCLUDE_DIRS) - set(GTK_LIBRARY_DIRS) - set(GTK_LIBRARIES) - - separate_arguments(gtk_config_cflags NATIVE_COMMAND - ${gtk_config_cflags}) - foreach(opt ${gtk_config_cflags}) - string(REGEX MATCH "^-I" ok ${opt}) - if(ok) - string(REGEX REPLACE "^-I" "" optval ${opt}) - list(APPEND GTK_INCLUDE_DIRS ${optval}) - endif() - endforeach() - - separate_arguments(gtk_config_libs NATIVE_COMMAND - ${gtk_config_libs}) - foreach(opt ${gtk_config_libs}) - string(REGEX MATCH "^-l" ok ${opt}) - if(ok) - list(APPEND GTK_LIBRARIES ${opt}) - endif() - string(REGEX MATCH "^-L" ok ${opt}) - if(ok) - string(REGEX REPLACE "^-L" "" optval ${opt}) - list(APPEND GTK_LIBRARY_DIRS ${optval}) - endif() - endforeach() - - message("-- Found GTK1") - set(GTK_FOUND TRUE) - endif() - endif() -endif() - -if(GTK_FOUND) - # Check for some particular Pango functions. - function(pango_check_subscope) - set(CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS}) - set(CMAKE_REQUIRED_LINK_OPTIONS ${GTK_LDFLAGS}) - set(CMAKE_REQUIRED_LIBRARIES ${GTK_LIBRARIES}) - check_symbol_exists(pango_font_family_is_monospace "pango/pango.h" - HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE) - check_symbol_exists(pango_font_map_list_families "pango/pango.h" - HAVE_PANGO_FONT_MAP_LIST_FAMILIES) - set(HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE - ${HAVE_PANGO_FONT_FAMILY_IS_MONOSPACE} PARENT_SCOPE) - set(HAVE_PANGO_FONT_MAP_LIST_FAMILIES - ${HAVE_PANGO_FONT_MAP_LIST_FAMILIES} PARENT_SCOPE) - endfunction() - pango_check_subscope() -endif() diff --git a/cmake/licence.cmake b/cmake/licence.cmake deleted file mode 100644 index e356ae93c..000000000 --- a/cmake/licence.cmake +++ /dev/null @@ -1,39 +0,0 @@ -# Pure cmake script to generate licence.h from LICENCE - -file(READ "${LICENCE_FILE}" LICENCE_TEXT) - -function(c_string_escape outvar value) - string(REPLACE "\\" "\\\\" value "${value}") - string(REPLACE "\"" "\\\"" value "${value}") - set("${outvar}" "${value}" PARENT_SCOPE) -endfunction() - -set(copyright_regex "PuTTY is copyright ([0-9]+-[0-9]+ [^\n]*[^\n.])\\.?\n") -string(REGEX MATCH "${copyright_regex}" COPYRIGHT_NOTICE "${LICENCE_TEXT}") -string(REGEX REPLACE "${copyright_regex}" "\\1" - COPYRIGHT_NOTICE "${COPYRIGHT_NOTICE}") -c_string_escape(COPYRIGHT_NOTICE "${COPYRIGHT_NOTICE}") - -string(REGEX REPLACE "\n$" "" LICENCE_TEXT "${LICENCE_TEXT}") -string(REPLACE "\r" "" LICENCE_TEXT "${LICENCE_TEXT}") -string(REPLACE "\n\n" "\r" LICENCE_TEXT "${LICENCE_TEXT}") -string(REPLACE "\n" " " LICENCE_TEXT "${LICENCE_TEXT}") -string(REPLACE "\r" "\n" LICENCE_TEXT "${LICENCE_TEXT}") - -c_string_escape(LICENCE_TEXT "${LICENCE_TEXT}") -string(REPLACE "\n" "\" \\\n parsep \\\n \"" - LICENCE_TEXT "${LICENCE_TEXT}") - -file(WRITE "${OUTPUT_FILE}" "\ -/* - * licence.h - macro definitions for the PuTTY licence. - * - * Generated by cmake/licence.cmake from ./LICENCE. - * You should edit those files rather than editing this one. - */ - -#define LICENCE_TEXT(parsep) \\ - \"${LICENCE_TEXT}\" - -#define SHORT_COPYRIGHT_DETAILS \"${COPYRIGHT_NOTICE}\" -") diff --git a/cmake/platforms/unix.cmake b/cmake/platforms/unix.cmake deleted file mode 100644 index 4d056d0a2..000000000 --- a/cmake/platforms/unix.cmake +++ /dev/null @@ -1,234 +0,0 @@ -set(PUTTY_GSSAPI DYNAMIC - CACHE STRING "Build PuTTY with dynamically or statically linked \ -Kerberos / GSSAPI support, if possible") -set_property(CACHE PUTTY_GSSAPI - PROPERTY STRINGS DYNAMIC STATIC OFF) - -include(CheckIncludeFile) -include(CheckLibraryExists) -include(CheckSymbolExists) -include(CheckCSourceCompiles) -include(GNUInstallDirs) - -set(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} - -D_DEFAULT_SOURCE -D_GNU_SOURCE) - -check_include_file(sys/auxv.h HAVE_SYS_AUXV_H) -check_include_file(asm/hwcap.h HAVE_ASM_HWCAP_H) -check_include_file(sys/sysctl.h HAVE_SYS_SYSCTL_H) -check_include_file(sys/types.h HAVE_SYS_TYPES_H) -check_include_file(glob.h HAVE_GLOB_H) -check_include_file(utmp.h HAVE_UTMP_H) -check_include_file(utmpx.h HAVE_UTMPX_H) - -check_symbol_exists(futimes "sys/time.h" HAVE_FUTIMES) -check_symbol_exists(getaddrinfo "sys/types.h;sys/socket.h;netdb.h" - HAVE_GETADDRINFO) -check_symbol_exists(posix_openpt "stdlib.h;fcntl.h" HAVE_POSIX_OPENPT) -check_symbol_exists(ptsname "stdlib.h" HAVE_PTSNAME) -check_symbol_exists(setresuid "unistd.h" HAVE_SETRESUID) -check_symbol_exists(setresgid "unistd.h" HAVE_SETRESGID) -check_symbol_exists(strsignal "string.h" HAVE_STRSIGNAL) -check_symbol_exists(updwtmpx "utmpx.h" HAVE_UPDWTMPX) -check_symbol_exists(fstatat "sys/types.h;sys/stat.h;unistd.h" HAVE_FSTATAT) -check_symbol_exists(dirfd "sys/types.h;dirent.h" HAVE_DIRFD) -check_symbol_exists(setpwent "sys/types.h;pwd.h" HAVE_SETPWENT) -check_symbol_exists(endpwent "sys/types.h;pwd.h" HAVE_ENDPWENT) -check_symbol_exists(getauxval "sys/auxv.h" HAVE_GETAUXVAL) -check_symbol_exists(elf_aux_info "sys/auxv.h" HAVE_ELF_AUX_INFO) -check_symbol_exists(sysctlbyname "sys/types.h;sys/sysctl.h" HAVE_SYSCTLBYNAME) -check_symbol_exists(CLOCK_MONOTONIC "time.h" HAVE_CLOCK_MONOTONIC) -check_symbol_exists(clock_gettime "time.h" HAVE_CLOCK_GETTIME) - -check_c_source_compiles(" -#define _GNU_SOURCE -#include -#include -int main(int argc, char **argv) { - struct ucred cr; - socklen_t crlen = sizeof(cr); - return getsockopt(0, SOL_SOCKET, SO_PEERCRED, &cr, &crlen) + - cr.pid + cr.uid + cr.gid; -}" HAVE_SO_PEERCRED) - -check_c_source_compiles(" -#include -#include - -int main(int argc, char **argv) { - setpgrp(); -}" HAVE_NULLARY_SETPGRP) -check_c_source_compiles(" -#include -#include - -int main(int argc, char **argv) { - setpgrp(0, 0); -}" HAVE_BINARY_SETPGRP) - -if(HAVE_GETADDRINFO AND PUTTY_IPV6) - set(NO_IPV6 OFF) -else() - set(NO_IPV6 ON) -endif() - -if(HAVE_UTMPX_H) - set(OMIT_UTMP OFF) -else() - set(OMIT_UTMP ON) -endif() - -include(cmake/gtk.cmake) - -if(GTK_FOUND) - # See if we have X11 available. This requires libX11 itself, and also - # the GDK integration to X11. - find_package(X11) - - function(check_x11) - list(APPEND CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS}) - check_include_file(gdk/gdkx.h HAVE_GDK_GDKX_H) - - if(X11_FOUND AND HAVE_GDK_GDKX_H) - set(NOT_X_WINDOWS OFF PARENT_SCOPE) - else() - set(NOT_X_WINDOWS ON PARENT_SCOPE) - endif() - endfunction() - check_x11() -else() - # If we didn't even have GTK, behave as if X11 is not available. - # (There's nothing useful we could do with it even if there was.) - set(NOT_X_WINDOWS ON) -endif() - -include_directories(${CMAKE_SOURCE_DIR}/charset ${GTK_INCLUDE_DIRS} ${X11_INCLUDE_DIR}) -link_directories(${GTK_LIBRARY_DIRS}) - -function(add_optional_system_lib library testfn) - check_library_exists(${library} ${testfn} "" HAVE_LIB${library}) - if (HAVE_LIB${library}) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES};-l${library}) - link_libraries(-l${library}) - endif() -endfunction() - -add_optional_system_lib(m pow) -add_optional_system_lib(rt clock_gettime) -add_optional_system_lib(xnet socket) - -set(extra_dirs charset) - -if(PUTTY_GSSAPI STREQUAL DYNAMIC) - add_optional_system_lib(dl dlopen) - if(HAVE_NO_LIBdl) - message(WARNING - "Could not find libdl -- cannot provide dynamic GSSAPI support") - set(NO_GSSAPI ON) - endif() -endif() - -if(PUTTY_GSSAPI STREQUAL STATIC) - set(KRB5_CFLAGS) - set(KRB5_LDFLAGS) - - # First try using pkg-config - find_package(PkgConfig) - pkg_check_modules(KRB5 krb5-gssapi) - - # Failing that, try the dedicated krb5-config - if(NOT KRB5_FOUND) - find_program(KRB5_CONFIG krb5-config) - if(KRB5_CONFIG) - execute_process(COMMAND ${KRB5_CONFIG} --cflags gssapi - OUTPUT_VARIABLE krb5_config_cflags - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE krb5_config_cflags_result) - execute_process(COMMAND ${KRB5_CONFIG} --libs gssapi - OUTPUT_VARIABLE krb5_config_libs - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE krb5_config_libs_result) - - if(krb5_config_cflags_result EQUAL 0 AND krb5_config_libs_result EQUAL 0) - set(KRB5_INCLUDE_DIRS) - set(KRB5_LIBRARY_DIRS) - set(KRB5_LIBRARIES) - - # We can safely put krb5-config's cflags directly into cmake's - # cflags, without bothering to extract the include directories. - set(KRB5_CFLAGS ${krb5_config_cflags}) - - # But krb5-config --libs isn't so simple. It will actually - # deliver a mix of libraries and other linker options. We have - # to separate them for cmake purposes, because if we pass the - # whole lot to add_link_options then they'll appear too early - # in the command line (so that by the time our own code refers - # to GSSAPI functions it'll be too late to search these - # libraries for them), and if we pass the whole lot to - # link_libraries then it'll get confused about options that - # aren't libraries. - separate_arguments(krb5_config_libs NATIVE_COMMAND - ${krb5_config_libs}) - foreach(opt ${krb5_config_libs}) - string(REGEX MATCH "^-l" ok ${opt}) - if(ok) - list(APPEND KRB5_LIBRARIES ${opt}) - continue() - endif() - string(REGEX MATCH "^-L" ok ${opt}) - if(ok) - string(REGEX REPLACE "^-L" "" optval ${opt}) - list(APPEND KRB5_LIBRARY_DIRS ${optval}) - continue() - endif() - list(APPEND KRB5_LDFLAGS ${opt}) - endforeach() - - message(STATUS "Found Kerberos via krb5-config") - set(KRB5_FOUND YES) - endif() - endif() - endif() - - if(KRB5_FOUND) - include_directories(${KRB5_INCLUDE_DIRS}) - link_directories(${KRB5_LIBRARY_DIRS}) - link_libraries(${KRB5_LIBRARIES}) - add_compile_options(${KRB5_CFLAGS}) - add_link_options(${KRB5_LDFLAGS}) - set(STATIC_GSSAPI ON) - else() - message(WARNING - "Could not find krb5 via pkg-config or krb5-config -- \ -cannot provide static GSSAPI support") - set(NO_GSSAPI ON) - endif() -endif() - -if(PUTTY_GSSAPI STREQUAL OFF) - set(NO_GSSAPI ON) -endif() - -if(STRICT AND (CMAKE_C_COMPILER_ID MATCHES "GNU" OR - CMAKE_C_COMPILER_ID MATCHES "Clang")) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wpointer-arith -Wvla") -endif() - -function(installed_program target) - if(CMAKE_VERSION VERSION_LESS 3.14) - # CMake 3.13 and earlier required an explicit install destination. - install(TARGETS ${target} RUNTIME DESTINATION bin) - else() - # 3.14 and above selects a sensible default, which we should avoid - # overriding here so that end users can override it using - # CMAKE_INSTALL_BINDIR. - install(TARGETS ${target}) - endif() - - if(HAVE_MANPAGE_${target}_1) - install(FILES ${CMAKE_BINARY_DIR}/doc/${target}.1 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) - else() - message(WARNING "Could not build man page ${target}.1") - endif() -endfunction() diff --git a/cmake/platforms/windows.cmake b/cmake/platforms/windows.cmake deleted file mode 100644 index a7ed7c7c1..000000000 --- a/cmake/platforms/windows.cmake +++ /dev/null @@ -1,202 +0,0 @@ -set(PUTTY_MINEFIELD OFF - CACHE BOOL "Build PuTTY with its built-in memory debugger 'Minefield'") -set(PUTTY_GSSAPI ON - CACHE BOOL "Build PuTTY with GSSAPI support") -set(PUTTY_LINK_MAPS OFF - CACHE BOOL "Attempt to generate link maps") -set(PUTTY_EMBEDDED_CHM_FILE "" - CACHE FILEPATH "Path to a .chm help file to embed in the binaries") - -if(PUTTY_SUBSYSTEM_VERSION) - string(REPLACE - "subsystem:windows" "subsystem:windows,${PUTTY_SUBSYSTEM_VERSION}" - CMAKE_C_CREATE_WIN32_EXE ${CMAKE_C_CREATE_WIN32_EXE}) - string(REPLACE - "subsystem:console" "subsystem:console,${PUTTY_SUBSYSTEM_VERSION}" - CMAKE_C_CREATE_CONSOLE_EXE ${CMAKE_C_CREATE_CONSOLE_EXE}) -endif() - -function(define_negation newvar oldvar) - if(${oldvar}) - set(${newvar} OFF PARENT_SCOPE) - else() - set(${newvar} ON PARENT_SCOPE) - endif() -endfunction() - -include(CheckIncludeFiles) -include(CheckSymbolExists) -include(CheckCSourceCompiles) - -# Still needed for AArch32 Windows builds -set(CMAKE_REQUIRED_DEFINITIONS -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE) - -check_include_files("windows.h;winresrc.h" HAVE_WINRESRC_H) -if(NOT HAVE_WINRESRC_H) - # A couple of fallback names for the header file you can include in - # .rc files. We conditionalise even these checks, to save effort at - # cmake time. - check_include_files("windows.h;winres.h" HAVE_WINRES_H) - if(NOT HAVE_WINRES_H) - check_include_files("windows.h;win.h" HAVE_WIN_H) - endif() -endif() -check_include_files("stdint.h" HAVE_STDINT_H) -define_negation(HAVE_NO_STDINT_H HAVE_STDINT_H) - -check_include_files("windows.h;multimon.h" HAVE_MULTIMON_H) -define_negation(NO_MULTIMON HAVE_MULTIMON_H) - -check_include_files("windows.h;htmlhelp.h" HAVE_HTMLHELP_H) -define_negation(NO_HTMLHELP HAVE_HTMLHELP_H) - -check_include_files("winsock2.h;afunix.h" HAVE_AFUNIX_H) - -check_symbol_exists(strtoumax "inttypes.h" HAVE_STRTOUMAX) -check_symbol_exists(AddDllDirectory "windows.h" HAVE_ADDDLLDIRECTORY) -check_symbol_exists(SetDefaultDllDirectories "windows.h" - HAVE_SETDEFAULTDLLDIRECTORIES) -check_symbol_exists(GetNamedPipeClientProcessId "windows.h" - HAVE_GETNAMEDPIPECLIENTPROCESSID) -check_symbol_exists(CreatePseudoConsole "windows.h" HAVE_CONPTY) - -check_c_source_compiles(" -#include -GCP_RESULTSW gcpw; -int main(void) { return 0; } -" HAVE_GCP_RESULTSW) - -function(dwmapi_test_wrapper) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} dwmapi.lib) - check_c_source_compiles(" -#include -#include -volatile HWND hwnd; -int main(void) { - RECT r; - DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &r, sizeof(r)); -} -" HAVE_DWMAPI_H) - set(HAVE_DWMAPI_H ${HAVE_DWMAPI_H} PARENT_SCOPE) -endfunction() -dwmapi_test_wrapper() - -set(NO_SECURITY ${PUTTY_NO_SECURITY}) - -add_compile_definitions( - _WINDOWS - _CRT_SECURE_NO_WARNINGS - _WINSOCK_DEPRECATED_NO_WARNINGS - _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE) - -if(PUTTY_MINEFIELD) - add_compile_definitions(MINEFIELD) -endif() -if(NOT PUTTY_GSSAPI) - add_compile_definitions(NO_GSSAPI) -endif() -if(PUTTY_EMBEDDED_CHM_FILE) - add_compile_definitions("EMBEDDED_CHM_FILE=\"${PUTTY_EMBEDDED_CHM_FILE}\"") -endif() - -if(WINELIB) - enable_language(RC) - set(LFLAG_MANIFEST_NO "") -elseif(CMAKE_C_COMPILER_ID MATCHES "MSVC" OR - CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") - set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} /nologo /C1252") - set(LFLAG_MANIFEST_NO "/manifest:no") -else() - set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -c1252") - set(LFLAG_MANIFEST_NO "") -endif() - -if(STRICT) - if(CMAKE_C_COMPILER_ID MATCHES "GNU") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wpointer-arith -Wvla") - elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wpointer-arith -Wvla") - endif() -endif() - -if(CMAKE_C_COMPILER_ID MATCHES "Clang") - # Switch back from MSVC-style error message format - # "file.c(line,col)" to clang's native style "file.c:line:col:". I - # find the latter more convenient because it matches other Unixy - # tools like grep, and I have tooling to parse that format and jump - # to the sites of error messages. - set(CMAKE_C_FLAGS - "${CMAKE_C_FLAGS} -Xclang -fdiagnostics-format -Xclang clang") -endif() - -if(CMAKE_C_COMPILER_ID MATCHES "MSVC") - # Turn off some warnings that I've just found too noisy. - # - # - 4244, 4267: "possible loss of data" when narrowing an integer - # type (separate warning numbers for initialisers and - # assignments). Every time I spot-check instances of this, they - # turn out to be sensible (e.g. something was already checked, or - # was assigned from a previous variable that must have been in - # range). I don't think putting a warning-suppression idiom at - # every one of these sites would improve code legibility. - # - # - 4018: "signed/unsigned mismatch" in integer comparison. Again, - # comes up a lot, and generally my spot checks make it look as if - # it's OK. - # - # - 4146: applying unary '-' to an unsigned type. We do that all - # the time in deliberate bit-twiddling code like mpint.c or - # crypto implementations. - # - # - 4293: warning about undefined behaviour if a shift count is too - # big. We often do this inside a ?: clause which doesn't evaluate - # the overlong shift unless the shift count _isn't_ too big. When - # the shift count is constant, MSVC spots the potential problem - # in one branch of the ?:, but doesn't also spot that that branch - # isn't ever taken, so it complains about a thing that's already - # guarded. - # - # - 4090: different 'const' qualifiers. It's a shame to suppress - # this one, because const mismatches really are a thing I'd - # normally like to be warned about. But MSVC (as of 2017 at - # least) seems to have a bug in which assigning a 'void *' into a - # 'const char **' thinks there's a const-qualifier mismatch. - # There isn't! Both are pointers to modifiable objects. The fact - # that in one case, the modifiable object is a pointer to - # something _else_ const should make no difference. - - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} \ -/wd4244 /wd4267 /wd4018 /wd4146 /wd4293 /wd4090") -endif() - -if(CMAKE_C_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") - set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} /dynamicbase /nxcompat") -endif() - -set(platform_libraries - advapi32.lib comdlg32.lib gdi32.lib imm32.lib - ole32.lib shell32.lib user32.lib ws2_32.lib kernel32.lib) - -# Generate link maps -if(PUTTY_LINK_MAPS) - if(CMAKE_C_COMPILER_ID MATCHES "Clang" AND - "x${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "xMSVC") - set(CMAKE_C_LINK_EXECUTABLE - "${CMAKE_C_LINK_EXECUTABLE} /lldmap:.map") - elseif(CMAKE_C_COMPILER_ID MATCHES "MSVC") - set(CMAKE_C_LINK_EXECUTABLE - "${CMAKE_C_LINK_EXECUTABLE} /map:.map") - else() - message(WARNING - "Don't know how to generate link maps on this toolchain") - endif() -endif() - -# Write out a file in the cmake output directory listing the -# executables that are 'official' enough to want to code-sign and -# ship. -file(WRITE ${CMAKE_BINARY_DIR}/shipped.txt "") -function(installed_program target) - file(APPEND ${CMAKE_BINARY_DIR}/shipped.txt - "${target}${CMAKE_EXECUTABLE_SUFFIX}\n") -endfunction() diff --git a/cmake/setup.cmake b/cmake/setup.cmake deleted file mode 100644 index d81d5a5c6..000000000 --- a/cmake/setup.cmake +++ /dev/null @@ -1,122 +0,0 @@ -# Forcibly re-enable assertions, even if we're building in release -# mode. This is a security project - assertions may be enforcing -# security-critical constraints. A backstop #ifdef in defs.h should -# give a #error if this manoeuvre doesn't do what it needs to. -string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") -string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") -string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") -string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") - -set(PUTTY_IPV6 ON - CACHE BOOL "Build PuTTY with IPv6 support if possible") -set(PUTTY_DEBUG OFF - CACHE BOOL "Build PuTTY with debug() statements enabled") -set(PUTTY_FUZZING OFF - CACHE BOOL "Build PuTTY binaries suitable for fuzzing, NOT FOR REAL USE") -set(PUTTY_COVERAGE OFF - CACHE BOOL "Build PuTTY binaries suitable for code coverage analysis") -set(PUTTY_COMPRESS_SCROLLBACK ON - # This is always on in production versions of PuTTY, but downstreams - # of the code have been known to find it a better tradeoff to - # disable it. So there's a #ifdef in terminal.c, and a cmake option - # to enable that ifdef just in case it needs testing or debugging. - CACHE BOOL "Store terminal scrollback in compressed form") - -set(STRICT OFF - CACHE BOOL "Enable extra compiler warnings and make them errors") - -include(FindGit) - -set(GENERATED_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}) - -set(GENERATED_LICENCE_H ${GENERATED_SOURCES_DIR}/licence.h) -set(INTERMEDIATE_LICENCE_H ${GENERATED_LICENCE_H}.tmp) -add_custom_command(OUTPUT ${INTERMEDIATE_LICENCE_H} - COMMAND ${CMAKE_COMMAND} - -DLICENCE_FILE=${CMAKE_SOURCE_DIR}/LICENCE - -DOUTPUT_FILE=${INTERMEDIATE_LICENCE_H} - -P ${CMAKE_SOURCE_DIR}/cmake/licence.cmake - DEPENDS ${CMAKE_SOURCE_DIR}/cmake/licence.cmake ${CMAKE_SOURCE_DIR}/LICENCE) -add_custom_target(generated_licence_h - BYPRODUCTS ${GENERATED_LICENCE_H} - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${INTERMEDIATE_LICENCE_H} ${GENERATED_LICENCE_H} - DEPENDS ${INTERMEDIATE_LICENCE_H} - COMMENT "Updating licence.h") - -set(GENERATED_COMMIT_C ${GENERATED_SOURCES_DIR}/cmake_commit.c) -set(INTERMEDIATE_COMMIT_C ${GENERATED_COMMIT_C}.tmp) -add_custom_target(check_git_commit - BYPRODUCTS ${INTERMEDIATE_COMMIT_C} - COMMAND ${CMAKE_COMMAND} - -DGIT_EXECUTABLE=${GIT_EXECUTABLE} - -DOUTPUT_FILE=${INTERMEDIATE_COMMIT_C} - -DOUTPUT_TYPE=header - -P ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake - DEPENDS ${CMAKE_SOURCE_DIR}/cmake/gitcommit.cmake - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - COMMENT "Checking current git commit") -add_custom_target(cmake_commit_c - BYPRODUCTS ${GENERATED_COMMIT_C} - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${INTERMEDIATE_COMMIT_C} ${GENERATED_COMMIT_C} - DEPENDS check_git_commit ${INTERMEDIATE_COMMIT_C} - COMMENT "Updating cmake_commit.c") - -if(CMAKE_VERSION VERSION_LESS 3.12) - function(add_compile_definitions) - foreach(i ${ARGN}) - add_compile_options(-D${i}) - endforeach() - endfunction() -endif() - -function(add_sources_from_current_dir target) - set(sources) - foreach(i ${ARGN}) - set(sources ${sources} ${CMAKE_CURRENT_SOURCE_DIR}/${i}) - endforeach() - target_sources(${target} PRIVATE ${sources}) -endfunction() - -set(extra_dirs) -if(CMAKE_SYSTEM_NAME MATCHES "Windows" OR WINELIB) - set(platform windows) -else() - set(platform unix) -endif() - -function(be_list TARGET NAME) - cmake_parse_arguments(OPT "SSH;SERIAL;OTHERBACKENDS" "" "" "${ARGN}") - add_library(${TARGET}-be-list OBJECT ${CMAKE_SOURCE_DIR}/be_list.c) - foreach(setting SSH SERIAL OTHERBACKENDS) - if(OPT_${setting}) - target_compile_definitions(${TARGET}-be-list PRIVATE ${setting}=1) - else() - target_compile_definitions(${TARGET}-be-list PRIVATE ${setting}=0) - endif() - endforeach() - target_compile_definitions(${TARGET}-be-list PRIVATE APPNAME=${NAME}) - target_sources(${TARGET} PRIVATE $) -endfunction() - -include(cmake/platforms/${platform}.cmake) - -include_directories( - ${CMAKE_CURRENT_SOURCE_DIR} - ${GENERATED_SOURCES_DIR} - ${platform} - ${extra_dirs}) - -if(PUTTY_DEBUG) - add_compile_definitions(DEBUG) -endif() -if(PUTTY_FUZZING) - add_compile_definitions(FUZZING) -endif() -if(NOT PUTTY_COMPRESS_SCROLLBACK) - set(NO_SCROLLBACK_COMPRESSION ON) -endif() -if(PUTTY_COVERAGE) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs -ftest-coverage -g ") -endif() diff --git a/cmake/toolchain-mingw.cmake b/cmake/toolchain-mingw.cmake deleted file mode 100644 index 2e0bc6690..000000000 --- a/cmake/toolchain-mingw.cmake +++ /dev/null @@ -1,12 +0,0 @@ -# Simple toolchain file for cross-building Windows PuTTY on Linux -# using MinGW (tested on Ubuntu). - -set(CMAKE_SYSTEM_NAME Windows) -set(CMAKE_SYSTEM_PROCESSOR x86_64) - -set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) -set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres) -set(CMAKE_AR x86_64-w64-mingw32-ar) -set(CMAKE_RANLIB x86_64-w64-mingw32-ranlib) - -add_compile_definitions(__USE_MINGW_ANSI_STDIO) diff --git a/cmake/toolchain-winegcc.cmake b/cmake/toolchain-winegcc.cmake deleted file mode 100644 index 73a9e53e0..000000000 --- a/cmake/toolchain-winegcc.cmake +++ /dev/null @@ -1,33 +0,0 @@ -# Toolchain file for cross-building a Winelib version of Windows PuTTY -# on Linux, using winegcc (tested on Ubuntu). - -# Winelib is weird because it's basically compiling ordinary Linux -# objects and executables, but we want to pretend to be Windows for -# purposes of (a) having resource files, and (b) selecting the Windows -# platform subdirectory. -# -# So, do we tag this as a weird kind of Windows build, or a weird kind -# of Linux build? Either way we have to do _something_ out of the -# ordinary. -# -# After some experimentation, it seems to make more sense to treat -# Winelib builds as basically Linux, and set a flag WINELIB that -# PuTTY's main build scripts will detect and handle specially. -# Specifically, that flag will cause cmake/setup.cmake to select the -# Windows platform (overriding the usual check of CMAKE_SYSTEM_NAME), -# and also trigger a call to enable_language(RC), which for some kind -# of cmake re-entrancy reason we can't do in this toolchain file -# itself. -set(CMAKE_SYSTEM_NAME Linux) -set(WINELIB ON) - -# We need a wrapper script around winegcc proper, because cmake's link -# command lines will refer to system libraries as "-lkernel32.lib" -# rather than the required "-lkernel32". The winegcc script alongside -# this toolchain file bodges that command-line translation. -set(CMAKE_C_COMPILER ${CMAKE_SOURCE_DIR}/cmake/winegcc) - -set(CMAKE_RC_COMPILER wrc) -set(CMAKE_RC_OUTPUT_EXTENSION .res.o) -set(CMAKE_RC_COMPILE_OBJECT - " -o ") diff --git a/cmake/winegcc b/cmake/winegcc deleted file mode 100644 index afa17de61..000000000 --- a/cmake/winegcc +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh - -# Wrapper for winegcc that allows it to be used in a build generated -# from PuTTY's CMakeLists.txt, by bodging around the command-line -# options that CMake gets wrong. - -init=true -link=true -for arg in init "$@"; do - if $init; then - set -- - init=false - continue - fi - - case "$arg" in - # The Windows build definition for PuTTY specifies all the - # system API libraries by names like kernel32.lib. When CMake - # reads that file and thinks it's compiling for Linux, it will - # generate link options such as -lkernel32.lib. But in fact - # winegcc expects -lkernel32, so we need to strip the ".lib" - # suffix. - -l*.lib) set -- "$@" "${arg%.lib}";; - - # Options that mean we're not linking. - -E | -S | -c) link=false set -- "$@" "$arg";; - - # Anything else, we leave unchanged. - *) set -- "$@" "$arg";; - esac -done - -if $link; then - # winegcc requires this library for _wfopen - set -- "$@" -lucrtbase -fi - -echo "$@" >&2 -exec winegcc "$@" diff --git a/cmdgen.c b/cmdgen.c deleted file mode 100644 index b12758a10..000000000 --- a/cmdgen.c +++ /dev/null @@ -1,1655 +0,0 @@ -/* - * cmdgen.c - command-line form of PuTTYgen - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshkeygen.h" -#include "mpint.h" - -static FILE *progress_fp = NULL; -static bool linear_progress_phase; -static unsigned last_progress_col; - -static ProgressPhase cmdgen_progress_add_linear( - ProgressReceiver *prog, double c) -{ - ProgressPhase ph = { .n = 0 }; - return ph; -} - -static ProgressPhase cmdgen_progress_add_probabilistic( - ProgressReceiver *prog, double c, double p) -{ - ProgressPhase ph = { .n = 1 }; - return ph; -} - -static void cmdgen_progress_start_phase(ProgressReceiver *prog, - ProgressPhase p) -{ - linear_progress_phase = (p.n == 0); - last_progress_col = 0; -} -static void cmdgen_progress_report(ProgressReceiver *prog, double p) -{ - unsigned new_col = p * 64 + 0.5; - for (; last_progress_col < new_col; last_progress_col++) - fputc('+', progress_fp); -} -static void cmdgen_progress_report_attempt(ProgressReceiver *prog) -{ - if (progress_fp) { - fputc('+', progress_fp); - fflush(progress_fp); - } -} -static void cmdgen_progress_report_phase_complete(ProgressReceiver *prog) -{ - if (linear_progress_phase) - cmdgen_progress_report(prog, 1.0); - if (progress_fp) { - fputc('\n', progress_fp); - fflush(progress_fp); - } -} - -static const ProgressReceiverVtable cmdgen_progress_vt = { - .add_linear = cmdgen_progress_add_linear, - .add_probabilistic = cmdgen_progress_add_probabilistic, - .ready = null_progress_ready, - .start_phase = cmdgen_progress_start_phase, - .report = cmdgen_progress_report, - .report_attempt = cmdgen_progress_report_attempt, - .report_phase_complete = cmdgen_progress_report_phase_complete, -}; - -static ProgressReceiver cmdgen_progress = { .vt = &cmdgen_progress_vt }; - -/* - * Stubs to let everything else link sensibly. - */ -char *x_get_default(const char *key) -{ - return NULL; -} -void sk_cleanup(void) -{ -} - -void showversion(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("puttygen: %s\n%s\n", ver, buildinfo_text); - sfree(buildinfo_text); -} - -void usage(bool standalone) -{ - fprintf(standalone ? stderr : stdout, - "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. - */ - printf("PuTTYgen: key generator and converter for the PuTTY tools\n" - "%s\n", ver); - usage(false); - printf(" -t specify key type when generating:\n" - " eddsa, ecdsa, rsa, dsa, rsa1 use with -b\n" - " ed25519, ed448 special cases of eddsa\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-openssh-new export OpenSSH private key " - "(force new format)\n" - " private-sshcom export ssh.com private key\n" - " public RFC 4716 / ssh.com public key\n" - " public-openssh OpenSSH public key\n" - " fingerprint output the key fingerprint\n" - " cert-info print certificate information\n" - " text output the key components as " - "'name=0x####'\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" - " --cert-info equivalent to `-O cert-info'\n" - " --dump equivalent to `-O text'\n" - " -E fptype specify fingerprint output type:\n" - " sha256, md5, sha256-cert, md5-cert\n" - " --certificate file incorporate a certificate into the key\n" - " --remove-certificate remove any certificate from the key\n" - " --reencrypt load a key and save it with fresh " - "encryption\n" - " --old-passphrase file\n" - " specify file containing old key passphrase\n" - " --new-passphrase file\n" - " specify file containing new key passphrase\n" - " --random-device device\n" - " specify device to read entropy from (e.g. /dev/urandom)\n" - " --primes select prime-generation method:\n" - " probable conventional probabilistic prime finding\n" - " proven numbers that have been proven to be prime\n" - " proven-even also try harder for an even distribution\n" - " --strong-rsa use \"strong\" primes as RSA key factors\n" - " --ppk-param =[,=,...]\n" - " specify parameters when writing PuTTY private key file " - "format:\n" - " version PPK format version (min 2, max 3, " - "default 3)\n" - " kdf key derivation function (argon2id, " - "argon2i, argon2d)\n" - " memory Kbyte of memory to use in passphrase " - "hash\n" - " (default 8192)\n" - " time approx milliseconds to hash for " - "(default 100)\n" - " passes number of hash passes to run " - "(alternative to 'time')\n" - " parallelism number of parallelisable threads in the " - "hash function\n" - " (default 1)\n" - ); -} - -static bool 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 *readpassphrase(const char *filename) -{ - FILE *fp; - char *line; - - fp = fopen(filename, "r"); - if (!fp) { - fprintf(stderr, "puttygen: cannot open %s: %s\n", - filename, strerror(errno)); - return NULL; - } - line = fgetline(fp); - if (line) - line[strcspn(line, "\r\n")] = '\0'; - else if (ferror(fp)) - fprintf(stderr, "puttygen: error reading from %s: %s\n", - filename, strerror(errno)); - else /* empty file */ - line = dupstr(""); - fclose(fp); - return line; -} - -#define DEFAULT_RSADSA_BITS 2048 - -static void spr_error(SeatPromptResult spr) -{ - if (spr.kind == SPRK_SW_ABORT) { - char *err = spr_get_error_message(spr); - fprintf(stderr, "puttygen: unable to read passphrase: %s", err); - sfree(err); - } -} - -/* For Unix in particular, but harmless if this main() is reused elsewhere */ -const bool buildinfo_gtk_relevant = false; - -int main(int argc, char **argv) -{ - char *infile = NULL; - Filename *infilename = NULL, *outfilename = NULL; - LoadedFile *infile_lf = NULL; - BinarySource *infile_bs = NULL; - enum { NOKEYGEN, RSA1, RSA2, DSA, ECDSA, EDDSA } keytype = NOKEYGEN; - char *outfile = NULL, *outfiletmp = NULL; - enum { PRIVATE, PUBLIC, PUBLICO, FP, OPENSSH_AUTO, - OPENSSH_NEW, SSHCOM, TEXT, CERTINFO } outtype = PRIVATE; - int bits = -1; - const char *comment = NULL; - char *origcomment = NULL; - bool change_passphrase = false, reencrypt = false; - bool errs = false, nogo = false; - int intype = SSH_KEYTYPE_UNOPENABLE; - int sshver = 0; - ssh2_userkey *ssh2key = NULL; - RSAKey *ssh1key = NULL; - strbuf *ssh2blob = NULL; - char *ssh2alg = NULL; - char *old_passphrase = NULL, *new_passphrase = NULL; - bool load_encrypted; - const char *random_device = NULL; - char *certfile = NULL; - bool remove_cert = false; - int exit_status = 0; - const PrimeGenerationPolicy *primegen = &primegen_probabilistic; - bool strong_rsa = false; - ppk_save_parameters params = ppk_save_default_parameters; - FingerprintType fptype = SSH_FPTYPE_DEFAULT; - - if (is_interactive()) - progress_fp = stderr; - - #define RETURN(status) do { exit_status = (status); goto out; } while (0) - - /* ------------------------------------------------------------------ - * 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[0] == '-' && p[1]) { - /* - * 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; - } - } else if (!strcmp(opt, "-old-passphrase")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - old_passphrase = readpassphrase(val); - if (!old_passphrase) - errs = true; - } - } else if (!strcmp(opt, "-new-passphrase")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - new_passphrase = readpassphrase(val); - if (!new_passphrase) - errs = true; - } - } else if (!strcmp(opt, "-random-device")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - random_device = val; - } - } else if (!strcmp(opt, "-dump")) { - outtype = TEXT; - } else if (!strcmp(opt, "-cert-info") || - !strcmp(opt, "-certinfo") || - !strcmp(opt, "-cert_info")) { - outtype = CERTINFO; - } else if (!strcmp(opt, "-primes")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else if (!strcmp(val, "probable") || - !strcmp(val, "probabilistic")) { - primegen = &primegen_probabilistic; - } else if (!strcmp(val, "provable") || - !strcmp(val, "proven") || - !strcmp(val, "simple") || - !strcmp(val, "maurer-simple")) { - primegen = &primegen_provable_maurer_simple; - } else if (!strcmp(val, "provable-even") || - !strcmp(val, "proven-even") || - !strcmp(val, "even") || - !strcmp(val, "complex") || - !strcmp(val, "maurer-complex")) { - primegen = &primegen_provable_maurer_complex; - } else { - errs = true; - fprintf(stderr, "puttygen: unrecognised prime-" - "generation mode `%s'\n", val); - } - } else if (!strcmp(opt, "-strong-rsa")) { - strong_rsa = true; - } else if (!strcmp(opt, "-certificate")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - certfile = val; - } - } else if (!strcmp(opt, "-remove-certificate")) { - remove_cert = true; - } else if (!strcmp(opt, "-reencrypt")) { - reencrypt = true; - } else if (!strcmp(opt, "-ppk-param") || - !strcmp(opt, "-ppk-params")) { - if (!val && argc > 1) - --argc, val = *++argv; - if (!val) { - errs = true; - fprintf(stderr, "puttygen: option `-%s'" - " expects an argument\n", opt); - } else { - char *nextval; - for (; val; val = nextval) { - nextval = strchr(val, ','); - if (nextval) - *nextval++ = '\0'; - - char *optvalue = strchr(val, '='); - if (!optvalue) { - errs = true; - fprintf(stderr, "puttygen: PPK parameter " - "'%s' expected a value\n", val); - continue; - } - *optvalue++ = '\0'; - - /* Non-numeric options */ - if (!strcmp(val, "kdf")) { - if (!strcmp(optvalue, "Argon2id") || - !strcmp(optvalue, "argon2id")) { - params.argon2_flavour = Argon2id; - } else if (!strcmp(optvalue, "Argon2i") || - !strcmp(optvalue, "argon2i")) { - params.argon2_flavour = Argon2i; - } else if (!strcmp(optvalue, "Argon2d") || - !strcmp(optvalue, "argon2d")) { - params.argon2_flavour = Argon2d; - } else { - errs = true; - fprintf(stderr, "puttygen: unrecognise" - "d kdf '%s'\n", optvalue); - } - continue; - } - - char *end; - unsigned long n = strtoul(optvalue, &end, 0); - if (!*optvalue || *end) { - errs = true; - fprintf(stderr, "puttygen: value '%s' for " - "PPK parameter '%s': expected a " - "number\n", optvalue, val); - continue; - } - - if (!strcmp(val, "version")) { - params.fmt_version = n; - } else if (!strcmp(val, "memory") || - !strcmp(val, "mem")) { - params.argon2_mem = n; - } else if (!strcmp(val, "time")) { - params.argon2_passes_auto = true; - params.argon2_milliseconds = n; - } else if (!strcmp(val, "passes")) { - params.argon2_passes_auto = false; - params.argon2_passes = n; - } else if (!strcmp(val, "parallelism") || - !strcmp(val, "parallel")) { - params.argon2_parallelism = n; - } else { - errs = true; - fprintf(stderr, "puttygen: unrecognised " - "PPK parameter '%s'\n", val); - continue; - } - } - } - } 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': - progress_fp = NULL; - break; - } - break; - case 't': - case 'b': - case 'C': - case 'O': - case 'o': - case 'E': - /* - * 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 if (!strcmp(p, "ecdsa")) - keytype = ECDSA, sshver = 2; - else if (!strcmp(p, "eddsa")) - keytype = EDDSA, sshver = 2; - else if (!strcmp(p, "ed25519")) - keytype = EDDSA, bits = 255, sshver = 2; - else if (!strcmp(p, "ed448")) - keytype = EDDSA, bits = 448, 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_AUTO, sshver = 2; - else if (!strcmp(p, "private-openssh-new")) - outtype = OPENSSH_NEW, sshver = 2; - else if (!strcmp(p, "private-sshcom")) - outtype = SSHCOM, sshver = 2; - else if (!strcmp(p, "text")) - outtype = TEXT; - else if (!strcmp(p, "cert-info")) - outtype = CERTINFO; - else { - fprintf(stderr, - "puttygen: unknown output type `%s'\n", p); - errs = true; - } - break; - case 'o': - outfile = p; - break; - case 'E': - if (!strcmp(p, "md5")) - fptype = SSH_FPTYPE_MD5; - else if (!strcmp(p, "sha256")) - fptype = SSH_FPTYPE_SHA256; - else if (!strcmp(p, "md5-cert")) - fptype = SSH_FPTYPE_MD5_CERT; - else if (!strcmp(p, "sha256-cert")) - fptype = SSH_FPTYPE_SHA256_CERT; - else { - fprintf(stderr, "puttygen: unknown fingerprint " - "type `%s'\n", p); - errs = true; - } - 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 (bits == -1) { - /* - * No explicit key size was specified. Default varies - * depending on key type. - */ - switch (keytype) { - case ECDSA: - bits = 384; - break; - case EDDSA: - bits = 255; - break; - default: - bits = DEFAULT_RSADSA_BITS; - break; - } - } - - if (keytype == ECDSA || keytype == EDDSA) { - const char *name = (keytype == ECDSA ? "ECDSA" : "EdDSA"); - const int *valid_lengths = (keytype == ECDSA ? ec_nist_curve_lengths : - ec_ed_curve_lengths); - size_t n_lengths = (keytype == ECDSA ? n_ec_nist_curve_lengths : - n_ec_ed_curve_lengths); - bool (*alg_and_curve_by_bits)(int, const struct ec_curve **, - const ssh_keyalg **) = - (keytype == ECDSA ? ec_nist_alg_and_curve_by_bits : - ec_ed_alg_and_curve_by_bits); - - const struct ec_curve *curve; - const ssh_keyalg *alg; - - if (!alg_and_curve_by_bits(bits, &curve, &alg)) { - fprintf(stderr, "puttygen: invalid bits for %s, choose", name); - for (size_t i = 0; i < n_lengths; i++) - fprintf(stderr, "%s%d", (i == 0 ? " " : - i == n_lengths-1 ? " or " : ", "), - valid_lengths[i]); - fputc('\n', stderr); - errs = true; - } - } - - if (keytype == RSA2 || keytype == RSA1 || keytype == DSA) { - if (bits < 256) { - fprintf(stderr, "puttygen: cannot generate %s keys shorter than" - " 256 bits\n", (keytype == DSA ? "DSA" : "RSA")); - errs = true; - } else if (bits < DEFAULT_RSADSA_BITS) { - fprintf(stderr, "puttygen: warning: %s keys shorter than" - " %d bits are probably not secure\n", - (keytype == DSA ? "DSA" : "RSA"), DEFAULT_RSADSA_BITS); - /* but this is just a warning, so proceed anyway */ - } - } - - 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_AUTO && - outtype != OPENSSH_NEW && outtype != SSHCOM && outtype != TEXT)) { - 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) { - const char *load_error; - - infilename = filename_from_str(infile); - if (!strcmp(infile, "-")) - infile_lf = lf_load_keyfile_fp(stdin, &load_error); - else - infile_lf = lf_load_keyfile(infilename, &load_error); - - if (!infile_lf) { - fprintf(stderr, "puttygen: unable to load file `%s': %s\n", - infile, load_error); - RETURN(1); - } - - infile_bs = BinarySource_UPCAST(infile_lf); - intype = key_type_s(infile_bs); - BinarySource_REWIND(infile_bs); - - switch (intype) { - 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: - case SSH_KEYTYPE_SSH1_PUBLIC: - 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_SSH2_PUBLIC_RFC4716: - case SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH: - case SSH_KEYTYPE_OPENSSH_PEM: - case SSH_KEYTYPE_OPENSSH_NEW: - 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; - - case SSH_KEYTYPE_OPENSSH_AUTO: - default: - unreachable("Should never see these types on an input file"); - } - } - - /* - * 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_PEM && outtype == OPENSSH_AUTO) || - (intype == SSH_KEYTYPE_OPENSSH_NEW && outtype == OPENSSH_NEW) || - (intype == SSH_KEYTYPE_SSHCOM && outtype == SSHCOM)) { - if (!outfile) { - outfile = infile; - outfiletmp = dupcat(outfile, ".tmp"); - } - - if (!change_passphrase && !comment && !reencrypt && !certfile && - !remove_cert) { - 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_AUTO || - outtype == OPENSSH_NEW || 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 (a) we need to write out - * a private key format, (b) the entire input key file is - * encrypted, or (c) we're outputting TEXT, in which case we - * want all of the input file including private material if it - * exists. - */ - bool intype_entirely_encrypted = - intype == SSH_KEYTYPE_OPENSSH_PEM || - intype == SSH_KEYTYPE_OPENSSH_NEW || - intype == SSH_KEYTYPE_SSHCOM; - bool intype_has_private = - !(intype == SSH_KEYTYPE_SSH1_PUBLIC || - intype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || - intype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH); - bool outtype_has_private = - outtype == PRIVATE || outtype == OPENSSH_AUTO || - outtype == OPENSSH_NEW || outtype == SSHCOM; - if (outtype_has_private || intype_entirely_encrypted || - (outtype == TEXT && intype_has_private)) - load_encrypted = true; - else - load_encrypted = false; - - if (load_encrypted && !intype_has_private) { - fprintf(stderr, "puttygen: cannot perform this action on a " - "public-key-only input file\n"); - RETURN(1); - } - - /* - * Check consistency properties relating to certificates. - */ - if (certfile && !(sshver == 2 && intype_has_private && - outtype_has_private && infile)) { - fprintf(stderr, "puttygen: certificates can only be added to " - "existing SSH-2 private key files\n"); - RETURN(1); - } - if (remove_cert && !(sshver == 2 && infile)) { - fprintf(stderr, "puttygen: certificates can only be removed from " - "existing SSH-2 key files\n"); - RETURN(1); - } - if (certfile && remove_cert) { - fprintf(stderr, "puttygen: cannot both add and remove a " - "certificate\n"); - RETURN(1); - } - - /* ------------------------------------------------------------------ - * 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; - - tm = ltime(); - if (keytype == DSA) - strftime(default_comment, 30, "dsa-key-%Y%m%d", &tm); - else if (keytype == ECDSA) - strftime(default_comment, 30, "ecdsa-key-%Y%m%d", &tm); - else if (keytype == EDDSA && bits == 255) - strftime(default_comment, 30, "ed25519-key-%Y%m%d", &tm); - else if (keytype == EDDSA) - strftime(default_comment, 30, "eddsa-key-%Y%m%d", &tm); - else - strftime(default_comment, 30, "rsa-key-%Y%m%d", &tm); - - entropy = get_random_data(bits / 8, random_device); - if (!entropy) { - fprintf(stderr, "puttygen: failed to collect entropy, " - "could not generate key\n"); - RETURN(1); - } - random_setup_special(); - random_reseed(make_ptrlen(entropy, bits / 8)); - smemclr(entropy, bits/8); - sfree(entropy); - - PrimeGenerationContext *pgc = primegen_new_context(primegen); - - if (keytype == DSA) { - struct dsa_key *dsakey = snew(struct dsa_key); - dsa_generate(dsakey, bits, pgc, &cmdgen_progress); - ssh2key = snew(ssh2_userkey); - ssh2key->key = &dsakey->sshk; - ssh1key = NULL; - } else if (keytype == ECDSA) { - struct ecdsa_key *ek = snew(struct ecdsa_key); - ecdsa_generate(ek, bits); - ssh2key = snew(ssh2_userkey); - ssh2key->key = &ek->sshk; - ssh1key = NULL; - } else if (keytype == EDDSA) { - struct eddsa_key *ek = snew(struct eddsa_key); - eddsa_generate(ek, bits); - ssh2key = snew(ssh2_userkey); - ssh2key->key = &ek->sshk; - ssh1key = NULL; - } else { - RSAKey *rsakey = snew(RSAKey); - rsa_generate(rsakey, bits, strong_rsa, pgc, &cmdgen_progress); - rsakey->comment = NULL; - if (keytype == RSA1) { - ssh1key = rsakey; - } else { - ssh2key = snew(ssh2_userkey); - ssh2key->key = &rsakey->sshk; - } - } - - primegen_free_context(pgc); - - if (ssh2key) - ssh2key->comment = dupstr(default_comment); - if (ssh1key) - ssh1key->comment = dupstr(default_comment); - - } else { - const char *error = NULL; - bool encrypted; - - assert(infile != NULL); - - sfree(origcomment); - origcomment = NULL; - - /* - * Find out whether the input key is encrypted. - */ - if (intype == SSH_KEYTYPE_SSH1) - encrypted = rsa1_encrypted_s(infile_bs, &origcomment); - else if (intype == SSH_KEYTYPE_SSH2) - encrypted = ppk_encrypted_s(infile_bs, &origcomment); - else - encrypted = import_encrypted_s(infilename, infile_bs, - intype, &origcomment); - BinarySource_REWIND(infile_bs); - - /* - * If so, ask for a passphrase. - */ - if (encrypted && load_encrypted) { - if (!old_passphrase) { - prompts_t *p = new_prompts(); - SeatPromptResult spr; - p->to_server = false; - p->from_server = false; - p->name = dupstr("SSH key passphrase"); - add_prompt(p, dupstr("Enter passphrase to load key: "), false); - spr = console_get_userpass_input(p); - assert(spr.kind != SPRK_INCOMPLETE); - if (spr_is_abort(spr)) { - free_prompts(p); - spr_error(spr); - RETURN(1); - } else { - old_passphrase = prompt_get_result(p->prompts[0]); - free_prompts(p); - } - } - } else { - old_passphrase = NULL; - } - - switch (intype) { - int ret; - - case SSH_KEYTYPE_SSH1: - case SSH_KEYTYPE_SSH1_PUBLIC: - ssh1key = snew(RSAKey); - memset(ssh1key, 0, sizeof(RSAKey)); - if (!load_encrypted) { - strbuf *blob; - BinarySource src[1]; - - sfree(origcomment); - origcomment = NULL; - - blob = strbuf_new(); - - ret = rsa1_loadpub_s(infile_bs, BinarySink_UPCAST(blob), - &origcomment, &error); - BinarySource_BARE_INIT(src, blob->u, blob->len); - get_rsa_ssh1_pub(src, ssh1key, RSA_SSH1_EXPONENT_FIRST); - strbuf_free(blob); - - ssh1key->comment = dupstr(origcomment); - ssh1key->private_exponent = NULL; - ssh1key->p = NULL; - ssh1key->q = NULL; - ssh1key->iqmp = NULL; - } else { - ret = rsa1_load_s(infile_bs, ssh1key, old_passphrase, &error); - } - BinarySource_REWIND(infile_bs); - if (ret > 0) - error = NULL; - else if (!error) - error = "unknown error"; - break; - - case SSH_KEYTYPE_SSH2: - case SSH_KEYTYPE_SSH2_PUBLIC_RFC4716: - case SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH: - if (!load_encrypted) { - sfree(origcomment); - origcomment = NULL; - ssh2blob = strbuf_new(); - if (ppk_loadpub_s(infile_bs, &ssh2alg, - BinarySink_UPCAST(ssh2blob), - &origcomment, &error)) { - const ssh_keyalg *alg = find_pubkey_alg(ssh2alg); - if (alg) - bits = ssh_key_public_bits( - alg, ptrlen_from_strbuf(ssh2blob)); - else - bits = -1; - } else { - strbuf_free(ssh2blob); - ssh2blob = NULL; - } - sfree(ssh2alg); - } else { - ssh2key = ppk_load_s(infile_bs, old_passphrase, &error); - } - BinarySource_REWIND(infile_bs); - 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_PEM: - case SSH_KEYTYPE_OPENSSH_NEW: - case SSH_KEYTYPE_SSHCOM: - ssh2key = import_ssh2_s(infile_bs, intype, old_passphrase, &error); - if (ssh2key) { - if (ssh2key != SSH2_WRONG_PASSPHRASE) - error = NULL; - else - error = "wrong passphrase"; - } else if (!error) - error = "unknown error"; - break; - - default: - unreachable("bad input key type"); - } - - 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); - } - } - - /* - * Swap out the public key for a different one, if asked to. - */ - if (certfile) { - Filename *certfilename = filename_from_str(certfile); - LoadedFile *certfile_lf; - const char *error = NULL; - - if (!strcmp(certfile, "-")) - certfile_lf = lf_load_keyfile_fp(stdin, &error); - else - certfile_lf = lf_load_keyfile(certfilename, &error); - - filename_free(certfilename); - - if (!certfile_lf) { - fprintf(stderr, "puttygen: unable to load certificate file `%s': " - "%s\n", certfile, error); - RETURN(1); - } - - char *algname = NULL; - char *comment = NULL; - strbuf *pub = strbuf_new(); - if (!ppk_loadpub_s(BinarySource_UPCAST(certfile_lf), &algname, - BinarySink_UPCAST(pub), &comment, &error)) { - fprintf(stderr, "puttygen: unable to load certificate file `%s': " - "%s\n", certfile, error); - strbuf_free(pub); - sfree(algname); - sfree(comment); - lf_free(certfile_lf); - RETURN(1); - } - - lf_free(certfile_lf); - sfree(comment); - - const ssh_keyalg *alg = find_pubkey_alg(algname); - if (!alg) { - fprintf(stderr, "puttygen: certificate file `%s' has unsupported " - "algorithm name `%s'\n", certfile, algname); - strbuf_free(pub); - sfree(algname); - RETURN(1); - } - - sfree(algname); - - /* Check the two public keys match apart from certificates */ - strbuf *old_basepub = strbuf_new(); - ssh_key_public_blob(ssh_key_base_key(ssh2key->key), - BinarySink_UPCAST(old_basepub)); - - ssh_key *new_pubkey = ssh_key_new_pub(alg, ptrlen_from_strbuf(pub)); - strbuf *new_basepub = strbuf_new(); - ssh_key_public_blob(ssh_key_base_key(new_pubkey), - BinarySink_UPCAST(new_basepub)); - ssh_key_free(new_pubkey); - - bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(old_basepub), - ptrlen_from_strbuf(new_basepub)); - strbuf_free(old_basepub); - strbuf_free(new_basepub); - - if (!match) { - fprintf(stderr, "puttygen: certificate in `%s' does not match " - "public key in `%s'\n", certfile, infile); - strbuf_free(pub); - RETURN(1); - } - - strbuf *priv = strbuf_new_nm(); - ssh_key_private_blob(ssh2key->key, BinarySink_UPCAST(priv)); - ssh_key *newkey = ssh_key_new_priv( - alg, ptrlen_from_strbuf(pub), ptrlen_from_strbuf(priv)); - strbuf_free(pub); - strbuf_free(priv); - - if (!newkey) { - fprintf(stderr, "puttygen: unable to combine certificate in `%s' " - "with private key\n", certfile); - RETURN(1); - } - - ssh_key_free(ssh2key->key); - ssh2key->key = newkey; - } else if (remove_cert) { - /* - * Removing a certificate can be meaningfully done to a pure - * public key blob, as well as a full key pair. - */ - if (ssh2key) { - ssh_key *newkey = ssh_key_clone(ssh_key_base_key(ssh2key->key)); - ssh_key_free(ssh2key->key); - ssh2key->key = newkey; - } else if (ssh2blob) { - ptrlen algname = pubkey_blob_to_alg_name( - ptrlen_from_strbuf(ssh2blob)); - - const ssh_keyalg *alg = find_pubkey_alg_len(algname); - - if (!alg) { - fprintf(stderr, "puttygen: input file `%s' has unsupported " - "algorithm name `%.*s'\n", infile, - PTRLEN_PRINTF(algname)); - RETURN(1); - } - - ssh_key *tmpkey = ssh_key_new_pub( - alg, ptrlen_from_strbuf(ssh2blob)); - strbuf_clear(ssh2blob); - ssh_key_public_blob(ssh_key_base_key(tmpkey), - BinarySink_UPCAST(ssh2blob)); - ssh_key_free(tmpkey); - } - } - - /* - * Unless we're changing the passphrase, the old one (if any) is a - * reasonable default. - */ - if (!change_passphrase && old_passphrase && !new_passphrase) - new_passphrase = dupstr(old_passphrase); - - /* - * Prompt for a new passphrase if we have been asked to, or if - * we have just generated a key. - * - * In the latter case, an exception is if we're producing text - * output, because that output format doesn't support encryption - * in any case. - */ - if (!new_passphrase && (change_passphrase || - (keytype != NOKEYGEN && outtype != TEXT))) { - prompts_t *p = new_prompts(); - SeatPromptResult spr; - - p->to_server = false; - p->from_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); - spr = console_get_userpass_input(p); - assert(spr.kind != SPRK_INCOMPLETE); - if (spr_is_abort(spr)) { - free_prompts(p); - spr_error(spr); - RETURN(1); - } else { - if (strcmp(prompt_get_result_ref(p->prompts[0]), - prompt_get_result_ref(p->prompts[1]))) { - free_prompts(p); - fprintf(stderr, "puttygen: passphrases do not match\n"); - RETURN(1); - } - new_passphrase = prompt_get_result(p->prompts[0]); - free_prompts(p); - } - } - if (new_passphrase && !*new_passphrase) { - sfree(new_passphrase); - new_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) { - bool ret; - int real_outtype; - - case PRIVATE: - random_ref(); /* we'll need a few random bytes in the save file */ - if (sshver == 1) { - assert(ssh1key); - ret = rsa1_save_f(outfilename, ssh1key, new_passphrase); - if (!ret) { - fprintf(stderr, "puttygen: unable to save SSH-1 private key\n"); - RETURN(1); - } - } else { - assert(ssh2key); - ret = ppk_save_f(outfilename, ssh2key, new_passphrase, ¶ms); - 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: { - FILE *fp; - - if (outfile) { - fp = f_open(outfilename, "w", false); - if (!fp) { - fprintf(stderr, "unable to open output file\n"); - exit(1); - } - } else { - fp = stdout; - } - - if (sshver == 1) { - ssh1_write_pubkey(fp, ssh1key); - } else { - if (!ssh2blob) { - assert(ssh2key); - ssh2blob = strbuf_new(); - ssh_key_public_blob(ssh2key->key, BinarySink_UPCAST(ssh2blob)); - } - - ssh2_write_pubkey(fp, ssh2key ? ssh2key->comment : origcomment, - ssh2blob->s, ssh2blob->len, - (outtype == PUBLIC ? - SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 : - SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)); - } - - if (outfile) - fclose(fp); - - break; - } - - case FP: { - FILE *fp; - char *fingerprint; - - if (sshver == 1) { - assert(ssh1key); - fingerprint = rsa_ssh1_fingerprint(ssh1key); - } else { - if (ssh2key) { - fingerprint = ssh2_fingerprint(ssh2key->key, fptype); - } else { - assert(ssh2blob); - fingerprint = ssh2_fingerprint_blob( - ptrlen_from_strbuf(ssh2blob), fptype); - } - } - - if (outfile) { - fp = f_open(outfilename, "w", false); - if (!fp) { - fprintf(stderr, "unable to open output file\n"); - exit(1); - } - } else { - fp = stdout; - } - fprintf(fp, "%s\n", fingerprint); - if (outfile) - fclose(fp); - - sfree(fingerprint); - break; - } - - case OPENSSH_AUTO: - case OPENSSH_NEW: - case SSHCOM: - assert(sshver == 2); - assert(ssh2key); - random_ref(); /* both foreign key types require randomness, - * for IV or padding */ - switch (outtype) { - case OPENSSH_AUTO: - real_outtype = SSH_KEYTYPE_OPENSSH_AUTO; - break; - case OPENSSH_NEW: - real_outtype = SSH_KEYTYPE_OPENSSH_NEW; - break; - case SSHCOM: - real_outtype = SSH_KEYTYPE_SSHCOM; - break; - default: - unreachable("control flow goof"); - } - ret = export_ssh2(outfilename, real_outtype, ssh2key, new_passphrase); - if (!ret) { - fprintf(stderr, "puttygen: unable to export key\n"); - RETURN(1); - } - if (outfiletmp) { - if (!move(outfiletmp, outfile)) - RETURN(1); /* rename failed */ - } - break; - - case TEXT: { - key_components *kc; - if (sshver == 1) { - assert(ssh1key); - kc = rsa_components(ssh1key); - } else { - if (ssh2key) { - kc = ssh_key_components(ssh2key->key); - } else { - assert(ssh2blob); - - ptrlen algname = pubkey_blob_to_alg_name( - ptrlen_from_strbuf(ssh2blob)); - const ssh_keyalg *alg = find_pubkey_alg_len(algname); - if (!alg) { - fprintf(stderr, "puttygen: cannot extract key components " - "from public key of unknown type '%.*s'\n", - PTRLEN_PRINTF(algname)); - RETURN(1); - } - ssh_key *sk = ssh_key_new_pub( - alg, ptrlen_from_strbuf(ssh2blob)); - if (!sk) { - fprintf(stderr, "puttygen: unable to decode public key\n"); - RETURN(1); - } - kc = ssh_key_components(sk); - ssh_key_free(sk); - } - } - - FILE *fp; - if (outfile) { - fp = f_open(outfilename, "w", false); - if (!fp) { - fprintf(stderr, "unable to open output file\n"); - exit(1); - } - } else { - fp = stdout; - } - - for (size_t i = 0; i < kc->ncomponents; i++) { - key_component *comp = &kc->components[i]; - fprintf(fp, "%s=", comp->name); - switch (comp->type) { - case KCT_MPINT: { - char *hex = mp_get_hex(comp->mp); - fprintf(fp, "0x%s\n", hex); - smemclr(hex, strlen(hex)); - sfree(hex); - break; - } - case KCT_TEXT: - fputs("\"", fp); - write_c_string_literal(fp, ptrlen_from_strbuf(comp->str)); - fputs("\"\n", fp); - break; - case KCT_BINARY: { - /* - * Display format for binary key components is to show - * them as base64, with a wrapper so that the actual - * printed string is along the lines of - * 'b64("aGVsbG8sIHdvcmxkCg==")'. - * - * That's a compromise between not being too verbose - * for a human reader, and still being reasonably - * friendly to people pasting the output of this - * 'puttygen --dump' option into Python code (which - * the format is designed to permit in general). - * - * Python users pasting a dump containing one of these - * will have to define a function 'b64' in advance - * which takes a string, which you can do most easily - * using this import statement, as seen in - * cryptsuite.py: - * - * from base64 import b64decode as b64 - */ - fputs("b64(\"", fp); - char b64[4]; - for (size_t j = 0; j < comp->str->len; j += 3) { - size_t len = comp->str->len - j; - if (len > 3) len = 3; - base64_encode_atom(comp->str->u + j, len, b64); - fwrite(b64, 1, 4, fp); - } - fputs("\")\n", fp); - break; - } - default: - unreachable("bad key component type"); - } - } - - if (outfile) - fclose(fp); - key_components_free(kc); - break; - } - - case CERTINFO: { - if (sshver == 1) { - fprintf(stderr, "puttygen: SSH-1 keys cannot contain " - "certificates\n"); - RETURN(1); - } - - const ssh_keyalg *alg; - ssh_key *sk; - bool sk_allocated = false; - - if (ssh2key) { - sk = ssh2key->key; - alg = ssh_key_alg(sk); - } else { - assert(ssh2blob); - ptrlen algname = pubkey_blob_to_alg_name( - ptrlen_from_strbuf(ssh2blob)); - alg = find_pubkey_alg_len(algname); - if (!alg) { - fprintf(stderr, "puttygen: cannot extract certificate info " - "from public key of unknown type '%.*s'\n", - PTRLEN_PRINTF(algname)); - RETURN(1); - } - sk = ssh_key_new_pub(alg, ptrlen_from_strbuf(ssh2blob)); - if (!sk) { - fprintf(stderr, "puttygen: unable to decode public key\n"); - RETURN(1); - } - sk_allocated = true; - } - - if (!alg->is_certificate) { - fprintf(stderr, "puttygen: key is not a certificate\n"); - } else { - SeatDialogText *text = ssh_key_cert_info(sk); - - FILE *fp; - if (outfile) { - fp = f_open(outfilename, "w", false); - if (!fp) { - fprintf(stderr, "unable to open output file\n"); - exit(1); - } - } else { - fp = stdout; - } - - for (SeatDialogTextItem *item = text->items, - *end = item+text->nitems; item < end; item++) { - switch (item->type) { - case SDT_MORE_INFO_KEY: - fprintf(fp, "%s", item->text); - break; - case SDT_MORE_INFO_VALUE_SHORT: - fprintf(fp, ": %s\n", item->text); - break; - case SDT_MORE_INFO_VALUE_BLOB: - fprintf(fp, ":\n%s\n", item->text); - break; - default: - break; - } - } - - if (outfile) - fclose(fp); - - seat_dialog_text_free(text); - } - - if (sk_allocated) - ssh_key_free(sk); - break; - } - } - - out: - - #undef RETURN - - if (old_passphrase) { - smemclr(old_passphrase, strlen(old_passphrase)); - sfree(old_passphrase); - } - if (new_passphrase) { - smemclr(new_passphrase, strlen(new_passphrase)); - sfree(new_passphrase); - } - - if (ssh1key) { - freersakey(ssh1key); - sfree(ssh1key); - } - if (ssh2key && ssh2key != SSH2_WRONG_PASSPHRASE) { - sfree(ssh2key->comment); - if (ssh2key->key) - ssh_key_free(ssh2key->key); - sfree(ssh2key); - } - if (ssh2blob) - strbuf_free(ssh2blob); - sfree(origcomment); - if (infilename) - filename_free(infilename); - if (infile_lf) - lf_free(infile_lf); - if (outfilename) - filename_free(outfilename); - sfree(outfiletmp); - - return exit_status; -} diff --git a/cmdline.c b/cmdline.c deleted file mode 100644 index b5a36eb04..000000000 --- a/cmdline.c +++ /dev/null @@ -1,974 +0,0 @@ -/* - * 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; - size_t 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(const char *p, const char *value, int pri) -{ - sgrowarray(saves[pri].params, saves[pri].savesize, saves[pri].nsaved); - saves[pri].params[saves[pri].nsaved].p = dupstr(p); - saves[pri].params[saves[pri].nsaved].value = dupstr(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 seat_get_userpass_input(), except that here a - * SPR(K)_INCOMPLETE return means that we aren't capable of processing - * the prompt and someone else should do it. - */ -SeatPromptResult cmdline_get_passwd_input( - prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable) -{ - /* - * 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. Also, we don't use a - * command-line password for any kind of prompt which is destined - * for local use rather than to be sent to the server: the idea is - * to pre-fill _passwords_, not private-key passphrases (for which - * there are better alternatives available). - */ - if (p->n_prompts != 1 || p->prompts[0]->echo || !p->to_server) { - return SPR_INCOMPLETE; - } - - /* - * If we've tried once, return utter failure (no more passwords left - * to try). - */ - if (state->tried) - return SPR_SW_ABORT("Configured password was not accepted"); - - /* - * If we never had a password available in the first place, we - * can't do anything in any case. (But we delay this test until - * after trying once, so that even if we free cmdline_password - * below, we'll still remember that we _used_ to have one.) - */ - if (!cmdline_password) - return SPR_INCOMPLETE; - - prompt_set_result(p->prompts[0], cmdline_password); - state->tried = true; - - if (!restartable) { - /* - * If there's no possibility of needing to do this again after - * a 'Restart Session' event, then wipe our copy of the - * password out of memory. - */ - smemclr(cmdline_password, strlen(cmdline_password)); - sfree(cmdline_password); - cmdline_password = NULL; - } - - return SPR_OK; -} - -static bool cmdline_check_unavailable(int flag, const char *p) -{ - if (cmdline_tooltype & flag) { - cmdline_error("option \"%s\" not available in this tool", p); - return true; - } - return false; -} - -#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) - -static bool seen_hostname_argument = false; -static bool seen_port_argument = false; -static bool seen_verbose_option = false; -static bool loaded_session = false; -bool cmdline_verbose(void) { return seen_verbose_option; } -bool cmdline_seat_verbose(Seat *seat) { return cmdline_verbose(); } -bool cmdline_lp_verbose(LogPolicy *lp) { return cmdline_verbose(); } -bool cmdline_loaded_session(void) { return loaded_session; } - -static void set_protocol(Conf *conf, int protocol) -{ - settings_set_default_protocol(protocol); - conf_set_int(conf, CONF_protocol, protocol); -} - -static void set_port(Conf *conf, int port) -{ - settings_set_default_port(port); - conf_set_int(conf, CONF_port, port); -} - -int cmdline_process_param(const char *p, char *value, - int need_save, Conf *conf) -{ - int ret = 0; - - if (p[0] != '-') { - if (need_save < 0) - return 0; - - /* - * Common handling for the tools whose initial command-line - * arguments specify a hostname to connect to, i.e. PuTTY and - * Plink. Doesn't count the file transfer tools, because their - * hostname specification appears as part of a more - * complicated scheme. - */ - - if ((cmdline_tooltype & TOOLTYPE_HOST_ARG) && - !seen_hostname_argument && - (!(cmdline_tooltype & TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD) || - !loaded_session || !conf_launchable(conf))) { - /* - * Treat this argument as a host name, if we have not yet - * seen a host name argument or -load. - * - * Exception, in some tools (Plink): if we have seen -load - * but it didn't create a launchable session, then we - * still accept a hostname argument following that -load. - * This allows you to make saved sessions that configure - * lots of other stuff (colour schemes, terminal settings - * etc) and then say 'putty -load sessionname hostname'. - * - * Also, we carefully _don't_ test conf for launchability - * if we haven't been explicitly told to load a session - * (otherwise saving a host name into Default Settings - * would cause 'putty' on its own to immediately launch - * the default session and never be able to do anything - * else). - */ - if (!strncmp(p, "telnet:", 7)) { - /* - * If the argument starts with "telnet:", set the - * protocol to Telnet and process the string as a - * Telnet URL. - */ - - /* - * Skip the "telnet:" or "telnet://" prefix. - */ - p += 7; - if (p[0] == '/' && p[1] == '/') - p += 2; - conf_set_int(conf, CONF_protocol, PROT_TELNET); - - /* - * The next thing we expect is a host name. - */ - { - const char *host = p; - char *buf; - - p += host_strcspn(p, ":/"); - buf = dupprintf("%.*s", (int)(p - host), host); - conf_set_str(conf, CONF_host, buf); - sfree(buf); - seen_hostname_argument = true; - } - - /* - * If the host name is followed by a colon, then - * expect a port number after it. - */ - if (*p == ':') { - p++; - - conf_set_int(conf, CONF_port, atoi(p)); - /* - * Set the flag that will stop us from treating - * the next argument as a separate port; this one - * counts as explicitly provided. - */ - seen_port_argument = true; - } else { - conf_set_int(conf, CONF_port, -1); - } - } else { - char *user = NULL, *hostname = NULL; - const char *hostname_after_user; - int port_override = -1; - size_t len; - - /* - * Otherwise, treat it as a bare host name. - */ - - if (cmdline_tooltype & TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX) { - /* - * Here Plink checks for a comma-separated - * protocol prefix, e.g. 'ssh,hostname' or - * 'ssh,user@hostname'. - * - * I'm not entirely sure why; this behaviour dates - * from 2000 and isn't explained. But I _think_ it - * has to do with CVS transport or similar use - * cases, in which the end user invokes the SSH - * client indirectly, via some means that only - * lets them pass a single string argument, and it - * was occasionally useful to shoehorn the choice - * of protocol into that argument. - */ - const char *comma = strchr(p, ','); - if (comma) { - char *prefix = dupprintf("%.*s", (int)(comma - p), p); - const struct BackendVtable *vt = - backend_vt_from_name(prefix); - - if (vt) { - set_protocol(conf, vt->protocol); - port_override = vt->default_port; - } else { - cmdline_error("unrecognised protocol prefix '%s'", - prefix); - } - - sfree(prefix); - p = comma + 1; - } - } - - hostname_after_user = p; - if (cmdline_tooltype & TOOLTYPE_HOST_ARG_CAN_BE_SESSION) { - /* - * If the hostname argument can also be a saved - * session (see below), then here we also check - * for a user@ prefix, which will override the - * username from the saved session. - * - * (If the hostname argument _isn't_ a saved - * session, we don't do this.) - */ - const char *at = strrchr(p, '@'); - if (at) { - user = dupprintf("%.*s", (int)(at - p), p); - hostname_after_user = at + 1; - } - } - - /* - * Write the whole hostname argument (minus only that - * optional protocol prefix) into the existing Conf, - * for tools that don't treat it as a saved session - * and as a fallback for those that do. - */ - hostname = dupstr(p + strspn(p, " \t")); - len = strlen(hostname); - while (len > 0 && (hostname[len-1] == ' ' || - hostname[len-1] == '\t')) - hostname[--len] = '\0'; - seen_hostname_argument = true; - conf_set_str(conf, CONF_host, hostname); - - if ((cmdline_tooltype & TOOLTYPE_HOST_ARG_CAN_BE_SESSION) && - !loaded_session) { - /* - * For some tools, we equivocate between a - * hostname argument and an argument naming a - * saved session. Here we attempt to load a - * session with the specified name, and if that - * session exists and is launchable, we overwrite - * the entire Conf with it. - * - * We skip this check if a -load option has - * already happened, so that - * - * plink -load non-launchable-session hostname - * - * will treat 'hostname' as a hostname _even_ if a - * saved session called 'hostname' exists. (This - * doesn't lose any functionality someone could - * have needed, because if 'hostname' did cause a - * session to be loaded, then it would overwrite - * everything from the previously loaded session. - * So if that was the behaviour someone wanted, - * then they could get it by leaving off the - * -load completely.) - */ - Conf *conf2 = conf_new(); - if (do_defaults(hostname_after_user, conf2) && - conf_launchable(conf2)) { - conf_copy_into(conf, conf2); - loaded_session = true; - /* And override the username if one was given. */ - if (user) - conf_set_str(conf, CONF_username, user); - } - conf_free(conf2); - } - - sfree(hostname); - sfree(user); - - if (port_override >= 0) - conf_set_int(conf, CONF_port, port_override); - } - - return 1; - } else if ((cmdline_tooltype & TOOLTYPE_PORT_ARG) && - !seen_port_argument) { - /* - * If we've already got a host name from the command line - * (either as a hostname argument or a qualifying -load), - * but not a port number, then treat the next argument as - * a port number. - * - * We handle this by calling ourself recursively to - * pretend we received a -P argument, so that it will be - * deferred until it's a good moment to run it. - */ - char *dup = dupstr(p); /* 'value' is not a const char * */ - int retd = cmdline_process_param("-P", dup, 1, conf); - sfree(dup); - assert(retd == 2); - seen_port_argument = true; - return 1; - } else { - /* - * Refuse to recognise this argument, and give it back to - * the tool's own command-line processing. - */ - return 0; - } - } - -#ifdef PUTTYNG - if (!stricmp(p, "-hwndparent")) { - RETURN(2); - hwnd_parent = atoi(value); - return 2; - } -#endif - - if (!strcmp(p, "-load")) { - RETURN(2); - /* This parameter must be processed immediately rather than being - * saved. */ - do_defaults(value, conf); - loaded_session = true; - return 2; - } - for (size_t i = 0; backends[i]; i++) { - if (p[0] == '-' && !strcmp(p+1, backends[i]->id)) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(0); - set_protocol(conf, backends[i]->protocol); - if (backends[i]->default_port) - set_port(conf, backends[i]->default_port); - if (backends[i]->protocol == PROT_SERIAL) { - /* Special handling: the 'where to connect to' argument will - * have been placed into CONF_host, but for this protocol, it - * needs to be in CONF_serline */ - conf_set_str(conf, CONF_serline, - conf_get_str(conf, CONF_host)); - } - return 1; - } - } - if (!strcmp(p, "-v")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_NO_VERBOSE_OPTION); - seen_verbose_option = true; - } - 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")) { - const char *filename; - FILE *fp; - - RETURN(2); - UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); - SAVEABLE(0); - - filename = value; - - fp = fopen(filename, "r"); - if (!fp) { - cmdline_error("unable to open command file \"%s\"", filename); - return ret; - } - strbuf *command = strbuf_new(); - char readbuf[4096]; - while (1) { - size_t nread = fread(readbuf, 1, sizeof(readbuf), fp); - if (nread == 0) - break; - put_data(command, readbuf, nread); - } - fclose(fp); - conf_set_str(conf, CONF_remote_cmd, command->s); - conf_set_str(conf, CONF_remote_cmd2, ""); - conf_set_bool(conf, CONF_nopty, true); /* command => no terminal */ - strbuf_free(command); - } - if (!strcmp(p, "-P")) { - RETURN(2); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(1); /* lower priority than -ssh, -telnet, etc */ - 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 { - if (cmdline_password) { - smemclr(cmdline_password, strlen(cmdline_password)); - sfree(cmdline_password); - } - - 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, "-pwfile")) { - 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 -pwfile option can only be used with the " - "SSH protocol"); - else { - Filename *fn = filename_from_str(value); - FILE *fp = f_open(fn, "r", false); - if (!fp) { - cmdline_error("unable to open password file '%s'", value); - } else { - if (cmdline_password) { - smemclr(cmdline_password, strlen(cmdline_password)); - sfree(cmdline_password); - } - - cmdline_password = chomp(fgetline(fp)); - if (!cmdline_password) { - cmdline_error("unable to read a password from file '%s'", - value); - } - fclose(fp); - } - filename_free(fn); - } - } - - if (!strcmp(p, "-agent") || !strcmp(p, "-pagent") || - !strcmp(p, "-pageant")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_bool(conf, CONF_tryagent, true); - } - if (!strcmp(p, "-noagent") || !strcmp(p, "-nopagent") || - !strcmp(p, "-nopageant")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_bool(conf, CONF_tryagent, false); - } - - if (!strcmp(p, "-no-trivial-auth")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_bool(conf, CONF_ssh_no_trivial_userauth, true); - } - - if (!strcmp(p, "-share")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_bool(conf, CONF_ssh_connection_sharing, true); - } - if (!strcmp(p, "-noshare")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_bool(conf, CONF_ssh_connection_sharing, false); - } - if (!strcmp(p, "-A")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_bool(conf, CONF_agentfwd, true); - } - if (!strcmp(p, "-a")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_bool(conf, CONF_agentfwd, false); - } - - if (!strcmp(p, "-X")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_bool(conf, CONF_x11_forward, true); - } - if (!strcmp(p, "-x")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_bool(conf, CONF_x11_forward, false); - } - - if (!strcmp(p, "-t")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); - SAVEABLE(1); /* lower priority than -m */ - conf_set_bool(conf, CONF_nopty, false); - } - if (!strcmp(p, "-T")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); - SAVEABLE(1); - conf_set_bool(conf, CONF_nopty, true); - } - - if (!strcmp(p, "-N")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_bool(conf, CONF_ssh_no_shell, true); - } - - if (!strcmp(p, "-C")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_bool(conf, CONF_compression, true); - } - - 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, "-cert")) { - Filename *fn; - RETURN(2); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(0); - fn = filename_from_str(value); - conf_set_filename(conf, CONF_detached_cert, fn); - filename_free(fn); - } - - if (!strcmp(p, "-4") || !strcmp(p, "-ipv4")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(1); - conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV4); - } - if (!strcmp(p, "-6") || !strcmp(p, "-ipv6")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - 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); - } - - if (!strcmp(p, "-logoverwrite")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_int(conf, CONF_logxfovr, LGXF_OVR); - } - - if (!strcmp(p, "-logappend")) { - RETURN(1); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_int(conf, CONF_logxfovr, LGXF_APN); - } - - if (!strcmp(p, "-proxycmd")) { - RETURN(2); - UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); - SAVEABLE(0); - conf_set_int(conf, CONF_proxy_type, PROXY_CMD); - conf_set_str(conf, CONF_proxy_telnet_command, value); - } - -#ifdef _WINDOWS - /* - * Cross-tool options only available on Windows. - */ - if (!strcmp(p, "-restrict-acl") || !strcmp(p, "-restrict_acl") || - !strcmp(p, "-restrictacl")) { - RETURN(1); - restrict_process_acl(); - } -#endif - - return ret; /* unrecognised */ -} - -void cmdline_run_saved(Conf *conf) -{ - for (size_t pri = 0; pri < NPRIORITIES; pri++) { - for (size_t i = 0; i < saves[pri].nsaved; i++) { - cmdline_process_param(saves[pri].params[i].p, - saves[pri].params[i].value, 0, conf); - sfree(saves[pri].params[i].p); - sfree(saves[pri].params[i].value); - } - saves[pri].nsaved = 0; - } -} - -bool cmdline_host_ok(Conf *conf) -{ - /* - * Return true if the command-line arguments we've processed in - * TOOLTYPE_HOST_ARG mode are sufficient to justify launching a - * session. - */ - assert(cmdline_tooltype & TOOLTYPE_HOST_ARG); - - /* - * Of course, if we _can't_ launch a session, the answer is - * clearly no. - */ - if (!conf_launchable(conf)) - return false; - - /* - * But also, if we haven't seen either a -load option or a - * hostname argument, i.e. the only saved settings we've loaded - * are Default Settings plus any non-hostname-based stuff from the - * command line, then the answer is still no, _even_ if this Conf - * is launchable. Otherwise, if you saved your favourite hostname - * into Default Settings, then just running 'putty' without - * arguments would connect to it without ever offering you the - * option to connect to something else or change the setting. - */ - if (!seen_hostname_argument && !loaded_session) - return false; - - return true; -} diff --git a/config.c b/config.c deleted file mode 100644 index 8cdeee24d..000000000 --- a/config.c +++ /dev/null @@ -1,3331 +0,0 @@ -/* - * config.c - the platform-independent parts of the PuTTY - * configuration box. - */ - -#include -#include - -#include "putty.h" -#include "dialog.h" -#include "storage.h" -#include "tree234.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(dlgcontrol *ctrl, dlgparam *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->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->context.i, - ctrl->radio.buttondata[button].i); - } -} - -void conf_radiobutton_bool_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - int button; - Conf *conf = (Conf *)data; - - /* - * Same as conf_radiobutton_handler, but using conf_set_bool in - * place of conf_set_int, because it's dealing with a bool-typed - * config option. - */ - if (event == EVENT_REFRESH) { - int val = conf_get_bool(conf, ctrl->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_bool(conf, ctrl->context.i, - ctrl->radio.buttondata[button].i); - } -} - -#define CHECKBOX_INVERT (1<<30) -void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - int key; - bool 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->context.i; - if (key & CHECKBOX_INVERT) { - key &= ~CHECKBOX_INVERT; - invert = true; - } else - invert = false; - - /* - * 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) { - bool val = conf_get_bool(conf, key); - dlg_checkbox_set(ctrl, dlg, (!val ^ !invert)); - } else if (event == EVENT_VALCHANGE) { - conf_set_bool(conf, key, !dlg_checkbox_get(ctrl,dlg) ^ !invert); - } -} - -const struct conf_editbox_handler_type conf_editbox_str = {.type = EDIT_STR}; -const struct conf_editbox_handler_type conf_editbox_int = {.type = EDIT_INT}; - -void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - /* - * The standard edit-box handler expects the main `context' field - * to contain the primary key. The secondary `context2' field is a - * pointer to the struct conf_editbox_handler_type defined in - * putty.h. - */ - int key = ctrl->context.i; - const struct conf_editbox_handler_type *type = ctrl->context2.cp; - Conf *conf = (Conf *)data; - - if (type->type == EDIT_STR) { - 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 (event == EVENT_REFRESH) { - char str[80]; - int value = conf_get_int(conf, key); - if (type->type == EDIT_INT) - sprintf(str, "%d", value); - else - sprintf(str, "%g", (double)value / type->denominator); - dlg_editbox_set(ctrl, dlg, str); - } else if (event == EVENT_VALCHANGE) { - char *str = dlg_editbox_get(ctrl, dlg); - if (type->type == EDIT_INT) - conf_set_int(conf, key, atoi(str)); - else - conf_set_int(conf, key, (int)(type->denominator * atof(str))); - sfree(str); - } - } -} - -void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - int key = ctrl->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(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - int key = ctrl->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(dlgcontrol *ctrl, dlgparam *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(dlgcontrol *ctrl, dlgparam *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 { - dlgcontrol *host, *port, *protradio, *protlist; - bool mid_refresh; -}; - -/* - * Shared handler for protocol radio-button and drop-list controls. - * Handles the interaction of those two controls, and also changes - * the setting of the port box to match the protocol if necessary, - * and refreshes both host and port boxes when switching to/from the - * serial backend. - */ -static void config_protocols_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - int curproto = conf_get_int(conf, CONF_protocol); - struct hostport *hp = (struct hostport *)ctrl->context.p; - - if (event == EVENT_REFRESH) { - /* - * Refresh the states of the controls from Conf. - * - * When refreshing these controls, we have to watch out for - * re-entrancy: because there are two controls involved, the - * refresh is not atomic, so the VALCHANGE and/or SELCHANGE - * callbacks resulting from our updates here might cause other - * settings here to change unwantedly. (E.g. setting the list - * selection shouldn't trigger the SELCHANGE side effect of - * selecting the Other radio button; setting the radio button - * to Other here shouldn't have the side effect of selecting - * whatever protocol is _currently_ selected in the list box, - * if we haven't selected the right one yet.) - */ - hp->mid_refresh = true; - - if (ctrl == hp->protradio) { - /* Available buttons were set up when control was created. - * Just select one of them, possibly. */ - for (int button = 0; button < ctrl->radio.nbuttons; button++) - /* The final button is "Other:". If we reach that one, the - * current protocol must be in the drop list, so we should - * select the "Other:" button. */ - if (curproto == ctrl->radio.buttondata[button].i || - button == ctrl->radio.nbuttons-1) { - dlg_radiobutton_set(ctrl, dlg, button); - break; - } - } else if (ctrl == hp->protlist) { - int curentry = -1; - dlg_update_start(ctrl, dlg); - dlg_listbox_clear(ctrl, dlg); - assert(n_ui_backends > 0 && n_ui_backends < PROTOCOL_LIMIT); - for (size_t i = n_ui_backends; - i < PROTOCOL_LIMIT && backends[i]; i++) { - dlg_listbox_addwithid(ctrl, dlg, - backends[i]->displayname_tc, - backends[i]->protocol); - if (backends[i]->protocol == curproto) - curentry = i - n_ui_backends; - } - if (curentry > 0) { - /* - * The currently configured protocol is one of the - * list-box ones, so select it in protlist. - * - * (The corresponding refresh event for protradio - * should have selected the "Other:" radio button, to - * keep things consistent.) - */ - dlg_listbox_select(ctrl, dlg, curentry); - } else { - /* - * If the currently configured protocol is one of the - * radio buttons, we must still ensure *something* is - * selected in the list box. The sensible default is - * the first list element, which be_*.c ought to have - * arranged to be the 'runner-up' in protocol - * popularity out of the ones relegated to the list - * box. - * - * We don't make much effort to retain the state of - * the list box when it doesn't correspond to an - * actual protocol. So it's easy for this case to be - * reached as a side effect of other actions, e.g. - * loading a saved session that has a radio-button - * protocol configured. - */ - dlg_listbox_select(ctrl, dlg, 0); - } - dlg_update_done(ctrl, dlg); - } - - hp->mid_refresh = false; - } else if (!hp->mid_refresh) { - /* - * Potentially update Conf from the states of the controls. - */ - int newproto = curproto; - - if (event == EVENT_VALCHANGE && ctrl == hp->protradio) { - int button = dlg_radiobutton_get(ctrl, dlg); - assert(button >= 0 && button < ctrl->radio.nbuttons); - if (ctrl->radio.buttondata[button].i == -1) { - /* - * The 'Other' radio button was selected, which means we - * have to set CONF_protocol based on the currently - * selected list box entry. - * - * (We conditionalise this on there _being_ a selected - * list box entry. I hope the case where nothing is - * selected can't actually come up except during - * initialisation, and I also hope that hp->mid_session - * will prevent that case from getting here. But as a - * last-ditch fallback, this if statement should at least - * guarantee that we don't pass a nonsense value to - * dlg_listbox_getid.) - */ - int i = dlg_listbox_index(hp->protlist, dlg); - if (i >= 0) - newproto = dlg_listbox_getid(hp->protlist, dlg, i); - } else { - newproto = ctrl->radio.buttondata[button].i; - } - } else if (event == EVENT_SELCHANGE && ctrl == hp->protlist) { - int i = dlg_listbox_index(ctrl, dlg); - if (i >= 0) { - newproto = dlg_listbox_getid(ctrl, dlg, i); - /* Select the "Other" radio button, too */ - dlg_radiobutton_set(hp->protradio, dlg, - hp->protradio->radio.nbuttons-1); - } - } - - if (newproto != curproto) { - conf_set_int(conf, CONF_protocol, newproto); - - const struct BackendVtable *cvt = backend_vt_from_proto(curproto); - const struct BackendVtable *nvt = backend_vt_from_proto(newproto); - assert(cvt); - assert(nvt); - /* - * 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. - */ - int port = conf_get_int(conf, CONF_port); - if (port == cvt->default_port) - conf_set_int(conf, CONF_port, nvt->default_port); - - dlg_refresh(hp->host, dlg); - dlg_refresh(hp->port, dlg); - } - } -} - -static void loggingbuttons_handler(dlgcontrol *ctrl, dlgparam *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(dlgcontrol *ctrl, dlgparam *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_bool(conf, CONF_nethack_keypad)) - button = 2; - else if (conf_get_bool(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_bool(conf, CONF_app_keypad, false); - conf_set_bool(conf, CONF_nethack_keypad, true); - } else { - conf_set_bool(conf, CONF_app_keypad, (button != 0)); - conf_set_bool(conf, CONF_nethack_keypad, false); - } - } -} - -static void cipherlist_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - if (event == EVENT_REFRESH) { - int i; - - static const struct { const char *s; int c; } ciphers[] = { - { "ChaCha20 (SSH-2 only)", CIPHER_CHACHA20 }, - { "AES-GCM (SSH-2 only)", CIPHER_AESGCM }, - { "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; - const 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(dlgcontrol *ctrl, dlgparam *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(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - if (event == EVENT_REFRESH) { - int i; - - static const struct { const char *s; int k; } kexes[] = { - { "Diffie-Hellman group 1 (1024-bit)", KEX_DHGROUP1 }, - { "Diffie-Hellman group 14 (2048-bit)", KEX_DHGROUP14 }, - { "Diffie-Hellman group 15 (3072-bit)", KEX_DHGROUP15 }, - { "Diffie-Hellman group 16 (4096-bit)", KEX_DHGROUP16 }, - { "Diffie-Hellman group 17 (6144-bit)", KEX_DHGROUP17 }, - { "Diffie-Hellman group 18 (8192-bit)", KEX_DHGROUP18 }, - { "Diffie-Hellman group exchange", KEX_DHGEX }, - { "RSA-based key exchange", KEX_RSA }, - { "ECDH key exchange", KEX_ECDH }, - { "NTRU Prime / Curve25519 hybrid kex", KEX_NTRU_HYBRID }, - { "-- 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; - const 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 hklist_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - if (event == EVENT_REFRESH) { - int i; - - static const struct { const char *s; int k; } hks[] = { - { "Ed25519", HK_ED25519 }, - { "Ed448", HK_ED448 }, - { "ECDSA", HK_ECDSA }, - { "DSA", HK_DSA }, - { "RSA", HK_RSA }, - { "-- warn below here --", HK_WARN } - }; - - /* Set up the "host key preference" box. */ - /* (hklist assumed to contain all algorithms) */ - dlg_update_start(ctrl, dlg); - dlg_listbox_clear(ctrl, dlg); - for (i = 0; i < HK_MAX; i++) { - int k = conf_get_int_int(conf, CONF_ssh_hklist, i); - int j; - const char *kstr = NULL; - for (j = 0; j < lenof(hks); j++) { - if (hks[j].k == k) { - kstr = hks[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 < HK_MAX; i++) - conf_set_int_int(conf, CONF_ssh_hklist, i, - dlg_listbox_getid(ctrl, dlg, i)); - } -} - -static void printerbox_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - if (event == EVENT_REFRESH) { - int nprinters, i; - printer_enum *pe; - const 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(dlgcontrol *ctrl, dlgparam *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(dlgcontrol *ctrl, dlgparam *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->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->context.i, i); - } -} - -static void sshbug_handler_manual_only(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - /* - * This is just like sshbug_handler, except that there's no 'Auto' - * option. Used for bug workaround flags that can't be - * autodetected, and have to be manually enabled if they're to be - * used at all. - */ - Conf *conf = (Conf *)data; - if (event == EVENT_REFRESH) { - int oldconf = conf_get_int(conf, ctrl->context.i); - dlg_update_start(ctrl, dlg); - dlg_listbox_clear(ctrl, dlg); - dlg_listbox_addwithid(ctrl, dlg, "Off", FORCE_OFF); - dlg_listbox_addwithid(ctrl, dlg, "On", FORCE_ON); - switch (oldconf) { - case FORCE_OFF: dlg_listbox_select(ctrl, dlg, 0); break; - case FORCE_ON: dlg_listbox_select(ctrl, dlg, 1); break; - } - dlg_update_done(ctrl, dlg); - } else if (event == EVENT_SELCHANGE) { - int i = dlg_listbox_index(ctrl, dlg); - if (i < 0) - i = FORCE_OFF; - else - i = dlg_listbox_getid(ctrl, dlg, i); - conf_set_int(conf, ctrl->context.i, i); - } -} - -struct sessionsaver_data { - dlgcontrol *editbox, *listbox, *loadbutton, *savebutton, *delbutton; - dlgcontrol *okbutton, *cancelbutton; - struct sesslist sesslist; - bool 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 bool load_selected_session( - struct sessionsaver_data *ssd, - dlgparam *dlg, Conf *conf, bool *maybe_launch) -{ - int i = dlg_listbox_index(ssd->listbox, dlg); - bool isdef; - if (i < 0) { - dlg_beep(dlg); - return false; - } - 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 true; -} - -static void sessionsaver_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - struct sessionsaver_data *ssd = - (struct sessionsaver_data *)ctrl->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) { - bool 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) { - bool 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) && dlg_is_visible(ssd->listbox, dlg)) { - Conf *conf2 = conf_new(); - bool 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 { - dlgcontrol *listbox, *editbox, *button; -}; - -static void charclass_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - struct charclass_data *ccd = - (struct charclass_data *)ctrl->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 { - dlgcontrol *listbox, *redit, *gedit, *bedit, *button; -}; - -/* Array of the user-visible colour names defined in the list macro in - * putty.h */ -static const char *const colours[] = { - #define CONF_COLOUR_NAME_DECL(id,name) name, - CONF_COLOUR_LIST(CONF_COLOUR_NAME_DECL) - #undef CONF_COLOUR_NAME_DECL -}; - -static void colour_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - struct colour_data *cd = - (struct colour_data *)ctrl->context.p; - bool update = false, clear = false; - int 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 { - dlgcontrol *valradio, *valbox, *setbutton, *listbox; -}; - -static void ttymodes_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - struct ttymodes_data *td = - (struct ttymodes_data *)ctrl->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[0] == 'N') ? "(don't send)" - : val+1)); - dlg_listbox_add(ctrl, dlg, disp); - sfree(disp); - } - dlg_update_done(ctrl, dlg); - } else if (ctrl == td->valradio) { - dlg_radiobutton_set(ctrl, dlg, 0); - } - } else if (event == EVENT_SELCHANGE) { - if (ctrl == td->listbox) { - int ind = dlg_listbox_index(td->listbox, dlg); - char *val; - if (ind < 0) { - return; /* no item selected */ - } - val = conf_get_str_str(conf, CONF_ttymodes, - conf_get_str_nthstrkey(conf, CONF_ttymodes, - ind)); - assert(val != NULL); - /* Do this first to defuse side-effects on radio buttons: */ - dlg_editbox_set(td->valbox, dlg, val+1); - dlg_radiobutton_set(td->valradio, dlg, - val[0] == 'A' ? 0 : (val[0] == 'N' ? 1 : 2)); - } - } else if (event == EVENT_VALCHANGE) { - if (ctrl == td->valbox) { - /* If they're editing the text box, we assume they want its - * value to be used. */ - dlg_radiobutton_set(td->valradio, dlg, 2); - } - } else if (event == EVENT_ACTION) { - if (ctrl == td->setbutton) { - int ind = dlg_listbox_index(td->listbox, dlg); - const char *key; - char *str, *val; - char type; - - { - const char types[] = {'A', 'N', 'V'}; - int button = dlg_radiobutton_get(td->valradio, dlg); - assert(button >= 0 && button < lenof(types)); - type = types[button]; - } - - /* Construct new entry */ - if (ind >= 0) { - key = conf_get_str_nthstrkey(conf, CONF_ttymodes, ind); - str = (type == 'V' ? dlg_editbox_get(td->valbox, dlg) - : dupstr("")); - val = dupprintf("%c%s", type, str); - sfree(str); - conf_set_str_str(conf, CONF_ttymodes, key, val); - sfree(val); - dlg_refresh(td->listbox, dlg); - dlg_listbox_select(td->listbox, dlg, ind); - } else { - /* Not a multisel listbox, so this means nothing selected */ - dlg_beep(dlg); - } - } - } -} - -struct environ_data { - dlgcontrol *varbox, *valbox, *addbutton, *rembutton, *listbox; -}; - -static void environ_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - struct environ_data *ed = - (struct environ_data *)ctrl->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); - 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 { - dlgcontrol *addbutton, *rembutton, *listbox; - dlgcontrol *sourcebox, *destbox, *direction; -#ifndef NO_IPV6 - dlgcontrol *addressfamily; -#endif -}; - -static void portfwd_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - struct portfwd_data *pfd = - (struct portfwd_data *)ctrl->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) { - const char *family, *type; - char *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); - 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, *p; - const char *val; - - key = conf_get_str_nthstrkey(conf, CONF_portfwd, i); - if (key) { - static const char *const afs = "A46"; - static const char *const dirs = "LRD"; - const 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 { - dlgcontrol *addbutton, *rembutton, *listbox, *keybox; -}; - -static void manual_hostkey_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - struct manual_hostkey_data *mh = - (struct manual_hostkey_data *)ctrl->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); - } - } -} - -static void clipboard_selector_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - Conf *conf = (Conf *)data; - int setting = ctrl->context.i; -#ifdef NAMED_CLIPBOARDS - int strsetting = ctrl->context2.i; -#endif - - static const struct { - const char *name; - int id; - } options[] = { - {"No action", CLIPUI_NONE}, - {CLIPNAME_IMPLICIT, CLIPUI_IMPLICIT}, - {CLIPNAME_EXPLICIT, CLIPUI_EXPLICIT}, - }; - - if (event == EVENT_REFRESH) { - int i, val = conf_get_int(conf, setting); - - dlg_update_start(ctrl, dlg); - dlg_listbox_clear(ctrl, dlg); - -#ifdef NAMED_CLIPBOARDS - for (i = 0; i < lenof(options); i++) - dlg_listbox_add(ctrl, dlg, options[i].name); - if (val == CLIPUI_CUSTOM) { - const char *sval = conf_get_str(conf, strsetting); - for (i = 0; i < lenof(options); i++) - if (!strcmp(sval, options[i].name)) - break; /* needs escaping */ - if (i < lenof(options) || sval[0] == '=') { - char *escaped = dupcat("=", sval); - dlg_editbox_set(ctrl, dlg, escaped); - sfree(escaped); - } else { - dlg_editbox_set(ctrl, dlg, sval); - } - } else { - dlg_editbox_set(ctrl, dlg, options[0].name); /* fallback */ - for (i = 0; i < lenof(options); i++) - if (val == options[i].id) - dlg_editbox_set(ctrl, dlg, options[i].name); - } -#else - for (i = 0; i < lenof(options); i++) - dlg_listbox_addwithid(ctrl, dlg, options[i].name, options[i].id); - dlg_listbox_select(ctrl, dlg, 0); /* fallback */ - for (i = 0; i < lenof(options); i++) - if (val == options[i].id) - dlg_listbox_select(ctrl, dlg, i); -#endif - dlg_update_done(ctrl, dlg); - } else if (event == EVENT_SELCHANGE -#ifdef NAMED_CLIPBOARDS - || event == EVENT_VALCHANGE -#endif - ) { -#ifdef NAMED_CLIPBOARDS - char *sval = dlg_editbox_get(ctrl, dlg); - int i; - - for (i = 0; i < lenof(options); i++) - if (!strcmp(sval, options[i].name)) { - conf_set_int(conf, setting, options[i].id); - conf_set_str(conf, strsetting, ""); - break; - } - if (i == lenof(options)) { - conf_set_int(conf, setting, CLIPUI_CUSTOM); - if (sval[0] == '=') - sval++; - conf_set_str(conf, strsetting, sval); - } - - sfree(sval); -#else - int index = dlg_listbox_index(ctrl, dlg); - if (index >= 0) { - int val = dlg_listbox_getid(ctrl, dlg, index); - conf_set_int(conf, setting, val); - } -#endif - } -} - -static void clipboard_control(struct controlset *s, const char *label, - char shortcut, int percentage, HelpCtx helpctx, - int setting, int strsetting) -{ -#ifdef NAMED_CLIPBOARDS - ctrl_combobox(s, label, shortcut, percentage, helpctx, - clipboard_selector_handler, I(setting), I(strsetting)); -#else - /* strsetting isn't needed in this case */ - ctrl_droplist(s, label, shortcut, percentage, helpctx, - clipboard_selector_handler, I(setting)); -#endif -} - -static void serial_parity_handler(dlgcontrol *ctrl, dlgparam *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->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 << parities[i].val)) - dlg_listbox_addwithid(ctrl, dlg, parities[i].name, - parities[i].val); - } - for (i = j = 0; i < lenof(parities); i++) { - if (mask & (1 << parities[i].val)) { - 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(dlgcontrol *ctrl, dlgparam *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->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 << flows[i].val)) - dlg_listbox_addwithid(ctrl, dlg, flows[i].name, flows[i].val); - } - for (i = j = 0; i < lenof(flows); i++) { - if (mask & (1 << flows[i].val)) { - 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 proxy_type_handler(dlgcontrol *ctrl, dlgparam *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 proxy_type = conf_get_int(conf, CONF_proxy_type); - - dlg_update_start(ctrl, dlg); - dlg_listbox_clear(ctrl, dlg); - - int index_to_select = 0, current_index = 0; - -#define ADD(id, title) do { \ - dlg_listbox_addwithid(ctrl, dlg, title, id); \ - if (id == proxy_type) \ - index_to_select = current_index; \ - current_index++; \ - } while (0) - - ADD(PROXY_NONE, "None"); - ADD(PROXY_SOCKS5, "SOCKS 5"); - ADD(PROXY_SOCKS4, "SOCKS 4"); - ADD(PROXY_HTTP, "HTTP CONNECT"); - if (ssh_proxy_supported) { - ADD(PROXY_SSH_TCPIP, "SSH to proxy and use port forwarding"); - ADD(PROXY_SSH_EXEC, "SSH to proxy and execute a command"); - ADD(PROXY_SSH_SUBSYSTEM, "SSH to proxy and invoke a subsystem"); - } - if (ctrl->context.i & PROXY_UI_FLAG_LOCAL) { - ADD(PROXY_CMD, "Local (run a subprogram to connect)"); - } - ADD(PROXY_TELNET, "'Telnet' (send an ad-hoc command)"); - -#undef ADD - - dlg_listbox_select(ctrl, dlg, index_to_select); - - 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, CONF_proxy_type, i); - } -} - -static void host_ca_button_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - if (event == EVENT_ACTION) - show_ca_config_box(dp); -} - -void setup_config_box(struct controlbox *b, bool midsession, - int protocol, int protcfginfo) -{ - const struct BackendVtable *backvt; - 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; - dlgcontrol *c; - bool resize_forbidden = false; - 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->column = 3; - ssd->cancelbutton = ctrl_pushbutton(s, "Cancel", 'c', HELPCTX(no_help), - sessionsaver_handler, P(ssd)); - ssd->cancelbutton->button.iscancel = true; - ssd->cancelbutton->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)); - memset(hp, 0, sizeof(*hp)); - - 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->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->column = 1; - hp->port = c; - - ctrl_columns(s, 1, 100); - c = ctrl_text(s, "Connection type:", HELPCTX(session_hostname)); - ctrl_columns(s, 2, 62, 38); - c = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, - HELPCTX(session_hostname), - config_protocols_handler, P(hp)); - c->column = 0; - hp->protradio = c; - c->radio.buttons = sresize(c->radio.buttons, PROTOCOL_LIMIT, char *); - c->radio.shortcuts = sresize(c->radio.shortcuts, PROTOCOL_LIMIT, char); - c->radio.buttondata = sresize(c->radio.buttondata, PROTOCOL_LIMIT, - intorptr); - assert(c->radio.nbuttons == 0); - /* UI design assumes there exists at least one 'real' radio button */ - assert(n_ui_backends > 0 && n_ui_backends < PROTOCOL_LIMIT); - for (size_t i = 0; i < n_ui_backends; i++) { - assert(backends[i]); - c->radio.buttons[c->radio.nbuttons] = - dupstr(backends[i]->displayname_tc); - c->radio.shortcuts[c->radio.nbuttons] = - (backends[i]->protocol == PROT_SSH ? 's' : - backends[i]->protocol == PROT_SERIAL ? 'r' : - backends[i]->protocol == PROT_RAW ? 'w' : /* FIXME unused */ - NO_SHORTCUT); - c->radio.buttondata[c->radio.nbuttons] = - I(backends[i]->protocol); - c->radio.nbuttons++; - } - /* UI design assumes there exists at least one droplist entry */ - assert(backends[c->radio.nbuttons]); - - c->radio.buttons[c->radio.nbuttons] = dupstr("Other:"); - c->radio.shortcuts[c->radio.nbuttons] = 't'; - c->radio.buttondata[c->radio.nbuttons] = I(-1); - c->radio.nbuttons++; - - c = ctrl_droplist(s, NULL, NO_SHORTCUT, 100, - HELPCTX(session_hostname), - config_protocols_handler, P(hp)); - hp->protlist = c; - /* droplist is populated in config_protocols_handler */ - c->column = 1; - - /* Vertically centre the two protocol controls w.r.t. each other */ - hp->protlist->align_next_to = hp->protradio; - - ctrl_columns(s, 1, 100); - } - - /* - * 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->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->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->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->column = 1; - if (!midsession) { - ssd->delbutton = ctrl_pushbutton(s, "Delete", 'd', - HELPCTX(session_saved), - sessionsaver_handler, P(ssd)); - ssd->delbutton->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)); - - /* - * 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. - */ - { - const char *sshlogname, *sshrawlogname; - if ((midsession && protocol == PROT_SSH) || - (!midsession && backend_vt_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)); - } - 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)); - ctrl_checkbox(s, "Flush log file frequently", 'u', - HELPCTX(logging_flush), - conf_checkbox_handler, I(CONF_logflush)); - ctrl_checkbox(s, "Include header", 'i', - HELPCTX(logging_header), - conf_checkbox_handler, I(CONF_logheader)); - - if ((midsession && protocol == PROT_SSH) || - (!midsession && backend_vt_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), ED_STR); - - 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)); - 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)); - - 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_bool_handler, - I(CONF_bksp_is_delete), - "Control-H", I(0), "Control-? (127)", I(1)); - ctrl_radiobuttons(s, "The Home and End keys", 'e', 2, - HELPCTX(keyboard_homeend), - conf_radiobutton_bool_handler, - I(CONF_rxvt_homeend), - "Standard", I(false), "rxvt", I(true)); - ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 4, - HELPCTX(keyboard_funkeys), - conf_radiobutton_handler, - I(CONF_funky_type), - "ESC[n~", I(FUNKY_TILDE), - "Linux", I(FUNKY_LINUX), - "Xterm R6", I(FUNKY_XTERM), - "VT400", I(FUNKY_VT400), - "VT100+", I(FUNKY_VT100P), - "SCO", I(FUNKY_SCO), - "Xterm 216+", I(FUNKY_XTERM_216)); - ctrl_radiobuttons(s, "Shift/Ctrl/Alt with the arrow keys", 'w', 2, - HELPCTX(keyboard_sharrow), - conf_radiobutton_handler, - I(CONF_sharrow_type), - "Ctrl toggles app mode", I(SHARROW_APPLICATION), - "xterm-style bitmap", I(SHARROW_BITMAP)); - - 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_bool_handler, - I(CONF_app_cursor), - "Normal", I(0), "Application", I(1)); - 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)); - - /* - * 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)); - - 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), ED_INT); - - static const struct conf_editbox_handler_type conf_editbox_tickspersec = { - .type = EDIT_FIXEDPOINT, .denominator = TICKSPERSEC}; - - ctrl_editbox(s, "... in this many seconds", 't', 20, - HELPCTX(bell_overload), - conf_editbox_handler, I(CONF_bellovl_t), - CP(&conf_editbox_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), - CP(&conf_editbox_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)); - ctrl_checkbox(s, "Disable remote-controlled clearing of scrollback", 'e', - HELPCTX(features_clearscroll), - conf_checkbox_handler, - I(CONF_no_remote_clearscroll)); - 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_no_arabicshaping)); - ctrl_checkbox(s, "Disable bidirectional text display", - 'd', HELPCTX(features_bidi), conf_checkbox_handler, - I(CONF_no_bidi)); - - /* - * The Window panel. - */ - str = dupprintf("Options controlling %s's window", appname); - ctrl_settitle(b, "Window", str); - sfree(str); - - backvt = backend_vt_from_proto(protocol); - if (backvt) - resize_forbidden = (backvt->flags & BACKEND_RESIZE_FORBIDDEN); - - if (!resize_forbidden || !midsession) { - 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), ED_INT); - c->column = 0; - c = ctrl_editbox(s, "Rows", 'r', 100, - HELPCTX(window_size), - conf_editbox_handler, I(CONF_height),ED_INT); - c->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), ED_INT); - 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)); - 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), ED_INT); - - /* - * 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), ED_STR); - 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)); - ctrl_checkbox(s, "Copy and paste line drawing characters as lqqqk",'d', - HELPCTX(selection_linedraw), - conf_checkbox_handler, I(CONF_rawcnp)); - ctrl_checkbox(s, "Enable VT100 line drawing even in UTF-8 mode",'8', - HELPCTX(translation_utf8linedraw), - conf_checkbox_handler, I(CONF_utf8linedraw)); - - /* - * 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_bool_handler, - I(CONF_rect_select), - "Normal", 'n', I(false), - "Rectangular block", 'r', I(true)); - - s = ctrl_getset(b, "Window/Selection", "clipboards", - "Assign copy/paste actions to clipboards"); - ctrl_checkbox(s, "Auto-copy selected text to " - CLIPNAME_EXPLICIT_OBJECT, - NO_SHORTCUT, HELPCTX(selection_autocopy), - conf_checkbox_handler, I(CONF_mouseautocopy)); - clipboard_control(s, "Mouse paste action:", NO_SHORTCUT, 60, - HELPCTX(selection_clipactions), - CONF_mousepaste, CONF_mousepaste_custom); - clipboard_control(s, "{Ctrl,Shift} + Ins:", NO_SHORTCUT, 60, - HELPCTX(selection_clipactions), - CONF_ctrlshiftins, CONF_ctrlshiftins_custom); - clipboard_control(s, "Ctrl + Shift + {C,V}:", NO_SHORTCUT, 60, - HELPCTX(selection_clipactions), - CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); - - s = ctrl_getset(b, "Window/Selection", "paste", - "Control pasting of text from clipboard to terminal"); - ctrl_checkbox(s, "Permit control characters in pasted text", - NO_SHORTCUT, HELPCTX(selection_pastectrl), - conf_checkbox_handler, I(CONF_paste_controls)); - - /* - * The Window/Selection/Copy panel. - */ - ctrl_settitle(b, "Window/Selection/Copy", - "Options controlling copying from terminal to clipboard"); - - s = ctrl_getset(b, "Window/Selection/Copy", "charclass", - "Classes of character that group together"); - ccd = (struct charclass_data *) - ctrl_alloc(b, sizeof(struct charclass_data)); - ccd->listbox = ctrl_listbox(s, "Character classes:", 'e', - HELPCTX(copy_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(copy_charclasses), - charclass_handler, P(ccd), P(NULL)); - ccd->editbox->column = 0; - ccd->button = ctrl_pushbutton(s, "Set", 's', - HELPCTX(copy_charclasses), - charclass_handler, P(ccd)); - ccd->button->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_checkbox(s, "Allow terminal to use 24-bit colours", '4', - HELPCTX(colours_truecolour), conf_checkbox_handler, - I(CONF_true_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)); - - 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->column = 0; - cd->listbox->listbox.height = 7; - c = ctrl_text(s, "RGB value:", HELPCTX(colours_config)); - c->column = 1; - cd->redit = ctrl_editbox(s, "Red", 'r', 50, HELPCTX(colours_config), - colour_handler, P(cd), P(NULL)); - cd->redit->column = 1; - cd->gedit = ctrl_editbox(s, "Green", 'n', 50, HELPCTX(colours_config), - colour_handler, P(cd), P(NULL)); - cd->gedit->column = 1; - cd->bedit = ctrl_editbox(s, "Blue", 'e', 50, HELPCTX(colours_config), - colour_handler, P(cd), P(NULL)); - cd->bedit->column = 1; - cd->button = ctrl_pushbutton(s, "Modify", 'm', HELPCTX(colours_config), - colour_handler, P(cd)); - cd->button->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), ED_INT); - - 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)); -#endif - - { - const char *label = backend_vt_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), ED_STR); - } - } - - /* - * 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), ED_STR); - { - /* 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_bool_handler, - I(CONF_username_from_env), - "Prompt", I(false), - userlabel, I(true)); - 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), ED_STR); - ctrl_editbox(s, "Terminal speeds", 's', 50, - HELPCTX(connection_termspeed), - conf_editbox_handler, I(CONF_termspeed), ED_STR); - - 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->column = 0; - ed->valbox = ctrl_editbox(s, "Value", 'l', 60, - HELPCTX(telnet_environ), - environ_handler, P(ed), P(NULL)); - ed->valbox->column = 0; - ed->addbutton = ctrl_pushbutton(s, "Add", 'd', - HELPCTX(telnet_environ), - environ_handler, P(ed)); - ed->addbutton->column = 1; - ed->rembutton = ctrl_pushbutton(s, "Remove", 'r', - HELPCTX(telnet_environ), - environ_handler, P(ed)); - ed->rembutton->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); - c = ctrl_droplist(s, "Proxy type:", 't', 70, - HELPCTX(proxy_type), proxy_type_handler, I(0)); - ctrl_columns(s, 2, 80, 20); - c = ctrl_editbox(s, "Proxy hostname", 'y', 100, - HELPCTX(proxy_main), - conf_editbox_handler, - I(CONF_proxy_host), ED_STR); - c->column = 0; - c = ctrl_editbox(s, "Port", 'p', 100, - HELPCTX(proxy_main), - conf_editbox_handler, - I(CONF_proxy_port), - ED_INT); - c->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), ED_STR); - 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)); - ctrl_editbox(s, "Username", 'u', 60, - HELPCTX(proxy_auth), - conf_editbox_handler, - I(CONF_proxy_username), ED_STR); - c = ctrl_editbox(s, "Password", 'w', 60, - HELPCTX(proxy_auth), - conf_editbox_handler, - I(CONF_proxy_password), ED_STR); - c->editbox.password = true; - ctrl_editbox(s, "Command to send to proxy (for some types)", 'm', 100, - HELPCTX(proxy_command), - conf_editbox_handler, - I(CONF_proxy_telnet_command), ED_STR); - - ctrl_radiobuttons(s, "Print proxy diagnostics " - "in the terminal window", 'r', 5, - HELPCTX(proxy_logging), - conf_radiobutton_handler, - I(CONF_proxy_log_to_term), - "No", I(FORCE_OFF), - "Yes", I(FORCE_ON), - "Only until session starts", I(AUTO)); - } - - /* - * Each per-protocol configuration GUI panel is conditionally - * displayed. We don't display it if this binary doesn't contain a - * backend for its protocol at all; we don't display it if we're - * already in mid-session with a different protocol selected; and - * even if we _do_ have this protocol selected, we don't display - * the panel if the protocol doesn't permit any mid-session - * reconfiguration anyway. - */ - -#define DISPLAY_RECONFIGURABLE_PROTOCOL(which_proto) \ - (backend_vt_from_proto(which_proto) && \ - (!midsession || protocol == (which_proto))) -#define DISPLAY_NON_RECONFIGURABLE_PROTOCOL(which_proto) \ - (backend_vt_from_proto(which_proto) && !midsession) - - if (DISPLAY_RECONFIGURABLE_PROTOCOL(PROT_SSH) || - DISPLAY_RECONFIGURABLE_PROTOCOL(PROT_SSHCONN)) { - /* - * 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), ED_STR); - - 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, "SSH protocol version:", NO_SHORTCUT, 2, - HELPCTX(ssh_protocol), - conf_radiobutton_handler, - I(CONF_sshprot), - "2", '2', I(3), - "1 (INSECURE)", '1', I(0)); - } - - /* - * 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 = KEX_MAX; -#ifndef NO_GSSAPI - ctrl_checkbox(s, "Attempt GSSAPI key exchange", - 'k', HELPCTX(ssh_gssapi), - conf_checkbox_handler, - I(CONF_try_gssapi_kex)); -#endif - - 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), - ED_INT); -#ifndef NO_GSSAPI - ctrl_editbox(s, "Minutes between GSS checks (0 for never)", NO_SHORTCUT, 20, - HELPCTX(ssh_kex_repeat), - conf_editbox_handler, - I(CONF_gssapirekey), - ED_INT); -#endif - 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), - ED_STR); - ctrl_text(s, "(Use 1M for 1 megabyte, 1G for 1 gigabyte etc)", - HELPCTX(ssh_kex_repeat)); - } - - /* - * The 'Connection/SSH/Host keys' panel. - */ - if (protcfginfo != 1 && protcfginfo != -1) { - ctrl_settitle(b, "Connection/SSH/Host keys", - "Options controlling SSH host keys"); - - s = ctrl_getset(b, "Connection/SSH/Host keys", "main", - "Host key algorithm preference"); - c = ctrl_draglist(s, "Algorithm selection policy:", 's', - HELPCTX(ssh_hklist), - hklist_handler, P(NULL)); - c->listbox.height = 5; - - ctrl_checkbox(s, "Prefer algorithms for which a host key is known", - 'p', HELPCTX(ssh_hk_known), conf_checkbox_handler, - I(CONF_ssh_prefer_known_hostkeys)); - } - - /* - * 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/Host keys", "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->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->column = 1; - mh->rembutton->delay_taborder = true; - 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->column = 0; - mh->addbutton = ctrl_pushbutton(s, "Add key", 'y', - HELPCTX(ssh_kex_manual_hostkeys), - manual_hostkey_handler, P(mh)); - mh->addbutton->column = 1; - ctrl_columns(s, 1, 100); - } - - /* - * But there's no reason not to forbid access to the host CA - * configuration box, which is common across sessions in any - * case. - */ - s = ctrl_getset(b, "Connection/SSH/Host keys", "ca", - "Configure trusted certification authorities"); - c = ctrl_pushbutton(s, "Configure host CAs", NO_SHORTCUT, - HELPCTX(ssh_kex_cert), - host_ca_button_handler, I(0)); - - 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, "Display pre-authentication banner (SSH-2 only)", - 'd', HELPCTX(ssh_auth_banner), - conf_checkbox_handler, - I(CONF_ssh_show_banner)); - 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, "Disconnect if authentication succeeds trivially", - 'n', HELPCTX(ssh_no_trivial_userauth), - conf_checkbox_handler, - I(CONF_ssh_no_trivial_userauth)); - - 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", "aux", - "Other authentication-related options"); - 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_settitle(b, "Connection/SSH/Auth/Credentials", - "Credentials to authenticate with"); - - s = ctrl_getset(b, "Connection/SSH/Auth/Credentials", "publickey", - "Public-key authentication"); - 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)); - ctrl_filesel(s, "Certificate to use with the private key:", 'e', - NULL, false, "Select certificate file", - HELPCTX(ssh_auth_cert), - conf_filesel_handler, I(CONF_detached_cert)); - - s = ctrl_getset(b, "Connection/SSH/Auth/Credentials", "plugin", - "Plugin to provide authentication responses"); - ctrl_editbox(s, "Plugin command to run", NO_SHORTCUT, 100, - HELPCTX(ssh_auth_plugin), - conf_editbox_handler, I(CONF_auth_plugin), ED_STR); -#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, "Attempt GSSAPI key exchange (SSH-2 only)", - 'k', HELPCTX(ssh_gssapi), - conf_checkbox_handler, - I(CONF_try_gssapi_kex)); - - 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_text(s, "Terminal modes to send:", HELPCTX(ssh_ttymodes)); - td->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, - HELPCTX(ssh_ttymodes), - ttymodes_handler, P(td)); - td->listbox->listbox.height = 8; - 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_columns(s, 2, 75, 25); - c = ctrl_text(s, "For selected mode, send:", HELPCTX(ssh_ttymodes)); - c->column = 0; - td->setbutton = ctrl_pushbutton(s, "Set", 's', - HELPCTX(ssh_ttymodes), - ttymodes_handler, P(td)); - td->setbutton->column = 1; - td->setbutton->delay_taborder = true; - 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, 2, 75, 25); - td->valradio = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, - HELPCTX(ssh_ttymodes), - ttymodes_handler, P(td), - "Auto", NO_SHORTCUT, P(NULL), - "Nothing", NO_SHORTCUT, P(NULL), - "This:", NO_SHORTCUT, P(NULL)); - td->valradio->column = 0; - td->valbox = ctrl_editbox(s, NULL, NO_SHORTCUT, 100, - HELPCTX(ssh_ttymodes), - ttymodes_handler, P(td), P(NULL)); - td->valbox->column = 1; - td->valbox->align_next_to = td->valradio; - ctrl_tabdelay(s, td->setbutton); - } - - 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), ED_STR); - 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)); - } - - /* - * 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->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->column = 2; - pfd->rembutton->delay_taborder = true; - 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->column = 2; - pfd->addbutton->delay_taborder = true; - pfd->sourcebox = ctrl_editbox(s, "Source port", 's', 40, - HELPCTX(ssh_tunnels_portfwd), - portfwd_handler, P(pfd), P(NULL)); - pfd->sourcebox->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)); -#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)); -#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-2 ignore messages", '2', 20, - HELPCTX(ssh_bugs_ignore2), - sshbug_handler, I(CONF_sshbug_ignore2)); - 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, "Chokes on PuTTY's SSH-2 'winadj' requests", 'j', - 20, HELPCTX(ssh_bugs_winadj), - sshbug_handler, I(CONF_sshbug_winadj)); - ctrl_droplist(s, "Replies to requests on closed channels", 'q', 20, - HELPCTX(ssh_bugs_chanreq), - sshbug_handler, I(CONF_sshbug_chanreq)); - ctrl_droplist(s, "Ignores SSH-2 maximum packet size", 'x', 20, - HELPCTX(ssh_bugs_maxpkt2), - sshbug_handler, I(CONF_sshbug_maxpkt2)); - - s = ctrl_getset(b, "Connection/SSH/Bugs", "manual", - "Manually enabled workarounds"); - ctrl_droplist(s, "Discards data sent before its greeting", 'd', 20, - HELPCTX(ssh_bugs_dropstart), - sshbug_handler_manual_only, - I(CONF_sshbug_dropstart)); - ctrl_droplist(s, "Chokes on PuTTY's full KEXINIT", 'p', 20, - HELPCTX(ssh_bugs_filter_kexinit), - sshbug_handler_manual_only, - I(CONF_sshbug_filter_kexinit)); - - 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, "Only supports pre-RFC4419 SSH-2 DH GEX", 'd', 20, - HELPCTX(ssh_bugs_oldgex2), - sshbug_handler, I(CONF_sshbug_oldgex2)); - ctrl_droplist(s, "Miscomputes SSH-2 HMAC keys", 'm', 20, - HELPCTX(ssh_bugs_hmac2), - sshbug_handler, I(CONF_sshbug_hmac2)); - 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, "Miscomputes SSH-2 encryption keys", 'e', 20, - HELPCTX(ssh_bugs_derivekey2), - sshbug_handler, I(CONF_sshbug_derivekey2)); - 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)); - } - } - - if (DISPLAY_RECONFIGURABLE_PROTOCOL(PROT_SERIAL)) { - const BackendVtable *ser_vt = backend_vt_from_proto(PROT_SERIAL); - - /* - * The Connection/Serial panel. - */ - 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), ED_STR); - } - - 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), ED_INT); - ctrl_editbox(s, "Data bits", 'b', 40, - HELPCTX(serial_databits), - conf_editbox_handler, I(CONF_serdatabits), ED_INT); - /* - * Stop bits come in units of one half. - */ - static const struct conf_editbox_handler_type conf_editbox_stopbits = { - .type = EDIT_FIXEDPOINT, .denominator = 2}; - - ctrl_editbox(s, "Stop bits", 't', 40, - HELPCTX(serial_stopbits), - conf_editbox_handler, I(CONF_serstopbits), - CP(&conf_editbox_stopbits)); - ctrl_droplist(s, "Parity", 'p', 40, - HELPCTX(serial_parity), serial_parity_handler, - I(ser_vt->serial_parity_mask)); - ctrl_droplist(s, "Flow control", 'f', 40, - HELPCTX(serial_flow), serial_flow_handler, - I(ser_vt->serial_flow_mask)); - } - - if (DISPLAY_RECONFIGURABLE_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_bool_handler, - I(CONF_rfc_environ), - "BSD (commonplace)", 'b', I(false), - "RFC 1408 (unusual)", 'f', I(true)); - ctrl_radiobuttons(s, "Telnet negotiation mode:", 't', 2, - HELPCTX(telnet_passive), - conf_radiobutton_bool_handler, - I(CONF_passive_telnet), - "Passive", I(true), "Active", I(false)); - } - 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 (DISPLAY_NON_RECONFIGURABLE_PROTOCOL(PROT_RLOGIN)) { - /* - * 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), ED_STR); - - } - - if (DISPLAY_NON_RECONFIGURABLE_PROTOCOL(PROT_SUPDUP)) { - /* - * The Connection/SUPDUP panel. - */ - ctrl_settitle(b, "Connection/SUPDUP", - "Options controlling SUPDUP connections"); - - s = ctrl_getset(b, "Connection/SUPDUP", "main", NULL); - - ctrl_editbox(s, "Location string", 'l', 70, - HELPCTX(supdup_location), - conf_editbox_handler, I(CONF_supdup_location), - ED_STR); - - ctrl_radiobuttons(s, "Extended ASCII Character set:", 'e', 4, - HELPCTX(supdup_ascii), - conf_radiobutton_handler, - I(CONF_supdup_ascii_set), - "None", I(SUPDUP_CHARSET_ASCII), - "ITS", I(SUPDUP_CHARSET_ITS), - "WAITS", I(SUPDUP_CHARSET_WAITS)); - - ctrl_checkbox(s, "**MORE** processing", 'm', - HELPCTX(supdup_more), - conf_checkbox_handler, - I(CONF_supdup_more)); - - ctrl_checkbox(s, "Terminal scrolling", 's', - HELPCTX(supdup_scroll), - conf_checkbox_handler, - I(CONF_supdup_scroll)); - } -} diff --git a/console.c b/console.c deleted file mode 100644 index 62c64afff..000000000 --- a/console.c +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Common pieces between the platform console frontend modules. - */ - -#include -#include - -#include "putty.h" -#include "misc.h" -#include "console.h" - -const char weakcrypto_msg_common_fmt[] = - "The first %s supported by the server is\n" - "%s, which is below the configured warning threshold.\n"; - -const char weakhk_msg_common_fmt[] = - "The first host key type we have stored for this server\n" - "is %s, which is below the configured warning threshold.\n" - "The server also provides the following types of host key\n" - "above the threshold, which we do not have stored:\n" - "%s\n"; - -const char console_continue_prompt[] = "Continue with connection? (y/n) "; -const char console_abandoned_msg[] = "Connection abandoned.\n"; - -const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat) -{ - static const SeatDialogPromptDescriptions descs = { - .hk_accept_action = "enter \"y\"", - .hk_connect_once_action = "enter \"n\"", - .hk_cancel_action = "press Return", - .hk_cancel_action_Participle = "Pressing Return", - }; - return &descs; -} - -bool console_batch_mode = false; - -/* - * Error message and/or fatal exit functions, all based on - * console_print_error_msg which the platform front end provides. - */ -void console_print_error_msg_fmt_v( - const char *prefix, const char *fmt, va_list ap) -{ - char *msg = dupvprintf(fmt, ap); - console_print_error_msg(prefix, msg); - sfree(msg); -} - -void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - console_print_error_msg_fmt_v(prefix, fmt, ap); - va_end(ap); -} - -void modalfatalbox(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - console_print_error_msg_fmt_v("FATAL ERROR", fmt, ap); - va_end(ap); - cleanup_exit(1); -} - -void nonfatal(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - console_print_error_msg_fmt_v("ERROR", fmt, ap); - va_end(ap); -} - -void console_connection_fatal(Seat *seat, const char *msg) -{ - console_print_error_msg("FATAL ERROR", msg); - cleanup_exit(1); -} - -/* - * Console front ends redo their select() or equivalent every time, so - * they don't need separate timer handling. - */ -void timer_change_notify(unsigned long next) -{ -} diff --git a/console.h b/console.h deleted file mode 100644 index f6222e6a9..000000000 --- a/console.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Common pieces between the platform console frontend modules. - */ - -char *hk_absentmsg_common(const char *host, int port, - const char *keytype, const char *fingerprint); -extern const char hk_absentmsg_interactive_intro[]; -extern const char hk_absentmsg_interactive_prompt[]; - -char *hk_wrongmsg_common(const char *host, int port, - const char *keytype, const char *fingerprint); -extern const char hk_wrongmsg_interactive_intro[]; -extern const char hk_wrongmsg_interactive_prompt[]; - -extern const char weakcrypto_msg_common_fmt[]; - -extern const char weakhk_msg_common_fmt[]; - -extern const char console_continue_prompt[]; -extern const char console_abandoned_msg[]; diff --git a/contrib/authplugin-example.py b/contrib/authplugin-example.py deleted file mode 100644 index 5932ad1ad..000000000 --- a/contrib/authplugin-example.py +++ /dev/null @@ -1,290 +0,0 @@ -#!/usr/bin/env python3 - -# This is a demonstration example of how to write a -# keyboard-interactive authentication helper plugin using PuTTY's -# protocol for involving it in SSH connection setup. -# The protocol, and the purpose of an authentication plugin, is -# fully documented in an appendix to the PuTTY manual. - -import io -import os -import struct -import sys - -# Exception class we'll use to get a clean exit on EOF. -class PluginEOF(Exception): pass - -# ---------------------------------------------------------------------- -# -# Marshalling and unmarshalling routines to write and read the -# necessary SSH data types to/from a binary file handle (which can -# include an io.BytesIO if you need to encode/decode in-process). -# -# Error handling is a totally ad-hoc mixture of 'assert' and just -# assuming things will have the right type, or be the right length of -# tuple, or be valid UTF-8. So it should be _robust_, in the sense -# that you'll get a Python exception if anything fails. But no -# sensible error reporting or recovery is implemented. -# -# That should be good enough, because PuTTY will log the plugin's -# standard error in its Event Log, so if the plugin crashes, you'll be -# able to retrieve the traceback. - -def wr_byte(fh, b): - assert 0 <= b < 0x100 - fh.write(bytes([b])) - -def wr_boolean(fh, b): - wr_byte(fh, 1 if b else 0) - -def wr_uint32(fh, u): - assert 0 <= u < 0x100000000 - fh.write(struct.pack(">I", u)) - -def wr_string(fh, s): - wr_uint32(fh, len(s)) - fh.write(s) - -def wr_string_utf8(fh, s): - wr_string(fh, s.encode("UTF-8")) - -def rd_n(fh, n): - data = fh.read(n) - if len(data) < n: - raise PluginEOF() - return data - -def rd_byte(fh): - return rd_n(fh, 1)[0] - -def rd_boolean(fh): - return rd_byte(fh) != 0 - -def rd_uint32(fh): - return struct.unpack(">I", rd_n(fh, 4))[0] - -def rd_string(fh): - length = rd_uint32(fh) - return rd_n(fh, length) - -def rd_string_utf8(fh): - return rd_string(fh).decode("UTF-8") - -# ---------------------------------------------------------------------- -# -# Protocol definitions. - -our_max_version = 2 - -PLUGIN_INIT = 1 -PLUGIN_INIT_RESPONSE = 2 -PLUGIN_PROTOCOL = 3 -PLUGIN_PROTOCOL_ACCEPT = 4 -PLUGIN_PROTOCOL_REJECT = 5 -PLUGIN_AUTH_SUCCESS = 6 -PLUGIN_AUTH_FAILURE = 7 -PLUGIN_INIT_FAILURE = 8 -PLUGIN_KI_SERVER_REQUEST = 20 -PLUGIN_KI_SERVER_RESPONSE = 21 -PLUGIN_KI_USER_REQUEST = 22 -PLUGIN_KI_USER_RESPONSE = 23 - -# ---------------------------------------------------------------------- -# -# Classes to make it easy to construct and receive messages. -# -# OutMessage is constructed with the message type; then you use the -# wr_foo() routines to add fields to it, and finally call its send() -# method. -# -# InMessage is constructed via the expect() class method, to which you -# give a list of message types you expect to see one of at this stage. -# Once you've got one, you can rd_foo() fields from it. - -class OutMessage: - def __init__(self, msgtype): - self.buf = io.BytesIO() - wr_byte(self.buf, msgtype) - self.write = self.buf.write - - def send(self, fh=sys.stdout.buffer): - wr_string(fh, self.buf.getvalue()) - fh.flush() - -class InMessage: - @classmethod - def expect(cls, expected_types, fh=sys.stdin.buffer): - self = cls() - self.buf = io.BytesIO(rd_string(fh)) - self.msgtype = rd_byte(self.buf) - self.read = self.buf.read - - if self.msgtype not in expected_types: - raise ValueError("received packet type {:d}, expected {}".format( - self.msgtype, ",".join(map("{:d}".format, - sorted(expected_types))))) - return self - -# ---------------------------------------------------------------------- -# -# The main implementation of the protocol. - -def protocol(): - # Start by expecting PLUGIN_INIT. - msg = InMessage.expect({PLUGIN_INIT}) - their_version = rd_uint32(msg) - hostname = rd_string_utf8(msg) - port = rd_uint32(msg) - username = rd_string_utf8(msg) - print(f"Got hostname {hostname!r}, port {port!r}", file=sys.stderr, - flush=True) - - # Decide which protocol version we're speaking. - version = min(their_version, our_max_version) - assert version != 0, "Protocol version 0 does not exist" - - if "TESTPLUGIN_INIT_FAIL" in os.environ: - # Test the plugin failing at startup time. - msg = OutMessage(PLUGIN_INIT_FAILURE) - wr_string_utf8(msg, os.environ["TESTPLUGIN_INIT_FAIL"]) - msg.send() - return - - # Send INIT_RESPONSE, with our protocol version and an overridden - # username. - # - # By default this test plugin doesn't override the username, but - # you can make it do so by setting TESTPLUGIN_USERNAME in the - # environment. - msg = OutMessage(PLUGIN_INIT_RESPONSE) - wr_uint32(msg, version) - wr_string_utf8(msg, os.environ.get("TESTPLUGIN_USERNAME", "")) - msg.send() - - # Outer loop run once per authentication protocol. - while True: - # Expect a message telling us what the protocol is. - msg = InMessage.expect({PLUGIN_PROTOCOL}) - method = rd_string(msg) - - if "TESTPLUGIN_PROTO_REJECT" in os.environ: - # Test the plugin failing at PLUGIN_PROTOCOL time. - msg = OutMessage(PLUGIN_PROTOCOL_REJECT) - wr_string_utf8(msg, os.environ["TESTPLUGIN_PROTO_REJECT"]) - msg.send() - continue - - # We only support keyboard-interactive. If we supported other - # auth methods, this would be the place to add further clauses - # to this if statement for them. - if method == b"keyboard-interactive": - msg = OutMessage(PLUGIN_PROTOCOL_ACCEPT) - msg.send() - - # Inner loop run once per keyboard-interactive exchange - # with the SSH server. - while True: - # Expect a set of prompts from the server, or - # terminate the loop on SUCCESS or FAILURE. - # - # (We could also respond to SUCCESS or FAILURE by - # updating caches of our own, if we had any that were - # useful.) - msg = InMessage.expect({PLUGIN_KI_SERVER_REQUEST, - PLUGIN_AUTH_SUCCESS, - PLUGIN_AUTH_FAILURE}) - if (msg.msgtype == PLUGIN_AUTH_SUCCESS or - msg.msgtype == PLUGIN_AUTH_FAILURE): - break - - # If we didn't just break, we're sitting on a - # PLUGIN_KI_SERVER_REQUEST message. Get all its bits - # and pieces out. - name = rd_string_utf8(msg) - instructions = rd_string_utf8(msg) - language = rd_string(msg) - nprompts = rd_uint32(msg) - prompts = [] - for i in range(nprompts): - prompt = rd_string_utf8(msg) - echo = rd_boolean(msg) - prompts.append((prompt, echo)) - - # Actually make up some answers for the prompts. This - # is the part that a non-example implementation would - # do very differently, of course! - # - # Here, we answer "foo" to every prompt, except that - # if there are exactly two prompts in the packet then - # we answer "stoat" to the first and "weasel" to the - # second. - # - # (These answers are consistent with the ones required - # by PuTTY's test SSH server Uppity in its own - # keyboard-interactive test implementation: that - # presents a two-prompt packet and expects - # "stoat","weasel" as the answers, and then presents a - # zero-prompt packet. So this test plugin will get you - # through Uppity's k-i in a one-touch manner. The - # "foo" in this code isn't used by Uppity at all; I - # just include it because I had to have _some_ - # handling for the else clause.) - # - # If TESTPLUGIN_PROMPTS is set in the environment, we - # ask the user questions of our own by sending them - # back to PuTTY as USER_REQUEST messages. - if nprompts == 2: - if "TESTPLUGIN_PROMPTS" in os.environ: - for i in range(2): - # Make up some questions to ask. - msg = OutMessage(PLUGIN_KI_USER_REQUEST) - wr_string_utf8( - msg, "Plugin request #{:d} (name)".format(i)) - wr_string_utf8( - msg, "Plugin request #{:d} (instructions)" - .format(i)) - wr_string(msg, b"") - wr_uint32(msg, 2) - wr_string_utf8(msg, "Prompt 1 of 2 (echo): ") - wr_boolean(msg, True) - wr_string_utf8(msg, "Prompt 2 of 2 (no echo): ") - wr_boolean(msg, False) - msg.send() - - # Expect the answers. - msg = InMessage.expect({PLUGIN_KI_USER_RESPONSE}) - user_nprompts = rd_uint32(msg) - assert user_nprompts == 2, ( - "Should match what we just sent") - for i in range(nprompts): - user_response = rd_string_utf8(msg) - # We don't actually check these - # responses for anything. - - answers = ["stoat", "weasel"] - - else: - answers = ["foo"] * nprompts - - # Send the answers to the SSH server's questions. - msg = OutMessage(PLUGIN_KI_SERVER_RESPONSE) - wr_uint32(msg, len(answers)) - for answer in answers: - wr_string_utf8(msg, answer) - msg.send() - - else: - # Default handler if we don't speak the offered protocol - # at all. - msg = OutMessage(PLUGIN_PROTOCOL_REJECT) - wr_string_utf8(msg, "") - msg.send() - -# Demonstration write to stderr, to prove that it shows up in PuTTY's -# Event Log. -print("Hello from test plugin's stderr", file=sys.stderr, flush=True) - -try: - protocol() -except PluginEOF: - pass diff --git a/contrib/cygtermd/README b/contrib/cygtermd/README deleted file mode 100644 index a722fe10a..000000000 --- a/contrib/cygtermd/README +++ /dev/null @@ -1,11 +0,0 @@ -This directory contains 'cygtermd', a small and specialist Telnet -server designed to act as middleware between PuTTY and a Cygwin shell -session running on the same machine, so that PuTTY can act as an -xterm-alike for Cygwin. - -To install it, you must compile it from source using Cygwin gcc, -install it in Cygwin's /bin, and configure PuTTY to use it as a local -proxy process. For detailed instructions, see the PuTTY Wishlist page -at - -https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/cygwin-terminal-window.html diff --git a/contrib/cygtermd/main.c b/contrib/cygtermd/main.c deleted file mode 100644 index 7bfd49f0e..000000000 --- a/contrib/cygtermd/main.c +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Main program. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "sel.h" -#include "pty.h" -#include "telnet.h" - -int signalpipe[2]; - -sel *asel; -sel_rfd *netr, *ptyr, *sigr; -int ptyfd; -sel_wfd *netw, *ptyw; -Telnet *telnet; - -#define BUF 65536 - -void sigchld(int signum) -{ - write(signalpipe[1], "C", 1); -} - -void fatal(const char *fmt, ...) -{ - va_list ap; - fprintf(stderr, "cygtermd: "); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, "\n"); - exit(1); -} - -void net_readdata(sel_rfd *rfd, void *data, size_t len) -{ - if (len == 0) - exit(0); /* EOF on network - client went away */ - telnet_from_net(telnet, data, len); - if (sel_write(netw, NULL, 0) > BUF) - sel_rfd_freeze(ptyr); - if (sel_write(ptyw, NULL, 0) > BUF) - sel_rfd_freeze(netr); -} - -void net_readerr(sel_rfd *rfd, int error) -{ - fprintf(stderr, "standard input: read: %s\n", strerror(errno)); - exit(1); -} - -void net_written(sel_wfd *wfd, size_t bufsize) -{ - if (bufsize < BUF) - sel_rfd_unfreeze(ptyr); -} - -void net_writeerr(sel_wfd *wfd, int error) -{ - fprintf(stderr, "standard input: write: %s\n", strerror(errno)); - exit(1); -} - -void pty_readdata(sel_rfd *rfd, void *data, size_t len) -{ - if (len == 0) - exit(0); /* EOF on pty */ - telnet_from_pty(telnet, data, len); - if (sel_write(netw, NULL, 0) > BUF) - sel_rfd_freeze(ptyr); - if (sel_write(ptyw, NULL, 0) > BUF) - sel_rfd_freeze(netr); -} - -void pty_readerr(sel_rfd *rfd, int error) -{ - if (error == EIO) /* means EOF, on a pty */ - exit(0); - fprintf(stderr, "pty: read: %s\n", strerror(errno)); - exit(1); -} - -void pty_written(sel_wfd *wfd, size_t bufsize) -{ - if (bufsize < BUF) - sel_rfd_unfreeze(netr); -} - -void pty_writeerr(sel_wfd *wfd, int error) -{ - fprintf(stderr, "pty: write: %s\n", strerror(errno)); - exit(1); -} - -void sig_readdata(sel_rfd *rfd, void *data, size_t len) -{ - char *p = data; - - while (len > 0) { - if (*p == 'C') { - int status; - waitpid(-1, &status, WNOHANG); - if (WIFEXITED(status) || WIFSIGNALED(status)) - exit(0); /* child process vanished */ - } - } -} - -void sig_readerr(sel_rfd *rfd, int error) -{ - fprintf(stderr, "signal pipe: read: %s\n", strerror(errno)); - exit(1); -} - -int main(int argc, char **argv) -{ - int ret; - int shell_started = 0; - char *directory = NULL; - char **program_args = NULL; - - if (argc > 1 && argv[1][0]) { - directory = argv[1]; - argc--, argv++; - } - if (argc > 1) { - program_args = argv + 1; - } - - pty_preinit(); - - asel = sel_new(NULL); - netr = sel_rfd_add(asel, 0, net_readdata, net_readerr, NULL); - netw = sel_wfd_add(asel, 1, net_written, net_writeerr, NULL); - ptyr = sel_rfd_add(asel, -1, pty_readdata, pty_readerr, NULL); - ptyw = sel_wfd_add(asel, -1, pty_written, pty_writeerr, NULL); - - telnet = telnet_new(netw, ptyw); - - if (pipe(signalpipe) < 0) { - perror("pipe"); - return 1; - } - sigr = sel_rfd_add(asel, signalpipe[0], sig_readdata, - sig_readerr, NULL); - - signal(SIGCHLD, sigchld); - - do { - struct shell_data shdata; - - ret = sel_iterate(asel, -1); - if (!shell_started && telnet_shell_ok(telnet, &shdata)) { - ptyfd = run_program_in_pty(&shdata, directory, program_args); - sel_rfd_setfd(ptyr, ptyfd); - sel_wfd_setfd(ptyw, ptyfd); - shell_started = 1; - } - } while (ret == 0); - - return 0; -} diff --git a/contrib/cygtermd/malloc.c b/contrib/cygtermd/malloc.c deleted file mode 100644 index 6bad344e3..000000000 --- a/contrib/cygtermd/malloc.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * malloc.c: implementation of malloc.h - */ - -#include -#include - -#include "malloc.h" - -extern void fatal(const char *, ...); - -void *smalloc(size_t size) { - void *p; - p = malloc(size); - if (!p) { - fatal("out of memory"); - } - return p; -} - -void sfree(void *p) { - if (p) { - free(p); - } -} - -void *srealloc(void *p, size_t size) { - void *q; - if (p) { - q = realloc(p, size); - } else { - q = malloc(size); - } - if (!q) - fatal("out of memory"); - return q; -} - -char *dupstr(const char *s) { - char *r = smalloc(1+strlen(s)); - strcpy(r,s); - return r; -} diff --git a/contrib/cygtermd/malloc.h b/contrib/cygtermd/malloc.h deleted file mode 100644 index 8cd4b30a1..000000000 --- a/contrib/cygtermd/malloc.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * malloc.h: safe wrappers around malloc, realloc, free, strdup - */ - -#ifndef UMLWRAP_MALLOC_H -#define UMLWRAP_MALLOC_H - -#include - -/* - * smalloc should guarantee to return a useful pointer - Halibut - * can do nothing except die when it's out of memory anyway. - */ -void *smalloc(size_t size); - -/* - * srealloc should guaranteeably be able to realloc NULL - */ -void *srealloc(void *p, size_t size); - -/* - * sfree should guaranteeably deal gracefully with freeing NULL - */ -void sfree(void *p); - -/* - * dupstr is like strdup, but with the never-return-NULL property - * of smalloc (and also reliably defined in all environments :-) - */ -char *dupstr(const char *s); - -/* - * snew allocates one instance of a given type, and casts the - * result so as to type-check that you're assigning it to the - * right kind of pointer. Protects against allocation bugs - * involving allocating the wrong size of thing. - */ -#define snew(type) \ - ( (type *) smalloc (sizeof (type)) ) - -/* - * snewn allocates n instances of a given type, for arrays. - */ -#define snewn(number, type) \ - ( (type *) smalloc ((number) * sizeof (type)) ) - -/* - * sresize wraps realloc so that you specify the new number of - * elements and the type of the element, with the same type- - * checking advantages. Also type-checks the input pointer. - */ -#define sresize(array, number, type) \ - ( (void)sizeof((array)-(type *)0), \ - (type *) srealloc ((array), (number) * sizeof (type)) ) - -#endif /* UMLWRAP_MALLOC_H */ diff --git a/contrib/cygtermd/pty.c b/contrib/cygtermd/pty.c deleted file mode 100644 index 9ae1e48f0..000000000 --- a/contrib/cygtermd/pty.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * pty.c - pseudo-terminal handling - */ - -#define _XOPEN_SOURCE 500 -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "pty.h" -#include "malloc.h" - -static char ptyname[FILENAME_MAX]; -int master = -1; - -void pty_preinit(void) -{ - /* - * Allocate the pty. - */ - master = open("/dev/ptmx", O_RDWR); - if (master < 0) { - perror("/dev/ptmx: open"); - exit(1); - } - - if (grantpt(master) < 0) { - perror("grantpt"); - exit(1); - } - - if (unlockpt(master) < 0) { - perror("unlockpt"); - exit(1); - } -} - -void pty_resize(int w, int h) -{ - struct winsize sz; - - assert(master >= 0); - - sz.ws_row = h; - sz.ws_col = w; - sz.ws_xpixel = sz.ws_ypixel = 0; - ioctl(master, TIOCSWINSZ, &sz); -} - -int run_program_in_pty(const struct shell_data *shdata, - char *directory, char **program_args) -{ - int slave, pid; - char *fallback_args[2]; - - assert(master >= 0); - - ptyname[FILENAME_MAX-1] = '\0'; - strncpy(ptyname, ptsname(master), FILENAME_MAX-1); - -#if 0 - { - struct winsize ws; - struct termios ts; - - /* - * FIXME: think up some good defaults here - */ - - if (!ioctl(0, TIOCGWINSZ, &ws)) - ioctl(master, TIOCSWINSZ, &ws); - if (!tcgetattr(0, &ts)) - tcsetattr(master, TCSANOW, &ts); - } -#endif - - slave = open(ptyname, O_RDWR | O_NOCTTY); - if (slave < 0) { - perror("slave pty: open"); - return 1; - } - - /* - * Fork and execute the command. - */ - pid = fork(); - if (pid < 0) { - perror("fork"); - return 1; - } - - if (pid == 0) { - int i, fd; - - /* - * We are the child. - */ - close(master); - - fcntl(slave, F_SETFD, 0); /* don't close on exec */ - dup2(slave, 0); - dup2(slave, 1); - if (slave != 0 && slave != 1) - close(slave); - dup2(1, 2); - setsid(); - setpgrp(); - i = 0; -#ifdef TIOCNOTTY - if ((fd = open("/dev/tty", O_RDWR)) >= 0) { - ioctl(fd, TIOCNOTTY, &i); - close(fd); - } -#endif - /* - * Make the new pty our controlling terminal. On some OSes - * this is done with TIOCSCTTY; Cygwin doesn't have that, so - * instead it's done by simply opening the pty without - * O_NOCTTY. This code is primarily intended for Cygwin, but - * it's useful to have it work in other contexts for testing - * purposes, so I leave the TIOCSCTTY here anyway. - */ - if ((fd = open(ptyname, O_RDWR)) >= 0) { -#ifdef TIOCSCTTY - ioctl(fd, TIOCSCTTY, &i); -#endif - close(fd); - } else { - perror("slave pty: open"); - exit(127); - } - tcsetpgrp(0, getpgrp()); - - for (i = 0; i < shdata->nenvvars; i++) - putenv(shdata->envvars[i]); - if (shdata->termtype) - putenv(shdata->termtype); - - if (directory) - chdir(directory); - - /* - * Use the provided shell program name, if the user gave - * one. Failing that, use $SHELL; failing that, look up - * the user's default shell in the password file; failing - * _that_, revert to the bog-standard /bin/sh. - */ - if (!program_args) { - char *shell; - - shell = getenv("SHELL"); - if (!shell) { - const char *login; - uid_t uid; - struct passwd *pwd; - - /* - * For maximum generality in the face of multiple - * /etc/passwd entries with different login names and - * shells but a shared uid, we start by using - * getpwnam(getlogin()) if it's available - but we - * insist that its uid must match our real one, or we - * give up and fall back to getpwuid(getuid()). - */ - uid = getuid(); - login = getlogin(); - if (login && (pwd = getpwnam(login)) && pwd->pw_uid == uid) - shell = pwd->pw_shell; - else if ((pwd = getpwuid(uid))) - shell = pwd->pw_shell; - } - if (!shell) - shell = "/bin/sh"; - - fallback_args[0] = shell; - fallback_args[1] = NULL; - program_args = fallback_args; - } - - execv(program_args[0], program_args); - - /* - * If we're here, exec has gone badly foom. - */ - perror("exec"); - exit(127); - } - - close(slave); - - return master; -} diff --git a/contrib/cygtermd/pty.h b/contrib/cygtermd/pty.h deleted file mode 100644 index 936963bf5..000000000 --- a/contrib/cygtermd/pty.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * pty.h - declare functions for pty setup - */ - -#ifndef CYGTERMD_PTY_H -#define CYGTERMD_PTY_H - -#include "telnet.h" /* for struct shdata */ - -/* - * Called at program startup to actually allocate a pty, so that - * we can start passing in resize events as soon as they arrive. - */ -void pty_preinit(void); - -/* - * Set the terminal size for the pty. - */ -void pty_resize(int w, int h); - -/* - * Start a program in a subprocess running in the pty we allocated. - * Returns the fd of the pty master. - */ -int run_program_in_pty(const struct shell_data *shdata, - char *directory, char **program_args); - -#endif /* CYGTERMD_PTY_H */ diff --git a/contrib/cygtermd/sel.c b/contrib/cygtermd/sel.c deleted file mode 100644 index 788de08eb..000000000 --- a/contrib/cygtermd/sel.c +++ /dev/null @@ -1,386 +0,0 @@ -/* - * sel.c: implementation of sel.h. - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "sel.h" -#include "malloc.h" - -/* ---------------------------------------------------------------------- - * Chunk of code lifted from PuTTY's misc.c to manage buffers of - * data to be written to an fd. - */ - -#define BUFFER_GRANULE 512 - -typedef struct bufchain_tag { - struct bufchain_granule *head, *tail; - size_t buffersize; /* current amount of buffered data */ -} bufchain; -struct bufchain_granule { - struct bufchain_granule *next; - size_t buflen, bufpos; - char buf[BUFFER_GRANULE]; -}; - -static void bufchain_init(bufchain *ch) -{ - ch->head = ch->tail = NULL; - ch->buffersize = 0; -} - -static 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; -} - -static size_t bufchain_size(bufchain *ch) -{ - return ch->buffersize; -} - -static void bufchain_add(bufchain *ch, const void *data, size_t len) -{ - const char *buf = (const char *)data; - - if (len == 0) return; - - ch->buffersize += len; - - if (ch->tail && ch->tail->buflen < BUFFER_GRANULE) { - size_t copylen = BUFFER_GRANULE - ch->tail->buflen; - if (copylen > len) - copylen = len; - memcpy(ch->tail->buf + ch->tail->buflen, buf, copylen); - buf += copylen; - len -= copylen; - ch->tail->buflen += copylen; - } - while (len > 0) { - struct bufchain_granule *newbuf; - size_t grainlen = BUFFER_GRANULE; - if (grainlen > len) - grainlen = len; - newbuf = snew(struct bufchain_granule); - newbuf->bufpos = 0; - newbuf->buflen = grainlen; - memcpy(newbuf->buf, buf, grainlen); - buf += grainlen; - len -= grainlen; - if (ch->tail) - ch->tail->next = newbuf; - else - ch->head = ch->tail = newbuf; - newbuf->next = NULL; - ch->tail = newbuf; - } -} - -static void bufchain_consume(bufchain *ch, size_t len) -{ - struct bufchain_granule *tmp; - - assert(ch->buffersize >= len); - while (len > 0) { - size_t remlen = len; - assert(ch->head != NULL); - if (remlen >= ch->head->buflen - ch->head->bufpos) { - remlen = ch->head->buflen - ch->head->bufpos; - tmp = ch->head; - ch->head = tmp->next; - sfree(tmp); - if (!ch->head) - ch->tail = NULL; - } else - ch->head->bufpos += remlen; - ch->buffersize -= remlen; - len -= remlen; - } -} - -static void bufchain_prefix(bufchain *ch, void **data, size_t *len) -{ - *len = ch->head->buflen - ch->head->bufpos; - *data = ch->head->buf + ch->head->bufpos; -} - -/* ---------------------------------------------------------------------- - * The actual implementation of the sel interface. - */ - -struct sel { - void *ctx; - sel_rfd *rhead, *rtail; - sel_wfd *whead, *wtail; -}; - -struct sel_rfd { - sel *parent; - sel_rfd *prev, *next; - sel_readdata_fn_t readdata; - sel_readerr_fn_t readerr; - void *ctx; - int fd; - int frozen; -}; - -struct sel_wfd { - sel *parent; - sel_wfd *prev, *next; - sel_written_fn_t written; - sel_writeerr_fn_t writeerr; - void *ctx; - int fd; - bufchain buf; -}; - -sel *sel_new(void *ctx) -{ - sel *sel = snew(struct sel); - - sel->ctx = ctx; - sel->rhead = sel->rtail = NULL; - sel->whead = sel->wtail = NULL; - - return sel; -} - -sel_wfd *sel_wfd_add(sel *sel, int fd, - sel_written_fn_t written, sel_writeerr_fn_t writeerr, - void *ctx) -{ - sel_wfd *wfd = snew(sel_wfd); - - wfd->written = written; - wfd->writeerr = writeerr; - wfd->ctx = ctx; - wfd->fd = fd; - bufchain_init(&wfd->buf); - - wfd->next = NULL; - wfd->prev = sel->wtail; - if (sel->wtail) - sel->wtail->next = wfd; - else - sel->whead = wfd; - sel->wtail = wfd; - wfd->parent = sel; - - return wfd; -} - -sel_rfd *sel_rfd_add(sel *sel, int fd, - sel_readdata_fn_t readdata, sel_readerr_fn_t readerr, - void *ctx) -{ - sel_rfd *rfd = snew(sel_rfd); - - rfd->readdata = readdata; - rfd->readerr = readerr; - rfd->ctx = ctx; - rfd->fd = fd; - rfd->frozen = 0; - - rfd->next = NULL; - rfd->prev = sel->rtail; - if (sel->rtail) - sel->rtail->next = rfd; - else - sel->rhead = rfd; - sel->rtail = rfd; - rfd->parent = sel; - - return rfd; -} - -size_t sel_write(sel_wfd *wfd, const void *data, size_t len) -{ - bufchain_add(&wfd->buf, data, len); - return bufchain_size(&wfd->buf); -} - -void sel_wfd_setfd(sel_wfd *wfd, int fd) -{ - wfd->fd = fd; -} - -void sel_rfd_setfd(sel_rfd *rfd, int fd) -{ - rfd->fd = fd; -} - -void sel_rfd_freeze(sel_rfd *rfd) -{ - rfd->frozen = 1; -} - -void sel_rfd_unfreeze(sel_rfd *rfd) -{ - rfd->frozen = 0; -} - -int sel_wfd_delete(sel_wfd *wfd) -{ - sel *sel = wfd->parent; - int ret; - - if (wfd->prev) - wfd->prev->next = wfd->next; - else - sel->whead = wfd->next; - if (wfd->next) - wfd->next->prev = wfd->prev; - else - sel->wtail = wfd->prev; - - bufchain_clear(&wfd->buf); - - ret = wfd->fd; - sfree(wfd); - return ret; -} - -int sel_rfd_delete(sel_rfd *rfd) -{ - sel *sel = rfd->parent; - int ret; - - if (rfd->prev) - rfd->prev->next = rfd->next; - else - sel->rhead = rfd->next; - if (rfd->next) - rfd->next->prev = rfd->prev; - else - sel->rtail = rfd->prev; - - ret = rfd->fd; - sfree(rfd); - return ret; -} - -void sel_free(sel *sel) -{ - while (sel->whead) - sel_wfd_delete(sel->whead); - while (sel->rhead) - sel_rfd_delete(sel->rhead); - sfree(sel); -} - -void *sel_get_ctx(sel *sel) { return sel->ctx; } -void sel_set_ctx(sel *sel, void *ctx) { sel->ctx = ctx; } -void *sel_wfd_get_ctx(sel_wfd *wfd) { return wfd->ctx; } -void sel_wfd_set_ctx(sel_wfd *wfd, void *ctx) { wfd->ctx = ctx; } -void *sel_rfd_get_ctx(sel_rfd *rfd) { return rfd->ctx; } -void sel_rfd_set_ctx(sel_rfd *rfd, void *ctx) { rfd->ctx = ctx; } - -int sel_iterate(sel *sel, long timeout) -{ - sel_rfd *rfd; - sel_wfd *wfd; - fd_set rset, wset; - int maxfd = 0; - struct timeval tv, *ptv; - char buf[65536]; - int ret; - - FD_ZERO(&rset); - FD_ZERO(&wset); - - for (rfd = sel->rhead; rfd; rfd = rfd->next) { - if (rfd->fd >= 0 && !rfd->frozen) { - FD_SET(rfd->fd, &rset); - if (maxfd < rfd->fd + 1) - maxfd = rfd->fd + 1; - } - } - - for (wfd = sel->whead; wfd; wfd = wfd->next) { - if (wfd->fd >= 0 && bufchain_size(&wfd->buf)) { - FD_SET(wfd->fd, &wset); - if (maxfd < wfd->fd + 1) - maxfd = wfd->fd + 1; - } - } - - if (timeout < 0) { - ptv = NULL; - } else { - ptv = &tv; - tv.tv_sec = timeout / 1000; - tv.tv_usec = 1000 * (timeout % 1000); - } - - do { - ret = select(maxfd, &rset, &wset, NULL, ptv); - } while (ret < 0 && (errno == EINTR || errno == EAGAIN)); - - if (ret < 0) - return errno; - - /* - * Just in case one of the callbacks destroys an rfd or wfd we - * had yet to get round to, we must loop from the start every - * single time. Algorithmically irritating, but necessary - * unless we want to store the rfd structures in a heavyweight - * tree sorted by fd. And let's face it, if we cared about - * good algorithmic complexity it's not at all clear we'd be - * using select in the first place. - */ - do { - for (wfd = sel->whead; wfd; wfd = wfd->next) - if (wfd->fd >= 0 && FD_ISSET(wfd->fd, &wset)) { - void *data; - size_t len; - - FD_CLR(wfd->fd, &wset); - bufchain_prefix(&wfd->buf, &data, &len); - ret = write(wfd->fd, data, len); - assert(ret != 0); - if (ret < 0) { - if (wfd->writeerr) - wfd->writeerr(wfd, errno); - } else { - bufchain_consume(&wfd->buf, len); - if (wfd->written) - wfd->written(wfd, bufchain_size(&wfd->buf)); - } - break; - } - } while (wfd); - do { - for (rfd = sel->rhead; rfd; rfd = rfd->next) - if (rfd->fd >= 0 && !rfd->frozen && FD_ISSET(rfd->fd, &rset)) { - FD_CLR(rfd->fd, &rset); - ret = read(rfd->fd, buf, sizeof(buf)); - if (ret < 0) { - if (rfd->readerr) - rfd->readerr(rfd, errno); - } else { - if (rfd->readdata) - rfd->readdata(rfd, buf, ret); - } - break; - } - } while (rfd); - - return 0; -} diff --git a/contrib/cygtermd/sel.h b/contrib/cygtermd/sel.h deleted file mode 100644 index 224fc6176..000000000 --- a/contrib/cygtermd/sel.h +++ /dev/null @@ -1,161 +0,0 @@ -/* - * sel.h: subsystem to manage the grubby details of a select loop, - * buffering data to write, and performing the actual writes and - * reads. - */ - -#ifndef CYGTERMD_SEL_H -#define CYGTERMD_SEL_H - -typedef struct sel sel; -typedef struct sel_wfd sel_wfd; -typedef struct sel_rfd sel_rfd; - -/* - * Callback called when some data is written to a wfd. "bufsize" - * is the remaining quantity of data buffered in that wfd. - */ -typedef void (*sel_written_fn_t)(sel_wfd *wfd, size_t bufsize); - -/* - * Callback called when an error occurs on a wfd, preventing - * further writing to it. "error" is the errno value. - */ -typedef void (*sel_writeerr_fn_t)(sel_wfd *wfd, int error); - -/* - * Callback called when some data is read from an rfd. On EOF, - * this will be called with len==0. - */ -typedef void (*sel_readdata_fn_t)(sel_rfd *rfd, void *data, size_t len); - -/* - * Callback called when an error occurs on an rfd, preventing - * further reading from it. "error" is the errno value. - */ -typedef void (*sel_readerr_fn_t)(sel_rfd *rfd, int error); - -/* - * Create a sel structure, which will oversee a select loop. - * - * "ctx" is user-supplied data stored in the sel structure; it can - * be read and written with sel_get_ctx() and sel_set_ctx(). - */ -sel *sel_new(void *ctx); - -/* - * Add a new fd for writing. Returns a sel_wfd which identifies - * that fd in the sel structure, e.g. for putting data into its - * output buffer. - * - * "ctx" is user-supplied data stored in the sel structure; it can - * be read and written with sel_wfd_get_ctx() and sel_wfd_set_ctx(). - * - * "written" and "writeerr" are called from the event loop when - * things happen. - * - * The fd passed in can be -1, in which case it will be assumed to - * be unwritable at all times. An actual fd can be passed in later - * using sel_wfd_setfd. - */ -sel_wfd *sel_wfd_add(sel *sel, int fd, - sel_written_fn_t written, sel_writeerr_fn_t writeerr, - void *ctx); - -/* - * Add a new fd for reading. Returns a sel_rfd which identifies - * that fd in the sel structure. - * - * "ctx" is user-supplied data stored in the sel structure; it can - * be read and written with sel_rfd_get_ctx() and sel_rfd_set_ctx(). - * - * "readdata" and "readerr" are called from the event loop when - * things happen. "ctx" is passed to both of them. - */ -sel_rfd *sel_rfd_add(sel *sel, int fd, - sel_readdata_fn_t readdata, sel_readerr_fn_t readerr, - void *ctx); - -/* - * Write data into the output buffer of a wfd. Returns the new - * size of the output buffer. (You can call it with len==0 if you - * just want to know the buffer size; in that situation data==NULL - * is also safe.) - */ -size_t sel_write(sel_wfd *wfd, const void *data, size_t len); - -/* - * Freeze and unfreeze an rfd. When frozen, sel will temporarily - * not attempt to read from it, but all its state is retained so - * it can be conveniently unfrozen later. (You might use this - * facility, for instance, if what you were doing with the - * incoming data could only accept it at a certain rate: freeze - * the rfd when you've got lots of backlog, and unfreeze it again - * when things get calmer.) - */ -void sel_rfd_freeze(sel_rfd *rfd); -void sel_rfd_unfreeze(sel_rfd *rfd); - -/* - * Delete a wfd structure from its containing sel. Returns the - * underlying fd, which the client may now consider itself to own - * once more. - */ -int sel_wfd_delete(sel_wfd *wfd); - -/* - * Delete an rfd structure from its containing sel. Returns the - * underlying fd, which the client may now consider itself to own - * once more. - */ -int sel_rfd_delete(sel_rfd *rfd); - -/* - * NOT IMPLEMENTED YET: useful functions here might be ones which - * enumerated all the wfds/rfds in a sel structure in some - * fashion, so you could go through them and remove them all while - * doing sensible things to them. Or, at the very least, just - * return an arbitrary one of the wfds/rfds. - */ - -/* - * Free a sel structure and all its remaining wfds and rfds. - */ -void sel_free(sel *sel); - -/* - * Read and write the ctx parameters in sel, sel_wfd and sel_rfd. - */ -void *sel_get_ctx(sel *sel); -void sel_set_ctx(sel *sel, void *ctx); -void *sel_wfd_get_ctx(sel_wfd *wfd); -void sel_wfd_set_ctx(sel_wfd *wfd, void *ctx); -void *sel_rfd_get_ctx(sel_rfd *rfd); -void sel_rfd_set_ctx(sel_rfd *rfd, void *ctx); - -/* - * Run one iteration of the sel event loop, calling callbacks as - * necessary. Returns zero on success; in the event of a fatal - * error, returns the errno value. - * - * "timeout" is a value in microseconds to limit the length of the - * select call. Less than zero means to wait indefinitely. - */ -int sel_iterate(sel *sel, long timeout); - -/* - * Change the underlying fd in a wfd. If set to -1, no write - * attempts will take place and the wfd's buffer will simply store - * everything passed to sel_write(). If later set to something - * other than -1, all that buffered data will become eligible for - * real writing. - */ -void sel_wfd_setfd(sel_wfd *wfd, int fd); - -/* - * Change the underlying fd in a rfd. If set to -1, no read - * attempts will take place. - */ -void sel_rfd_setfd(sel_rfd *rfd, int fd); - -#endif /* CYGTERMD_SEL_H */ diff --git a/contrib/cygtermd/telnet.c b/contrib/cygtermd/telnet.c deleted file mode 100644 index f51823bcd..000000000 --- a/contrib/cygtermd/telnet.c +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Simple Telnet server code, adapted from PuTTY's own Telnet - * client code for use as a Cygwin local pty proxy. - */ - -#include -#include -#include -#include - -#include "sel.h" -#include "telnet.h" -#include "malloc.h" -#include "pty.h" - -#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 && (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 = - { DO, DONT, WILL, WONT, TELOPT_NAWS, OPTINDEX_NAWS, REQUESTED }; -static const struct Opt o_ttype = - { DO, DONT, WILL, WONT, TELOPT_TTYPE, OPTINDEX_TTYPE, REQUESTED }; -static const struct Opt o_oenv = - { DO, DONT, WILL, WONT, TELOPT_OLD_ENVIRON, OPTINDEX_OENV, INACTIVE }; -static const struct Opt o_nenv = - { DO, DONT, WILL, WONT, TELOPT_NEW_ENVIRON, OPTINDEX_NENV, REQUESTED }; -static const struct Opt o_echo = - { WILL, WONT, DO, DONT, TELOPT_ECHO, OPTINDEX_ECHO, REQUESTED }; -static const struct Opt o_they_sga = - { DO, DONT, WILL, WONT, TELOPT_SGA, OPTINDEX_WE_SGA, REQUESTED }; -static const struct Opt o_we_sga = - { WILL, WONT, DO, DONT, TELOPT_SGA, OPTINDEX_THEY_SGA, REQUESTED }; - -static const struct Opt *const opts[] = { - &o_echo, &o_we_sga, &o_they_sga, &o_naws, &o_ttype, &o_oenv, &o_nenv, NULL -}; - -struct Telnet { - int opt_states[NUM_OPTS]; - - 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; - - sel_wfd *net, *pty; - - /* - * Options we must finish processing before launching the shell - */ - int old_environ_done, new_environ_done, ttype_done; - - /* - * Ready to start shell? - */ - int shell_ok; - int envvarsize; - struct shell_data shdata; -}; - -#define TELNET_MAX_BACKLOG 4096 - -#define SB_DELTA 1024 - -static void send_opt(Telnet *telnet, int cmd, int option) -{ - unsigned char b[3]; - - b[0] = IAC; - b[1] = cmd; - b[2] = option; - sel_write(telnet->net, b, 3); -} - -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) -{ -} - -static void activate_option(Telnet *telnet, const struct Opt *o) -{ - if (o->option == TELOPT_NEW_ENVIRON || - o->option == TELOPT_OLD_ENVIRON || - o->option == TELOPT_TTYPE) { - char buf[6]; - buf[0] = IAC; - buf[1] = SB; - buf[2] = o->option; - buf[3] = TELQUAL_SEND; - buf[4] = IAC; - buf[5] = SE; - sel_write(telnet->net, buf, 6); - } - option_side_effects(telnet, o, 1); -} - -static void done_option(Telnet *telnet, int option) -{ - if (option == TELOPT_OLD_ENVIRON) - telnet->old_environ_done = 1; - else if (option == TELOPT_NEW_ENVIRON) - telnet->new_environ_done = 1; - else if (option == TELOPT_TTYPE) - telnet->ttype_done = 1; - - if (telnet->old_environ_done && telnet->new_environ_done && - telnet->ttype_done) { - telnet->shell_ok = 1; - } -} - -static void refused_option(Telnet *telnet, const struct Opt *o) -{ - done_option(telnet, o->option); - 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; - telnet->old_environ_done = 0; - } - option_side_effects(telnet, o, 0); -} - -static void proc_rec_opt(Telnet *telnet, int cmd, int option) -{ - const struct Opt *const *o; - - 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) -{ - int var, value, n; - - switch (telnet->sb_opt) { - case TELOPT_OLD_ENVIRON: - case TELOPT_NEW_ENVIRON: - if (telnet->sb_buf[0] == TELQUAL_IS) { - if (telnet->sb_opt == TELOPT_NEW_ENVIRON) { - var = RFC_VAR; - value = RFC_VALUE; - } else { - if (telnet->sb_len > 1 && !(telnet->sb_buf[0] &~ 1)) { - var = telnet->sb_buf[0]; - value = BSD_VAR ^ BSD_VALUE ^ var; - } else { - var = BSD_VAR; - value = BSD_VALUE; - } - } - } - n = 1; - while (n < telnet->sb_len && telnet->sb_buf[n] == var) { - int varpos, varlen, valpos, vallen; - char *result; - - varpos = ++n; - while (n < telnet->sb_len && telnet->sb_buf[n] != value) - n++; - if (n == telnet->sb_len) - break; - varlen = n - varpos; - valpos = ++n; - while (n < telnet->sb_len && telnet->sb_buf[n] != var) - n++; - vallen = n - valpos; - - result = snewn(varlen + vallen + 2, char); - sprintf(result, "%.*s=%.*s", - varlen, telnet->sb_buf+varpos, - vallen, telnet->sb_buf+valpos); - if (telnet->shdata.nenvvars >= telnet->envvarsize) { - telnet->envvarsize = telnet->shdata.nenvvars * 3 / 2 + 16; - telnet->shdata.envvars = sresize(telnet->shdata.envvars, - telnet->envvarsize, char *); - } - telnet->shdata.envvars[telnet->shdata.nenvvars++] = result; - } - done_option(telnet, telnet->sb_opt); - break; - case TELOPT_TTYPE: - if (telnet->sb_len >= 1 && telnet->sb_buf[0] == TELQUAL_IS) { - telnet->shdata.termtype = snewn(5 + telnet->sb_len, char); - strcpy(telnet->shdata.termtype, "TERM="); - for (n = 0; n < telnet->sb_len-1; n++) { - char c = telnet->sb_buf[n+1]; - if (c >= 'A' && c <= 'Z') - c = c + 'a' - 'A'; - telnet->shdata.termtype[n+5] = c; - } - telnet->shdata.termtype[telnet->sb_len+5-1] = '\0'; - } - done_option(telnet, telnet->sb_opt); - break; - case TELOPT_NAWS: - if (telnet->sb_len == 4) { - int w, h; - w = (unsigned char)telnet->sb_buf[0]; - w = (w << 8) | (unsigned char)telnet->sb_buf[1]; - h = (unsigned char)telnet->sb_buf[2]; - h = (h << 8) | (unsigned char)telnet->sb_buf[3]; - pty_resize(w, h); - } - break; - } -} - -void telnet_from_net(Telnet *telnet, char *buf, int len) -{ - while (len--) { - int c = (unsigned char) *buf++; - - switch (telnet->state) { - case TOP_LEVEL: - case SEENCR: - /* - * PuTTY sends Telnet's new line sequence (CR LF on - * the wire) in response to the return key. We must - * therefore treat that as equivalent to CR NUL, and - * send CR to the pty. - */ - if ((c == NUL || c == '\n') && telnet->state == SEENCR) - telnet->state = TOP_LEVEL; - else if (c == IAC) - telnet->state = SEENIAC; - else { - char cc = c; - sel_write(telnet->pty, &cc, 1); - - if (c == CR) - 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->state = TOP_LEVEL; - else { - /* ignore everything else; print it if it's IAC */ - if (c == IAC) { - char cc = c; - sel_write(telnet->pty, &cc, 1); - } - 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; - } - } -} - -Telnet *telnet_new(sel_wfd *net, sel_wfd *pty) -{ - Telnet *telnet; - - telnet = snew(Telnet); - telnet->sb_buf = NULL; - telnet->sb_size = 0; - telnet->state = TOP_LEVEL; - telnet->net = net; - telnet->pty = pty; - telnet->shdata.envvars = NULL; - telnet->shdata.nenvvars = telnet->envvarsize = 0; - telnet->shdata.termtype = NULL; - - /* - * Initialise option states. - */ - { - 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->old_environ_done = 1; /* initially don't want to bother */ - telnet->new_environ_done = 0; - telnet->ttype_done = 0; - telnet->shell_ok = 0; - - return telnet; -} - -void telnet_free(Telnet *telnet) -{ - sfree(telnet->sb_buf); - sfree(telnet); -} - -void telnet_from_pty(Telnet *telnet, char *buf, int len) -{ - 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 - - p = (unsigned char *)buf; - end = (unsigned char *)(buf + len); - while (p < end) { - unsigned char *q = p; - - while (p < end && iswritable(*p)) - p++; - sel_write(telnet->net, q, p - q); - - while (p < end && !iswritable(*p)) { - sel_write(telnet->net, *p == IAC ? iac : cr, 2); - p++; - } - } -} - -int telnet_shell_ok(Telnet *telnet, struct shell_data *shdata) -{ - if (telnet->shell_ok) - *shdata = telnet->shdata; /* structure copy */ - return telnet->shell_ok; -} diff --git a/contrib/cygtermd/telnet.h b/contrib/cygtermd/telnet.h deleted file mode 100644 index 8eefa4ffe..000000000 --- a/contrib/cygtermd/telnet.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Header declaring Telnet-handling functions. - */ - -#ifndef CYGTERMD_TELNET_H -#define CYGTERMD_TELNET_H - -#include "sel.h" - -typedef struct Telnet Telnet; - -struct shell_data { - char **envvars; /* array of "VAR=value" terms */ - int nenvvars; - char *termtype; -}; - -/* - * Create and destroy a Telnet structure. - */ -Telnet *telnet_new(sel_wfd *net, sel_wfd *pty); -void telnet_free(Telnet *telnet); - -/* - * Process data read from the pty. - */ -void telnet_from_pty(Telnet *telnet, char *buf, int len); - -/* - * Process Telnet protocol data received from the network. - */ -void telnet_from_net(Telnet *telnet, char *buf, int len); - -/* - * Return true if pre-shell-startup negotiations are complete and - * it's safe to start the shell subprocess now. On a true return, - * also fills in the shell_data structure. - */ -int telnet_shell_ok(Telnet *telnet, struct shell_data *shdata); - -#endif /* CYGTERMD_TELNET_H */ diff --git a/contrib/encodelib.py b/contrib/encodelib.py deleted file mode 100644 index f64d96344..000000000 --- a/contrib/encodelib.py +++ /dev/null @@ -1,132 +0,0 @@ -# Python module to make it easy to manually encode SSH packets, by -# supporting the various uint32, string, mpint primitives. -# -# The idea of this is that you can use it to manually construct key -# exchange sequences of interesting kinds, for testing purposes. - -import sys -import struct -import random - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -def tobytes(s): - return s if isinstance(s, bytes) else s.encode('ASCII') - -def boolean(b): - return b"\1" if b else b"\0" - -def byte(b): - assert 0 <= b < 0x100 - return bytes([b]) - -def uint32(u): - assert 0 <= u < 0x100000000 - return struct.pack(">I", u) - -def uint64(u): - assert 0 <= u < 0x10000000000000000 - return struct.pack(">L", u) - -def string(s): - return uint32(len(s)) + tobytes(s) - -def mpint(m): - s = [] - while m > 0: - s.append(m & 0xFF) - m >>= 8 - if len(s) > 0 and (s[-1] & 0x80): - s.append(0) - s.reverse() - return string(bytes(s)) - -def name_list(ns): - s = b"" - for n in map(tobytes, ns): - assert b"," not in n - if s != b"": - s += b"," - s += n - return string(s) - -def ssh_rsa_key_blob(modulus, exponent): - return string(string("ssh-rsa") + mpint(modulus) + mpint(exponent)) - -def ssh_rsa_signature_blob(signature): - return string(string("ssh-rsa") + mpint(signature)) - -def greeting(string): - # Greeting at the start of an SSH connection. - return tobytes(string) + b"\r\n" - -# Packet types. -SSH2_MSG_DISCONNECT = 1 -SSH2_MSG_IGNORE = 2 -SSH2_MSG_UNIMPLEMENTED = 3 -SSH2_MSG_DEBUG = 4 -SSH2_MSG_SERVICE_REQUEST = 5 -SSH2_MSG_SERVICE_ACCEPT = 6 -SSH2_MSG_KEXINIT = 20 -SSH2_MSG_NEWKEYS = 21 -SSH2_MSG_KEXDH_INIT = 30 -SSH2_MSG_KEXDH_REPLY = 31 -SSH2_MSG_KEX_DH_GEX_REQUEST_OLD = 30 -SSH2_MSG_KEX_DH_GEX_GROUP = 31 -SSH2_MSG_KEX_DH_GEX_INIT = 32 -SSH2_MSG_KEX_DH_GEX_REPLY = 33 -SSH2_MSG_KEX_DH_GEX_REQUEST = 34 -SSH2_MSG_KEXRSA_PUBKEY = 30 -SSH2_MSG_KEXRSA_SECRET = 31 -SSH2_MSG_KEXRSA_DONE = 32 -SSH2_MSG_USERAUTH_REQUEST = 50 -SSH2_MSG_USERAUTH_FAILURE = 51 -SSH2_MSG_USERAUTH_SUCCESS = 52 -SSH2_MSG_USERAUTH_BANNER = 53 -SSH2_MSG_USERAUTH_PK_OK = 60 -SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ = 60 -SSH2_MSG_USERAUTH_INFO_REQUEST = 60 -SSH2_MSG_USERAUTH_INFO_RESPONSE = 61 -SSH2_MSG_GLOBAL_REQUEST = 80 -SSH2_MSG_REQUEST_SUCCESS = 81 -SSH2_MSG_REQUEST_FAILURE = 82 -SSH2_MSG_CHANNEL_OPEN = 90 -SSH2_MSG_CHANNEL_OPEN_CONFIRMATION = 91 -SSH2_MSG_CHANNEL_OPEN_FAILURE = 92 -SSH2_MSG_CHANNEL_WINDOW_ADJUST = 93 -SSH2_MSG_CHANNEL_DATA = 94 -SSH2_MSG_CHANNEL_EXTENDED_DATA = 95 -SSH2_MSG_CHANNEL_EOF = 96 -SSH2_MSG_CHANNEL_CLOSE = 97 -SSH2_MSG_CHANNEL_REQUEST = 98 -SSH2_MSG_CHANNEL_SUCCESS = 99 -SSH2_MSG_CHANNEL_FAILURE = 100 -SSH2_MSG_USERAUTH_GSSAPI_RESPONSE = 60 -SSH2_MSG_USERAUTH_GSSAPI_TOKEN = 61 -SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE = 63 -SSH2_MSG_USERAUTH_GSSAPI_ERROR = 64 -SSH2_MSG_USERAUTH_GSSAPI_ERRTOK = 65 -SSH2_MSG_USERAUTH_GSSAPI_MIC = 66 - -def clearpkt(msgtype, *stuff): - # SSH-2 binary packet, in the cleartext format used for initial - # setup and kex. - s = byte(msgtype) - for thing in stuff: - s += thing - padlen = 0 - while padlen < 4 or len(s) % 8 != 3: - padlen += 1 - s += byte(random.randint(0,255)) - s = byte(padlen) + s - return string(s) - -def decode_uint32(s): - assert len(s) == 4 - return struct.unpack(">I", s)[0] - -def read_clearpkt(fh): - length_field = fh.read(4) - s = fh.read(decode_uint32(length_field)) - padlen, msgtype = s[:2] - return msgtype, s[2:-padlen] diff --git a/contrib/gdb.py b/contrib/gdb.py deleted file mode 100644 index fb7413ec5..000000000 --- a/contrib/gdb.py +++ /dev/null @@ -1,289 +0,0 @@ -import gdb -import re -import gdb.printing - -class PuTTYMpintPrettyPrinter(gdb.printing.PrettyPrinter): - "Pretty-print PuTTY's mp_int type." - name = "mp_int" - - def __init__(self, val): - super(PuTTYMpintPrettyPrinter, self).__init__(self.name) - self.val = val - - def to_string(self): - type_BignumInt = gdb.lookup_type("BignumInt") - type_BignumIntPtr = type_BignumInt.pointer() - BIGNUM_INT_BITS = 8 * type_BignumInt.sizeof - array = self.val["w"] - aget = lambda i: int(array[i]) & ((1 << BIGNUM_INT_BITS)-1) - - try: - length = int(self.val["nw"]) - value = 0 - for i in range(length): - value |= aget(i) << (BIGNUM_INT_BITS * i) - return "mp_int({:#x})".format(value) - - except gdb.MemoryError: - address = int(self.val) - if address == 0: - return "mp_int(NULL)".format(address) - return "mp_int(invalid @ {:#x})".format(address) - -class PuTTYPtrlenPrettyPrinter(gdb.printing.PrettyPrinter): - "Pretty-print strings in PuTTY's ptrlen type." - name = "ptrlen" - - def __init__(self, val): - super(PuTTYPtrlenPrettyPrinter, self).__init__(self.name) - self.val = val - - def to_string(self): - length = int(self.val["len"]) - char_array_ptr_type = gdb.lookup_type( - "char").const().array(length).pointer() - line = self.val["ptr"].cast(char_array_ptr_type).dereference() - return repr(bytes(int(line[i]) for i in range(length))).lstrip('b') - -class PuTTYPrinterSelector(gdb.printing.PrettyPrinter): - def __init__(self): - super(PuTTYPrinterSelector, self).__init__("PuTTY") - def __call__(self, val): - if str(val.type) == "mp_int *": - return PuTTYMpintPrettyPrinter(val) - if str(val.type) == "ptrlen": - return PuTTYPtrlenPrettyPrinter(val) - return None - -gdb.printing.register_pretty_printer(None, PuTTYPrinterSelector()) - -class MemDumpCommand(gdb.Command): - """Print a hex+ASCII dump of object EXP. - -EXP must be an expression whose value resides in memory. The -contents of the memory it occupies are printed in a standard hex -dump format, with each line showing an offset relative to the -address of EXP, then the hex byte values of the memory at that -offset, and then a translation into ASCII of the same bytes (with -values outside the printable ASCII range translated as '.'). - -To dump a number of bytes from a particular address, it's useful -to use the gdb expression extensions {TYPE} and @LENGTH. For -example, if 'ptr' and 'len' are variables giving an address and a -length in bytes, then the command - - memdump {char} ptr @ len - -will dump the range of memory described by those two variables.""" - - def __init__(self): - super(MemDumpCommand, self).__init__( - "memdump", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) - - def invoke(self, cmdline, from_tty): - expr = gdb.parse_and_eval(cmdline) - try: - start, size = int(expr.address), expr.type.sizeof - except gdb.error as e: - raise gdb.GdbError(str(e)) - except (TypeError, AttributeError): - raise gdb.GdbError("expression must identify an object in memory") - return - - width = 16 - line_ptr_type = gdb.lookup_type( - "unsigned char").const().array(width).pointer() - - dumpaddr = 0 - while size > 0: - line = gdb.Value(start).cast(line_ptr_type).dereference() - thislinelen = min(size, width) - start += thislinelen - size -= thislinelen - - dumpline = [None, " "] + [" "] * width + [" "] + [""] * width - - dumpline[0] = "{:08x}".format(dumpaddr) - dumpaddr += thislinelen - - for i in range(thislinelen): - ch = int(line[i]) & 0xFF - dumpline[2+i] = " {:02x}".format(ch) - dumpline[3+width+i] = chr(ch) if 0x20 <= ch < 0x7F else "." - - sys.stdout.write("".join(dumpline) + "\n") - -MemDumpCommand() - -class ContainerOf(gdb.Function): - """Implement the container_of macro from PuTTY's defs.h. - - Arguments are an object or pointer to object; a type to convert it - to; and, optionally the name of the structure member in the - destination type that the pointer points to. (If the member name - is not provided, then the default is whichever member of the - destination structure type has the same type as the input object, - provided there's only one.) - - Due to limitations of GDB's convenience function syntax, the type - and member names must be provided as strings. - - """ - - def __init__(self): - super(ContainerOf, self).__init__("container_of") - - def match_type(self, obj, typ): - if obj.type == typ: - return obj - - try: - ref = obj.referenced_value() - if ref.type == typ: - return ref - except gdb.error: - pass - - return None - - def invoke(self, obj, dest_type_name_val, member_name_val=None): - try: - dest_type_name = dest_type_name_val.string() - except gdb.error: - raise gdb.GdbError("destination type name must be a string") - - try: - dest_type = gdb.lookup_type(dest_type_name) - except gdb.error: - raise gdb.GdbError("no such type '{dt}'".format(dt=dest_type_name)) - - if member_name_val is not None: - try: - member_name = member_name_val.string() - except gdb.error: - raise gdb.GdbError("member name must be a string") - - for field in dest_type.fields(): - if field.name == member_name: - break - else: - raise gdb.GdbError( - "type '{dt}' has no member called '{memb}'" - .format(dt=dest_type_name, memb=member_name)) - - match_obj = self.match_type(obj, field.type) - else: - matches = [] - - for field in dest_type.fields(): - this_match_obj = self.match_type(obj, field.type) - if this_match_obj is not None: - match_obj = this_match_obj - matches.append(field) - - if len(matches) == 0: - raise gdb.GdbError( - "type '{dt}' has no member matching type '{ot}'" - .format(dt=dest_type_name, ot=obj.type)) - - if len(matches) > 1: - raise gdb.GdbError( - "type '{dt}' has multiple members matching type '{ot}'" - " ({memberlist})" - .format(dt=dest_type_name, ot=obj.type, - memberlist=", ".join(f.name for f in matches))) - - field = matches[0] - - if field.bitpos % 8 != 0: - raise gdb.GdbError( - "offset of field '{memb}' is a fractional number of bytes" - .format(dt=dest_type_name, memb=member_name)) - offset = field.bitpos // 8 - - if match_obj.type != field.type: - raise gdb.GdbError( - "value to convert does not have type '{ft}'" - .format(ft=field.type)) - - try: - addr = int(match_obj.address) - except gdb.error: - raise gdb.GdbError("cannot take address of value to convert") - - return gdb.Value(addr - offset).cast(dest_type.pointer()) - -ContainerOf() - -class List234(gdb.Function): - """List the elements currently stored in a tree234. - - Arguments are a tree234, and optionally a value type. If no value - type is given, the result is a list of the raw void * pointers - stored in the tree. Otherwise, each one is cast to a pointer to the - value type and dereferenced. - - Due to limitations of GDB's convenience function syntax, the value - type must be provided as a string. - - """ - - def __init__(self): - super(List234, self).__init__("list234") - - def add_elements(self, node, destlist): - kids = node["kids"] - elems = node["elems"] - for i in range(4): - if int(kids[i]) != 0: - add_elements(self, kids[i].dereference(), destlist) - if i < 3 and int(elems[i]) != 0: - destlist.append(elems[i]) - - def invoke(self, tree, value_type_name_val=None): - if value_type_name_val is not None: - try: - value_type_name = value_type_name_val.string() - except gdb.error: - raise gdb.GdbError("value type name must be a string") - - try: - value_type = gdb.lookup_type(value_type_name) - except gdb.error: - raise gdb.GdbError("no such type '{dt}'" - .format(dt=value_type_name)) - else: - value_type = None - - try: - tree = tree.dereference() - except gdb.error: - pass - - if tree.type == gdb.lookup_type("tree234"): - tree = tree["root"].dereference() - - if tree.type != gdb.lookup_type("node234"): - raise gdb.GdbError( - "input value is not a tree234") - - if int(tree.address) == 0: - # If you try to return {} for the empty list, gdb gives - # the cryptic error "bad array bounds (0, -1)"! We return - # NULL as the best approximation to 'sorry, list is - # empty'. - return gdb.parse_and_eval("((void *)0)") - - elems = [] - self.add_elements(tree, elems) - - if value_type is not None: - value_ptr_type_name = str(value_type.pointer()) - elem_fmt = lambda p: "*({}){}".format(value_ptr_type_name, int(p)) - else: - elem_fmt = lambda p: "(void *){}".format(int(p)) - - elems_str = "{" + ",".join(elem_fmt(elem) for elem in elems) + "}" - return gdb.parse_and_eval(elems_str) - -List234() diff --git a/contrib/kh2reg.py b/contrib/kh2reg.py deleted file mode 100644 index cff06c8f7..000000000 --- a/contrib/kh2reg.py +++ /dev/null @@ -1,442 +0,0 @@ -#!/usr/bin/env python3 - -# Convert OpenSSH known_hosts and known_hosts2 files to "new format" PuTTY -# host keys. -# usage: -# kh2reg.py [ --win ] known_hosts1 2 3 4 ... > hosts.reg -# Creates a Windows .REG file (double-click to install). -# kh2reg.py --unix known_hosts1 2 3 4 ... > sshhostkeys -# Creates data suitable for storing in ~/.putty/sshhostkeys (Unix). -# Line endings are someone else's problem as is traditional. -# Should run under either Python 2 or 3. - -import fileinput -import base64 -import struct -import string -import re -import sys -import argparse -import itertools -import collections -import hashlib -from functools import reduce - -def winmungestr(s): - "Duplicate of PuTTY's mungestr() in winstore.c:1.10 for Registry keys" - candot = 0 - r = "" - for c in s: - if c in ' \*?%~' or ord(c){:d}B".format(len(s)), s) - return reduce ((lambda a, b: (int(a) << 8) + int(b)), bytes) - -def strtoint_le(s): - "Convert arbitrary-length little-endian binary data to a Python int" - bytes = reversed(struct.unpack(">{:d}B".format(len(s)), s)) - return reduce ((lambda a, b: (int(a) << 8) + int(b)), bytes) - -def inttohex(n): - "Convert int to lower-case hex." - return "0x{:x}".format(n) - -def warn(s): - "Warning with file/line number" - sys.stderr.write("%s:%d: %s\n" - % (fileinput.filename(), fileinput.filelineno(), s)) - -class HMAC(object): - def __init__(self, hashclass, blocksize): - self.hashclass = hashclass - self.blocksize = blocksize - self.struct = struct.Struct(">{:d}B".format(self.blocksize)) - def pad_key(self, key): - return key + b'\0' * (self.blocksize - len(key)) - def xor_key(self, key, xor): - return self.struct.pack(*[b ^ xor for b in self.struct.unpack(key)]) - def keyed_hash(self, key, padbyte, string): - return self.hashclass(self.xor_key(key, padbyte) + string).digest() - def compute(self, key, string): - if len(key) > self.blocksize: - key = self.hashclass(key).digest() - key = self.pad_key(key) - return self.keyed_hash(key, 0x5C, self.keyed_hash(key, 0x36, string)) - -def openssh_hashed_host_match(hashed_host, try_host): - if hashed_host.startswith(b'|1|'): - salt, expected = hashed_host[3:].split(b'|') - salt = base64.decodebytes(salt) - expected = base64.decodebytes(expected) - mac = HMAC(hashlib.sha1, 64) - else: - return False # unrecognised magic number prefix - - return mac.compute(salt, try_host) == expected - -def invert(n, p): - """Compute inverse mod p.""" - if n % p == 0: - raise ZeroDivisionError() - a = n, 1, 0 - b = p, 0, 1 - while b[0]: - q = a[0] // b[0] - a = a[0] - q*b[0], a[1] - q*b[1], a[2] - q*b[2] - b, a = a, b - assert abs(a[0]) == 1 - return a[1]*a[0] - -def jacobi(n,m): - """Compute the Jacobi symbol. - - The special case of this when m is prime is the Legendre symbol, - which is 0 if n is congruent to 0 mod m; 1 if n is congruent to a - non-zero square number mod m; -1 if n is not congruent to any - square mod m. - - """ - assert m & 1 - acc = 1 - while True: - n %= m - if n == 0: - return 0 - while not (n & 1): - n >>= 1 - if (m & 7) not in {1,7}: - acc *= -1 - if n == 1: - return acc - if (n & 3) == 3 and (m & 3) == 3: - acc *= -1 - n, m = m, n - -class SqrtModP(object): - """Class for finding square roots of numbers mod p. - - p must be an odd prime (but its primality is not checked).""" - - def __init__(self, p): - p = abs(p) - assert p & 1 - self.p = p - - # Decompose p as 2^e k + 1 for odd k. - self.k = p-1 - self.e = 0 - while not (self.k & 1): - self.k >>= 1 - self.e += 1 - - # Find a non-square mod p. - for self.z in itertools.count(1): - if jacobi(self.z, self.p) == -1: - break - self.zinv = invert(self.z, self.p) - - def sqrt_recurse(self, a): - ak = pow(a, self.k, self.p) - for i in range(self.e, -1, -1): - if ak == 1: - break - ak = ak*ak % self.p - assert i > 0 - if i == self.e: - return pow(a, (self.k+1) // 2, self.p) - r_prime = self.sqrt_recurse(a * pow(self.z, 2**i, self.p)) - return r_prime * pow(self.zinv, 2**(i-1), self.p) % self.p - - def sqrt(self, a): - j = jacobi(a, self.p) - if j == 0: - return 0 - if j < 0: - raise ValueError("{} has no square root mod {}".format(a, self.p)) - a %= self.p - r = self.sqrt_recurse(a) - assert r*r % self.p == a - # Normalise to the smaller (or 'positive') one of the two roots. - return min(r, self.p - r) - - def __str__(self): - return "{}({})".format(type(self).__name__, self.p) - def __repr__(self): - return self.__str__() - - instances = {} - - @classmethod - def make(cls, p): - if p not in cls.instances: - cls.instances[p] = cls(p) - return cls.instances[p] - - @classmethod - def root(cls, n, p): - return cls.make(p).sqrt(n) - -NistCurve = collections.namedtuple("NistCurve", "p a b") -nist_curves = { - "ecdsa-sha2-nistp256": NistCurve(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff, 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc, 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b), - "ecdsa-sha2-nistp384": NistCurve(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc, 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef), - "ecdsa-sha2-nistp521": NistCurve(0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, 0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc, 0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00), -} - -class BlankInputLine(Exception): - pass - -class UnknownKeyType(Exception): - def __init__(self, keytype): - self.keytype = keytype - -class KeyFormatError(Exception): - def __init__(self, msg): - self.msg = msg - -def handle_line(line, output_formatter, try_hosts): - try: - # Remove leading/trailing whitespace (should zap CR and LF) - line = line.strip() - - # Skip blanks and comments - if line == '' or line[0] == '#': - raise BlankInputLine - - # Split line on spaces. - fields = line.split(' ') - - # Common fields - hostpat = fields[0] - keyparams = [] # placeholder - keytype = "" # placeholder - - # Grotty heuristic to distinguish known_hosts from known_hosts2: - # is second field entirely decimal digits? - if re.match (r"\d*$", fields[1]): - - # Treat as SSH-1-type host key. - # Format: hostpat bits10 exp10 mod10 comment... - # (PuTTY doesn't store the number of bits.) - keyparams = list(map(int, fields[2:4])) - keytype = "rsa" - - else: - - # Treat as SSH-2-type host key. - # Format: hostpat keytype keyblob64 comment... - sshkeytype, blob = fields[1], base64.decodebytes( - fields[2].encode("ASCII")) - - # 'blob' consists of a number of - # uint32 N (big-endian) - # uint8[N] field_data - subfields = [] - while blob: - sizefmt = ">L" - (size,) = struct.unpack (sizefmt, blob[0:4]) - size = int(size) # req'd for slicage - (data,) = struct.unpack (">%lus" % size, blob[4:size+4]) - subfields.append(data) - blob = blob [struct.calcsize(sizefmt) + size : ] - - # The first field is keytype again. - if subfields[0].decode("ASCII") != sshkeytype: - raise KeyFormatError(""" - outer and embedded key types do not match: '%s', '%s' - """ % (sshkeytype, subfields[1])) - - # Translate key type string into something PuTTY can use, and - # munge the rest of the data. - if sshkeytype == "ssh-rsa": - keytype = "rsa2" - # The rest of the subfields we can treat as an opaque list - # of bignums (same numbers and order as stored by PuTTY). - keyparams = list(map(strtoint, subfields[1:])) - - elif sshkeytype == "ssh-dss": - keytype = "dss" - # Same again. - keyparams = list(map(strtoint, subfields[1:])) - - elif sshkeytype in nist_curves: - keytype = sshkeytype - # Have to parse this a bit. - if len(subfields) > 3: - raise KeyFormatError("too many subfields in blob") - (curvename, Q) = subfields[1:] - # First is yet another copy of the key name. - if not re.match("ecdsa-sha2-" + re.escape( - curvename.decode("ASCII")), sshkeytype): - raise KeyFormatError("key type mismatch ('%s' vs '%s')" - % (sshkeytype, curvename)) - # Second contains key material X and Y (hopefully). - # First a magic octet indicating point compression. - point_type = struct.unpack_from("B", Q, 0)[0] - Qrest = Q[1:] - if point_type == 4: - # Then two equal-length bignums (X and Y). - bnlen = len(Qrest) - if (bnlen % 1) != 0: - raise KeyFormatError("odd-length X+Y") - bnlen = bnlen // 2 - x = strtoint(Qrest[:bnlen]) - y = strtoint(Qrest[bnlen:]) - elif 2 <= point_type <= 3: - # A compressed point just specifies X, and leaves - # Y implicit except for parity, so we have to - # recover it from the curve equation. - curve = nist_curves[sshkeytype] - x = strtoint(Qrest) - yy = (x*x*x + curve.a*x + curve.b) % curve.p - y = SqrtModP.root(yy, curve.p) - if y % 2 != point_type % 2: - y = curve.p - y - - keyparams = [curvename, x, y] - - elif sshkeytype in { "ssh-ed25519", "ssh-ed448" }: - keytype = sshkeytype - - if len(subfields) != 2: - raise KeyFormatError("wrong number of subfields in blob") - # Key material y, with the top bit being repurposed as - # the expected parity of the associated x (point - # compression). - y = strtoint_le(subfields[1]) - x_parity = y >> 255 - y &= ~(1 << 255) - - # Curve parameters. - p, d, a = { - "ssh-ed25519": (2**255 - 19, 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3, -1), - "ssh-ed448": (2**448-2**224-1, -39081, +1), - }[sshkeytype] - - # Recover x^2 = (y^2 - 1) / (d y^2 - a). - xx = (y*y - 1) * invert(d*y*y - a, p) % p - - # Take the square root. - x = SqrtModP.root(xx, p) - - # Pick the square root of the correct parity. - if (x % 2) != x_parity: - x = p - x - - keyparams = [x, y] - else: - raise UnknownKeyType(sshkeytype) - - # Now print out one line per host pattern, discarding wildcards. - for host in hostpat.split(','): - if re.search (r"[*?!]", host): - warn("skipping wildcard host pattern '%s'" % host) - continue - - if re.match (r"\|", host): - for try_host in try_hosts: - if openssh_hashed_host_match(host.encode('ASCII'), - try_host.encode('UTF-8')): - host = try_host - break - else: - warn("unable to match hashed hostname '%s'" % host) - continue - - m = re.match (r"\[([^]]*)\]:(\d*)$", host) - if m: - (host, port) = m.group(1,2) - port = int(port) - else: - port = 22 - # Slightly bizarre output key format: 'type@port:hostname' - # XXX: does PuTTY do anything useful with literal IP[v4]s? - key = keytype + ("@%d:%s" % (port, host)) - # Most of these are numbers, but there's the occasional - # string that needs passing through - value = ",".join(map( - lambda x: x if isinstance(x, str) - else x.decode('ASCII') if isinstance(x, bytes) - else inttohex(x), keyparams)) - output_formatter.key(key, value) - - except UnknownKeyType as k: - warn("unknown SSH key type '%s', skipping" % k.keytype) - except KeyFormatError as k: - warn("trouble parsing key (%s), skipping" % k.msg) - except BlankInputLine: - pass - -class OutputFormatter(object): - def __init__(self, fh): - self.fh = fh - def header(self): - pass - def trailer(self): - pass - -class WindowsOutputFormatter(OutputFormatter): - def header(self): - # Output REG file header. - self.fh.write("""REGEDIT4 - -[HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys] -""") - - def key(self, key, value): - # XXX: worry about double quotes? - self.fh.write("\"%s\"=\"%s\"\n" % (winmungestr(key), value)) - - def trailer(self): - # The spec at http://support.microsoft.com/kb/310516 says we need - # a blank line at the end of the reg file: - # - # Note the registry file should contain a blank line at the - # bottom of the file. - # - self.fh.write("\n") - -class UnixOutputFormatter(OutputFormatter): - def key(self, key, value): - self.fh.write('%s %s\n' % (key, value)) - -def main(): - parser = argparse.ArgumentParser( - description="Convert OpenSSH known hosts files to PuTTY's format.") - group = parser.add_mutually_exclusive_group() - group.add_argument( - "--windows", "--win", action='store_const', - dest="output_formatter_class", const=WindowsOutputFormatter, - help="Produce Windows .reg file output that regedit.exe can import" - " (default).") - group.add_argument( - "--unix", action='store_const', - dest="output_formatter_class", const=UnixOutputFormatter, - help="Produce a file suitable for use as ~/.putty/sshhostkeys.") - parser.add_argument("-o", "--output", type=argparse.FileType("w"), - default=argparse.FileType("w")("-"), - help="Output file to write to (default stdout).") - parser.add_argument("--hostname", action="append", - help="Host name(s) to try matching against hashed " - "host entries in input.") - parser.add_argument("infile", nargs="*", - help="Input file(s) to read from (default stdin).") - parser.set_defaults(output_formatter_class=WindowsOutputFormatter, - hostname=[]) - args = parser.parse_args() - - output_formatter = args.output_formatter_class(args.output) - output_formatter.header() - for line in fileinput.input(args.infile): - handle_line(line, output_formatter, args.hostname) - output_formatter.trailer() - -if __name__ == "__main__": - main() diff --git a/contrib/logparse.pl b/contrib/logparse.pl deleted file mode 100644 index eb429302f..000000000 --- a/contrib/logparse.pl +++ /dev/null @@ -1,1329 +0,0 @@ -#!/usr/bin/perl - -use Getopt::Long; -use strict; -use warnings; -use FileHandle; - -my $dumpchannels = 0; -my $dumpdata = 0; -my $pass_through_events = 0; -my $verbose_all; -my %verbose_packet; -GetOptions("dump-channels|c" => \$dumpchannels, - "dump-data|d" => \$dumpdata, - "verbose|v" => \$verbose_all, - "full|f=s" => sub { $verbose_packet{$_[1]} = 1; }, - "events|e" => \$pass_through_events, - "help" => sub { &usage(\*STDOUT, 0); }) - or &usage(\*STDERR, 1); - -sub usage { - my ($fh, $exitstatus) = @_; - print $fh <<'EOF'; -usage: logparse.pl [ options ] [ input-log-file ] -options: --dump-channels, -c dump the final state of every channel - --dump-data, -d save data of every channel to ch0.i, ch0.o, ... - --full=PKT, -f PKT print extra detail for packets of type PKT - --verbose, -v print extra detail for all packets if available - --events, -e copy Event Log messages from input log file -EOF - exit $exitstatus; -} - -my @channels = (); # ultimate channel ids are indices in this array -my %chan_by_id = (); # indexed by 'c%d' or 's%d' for client and server ids -my %globalreq = (); # indexed by 'i' or 'o' - -my %packets = ( -#define SSH2_MSG_DISCONNECT 1 /* 0x1 */ - 'SSH2_MSG_DISCONNECT' => sub { - my ($direction, $seq, $data) = @_; - my ($reason, $description, $lang) = &parse("uss", $data); - printf "%s\n", &str($description); - }, -#define SSH2_MSG_IGNORE 2 /* 0x2 */ - 'SSH2_MSG_IGNORE' => sub { - my ($direction, $seq, $data) = @_; - my ($str) = &parse("s", $data); - printf "(%d bytes)\n", length $str; - }, -#define SSH2_MSG_UNIMPLEMENTED 3 /* 0x3 */ - 'SSH2_MSG_UNIMPLEMENTED' => sub { - my ($direction, $seq, $data) = @_; - my ($rseq) = &parse("u", $data); - printf "i%d\n", $rseq; - }, -#define SSH2_MSG_DEBUG 4 /* 0x4 */ - 'SSH2_MSG_DEBUG' => sub { - my ($direction, $seq, $data) = @_; - my ($disp, $message, $lang) = &parse("bss", $data); - printf "%s\n", &str($message); - }, -#define SSH2_MSG_SERVICE_REQUEST 5 /* 0x5 */ - 'SSH2_MSG_SERVICE_REQUEST' => sub { - my ($direction, $seq, $data) = @_; - my ($service) = &parse("s", $data); - printf "%s\n", &str($service); - }, -#define SSH2_MSG_SERVICE_ACCEPT 6 /* 0x6 */ - 'SSH2_MSG_SERVICE_ACCEPT' => sub { - my ($direction, $seq, $data) = @_; - my ($service) = &parse("s", $data); - printf "%s\n", &str($service); - }, -#define SSH2_MSG_KEXINIT 20 /* 0x14 */ - 'SSH2_MSG_KEXINIT' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_NEWKEYS 21 /* 0x15 */ - 'SSH2_MSG_NEWKEYS' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEXDH_INIT 30 /* 0x1e */ - 'SSH2_MSG_KEXDH_INIT' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEXDH_REPLY 31 /* 0x1f */ - 'SSH2_MSG_KEXDH_REPLY' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEX_DH_GEX_REQUEST 30 /* 0x1e */ - 'SSH2_MSG_KEX_DH_GEX_REQUEST' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */ - 'SSH2_MSG_KEX_DH_GEX_GROUP' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */ - 'SSH2_MSG_KEX_DH_GEX_INIT' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */ - 'SSH2_MSG_KEX_DH_GEX_REPLY' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEXGSS_INIT 30 /* 0x1e */ - 'SSH2_MSG_KEXGSS_INIT' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEXGSS_CONTINUE 31 /* 0x1f */ - 'SSH2_MSG_KEXGSS_CONTINUE' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEXGSS_COMPLETE 32 /* 0x20 */ - 'SSH2_MSG_KEXGSS_COMPLETE' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEXGSS_HOSTKEY 33 /* 0x21 */ - 'SSH2_MSG_KEXGSS_HOSTKEY' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEXGSS_ERROR 34 /* 0x22 */ - 'SSH2_MSG_KEXGSS_ERROR' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEXGSS_GROUPREQ 40 /* 0x28 */ - 'SSH2_MSG_KEXGSS_GROUPREQ' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEXGSS_GROUP 41 /* 0x29 */ - 'SSH2_MSG_KEXGSS_GROUP' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */ - 'SSH2_MSG_KEXRSA_PUBKEY' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */ - 'SSH2_MSG_KEXRSA_SECRET' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */ - 'SSH2_MSG_KEXRSA_DONE' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEX_ECDH_INIT 30 /* 0x1e */ - 'SSH2_MSG_KEX_ECDH_INIT' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_KEX_ECDH_REPLY 31 /* 0x1f */ - 'SSH2_MSG_KEX_ECDH_REPLY' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */ - 'SSH2_MSG_USERAUTH_REQUEST' => sub { - my ($direction, $seq, $data) = @_; - my ($user, $service, $method) = &parse("sss", $data); - my $out = sprintf "%s %s %s", - &str($user), &str($service), &str($method); - if ($method eq "publickey") { - my ($real) = &parse("b", $data); - $out .= " real=$real"; - } elsif ($method eq "password") { - my ($change) = &parse("b", $data); - $out .= " change=$change"; - } - print "$out\n"; - }, -#define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */ - 'SSH2_MSG_USERAUTH_FAILURE' => sub { - my ($direction, $seq, $data) = @_; - my ($options) = &parse("s", $data); - printf "%s\n", &str($options); - }, -#define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */ - 'SSH2_MSG_USERAUTH_SUCCESS' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_USERAUTH_BANNER 53 /* 0x35 */ - 'SSH2_MSG_USERAUTH_BANNER' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_USERAUTH_PK_OK 60 /* 0x3c */ - 'SSH2_MSG_USERAUTH_PK_OK' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 /* 0x3c */ - 'SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 /* 0x3c */ - 'SSH2_MSG_USERAUTH_INFO_REQUEST' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 /* 0x3d */ - 'SSH2_MSG_USERAUTH_INFO_RESPONSE' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_GLOBAL_REQUEST 80 /* 0x50 */ - 'SSH2_MSG_GLOBAL_REQUEST' => sub { - my ($direction, $seq, $data) = @_; - my ($type, $wantreply) = &parse("sb", $data); - printf "%s (%s)", $type, $wantreply eq "yes" ? "reply" : "noreply"; - my $request = [$seq, $type]; - push @{$globalreq{$direction}}, $request if $wantreply eq "yes"; - if ($type eq "tcpip-forward" or $type eq "cancel-tcpip-forward") { - my ($addr, $port) = &parse("su", $data); - printf " %s:%s", $addr, $port; - push @$request, $port; - } - print "\n"; - }, -#define SSH2_MSG_REQUEST_SUCCESS 81 /* 0x51 */ - 'SSH2_MSG_REQUEST_SUCCESS' => sub { - my ($direction, $seq, $data) = @_; - my $otherdir = ($direction eq "i" ? "o" : "i"); - my $request = shift @{$globalreq{$otherdir}}; - if (defined $request) { - printf "to %s", $request->[0]; - if ($request->[1] eq "tcpip-forward" and $request->[2] == 0) { - my ($port) = &parse("u", $data); - printf " port=%s", $port; - } - } else { - print "(spurious?)"; - } - print "\n"; - }, -#define SSH2_MSG_REQUEST_FAILURE 82 /* 0x52 */ - 'SSH2_MSG_REQUEST_FAILURE' => sub { - my ($direction, $seq, $data) = @_; - my $otherdir = ($direction eq "i" ? "o" : "i"); - my $request = shift @{$globalreq{$otherdir}}; - if (defined $request) { - printf "to %s", $request->[0]; - } else { - print "(spurious?)"; - } - print "\n"; - }, -#define SSH2_MSG_CHANNEL_OPEN 90 /* 0x5a */ - 'SSH2_MSG_CHANNEL_OPEN' => sub { - my ($direction, $seq, $data) = @_; - my ($type, $sid, $winsize, $packet) = &parse("suuu", $data); - # CHANNEL_OPEN tells the other side the _sender's_ id for the - # channel, so this choice between "s" and "c" prefixes is - # opposite to every other message in the protocol, which all - # quote the _recipient's_ id of the channel. - $sid = ($direction eq "i" ? "s" : "c") . $sid; - my $chan = {'id'=>$sid, 'state'=>'halfopen', - 'i'=>{'win'=>0, 'seq'=>0}, - 'o'=>{'win'=>0, 'seq'=>0}}; - $chan->{$direction}{'win'} = $winsize; - push @channels, $chan; - my $index = $#channels; - $chan_by_id{$sid} = $index; - printf "ch%d (%s) %s (--%d)", $index, $chan->{'id'}, $type, - $chan->{$direction}{'win'}; - if ($type eq "x11") { - my ($addr, $port) = &parse("su", $data); - printf " from %s:%s", $addr, $port; - } elsif ($type eq "forwarded-tcpip") { - my ($saddr, $sport, $paddr, $pport) = &parse("susu", $data); - printf " to %s:%s from %s:%s", $saddr, $sport, $paddr, $pport; - } elsif ($type eq "direct-tcpip") { - my ($daddr, $dport, $saddr, $sport) = &parse("susu", $data); - printf " to %s:%s from %s:%s", $daddr, $dport, $saddr, $sport; - } - print "\n"; - }, -#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 /* 0x5b */ - 'SSH2_MSG_CHANNEL_OPEN_CONFIRMATION' => sub { - my ($direction, $seq, $data) = @_; - my ($rid, $sid, $winsize, $packet) = &parse("uuuu", $data); - $rid = ($direction eq "i" ? "c" : "s") . $rid; - my $index = $chan_by_id{$rid}; - if (!defined $index) { - printf "UNKNOWN_CHANNEL (%s) (--%d)\n", $rid, $winsize; - return; - } - $sid = ($direction eq "i" ? "s" : "c") . $sid; - $chan_by_id{$sid} = $index; - my $chan = $channels[$index]; - $chan->{'id'} = ($direction eq "i" ? "$rid/$sid" : "$sid/$rid"); - $chan->{'state'} = 'open'; - $chan->{$direction}{'win'} = $winsize; - printf "ch%d (%s) (--%d)\n", $index, $chan->{'id'}, - $chan->{$direction}{'win'}; - }, -#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 /* 0x5c */ - 'SSH2_MSG_CHANNEL_OPEN_FAILURE' => sub { - my ($direction, $seq, $data) = @_; - my ($rid, $reason, $desc, $lang) = &parse("uuss", $data); - $rid = ($direction eq "i" ? "c" : "s") . $rid; - my $index = $chan_by_id{$rid}; - if (!defined $index) { - printf "UNKNOWN_CHANNEL (%s) %s\n", $rid, &str($reason); - return; - } - my $chan = $channels[$index]; - $chan->{'state'} = 'rejected'; - printf "ch%d (%s) %s\n", $index, $chan->{'id'}, &str($reason); - }, -#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 /* 0x5d */ - 'SSH2_MSG_CHANNEL_WINDOW_ADJUST' => sub { - my ($direction, $seq, $data) = @_; - my ($rid, $bytes) = &parse("uu", $data); - $rid = ($direction eq "i" ? "c" : "s") . $rid; - my $index = $chan_by_id{$rid}; - if (!defined $index) { - printf "UNKNOWN_CHANNEL (%s) +%d\n", $rid, $bytes; - return; - } - my $chan = $channels[$index]; - $chan->{$direction}{'win'} += $bytes; - printf "ch%d (%s) +%d (--%d)\n", $index, $chan->{'id'}, $bytes, - $chan->{$direction}{'win'}; - }, -#define SSH2_MSG_CHANNEL_DATA 94 /* 0x5e */ - 'SSH2_MSG_CHANNEL_DATA' => sub { - my ($direction, $seq, $data) = @_; - my ($rid, $bytes) = &parse("uu", $data); - $rid = ($direction eq "i" ? "c" : "s") . $rid; - my $index = $chan_by_id{$rid}; - if (!defined $index) { - printf "UNKNOWN_CHANNEL (%s), %s bytes\n", $rid, $bytes; - return; - } - my $chan = $channels[$index]; - $chan->{$direction}{'seq'} += $bytes; - printf "ch%d (%s), %s bytes (%d--%d)\n", $index, $chan->{'id'}, $bytes, - $chan->{$direction}{'seq'}-$bytes, $chan->{$direction}{'seq'}; - my @realdata = splice @$data, 0, $bytes; - if ($dumpdata) { - my $filekey = $direction . "file"; - if (!defined $chan->{$filekey}) { - my $filename = sprintf "ch%d.%s", $index, $direction; - $chan->{$filekey} = FileHandle->new(">$filename"); - if (!defined $chan->{$filekey}) { - die "$filename: $!\n"; - } - } - die "channel data not present in $seq\n" if @realdata < $bytes; - my $rawdata = pack "C*", @realdata; - my $fh = $chan->{$filekey}; - print $fh $rawdata; - } - if (@realdata == $bytes and defined $chan->{$direction."data"}) { - my $rawdata = pack "C*", @realdata; - $chan->{$direction."data"}->($chan, $index, $direction, $rawdata); - } - }, -#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 /* 0x5f */ - 'SSH2_MSG_CHANNEL_EXTENDED_DATA' => sub { - my ($direction, $seq, $data) = @_; - my ($rid, $type, $bytes) = &parse("uuu", $data); - if ($type == 1) { - $type = "SSH_EXTENDED_DATA_STDERR"; - } - $rid = ($direction eq "i" ? "c" : "s") . $rid; - my $index = $chan_by_id{$rid}; - if (!defined $index) { - printf "UNKNOWN_CHANNEL (%s), type %s, %s bytes\n", $rid, - $type, $bytes; - return; - } - my $chan = $channels[$index]; - $chan->{$direction}{'seq'} += $bytes; - printf "ch%d (%s), type %s, %s bytes (%d--%d)\n", $index,$chan->{'id'}, - $type, $bytes, $chan->{$direction}{'seq'}-$bytes, - $chan->{$direction}{'seq'}; - my @realdata = splice @$data, 0, $bytes; - if ($dumpdata) { - # We treat EXTENDED_DATA as equivalent to DATA, for the - # moment. It's not clear what else would be a better thing - # to do with it, and this at least is the Right Answer if - # the data is going to a terminal and the aim is to debug - # the terminal emulator. - my $filekey = $direction . "file"; - if (!defined $chan->{$filekey}) { - my $filename = sprintf "ch%d.%s", $index, $direction; - $chan->{$filekey} = FileHandle->new(">$filename"); - if (!defined $chan->{$filekey}) { - die "$filename: $!\n"; - } - } - die "channel data not present in $seq\n" if @realdata < $bytes; - my $rawdata = pack "C*", @realdata; - my $fh = $chan->{$filekey}; - print $fh $rawdata; - } - if (@realdata == $bytes and defined $chan->{$direction."data"}) { - my $rawdata = pack "C*", @realdata; - $chan->{$direction."data"}->($chan, $index, $direction, $rawdata); - } - }, -#define SSH2_MSG_CHANNEL_EOF 96 /* 0x60 */ - 'SSH2_MSG_CHANNEL_EOF' => sub { - my ($direction, $seq, $data) = @_; - my ($rid) = &parse("uu", $data); - $rid = ($direction eq "i" ? "c" : "s") . $rid; - my $index = $chan_by_id{$rid}; - if (!defined $index) { - printf "UNKNOWN_CHANNEL (%s)\n", $rid; - return; - } - my $chan = $channels[$index]; - printf "ch%d (%s)\n", $index, $chan->{'id'}; - }, -#define SSH2_MSG_CHANNEL_CLOSE 97 /* 0x61 */ - 'SSH2_MSG_CHANNEL_CLOSE' => sub { - my ($direction, $seq, $data) = @_; - my ($rid) = &parse("uu", $data); - $rid = ($direction eq "i" ? "c" : "s") . $rid; - my $index = $chan_by_id{$rid}; - if (!defined $index) { - printf "UNKNOWN_CHANNEL (%s)\n", $rid; - return; - } - my $chan = $channels[$index]; - $chan->{'state'} = ($chan->{'state'} eq "open" ? "halfclosed" : - $chan->{'state'} eq "halfclosed" ? "closed" : - "confused"); - if ($chan->{'state'} eq "closed") { - $chan->{'ifile'}->close if defined $chan->{'ifile'}; - $chan->{'ofile'}->close if defined $chan->{'ofile'}; - } - printf "ch%d (%s)\n", $index, $chan->{'id'}; - }, -#define SSH2_MSG_CHANNEL_REQUEST 98 /* 0x62 */ - 'SSH2_MSG_CHANNEL_REQUEST' => sub { - my ($direction, $seq, $data) = @_; - my ($rid, $type, $wantreply) = &parse("usb", $data); - $rid = ($direction eq "i" ? "c" : "s") . $rid; - my $index = $chan_by_id{$rid}; - my $chan; - if (!defined $index) { - printf "UNKNOWN_CHANNEL (%s) %s (%s)", $rid, - $type, $wantreply eq "yes" ? "reply" : "noreply"; - } else { - $chan = $channels[$index]; - printf "ch%d (%s) %s (%s)", $index, $chan->{'id'}, - $type, $wantreply eq "yes" ? "reply" : "noreply"; - push @{$chan->{'requests_'.$direction}}, [$seq, $type] - if $wantreply eq "yes"; - } - if ($type eq "pty-req") { - my ($term, $w, $h, $pw, $ph, $modes) = &parse("suuuus", $data); - printf " %s %sx%s", &str($term), $w, $h; - } elsif ($type eq "x11-req") { - my ($single, $xprot, $xcookie, $xscreen) = &parse("bssu", $data); - print " one-off" if $single eq "yes"; - printf " %s :%s", $xprot, $xscreen; - } elsif ($type eq "exec") { - my ($command) = &parse("s", $data); - printf " %s", &str($command); - } elsif ($type eq "subsystem") { - my ($subsys) = &parse("s", $data); - printf " %s", &str($subsys); - if ($subsys eq "sftp") { - &sftp_setup($index); - } - } elsif ($type eq "window-change") { - my ($w, $h, $pw, $ph) = &parse("uuuu", $data); - printf " %sx%s", $w, $h; - } elsif ($type eq "xon-xoff") { - my ($can) = &parse("b", $data); - printf " %s", $can; - } elsif ($type eq "signal") { - my ($sig) = &parse("s", $data); - printf " %s", &str($sig); - } elsif ($type eq "exit-status") { - my ($status) = &parse("u", $data); - printf " %s", $status; - } elsif ($type eq "exit-signal") { - my ($sig, $core, $error, $lang) = &parse("sbss", $data); - printf " %s", &str($sig); - print " (core dumped)" if $core eq "yes"; - } - print "\n"; - }, -#define SSH2_MSG_CHANNEL_SUCCESS 99 /* 0x63 */ - 'SSH2_MSG_CHANNEL_SUCCESS' => sub { - my ($direction, $seq, $data) = @_; - my ($rid) = &parse("uu", $data); - $rid = ($direction eq "i" ? "c" : "s") . $rid; - my $index = $chan_by_id{$rid}; - if (!defined $index) { - printf "UNKNOWN_CHANNEL (%s)\n", $rid; - return; - } - my $chan = $channels[$index]; - printf "ch%d (%s)", $index, $chan->{'id'}; - my $otherdir = ($direction eq "i" ? "o" : "i"); - my $request = shift @{$chan->{'requests_' . $otherdir}}; - if (defined $request) { - printf " to %s", $request->[0]; - } else { - print " (spurious?)"; - } - print "\n"; - }, -#define SSH2_MSG_CHANNEL_FAILURE 100 /* 0x64 */ - 'SSH2_MSG_CHANNEL_FAILURE' => sub { - my ($direction, $seq, $data) = @_; - my ($rid) = &parse("uu", $data); - $rid = ($direction eq "i" ? "c" : "s") . $rid; - my $index = $chan_by_id{$rid}; - if (!defined $index) { - printf "UNKNOWN_CHANNEL (%s)\n", $rid; - return; - } - my $chan = $channels[$index]; - printf "ch%d (%s)", $index, $chan->{'id'}; - my $otherdir = ($direction eq "i" ? "o" : "i"); - my $request = shift @{$chan->{'requests_' . $otherdir}}; - if (defined $request) { - printf " to %s", $request->[0]; - } else { - print " (spurious?)"; - } - print "\n"; - }, -#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 - 'SSH2_MSG_USERAUTH_GSSAPI_RESPONSE' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 - 'SSH2_MSG_USERAUTH_GSSAPI_TOKEN' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 - 'SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 - 'SSH2_MSG_USERAUTH_GSSAPI_ERROR' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 - 'SSH2_MSG_USERAUTH_GSSAPI_ERRTOK' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -#define SSH2_MSG_USERAUTH_GSSAPI_MIC 66 - 'SSH2_MSG_USERAUTH_GSSAPI_MIC' => sub { - my ($direction, $seq, $data) = @_; - print "\n"; - }, -); - -our %disc_reasons = ( - 1 => "SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT", - 2 => "SSH_DISCONNECT_PROTOCOL_ERROR", - 3 => "SSH_DISCONNECT_KEY_EXCHANGE_FAILED", - 4 => "SSH_DISCONNECT_RESERVED", - 5 => "SSH_DISCONNECT_MAC_ERROR", - 6 => "SSH_DISCONNECT_COMPRESSION_ERROR", - 7 => "SSH_DISCONNECT_SERVICE_NOT_AVAILABLE", - 8 => "SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED", - 9 => "SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE", - 10 => "SSH_DISCONNECT_CONNECTION_LOST", - 11 => "SSH_DISCONNECT_BY_APPLICATION", - 12 => "SSH_DISCONNECT_TOO_MANY_CONNECTIONS", - 13 => "SSH_DISCONNECT_AUTH_CANCELLED_BY_USER", - 14 => "SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE", - 15 => "SSH_DISCONNECT_ILLEGAL_USER_NAME", -); - -my %verbose_packet_dump_functions = ( - 'SSH2_MSG_KEXINIT' => sub { - my ($data) = @_; - my ($cookie0, $cookie1, $cookie2, $cookie3, - $kex, $hostkey, $cscipher, $sccipher, $csmac, $scmac, - $cscompress, $sccompress, $cslang, $sclang, $guess, $reserved) = - &parse("uuuussssssssssbu", $data); - printf(" cookie: %08x%08x%08x%08x\n", - $cookie0, $cookie1, $cookie2, $cookie3); - my $print_namelist = sub { - my @names = split /,/, $_[1]; - printf " %s: name-list with %d items%s\n", $_[0], (scalar @names), - join "", map { "\n $_" } @names; - }; - $print_namelist->("kex", $kex); - $print_namelist->("host key", $hostkey); - $print_namelist->("client->server cipher", $cscipher); - $print_namelist->("server->client cipher", $sccipher); - $print_namelist->("client->server MAC", $csmac); - $print_namelist->("server->client MAC", $scmac); - $print_namelist->("client->server compression", $cscompress); - $print_namelist->("server->client compression", $sccompress); - $print_namelist->("client->server language", $cslang); - $print_namelist->("server->client language", $sclang); - printf " first kex packet follows: %s\n", $guess; - printf " reserved field: %#x\n", $reserved; - }, - 'SSH2_MSG_KEXDH_INIT' => sub { - my ($data) = @_; - my ($e) = &parse("m", $data); - printf " e: %s\n", $e; - }, - 'SSH2_MSG_KEX_DH_GEX_REQUEST' => sub { - my ($data) = @_; - my ($min, $pref, $max) = &parse("uuu", $data); - printf " min bits: %d\n", $min; - printf " preferred bits: %d\n", $pref; - printf " max bits: %d\n", $max; - }, - 'SSH2_MSG_KEX_DH_GEX_GROUP' => sub { - my ($data) = @_; - my ($p, $g) = &parse("mm", $data); - printf " p: %s\n", $p; - printf " g: %s\n", $g; - }, - 'SSH2_MSG_KEX_DH_GEX_INIT' => sub { - my ($data) = @_; - my ($e) = &parse("m", $data); - printf " e: %s\n", $e; - }, - 'SSH2_MSG_KEX_ECDH_INIT' => sub { - my ($data) = @_; - my ($cpv) = &parse("s", $data); - # Public values in ECDH depend for their interpretation on the - # selected curve, and this script doesn't cross-analyse the - # two KEXINIT packets to independently figure out what that - # curve is. So the best we can do is just dump the raw data. - printf " client public value: %s\n", (unpack "H*", $cpv); - }, - 'SSH2_MSG_KEXDH_REPLY' => sub { - my ($data) = @_; - my ($hostkeyblob, $f, $sigblob) = &parse("sms", $data); - my ($hktype, @hostkey) = &parse_public_key($hostkeyblob); - printf " host key: %s\n", $hktype; - while (@hostkey) { - my ($key, $value) = splice @hostkey, 0, 2; - printf " $key: $value\n"; - } - printf " f: %s\n", $f; - printf " signature:\n"; - my @signature = &parse_signature($sigblob, $hktype); - while (@signature) { - my ($key, $value) = splice @signature, 0, 2; - printf " $key: $value\n"; - } - }, - 'SSH2_MSG_KEX_DH_GEX_REPLY' => sub { - my ($data) = @_; - my ($hostkeyblob, $f, $sigblob) = &parse("sms", $data); - my ($hktype, @hostkey) = &parse_public_key($hostkeyblob); - printf " host key: %s\n", $hktype; - while (@hostkey) { - my ($key, $value) = splice @hostkey, 0, 2; - printf " $key: $value\n"; - } - printf " f: %s\n", $f; - printf " signature:\n"; - my @signature = &parse_signature($sigblob, $hktype); - while (@signature) { - my ($key, $value) = splice @signature, 0, 2; - printf " $key: $value\n"; - } - }, - 'SSH2_MSG_KEX_ECDH_REPLY' => sub { - my ($data) = @_; - my ($hostkeyblob, $spv, $sigblob) = &parse("sss", $data); - my ($hktype, @hostkey) = &parse_public_key($hostkeyblob); - printf " host key: %s\n", $hktype; - while (@hostkey) { - my ($key, $value) = splice @hostkey, 0, 2; - printf " $key: $value\n"; - } - printf " server public value: %s\n", (unpack "H*", $spv); - printf " signature:\n"; - my @signature = &parse_signature($sigblob, $hktype); - while (@signature) { - my ($key, $value) = splice @signature, 0, 2; - printf " $key: $value\n"; - } - }, - 'SSH2_MSG_NEWKEYS' => sub {}, - 'SSH2_MSG_SERVICE_REQUEST' => sub { - my ($data) = @_; - my ($servname) = &parse("s", $data); - printf " service name: %s\n", $servname; - }, - 'SSH2_MSG_SERVICE_ACCEPT' => sub { - my ($data) = @_; - my ($servname) = &parse("s", $data); - printf " service name: %s\n", $servname; - }, - 'SSH2_MSG_DISCONNECT' => sub { - my ($data) = @_; - my ($reason, $desc, $lang) = &parse("uss", $data); - printf(" reason code: %d%s\n", $reason, - defined $disc_reasons{$reason} ? - " ($disc_reasons{$reason})" : "" ); - printf " description: '%s'\n", $desc; - printf " language tag: '%s'\n", $lang; - }, - 'SSH2_MSG_DEBUG' => sub { - my ($data) = @_; - my ($display, $desc, $lang) = &parse("bss", $data); - printf " always display: %s\n", $display; - printf " description: '%s'\n", $desc; - printf " language tag: '%s'\n", $lang; - }, - 'SSH2_MSG_IGNORE' => sub { - my ($data) = @_; - my ($payload) = &parse("s", $data); - printf " data: %s\n", unpack "H*", $payload; - }, - 'SSH2_MSG_UNIMPLEMENTED' => sub { - my ($data) = @_; - my ($seq) = &parse("u", $data); - printf " sequence number: %d\n", $seq; - }, - 'SSH2_MSG_KEXGSS_INIT' => sub { - my ($data) = @_; - my ($token, $e) = &parse("sm", $data); - printf " output token: %s\n", unpack "H*", $token; - printf " e: %s\n", $e; - }, - 'SSH2_MSG_KEXGSS_CONTINUE' => sub { - my ($data) = @_; - my ($token) = &parse("s", $data); - printf " output token: %s\n", unpack "H*", $token; - }, - 'SSH2_MSG_KEXGSS_COMPLETE' => sub { - my ($data) = @_; - my ($f, $permsgtoken, $got_output) = &parse("msb", $data); - printf " f: %s\n", $f; - printf " per-message token: %s\n", unpack "H*", $permsgtoken; - printf " output token present: %s\n", $got_output; - if ($got_output eq "yes") { - my ($token) = &parse("s", $data); - printf " output token: %s\n", unpack "H*", $token; - } - }, - 'SSH2_MSG_KEXGSS_HOSTKEY' => sub { - my ($data) = @_; - my ($hostkey) = &parse("s", $data); - printf " host key: %s\n", unpack "H*", $hostkey; - }, - 'SSH2_MSG_KEXGSS_ERROR' => sub { - my ($data) = @_; - my ($maj, $min, $msg, $lang) = &parse("uuss", $data); - printf " major status: %d\n", $maj; - printf " minor status: %d\n", $min; - printf " message: '%s'\n", $msg; - printf " language tag: '%s'\n", $lang; - }, - 'SSH2_MSG_KEXGSS_GROUPREQ' => sub { - my ($data) = @_; - my ($min, $pref, $max) = &parse("uuu", $data); - printf " min bits: %d\n", $min; - printf " preferred bits: %d\n", $pref; - printf " max bits: %d\n", $max; - }, - 'SSH2_MSG_KEXGSS_GROUP' => sub { - my ($data) = @_; - my ($p, $g) = &parse("mm", $data); - printf " p: %s\n", $p; - printf " g: %s\n", $g; - }, -); - -my %sftp_packets = ( -#define SSH_FXP_INIT 1 /* 0x1 */ - 0x1 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($ver) = &parse("u", $data); - printf "SSH_FXP_INIT %d\n", $ver; - }, -#define SSH_FXP_VERSION 2 /* 0x2 */ - 0x2 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($ver) = &parse("u", $data); - printf "SSH_FXP_VERSION %d\n", $ver; - }, -#define SSH_FXP_OPEN 3 /* 0x3 */ - 0x3 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $path, $pflags) = &parse("usu", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_OPEN"); - printf " \"%s\" ", $path; - if ($pflags eq 0) { - print "0"; - } else { - my $sep = ""; - if ($pflags & 1) { $pflags ^= 1; print "${sep}READ"; $sep = "|"; } - if ($pflags & 2) { $pflags ^= 2; print "${sep}WRITE"; $sep = "|"; } - if ($pflags & 4) { $pflags ^= 4; print "${sep}APPEND"; $sep = "|"; } - if ($pflags & 8) { $pflags ^= 8; print "${sep}CREAT"; $sep = "|"; } - if ($pflags & 16) { $pflags ^= 16; print "${sep}TRUNC"; $sep = "|"; } - if ($pflags & 32) { $pflags ^= 32; print "${sep}EXCL"; $sep = "|"; } - if ($pflags) { print "${sep}${pflags}"; } - } - print "\n"; - }, -#define SSH_FXP_CLOSE 4 /* 0x4 */ - 0x4 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $handle) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_CLOSE"); - printf " \"%s\"", &stringescape($handle); - print "\n"; - }, -#define SSH_FXP_READ 5 /* 0x5 */ - 0x5 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $handle, $offset, $len) = &parse("usUu", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_READ"); - printf " \"%s\" %d %d", &stringescape($handle), $offset, $len; - print "\n"; - }, -#define SSH_FXP_WRITE 6 /* 0x6 */ - 0x6 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $handle, $offset, $wdata) = &parse("usUs", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_WRITE"); - printf " \"%s\" %d [%d bytes]", &stringescape($handle), $offset, length $wdata; - print "\n"; - }, -#define SSH_FXP_LSTAT 7 /* 0x7 */ - 0x7 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $path) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_LSTAT"); - printf " \"%s\"", $path; - print "\n"; - }, -#define SSH_FXP_FSTAT 8 /* 0x8 */ - 0x8 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $handle) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_FSTAT"); - printf " \"%s\"", &stringescape($handle); - print "\n"; - }, -#define SSH_FXP_SETSTAT 9 /* 0x9 */ - 0x9 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $path) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_SETSTAT"); - my $attrs = &sftp_parse_attrs($data); - printf " \"%s\" %s", $path, $attrs; - print "\n"; - }, -#define SSH_FXP_FSETSTAT 10 /* 0xa */ - 0xa => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $handle) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_FSETSTAT"); - my $attrs = &sftp_parse_attrs($data); - printf " \"%s\" %s", &stringescape($handle), $attrs; - print "\n"; - }, -#define SSH_FXP_OPENDIR 11 /* 0xb */ - 0xb => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $path) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_OPENDIR"); - printf " \"%s\"", $path; - print "\n"; - }, -#define SSH_FXP_READDIR 12 /* 0xc */ - 0xc => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $handle) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_READDIR"); - printf " \"%s\"", &stringescape($handle); - print "\n"; - }, -#define SSH_FXP_REMOVE 13 /* 0xd */ - 0xd => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $path) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_REMOVE"); - printf " \"%s\"", $path; - print "\n"; - }, -#define SSH_FXP_MKDIR 14 /* 0xe */ - 0xe => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $path) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_MKDIR"); - printf " \"%s\"", $path; - print "\n"; - }, -#define SSH_FXP_RMDIR 15 /* 0xf */ - 0xf => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $path) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_RMDIR"); - printf " \"%s\"", $path; - print "\n"; - }, -#define SSH_FXP_REALPATH 16 /* 0x10 */ - 0x10 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $path) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_REALPATH"); - printf " \"%s\"", $path; - print "\n"; - }, -#define SSH_FXP_STAT 17 /* 0x11 */ - 0x11 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $path) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_STAT"); - printf " \"%s\"", $path; - print "\n"; - }, -#define SSH_FXP_RENAME 18 /* 0x12 */ - 0x12 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $srcpath, $dstpath) = &parse("uss", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_RENAME"); - printf " \"%s\" \"%s\"", $srcpath, $dstpath; - print "\n"; - }, -#define SSH_FXP_STATUS 101 /* 0x65 */ - 0x65 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $status) = &parse("uu", $data); - &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_STATUS"); - print " "; - if ($status eq "0") { print "SSH_FX_OK"; } - elsif ($status eq "1") { print "SSH_FX_EOF"; } - elsif ($status eq "2") { print "SSH_FX_NO_SUCH_FILE"; } - elsif ($status eq "3") { print "SSH_FX_PERMISSION_DENIED"; } - elsif ($status eq "4") { print "SSH_FX_FAILURE"; } - elsif ($status eq "5") { print "SSH_FX_BAD_MESSAGE"; } - elsif ($status eq "6") { print "SSH_FX_NO_CONNECTION"; } - elsif ($status eq "7") { print "SSH_FX_CONNECTION_LOST"; } - elsif ($status eq "8") { print "SSH_FX_OP_UNSUPPORTED"; } - else { printf "[unknown status %d]", $status; } - print "\n"; - }, -#define SSH_FXP_HANDLE 102 /* 0x66 */ - 0x66 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $handle) = &parse("us", $data); - &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_HANDLE"); - printf " \"%s\"", &stringescape($handle); - print "\n"; - }, -#define SSH_FXP_DATA 103 /* 0x67 */ - 0x67 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $retdata) = &parse("us", $data); - &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_DATA"); - printf " [%d bytes]", length $retdata; - print "\n"; - }, -#define SSH_FXP_NAME 104 /* 0x68 */ - 0x68 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $count) = &parse("uu", $data); - &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_NAME"); - for my $i (1..$count) { - my ($name, $longname) = &parse("ss", $data); - my $attrs = &sftp_parse_attrs($data); - print " [name=\"$name\", longname=\"$longname\", attrs=$attrs]"; - } - print "\n"; - }, -#define SSH_FXP_ATTRS 105 /* 0x69 */ - 0x69 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid) = &parse("u", $data); - &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_ATTRS"); - my $attrs = &sftp_parse_attrs($data); - printf " %s", $attrs; - print "\n"; - }, -#define SSH_FXP_EXTENDED 200 /* 0xc8 */ - 0xc8 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid, $type) = &parse("us", $data); - &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_EXTENDED"); - printf " \"%s\"", $type; - print "\n"; - }, -#define SSH_FXP_EXTENDED_REPLY 201 /* 0xc9 */ - 0xc9 => sub { - my ($chan, $index, $direction, $id, $data) = @_; - my ($reqid) = &parse("u", $data); - print "\n"; - &sftp_logreply($chan, $direction, $reqid,$id,"SSH_FXP_EXTENDED_REPLY"); - }, -); - -for my $type (keys %verbose_packet) { - if (!defined $verbose_packet_dump_functions{$type}) { - die "no verbose dump available for packet type $type\n"; - } -} - -my ($direction, $seq, $ourseq, $type, $data, $recording); -my %ourseqs = ('i'=>0, 'o'=>0); - -$recording = 0; -while (<<>>) { - if ($recording) { - if (/^ [0-9a-fA-F]{8} ((?:[0-9a-fA-F]{2} )*[0-9a-fA-F]{2})/) { - push @$data, map { $_ eq "XX" ? -1 : hex $_ } split / /, $1; - } else { - $recording = 0; - my $fullseq = "$direction$ourseq"; - print "$fullseq: $type "; - - my ($verbose_dump, $verbose_data) = undef; - if (defined $verbose_packet_dump_functions{$type} && - ($verbose_all || defined $verbose_packet{$type})) { - $verbose_dump = $verbose_packet_dump_functions{$type}; - $verbose_data = [ @$data ]; - } - - if (defined $packets{$type}) { - $packets{$type}->($direction, $fullseq, $data); - } else { - printf "raw %s\n", join "", map { sprintf "%02x", $_ } @$data; - } - if (defined $verbose_dump) { - $verbose_dump->($verbose_data); - if (@$verbose_data) { - printf(" trailing bytes: %s\n", - unpack "H*", pack "C*", @$verbose_data); - } - } - } - } - if (/^(Incoming|Outgoing) packet #0x([0-9a-fA-F]+), type \d+ \/ 0x[0-9a-fA-F]+ \((.*)\)/) { - $direction = ($1 eq "Incoming" ? 'i' : 'o'); - # $seq is the sequence number quoted in the log file. $ourseq - # is our own count of the sequence number, which differs in - # that it shouldn't wrap at 2^32, should anyone manage to run - # this script over such a huge log file. - $seq = hex $2; - $ourseq = $ourseqs{$direction}++; - $type = $3; - $data = []; - $recording = 1; - } - if ($pass_through_events && m/^Event Log: ([^\n]*)$/) { - printf "event: $1\n"; - } -} - -if ($dumpchannels) { - my %stateorder = ('closed'=>0, 'rejected'=>1, - 'halfclosed'=>2, 'open'=>3, 'halfopen'=>4); - for my $index (0..$#channels) { - my $chan = $channels[$index]; - my $so = $stateorder{$chan->{'state'}}; - $so = 1000 unless defined $so; # any state I've missed above comes last - $chan->{'index'} = sprintf "ch%d", $index; - $chan->{'order'} = sprintf "%08d %08d", $so, $index; - } - my @sortedchannels = sort { $a->{'order'} cmp $b->{'order'} } @channels; - for my $chan (@sortedchannels) { - printf "%s (%s): %s\n", $chan->{'index'}, $chan->{'id'}, $chan->{'state'}; - } -} - -sub format_unsigned_hex_integer { - my $abs = join "", map { sprintf "%02x", $_ } @_; - $abs =~ s!^0*!!g; - $abs = "0" if $abs eq ""; - return "0x" . $abs; -} - -sub parseone { - my ($type, $data) = @_; - if ($type eq "u") { # uint32 - my @bytes = splice @$data, 0, 4; - return "" if @bytes < 4 or grep { $_<0 } @bytes; - return unpack "N", pack "C*", @bytes; - } elsif ($type eq "U") { # uint64 - my @bytes = splice @$data, 0, 8; - return "" if @bytes < 8 or grep { $_<0 } @bytes; - my @words = unpack "NN", pack "C*", @bytes; - return ($words[0] << 32) + $words[1]; - } elsif ($type eq "b") { # boolean - my $byte = shift @$data; - return "" if !defined $byte or $byte < 0; - return $byte ? "yes" : "no"; - } elsif ($type eq "B") { # byte - my $byte = shift @$data; - return "" if !defined $byte or $byte < 0; - return $byte; - } elsif ($type eq "s" or $type eq "m") { # string, mpint - my @bytes = splice @$data, 0, 4; - return "" if @bytes < 4 or grep { $_<0 } @bytes; - my $len = unpack "N", pack "C*", @bytes; - @bytes = splice @$data, 0, $len; - return "" if @bytes < $len or grep { $_<0 } @bytes; - if ($type eq "m") { - my $str = ""; - if ($bytes[0] >= 128) { - # Take two's complement. - @bytes = map { 0xFF ^ $_ } @bytes; - for my $i (reverse 0..$#bytes) { - if ($bytes[$i] < 0xFF) { - $bytes[$i]++; - last; - } else { - $bytes[$i] = 0; - } - } - $str = "-"; - } - $str .= &format_unsigned_hex_integer(@bytes); - return $str; - } else { - return pack "C*", @bytes; - } - } -} - -sub parse { - my ($template, $data) = @_; - return map { &parseone($_, $data) } split //, $template; -} - -sub str { - # Quote as a string. If I get enthusiastic I might arrange for - # strange characters inside the string to be quoted. - my $str = shift @_; - return "'$str'"; -} - -sub sftp_setup { - my $index = shift @_; - my $chan = $channels[$index]; - $chan->{'obuf'} = $chan->{'ibuf'} = ''; - $chan->{'ocnt'} = $chan->{'icnt'} = 0; - $chan->{'odata'} = $chan->{'idata'} = \&sftp_data; - $chan->{'sftpreqs'} = {}; -} - -sub sftp_data { - my ($chan, $index, $direction, $data) = @_; - my $buf = \$chan->{$direction."buf"}; - my $cnt = \$chan->{$direction."cnt"}; - $$buf .= $data; - while (length $$buf >= 4) { - my $msglen = unpack "N", $$buf; - last if length $$buf < 4 + $msglen; - my $msg = substr $$buf, 4, $msglen; - $$buf = substr $$buf, 4 + $msglen; - $msg = [unpack "C*", $msg]; - my $type = shift @$msg; - my $id = sprintf "ch%d_sftp_%s%d", $index, $direction, ${$cnt}++; - print "$id: "; - if (defined $sftp_packets{$type}) { - $sftp_packets{$type}->($chan, $index, $direction, $id, $msg); - } else { - printf "unknown SFTP packet type %d\n", $type; - } - } -} - -sub sftp_logreq { - my ($chan, $direction, $reqid, $id, $name) = @_; - print "$name"; - if ($direction eq "o") { # requests coming _in_ are too weird to track - $chan->{'sftpreqs'}->{$reqid} = $id; - } -} - -sub sftp_logreply { - my ($chan, $direction, $reqid, $id, $name) = @_; - print "$name"; - if ($direction eq "i") { # replies going _out_ are too weird to track - if (defined $chan->{'sftpreqs'}->{$reqid}) { - print " to ", $chan->{'sftpreqs'}->{$reqid}; - $chan->{'sftpreqs'}->{$reqid} = undef; - } - } -} - -sub sftp_parse_attrs { - my ($data) = @_; - my ($flags) = &parse("u", $data); - return $flags if $flags eq ""; - my $out = "{"; - my $sep = ""; - if ($flags & 0x00000001) { # SSH_FILEXFER_ATTR_SIZE - $out .= $sep . sprintf "size=%d", &parse("U", $data); - $sep = ", "; - } - if ($flags & 0x00000002) { # SSH_FILEXFER_ATTR_UIDGID - $out .= $sep . sprintf "uid=%d", &parse("u", $data); - $out .= $sep . sprintf "gid=%d", &parse("u", $data); - $sep = ", "; - } - if ($flags & 0x00000004) { # SSH_FILEXFER_ATTR_PERMISSIONS - $out .= $sep . sprintf "perms=%#o", &parse("u", $data); - $sep = ", "; - } - if ($flags & 0x00000008) { # SSH_FILEXFER_ATTR_ACMODTIME - $out .= $sep . sprintf "atime=%d", &parse("u", $data); - $out .= $sep . sprintf "mtime=%d", &parse("u", $data); - $sep = ", "; - } - if ($flags & 0x80000000) { # SSH_FILEXFER_ATTR_EXTENDED - my $extcount = &parse("u", $data); - while ($extcount-- > 0) { - $out .= $sep . sprintf "\"%s\"=\"%s\"", &parse("ss", $data); - $sep = ", "; - } - } - $out .= "}"; - return $out; -} - -sub parse_public_key { - my ($blob) = @_; - my $data = [ unpack "C*", $blob ]; - my @toret; - my ($type) = &parse("s", $data); - push @toret, $type; - if ($type eq "ssh-rsa") { - my ($e, $n) = &parse("mm", $data); - push @toret, "e", $e, "n", $n; - } elsif ($type eq "ssh-dss") { - my ($p, $q, $g, $y) = &parse("mmmm", $data); - push @toret, "p", $p, "q", $q, "g", $g, "y", $y; - } elsif ($type eq "ssh-ed25519") { - my ($xyblob) = &parse("s", $data); - my @y = unpack "C*", $xyblob; - push @toret, "hibit(x)", $y[$#y] & 1; - $y[$#y] &= ~1; - push @toret, "y & ~1", &format_unsigned_hex_integer(@y); - } elsif ($type =~ m!^ecdsa-sha2-nistp(256|384|521)$!) { - my ($curvename, $blob) = &parse("ss", $data); - push @toret, "curve name", $curvename; - my @blobdata = unpack "C*", $blob; - my ($fmt) = &parse("B", \@blobdata); - push @toret, "format byte", $fmt; - if ($fmt == 4) { - push @toret, "x", &format_unsigned_hex_integer( - @blobdata[0..($#blobdata+1)/2-1]); - push @toret, "y", &format_unsigned_hex_integer( - @blobdata[($#blobdata+1)/2..$#blobdata]); - } - } else { - push @toret, "undecoded data", unpack "H*", pack "C*", @$data; - } - return @toret; -}; - -sub parse_signature { - my ($blob, $keytype) = @_; - my $data = [ unpack "C*", $blob ]; - my @toret; - if ($keytype eq "ssh-rsa") { - my ($type, $s) = &parse("ss", $data); - push @toret, "sig type", $type; - push @toret, "s", &format_unsigned_hex_integer(unpack "C*", $s); - } elsif ($keytype eq "ssh-dss") { - my ($type, $subblob) = &parse("ss", $data); - push @toret, "sig type", $type; - push @toret, "r", &format_unsigned_hex_integer( - unpack "C*", substr($subblob, 0, 20)); - push @toret, "s", &format_unsigned_hex_integer( - unpack "C*", substr($subblob, 20, 40)); - } elsif ($keytype eq "ssh-ed25519") { - my ($type, $rsblob) = &parse("ss", $data); - push @toret, "sig type", $type; - my @ry = unpack "C*", $rsblob; - my @sy = splice @ry, 32, 32; - push @toret, "hibit(r.x)", $ry[$#ry] & 1; - $ry[$#ry] &= ~1; - push @toret, "r.y & ~1", &format_unsigned_hex_integer(@ry); - push @toret, "hibit(s.x)", $sy[$#sy] & 1; - $sy[$#sy] &= ~1; - push @toret, "s.y & ~1", &format_unsigned_hex_integer(@sy); - } elsif ($keytype =~ m!^ecdsa-sha2-nistp(256|384|521)$!) { - my ($sigtype, $subblob) = &parse("ss", $data); - push @toret, "sig type", $sigtype; - my @sbdata = unpack "C*", $subblob; - my ($r, $s) = &parse("mm", \@sbdata); - push @toret, "r", $r, "s", $s; - } else { - push @toret, "undecoded data", unpack "H*", pack "C*", @$data; - } - return @toret; -}; - -sub stringescape { - my ($str) = @_; - $str =~ s!\\!\\\\!g; - $str =~ s![^ -~]!sprintf "\\x%02X", ord $&!eg; - return $str; -} diff --git a/contrib/logrewrap.pl b/contrib/logrewrap.pl deleted file mode 100644 index 3a50b0b3c..000000000 --- a/contrib/logrewrap.pl +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/perl - -# Process a PuTTY SSH packet log that has gone through inappropriate -# line wrapping, and try to make it legible again. -# -# Motivation: people often include PuTTY packet logs in email -# messages, and if they're not careful, the sending MUA 'helpfully' -# wraps the lines at 72 characters, corrupting all the hex dumps into -# total unreadability. -# -# But as long as it's only the ASCII part of the dump at the end of -# the line that gets wrapped, and the hex part is untouched, this is a -# mechanically recoverable kind of corruption, because the ASCII is -# redundant and can be reconstructed from the hex. Better still, you -# can spot lines in which this has happened (because the ASCII at the -# end of the line is a truncated version of what we think it should -# say), and use that as a cue to remove the following line. - -use strict; -use warnings; - -while (<<>>) { - if (/^ ([0-9a-f]{8}) ((?:[0-9a-f]{2} ){0,15}(?:[0-9a-f]{2}))/) { - my $addr = $1; - my $hex = $2; - my $ascii = ""; - for (my $i = 0; $i < length($2); $i += 3) { - my $byte = hex(substr($hex, $i, 2)); - my $char = ($byte >= 32 && $byte < 127 ? chr($byte) : "."); - $ascii .= $char; - } - $hex = substr($hex . (" " x 48), 0, 47); - my $old_line = $_; - chomp($old_line); - my $new_line = " $addr $hex $ascii"; - if ($old_line ne $new_line and - $old_line eq substr($new_line, 0, length($old_line))) { - print "$new_line\n"; - <<>>; # eat the subsequent wrapped line - next; - } - } - print $_; -} diff --git a/contrib/make1305.py b/contrib/make1305.py deleted file mode 100644 index 66b7881f3..000000000 --- a/contrib/make1305.py +++ /dev/null @@ -1,374 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import string -from collections import namedtuple - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -class Multiprecision(object): - def __init__(self, target, minval, maxval, words): - self.target = target - self.minval = minval - self.maxval = maxval - self.words = words - assert 0 <= self.minval - assert self.minval <= self.maxval - assert self.target.nwords(self.maxval) == len(words) - - def getword(self, n): - return self.words[n] if n < len(self.words) else "0" - - def __add__(self, rhs): - newmin = self.minval + rhs.minval - newmax = self.maxval + rhs.maxval - nwords = self.target.nwords(newmax) - words = [] - - addfn = self.target.add - for i in range(nwords): - words.append(addfn(self.getword(i), rhs.getword(i))) - addfn = self.target.adc - - return Multiprecision(self.target, newmin, newmax, words) - - def __mul__(self, rhs): - newmin = self.minval * rhs.minval - newmax = self.maxval * rhs.maxval - nwords = self.target.nwords(newmax) - words = [] - - # There are basically two strategies we could take for - # multiplying two multiprecision integers. One is to enumerate - # the space of pairs of word indices in lexicographic order, - # essentially computing a*b[i] for each i and adding them - # together; the other is to enumerate in diagonal order, - # computing everything together that belongs at a particular - # output word index. - # - # For the moment, I've gone for the former. - - sprev = [] - for i, sword in enumerate(self.words): - rprev = None - sthis = sprev[:i] - for j, rword in enumerate(rhs.words): - prevwords = [] - if i+j < len(sprev): - prevwords.append(sprev[i+j]) - if rprev is not None: - prevwords.append(rprev) - vhi, vlo = self.target.muladd(sword, rword, *prevwords) - sthis.append(vlo) - rprev = vhi - sthis.append(rprev) - sprev = sthis - - # Remove unneeded words from the top of the output, if we can - # prove by range analysis that they'll always be zero. - sprev = sprev[:self.target.nwords(newmax)] - - return Multiprecision(self.target, newmin, newmax, sprev) - - def extract_bits(self, start, bits=None): - if bits is None: - bits = (self.maxval >> start).bit_length() - - # Overly thorough range analysis: if min and max have the same - # *quotient* by 2^bits, then the result of reducing anything - # in the range [min,max] mod 2^bits has to fall within the - # obvious range. But if they have different quotients, then - # you can wrap round the modulus and so any value mod 2^bits - # is possible. - newmin = self.minval >> start - newmax = self.maxval >> start - if (newmin >> bits) != (newmax >> bits): - newmin = 0 - newmax = (1 << bits) - 1 - - nwords = self.target.nwords(newmax) - words = [] - for i in range(nwords): - srcpos = i * self.target.bits + start - maxbits = min(self.target.bits, start + bits - srcpos) - wordindex = srcpos // self.target.bits - if srcpos % self.target.bits == 0: - word = self.getword(srcpos // self.target.bits) - elif (wordindex+1 >= len(self.words) or - srcpos % self.target.bits + maxbits < self.target.bits): - word = self.target.new_value( - "(%%s) >> %d" % (srcpos % self.target.bits), - self.getword(srcpos // self.target.bits)) - else: - word = self.target.new_value( - "((%%s) >> %d) | ((%%s) << %d)" % ( - srcpos % self.target.bits, - self.target.bits - (srcpos % self.target.bits)), - self.getword(srcpos // self.target.bits), - self.getword(srcpos // self.target.bits + 1)) - if maxbits < self.target.bits and maxbits < bits: - word = self.target.new_value( - "(%%s) & ((((BignumInt)1) << %d)-1)" % maxbits, - word) - words.append(word) - - return Multiprecision(self.target, newmin, newmax, words) - -# Each Statement has a list of variables it reads, and a list of ones -# it writes. 'forms' is a list of multiple actual C statements it -# could be generated as, depending on which of its output variables is -# actually used (e.g. no point calling BignumADC if the generated -# carry in a particular case is unused, or BignumMUL if nobody needs -# the top half). It is indexed by a bitmap whose bits correspond to -# the entries in wvars, with wvars[0] the MSB and wvars[-1] the LSB. -Statement = namedtuple("Statement", "rvars wvars forms") - -class CodegenTarget(object): - def __init__(self, bits): - self.bits = bits - self.valindex = 0 - self.stmts = [] - self.generators = {} - self.bv_words = (130 + self.bits - 1) // self.bits - self.carry_index = 0 - - def nwords(self, maxval): - return (maxval.bit_length() + self.bits - 1) // self.bits - - def stmt(self, stmt, needed=False): - index = len(self.stmts) - self.stmts.append([needed, stmt]) - for val in stmt.wvars: - self.generators[val] = index - - def new_value(self, formatstr=None, *deps): - name = "v%d" % self.valindex - self.valindex += 1 - if formatstr is not None: - self.stmt(Statement( - rvars=deps, wvars=[name], - forms=[None, name + " = " + formatstr % deps])) - return name - - def bigval_input(self, name, bits): - words = (bits + self.bits - 1) // self.bits - # Expect not to require an entire extra word - assert words == self.bv_words - - return Multiprecision(self, 0, (1<w[%d]" % (name, i)) for i in range(words)]) - - def const(self, value): - # We only support constants small enough to both fit in a - # BignumInt (of any size supported) _and_ be expressible in C - # with no weird integer literal syntax like a trailing LL. - # - # Supporting larger constants would be possible - you could - # break 'value' up into word-sized pieces on the Python side, - # and generate a legal C expression for each piece by - # splitting it further into pieces within the - # standards-guaranteed 'unsigned long' limit of 32 bits and - # then casting those to BignumInt before combining them with - # shifts. But it would be a lot of effort, and since the - # application for this code doesn't even need it, there's no - # point in bothering. - assert value < 2**16 - return Multiprecision(self, value, value, ["%d" % value]) - - def current_carry(self): - return "carry%d" % self.carry_index - - def add(self, a1, a2): - ret = self.new_value() - adcform = "BignumADC(%s, carry, %s, %s, 0)" % (ret, a1, a2) - plainform = "%s = %s + %s" % (ret, a1, a2) - self.carry_index += 1 - carryout = self.current_carry() - self.stmt(Statement( - rvars=[a1,a2], wvars=[ret,carryout], - forms=[None, adcform, plainform, adcform])) - return ret - - def adc(self, a1, a2): - ret = self.new_value() - adcform = "BignumADC(%s, carry, %s, %s, carry)" % (ret, a1, a2) - plainform = "%s = %s + %s + carry" % (ret, a1, a2) - carryin = self.current_carry() - self.carry_index += 1 - carryout = self.current_carry() - self.stmt(Statement( - rvars=[a1,a2,carryin], wvars=[ret,carryout], - forms=[None, adcform, plainform, adcform])) - return ret - - def muladd(self, m1, m2, *addends): - rlo = self.new_value() - rhi = self.new_value() - wideform = "BignumMUL%s(%s)" % ( - { 0:"", 1:"ADD", 2:"ADD2" }[len(addends)], - ", ".join([rhi, rlo, m1, m2] + list(addends))) - narrowform = " + ".join(["%s = %s * %s" % (rlo, m1, m2)] + - list(addends)) - self.stmt(Statement( - rvars=[m1,m2]+list(addends), wvars=[rhi,rlo], - forms=[None, narrowform, wideform, wideform])) - return rhi, rlo - - def write_bigval(self, name, val): - for i in range(self.bv_words): - word = val.getword(i) - self.stmt(Statement( - rvars=[word], wvars=[], - forms=["%s->w[%d] = %s" % (name, i, word)]), - needed=True) - - def compute_needed(self): - used_vars = set() - - self.queue = [stmt for (needed,stmt) in self.stmts if needed] - while len(self.queue) > 0: - stmt = self.queue.pop(0) - deps = [] - for var in stmt.rvars: - if var[0] in string.digits: - continue # constant - deps.append(self.generators[var]) - used_vars.add(var) - for index in deps: - if not self.stmts[index][0]: - self.stmts[index][0] = True - self.queue.append(self.stmts[index][1]) - - forms = [] - for i, (needed, stmt) in enumerate(self.stmts): - if needed: - formindex = 0 - for (j, var) in enumerate(stmt.wvars): - formindex *= 2 - if var in used_vars: - formindex += 1 - forms.append(stmt.forms[formindex]) - - # Now we must check whether this form of the statement - # also writes some variables we _don't_ actually need - # (e.g. if you only wanted the top half from a mul, or - # only the carry from an adc, you'd be forced to - # generate the other output too). Easiest way to do - # this is to look for an identical statement form - # later in the array. - maxindex = max(i for i in range(len(stmt.forms)) - if stmt.forms[i] == stmt.forms[formindex]) - extra_vars = maxindex & ~formindex - bitpos = 0 - while extra_vars != 0: - if extra_vars & (1 << bitpos): - extra_vars &= ~(1 << bitpos) - var = stmt.wvars[-1-bitpos] - used_vars.add(var) - # Also, write out a cast-to-void for each - # subsequently unused value, to prevent gcc - # warnings when the output code is compiled. - forms.append("(void)" + var) - bitpos += 1 - - used_carry = any(v.startswith("carry") for v in used_vars) - used_vars = [v for v in used_vars if v.startswith("v")] - used_vars.sort(key=lambda v: int(v[1:])) - - return used_carry, used_vars, forms - - def text(self): - used_carry, values, forms = self.compute_needed() - - ret = "" - while len(values) > 0: - prefix, sep, suffix = " BignumInt ", ", ", ";" - currline = values.pop(0) - while (len(values) > 0 and - len(prefix+currline+sep+values[0]+suffix) < 79): - currline += sep + values.pop(0) - ret += prefix + currline + suffix + "\n" - if used_carry: - ret += " BignumCarry carry;\n" - if ret != "": - ret += "\n" - for stmtform in forms: - ret += " %s;\n" % stmtform - return ret - -def gen_add(target): - # This is an addition _without_ reduction mod p, so that it can be - # used both during accumulation of the polynomial and for adding - # on the encrypted nonce at the end (which is mod 2^128, not mod - # p). - # - # Because one of the inputs will have come from our - # not-completely-reducing multiplication function, we expect up to - # 3 extra bits of input. - - a = target.bigval_input("a", 133) - b = target.bigval_input("b", 133) - ret = a + b - target.write_bigval("r", ret) - return """\ -static void bigval_add(bigval *r, const bigval *a, const bigval *b) -{ -%s} -\n""" % target.text() - -def gen_mul(target): - # The inputs are not 100% reduced mod p. Specifically, we can get - # a full 130-bit number from the pow5==0 pass, and then a 130-bit - # number times 5 from the pow5==1 pass, plus a possible carry. The - # total of that can be easily bounded above by 2^130 * 8, so we - # need to assume we're multiplying two 133-bit numbers. - - a = target.bigval_input("a", 133) - b = target.bigval_input("b", 133) - ab = a * b - ab0 = ab.extract_bits(0, 130) - ab1 = ab.extract_bits(130, 130) - ab2 = ab.extract_bits(260) - ab1_5 = target.const(5) * ab1 - ab2_25 = target.const(25) * ab2 - ret = ab0 + ab1_5 + ab2_25 - target.write_bigval("r", ret) - return """\ -static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b) -{ -%s} -\n""" % target.text() - -def gen_final_reduce(target): - # Given our input number n, n >> 130 is usually precisely the - # multiple of p that needs to be subtracted from n to reduce it to - # strictly less than p, but it might be too low by 1 (but not more - # than 1, given the range of our input is nowhere near the square - # of the modulus). So we add another 5, which will push a carry - # into the 130th bit if and only if that has happened, and then - # use that to decide whether to subtract one more copy of p. - - a = target.bigval_input("n", 133) - q = a.extract_bits(130) - adjusted = a.extract_bits(0, 130) + target.const(5) * q - final_subtract = (adjusted + target.const(5)).extract_bits(130) - adjusted2 = adjusted + target.const(5) * final_subtract - ret = adjusted2.extract_bits(0, 130) - target.write_bigval("n", ret) - return """\ -static void bigval_final_reduce(bigval *n) -{ -%s} -\n""" % target.text() - -pp_keyword = "#if" -for bits in [16, 32, 64]: - sys.stdout.write("%s BIGNUM_INT_BITS == %d\n\n" % (pp_keyword, bits)) - pp_keyword = "#elif" - sys.stdout.write(gen_add(CodegenTarget(bits))) - sys.stdout.write(gen_mul(CodegenTarget(bits))) - sys.stdout.write(gen_final_reduce(CodegenTarget(bits))) -sys.stdout.write("""#else -#error Add another bit count to contrib/make1305.py and rerun it -#endif -""") diff --git a/contrib/nice-ibeam.cur b/contrib/nice-ibeam.cur deleted file mode 100644 index af185328407071f52d22c001e6197e621e3ab84c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 766 zcmd^-yAgme3_~9|`czO;G6tLB(XtB-B^v~pPZMaM<4_`hq+AN0AyCnEP}496Xn|go z#2{93947A`CR2{to)lGzMoLLW-Yz+NCF;23p+L;B5SQY`zx(Tait&7{9km1pCoRNV Jo_~0Lf)$mepQ-== diff --git a/contrib/plinkfs b/contrib/plinkfs deleted file mode 100644 index e4b458007..000000000 --- a/contrib/plinkfs +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 - -# Wrapper around the FUSE 'sshfs' client, which arranges to use Plink -# as the SSH transport subcommand. -# -# This is not totally trivial because sshfs assumes slightly more of -# OpenSSH's command-line syntax than Plink supports. So we actually -# give sshfs a subcommand which is this script itself, re-invoked with -# the --helper option. - -import sys -import os -import shlex - -if sys.argv[1:2] == ["--helper"]: - # Helper mode. Strip OpenSSH-specific '-o' options from the - # command line, and invoke Plink. - plink_command = ["plink"] - - it = iter(sys.argv) - next(it) # discard command name - next(it) # discard --helper - - for arg in it: - if arg == "-o": - next(it) # discard -o option - elif arg.startswith("-o"): - pass - else: - plink_command.append(arg) - - os.execvp(plink_command[0], plink_command) - -else: - # Normal mode, invoked by the user. - sshfs_command = [ - "sshfs", "-o", "ssh_command={} --helper".format( - os.path.realpath(__file__)) - ] + sys.argv[1:] - - os.execvp(sshfs_command[0], sshfs_command) diff --git a/contrib/proveprime.py b/contrib/proveprime.py deleted file mode 100644 index 655e68eaa..000000000 --- a/contrib/proveprime.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import functools -import math -import os -import re -import subprocess -import sys -import itertools - -def gen_names(): - for i in itertools.count(): - name = "p{:d}".format(i) - if name not in nameset: - yield name -nameset=set() -names = gen_names() - -class YafuError(Exception): - pass - -verbose = False -def diag(*args): - if verbose: - print(*args, file=sys.stderr) - -factorcache = set() -factorcachefile = None -def cache_factor(f): - if f not in factorcache: - factorcache.add(f) - if factorcachefile is not None: - factorcachefile.write("{:d}\n".format(f)) - factorcachefile.flush() - -yafu = None -yafu_pattern = re.compile(rb"^P\d+ = (\d+)$") -def call_yafu(n): - n_orig = n - diag("starting yafu", n_orig) - p = subprocess.Popen([yafu, "-v", "-v"], stdin=subprocess.PIPE, - stdout=subprocess.PIPE) - p.stdin.write("{:d}\n".format(n).encode("ASCII")) - p.stdin.close() - factors = [] - for line in iter(p.stdout.readline, b''): - line = line.rstrip(b"\r\n") - diag("yafu output:", line.decode()) - m = yafu_pattern.match(line) - if m is not None: - f = int(m.group(1)) - if n % f != 0: - raise YafuError("bad yafu factor {:d}".format(f)) - factors.append(f) - if f >> 64: - cache_factor(f) - n //= f - p.wait() - diag("done yafu", n_orig) - return factors, n - -def factorise(n): - allfactors = [] - for f in factorcache: - if n % f == 0: - n //= f - allfactors.append(f) - while n > 1: - factors, n = call_yafu(n) - allfactors.extend(factors) - return sorted(allfactors) - -def product(ns): - return functools.reduce(lambda a,b: a*b, ns, 1) - -smallprimes = set() -commands = {} - -def proveprime(p, name=None): - if p >> 32 == 0: - smallprimes.add(p) - return "{:d}".format(p) - - if name is None: - name = next(names) - print("{} = {:d}".format(name, p)) - - fs = factorise(p-1) - fs.reverse() - prod = product(fs) - qs = [] - for q in fs: - newprod = prod // q - if newprod * newprod * newprod > p: - prod = newprod - else: - qs.append(q) - assert prod == product(qs) - assert prod * prod * prod > p - qset = set(qs) - qnamedict = {q: proveprime(q) for q in qset} - qnames = [qnamedict[q] for q in qs] - for w in itertools.count(2): - assert pow(w, p-1, p) == 1, "{}={:d} is not prime!".format(name, p) - diag("trying witness", w, "for", p) - for q in qset: - wpower = pow(w, (p-1) // q, p) - 1 - if math.gcd(wpower, p) != 1: - break - else: - diag("found witness", w, "for", p) - break - commands[p]= (name, w, qnames) - return name - -def main(): - parser = argparse.ArgumentParser(description='') - parser.add_argument("prime", nargs="+", - help="Number to prove prime. Can be prefixed by a " - "variable name and '=', e.g. 'x=9999999967'.") - parser.add_argument("--cryptsuite", action="store_true", - help="Generate abbreviated Pockle calls suitable " - "for the tests in cryptsuite.py.") - parser.add_argument("--yafu", default="yafu", - help="yafu binary to help with factoring.") - parser.add_argument("-v", "--verbose", action="store_true", - help="Write diagnostics to standard error.") - parser.add_argument("--cache", help="Cache of useful factors of things.") - args = parser.parse_args() - - global verbose, yafu - verbose = args.verbose - yafu = args.yafu - - if args.cache is not None: - with open(args.cache, "r") as fh: - for line in iter(fh.readline, ""): - factorcache.add(int(line.rstrip("\r\n"))) - global factorcachefile - factorcachefile = open(args.cache, "a") - - for ps in args.prime: - name, value = (ps.split("=", 1) if "=" in ps - else (None, ps)) - proveprime(int(value, 0), name) - - print("po = pockle_new()") - if len(smallprimes) > 0: - if args.cryptsuite: - print("add_small(po, {})".format( - ", ".join("{:d}".format(q) for q in sorted(smallprimes)))) - else: - for q in sorted(smallprimes): - print("pockle_add_small_prime(po, {:d})".format(q)) - for p, (name, w, qnames) in sorted(commands.items()): - print("{cmd}(po, {name}, [{qs}], {w:d})".format( - cmd = "add" if args.cryptsuite else "pockle_add_prime", - name=name, w=w, qs=", ".join(qnames))) - -if __name__ == '__main__': - main() diff --git a/contrib/samplekex.py b/contrib/samplekex.py deleted file mode 100644 index 16842893d..000000000 --- a/contrib/samplekex.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 - -# Example Python script to synthesise the server end of an SSH key exchange. - -# This script expects to be run with its standard input and output -# channels both connected to PuTTY. Run it by means of a command such -# as -# -# rm -f test.log && ./plink -sshrawlog test.log -v -proxycmd './contrib/samplekex.py' dummy -# -# It will conduct the whole of an SSH connection setup, up to the -# point where it ought to present a valid host key signature and -# switch over to the encrypted protocol; but because this is a simple -# script (and also because at that point PuTTY would annoyingly give a -# host key prompt), it doesn't actually bother to do either, and will -# instead present a nonsense signature and terminate. The above sample -# command will log the whole of the exchange from PuTTY's point of -# view in 'test.log'. -# -# The intention is that this forms example code that can be easily -# adapted to demonstrate bugs in our SSH connection setup. With more -# effort it could be expanded into some kind of a regression-testing -# suite, although in order to reliably test particular corner cases -# that would probably also need PuTTY-side modifications to make the -# random numbers deterministic. - -import sys, random -from encodelib import * - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -# A random Diffie-Hellman group, taken from an SSH server I made a -# test connection to. -groupgen = 5 -group = 0xf5d3849d2092fd427b4ebd838ea4830397a55f80b644626320dbbe51e8f63ed88148d787c94e7e67e4f393f26c565e1992b0cff8a47a953439462a4d0ffa5763ef60ff908f8ee6c4f6ef9f32b9ba50f01ad56fe7ebe90876a5cf61813a4ad4ba7ec0704303c9bf887d36abbd6c2aa9545fc2263232927e731060f5c701c96dc34016636df438ce30973715f121d767cfb98b5d09ae7b86fa36a051ad3c2941a295a68e2f583a56bc69913ec9d25abef4fdf1e31ede827a02620db058b9f041da051c8c0f13b132c17ceb893fa7c4cd8d8feebd82c5f9120cb221b8e88c5fe4dc17ca020a535484c92c7d4bee69c7703e1fa9a652d444c80065342c6ec0fac23c24de246e3dee72ca8bc8beccdade2b36771efcc350558268f5352ae53f2f71db62249ad9ac4fabdd6dfb099c6cff8c05bdea894390f9860f011cca046dfeb2f6ef81094e7980be526742706d1f3db920db107409291bb4c11f9a7dcbfaf26d808e6f9fe636b26b939de419129e86b1e632c60ec23b65c815723c5d861af068fd0ac8b37f4c06ecbd5cb2ef069ca8daac5cbd67c6182a65fed656d0dfbbb8a430b1dbac7bd6303bec8de078fe69f443a7bc8131a284d25dc2844f096240bfc61b62e91a87802987659b884c094c68741d29aa5ca19b9457e1f9df61c7dbbb13a61a79e4670b086027f20da2af4f5b020725f8828726379f429178926a1f0ea03f - -# An RSA key, generated specially for this script. -rsaexp = 0x10001 -rsamod = 0xB98FE0C0BEE1E05B35FDDF5517B3E29D8A9A6A7834378B6783A19536968968F755E341B5822CAE15B465DECB80EE4116CF8D22DB5A6C85444A68D0D45D9D42008329619BE3CAC3B192EF83DD2B75C4BB6B567E11B841073BACE92108DA7E97E543ED7F032F454F7AC3C6D3F27DB34BC9974A85C7963C546662AE300A61CBABEE274481FD041C41D0145704F5FA9C77A5A442CD7A64827BB0F21FB56FDE388B596A20D7A7D1C5F22DA96C6C2171D90A673DABC66596CD99499D75AD82FEFDBE04DEC2CC7E1414A95388F668591B3F4D58249F80489646ED2C318E77D4F4E37EE8A588E79F2960620E6D28BF53653F1C974C91845F0BABFE5D167E1CA7044EE20D - -# 16 bytes of random data for the start of KEXINIT. -cookie = bytes(random.randint(0,255) for i in range(16)) - -def expect(var, expr): - expected_val = eval(expr) - if var != expected_val: - sys.stderr.write("Expected %s (%s), got %s\n" % ( - expr, repr(expected_val), repr(var))) - sys.exit(1) - -sys.stdout.buffer.write(greeting("SSH-2.0-Example KEX synthesis")) - -greeting = sys.stdin.buffer.readline() -expect(greeting[:8].decode("ASCII"), '"SSH-2.0-"') - -sys.stdout.buffer.write( - clearpkt(SSH2_MSG_KEXINIT, - cookie, - name_list(("diffie-hellman-group-exchange-sha256",)), # kex - name_list(("ssh-rsa",)), # host keys - name_list(("aes128-ctr",)), # client->server ciphers - name_list(("aes128-ctr",)), # server->client ciphers - name_list(("hmac-sha2-256",)), # client->server MACs - name_list(("hmac-sha2-256",)), # server->client MACs - name_list(("none",)), # client->server compression - name_list(("none",)), # server->client compression - name_list(()), # client->server languages - name_list(()), # server->client languages - boolean(False), # first kex packet does not follow - uint32(0))) -sys.stdout.buffer.flush() - -intype, inpkt = read_clearpkt(sys.stdin.buffer) -expect(intype, "SSH2_MSG_KEXINIT") - -intype, inpkt = read_clearpkt(sys.stdin.buffer) -expect(intype, "SSH2_MSG_KEX_DH_GEX_REQUEST") -expect(inpkt, "uint32(0x400) + uint32(0x400) + uint32(0x2000)") - -sys.stdout.buffer.write( - clearpkt(SSH2_MSG_KEX_DH_GEX_GROUP, - mpint(group), - mpint(groupgen))) -sys.stdout.buffer.flush() - -intype, inpkt = read_clearpkt(sys.stdin.buffer) -expect(intype, "SSH2_MSG_KEX_DH_GEX_INIT") - -sys.stdout.buffer.write( - clearpkt(SSH2_MSG_KEX_DH_GEX_REPLY, - ssh_rsa_key_blob(rsaexp, rsamod), - mpint(random.randint(2, group-2)), - ssh_rsa_signature_blob(random.randint(2, rsamod-2)))) -sys.stdout.buffer.flush() diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt deleted file mode 100644 index 4b0aa9078..000000000 --- a/crypto/CMakeLists.txt +++ /dev/null @@ -1,242 +0,0 @@ -add_sources_from_current_dir(crypto - aes-common.c - aes-select.c - aes-sw.c - aesgcm-common.c - aesgcm-select.c - aesgcm-sw.c - aesgcm-ref-poly.c - arcfour.c - argon2.c - bcrypt.c - blake2.c - blowfish.c - chacha20-poly1305.c - crc32.c - des.c - diffie-hellman.c - dsa.c - ecc-arithmetic.c - ecc-ssh.c - hash_simple.c - hmac.c - mac.c - mac_simple.c - md5.c - mpint.c - ntru.c - openssh-certs.c - prng.c - pubkey-pem.c - pubkey-ppk.c - pubkey-ssh1.c - rsa.c - sha256-common.c - sha256-select.c - sha256-sw.c - sha512-common.c - sha512-select.c - sha512-sw.c - sha3.c - sha1-common.c - sha1-select.c - sha1-sw.c - xdmauth.c) - -include(CheckCSourceCompiles) - -function(test_compile_with_flags outvar) - cmake_parse_arguments(OPT "" "" - "GNU_FLAGS;MSVC_FLAGS;ADD_SOURCES_IF_SUCCESSFUL;TEST_SOURCE" "${ARGN}") - - # Figure out what flags are applicable to this compiler. - set(flags) - if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR - CMAKE_C_COMPILER_ID MATCHES "Clang") - set(flags ${OPT_GNU_FLAGS}) - endif() - if(CMAKE_C_COMPILER_ID MATCHES "MSVC") - set(flags ${OPT_MSVC_FLAGS}) - endif() - - # See if we can compile the provided test program. - foreach(i ${flags}) - set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${i}") - endforeach() - check_c_source_compiles("${OPT_TEST_SOURCE}" "${outvar}") - - if(${outvar} AND OPT_ADD_SOURCES_IF_SUCCESSFUL) - # Make an object library that compiles the implementation with the - # necessary flags, and add the resulting objects to the crypto - # library. - set(libname object_lib_${outvar}) - add_library(${libname} OBJECT ${OPT_ADD_SOURCES_IF_SUCCESSFUL}) - target_compile_options(${libname} PRIVATE ${flags}) - target_sources(crypto PRIVATE $) - endif() - - # Export the output to the caller's scope, so that further tests can - # be based on it. - set(${outvar} ${${outvar}} PARENT_SCOPE) -endfunction() - -# ---------------------------------------------------------------------- -# Try to enable x86 intrinsics-based crypto implementations. - -test_compile_with_flags(HAVE_WMMINTRIN_H - GNU_FLAGS -msse4.1 - TEST_SOURCE " - #include - #include - volatile __m128i r, a, b; - int main(void) { r = _mm_xor_si128(a, b); }") -if(HAVE_WMMINTRIN_H) - test_compile_with_flags(HAVE_AES_NI - GNU_FLAGS -msse4.1 -maes - TEST_SOURCE " - #include - #include - volatile __m128i r, a, b; - int main(void) { r = _mm_aesenc_si128(a, b); }" - ADD_SOURCES_IF_SUCCESSFUL aes-ni aes-ni.c) - - # shaintrin.h doesn't exist on all compilers; sometimes it's folded - # into the other headers - test_compile_with_flags(HAVE_SHAINTRIN_H - GNU_FLAGS -msse4.1 -msha - TEST_SOURCE " - #include - #include - #include - #include - volatile __m128i r, a, b; - int main(void) { r = _mm_xor_si128(a, b); }") - if(HAVE_SHAINTRIN_H) - set(include_shaintrin "#include ") - else() - set(include_shaintrin "") - endif() - - test_compile_with_flags(HAVE_SHA_NI - GNU_FLAGS -msse4.1 -msha - TEST_SOURCE " - #include - #include - #include - ${include_shaintrin} - volatile __m128i r, a, b, c; - int main(void) { r = _mm_sha256rnds2_epu32(a, b, c); }" - ADD_SOURCES_IF_SUCCESSFUL sha256-ni.c sha1-ni.c) - - test_compile_with_flags(HAVE_CLMUL - GNU_FLAGS -msse4.1 -mpclmul - TEST_SOURCE " - #include - #include - volatile __m128i r, a, b; - int main(void) { r = _mm_clmulepi64_si128(a, b, 5); - r = _mm_shuffle_epi8(r, a); }" - ADD_SOURCES_IF_SUCCESSFUL aesgcm-clmul.c) -endif() - -# ---------------------------------------------------------------------- -# Try to enable Arm Neon intrinsics-based crypto implementations. - -# Start by checking which header file we need. ACLE specifies that it -# ought to be , on both 32- and 64-bit Arm, but Visual -# Studio for some reason renamed the header to in -# 64-bit, and gives an error if you use the standard name. (However, -# clang-cl does let you use the standard name.) -test_compile_with_flags(HAVE_ARM_NEON_H - MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS - TEST_SOURCE " - #include - volatile uint8x16_t r, a, b; - int main(void) { r = veorq_u8(a, b); }") -if(HAVE_ARM_NEON_H) - set(neon ON) - set(neon_header "arm_neon.h") -else() - test_compile_with_flags(HAVE_ARM64_NEON_H TEST_SOURCE " - #include - volatile uint8x16_t r, a, b; - int main(void) { r = veorq_u8(a, b); }") - if(HAVE_ARM64_NEON_H) - set(neon ON) - set(neon_header "arm64_neon.h") - set(USE_ARM64_NEON_H ON) - endif() -endif() - -if(neon) - # If we have _some_ NEON header, look for the individual things we - # can enable with it. - - # The 'crypto' architecture extension includes support for AES, - # SHA-1, and SHA-256. - test_compile_with_flags(HAVE_NEON_CRYPTO - GNU_FLAGS -march=armv8-a+crypto - MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS - TEST_SOURCE " - #include <${neon_header}> - volatile uint8x16_t r, a, b; - volatile uint32x4_t s, x, y, z; - int main(void) { r = vaeseq_u8(a, b); s = vsha256hq_u32(x, y, z); }" - ADD_SOURCES_IF_SUCCESSFUL aes-neon.c sha256-neon.c sha1-neon.c) - - test_compile_with_flags(HAVE_NEON_PMULL - GNU_FLAGS -march=armv8-a+crypto - MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS - TEST_SOURCE " - #include <${neon_header}> - volatile poly128_t r; - volatile poly64_t a, b; - volatile poly64x2_t u, v; - int main(void) { r = vmull_p64(a, b); r = vmull_high_p64(u, v); }" - ADD_SOURCES_IF_SUCCESSFUL aesgcm-neon.c) - - test_compile_with_flags(HAVE_NEON_VADDQ_P128 - GNU_FLAGS -march=armv8-a+crypto - MSVC_FLAGS -D_ARM_USE_NEW_NEON_INTRINSICS - TEST_SOURCE " - #include <${neon_header}> - volatile poly128_t r; - int main(void) { r = vaddq_p128(r, r); }") - - # The 'sha3' architecture extension, despite the name, includes - # support for SHA-512 (from the SHA-2 standard) as well as SHA-3 - # proper. - # - # Versions of clang up to and including clang 12 support this - # extension in assembly language, but not the ACLE intrinsics for - # it. So we check both. - test_compile_with_flags(HAVE_NEON_SHA512_INTRINSICS - GNU_FLAGS -march=armv8.2-a+crypto+sha3 - TEST_SOURCE " - #include <${neon_header}> - volatile uint64x2_t r, a, b; - int main(void) { r = vsha512su0q_u64(a, b); }" - ADD_SOURCES_IF_SUCCESSFUL sha512-neon.c) - if(HAVE_NEON_SHA512_INTRINSICS) - set(HAVE_NEON_SHA512 ON) - else() - test_compile_with_flags(HAVE_NEON_SHA512_ASM - GNU_FLAGS -march=armv8.2-a+crypto+sha3 - TEST_SOURCE " - #include <${neon_header}> - volatile uint64x2_t r, a; - int main(void) { __asm__(\"sha512su0 %0.2D,%1.2D\" : \"+w\" (r) : \"w\" (a)); }" - ADD_SOURCES_IF_SUCCESSFUL sha512-neon.c) - if(HAVE_NEON_SHA512_ASM) - set(HAVE_NEON_SHA512 ON) - endif() - endif() -endif() - -set(HAVE_AES_NI ${HAVE_AES_NI} PARENT_SCOPE) -set(HAVE_SHA_NI ${HAVE_SHA_NI} PARENT_SCOPE) -set(HAVE_SHAINTRIN_H ${HAVE_SHAINTRIN_H} PARENT_SCOPE) -set(HAVE_NEON_CRYPTO ${HAVE_NEON_CRYPTO} PARENT_SCOPE) -set(HAVE_NEON_SHA512 ${HAVE_NEON_SHA512} PARENT_SCOPE) -set(HAVE_NEON_SHA512_INTRINSICS ${HAVE_NEON_SHA512_INTRINSICS} PARENT_SCOPE) -set(USE_ARM64_NEON_H ${USE_ARM64_NEON_H} PARENT_SCOPE) diff --git a/crypto/aes-common.c b/crypto/aes-common.c deleted file mode 100644 index 3bed2af15..000000000 --- a/crypto/aes-common.c +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Common variable definitions across all the AES implementations. - */ - -#include "ssh.h" -#include "aes.h" - -const uint8_t aes_key_setup_round_constants[10] = { - /* The first few powers of X in GF(2^8), used during key setup. - * This can safely be a lookup table without side channel risks, - * because key setup iterates through it once in a standard way - * regardless of the key. */ - 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, -}; - -void aesgcm_cipher_crypt_length( - ssh_cipher *cipher, void *blk, int len, unsigned long seq) -{ - /* Do nothing: lengths are sent in clear for this cipher. */ -} diff --git a/crypto/aes-neon.c b/crypto/aes-neon.c deleted file mode 100644 index 5cd9f2d1a..000000000 --- a/crypto/aes-neon.c +++ /dev/null @@ -1,359 +0,0 @@ -/* ---------------------------------------------------------------------- - * Hardware-accelerated implementation of AES using Arm NEON. - */ - -#include "ssh.h" -#include "aes.h" - -#if USE_ARM64_NEON_H -#include -#else -#include -#endif - -static bool aes_neon_available(void) -{ - /* - * For Arm, we delegate to a per-platform AES detection function, - * because it has to be implemented by asking the operating system - * rather than directly querying the CPU. - * - * That's because Arm systems commonly have multiple cores that - * are not all alike, so any method of querying whether NEON - * crypto instructions work on the _current_ CPU - even one as - * crude as just trying one and catching the SIGILL - wouldn't - * give an answer that you could still rely on the first time the - * OS migrated your process to another CPU. - */ - return platform_aes_neon_available(); -} - -/* - * Core NEON encrypt/decrypt functions, one per length and direction. - */ - -#define NEON_CIPHER(len, repmacro) \ - static inline uint8x16_t aes_neon_##len##_e( \ - uint8x16_t v, const uint8x16_t *keysched) \ - { \ - repmacro(v = vaesmcq_u8(vaeseq_u8(v, *keysched++));); \ - v = vaeseq_u8(v, *keysched++); \ - return veorq_u8(v, *keysched); \ - } \ - static inline uint8x16_t aes_neon_##len##_d( \ - uint8x16_t v, const uint8x16_t *keysched) \ - { \ - repmacro(v = vaesimcq_u8(vaesdq_u8(v, *keysched++));); \ - v = vaesdq_u8(v, *keysched++); \ - return veorq_u8(v, *keysched); \ - } - -NEON_CIPHER(128, REP9) -NEON_CIPHER(192, REP11) -NEON_CIPHER(256, REP13) - -/* - * The main key expansion. - */ -static void aes_neon_key_expand( - const unsigned char *key, size_t key_words, - uint8x16_t *keysched_e, uint8x16_t *keysched_d) -{ - size_t rounds = key_words + 6; - size_t sched_words = (rounds + 1) * 4; - - /* - * Store the key schedule as 32-bit integers during expansion, so - * that it's easy to refer back to individual previous words. We - * collect them into the final uint8x16_t form at the end. - */ - uint32_t sched[MAXROUNDKEYS * 4]; - - unsigned rconpos = 0; - - for (size_t i = 0; i < sched_words; i++) { - if (i < key_words) { - sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i); - } else { - uint32_t temp = sched[i - 1]; - - bool rotate_and_round_constant = (i % key_words == 0); - bool sub = rotate_and_round_constant || - (key_words == 8 && i % 8 == 4); - - if (rotate_and_round_constant) - temp = (temp << 24) | (temp >> 8); - - if (sub) { - uint32x4_t v32 = vdupq_n_u32(temp); - uint8x16_t v8 = vreinterpretq_u8_u32(v32); - v8 = vaeseq_u8(v8, vdupq_n_u8(0)); - v32 = vreinterpretq_u32_u8(v8); - temp = vget_lane_u32(vget_low_u32(v32), 0); - } - - if (rotate_and_round_constant) { - assert(rconpos < lenof(aes_key_setup_round_constants)); - temp ^= aes_key_setup_round_constants[rconpos++]; - } - - sched[i] = sched[i - key_words] ^ temp; - } - } - - /* - * Combine the key schedule words into uint8x16_t vectors and - * store them in the output context. - */ - for (size_t round = 0; round <= rounds; round++) - keysched_e[round] = vreinterpretq_u8_u32(vld1q_u32(sched + 4*round)); - - smemclr(sched, sizeof(sched)); - - /* - * Now prepare the modified keys for the inverse cipher. - */ - for (size_t eround = 0; eround <= rounds; eround++) { - size_t dround = rounds - eround; - uint8x16_t rkey = keysched_e[eround]; - if (eround && dround) /* neither first nor last */ - rkey = vaesimcq_u8(rkey); - keysched_d[dround] = rkey; - } -} - -/* - * Auxiliary routine to reverse the byte order of a vector, so that - * the SDCTR IV can be made big-endian for feeding to the cipher. - * - * In fact we don't need to reverse the vector _all_ the way; we leave - * the two lanes in MSW,LSW order, because that makes no difference to - * the efficiency of the increment. That way we only have to reverse - * bytes within each lane in this function. - */ -static inline uint8x16_t aes_neon_sdctr_reverse(uint8x16_t v) -{ - return vrev64q_u8(v); -} - -/* - * Auxiliary routine to increment the 128-bit counter used in SDCTR - * mode. There's no instruction to treat a 128-bit vector as a single - * long integer, so instead we have to increment the bottom half - * unconditionally, and the top half if the bottom half started off as - * all 1s (in which case there was about to be a carry). - */ -static inline uint8x16_t aes_neon_sdctr_increment(uint8x16_t in) -{ -#ifdef __aarch64__ - /* There will be a carry if the low 64 bits are all 1s. */ - uint64x1_t all1 = vcreate_u64(0xFFFFFFFFFFFFFFFF); - uint64x1_t carry = vceq_u64(vget_high_u64(vreinterpretq_u64_u8(in)), all1); - - /* Make a word whose bottom half is unconditionally all 1s, and - * the top half is 'carry', i.e. all 0s most of the time but all - * 1s if we need to increment the top half. Then that word is what - * we need to _subtract_ from the input counter. */ - uint64x2_t subtrahend = vcombine_u64(carry, all1); -#else - /* AArch32 doesn't have comparisons that operate on a 64-bit lane, - * so we start by comparing each 32-bit half of the low 64 bits - * _separately_ to all-1s. */ - uint32x2_t all1 = vdup_n_u32(0xFFFFFFFF); - uint32x2_t carry = vceq_u32( - vget_high_u32(vreinterpretq_u32_u8(in)), all1); - - /* Swap the 32-bit words of the compare output, and AND with the - * unswapped version. Now carry is all 1s iff the bottom half of - * the input counter was all 1s, and all 0s otherwise. */ - carry = vand_u32(carry, vrev64_u32(carry)); - - /* Now make the vector to subtract in the same way as above. */ - uint64x2_t subtrahend = vreinterpretq_u64_u32(vcombine_u32(carry, all1)); -#endif - - return vreinterpretq_u8_u64( - vsubq_u64(vreinterpretq_u64_u8(in), subtrahend)); -} - -/* - * Much simpler auxiliary routine to increment the counter for GCM - * mode. This only has to increment the low word. - */ -static inline uint8x16_t aes_neon_gcm_increment(uint8x16_t in) -{ - uint32x4_t inw = vreinterpretq_u32_u8(in); - uint32x4_t ONE = vcombine_u32(vcreate_u32(0), vcreate_u32(1)); - inw = vaddq_u32(inw, ONE); - return vreinterpretq_u8_u32(inw); -} - -/* - * The SSH interface and the cipher modes. - */ - -typedef struct aes_neon_context aes_neon_context; -struct aes_neon_context { - uint8x16_t keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv; - - ssh_cipher ciph; -}; - -static ssh_cipher *aes_neon_new(const ssh_cipheralg *alg) -{ - const struct aes_extra *extra = (const struct aes_extra *)alg->extra; - if (!check_availability(extra)) - return NULL; - - aes_neon_context *ctx = snew(aes_neon_context); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void aes_neon_free(ssh_cipher *ciph) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void aes_neon_setkey(ssh_cipher *ciph, const void *vkey) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - const unsigned char *key = (const unsigned char *)vkey; - - aes_neon_key_expand(key, ctx->ciph.vt->real_keybits / 32, - ctx->keysched_e, ctx->keysched_d); -} - -static void aes_neon_setiv_cbc(ssh_cipher *ciph, const void *iv) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - ctx->iv = vld1q_u8(iv); -} - -static void aes_neon_setiv_sdctr(ssh_cipher *ciph, const void *iv) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - uint8x16_t counter = vld1q_u8(iv); - ctx->iv = aes_neon_sdctr_reverse(counter); -} - -static void aes_neon_setiv_gcm(ssh_cipher *ciph, const void *iv) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - uint8x16_t counter = vld1q_u8(iv); - ctx->iv = aes_neon_sdctr_reverse(counter); - ctx->iv = vreinterpretq_u8_u32(vsetq_lane_u32( - 1, vreinterpretq_u32_u8(ctx->iv), 2)); -} - -static void aes_neon_next_message_gcm(ssh_cipher *ciph) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - uint32x4_t iv = vreinterpretq_u32_u8(ctx->iv); - uint64_t msg_counter = vgetq_lane_u32(iv, 0); - msg_counter = (msg_counter << 32) | vgetq_lane_u32(iv, 3); - msg_counter++; - iv = vsetq_lane_u32(msg_counter >> 32, iv, 0); - iv = vsetq_lane_u32(msg_counter, iv, 3); - iv = vsetq_lane_u32(1, iv, 2); - ctx->iv = vreinterpretq_u8_u32(iv); -} - -typedef uint8x16_t (*aes_neon_fn)(uint8x16_t v, const uint8x16_t *keysched); - -static inline void aes_cbc_neon_encrypt( - ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - uint8x16_t plaintext = vld1q_u8(blk); - uint8x16_t cipher_input = veorq_u8(plaintext, ctx->iv); - uint8x16_t ciphertext = encrypt(cipher_input, ctx->keysched_e); - vst1q_u8(blk, ciphertext); - ctx->iv = ciphertext; - } -} - -static inline void aes_cbc_neon_decrypt( - ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn decrypt) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - uint8x16_t ciphertext = vld1q_u8(blk); - uint8x16_t decrypted = decrypt(ciphertext, ctx->keysched_d); - uint8x16_t plaintext = veorq_u8(decrypted, ctx->iv); - vst1q_u8(blk, plaintext); - ctx->iv = ciphertext; - } -} - -static inline void aes_sdctr_neon( - ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - uint8x16_t counter = aes_neon_sdctr_reverse(ctx->iv); - uint8x16_t keystream = encrypt(counter, ctx->keysched_e); - uint8x16_t input = vld1q_u8(blk); - uint8x16_t output = veorq_u8(input, keystream); - vst1q_u8(blk, output); - ctx->iv = aes_neon_sdctr_increment(ctx->iv); - } -} - -static inline void aes_encrypt_ecb_block_neon( - ssh_cipher *ciph, void *blk, aes_neon_fn encrypt) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - uint8x16_t plaintext = vld1q_u8(blk); - uint8x16_t ciphertext = encrypt(plaintext, ctx->keysched_e); - vst1q_u8(blk, ciphertext); -} - -static inline void aes_gcm_neon( - ssh_cipher *ciph, void *vblk, int blklen, aes_neon_fn encrypt) -{ - aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - uint8x16_t counter = aes_neon_sdctr_reverse(ctx->iv); - uint8x16_t keystream = encrypt(counter, ctx->keysched_e); - uint8x16_t input = vld1q_u8(blk); - uint8x16_t output = veorq_u8(input, keystream); - vst1q_u8(blk, output); - ctx->iv = aes_neon_gcm_increment(ctx->iv); - } -} - -#define NEON_ENC_DEC(len) \ - static void aes##len##_neon_cbc_encrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_neon_encrypt(ciph, vblk, blklen, aes_neon_##len##_e); } \ - static void aes##len##_neon_cbc_decrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_neon_decrypt(ciph, vblk, blklen, aes_neon_##len##_d); } \ - static void aes##len##_neon_sdctr( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_sdctr_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \ - static void aes##len##_neon_gcm( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_gcm_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \ - static void aes##len##_neon_encrypt_ecb_block( \ - ssh_cipher *ciph, void *vblk) \ - { aes_encrypt_ecb_block_neon(ciph, vblk, aes_neon_##len##_e); } - -NEON_ENC_DEC(128) -NEON_ENC_DEC(192) -NEON_ENC_DEC(256) - -AES_EXTRA(_neon); -AES_ALL_VTABLES(_neon, "NEON accelerated"); diff --git a/crypto/aes-ni.c b/crypto/aes-ni.c deleted file mode 100644 index 67d82b86a..000000000 --- a/crypto/aes-ni.c +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Hardware-accelerated implementation of AES using x86 AES-NI. - */ - -#include "ssh.h" -#include "aes.h" - -#include -#include - -#if defined(__clang__) || defined(__GNUC__) -#include -#define GET_CPU_ID(out) __cpuid(1, (out)[0], (out)[1], (out)[2], (out)[3]) -#else -#define GET_CPU_ID(out) __cpuid(out, 1) -#endif - -static bool aes_ni_available(void) -{ - /* - * Determine if AES is available on this CPU, by checking that - * both AES itself and SSE4.1 are supported. - */ - unsigned int CPUInfo[4]; - GET_CPU_ID(CPUInfo); - return (CPUInfo[2] & (1 << 25)) && (CPUInfo[2] & (1 << 19)); -} - -/* - * Core AES-NI encrypt/decrypt functions, one per length and direction. - */ - -#define NI_CIPHER(len, dir, dirlong, repmacro) \ - static inline __m128i aes_ni_##len##_##dir( \ - __m128i v, const __m128i *keysched) \ - { \ - v = _mm_xor_si128(v, *keysched++); \ - repmacro(v = _mm_aes##dirlong##_si128(v, *keysched++);); \ - return _mm_aes##dirlong##last_si128(v, *keysched); \ - } - -NI_CIPHER(128, e, enc, REP9) -NI_CIPHER(128, d, dec, REP9) -NI_CIPHER(192, e, enc, REP11) -NI_CIPHER(192, d, dec, REP11) -NI_CIPHER(256, e, enc, REP13) -NI_CIPHER(256, d, dec, REP13) - -/* - * The main key expansion. - */ -static void aes_ni_key_expand( - const unsigned char *key, size_t key_words, - __m128i *keysched_e, __m128i *keysched_d) -{ - size_t rounds = key_words + 6; - size_t sched_words = (rounds + 1) * 4; - - /* - * Store the key schedule as 32-bit integers during expansion, so - * that it's easy to refer back to individual previous words. We - * collect them into the final __m128i form at the end. - */ - uint32_t sched[MAXROUNDKEYS * 4]; - - unsigned rconpos = 0; - - for (size_t i = 0; i < sched_words; i++) { - if (i < key_words) { - sched[i] = GET_32BIT_LSB_FIRST(key + 4 * i); - } else { - uint32_t temp = sched[i - 1]; - - bool rotate_and_round_constant = (i % key_words == 0); - bool only_sub = (key_words == 8 && i % 8 == 4); - - if (rotate_and_round_constant) { - __m128i v = _mm_setr_epi32(0,temp,0,0); - v = _mm_aeskeygenassist_si128(v, 0); - temp = _mm_extract_epi32(v, 1); - - assert(rconpos < lenof(aes_key_setup_round_constants)); - temp ^= aes_key_setup_round_constants[rconpos++]; - } else if (only_sub) { - __m128i v = _mm_setr_epi32(0,temp,0,0); - v = _mm_aeskeygenassist_si128(v, 0); - temp = _mm_extract_epi32(v, 0); - } - - sched[i] = sched[i - key_words] ^ temp; - } - } - - /* - * Combine the key schedule words into __m128i vectors and store - * them in the output context. - */ - for (size_t round = 0; round <= rounds; round++) - keysched_e[round] = _mm_setr_epi32( - sched[4*round ], sched[4*round+1], - sched[4*round+2], sched[4*round+3]); - - smemclr(sched, sizeof(sched)); - - /* - * Now prepare the modified keys for the inverse cipher. - */ - for (size_t eround = 0; eround <= rounds; eround++) { - size_t dround = rounds - eround; - __m128i rkey = keysched_e[eround]; - if (eround && dround) /* neither first nor last */ - rkey = _mm_aesimc_si128(rkey); - keysched_d[dround] = rkey; - } -} - -/* - * Auxiliary routine to increment the 128-bit counter used in SDCTR - * mode. - */ -static inline __m128i aes_ni_sdctr_increment(__m128i v) -{ - const __m128i ONE = _mm_setr_epi32(1,0,0,0); - const __m128i ZERO = _mm_setzero_si128(); - - /* Increment the low-order 64 bits of v */ - v = _mm_add_epi64(v, ONE); - /* Check if they've become zero */ - __m128i cmp = _mm_cmpeq_epi64(v, ZERO); - /* If so, the low half of cmp is all 1s. Pack that into the high - * half of addend with zero in the low half. */ - __m128i addend = _mm_unpacklo_epi64(ZERO, cmp); - /* And subtract that from v, which increments the high 64 bits iff - * the low 64 wrapped round. */ - v = _mm_sub_epi64(v, addend); - - return v; -} - -/* - * Much simpler auxiliary routine to increment the counter for GCM - * mode. This only has to increment the low word. - */ -static inline __m128i aes_ni_gcm_increment(__m128i v) -{ - const __m128i ONE = _mm_setr_epi32(1,0,0,0); - return _mm_add_epi32(v, ONE); -} - -/* - * Auxiliary routine to reverse the byte order of a vector, so that - * the SDCTR IV can be made big-endian for feeding to the cipher. - */ -static inline __m128i aes_ni_sdctr_reverse(__m128i v) -{ - v = _mm_shuffle_epi8( - v, _mm_setr_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)); - return v; -} - -/* - * The SSH interface and the cipher modes. - */ - -typedef struct aes_ni_context aes_ni_context; -struct aes_ni_context { - __m128i keysched_e[MAXROUNDKEYS], keysched_d[MAXROUNDKEYS], iv; - - void *pointer_to_free; - ssh_cipher ciph; -}; - -static ssh_cipher *aes_ni_new(const ssh_cipheralg *alg) -{ - const struct aes_extra *extra = (const struct aes_extra *)alg->extra; - if (!check_availability(extra)) - return NULL; - - /* - * The __m128i variables in the context structure need to be - * 16-byte aligned, but not all malloc implementations that this - * code has to work with will guarantee to return a 16-byte - * aligned pointer. So we over-allocate, manually realign the - * pointer ourselves, and store the original one inside the - * context so we know how to free it later. - */ - void *allocation = smalloc(sizeof(aes_ni_context) + 15); - uintptr_t alloc_address = (uintptr_t)allocation; - uintptr_t aligned_address = (alloc_address + 15) & ~15; - aes_ni_context *ctx = (aes_ni_context *)aligned_address; - - ctx->ciph.vt = alg; - ctx->pointer_to_free = allocation; - return &ctx->ciph; -} - -static void aes_ni_free(ssh_cipher *ciph) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - void *allocation = ctx->pointer_to_free; - smemclr(ctx, sizeof(*ctx)); - sfree(allocation); -} - -static void aes_ni_setkey(ssh_cipher *ciph, const void *vkey) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - const unsigned char *key = (const unsigned char *)vkey; - - aes_ni_key_expand(key, ctx->ciph.vt->real_keybits / 32, - ctx->keysched_e, ctx->keysched_d); -} - -static void aes_ni_setiv_cbc(ssh_cipher *ciph, const void *iv) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - ctx->iv = _mm_loadu_si128(iv); -} - -static void aes_ni_setiv_sdctr(ssh_cipher *ciph, const void *iv) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - __m128i counter = _mm_loadu_si128(iv); - ctx->iv = aes_ni_sdctr_reverse(counter); -} - -static void aes_ni_setiv_gcm(ssh_cipher *ciph, const void *iv) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - __m128i counter = _mm_loadu_si128(iv); - ctx->iv = aes_ni_sdctr_reverse(counter); - ctx->iv = _mm_insert_epi32(ctx->iv, 1, 0); -} - -static void aes_ni_next_message_gcm(ssh_cipher *ciph) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - uint32_t fixed = _mm_extract_epi32(ctx->iv, 3); - uint64_t msg_counter = _mm_extract_epi32(ctx->iv, 2); - msg_counter <<= 32; - msg_counter |= (uint32_t)_mm_extract_epi32(ctx->iv, 1); - msg_counter++; - ctx->iv = _mm_set_epi32(fixed, msg_counter >> 32, msg_counter, 1); -} - -typedef __m128i (*aes_ni_fn)(__m128i v, const __m128i *keysched); - -static inline void aes_cbc_ni_encrypt( - ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - __m128i plaintext = _mm_loadu_si128((const __m128i *)blk); - __m128i cipher_input = _mm_xor_si128(plaintext, ctx->iv); - __m128i ciphertext = encrypt(cipher_input, ctx->keysched_e); - _mm_storeu_si128((__m128i *)blk, ciphertext); - ctx->iv = ciphertext; - } -} - -static inline void aes_cbc_ni_decrypt( - ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn decrypt) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - __m128i ciphertext = _mm_loadu_si128((const __m128i *)blk); - __m128i decrypted = decrypt(ciphertext, ctx->keysched_d); - __m128i plaintext = _mm_xor_si128(decrypted, ctx->iv); - _mm_storeu_si128((__m128i *)blk, plaintext); - ctx->iv = ciphertext; - } -} - -static inline void aes_sdctr_ni( - ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - __m128i counter = aes_ni_sdctr_reverse(ctx->iv); - __m128i keystream = encrypt(counter, ctx->keysched_e); - __m128i input = _mm_loadu_si128((const __m128i *)blk); - __m128i output = _mm_xor_si128(input, keystream); - _mm_storeu_si128((__m128i *)blk, output); - ctx->iv = aes_ni_sdctr_increment(ctx->iv); - } -} - -static inline void aes_encrypt_ecb_block_ni( - ssh_cipher *ciph, void *blk, aes_ni_fn encrypt) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - __m128i plaintext = _mm_loadu_si128(blk); - __m128i ciphertext = encrypt(plaintext, ctx->keysched_e); - _mm_storeu_si128(blk, ciphertext); -} - -static inline void aes_gcm_ni( - ssh_cipher *ciph, void *vblk, int blklen, aes_ni_fn encrypt) -{ - aes_ni_context *ctx = container_of(ciph, aes_ni_context, ciph); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - __m128i counter = aes_ni_sdctr_reverse(ctx->iv); - __m128i keystream = encrypt(counter, ctx->keysched_e); - __m128i input = _mm_loadu_si128((const __m128i *)blk); - __m128i output = _mm_xor_si128(input, keystream); - _mm_storeu_si128((__m128i *)blk, output); - ctx->iv = aes_ni_gcm_increment(ctx->iv); - } -} - -#define NI_ENC_DEC(len) \ - static void aes##len##_ni_cbc_encrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_ni_encrypt(ciph, vblk, blklen, aes_ni_##len##_e); } \ - static void aes##len##_ni_cbc_decrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_ni_decrypt(ciph, vblk, blklen, aes_ni_##len##_d); } \ - static void aes##len##_ni_sdctr( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_sdctr_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \ - static void aes##len##_ni_gcm( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_gcm_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \ - static void aes##len##_ni_encrypt_ecb_block( \ - ssh_cipher *ciph, void *vblk) \ - { aes_encrypt_ecb_block_ni(ciph, vblk, aes_ni_##len##_e); } - -NI_ENC_DEC(128) -NI_ENC_DEC(192) -NI_ENC_DEC(256) - -AES_EXTRA(_ni); -AES_ALL_VTABLES(_ni, "AES-NI accelerated"); diff --git a/crypto/aes-select.c b/crypto/aes-select.c deleted file mode 100644 index b4daeed1e..000000000 --- a/crypto/aes-select.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Top-level vtables to select an AES implementation. - */ - -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "aes.h" - -static ssh_cipher *aes_select(const ssh_cipheralg *alg) -{ - const ssh_cipheralg *const *real_algs = (const ssh_cipheralg **)alg->extra; - - for (size_t i = 0; real_algs[i]; i++) { - const ssh_cipheralg *alg = real_algs[i]; - const struct aes_extra *alg_extra = - (const struct aes_extra *)alg->extra; - if (check_availability(alg_extra)) - return ssh_cipher_new(alg); - } - - /* We should never reach the NULL at the end of the list, because - * the last non-NULL entry should be software-only AES, which is - * always available. */ - unreachable("aes_select ran off the end of its list"); -} - -#if HAVE_AES_NI -#define IF_NI(...) __VA_ARGS__ -#else -#define IF_NI(...) -#endif - -#if HAVE_NEON_CRYPTO -#define IF_NEON(...) __VA_ARGS__ -#else -#define IF_NEON(...) -#endif - -#define AES_SELECTOR_VTABLE(mode_c, id, mode_display, bits, ...) \ - static const ssh_cipheralg * \ - ssh_aes ## bits ## _ ## mode_c ## _impls[] = { \ - IF_NI(&ssh_aes ## bits ## _ ## mode_c ## _ni,) \ - IF_NEON(&ssh_aes ## bits ## _ ## mode_c ## _neon,) \ - &ssh_aes ## bits ## _ ## mode_c ## _sw, \ - NULL, \ - }; \ - const ssh_cipheralg ssh_aes ## bits ## _ ## mode_c = { \ - .new = aes_select, \ - .ssh2_id = id, \ - .blksize = 16, \ - .real_keybits = bits, \ - .padded_keybytes = bits/8, \ - .text_name = "AES-" #bits " " mode_display \ - " (dummy selector vtable)", \ - .extra = ssh_aes ## bits ## _ ## mode_c ## _impls, \ - __VA_ARGS__ \ - } - -AES_SELECTOR_VTABLE(cbc, "aes128-cbc", "CBC", 128, .flags = SSH_CIPHER_IS_CBC); -AES_SELECTOR_VTABLE(cbc, "aes192-cbc", "CBC", 192, .flags = SSH_CIPHER_IS_CBC); -AES_SELECTOR_VTABLE(cbc, "aes256-cbc", "CBC", 256, .flags = SSH_CIPHER_IS_CBC); -AES_SELECTOR_VTABLE(sdctr, "aes128-ctr", "SDCTR", 128, ); -AES_SELECTOR_VTABLE(sdctr, "aes192-ctr", "SDCTR", 192, ); -AES_SELECTOR_VTABLE(sdctr, "aes256-ctr", "SDCTR", 256, ); -AES_SELECTOR_VTABLE(gcm, "aes128-gcm@openssh.com", "GCM", 128, - .required_mac = &ssh2_aesgcm_mac, - .flags = SSH_CIPHER_SEPARATE_LENGTH); -AES_SELECTOR_VTABLE(gcm, "aes256-gcm@openssh.com", "GCM", 256, - .required_mac = &ssh2_aesgcm_mac, - .flags = SSH_CIPHER_SEPARATE_LENGTH); - -/* 192-bit AES-GCM is included only so that testcrypt can run standard - * test vectors against it. OpenSSH doesn't define a protocol id for - * it. Hence setting its ssh2_id to NULL here, and more importantly, - * leaving it out of aesgcm_list[] below. */ -AES_SELECTOR_VTABLE(gcm, NULL, "GCM", 192, - .required_mac = &ssh2_aesgcm_mac, - .flags = SSH_CIPHER_SEPARATE_LENGTH); - -static const ssh_cipheralg ssh_rijndael_lysator = { - /* Same as aes256_cbc, but with a different protocol ID */ - .new = aes_select, - .ssh2_id = "rijndael-cbc@lysator.liu.se", - .blksize = 16, - .real_keybits = 256, - .padded_keybytes = 256/8, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "AES-256 CBC (dummy selector vtable)", - .extra = ssh_aes256_cbc_impls, -}; - -static const ssh_cipheralg *const aes_list[] = { - &ssh_aes256_sdctr, - &ssh_aes256_cbc, - &ssh_rijndael_lysator, - &ssh_aes192_sdctr, - &ssh_aes192_cbc, - &ssh_aes128_sdctr, - &ssh_aes128_cbc, -}; - -const ssh2_ciphers ssh2_aes = { lenof(aes_list), aes_list }; - -static const ssh_cipheralg *const aesgcm_list[] = { - /* OpenSSH only defines protocol ids for 128- and 256-bit AES-GCM, - * not 192-bit. */ - &ssh_aes128_gcm, - &ssh_aes256_gcm, -}; - -const ssh2_ciphers ssh2_aesgcm = { lenof(aesgcm_list), aesgcm_list }; diff --git a/crypto/aes-sw.c b/crypto/aes-sw.c deleted file mode 100644 index aaa3c4756..000000000 --- a/crypto/aes-sw.c +++ /dev/null @@ -1,1133 +0,0 @@ -/* - * Software implementation of AES. - * - * This implementation uses a bit-sliced representation. Instead of - * the obvious approach of storing the cipher state so that each byte - * (or field element, or entry in the cipher matrix) occupies 8 - * contiguous bits in a machine integer somewhere, we organise the - * cipher state as an array of 8 integers, in such a way that each - * logical byte of the cipher state occupies one bit in each integer, - * all at the same position. This allows us to do parallel logic on - * all bytes of the state by doing bitwise operations between the 8 - * integers; in particular, the S-box (SubBytes) lookup is done this - * way, which takes about 110 operations - but for those 110 bitwise - * ops you get 64 S-box lookups, not just one. - */ - -#include "ssh.h" -#include "aes.h" -#include "mpint_i.h" /* we reuse the BignumInt system */ - -static bool aes_sw_available(void) -{ - /* Software AES is always available */ - return true; -} - -#define SLICE_PARALLELISM (BIGNUM_INT_BYTES / 2) - -#ifdef BITSLICED_DEBUG -/* Dump function that undoes the bitslicing transform, so you can see - * the logical data represented by a set of slice words. */ -static inline void dumpslices_uint16_t( - const char *prefix, const uint16_t slices[8]) -{ - printf("%-30s", prefix); - for (unsigned byte = 0; byte < 16; byte++) { - unsigned byteval = 0; - for (unsigned bit = 0; bit < 8; bit++) - byteval |= (1 & (slices[bit] >> byte)) << bit; - printf("%02x", byteval); - } - printf("\n"); -} - -static inline void dumpslices_BignumInt( - const char *prefix, const BignumInt slices[8]) -{ - printf("%-30s", prefix); - for (unsigned iter = 0; iter < SLICE_PARALLELISM; iter++) { - for (unsigned byte = 0; byte < 16; byte++) { - unsigned byteval = 0; - for (unsigned bit = 0; bit < 8; bit++) - byteval |= (1 & (slices[bit] >> (iter*16+byte))) << bit; - printf("%02x", byteval); - } - if (iter+1 < SLICE_PARALLELISM) - printf(" "); - } - printf("\n"); -} -#else -#define dumpslices_uintN_t(prefix, slices) ((void)0) -#define dumpslices_BignumInt(prefix, slices) ((void)0) -#endif - -/* ----- - * Bit-slicing transformation: convert between an array of 16 uint8_t - * and an array of 8 uint16_t, so as to interchange the bit index - * within each element and the element index within the array. (That - * is, bit j of input[i] == bit i of output[j]. - */ - -#define SWAPWORDS(shift) do \ - { \ - uint64_t mask = ~(uint64_t)0 / ((1ULL << shift) + 1); \ - uint64_t diff = ((i0 >> shift) ^ i1) & mask; \ - i0 ^= diff << shift; \ - i1 ^= diff; \ - } while (0) - -#define SWAPINWORD(i, bigshift, smallshift) do \ - { \ - uint64_t mask = ~(uint64_t)0; \ - mask /= ((1ULL << bigshift) + 1); \ - mask /= ((1ULL << smallshift) + 1); \ - mask <<= smallshift; \ - unsigned shift = bigshift - smallshift; \ - uint64_t diff = ((i >> shift) ^ i) & mask; \ - i ^= diff ^ (diff << shift); \ - } while (0) - -#define TO_BITSLICES(slices, bytes, uintN_t, assign_op, shift) do \ - { \ - uint64_t i0 = GET_64BIT_LSB_FIRST(bytes); \ - uint64_t i1 = GET_64BIT_LSB_FIRST(bytes + 8); \ - SWAPINWORD(i0, 8, 1); \ - SWAPINWORD(i1, 8, 1); \ - SWAPINWORD(i0, 16, 2); \ - SWAPINWORD(i1, 16, 2); \ - SWAPINWORD(i0, 32, 4); \ - SWAPINWORD(i1, 32, 4); \ - SWAPWORDS(8); \ - slices[0] assign_op (uintN_t)((i0 >> 0) & 0xFFFF) << (shift); \ - slices[2] assign_op (uintN_t)((i0 >> 16) & 0xFFFF) << (shift); \ - slices[4] assign_op (uintN_t)((i0 >> 32) & 0xFFFF) << (shift); \ - slices[6] assign_op (uintN_t)((i0 >> 48) & 0xFFFF) << (shift); \ - slices[1] assign_op (uintN_t)((i1 >> 0) & 0xFFFF) << (shift); \ - slices[3] assign_op (uintN_t)((i1 >> 16) & 0xFFFF) << (shift); \ - slices[5] assign_op (uintN_t)((i1 >> 32) & 0xFFFF) << (shift); \ - slices[7] assign_op (uintN_t)((i1 >> 48) & 0xFFFF) << (shift); \ - } while (0) - -#define FROM_BITSLICES(bytes, slices, shift) do \ - { \ - uint64_t i1 = ((slices[7] >> (shift)) & 0xFFFF); \ - i1 = (i1 << 16) | ((slices[5] >> (shift)) & 0xFFFF); \ - i1 = (i1 << 16) | ((slices[3] >> (shift)) & 0xFFFF); \ - i1 = (i1 << 16) | ((slices[1] >> (shift)) & 0xFFFF); \ - uint64_t i0 = ((slices[6] >> (shift)) & 0xFFFF); \ - i0 = (i0 << 16) | ((slices[4] >> (shift)) & 0xFFFF); \ - i0 = (i0 << 16) | ((slices[2] >> (shift)) & 0xFFFF); \ - i0 = (i0 << 16) | ((slices[0] >> (shift)) & 0xFFFF); \ - SWAPWORDS(8); \ - SWAPINWORD(i0, 32, 4); \ - SWAPINWORD(i1, 32, 4); \ - SWAPINWORD(i0, 16, 2); \ - SWAPINWORD(i1, 16, 2); \ - SWAPINWORD(i0, 8, 1); \ - SWAPINWORD(i1, 8, 1); \ - PUT_64BIT_LSB_FIRST(bytes, i0); \ - PUT_64BIT_LSB_FIRST((bytes) + 8, i1); \ - } while (0) - -/* ----- - * Some macros that will be useful repeatedly. - */ - -/* Iterate a unary transformation over all 8 slices. */ -#define ITERATE(MACRO, output, input, uintN_t) do \ - { \ - MACRO(output[0], input[0], uintN_t); \ - MACRO(output[1], input[1], uintN_t); \ - MACRO(output[2], input[2], uintN_t); \ - MACRO(output[3], input[3], uintN_t); \ - MACRO(output[4], input[4], uintN_t); \ - MACRO(output[5], input[5], uintN_t); \ - MACRO(output[6], input[6], uintN_t); \ - MACRO(output[7], input[7], uintN_t); \ - } while (0) - -/* Simply add (i.e. XOR) two whole sets of slices together. */ -#define BITSLICED_ADD(output, lhs, rhs) do \ - { \ - output[0] = lhs[0] ^ rhs[0]; \ - output[1] = lhs[1] ^ rhs[1]; \ - output[2] = lhs[2] ^ rhs[2]; \ - output[3] = lhs[3] ^ rhs[3]; \ - output[4] = lhs[4] ^ rhs[4]; \ - output[5] = lhs[5] ^ rhs[5]; \ - output[6] = lhs[6] ^ rhs[6]; \ - output[7] = lhs[7] ^ rhs[7]; \ - } while (0) - -/* ----- - * The AES S-box, in pure bitwise logic so that it can be run in - * parallel on whole words full of bit-sliced field elements. - * - * Source: 'A new combinational logic minimization technique with - * applications to cryptology', https://eprint.iacr.org/2009/191 - * - * As a minor speed optimisation, I use a modified version of the - * S-box which omits the additive constant 0x63, i.e. this S-box - * consists of only the field inversion and linear map components. - * Instead, the addition of the constant is deferred until after the - * subsequent ShiftRows and MixColumns stages, so that it happens at - * the same time as adding the next round key - and then we just make - * it _part_ of the round key, so it doesn't cost any extra - * instructions to add. - * - * (Obviously adding a constant to each byte commutes with ShiftRows, - * which only permutes the bytes. It also commutes with MixColumns: - * that's not quite so obvious, but since the effect of MixColumns is - * to multiply a constant polynomial M into each column, it is obvious - * that adding some polynomial K and then multiplying by M is - * equivalent to multiplying by M and then adding the product KM. And - * in fact, since the coefficients of M happen to sum to 1, it turns - * out that KM = K, so we don't even have to change the constant when - * we move it to the far side of MixColumns.) - * - * Of course, one knock-on effect of this is that the use of the S-box - * *during* key setup has to be corrected by manually adding on the - * constant afterwards! - */ - -/* Initial linear transformation for the forward S-box, from Fig 2 of - * the paper. */ -#define SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t) \ - uintN_t y14 = input[4] ^ input[2]; \ - uintN_t y13 = input[7] ^ input[1]; \ - uintN_t y9 = input[7] ^ input[4]; \ - uintN_t y8 = input[7] ^ input[2]; \ - uintN_t t0 = input[6] ^ input[5]; \ - uintN_t y1 = t0 ^ input[0]; \ - uintN_t y4 = y1 ^ input[4]; \ - uintN_t y12 = y13 ^ y14; \ - uintN_t y2 = y1 ^ input[7]; \ - uintN_t y5 = y1 ^ input[1]; \ - uintN_t y3 = y5 ^ y8; \ - uintN_t t1 = input[3] ^ y12; \ - uintN_t y15 = t1 ^ input[2]; \ - uintN_t y20 = t1 ^ input[6]; \ - uintN_t y6 = y15 ^ input[0]; \ - uintN_t y10 = y15 ^ t0; \ - uintN_t y11 = y20 ^ y9; \ - uintN_t y7 = input[0] ^ y11; \ - uintN_t y17 = y10 ^ y11; \ - uintN_t y19 = y10 ^ y8; \ - uintN_t y16 = t0 ^ y11; \ - uintN_t y21 = y13 ^ y16; \ - uintN_t y18 = input[7] ^ y16; \ - /* Make a copy of input[0] under a new name, because the core - * will refer to it, and in the inverse version of the S-box - * the corresponding value will be one of the calculated ones - * and not in input[0] itself. */ \ - uintN_t i0 = input[0]; \ - /* end */ - -/* Core nonlinear component, from Fig 3 of the paper. */ -#define SBOX_CORE(uintN_t) \ - uintN_t t2 = y12 & y15; \ - uintN_t t3 = y3 & y6; \ - uintN_t t4 = t3 ^ t2; \ - uintN_t t5 = y4 & i0; \ - uintN_t t6 = t5 ^ t2; \ - uintN_t t7 = y13 & y16; \ - uintN_t t8 = y5 & y1; \ - uintN_t t9 = t8 ^ t7; \ - uintN_t t10 = y2 & y7; \ - uintN_t t11 = t10 ^ t7; \ - uintN_t t12 = y9 & y11; \ - uintN_t t13 = y14 & y17; \ - uintN_t t14 = t13 ^ t12; \ - uintN_t t15 = y8 & y10; \ - uintN_t t16 = t15 ^ t12; \ - uintN_t t17 = t4 ^ t14; \ - uintN_t t18 = t6 ^ t16; \ - uintN_t t19 = t9 ^ t14; \ - uintN_t t20 = t11 ^ t16; \ - uintN_t t21 = t17 ^ y20; \ - uintN_t t22 = t18 ^ y19; \ - uintN_t t23 = t19 ^ y21; \ - uintN_t t24 = t20 ^ y18; \ - uintN_t t25 = t21 ^ t22; \ - uintN_t t26 = t21 & t23; \ - uintN_t t27 = t24 ^ t26; \ - uintN_t t28 = t25 & t27; \ - uintN_t t29 = t28 ^ t22; \ - uintN_t t30 = t23 ^ t24; \ - uintN_t t31 = t22 ^ t26; \ - uintN_t t32 = t31 & t30; \ - uintN_t t33 = t32 ^ t24; \ - uintN_t t34 = t23 ^ t33; \ - uintN_t t35 = t27 ^ t33; \ - uintN_t t36 = t24 & t35; \ - uintN_t t37 = t36 ^ t34; \ - uintN_t t38 = t27 ^ t36; \ - uintN_t t39 = t29 & t38; \ - uintN_t t40 = t25 ^ t39; \ - uintN_t t41 = t40 ^ t37; \ - uintN_t t42 = t29 ^ t33; \ - uintN_t t43 = t29 ^ t40; \ - uintN_t t44 = t33 ^ t37; \ - uintN_t t45 = t42 ^ t41; \ - uintN_t z0 = t44 & y15; \ - uintN_t z1 = t37 & y6; \ - uintN_t z2 = t33 & i0; \ - uintN_t z3 = t43 & y16; \ - uintN_t z4 = t40 & y1; \ - uintN_t z5 = t29 & y7; \ - uintN_t z6 = t42 & y11; \ - uintN_t z7 = t45 & y17; \ - uintN_t z8 = t41 & y10; \ - uintN_t z9 = t44 & y12; \ - uintN_t z10 = t37 & y3; \ - uintN_t z11 = t33 & y4; \ - uintN_t z12 = t43 & y13; \ - uintN_t z13 = t40 & y5; \ - uintN_t z14 = t29 & y2; \ - uintN_t z15 = t42 & y9; \ - uintN_t z16 = t45 & y14; \ - uintN_t z17 = t41 & y8; \ - /* end */ - -/* Final linear transformation for the forward S-box, from Fig 4 of - * the paper. */ -#define SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t) \ - uintN_t t46 = z15 ^ z16; \ - uintN_t t47 = z10 ^ z11; \ - uintN_t t48 = z5 ^ z13; \ - uintN_t t49 = z9 ^ z10; \ - uintN_t t50 = z2 ^ z12; \ - uintN_t t51 = z2 ^ z5; \ - uintN_t t52 = z7 ^ z8; \ - uintN_t t53 = z0 ^ z3; \ - uintN_t t54 = z6 ^ z7; \ - uintN_t t55 = z16 ^ z17; \ - uintN_t t56 = z12 ^ t48; \ - uintN_t t57 = t50 ^ t53; \ - uintN_t t58 = z4 ^ t46; \ - uintN_t t59 = z3 ^ t54; \ - uintN_t t60 = t46 ^ t57; \ - uintN_t t61 = z14 ^ t57; \ - uintN_t t62 = t52 ^ t58; \ - uintN_t t63 = t49 ^ t58; \ - uintN_t t64 = z4 ^ t59; \ - uintN_t t65 = t61 ^ t62; \ - uintN_t t66 = z1 ^ t63; \ - output[7] = t59 ^ t63; \ - output[1] = t56 ^ t62; \ - output[0] = t48 ^ t60; \ - uintN_t t67 = t64 ^ t65; \ - output[4] = t53 ^ t66; \ - output[3] = t51 ^ t66; \ - output[2] = t47 ^ t65; \ - output[6] = t64 ^ output[4]; \ - output[5] = t55 ^ t67; \ - /* end */ - -#define BITSLICED_SUBBYTES(output, input, uintN_t) do { \ - SBOX_FORWARD_TOP_TRANSFORM(input, uintN_t); \ - SBOX_CORE(uintN_t); \ - SBOX_FORWARD_BOTTOM_TRANSFORM(output, uintN_t); \ - } while (0) - -/* - * Initial and final linear transformations for the backward S-box. I - * generated these myself, by implementing the linear-transform - * optimisation algorithm in the paper, and applying it to the - * matrices calculated by _their_ top and bottom transformations, pre- - * and post-multiplied as appropriate by the linear map in the inverse - * S_box. - */ -#define SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t) \ - uintN_t y5 = input[4] ^ input[6]; \ - uintN_t y19 = input[3] ^ input[0]; \ - uintN_t itmp8 = y5 ^ input[0]; \ - uintN_t y4 = itmp8 ^ input[1]; \ - uintN_t y9 = input[4] ^ input[3]; \ - uintN_t y2 = y9 ^ y4; \ - uintN_t itmp9 = y2 ^ input[7]; \ - uintN_t y1 = y9 ^ input[0]; \ - uintN_t y6 = y5 ^ input[7]; \ - uintN_t y18 = y9 ^ input[5]; \ - uintN_t y7 = y18 ^ y2; \ - uintN_t y16 = y7 ^ y1; \ - uintN_t y21 = y7 ^ input[1]; \ - uintN_t y3 = input[4] ^ input[7]; \ - uintN_t y13 = y16 ^ y21; \ - uintN_t y8 = input[4] ^ y6; \ - uintN_t y10 = y8 ^ y19; \ - uintN_t y14 = y8 ^ y9; \ - uintN_t y20 = itmp9 ^ input[2]; \ - uintN_t y11 = y9 ^ y20; \ - uintN_t i0 = y11 ^ y7; \ - uintN_t y15 = i0 ^ y6; \ - uintN_t y17 = y16 ^ y15; \ - uintN_t y12 = itmp9 ^ input[3]; \ - /* end */ -#define SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t) \ - uintN_t otmp18 = z15 ^ z6; \ - uintN_t otmp19 = z13 ^ otmp18; \ - uintN_t otmp20 = z12 ^ otmp19; \ - uintN_t otmp21 = z16 ^ otmp20; \ - uintN_t otmp22 = z8 ^ otmp21; \ - uintN_t otmp23 = z0 ^ otmp22; \ - uintN_t otmp24 = otmp22 ^ z3; \ - uintN_t otmp25 = otmp24 ^ z4; \ - uintN_t otmp26 = otmp25 ^ z2; \ - uintN_t otmp27 = z1 ^ otmp26; \ - uintN_t otmp28 = z14 ^ otmp27; \ - uintN_t otmp29 = otmp28 ^ z10; \ - output[4] = z2 ^ otmp23; \ - output[7] = z5 ^ otmp24; \ - uintN_t otmp30 = z11 ^ otmp29; \ - output[5] = z13 ^ otmp30; \ - uintN_t otmp31 = otmp25 ^ z8; \ - output[1] = z7 ^ otmp31; \ - uintN_t otmp32 = z11 ^ z9; \ - uintN_t otmp33 = z17 ^ otmp32; \ - uintN_t otmp34 = otmp30 ^ otmp33; \ - output[0] = z15 ^ otmp33; \ - uintN_t otmp35 = z12 ^ otmp34; \ - output[6] = otmp35 ^ z16; \ - uintN_t otmp36 = z1 ^ otmp23; \ - uintN_t otmp37 = z5 ^ otmp36; \ - output[2] = z4 ^ otmp37; \ - uintN_t otmp38 = z11 ^ output[1]; \ - uintN_t otmp39 = z2 ^ otmp38; \ - uintN_t otmp40 = z17 ^ otmp39; \ - uintN_t otmp41 = z0 ^ otmp40; \ - uintN_t otmp42 = z5 ^ otmp41; \ - uintN_t otmp43 = otmp42 ^ z10; \ - uintN_t otmp44 = otmp43 ^ z3; \ - output[3] = otmp44 ^ z16; \ - /* end */ - -#define BITSLICED_INVSUBBYTES(output, input, uintN_t) do { \ - SBOX_BACKWARD_TOP_TRANSFORM(input, uintN_t); \ - SBOX_CORE(uintN_t); \ - SBOX_BACKWARD_BOTTOM_TRANSFORM(output, uintN_t); \ - } while (0) - - -/* ----- - * The ShiftRows transformation. This operates independently on each - * bit slice. - */ - -#define SINGLE_BITSLICE_SHIFTROWS(output, input, uintN_t) do \ - { \ - uintN_t mask, mask2, mask3, diff, x = (input); \ - /* Rotate rows 2 and 3 by 16 bits */ \ - mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - diff = ((x >> 8) ^ x) & mask; \ - x ^= diff ^ (diff << 8); \ - /* Rotate rows 1 and 3 by 8 bits */ \ - mask = 0x0AAA * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - mask2 = 0xA000 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - x = ((x >> 4) & mask) | ((x << 12) & mask2) | (x & mask3); \ - /* Write output */ \ - (output) = x; \ - } while (0) - -#define SINGLE_BITSLICE_INVSHIFTROWS(output, input, uintN_t) do \ - { \ - uintN_t mask, mask2, mask3, diff, x = (input); \ - /* Rotate rows 2 and 3 by 16 bits */ \ - mask = 0x00CC * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - diff = ((x >> 8) ^ x) & mask; \ - x ^= diff ^ (diff << 8); \ - /* Rotate rows 1 and 3 by 8 bits, the opposite way to ShiftRows */ \ - mask = 0x000A * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - mask2 = 0xAAA0 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - mask3 = 0x5555 * (((uintN_t)~(uintN_t)0) / 0xFFFF); \ - x = ((x >> 12) & mask) | ((x << 4) & mask2) | (x & mask3); \ - /* Write output */ \ - (output) = x; \ - } while (0) - -#define BITSLICED_SHIFTROWS(output, input, uintN_t) do \ - { \ - ITERATE(SINGLE_BITSLICE_SHIFTROWS, output, input, uintN_t); \ - } while (0) - -#define BITSLICED_INVSHIFTROWS(output, input, uintN_t) do \ - { \ - ITERATE(SINGLE_BITSLICE_INVSHIFTROWS, output, input, uintN_t); \ - } while (0) - -/* ----- - * The MixColumns transformation. This has to operate on all eight bit - * slices at once, and also passes data back and forth between the - * bits in an adjacent group of 4 within each slice. - * - * Notation: let F = GF(2)[X]/ be the finite field - * used in AES, and let R = F[Y]/ be the ring whose elements - * represent the possible contents of a column of the matrix. I use X - * and Y below in those senses, i.e. X is the value in F that - * represents the byte 0x02, and Y is the value in R that cycles the - * four bytes around by one if you multiply by it. - */ - -/* Multiply every column by Y^3, i.e. cycle it round one place to the - * right. Operates on one bit slice at a time; you have to wrap it in - * ITERATE to affect all the data at once. */ -#define BITSLICED_MUL_BY_Y3(output, input, uintN_t) do \ - { \ - uintN_t mask, mask2, x; \ - mask = 0x8 * (((uintN_t)~(uintN_t)0) / 0xF); \ - mask2 = 0x7 * (((uintN_t)~(uintN_t)0) / 0xF); \ - x = input; \ - output = ((x << 3) & mask) ^ ((x >> 1) & mask2); \ - } while (0) - -/* Multiply every column by Y^2. */ -#define BITSLICED_MUL_BY_Y2(output, input, uintN_t) do \ - { \ - uintN_t mask, mask2, x; \ - mask = 0xC * (((uintN_t)~(uintN_t)0) / 0xF); \ - mask2 = 0x3 * (((uintN_t)~(uintN_t)0) / 0xF); \ - x = input; \ - output = ((x << 2) & mask) ^ ((x >> 2) & mask2); \ - } while (0) - -#define BITSLICED_MUL_BY_1_Y3(output, input, uintN_t) do \ - { \ - uintN_t tmp = input; \ - BITSLICED_MUL_BY_Y3(tmp, input, uintN_t); \ - output = input ^ tmp; \ - } while (0) - -/* Multiply every column by 1+Y^2. */ -#define BITSLICED_MUL_BY_1_Y2(output, input, uintN_t) do \ - { \ - uintN_t tmp = input; \ - BITSLICED_MUL_BY_Y2(tmp, input, uintN_t); \ - output = input ^ tmp; \ - } while (0) - -/* Multiply every field element by X. This has to feed data between - * slices, so it does the whole job in one go without needing ITERATE. */ -#define BITSLICED_MUL_BY_X(output, input, uintN_t) do \ - { \ - uintN_t bit7 = input[7]; \ - output[7] = input[6]; \ - output[6] = input[5]; \ - output[5] = input[4]; \ - output[4] = input[3] ^ bit7; \ - output[3] = input[2] ^ bit7; \ - output[2] = input[1]; \ - output[1] = input[0] ^ bit7; \ - output[0] = bit7; \ - } while (0) - -/* - * The MixColumns constant is - * M = X + Y + Y^2 + (X+1)Y^3 - * which we construct by rearranging it into - * M = 1 + (1+Y^3) [ X + (1+Y^2) ] - */ -#define BITSLICED_MIXCOLUMNS(output, input, uintN_t) do \ - { \ - uintN_t a[8], aX[8], b[8]; \ - /* a = input * (1+Y^3) */ \ - ITERATE(BITSLICED_MUL_BY_1_Y3, a, input, uintN_t); \ - /* aX = a * X */ \ - BITSLICED_MUL_BY_X(aX, a, uintN_t); \ - /* b = a * (1+Y^2) = input * (1+Y+Y^2+Y^3) */ \ - ITERATE(BITSLICED_MUL_BY_1_Y2, b, a, uintN_t); \ - /* output = input + aX + b (reusing a as a temp */ \ - BITSLICED_ADD(a, aX, b); \ - BITSLICED_ADD(output, input, a); \ - } while (0) - -/* - * The InvMixColumns constant, written out longhand, is - * I = (X^3+X^2+X) + (X^3+1)Y + (X^3+X^2+1)Y^2 + (X^3+X+1)Y^3 - * We represent this as - * I = (X^3+X^2+X+1)(Y^3+Y^2+Y+1) + 1 + X(Y+Y^2) + X^2(Y+Y^3) - */ -#define BITSLICED_INVMIXCOLUMNS(output, input, uintN_t) do \ - { \ - /* We need input * X^i for i=1,...,3 */ \ - uintN_t X[8], X2[8], X3[8]; \ - BITSLICED_MUL_BY_X(X, input, uintN_t); \ - BITSLICED_MUL_BY_X(X2, X, uintN_t); \ - BITSLICED_MUL_BY_X(X3, X2, uintN_t); \ - /* Sum them all and multiply by 1+Y+Y^2+Y^3. */ \ - uintN_t S[8]; \ - BITSLICED_ADD(S, input, X); \ - BITSLICED_ADD(S, S, X2); \ - BITSLICED_ADD(S, S, X3); \ - ITERATE(BITSLICED_MUL_BY_1_Y3, S, S, uintN_t); \ - ITERATE(BITSLICED_MUL_BY_1_Y2, S, S, uintN_t); \ - /* Compute the X(Y+Y^2) term. */ \ - uintN_t A[8]; \ - ITERATE(BITSLICED_MUL_BY_1_Y3, A, X, uintN_t); \ - ITERATE(BITSLICED_MUL_BY_Y2, A, A, uintN_t); \ - /* Compute the X^2(Y+Y^3) term. */ \ - uintN_t B[8]; \ - ITERATE(BITSLICED_MUL_BY_1_Y2, B, X2, uintN_t); \ - ITERATE(BITSLICED_MUL_BY_Y3, B, B, uintN_t); \ - /* And add all the pieces together. */ \ - BITSLICED_ADD(S, S, input); \ - BITSLICED_ADD(S, S, A); \ - BITSLICED_ADD(output, S, B); \ - } while (0) - -/* ----- - * Put it all together into a cipher round. - */ - -/* Dummy macro to get rid of the MixColumns in the final round. */ -#define NO_MIXCOLUMNS(out, in, uintN_t) do {} while (0) - -#define ENCRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro) \ - static void aes_sliced_round_e_##suffix( \ - uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \ - { \ - BITSLICED_SUBBYTES(output, input, uintN_t); \ - BITSLICED_SHIFTROWS(output, output, uintN_t); \ - mixcol_macro(output, output, uintN_t); \ - BITSLICED_ADD(output, output, roundkey); \ - } - -ENCRYPT_ROUND_FN(serial, uint16_t, BITSLICED_MIXCOLUMNS) -ENCRYPT_ROUND_FN(serial_last, uint16_t, NO_MIXCOLUMNS) -ENCRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_MIXCOLUMNS) -ENCRYPT_ROUND_FN(parallel_last, BignumInt, NO_MIXCOLUMNS) - -#define DECRYPT_ROUND_FN(suffix, uintN_t, mixcol_macro) \ - static void aes_sliced_round_d_##suffix( \ - uintN_t output[8], const uintN_t input[8], const uintN_t roundkey[8]) \ - { \ - BITSLICED_ADD(output, input, roundkey); \ - mixcol_macro(output, output, uintN_t); \ - BITSLICED_INVSUBBYTES(output, output, uintN_t); \ - BITSLICED_INVSHIFTROWS(output, output, uintN_t); \ - } - -#if 0 /* no cipher mode we support requires serial decryption */ -DECRYPT_ROUND_FN(serial, uint16_t, BITSLICED_INVMIXCOLUMNS) -DECRYPT_ROUND_FN(serial_first, uint16_t, NO_MIXCOLUMNS) -#endif -DECRYPT_ROUND_FN(parallel, BignumInt, BITSLICED_INVMIXCOLUMNS) -DECRYPT_ROUND_FN(parallel_first, BignumInt, NO_MIXCOLUMNS) - -/* ----- - * Key setup function. - */ - -typedef struct aes_sliced_key aes_sliced_key; -struct aes_sliced_key { - BignumInt roundkeys_parallel[MAXROUNDKEYS * 8]; - uint16_t roundkeys_serial[MAXROUNDKEYS * 8]; - unsigned rounds; -}; - -static void aes_sliced_key_setup( - aes_sliced_key *sk, const void *vkey, size_t keybits) -{ - const unsigned char *key = (const unsigned char *)vkey; - - size_t key_words = keybits / 32; - sk->rounds = key_words + 6; - size_t sched_words = (sk->rounds + 1) * 4; - - unsigned rconpos = 0; - - uint16_t *outslices = sk->roundkeys_serial; - unsigned outshift = 0; - - memset(sk->roundkeys_serial, 0, sizeof(sk->roundkeys_serial)); - - uint8_t inblk[16]; - memset(inblk, 0, 16); - uint16_t slices[8]; - - for (size_t i = 0; i < sched_words; i++) { - /* - * Prepare a word of round key in the low 4 bits of each - * integer in slices[]. - */ - if (i < key_words) { - memcpy(inblk, key + 4*i, 4); - TO_BITSLICES(slices, inblk, uint16_t, =, 0); - } else { - unsigned wordindex, bitshift; - uint16_t *prevslices; - - /* Fetch the (i-1)th key word */ - wordindex = i-1; - bitshift = 4 * (wordindex & 3); - prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2); - for (size_t i = 0; i < 8; i++) - slices[i] = prevslices[i] >> bitshift; - - /* Decide what we're doing in this expansion stage */ - bool rotate_and_round_constant = (i % key_words == 0); - bool sub = rotate_and_round_constant || - (key_words == 8 && i % 8 == 4); - - if (rotate_and_round_constant) { - for (size_t i = 0; i < 8; i++) - slices[i] = ((slices[i] << 3) | (slices[i] >> 1)) & 0xF; - } - - if (sub) { - /* Apply the SubBytes transform to the key word. But - * here we need to apply the _full_ SubBytes from the - * spec, including the constant which our S-box leaves - * out. */ - BITSLICED_SUBBYTES(slices, slices, uint16_t); - slices[0] ^= 0xFFFF; - slices[1] ^= 0xFFFF; - slices[5] ^= 0xFFFF; - slices[6] ^= 0xFFFF; - } - - if (rotate_and_round_constant) { - assert(rconpos < lenof(aes_key_setup_round_constants)); - uint8_t rcon = aes_key_setup_round_constants[rconpos++]; - for (size_t i = 0; i < 8; i++) - slices[i] ^= 1 & (rcon >> i); - } - - /* Combine with the (i-Nk)th key word */ - wordindex = i - key_words; - bitshift = 4 * (wordindex & 3); - prevslices = sk->roundkeys_serial + 8 * (wordindex >> 2); - for (size_t i = 0; i < 8; i++) - slices[i] ^= prevslices[i] >> bitshift; - } - - /* - * Now copy it into sk. - */ - for (unsigned b = 0; b < 8; b++) - outslices[b] |= (slices[b] & 0xF) << outshift; - outshift += 4; - if (outshift == 16) { - outshift = 0; - outslices += 8; - } - } - - smemclr(inblk, sizeof(inblk)); - smemclr(slices, sizeof(slices)); - - /* - * Add the S-box constant to every round key after the first one, - * compensating for it being left out in the main cipher. - */ - for (size_t i = 8; i < 8 * (sched_words/4); i += 8) { - sk->roundkeys_serial[i+0] ^= 0xFFFF; - sk->roundkeys_serial[i+1] ^= 0xFFFF; - sk->roundkeys_serial[i+5] ^= 0xFFFF; - sk->roundkeys_serial[i+6] ^= 0xFFFF; - } - - /* - * Replicate that set of round keys into larger integers for the - * parallel versions of the cipher. - */ - for (size_t i = 0; i < 8 * (sched_words / 4); i++) { - sk->roundkeys_parallel[i] = sk->roundkeys_serial[i] * - ((BignumInt)~(BignumInt)0 / 0xFFFF); - } -} - -/* ----- - * The full cipher primitive, including transforming the input and - * output to/from bit-sliced form. - */ - -#define ENCRYPT_FN(suffix, uintN_t, nblocks) \ - static void aes_sliced_e_##suffix( \ - uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \ - { \ - uintN_t state[8]; \ - TO_BITSLICES(state, input, uintN_t, =, 0); \ - for (unsigned i = 1; i < nblocks; i++) { \ - input += 16; \ - TO_BITSLICES(state, input, uintN_t, |=, i*16); \ - } \ - const uintN_t *keys = sk->roundkeys_##suffix; \ - BITSLICED_ADD(state, state, keys); \ - keys += 8; \ - for (unsigned i = 0; i < sk->rounds-1; i++) { \ - aes_sliced_round_e_##suffix(state, state, keys); \ - keys += 8; \ - } \ - aes_sliced_round_e_##suffix##_last(state, state, keys); \ - for (unsigned i = 0; i < nblocks; i++) { \ - FROM_BITSLICES(output, state, i*16); \ - output += 16; \ - } \ - } - -#define DECRYPT_FN(suffix, uintN_t, nblocks) \ - static void aes_sliced_d_##suffix( \ - uint8_t *output, const uint8_t *input, const aes_sliced_key *sk) \ - { \ - uintN_t state[8]; \ - TO_BITSLICES(state, input, uintN_t, =, 0); \ - for (unsigned i = 1; i < nblocks; i++) { \ - input += 16; \ - TO_BITSLICES(state, input, uintN_t, |=, i*16); \ - } \ - const uintN_t *keys = sk->roundkeys_##suffix + 8*sk->rounds; \ - aes_sliced_round_d_##suffix##_first(state, state, keys); \ - keys -= 8; \ - for (unsigned i = 0; i < sk->rounds-1; i++) { \ - aes_sliced_round_d_##suffix(state, state, keys); \ - keys -= 8; \ - } \ - BITSLICED_ADD(state, state, keys); \ - for (unsigned i = 0; i < nblocks; i++) { \ - FROM_BITSLICES(output, state, i*16); \ - output += 16; \ - } \ - } - -ENCRYPT_FN(serial, uint16_t, 1) -#if 0 /* no cipher mode we support requires serial decryption */ -DECRYPT_FN(serial, uint16_t, 1) -#endif -ENCRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM) -DECRYPT_FN(parallel, BignumInt, SLICE_PARALLELISM) - -/* ----- - * The SSH interface and the cipher modes. - */ - -#define SDCTR_WORDS (16 / BIGNUM_INT_BYTES) - -typedef struct aes_sw_context aes_sw_context; -struct aes_sw_context { - aes_sliced_key sk; - union { - struct { - /* In CBC mode, the IV is just a copy of the last seen - * cipher block. */ - uint8_t prevblk[16]; - } cbc; - struct { - /* In SDCTR mode, we keep the counter itself in a form - * that's easy to increment. We also use the parallel - * version of the core AES function, so we'll encrypt - * multiple counter values in one go. That won't align - * nicely with the sizes of data we're asked to encrypt, - * so we must also store a cache of the last set of - * keystream blocks we generated, and our current position - * within that cache. */ - BignumInt counter[SDCTR_WORDS]; - uint8_t keystream[SLICE_PARALLELISM * 16]; - uint8_t *keystream_pos; - } sdctr; - struct { - /* In GCM mode, the cipher preimage consists of three - * sections: one fixed, one that increments per message - * sent and MACed, and one that increments per cipher - * block. */ - uint64_t msg_counter; - uint32_t fixed_iv, block_counter; - /* But we keep the precomputed keystream chunks just like - * SDCTR mode. */ - uint8_t keystream[SLICE_PARALLELISM * 16]; - uint8_t *keystream_pos; - } gcm; - } iv; - ssh_cipher ciph; -}; - -static ssh_cipher *aes_sw_new(const ssh_cipheralg *alg) -{ - aes_sw_context *ctx = snew(aes_sw_context); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void aes_sw_free(ssh_cipher *ciph) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void aes_sw_setkey(ssh_cipher *ciph, const void *vkey) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - aes_sliced_key_setup(&ctx->sk, vkey, ctx->ciph.vt->real_keybits); -} - -static void aes_sw_setiv_cbc(ssh_cipher *ciph, const void *iv) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - memcpy(ctx->iv.cbc.prevblk, iv, 16); -} - -static void aes_sw_setiv_sdctr(ssh_cipher *ciph, const void *viv) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - const uint8_t *iv = (const uint8_t *)viv; - - /* Import the initial counter value into the internal representation */ - for (unsigned i = 0; i < SDCTR_WORDS; i++) - ctx->iv.sdctr.counter[i] = - GET_BIGNUMINT_MSB_FIRST( - iv + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES); - - /* Set keystream_pos to indicate that the keystream cache is - * currently empty */ - ctx->iv.sdctr.keystream_pos = - ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream); -} - -static void aes_sw_setiv_gcm(ssh_cipher *ciph, const void *viv) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - const uint8_t *iv = (const uint8_t *)viv; - - ctx->iv.gcm.fixed_iv = GET_32BIT_MSB_FIRST(iv); - ctx->iv.gcm.msg_counter = GET_64BIT_MSB_FIRST(iv + 4); - ctx->iv.gcm.block_counter = 1; - - /* Set keystream_pos to indicate that the keystream cache is - * currently empty */ - ctx->iv.gcm.keystream_pos = - ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream); -} - -static void aes_sw_next_message_gcm(ssh_cipher *ciph) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - - ctx->iv.gcm.msg_counter++; - ctx->iv.gcm.block_counter = 1; - ctx->iv.gcm.keystream_pos = - ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream); -} - -typedef void (*aes_sw_fn)(uint32_t v[4], const uint32_t *keysched); - -static inline void memxor16(void *vout, const void *vlhs, const void *vrhs) -{ - uint8_t *out = (uint8_t *)vout; - const uint8_t *lhs = (const uint8_t *)vlhs, *rhs = (const uint8_t *)vrhs; - uint64_t w; - - w = GET_64BIT_LSB_FIRST(lhs); - w ^= GET_64BIT_LSB_FIRST(rhs); - PUT_64BIT_LSB_FIRST(out, w); - w = GET_64BIT_LSB_FIRST(lhs + 8); - w ^= GET_64BIT_LSB_FIRST(rhs + 8); - PUT_64BIT_LSB_FIRST(out + 8, w); -} - -static inline void aes_cbc_sw_encrypt( - ssh_cipher *ciph, void *vblk, int blklen) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - - /* - * CBC encryption has to be done serially, because the input to - * each run of the cipher includes the output from the previous - * run. - */ - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - /* - * We use the IV array itself as the location for the - * encryption, because there's no reason not to. - */ - - /* XOR the new plaintext block into the previous cipher block */ - memxor16(ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, blk); - - /* Run the cipher over the result, which leaves it - * conveniently already stored in ctx->iv */ - aes_sliced_e_serial( - ctx->iv.cbc.prevblk, ctx->iv.cbc.prevblk, &ctx->sk); - - /* Copy it to the output location */ - memcpy(blk, ctx->iv.cbc.prevblk, 16); - } -} - -static inline void aes_cbc_sw_decrypt( - ssh_cipher *ciph, void *vblk, int blklen) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - uint8_t *blk = (uint8_t *)vblk; - - /* - * CBC decryption can run in parallel, because all the - * _ciphertext_ blocks are already available. - */ - - size_t blocks_remaining = blklen / 16; - - uint8_t data[SLICE_PARALLELISM * 16]; - /* Zeroing the data array is probably overcautious, but it avoids - * technically undefined behaviour from leaving it uninitialised - * if our very first iteration doesn't include enough cipher - * blocks to populate it fully */ - memset(data, 0, sizeof(data)); - - while (blocks_remaining > 0) { - /* Number of blocks we'll handle in this iteration. If we're - * dealing with fewer than the maximum, it doesn't matter - - * it's harmless to run the full parallel cipher function - * anyway. */ - size_t blocks = (blocks_remaining < SLICE_PARALLELISM ? - blocks_remaining : SLICE_PARALLELISM); - - /* Parallel-decrypt the input, in a separate array so we still - * have the cipher stream available for XORing. */ - memcpy(data, blk, 16 * blocks); - aes_sliced_d_parallel(data, data, &ctx->sk); - - /* Write the output and update the IV */ - for (size_t i = 0; i < blocks; i++) { - uint8_t *decrypted = data + 16*i; - uint8_t *output = blk + 16*i; - - memxor16(decrypted, decrypted, ctx->iv.cbc.prevblk); - memcpy(ctx->iv.cbc.prevblk, output, 16); - memcpy(output, decrypted, 16); - } - - /* Advance the input pointer. */ - blk += 16 * blocks; - blocks_remaining -= blocks; - } - - smemclr(data, sizeof(data)); -} - -static inline void aes_sdctr_sw( - ssh_cipher *ciph, void *vblk, int blklen) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - - /* - * SDCTR encrypt/decrypt loops round one block at a time XORing - * the keystream into the user's data, and periodically has to run - * a parallel encryption operation to get more keystream. - */ - - uint8_t *keystream_end = - ctx->iv.sdctr.keystream + sizeof(ctx->iv.sdctr.keystream); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - - if (ctx->iv.sdctr.keystream_pos == keystream_end) { - /* - * Generate some keystream. - */ - for (uint8_t *block = ctx->iv.sdctr.keystream; - block < keystream_end; block += 16) { - /* Format the counter value into the buffer. */ - for (unsigned i = 0; i < SDCTR_WORDS; i++) - PUT_BIGNUMINT_MSB_FIRST( - block + 16 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES, - ctx->iv.sdctr.counter[i]); - - /* Increment the counter. */ - BignumCarry carry = 1; - for (unsigned i = 0; i < SDCTR_WORDS; i++) - BignumADC(ctx->iv.sdctr.counter[i], carry, - ctx->iv.sdctr.counter[i], 0, carry); - } - - /* Encrypt all those counter blocks. */ - aes_sliced_e_parallel(ctx->iv.sdctr.keystream, - ctx->iv.sdctr.keystream, &ctx->sk); - - /* Reset keystream_pos to the start of the buffer. */ - ctx->iv.sdctr.keystream_pos = ctx->iv.sdctr.keystream; - } - - memxor16(blk, blk, ctx->iv.sdctr.keystream_pos); - ctx->iv.sdctr.keystream_pos += 16; - } -} - -static inline void aes_encrypt_ecb_block_sw(ssh_cipher *ciph, void *blk) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - aes_sliced_e_serial(blk, blk, &ctx->sk); -} - -static inline void aes_gcm_sw( - ssh_cipher *ciph, void *vblk, int blklen) -{ - aes_sw_context *ctx = container_of(ciph, aes_sw_context, ciph); - - /* - * GCM encrypt/decrypt looks just like SDCTR, except that the - * method of generating more keystream varies slightly. - */ - - uint8_t *keystream_end = - ctx->iv.gcm.keystream + sizeof(ctx->iv.gcm.keystream); - - for (uint8_t *blk = (uint8_t *)vblk, *finish = blk + blklen; - blk < finish; blk += 16) { - - if (ctx->iv.gcm.keystream_pos == keystream_end) { - /* - * Generate some keystream. - */ - for (uint8_t *block = ctx->iv.gcm.keystream; - block < keystream_end; block += 16) { - /* Format the counter value into the buffer. */ - PUT_32BIT_MSB_FIRST(block, ctx->iv.gcm.fixed_iv); - PUT_64BIT_MSB_FIRST(block + 4, ctx->iv.gcm.msg_counter); - PUT_32BIT_MSB_FIRST(block + 12, ctx->iv.gcm.block_counter); - - /* Increment the counter. */ - ctx->iv.gcm.block_counter++; - } - - /* Encrypt all those counter blocks. */ - aes_sliced_e_parallel(ctx->iv.gcm.keystream, - ctx->iv.gcm.keystream, &ctx->sk); - - /* Reset keystream_pos to the start of the buffer. */ - ctx->iv.gcm.keystream_pos = ctx->iv.gcm.keystream; - } - - memxor16(blk, blk, ctx->iv.gcm.keystream_pos); - ctx->iv.gcm.keystream_pos += 16; - } -} - -#define SW_ENC_DEC(len) \ - static void aes##len##_sw_cbc_encrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_sw_encrypt(ciph, vblk, blklen); } \ - static void aes##len##_sw_cbc_decrypt( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_cbc_sw_decrypt(ciph, vblk, blklen); } \ - static void aes##len##_sw_sdctr( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_sdctr_sw(ciph, vblk, blklen); } \ - static void aes##len##_sw_gcm( \ - ssh_cipher *ciph, void *vblk, int blklen) \ - { aes_gcm_sw(ciph, vblk, blklen); } \ - static void aes##len##_sw_encrypt_ecb_block( \ - ssh_cipher *ciph, void *vblk) \ - { aes_encrypt_ecb_block_sw(ciph, vblk); } - -SW_ENC_DEC(128) -SW_ENC_DEC(192) -SW_ENC_DEC(256) - -AES_EXTRA(_sw); -AES_ALL_VTABLES(_sw, "unaccelerated"); diff --git a/crypto/aes.h b/crypto/aes.h deleted file mode 100644 index cab5b9896..000000000 --- a/crypto/aes.h +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Definitions likely to be helpful to multiple AES implementations. - */ - -/* - * The 'extra' structure used by AES implementations is used to - * include information about how to check if a given implementation is - * available at run time, and whether we've already checked. - */ -struct aes_extra_mutable; -struct aes_extra { - /* Function to check availability. Might be expensive, so we don't - * want to call it more than once. */ - bool (*check_available)(void); - - /* Point to a writable substructure. */ - struct aes_extra_mutable *mut; - - /* Extra API function specific to AES, to encrypt a single block - * in ECB mode without touching the IV. Used by AES-GCM MAC - * setup. */ - void (*encrypt_ecb_block)(ssh_cipher *, void *); -}; -struct aes_extra_mutable { - bool checked_availability; - bool is_available; -}; -static inline bool check_availability(const struct aes_extra *extra) -{ - if (!extra->mut->checked_availability) { - extra->mut->is_available = extra->check_available(); - extra->mut->checked_availability = true; - } - - return extra->mut->is_available; -} - -/* Shared stub function for all the AES-GCM vtables. */ -void aesgcm_cipher_crypt_length( - ssh_cipher *cipher, void *blk, int len, unsigned long seq); - -/* External entry point for the encrypt_ecb_block function. */ -static inline void aes_encrypt_ecb_block(ssh_cipher *ciph, void *blk) -{ - const struct aes_extra *extra = ciph->vt->extra; - extra->encrypt_ecb_block(ciph, blk); -} - -/* - * Macros to define vtables for AES variants. There are a lot of - * these, because of the cross product between cipher modes, key - * sizes, and assorted HW/SW implementations, so it's worth spending - * some effort here to reduce the boilerplate in the sub-files. - */ - -#define AES_EXTRA_BITS(impl_c, bits) \ - static struct aes_extra_mutable aes ## impl_c ## _extra_mut; \ - static const struct aes_extra aes ## bits ## impl_c ## _extra = { \ - .check_available = aes ## impl_c ## _available, \ - .mut = &aes ## impl_c ## _extra_mut, \ - .encrypt_ecb_block = &aes ## bits ## impl_c ## _encrypt_ecb_block, \ - } - -#define AES_EXTRA(impl_c) \ - AES_EXTRA_BITS(impl_c, 128); \ - AES_EXTRA_BITS(impl_c, 192); \ - AES_EXTRA_BITS(impl_c, 256) - -#define AES_CBC_VTABLE(impl_c, impl_display, bits) \ - const ssh_cipheralg ssh_aes ## bits ## _cbc ## impl_c = { \ - .new = aes ## impl_c ## _new, \ - .free = aes ## impl_c ## _free, \ - .setiv = aes ## impl_c ## _setiv_cbc, \ - .setkey = aes ## impl_c ## _setkey, \ - .encrypt = aes ## bits ## impl_c ## _cbc_encrypt, \ - .decrypt = aes ## bits ## impl_c ## _cbc_decrypt, \ - .next_message = nullcipher_next_message, \ - .ssh2_id = "aes" #bits "-cbc", \ - .blksize = 16, \ - .real_keybits = bits, \ - .padded_keybytes = bits/8, \ - .flags = SSH_CIPHER_IS_CBC, \ - .text_name = "AES-" #bits " CBC (" impl_display ")", \ - .extra = &aes ## bits ## impl_c ## _extra, \ - } - -#define AES_SDCTR_VTABLE(impl_c, impl_display, bits) \ - const ssh_cipheralg ssh_aes ## bits ## _sdctr ## impl_c = { \ - .new = aes ## impl_c ## _new, \ - .free = aes ## impl_c ## _free, \ - .setiv = aes ## impl_c ## _setiv_sdctr, \ - .setkey = aes ## impl_c ## _setkey, \ - .encrypt = aes ## bits ## impl_c ## _sdctr, \ - .decrypt = aes ## bits ## impl_c ## _sdctr, \ - .next_message = nullcipher_next_message, \ - .ssh2_id = "aes" #bits "-ctr", \ - .blksize = 16, \ - .real_keybits = bits, \ - .padded_keybytes = bits/8, \ - .flags = 0, \ - .text_name = "AES-" #bits " SDCTR (" impl_display ")", \ - .extra = &aes ## bits ## impl_c ## _extra, \ - } - -#define AES_GCM_VTABLE(impl_c, impl_display, bits) \ - const ssh_cipheralg ssh_aes ## bits ## _gcm ## impl_c = { \ - .new = aes ## impl_c ## _new, \ - .free = aes ## impl_c ## _free, \ - .setiv = aes ## impl_c ## _setiv_gcm, \ - .setkey = aes ## impl_c ## _setkey, \ - .encrypt = aes ## bits ## impl_c ## _gcm, \ - .decrypt = aes ## bits ## impl_c ## _gcm, \ - .encrypt_length = aesgcm_cipher_crypt_length, \ - .decrypt_length = aesgcm_cipher_crypt_length, \ - .next_message = aes ## impl_c ## _next_message_gcm, \ - /* 192-bit AES-GCM is included only so that testcrypt can run \ - * standard test vectors against it. OpenSSH doesn't define a \ - * protocol id for it. So we set its ssh2_id to NULL. */ \ - .ssh2_id = bits==192 ? NULL : "aes" #bits "-gcm@openssh.com", \ - .blksize = 16, \ - .real_keybits = bits, \ - .padded_keybytes = bits/8, \ - .flags = SSH_CIPHER_SEPARATE_LENGTH, \ - .text_name = "AES-" #bits " GCM (" impl_display ")", \ - .required_mac = &ssh2_aesgcm_mac, \ - .extra = &aes ## bits ## impl_c ## _extra, \ - } - -#define AES_ALL_VTABLES(impl_c, impl_display) \ - AES_CBC_VTABLE(impl_c, impl_display, 128); \ - AES_CBC_VTABLE(impl_c, impl_display, 192); \ - AES_CBC_VTABLE(impl_c, impl_display, 256); \ - AES_SDCTR_VTABLE(impl_c, impl_display, 128); \ - AES_SDCTR_VTABLE(impl_c, impl_display, 192); \ - AES_SDCTR_VTABLE(impl_c, impl_display, 256); \ - AES_GCM_VTABLE(impl_c, impl_display, 128); \ - AES_GCM_VTABLE(impl_c, impl_display, 192); \ - AES_GCM_VTABLE(impl_c, impl_display, 256) - -/* - * Macros to repeat a piece of code particular numbers of times that - * correspond to 1 fewer than the number of AES rounds. (Because the - * last round is different.) - */ -#define REP2(x) x x -#define REP4(x) REP2(REP2(x)) -#define REP8(x) REP2(REP4(x)) -#define REP9(x) REP8(x) x -#define REP11(x) REP8(x) REP2(x) x -#define REP13(x) REP8(x) REP4(x) x - -/* - * The round constants used in key schedule expansion. - */ -extern const uint8_t aes_key_setup_round_constants[10]; - -/* - * The largest number of round keys ever needed. - */ -#define MAXROUNDKEYS 15 diff --git a/crypto/aesgcm-clmul.c b/crypto/aesgcm-clmul.c deleted file mode 100644 index cfb72e269..000000000 --- a/crypto/aesgcm-clmul.c +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Implementation of the GCM polynomial hash using the x86 CLMUL - * extension, which provides 64x64->128 polynomial multiplication (or - * 'carry-less', which is what the CL stands for). - * - * Follows the reference implementation in aesgcm-ref-poly.c; see - * there for comments on the underlying technique. Here the comments - * just discuss the x86-specific details. - */ - -#include -#include - -#if defined(__clang__) || defined(__GNUC__) -#include -#define GET_CPU_ID(out) __cpuid(1, (out)[0], (out)[1], (out)[2], (out)[3]) -#else -#define GET_CPU_ID(out) __cpuid(out, 1) -#endif - -#include "ssh.h" -#include "aesgcm.h" - -typedef struct aesgcm_clmul { - AESGCM_COMMON_FIELDS; - __m128i var, acc, mask; - void *ptr_to_free; -} aesgcm_clmul; - -static bool aesgcm_clmul_available(void) -{ - /* - * Determine if CLMUL is available on this CPU. - */ - unsigned int CPUInfo[4]; - GET_CPU_ID(CPUInfo); - return (CPUInfo[2] & (1 << 1)); -} - -/* - * __m128i has to be aligned to 16 bytes, and x86 mallocs may not - * guarantee that, so we must over-allocate to make sure a large - * enough 16-byte region can be found, and ensure the aesgcm_clmul - * struct pointer is at least that well aligned. - */ -#define SPECIAL_ALLOC -static aesgcm_clmul *aesgcm_clmul_alloc(void) -{ - char *p = smalloc(sizeof(aesgcm_clmul) + 15); - uintptr_t ip = (uintptr_t)p; - ip = (ip + 15) & ~15; - aesgcm_clmul *ctx = (aesgcm_clmul *)ip; - memset(ctx, 0, sizeof(aesgcm_clmul)); - ctx->ptr_to_free = p; - return ctx; -} - -#define SPECIAL_FREE -static void aesgcm_clmul_free(aesgcm_clmul *ctx) -{ - void *ptf = ctx->ptr_to_free; - smemclr(ctx, sizeof(*ctx)); - sfree(ptf); -} - -/* Helper function to reverse the 16 bytes in a 128-bit vector */ -static inline __m128i mm_byteswap(__m128i vec) -{ - const __m128i reverse = _mm_set_epi64x( - 0x0001020304050607ULL, 0x08090a0b0c0d0e0fULL); - return _mm_shuffle_epi8(vec, reverse); -} - -/* Helper function to swap the two 64-bit words in a 128-bit vector */ -static inline __m128i mm_wordswap(__m128i vec) -{ - return _mm_shuffle_epi32(vec, 0x4E); -} - -/* Load and store a 128-bit vector in big-endian fashion */ -static inline __m128i mm_load_be(const void *p) -{ - return mm_byteswap(_mm_loadu_si128(p)); -} -static inline void mm_store_be(void *p, __m128i vec) -{ - _mm_storeu_si128(p, mm_byteswap(vec)); -} - -/* - * Key setup is just like in aesgcm-ref-poly.c. There's no point using - * vector registers to accelerate this, because it happens rarely. - */ -static void aesgcm_clmul_setkey_impl(aesgcm_clmul *ctx, - const unsigned char *var) -{ - uint64_t hi = GET_64BIT_MSB_FIRST(var); - uint64_t lo = GET_64BIT_MSB_FIRST(var + 8); - - uint64_t bit = 1 & (hi >> 63); - hi = (hi << 1) ^ (lo >> 63); - lo = (lo << 1) ^ bit; - hi ^= 0xC200000000000000 & -bit; - - ctx->var = _mm_set_epi64x(hi, lo); -} - -static inline void aesgcm_clmul_setup(aesgcm_clmul *ctx, - const unsigned char *mask) -{ - ctx->mask = mm_load_be(mask); - ctx->acc = _mm_set_epi64x(0, 0); -} - -/* - * Folding a coefficient into the accumulator is done by essentially - * the algorithm in aesgcm-ref-poly.c. I don't speak these intrinsics - * all that well, so in the parts where I needed to XOR half of one - * vector into half of another, I did a lot of faffing about with - * masks like 0xFFFFFFFFFFFFFFFF0000000000000000. Very likely this can - * be streamlined by a better x86-speaker than me. Patches welcome. - */ -static inline void aesgcm_clmul_coeff(aesgcm_clmul *ctx, - const unsigned char *coeff) -{ - ctx->acc = _mm_xor_si128(ctx->acc, mm_load_be(coeff)); - - /* Compute ah^al and bh^bl by word-swapping each of a and b and - * XORing with the original. That does more work than necessary - - * you end up with each of the desired values repeated twice - - * but I don't know of a neater way. */ - __m128i aswap = mm_wordswap(ctx->acc); - __m128i vswap = mm_wordswap(ctx->var); - aswap = _mm_xor_si128(ctx->acc, aswap); - vswap = _mm_xor_si128(ctx->var, vswap); - - /* Do the three multiplications required by Karatsuba */ - __m128i md = _mm_clmulepi64_si128(aswap, vswap, 0x00); - __m128i lo = _mm_clmulepi64_si128(ctx->acc, ctx->var, 0x00); - __m128i hi = _mm_clmulepi64_si128(ctx->acc, ctx->var, 0x11); - /* Combine lo and hi into md */ - md = _mm_xor_si128(md, lo); - md = _mm_xor_si128(md, hi); - - /* Now we must XOR the high half of md into the low half of hi, - * and the low half of md into the high half of hi. Simplest thing - * is to swap the words of md (so that each one lines up with the - * register it's going to end up in), and then mask one off in - * each case. */ - md = mm_wordswap(md); - lo = _mm_xor_si128(lo, _mm_and_si128(md, _mm_set_epi64x(~0ULL, 0ULL))); - hi = _mm_xor_si128(hi, _mm_and_si128(md, _mm_set_epi64x(0ULL, ~0ULL))); - - /* The reduction stage is transformed similarly from the version - * in aesgcm-ref-poly.c. */ - __m128i r1 = _mm_clmulepi64_si128(_mm_set_epi64x(0, 0xC200000000000000), - lo, 0x00); - r1 = mm_wordswap(r1); - r1 = _mm_xor_si128(r1, lo); - hi = _mm_xor_si128(hi, _mm_and_si128(r1, _mm_set_epi64x(~0ULL, 0ULL))); - - __m128i r2 = _mm_clmulepi64_si128(_mm_set_epi64x(0, 0xC200000000000000), - r1, 0x10); - hi = _mm_xor_si128(hi, r2); - hi = _mm_xor_si128(hi, _mm_and_si128(r1, _mm_set_epi64x(0ULL, ~0ULL))); - - ctx->acc = hi; -} - -static inline void aesgcm_clmul_output(aesgcm_clmul *ctx, - unsigned char *output) -{ - mm_store_be(output, _mm_xor_si128(ctx->acc, ctx->mask)); - smemclr(&ctx->acc, 16); - smemclr(&ctx->mask, 16); -} - -#define AESGCM_FLAVOUR clmul -#define AESGCM_NAME "CLMUL accelerated" -#include "aesgcm-footer.h" diff --git a/crypto/aesgcm-common.c b/crypto/aesgcm-common.c deleted file mode 100644 index 1e20c87b2..000000000 --- a/crypto/aesgcm-common.c +++ /dev/null @@ -1,8 +0,0 @@ -#include "ssh.h" -#include "aesgcm.h" - -void aesgcm_set_prefix_lengths(ssh2_mac *mac, size_t skip, size_t aad) -{ - const struct aesgcm_extra *extra = mac->vt->extra; - extra->set_prefix_lengths(mac, skip, aad); -} diff --git a/crypto/aesgcm-footer.h b/crypto/aesgcm-footer.h deleted file mode 100644 index fcbaf17ab..000000000 --- a/crypto/aesgcm-footer.h +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Common footer included by every implementation of the AES-GCM MAC. - * - * The difficult part of AES-GCM, which is done differently depending - * on what hardware acceleration is available, is the actual - * evaluation of a polynomial over GF(2^128) whose coefficients are - * 128-bit chunks of data. But preparing those chunks in the first - * place (out of the ciphertext, associated data, and an - * administrative block containing the lengths of both) is done in the - * same way no matter what technique is used for the evaluation, so - * that's centralised into this file, along with as much of the other - * functionality as possible. - * - * This footer file is #included by each implementation, but each one - * will define its own struct type for the state, so that each alloc - * function will test sizeof() a different structure, and similarly - * for free when it zeroes out the state on cleanup. - * - * The functions in the source file may be defined as 'inline' so that - * the functions in here can inline them. The 'coeff' function in - * particular probably should be, because that's called once per - * 16-byte block, so eliminating function call overheads is especially - * useful there. - * - * This footer has the following expectations from the source file - * that #includes it: - * - * - define AESGCM_FLAVOUR to be a fragment of a C identifier that - * will be included in all the function names (both the ones - * defined in the implementation source file and those in here). - * For example purposes below I'll suppose that this is 'foo'. - * - * - define AESGCM_NAME to be a string literal that will be included - * in the display name of the implementation. - * - * - define a typedef 'aesgcm_foo' to be the state structure for the - * implementation, and inside that structure, expand the macro - * AESGCM_COMMON_FIELDS defined in aesgcm.h - * - * - define the following functions: - * - * // Determine whether this implementation is available at run time - * static bool aesgcm_foo_available(void); - * - * // Set up the 'key' of the polynomial part of the MAC, that is, - * // the value at which the polynomial will be evaluated. 'var' is - * // a 16-byte data block in the byte order it comes out of AES. - * static void aesgcm_foo_setkey_impl(aesgcm_foo *ctx, - * const unsigned char *var); - * - * // Set up at the start of evaluating an individual polynomial. - * // 'mask' is the 16-byte data block that will be XORed into the - * // output value of the polynomial, also in AES byte order. This - * // function should store 'mask' in whatever form is most - * // convenient, and initialise an accumulator to zero. - * static void aesgcm_foo_setup(aesgcm_foo *ctx, - * const unsigned char *mask); - * - * // Fold in a coefficient of the polynomial, by means of XORing - * // it into the accumulator and then multiplying the accumulator - * // by the variable passed to setkey_impl() above. - * // - * // 'coeff' points to the 16-byte block of data that the - * // polynomial coefficient will be made out of. - * // - * // You probably want to mark this function 'inline'. - * static void aesgcm_foo_coeff(aesgcm_foo *ctx, - * const unsigned char *coeff); - * - * // Generate the output MAC, by XORing the accumulator's final - * // value with the mask passed to setup() above. - * // - * // 'output' points to a 16-byte region of memory to write the - * // result to. - * static void aesgcm_foo_output(aesgcm_foo *ctx, - * unsigned char *output); - * - * - if allocation of the state structure must be done in a - * non-standard way (e.g. x86 needs this to force greater alignment - * than standard malloc provides), then #define SPECIAL_ALLOC and - * define this additional function: - * - * // Allocate a state structure, zero out its contents, and return it. - * static aesgcm_foo *aesgcm_foo_alloc(void); - * - * - if freeing must also be done in an unusual way, #define - * SPECIAL_FREE and define this function: - * - * // Zero out the state structure to avoid information leaks if the - * // memory is reused, and then free it. - * static void aesgcm_foo_free(aesgcm_foo *ctx); - */ - -#ifndef AESGCM_FLAVOUR -#error AESGCM_FLAVOUR must be defined by any module including this footer -#endif -#ifndef AESGCM_NAME -#error AESGCM_NAME must be defined by any module including this footer -#endif - -#define CONTEXT CAT(aesgcm_, AESGCM_FLAVOUR) -#define PREFIX(name) CAT(CAT(aesgcm_, AESGCM_FLAVOUR), CAT(_, name)) - -#include "aes.h" // for aes_encrypt_ecb_block - -static const char *PREFIX(mac_text_name)(ssh2_mac *mac) -{ - return "AES-GCM (" AESGCM_NAME ")"; -} - -static void PREFIX(mac_next_message)(ssh2_mac *mac) -{ - CONTEXT *ctx = container_of(mac, CONTEXT, mac); - - /* - * Make the mask value for a single MAC instance, by encrypting - * the all-zeroes word using the associated AES instance in its - * ordinary GCM fashion. This consumes the first block of - * keystream (with per-block counter equal to 1), leaving the - * second block of keystream ready to be used on the first block - * of plaintext. - */ - unsigned char buf[16]; - memset(buf, 0, 16); - ssh_cipher_encrypt(ctx->cipher, buf, 16); - PREFIX(setup)(ctx, buf); /* give it to the implementation to store */ - smemclr(buf, sizeof(buf)); -} - -static void PREFIX(mac_setkey)(ssh2_mac *mac, ptrlen key) -{ - CONTEXT *ctx = container_of(mac, CONTEXT, mac); - - /* - * Make the value of the polynomial variable, by encrypting the - * all-zeroes word using the associated AES instance in the - * special ECB mode. This is done via the special AES-specific API - * function encrypt_ecb_block, which doesn't touch the counter - * state at all. - */ - unsigned char var[16]; - memset(var, 0, 16); - aes_encrypt_ecb_block(ctx->cipher, var); - PREFIX(setkey_impl)(ctx, var); - smemclr(var, sizeof(var)); - - PREFIX(mac_next_message)(mac); /* set up mask */ -} - -static void PREFIX(mac_start)(ssh2_mac *mac) -{ - CONTEXT *ctx = container_of(mac, CONTEXT, mac); - - ctx->skipgot = ctx->aadgot = ctx->ciphertextlen = ctx->partlen = 0; -} - -/* - * Handle receiving data via the BinarySink API and turning it into a - * collection of 16-byte blocks to use as polynomial coefficients. - * - * This code is written in a fully general way, which is able to - * handle an arbitrary number of bytes at the start of the data to - * ignore completely (necessary for PuTTY integration), and an - * arbitrary number to treat as associated data, and the rest will be - * regarded as ciphertext. The stream can be interrupted at any byte - * position and resumed later; a partial block will be stored as - * necessary. - * - * At the time of writing this comment, in live use most of that - * generality isn't required: the full data is passed to this function - * in just one call. But there's no guarantee of that staying true in - * future, so we do the full deal here just in case, and the test - * vectors in cryptsuite.py will test it. (And they'll use - * set_prefix_lengths to set up different configurations from the SSH - * usage.) - */ -static void PREFIX(mac_BinarySink_write)( - BinarySink *bs, const void *blkv, size_t len) -{ - CONTEXT *ctx = BinarySink_DOWNCAST(bs, CONTEXT); - const unsigned char *blk = (const unsigned char *)blkv; - - /* - * Skip the prefix sequence number used as implicit extra data in - * SSH MACs. This is not included in the associated data field for - * GCM, because the IV incrementation policy provides its own - * sequence numbering. - */ - if (ctx->skipgot < ctx->skiplen) { - size_t n = ctx->skiplen - ctx->skipgot; - if (n > len) - n = len; - blk += n; - len -= n; - ctx->skipgot += n; - - if (len == 0) - return; - } - - /* - * Read additional authenticated data and fold it in to the MAC. - */ - while (ctx->aadgot < ctx->aadlen) { - size_t n = ctx->aadlen - ctx->aadgot; - if (n > len) - n = len; - - if (ctx->partlen || n < 16) { - /* - * Fold data into the partial block. - */ - if (n > 16 - ctx->partlen) - n = 16 - ctx->partlen; - memcpy(ctx->partblk + ctx->partlen, blk, n); - ctx->partlen += n; - } else if (n >= 16) { - /* - * Consume a whole block of AAD. - */ - PREFIX(coeff)(ctx, blk); - n = 16; - } - blk += n; - len -= n; - ctx->aadgot += n; - - if (ctx->partlen == 16) { - PREFIX(coeff)(ctx, ctx->partblk); - ctx->partlen = 0; - } - - if (ctx->aadgot == ctx->aadlen && ctx->partlen) { - memset(ctx->partblk + ctx->partlen, 0, 16 - ctx->partlen); - PREFIX(coeff)(ctx, ctx->partblk); - ctx->partlen = 0; - } - - if (len == 0) - return; - } - - /* - * Read the main ciphertext and fold it in to the MAC. - */ - while (len > 0) { - size_t n = len; - - if (ctx->partlen || n < 16) { - /* - * Fold data into the partial block. - */ - if (n > 16 - ctx->partlen) - n = 16 - ctx->partlen; - memcpy(ctx->partblk + ctx->partlen, blk, n); - ctx->partlen += n; - } else if (n >= 16) { - /* - * Consume a whole block of ciphertext. - */ - PREFIX(coeff)(ctx, blk); - n = 16; - } - blk += n; - len -= n; - ctx->ciphertextlen += n; - - if (ctx->partlen == 16) { - PREFIX(coeff)(ctx, ctx->partblk); - ctx->partlen = 0; - } - } -} - -static void PREFIX(mac_genresult)(ssh2_mac *mac, unsigned char *output) -{ - CONTEXT *ctx = container_of(mac, CONTEXT, mac); - - /* - * Consume any partial block of ciphertext remaining. - */ - if (ctx->partlen) { - memset(ctx->partblk + ctx->partlen, 0, 16 - ctx->partlen); - PREFIX(coeff)(ctx, ctx->partblk); - } - - /* - * Consume the final block giving the lengths of the AAD and ciphertext. - */ - unsigned char blk[16]; - memset(blk, 0, 16); - PUT_64BIT_MSB_FIRST(blk, ctx->aadlen * 8); - PUT_64BIT_MSB_FIRST(blk + 8, ctx->ciphertextlen * 8); - PREFIX(coeff)(ctx, blk); - - /* - * And call the implementation's output function. - */ - PREFIX(output)(ctx, output); - - smemclr(blk, sizeof(blk)); - smemclr(ctx->partblk, 16); -} - -static ssh2_mac *PREFIX(mac_new)(const ssh2_macalg *alg, ssh_cipher *cipher) -{ - const struct aesgcm_extra *extra = alg->extra; - if (!check_aesgcm_availability(extra)) - return NULL; - -#ifdef SPECIAL_ALLOC - CONTEXT *ctx = PREFIX(alloc)(); -#else - CONTEXT *ctx = snew(CONTEXT); - memset(ctx, 0, sizeof(CONTEXT)); -#endif - - ctx->mac.vt = alg; - ctx->cipher = cipher; - /* Default values for SSH-2, overridable by set_prefix_lengths for - * testcrypt purposes */ - ctx->skiplen = 4; - ctx->aadlen = 4; - BinarySink_INIT(ctx, PREFIX(mac_BinarySink_write)); - BinarySink_DELEGATE_INIT(&ctx->mac, ctx); - return &ctx->mac; -} - -static void PREFIX(set_prefix_lengths)(ssh2_mac *mac, size_t skip, size_t aad) -{ - CONTEXT *ctx = container_of(mac, CONTEXT, mac); - ctx->skiplen = skip; - ctx->aadlen = aad; -} - -static void PREFIX(mac_free)(ssh2_mac *mac) -{ - CONTEXT *ctx = container_of(mac, CONTEXT, mac); -#ifdef SPECIAL_FREE - PREFIX(free)(ctx); -#else - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -#endif -} - -static struct aesgcm_extra_mutable PREFIX(extra_mut); - -static const struct aesgcm_extra PREFIX(extra) = { - .check_available = PREFIX(available), - .mut = &PREFIX(extra_mut), - .set_prefix_lengths = PREFIX(set_prefix_lengths), -}; - -const ssh2_macalg CAT(ssh2_aesgcm_mac_, AESGCM_FLAVOUR) = { - .new = PREFIX(mac_new), - .free = PREFIX(mac_free), - .setkey = PREFIX(mac_setkey), - .start = PREFIX(mac_start), - .genresult = PREFIX(mac_genresult), - .next_message = PREFIX(mac_next_message), - .text_name = PREFIX(mac_text_name), - .name = "", - .etm_name = "", /* Not selectable independently */ - .len = 16, - .keylen = 0, - .extra = &PREFIX(extra), -}; diff --git a/crypto/aesgcm-neon.c b/crypto/aesgcm-neon.c deleted file mode 100644 index 64bc83491..000000000 --- a/crypto/aesgcm-neon.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Implementation of the GCM polynomial hash using Arm NEON vector - * intrinsics, in particular the multiplication operation for - * polynomials over GF(2). - * - * Follows the reference implementation in aesgcm-ref-poly.c; see - * there for comments on the underlying technique. Here the comments - * just discuss the NEON-specific details. - */ - -#include "ssh.h" -#include "aesgcm.h" - -#if USE_ARM64_NEON_H -#include -#else -#include -#endif - -typedef struct aesgcm_neon { - AESGCM_COMMON_FIELDS; - poly128_t var, acc, mask; -} aesgcm_neon; - -static bool aesgcm_neon_available(void) -{ - return platform_pmull_neon_available(); -} - -/* - * The NEON types involved are: - * - * 'poly128_t' is a type that lives in a 128-bit vector register and - * represents a 128-bit polynomial over GF(2) - * - * 'poly64x2_t' is a type that lives in a 128-bit vector register and - * represents a vector of two 64-bit polynomials. These appear as - * intermediate results in some of the helper functions below, but we - * never need to actually have a variable of that type. - * - * 'poly64x1_t' is a type that lives in a 128-bit vector register and - * represents a vector of one 64-bit polynomial. - * - * That is distinct from 'poly64_t', which is a type that lives in - * ordinary scalar registers and is a typedef for an integer type. - * - * Generally here we try to work in terms of poly128_t and 64-bit - * integer types, and let everything else be handled as internal - * details of these helper functions. - */ - -/* Make a poly128_t from two halves */ -static inline poly128_t create_p128(poly64_t hi, poly64_t lo) -{ - return vreinterpretq_p128_p64( - vcombine_p64(vcreate_p64(lo), vcreate_p64(hi))); -} - -/* Retrieve the high and low halves of a poly128_t */ -static inline poly64_t hi_half(poly128_t v) -{ - return vgetq_lane_p64(vreinterpretq_p64_p128(v), 1); -} -static inline poly64_t lo_half(poly128_t v) -{ - return vgetq_lane_p64(vreinterpretq_p64_p128(v), 0); -} - -/* 64x64 -> 128 bit polynomial multiplication, the largest we can do - * in one CPU operation */ -static inline poly128_t pmul(poly64_t v, poly64_t w) -{ - return vmull_p64(v, w); -} - -/* Load and store a poly128_t in the form of big-endian bytes. This - * involves separately swapping the halves of the register and - * reversing the bytes within each half. */ -static inline poly128_t load_p128_be(const void *p) -{ - poly128_t swapped = vreinterpretq_p128_u8(vrev64q_u8(vld1q_u8(p))); - return create_p128(lo_half(swapped), hi_half(swapped)); -} -static inline void store_p128_be(void *p, poly128_t v) -{ - poly128_t swapped = create_p128(lo_half(v), hi_half(v)); - vst1q_u8(p, vrev64q_u8(vreinterpretq_u8_p128(swapped))); -} - -#if !HAVE_NEON_VADDQ_P128 -static inline poly128_t vaddq_p128(poly128_t a, poly128_t b) -{ - return vreinterpretq_p128_u32(veorq_u32( - vreinterpretq_u32_p128(a), vreinterpretq_u32_p128(b))); -} -#endif - -/* - * Key setup is just like in aesgcm-ref-poly.c. There's no point using - * vector registers to accelerate this, because it happens rarely. - */ -static void aesgcm_neon_setkey_impl(aesgcm_neon *ctx, const unsigned char *var) -{ - uint64_t hi = GET_64BIT_MSB_FIRST(var); - uint64_t lo = GET_64BIT_MSB_FIRST(var + 8); - - uint64_t bit = 1 & (hi >> 63); - hi = (hi << 1) ^ (lo >> 63); - lo = (lo << 1) ^ bit; - hi ^= 0xC200000000000000 & -bit; - - ctx->var = create_p128(hi, lo); -} - -static inline void aesgcm_neon_setup(aesgcm_neon *ctx, - const unsigned char *mask) -{ - ctx->mask = load_p128_be(mask); - ctx->acc = create_p128(0, 0); -} - -/* - * Folding a coefficient into the accumulator is done by exactly the - * algorithm in aesgcm-ref-poly.c, translated line by line. - * - * It's possible that this could be improved by some clever manoeuvres - * that avoid having to break vectors in half and put them together - * again. Patches welcome if anyone has better ideas. - */ -static inline void aesgcm_neon_coeff(aesgcm_neon *ctx, - const unsigned char *coeff) -{ - ctx->acc = vaddq_p128(ctx->acc, load_p128_be(coeff)); - - poly64_t ah = hi_half(ctx->acc), al = lo_half(ctx->acc); - poly64_t bh = hi_half(ctx->var), bl = lo_half(ctx->var); - poly128_t md = pmul(ah ^ al, bh ^ bl); - poly128_t lo = pmul(al, bl); - poly128_t hi = pmul(ah, bh); - md = vaddq_p128(md, vaddq_p128(hi, lo)); - hi = create_p128(hi_half(hi), lo_half(hi) ^ hi_half(md)); - lo = create_p128(hi_half(lo) ^ lo_half(md), lo_half(lo)); - - poly128_t r1 = pmul((poly64_t)0xC200000000000000, lo_half(lo)); - hi = create_p128(hi_half(hi), lo_half(hi) ^ lo_half(lo) ^ hi_half(r1)); - lo = create_p128(hi_half(lo) ^ lo_half(r1), lo_half(lo)); - - poly128_t r2 = pmul((poly64_t)0xC200000000000000, hi_half(lo)); - hi = vaddq_p128(hi, r2); - hi = create_p128(hi_half(hi) ^ hi_half(lo), lo_half(hi)); - - ctx->acc = hi; -} - -static inline void aesgcm_neon_output(aesgcm_neon *ctx, unsigned char *output) -{ - store_p128_be(output, vaddq_p128(ctx->acc, ctx->mask)); - ctx->acc = create_p128(0, 0); - ctx->mask = create_p128(0, 0); -} - -#define AESGCM_FLAVOUR neon -#define AESGCM_NAME "NEON accelerated" -#include "aesgcm-footer.h" diff --git a/crypto/aesgcm-ref-poly.c b/crypto/aesgcm-ref-poly.c deleted file mode 100644 index f6ca0fa55..000000000 --- a/crypto/aesgcm-ref-poly.c +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Implementation of the GCM polynomial hash in pure software, but - * based on a primitive that performs 64x64->128 bit polynomial - * multiplication over GF(2). - * - * This implementation is technically correct (should even be - * side-channel safe as far as I can see), but it's hopelessly slow, - * so no live SSH connection should ever use it. Therefore, it's - * deliberately not included in the lists in aesgcm-select.c. For pure - * software GCM in live use, you want aesgcm-sw.c, and that's what the - * selection system will choose. - * - * However, this implementation _is_ made available to testcrypt, so - * all the GCM tests in cryptsuite.py are run over this as well as the - * other implementations. - * - * The reason why this code exists at all is to act as a reference for - * GCM implementations that use a CPU-specific polynomial multiply - * intrinsic or asm statement. This version will run on whatever - * platform you're trying to port to, and will generate all the same - * intermediate results you expect the CPU-specific one to go through. - * So you can insert parallel diagnostics in this version and in your - * new version, to see where the two diverge. - * - * Also, this version is a good place to put long comments explaining - * the entire strategy of this implementation and its rationale. That - * avoids those comments having to be duplicated in the multiple - * platform-specific implementations, which can focus on commenting - * the way the local platform's idioms match up to this version, and - * refer to this file for the explanation of the underlying technique. - */ - -#include "ssh.h" -#include "aesgcm.h" - -/* - * Store a 128-bit value in the most convenient form standard C will - * let us, namely two uint64_t giving its most and least significant - * halves. - */ -typedef struct { - uint64_t hi, lo; -} value128_t; - -typedef struct aesgcm_ref_poly { - AESGCM_COMMON_FIELDS; - - /* - * The state of our GCM implementation is represented entirely by - * three 128-bit values: - */ - - /* - * The value at which we're evaluating the polynomial. The GCM - * spec calls this 'H'. It's defined once at GCM key setup time, - * by encrypting the all-zeroes value with our block cipher. - */ - value128_t var; - - /* - * Accumulator containing the result of evaluating the polynomial - * so far. - */ - value128_t acc; - - /* - * The mask value that is XORed into the final value of 'acc' to - * produce the output MAC. This is different for every MAC - * generated, because its purpose is to ensure that no information - * gathered from a legal MAC can be used to help the forgery of - * another one, and that comparing two legal MACs gives you no - * useful information about the text they cover, because in each - * case, the masks are different and pseudorandom. - */ - value128_t mask; -} aesgcm_ref_poly; - -static bool aesgcm_ref_poly_available(void) -{ - return true; /* pure software implementation, always available */ -} - -/* - * Primitive function that takes two uint64_t values representing - * polynomials, multiplies them, and returns a value128_t struct - * containing the full product. - * - * Because the input polynomials have maximum degree 63, the output - * has max degree 63+63 = 127, not 128. As a result, the topmost bit - * of the output is always zero. - * - * The inside of this function is implemented in the simplest way, - * with no attention paid to performance. The important feature of - * this implementation is not what's _inside_ this function, but - * what's _outside_ it: aesgcm_ref_poly_coeff() tries to minimise the - * number of these operations. - */ -static value128_t pmul(uint64_t x, uint64_t y) -{ - value128_t r; - r.hi = r.lo = 0; - - uint64_t bit = 1 & y; - r.lo ^= x & -bit; - - for (unsigned i = 1; i < 64; i++) { - bit = 1 & (y >> i); - uint64_t z = x & -bit; - r.lo ^= z << i; - r.hi ^= z >> (64-i); - } - - return r; -} - -/* - * OK, I promised a long comment explaining what's going on in this - * implementation, and now it's time. - * - * The way AES-GCM _itself_ is defined by its own spec, its finite - * field consists of polynomials over GF(2), constrained to be 128 - * bits long by reducing them modulo P = x^128 + x^7 + x^2 + x + 1. - * Using the usual binary representation in which bit i is the - * coefficient of x^i, that's 0x100000000000000000000000000000087. - * - * That is, whenever you multiply two polynomials and find a term - * x^128, you can replace it with x^7+x^2+x+1. More generally, - * x^(128+n) can be replaced with x^(7+n)+x^(2+n)+x^(1+n)+x^n. In - * binary terms, a 1 bit at the 128th position or above is replaced by - * 0x87 exactly 128 bits further down. - * - * So you'd think that multiplying two 128-bit polynomials mod P would - * be a matter of generating their full 256-bit product in the form of - * four words HI:HU:LU:LO, and then reducing it mod P by a two-stage - * process of computing HI * 0x87 and XORing it into HU:LU, then - * computing HU * 0x87 and XORing it into LU:LO. - * - * But it's not! - * - * The reason why not is because when AES-GCM is applied to SSH, - * somehow the _bit_ order got reversed. A 16-byte block of data in - * memory is converted into a polynomial by regarding bit 7 of the - * first byte as the constant term, bit 0 of the first byte as the x^7 - * coefficient, ..., bit 0 of the last byte as the x^127 coefficient. - * So if we load that 16-byte block as a big-endian 128-bit integer, - * we end up with it representing a polynomial back to front, with the - * constant term at the top and the x^127 bit at the bottom. - * - * Well, that _shouldn't_ be a problem, right? The nice thing about - * polynomial multiplication is that it's essentially reversible. If - * you reverse the order of the coefficients of two polynomials, then - * the product of the reversed polys is exactly the reversal of the - * product of the original ones. So we bit-reverse our modulo - * polynomial to get 0x1c2000000000000000000000000000001, and we just - * pretend we're working mod that instead. - * - * And that is basically what we're about to do. But there's one - * complication, that arises from the semantics of the polynomial - * multiplication function we're using as our primitive operation. - * - * That function multiplies two polynomials of degree at most 63, to - * give one with degree at most 127. So it returns a 128-bit integer - * whose low bit is the constant term, and its very highest bit is 0, - * and its _next_ highest bit is the product of the high bits of the - * two inputs. - * - * That operation is _not_ symmetric in bit-reversal. If you give it - * the 64-bit-wise reversals of two polynomials P,Q, then its output - * is not the 128-bit-wise reversal of their product PQ, because that - * would require the constant term of PQ to appear in bit 127 of the - * output, and in fact it appears in bit 126. So in fact, what we get - * is offset by one bit from where we'd like it: it's the bit-reversal - * of PQx, not of PQ. - * - * There's more than one way we could fix this. One approach would be - * to work around this off-by-one error by taking the 128-bit output - * of pmul() and shifting it left by a bit. Then it _is_ the bitwise - * reversal of the 128-bit value we'd have wanted to get, and we could - * implement the exact algorithm described above, in fully - * bit-reversed form. - * - * But a 128-bit left shift is not a trivial operation in the vector - * architectures that this code is acting as a reference for. So we'd - * prefer to find a fix that doesn't need compensation during the - * actual per-block multiplication step. - * - * If we did the obvious thing anyway - compute the unshifted 128-bit - * product representing the bit-reversal of PQx, and reduce it mod - * 0x1c2000000000000000000000000000001 - then we'd get a result which - * is exactly what we want, except that it's got a factor of x in it - * that we need to get rid of. The obvious answer is to divide by x - * (which is legal and safe, since mod P, x is invertible). - * - * Dividing a 128-bit polynomial by x is easy in principle. Shift left - * (because we're still bit-reversed), and if that shifted a 1 bit off - * the top, XOR 0xc2000000000000000000000000000001 into the remaining - * 128 bits. - * - * But we're back to having that expensive left shift. What can we do - * about that? - * - * Happily, one of the two input values to our per-block multiply - * operation is fixed! It's given to us at key setup stage, and never - * changed until the next rekey. So if at key setup time we do this - * left shift business _once_, replacing the logical value Q with Q/x, - * then that exactly cancels out the unwanted factor of x that shows - * up in our multiply operation. And now it doesn't matter that it's - * expensive (in the sense of 'a few more instructions than you'd - * like'), because it only happens once per SSH key exchange, not once - * per 16 bytes of data transferred. - */ - -static void aesgcm_ref_poly_setkey_impl(aesgcm_ref_poly *ctx, - const unsigned char *var) -{ - /* - * Key setup function. We copy the provided 16-byte 'var' - * value into our polynomial. But, as discussed above, we also - * need to divide it by x. - */ - - ctx->var.hi = GET_64BIT_MSB_FIRST(var); - ctx->var.lo = GET_64BIT_MSB_FIRST(var + 8); - - uint64_t bit = 1 & (ctx->var.hi >> 63); - ctx->var.hi = (ctx->var.hi << 1) ^ (ctx->var.lo >> 63); - ctx->var.lo = (ctx->var.lo << 1) ^ bit; - ctx->var.hi ^= 0xC200000000000000 & -bit; -} - -static inline void aesgcm_ref_poly_setup(aesgcm_ref_poly *ctx, - const unsigned char *mask) -{ - /* - * Set up to start evaluating a particular MAC. Copy in the mask - * value for this packet, and initialise acc to zero. - */ - - ctx->mask.hi = GET_64BIT_MSB_FIRST(mask); - ctx->mask.lo = GET_64BIT_MSB_FIRST(mask + 8); - ctx->acc.hi = ctx->acc.lo = 0; -} - -static inline void aesgcm_ref_poly_coeff(aesgcm_ref_poly *ctx, - const unsigned char *coeff) -{ - /* - * One step of Horner's-rule polynomial evaluation (with each - * coefficient of the polynomial being an element of GF(2^128), - * itself composed of polynomials over GF(2) mod P). - * - * We take our accumulator value, add the incoming coefficient - * (which means XOR, by GF(2) rules), and multiply by x (that is, - * 'var'). - */ - - /* - * The addition first, which is easy. - */ - ctx->acc.hi ^= GET_64BIT_MSB_FIRST(coeff); - ctx->acc.lo ^= GET_64BIT_MSB_FIRST(coeff + 8); - - /* - * First, create the 256-bit product of the two 128-bit - * polynomials over GF(2) stored in ctx->acc and ctx->var. - * - * The obvious way to do this is by four smaller multiplications - * of 64x64 -> 128 bits. But we can do better using a single - * iteration of the Karatsuba technique, which is actually more - * convenient in polynomials over GF(2) than it is in integers, - * because there aren't all those awkward carries complicating - * things. - * - * Letting B denote x^64, and imagining our two inputs are split - * up into 64-bit chunks ah,al,bh,bl, the product we want is - * - * (ah B + al) (bh B + bl) - * = (ah bh) B^2 + (al bh + ah bl) B + (al bl) - * - * which looks like four smaller multiplications of each of ah,al - * with each of bh,bl. But Karatsuba's trick is to first compute - * - * (ah + al) (bh + bl) - * = ah bh + al bh + ah bl + al bl - * - * and then subtract the terms (ah bh) and (al bl), which we had - * to compute anyway, to get the middle two terms (al bh + ah bl) - * which are our coefficient of B. - * - * This involves more bookkeeping instructions like XORs, but with - * any luck those are faster than the main multiplication. - */ - uint64_t ah = ctx->acc.hi, al = ctx->acc.lo; - uint64_t bh = ctx->var.hi, bl = ctx->var.lo; - /* Compute the outer two terms */ - value128_t lo = pmul(al, bl); - value128_t hi = pmul(ah, bh); - /* Compute the trick product (ah+al)(bh+bl) */ - value128_t md = pmul(ah ^ al, bh ^ bl); - /* Subtract off the outer two terms to get md = al bh + ah bl */ - md.hi ^= lo.hi ^ hi.hi; - md.lo ^= lo.lo ^ hi.lo; - /* And add that into the 256-bit value given by hi * x^128 + lo */ - lo.hi ^= md.lo; - hi.lo ^= md.hi; - - /* - * OK. Now hi and lo together make up the 256-bit full product. - * Now reduce it mod the reversal of the GCM modulus polynomial. - * As discussed above, that's 0x1c2000000000000000000000000000001. - * - * We want the _topmost_ 128 bits of this, because we're working - * in a bit-reversed world. So what we fundamentally want to do is - * to take our 256-bit product, and add to it the product of its - * low 128 bits with 0x1c2000000000000000000000000000001. Then the - * top 128 bits will be the output we want. - * - * Since there's no carrying in this arithmetic, it's enough to - * discard the 1 bit at the bottom of that, because it won't - * affect anything in the half we're keeping. So it's enough to - * add 0x1c2000000000000000000000000000000 * lo to (hi:lo). - * - * We can only work with 64 bits at a time, so the first thing we - * do is to break that up: - * - * - add 0x1c200000000000000 * lo.lo to (hi.lo : lo.hi) - * - add 0x1c200000000000000 * lo.hi to (hi.hi : hi.lo) - * - * But there's still a problem: 0x1c200000000000000 is just too - * _big_ to fit in 64 bits. So we have to break it up into the low - * 64 bits 0xc200000000000000, and its leading 1. So each of those - * steps of the form 'add 0x1c200000000000000 * x to y:z' becomes - * 'add 0xc200000000000000 * x to y:z' followed by 'add x to y', - * the latter step dealing with the leading 1. - */ - - /* First step, adding to the middle two words of our number. After - * this the lowest word (in lo.lo) is ignored. */ - value128_t r1 = pmul(0xC200000000000000, lo.lo); - hi.lo ^= r1.hi ^ lo.lo; - lo.hi ^= r1.lo; - - /* Second of those steps, adding to the top two words, and - * discarding lo.hi. */ - value128_t r2 = pmul(0xC200000000000000, lo.hi); - hi.hi ^= r2.hi ^ lo.hi; - hi.lo ^= r2.lo; - - /* Now 'hi' is precisely what we have left. */ - ctx->acc = hi; -} - -static inline void aesgcm_ref_poly_output(aesgcm_ref_poly *ctx, - unsigned char *output) -{ - PUT_64BIT_MSB_FIRST(output, ctx->acc.hi ^ ctx->mask.hi); - PUT_64BIT_MSB_FIRST(output + 8, ctx->acc.lo ^ ctx->mask.lo); - smemclr(&ctx->acc, 16); - smemclr(&ctx->mask, 16); -} - -#define AESGCM_FLAVOUR ref_poly -#define AESGCM_NAME "reference polynomial-based implementation" -#include "aesgcm-footer.h" diff --git a/crypto/aesgcm-select.c b/crypto/aesgcm-select.c deleted file mode 100644 index eefe71488..000000000 --- a/crypto/aesgcm-select.c +++ /dev/null @@ -1,38 +0,0 @@ -#include "ssh.h" -#include "aesgcm.h" - -static ssh2_mac *aesgcm_mac_selector_new(const ssh2_macalg *alg, - ssh_cipher *cipher) -{ - static const ssh2_macalg *const real_algs[] = { -#if HAVE_CLMUL - &ssh2_aesgcm_mac_clmul, -#endif -#if HAVE_NEON_PMULL - &ssh2_aesgcm_mac_neon, -#endif - &ssh2_aesgcm_mac_sw, - NULL, - }; - - for (size_t i = 0; real_algs[i]; i++) { - const ssh2_macalg *alg = real_algs[i]; - const struct aesgcm_extra *alg_extra = - (const struct aesgcm_extra *)alg->extra; - if (check_aesgcm_availability(alg_extra)) - return ssh2_mac_new(alg, cipher); - } - - /* We should never reach the NULL at the end of the list, because - * the last non-NULL entry should be software-only GCM, which is - * always available. */ - unreachable("aesgcm_select ran off the end of its list"); -} - -const ssh2_macalg ssh2_aesgcm_mac = { - .new = aesgcm_mac_selector_new, - .name = "", - .etm_name = "", /* Not selectable independently */ - .len = 16, - .keylen = 0, -}; diff --git a/crypto/aesgcm-sw.c b/crypto/aesgcm-sw.c deleted file mode 100644 index f322ae308..000000000 --- a/crypto/aesgcm-sw.c +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Implementation of the GCM polynomial hash in pure software. - * - * I don't know of a faster way to do this in a side-channel safe - * manner than by precomputing a giant table and iterating over the - * whole thing. - * - * The original GCM reference suggests that you precompute the effects - * of multiplying a 128-bit value by the fixed key, in the form of a - * table indexed by some number of bits of the input value, so that - * you end up computing something of the form - * - * table1[x & 0xFF] ^ table2[(x>>8) & 0xFF] ^ ... ^ table15[(x>>120) & 0xFF] - * - * But that was obviously written before cache and timing leaks were - * known about. What's a time-safe approach? - * - * Well, the above technique isn't fixed to 8 bits of input per table. - * You could trade off the number of tables against the size of each - * table. At one extreme of this tradeoff, you have 128 tables each - * indexed by a single input bit - which is to say, you have 128 - * values, each 128 bits wide, and you XOR together the subset of - * those values corresponding to the input bits, which you can do by - * making a bitmask out of each input bit using standard constant- - * time-coding bit twiddling techniques. - * - * That's pretty unpleasant when GCM is supposed to be a fast - * algorithm, but I don't know of a better approach that meets current - * security standards! Suggestions welcome, if they can get through - * testsc. - */ - -#include "ssh.h" -#include "aesgcm.h" - -/* - * Store a 128-bit value in the most convenient form standard C will - * let us, namely two uint64_t giving its most and least significant - * halves. - */ -typedef struct { - uint64_t hi, lo; -} value128_t; - -typedef struct aesgcm_sw { - AESGCM_COMMON_FIELDS; - - /* Accumulator for the current evaluation, and mask that will be - * XORed in at the end. High */ - value128_t acc, mask; - - /* - * Table of values to XOR in for each bit, representing the effect - * of multiplying by the fixed key. The key itself doesn't need to - * be stored separately, because it's never used. (However, it is - * also the first entry in the table, so if you _do_ need it, - * there it is.) - * - * Table is indexed from the low bit of the input upwards. - */ - value128_t table[128]; -} aesgcm_sw; - -static bool aesgcm_sw_available(void) -{ - return true; /* pure software implementation, always available */ -} - -static void aesgcm_sw_setkey_impl(aesgcm_sw *gcm, const unsigned char *var) -{ - value128_t v; - v.hi = GET_64BIT_MSB_FIRST(var); - v.lo = GET_64BIT_MSB_FIRST(var + 8); - - /* - * Prepare the table. This has to be done in reverse order, so - * that the original value of the variable corresponds to - * table[127], because AES-GCM works in the bit-reversal of its - * logical specification so that's where the logical constant term - * lives. (See more detailed comment in aesgcm-ref-poly.c.) - */ - for (size_t i = 0; i < 128; i++) { - gcm->table[127 - i] = v; - - /* Multiply v by x, which means shifting right (bit reversal - * again) and then adding 0xE1 at the top if we shifted a 1 out. */ - uint64_t lobit = v.lo & 1; - v.lo = (v.lo >> 1) ^ (v.hi << 63); - v.hi = (v.hi >> 1) ^ (0xE100000000000000ULL & -lobit); - } -} - -static inline void aesgcm_sw_setup(aesgcm_sw *gcm, const unsigned char *mask) -{ - gcm->mask.hi = GET_64BIT_MSB_FIRST(mask); - gcm->mask.lo = GET_64BIT_MSB_FIRST(mask + 8); - gcm->acc.hi = gcm->acc.lo = 0; -} - -static inline void aesgcm_sw_coeff(aesgcm_sw *gcm, const unsigned char *coeff) -{ - /* XOR in the new coefficient */ - gcm->acc.hi ^= GET_64BIT_MSB_FIRST(coeff); - gcm->acc.lo ^= GET_64BIT_MSB_FIRST(coeff + 8); - - /* And now just loop over the bits of acc, making up a new value - * by XORing together the entries of 'table' corresponding to set - * bits. */ - - value128_t out; - out.lo = out.hi = 0; - - const value128_t *tableptr = gcm->table; - - for (size_t i = 0; i < 64; i++) { - uint64_t bit = 1 & gcm->acc.lo; - gcm->acc.lo >>= 1; - uint64_t mask = -bit; - out.hi ^= mask & tableptr->hi; - out.lo ^= mask & tableptr->lo; - tableptr++; - } - for (size_t i = 0; i < 64; i++) { - uint64_t bit = 1 & gcm->acc.hi; - gcm->acc.hi >>= 1; - uint64_t mask = -bit; - out.hi ^= mask & tableptr->hi; - out.lo ^= mask & tableptr->lo; - tableptr++; - } - - gcm->acc = out; -} - -static inline void aesgcm_sw_output(aesgcm_sw *gcm, unsigned char *output) -{ - PUT_64BIT_MSB_FIRST(output, gcm->acc.hi ^ gcm->mask.hi); - PUT_64BIT_MSB_FIRST(output + 8, gcm->acc.lo ^ gcm->mask.lo); - smemclr(&gcm->acc, 16); - smemclr(&gcm->mask, 16); -} - -#define AESGCM_FLAVOUR sw -#define AESGCM_NAME "unaccelerated" -#include "aesgcm-footer.h" diff --git a/crypto/aesgcm.h b/crypto/aesgcm.h deleted file mode 100644 index 48077004c..000000000 --- a/crypto/aesgcm.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Common parts of the state structure for AESGCM MAC implementations. - */ -#define AESGCM_COMMON_FIELDS \ - ssh_cipher *cipher; \ - unsigned char partblk[16]; \ - size_t skiplen, aadlen, ciphertextlen; \ - size_t skipgot, aadgot, partlen; \ - BinarySink_IMPLEMENTATION; \ - ssh2_mac mac - -/* - * The 'extra' structure is used to include information about how to - * check if a given implementation is available at run time, and - * whether we've already checked. - */ -struct aesgcm_extra_mutable; -struct aesgcm_extra { - /* Function to check availability. Might be expensive, so we don't - * want to call it more than once. */ - bool (*check_available)(void); - - /* Point to a writable substructure. */ - struct aesgcm_extra_mutable *mut; - - /* - * Extra API function specific to this MAC type that allows - * testcrypt to set more general lengths for skiplen and aadlen. - */ - void (*set_prefix_lengths)(ssh2_mac *mac, size_t skip, size_t aad); -}; -struct aesgcm_extra_mutable { - bool checked_availability; - bool is_available; -}; -static inline bool check_aesgcm_availability(const struct aesgcm_extra *extra) -{ - if (!extra->mut->checked_availability) { - extra->mut->is_available = extra->check_available(); - extra->mut->checked_availability = true; - } - - return extra->mut->is_available; -} diff --git a/crypto/arcfour.c b/crypto/arcfour.c deleted file mode 100644 index 87d590228..000000000 --- a/crypto/arcfour.c +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Arcfour (RC4) implementation for PuTTY. - * - * Coded from Schneier. - */ - -#include -#include "ssh.h" - -typedef struct { - unsigned char i, j, s[256]; - ssh_cipher ciph; -} ArcfourContext; - -static void arcfour_block(void *handle, void *vblk, int len) -{ - unsigned char *blk = (unsigned char *)vblk; - 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 ssh_cipher *arcfour_new(const ssh_cipheralg *alg) -{ - ArcfourContext *ctx = snew(ArcfourContext); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void arcfour_free(ssh_cipher *cipher) -{ - ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -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 arcfour_ssh2_setiv(ssh_cipher *cipher, const void *key) -{ - /* As a pure stream cipher, Arcfour has no IV separate from the key */ -} - -static void arcfour_ssh2_setkey(ssh_cipher *cipher, const void *key) -{ - ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph); - arcfour_setkey(ctx, key, ctx->ciph.vt->padded_keybytes); - arcfour_stir(ctx); -} - -static void arcfour_ssh2_block(ssh_cipher *cipher, void *blk, int len) -{ - ArcfourContext *ctx = container_of(cipher, ArcfourContext, ciph); - arcfour_block(ctx, blk, len); -} - -const ssh_cipheralg ssh_arcfour128_ssh2 = { - .new = arcfour_new, - .free = arcfour_free, - .setiv = arcfour_ssh2_setiv, - .setkey = arcfour_ssh2_setkey, - .encrypt = arcfour_ssh2_block, - .decrypt = arcfour_ssh2_block, - .next_message = nullcipher_next_message, - .ssh2_id = "arcfour128", - .blksize = 1, - .real_keybits = 128, - .padded_keybytes = 16, - .flags = 0, - .text_name = "Arcfour-128", -}; - -const ssh_cipheralg ssh_arcfour256_ssh2 = { - .new = arcfour_new, - .free = arcfour_free, - .setiv = arcfour_ssh2_setiv, - .setkey = arcfour_ssh2_setkey, - .encrypt = arcfour_ssh2_block, - .decrypt = arcfour_ssh2_block, - .next_message = nullcipher_next_message, - .ssh2_id = "arcfour256", - .blksize = 1, - .real_keybits = 256, - .padded_keybytes = 32, - .flags = 0, - .text_name = "Arcfour-256", -}; - -static const ssh_cipheralg *const arcfour_list[] = { - &ssh_arcfour256_ssh2, - &ssh_arcfour128_ssh2, -}; - -const ssh2_ciphers ssh2_arcfour = { lenof(arcfour_list), arcfour_list }; diff --git a/crypto/argon2.c b/crypto/argon2.c deleted file mode 100644 index 99bcfa17d..000000000 --- a/crypto/argon2.c +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Implementation of the Argon2 password hash function. - * - * My sources for the algorithm description and test vectors (the latter in - * test/cryptsuite.py) were the reference implementation on Github, and also - * the Internet-Draft description: - * - * https://github.com/P-H-C/phc-winner-argon2 - * https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-argon2-13 - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "marshal.h" - -/* ---------------------------------------------------------------------- - * Argon2 uses data marshalling rules similar to SSH but with 32-bit integers - * stored little-endian. Start with some local BinarySink routines for storing - * a uint32 and a string in that fashion. - */ - -static void BinarySink_put_uint32_le(BinarySink *bs, unsigned long val) -{ - unsigned char data[4]; - PUT_32BIT_LSB_FIRST(data, val); - bs->write(bs, data, sizeof(data)); -} - -static void BinarySink_put_stringpl_le(BinarySink *bs, ptrlen pl) -{ - /* Check that the string length fits in a uint32, without doing a - * potentially implementation-defined shift of more than 31 bits */ - assert((pl.len >> 31) < 2); - - BinarySink_put_uint32_le(bs, pl.len); - bs->write(bs, pl.ptr, pl.len); -} - -#define put_uint32_le(bs, val) \ - BinarySink_put_uint32_le(BinarySink_UPCAST(bs), val) -#define put_stringpl_le(bs, val) \ - BinarySink_put_stringpl_le(BinarySink_UPCAST(bs), val) - -/* ---------------------------------------------------------------------- - * Argon2 defines a hash-function family that's an extension of BLAKE2b to - * generate longer output digests, by repeatedly outputting half of a BLAKE2 - * hash output and then re-hashing the whole thing until there are 64 or fewer - * bytes left to output. The spec calls this H' (a variant of the original - * hash it calls H, which is the unmodified BLAKE2b). - */ - -static ssh_hash *hprime_new(unsigned length) -{ - ssh_hash *h = blake2b_new_general(length > 64 ? 64 : length); - put_uint32_le(h, length); - return h; -} - -static void hprime_final(ssh_hash *h, unsigned length, void *vout) -{ - uint8_t *out = (uint8_t *)vout; - - while (length > 64) { - uint8_t hashbuf[64]; - ssh_hash_final(h, hashbuf); - - memcpy(out, hashbuf, 32); - out += 32; - length -= 32; - - h = blake2b_new_general(length > 64 ? 64 : length); - put_data(h, hashbuf, 64); - - smemclr(hashbuf, sizeof(hashbuf)); - } - - ssh_hash_final(h, out); -} - -/* Externally visible entry point for the long hash function. This is only - * used by testcrypt, so it would be overkill to set it up like a proper - * ssh_hash. */ -strbuf *argon2_long_hash(unsigned length, ptrlen data) -{ - ssh_hash *h = hprime_new(length); - put_datapl(h, data); - strbuf *out = strbuf_new(); - hprime_final(h, length, strbuf_append(out, length)); - return out; -} - -/* ---------------------------------------------------------------------- - * Argon2's own mixing function G, which operates on 1Kb blocks of data. - * - * The definition of G in the spec takes two 1Kb blocks as input and produces - * a 1Kb output block. The first thing that happens to the input blocks is - * that they get XORed together, and then only the XOR output is used, so you - * could perfectly well regard G as a 1Kb->1Kb function. - */ - -static inline uint64_t ror(uint64_t x, unsigned rotation) -{ - unsigned lshift = 63 & -rotation, rshift = 63 & rotation; - return (x << lshift) | (x >> rshift); -} - -static inline uint64_t trunc32(uint64_t x) -{ - return x & 0xFFFFFFFF; -} - -/* Internal function similar to the BLAKE2b round, which mixes up four 64-bit - * words */ -static inline void GB(uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d) -{ - *a += *b + 2 * trunc32(*a) * trunc32(*b); - *d = ror(*d ^ *a, 32); - *c += *d + 2 * trunc32(*c) * trunc32(*d); - *b = ror(*b ^ *c, 24); - *a += *b + 2 * trunc32(*a) * trunc32(*b); - *d = ror(*d ^ *a, 16); - *c += *d + 2 * trunc32(*c) * trunc32(*d); - *b = ror(*b ^ *c, 63); -} - -/* Higher-level internal function which mixes up sixteen 64-bit words. This is - * applied to different subsets of the 128 words in a kilobyte block, and the - * API here is designed to make it easy to apply in the circumstances the spec - * requires. In every call, the sixteen words form eight pairs adjacent in - * memory, whose addresses are in arithmetic progression. So the 16 input - * words are in[0], in[1], in[instep], in[instep+1], ..., in[7*instep], - * in[7*instep+1], and the 16 output words similarly. */ -static inline void P(uint64_t *out, unsigned outstep, - uint64_t *in, unsigned instep) -{ - for (unsigned i = 0; i < 8; i++) { - out[i*outstep] = in[i*instep]; - out[i*outstep+1] = in[i*instep+1]; - } - - GB(out+0*outstep+0, out+2*outstep+0, out+4*outstep+0, out+6*outstep+0); - GB(out+0*outstep+1, out+2*outstep+1, out+4*outstep+1, out+6*outstep+1); - GB(out+1*outstep+0, out+3*outstep+0, out+5*outstep+0, out+7*outstep+0); - GB(out+1*outstep+1, out+3*outstep+1, out+5*outstep+1, out+7*outstep+1); - - GB(out+0*outstep+0, out+2*outstep+1, out+5*outstep+0, out+7*outstep+1); - GB(out+0*outstep+1, out+3*outstep+0, out+5*outstep+1, out+6*outstep+0); - GB(out+1*outstep+0, out+3*outstep+1, out+4*outstep+0, out+6*outstep+1); - GB(out+1*outstep+1, out+2*outstep+0, out+4*outstep+1, out+7*outstep+0); -} - -/* The full G function, taking input blocks X and Y. The result of G is most - * often XORed into an existing output block, so this API is designed with - * that in mind: the mixing function's output is always XORed into whatever - * 1Kb of data is already at 'out'. */ -static void G_xor(uint8_t *out, const uint8_t *X, const uint8_t *Y) -{ - uint64_t R[128], Q[128], Z[128]; - - for (unsigned i = 0; i < 128; i++) - R[i] = GET_64BIT_LSB_FIRST(X + 8*i) ^ GET_64BIT_LSB_FIRST(Y + 8*i); - - for (unsigned i = 0; i < 8; i++) - P(Q+16*i, 2, R+16*i, 2); - - for (unsigned i = 0; i < 8; i++) - P(Z+2*i, 16, Q+2*i, 16); - - for (unsigned i = 0; i < 128; i++) - PUT_64BIT_LSB_FIRST(out + 8*i, - GET_64BIT_LSB_FIRST(out + 8*i) ^ R[i] ^ Z[i]); - - smemclr(R, sizeof(R)); - smemclr(Q, sizeof(Q)); - smemclr(Z, sizeof(Z)); -} - -/* ---------------------------------------------------------------------- - * The main Argon2 function. - */ - -static void argon2_internal(uint32_t p, uint32_t T, uint32_t m, uint32_t t, - uint32_t y, ptrlen P, ptrlen S, ptrlen K, ptrlen X, - uint8_t *out) -{ - /* - * Start by hashing all the input data together: the four string arguments - * (password P, salt S, optional secret key K, optional associated data - * X), plus all the parameters for the function's memory and time usage. - * - * The output of this hash is the sole input to the subsequent mixing - * step: Argon2 does not preserve any more entropy from the inputs, it - * just makes it extra painful to get the final answer. - */ - uint8_t h0[64]; - { - ssh_hash *h = blake2b_new_general(64); - put_uint32_le(h, p); - put_uint32_le(h, T); - put_uint32_le(h, m); - put_uint32_le(h, t); - put_uint32_le(h, 0x13); /* hash function version number */ - put_uint32_le(h, y); - put_stringpl_le(h, P); - put_stringpl_le(h, S); - put_stringpl_le(h, K); - put_stringpl_le(h, X); - ssh_hash_final(h, h0); - } - - struct blk { uint8_t data[1024]; }; - - /* - * Array of 1Kb blocks. The total size is (approximately) m, the - * caller-specified parameter for how much memory to use; the blocks are - * regarded as a rectangular array of p rows ('lanes') by q columns, where - * p is the 'parallelism' input parameter (the lanes can be processed - * concurrently up to a point) and q is whatever makes the product pq come - * to m. - * - * Additionally, each row is divided into four equal 'segments', which are - * important to the way the algorithm decides which blocks to use as input - * to each step of the function. - * - * The term 'slice' refers to a whole set of vertically aligned segments, - * i.e. slice 0 is the whole left quarter of the array, and slice 3 the - * whole right quarter. - */ - size_t SL = m / (4*p); /* segment length: # of 1Kb blocks in a segment */ - size_t q = 4 * SL; /* width of the array: 4 segments times SL */ - size_t mprime = q * p; /* total size of the array, approximately m */ - - /* Allocate the memory. */ - struct blk *B = snewn(mprime, struct blk); - memset(B, 0, mprime * sizeof(struct blk)); - - /* - * Initial setup: fill the first two full columns of the array with data - * expanded from the starting hash h0. Each block is the result of using - * the long-output hash function H' to hash h0 itself plus the block's - * coordinates in the array. - */ - for (size_t i = 0; i < p; i++) { - ssh_hash *h = hprime_new(1024); - put_data(h, h0, 64); - put_uint32_le(h, 0); - put_uint32_le(h, i); - hprime_final(h, 1024, B[i].data); - } - for (size_t i = 0; i < p; i++) { - ssh_hash *h = hprime_new(1024); - put_data(h, h0, 64); - put_uint32_le(h, 1); - put_uint32_le(h, i); - hprime_final(h, 1024, B[i+p].data); - } - - /* - * Declarations for the main loop. - * - * The basic structure of the main loop is going to involve processing the - * array one whole slice (vertically divided quarter) at a time. Usually - * we'll write a new value into every single block in the slice, except - * that in the initial slice on the first pass, we've already written - * values into the first two columns during the initial setup above. So - * 'jstart' indicates the starting index in each segment we process; it - * starts off as 2 so that we don't overwrite the initial setup, and then - * after the first slice is done, we set it to 0, and it stays there. - * - * d_mode indicates whether we're being data-dependent (true) or - * data-independent (false). In the hybrid Argon2id mode, we start off - * independent, and then once we've mixed things up enough, switch over to - * dependent mode to force long serial chains of computation. - */ - size_t jstart = 2; - bool d_mode = (y == 0); - struct blk out2i, tmp2i, in2i; - - /* Outermost loop: t whole passes from left to right over the array */ - for (size_t pass = 0; pass < t; pass++) { - - /* Within that, we process the array in its four main slices */ - for (unsigned slice = 0; slice < 4; slice++) { - - /* In Argon2id mode, if we're half way through the first pass, - * this is the moment to switch d_mode from false to true */ - if (pass == 0 && slice == 2 && y == 2) - d_mode = true; - - /* Loop over every segment in the slice (i.e. every row). So i is - * the y-coordinate of each block we process. */ - for (size_t i = 0; i < p; i++) { - - /* And within that segment, process the blocks from left to - * right, starting at 'jstart' (usually 0, but 2 in the first - * slice). */ - for (size_t jpre = jstart; jpre < SL; jpre++) { - - /* j is the x-coordinate of each block we process, made up - * of the slice number and the index 'jpre' within the - * segment. */ - size_t j = slice * SL + jpre; - - /* jm1 is j-1 (mod q) */ - uint32_t jm1 = (j == 0 ? q-1 : j-1); - - /* - * Construct two 32-bit pseudorandom integers J1 and J2. - * This is the part of the algorithm that varies between - * the data-dependent and independent modes. - */ - uint32_t J1, J2; - if (d_mode) { - /* - * Data-dependent: grab the first 64 bits of the block - * to the left of this one. - */ - J1 = GET_32BIT_LSB_FIRST(B[i + p * jm1].data); - J2 = GET_32BIT_LSB_FIRST(B[i + p * jm1].data + 4); - } else { - /* - * Data-independent: generate pseudorandom data by - * hashing a sequence of preimage blocks that include - * all our input parameters, plus the coordinates of - * this point in the algorithm (array position and - * pass number) to make all the hash outputs distinct. - * - * The hash we use is G itself, applied twice. So we - * generate 1Kb of data at a time, which is enough for - * 128 (J1,J2) pairs. Hence we only need to do the - * hashing if our index within the segment is a - * multiple of 128, or if we're at the very start of - * the algorithm (in which case we started at 2 rather - * than 0). After that we can just keep picking data - * out of our most recent hash output. - */ - if (jpre == jstart || jpre % 128 == 0) { - /* - * Hash preimage is mostly zeroes, with a - * collection of assorted integer values we had - * anyway. - */ - memset(in2i.data, 0, sizeof(in2i.data)); - PUT_64BIT_LSB_FIRST(in2i.data + 0, pass); - PUT_64BIT_LSB_FIRST(in2i.data + 8, i); - PUT_64BIT_LSB_FIRST(in2i.data + 16, slice); - PUT_64BIT_LSB_FIRST(in2i.data + 24, mprime); - PUT_64BIT_LSB_FIRST(in2i.data + 32, t); - PUT_64BIT_LSB_FIRST(in2i.data + 40, y); - PUT_64BIT_LSB_FIRST(in2i.data + 48, jpre / 128 + 1); - - /* - * Now apply G twice to generate the hash output - * in out2i. - */ - memset(tmp2i.data, 0, sizeof(tmp2i.data)); - G_xor(tmp2i.data, tmp2i.data, in2i.data); - memset(out2i.data, 0, sizeof(out2i.data)); - G_xor(out2i.data, out2i.data, tmp2i.data); - } - - /* - * Extract J1 and J2 from the most recent hash output - * (whether we've just computed it or not). - */ - J1 = GET_32BIT_LSB_FIRST( - out2i.data + 8 * (jpre % 128)); - J2 = GET_32BIT_LSB_FIRST( - out2i.data + 8 * (jpre % 128) + 4); - } - - /* - * Now convert J1 and J2 into the index of an existing - * block of the array to use as input to this step. This - * is fairly fiddly. - * - * The easy part: the y-coordinate of the input block is - * obtained by reducing J2 mod p, except that at the very - * start of the algorithm (processing the first slice on - * the first pass) we simply use the same y-coordinate as - * our output block. - * - * Note that it's safe to use the ordinary % operator - * here, without any concern for timing side channels: in - * data-independent mode J2 is not correlated to any - * secrets, and in data-dependent mode we're going to be - * giving away side-channel data _anyway_ when we use it - * as an array index (and by assumption we don't care, - * because it's already massively randomised from the real - * inputs). - */ - uint32_t index_l = (pass == 0 && slice == 0) ? i : J2 % p; - - /* - * The hard part: which block in this array row do we use? - * - * First, we decide what the possible candidates are. This - * requires some case analysis, and depends on whether the - * array row is the same one we're writing into or not. - * - * If it's not the same row: we can't use any block from - * the current slice (because the segments within a slice - * have to be processable in parallel, so in a concurrent - * implementation those blocks are potentially in the - * process of being overwritten by other threads). But the - * other three slices are fair game, except that in the - * first pass, slices to the right of us won't have had - * any values written into them yet at all. - * - * If it is the same row, we _are_ allowed to use blocks - * from the current slice, but only the ones before our - * current position. - * - * In both cases, we also exclude the individual _column_ - * just to the left of the current one. (The block - * immediately to our left is going to be the _other_ - * input to G, but the spec also says that we avoid that - * column even in a different row.) - * - * All of this means that we end up choosing from a - * cyclically contiguous interval of blocks within this - * lane, but the start and end points require some thought - * to get them right. - */ - - /* Start position is the beginning of the _next_ slice - * (containing data from the previous pass), unless we're - * on pass 0, where the start position has to be 0. */ - uint32_t Wstart = (pass == 0 ? 0 : (slice + 1) % 4 * SL); - - /* End position splits up by cases. */ - uint32_t Wend; - if (index_l == i) { - /* Same lane as output: we can use anything up to (but - * not including) the block immediately left of us. */ - Wend = jm1; - } else { - /* Different lane from output: we can use anything up - * to the previous slice boundary, or one less than - * that if we're at the very left edge of our slice - * right now. */ - Wend = SL * slice; - if (jpre == 0) - Wend = (Wend + q-1) % q; - } - - /* Total number of blocks available to choose from */ - uint32_t Wsize = (Wend + q - Wstart) % q; - - /* Fiddly computation from the spec that chooses from the - * available blocks, in a deliberately non-uniform - * fashion, using J1 as pseudorandom input data. Output is - * zz which is the index within our contiguous interval. */ - uint32_t x = ((uint64_t)J1 * J1) >> 32; - uint32_t y = ((uint64_t)Wsize * x) >> 32; - uint32_t zz = Wsize - 1 - y; - - /* And index_z is the actual x coordinate of the block we - * want. */ - uint32_t index_z = (Wstart + zz) % q; - - /* Phew! Combine that block with the one immediately to - * our left, and XOR over the top of whatever is already - * in our current output block. */ - G_xor(B[i + p * j].data, B[i + p * jm1].data, - B[index_l + p * index_z].data); - } - } - - /* We've finished processing a slice. Reset jstart to 0. It will - * onily _not_ have been 0 if this was pass 0 slice 0, in which - * case it still had its initial value of 2 to avoid the starting - * data. */ - jstart = 0; - } - } - - /* - * The main output is all done. Final output works by taking the XOR of - * all the blocks in the rightmost column of the array, and then using - * that as input to our long hash H'. The output of _that_ is what we - * deliver to the caller. - */ - - struct blk C = B[p * (q-1)]; - for (size_t i = 1; i < p; i++) - memxor(C.data, C.data, B[i + p * (q-1)].data, 1024); - - { - ssh_hash *h = hprime_new(T); - put_data(h, C.data, 1024); - hprime_final(h, T, out); - } - - /* - * Clean up. - */ - smemclr(out2i.data, sizeof(out2i.data)); - smemclr(tmp2i.data, sizeof(tmp2i.data)); - smemclr(in2i.data, sizeof(in2i.data)); - smemclr(C.data, sizeof(C.data)); - smemclr(B, mprime * sizeof(struct blk)); - sfree(B); -} - -/* - * Wrapper function that appends to a strbuf (which sshpubk.c will want). - */ -void argon2(Argon2Flavour flavour, uint32_t mem, uint32_t passes, - uint32_t parallel, uint32_t taglen, - ptrlen P, ptrlen S, ptrlen K, ptrlen X, strbuf *out) -{ - argon2_internal(parallel, taglen, mem, passes, flavour, - P, S, K, X, strbuf_append(out, taglen)); -} - -/* - * Wrapper function which dynamically chooses the number of passes to run in - * order to hit an approximate total amount of CPU time. Writes the result - * into 'passes'. - */ -void argon2_choose_passes( - Argon2Flavour flavour, uint32_t mem, - uint32_t milliseconds, uint32_t *passes, - uint32_t parallel, uint32_t taglen, - ptrlen P, ptrlen S, ptrlen K, ptrlen X, - strbuf *out) -{ - unsigned long desired_time = (TICKSPERSEC * milliseconds) / 1000; - - /* - * We only need the time taken to be approximately right, so we - * scale up the number of passes geometrically, which avoids - * taking O(t^2) time to find a pass count taking time t. - * - * Using the Fibonacci numbers is slightly nicer than the obvious - * approach of powers of 2, because it's still very easy to - * compute, and grows less fast (powers of 1.6 instead of 2), so - * you get just a touch more precision. - */ - uint32_t a = 1, b = 1; - - while (true) { - unsigned long start_time = GETTICKCOUNT(); - argon2(flavour, mem, b, parallel, taglen, P, S, K, X, out); - unsigned long ticks = GETTICKCOUNT() - start_time; - - /* But just in case computers get _too_ fast, we have to cap - * the growth before it gets past the uint32_t upper bound! So - * if computing a+b would overflow, stop here. */ - - if (ticks >= desired_time || a > (uint32_t)~b) { - *passes = b; - return; - } else { - strbuf_clear(out); - - /* Next Fibonacci number: replace (a, b) with (b, a+b) */ - b += a; - a = b - a; - } - } -} diff --git a/crypto/bcrypt.c b/crypto/bcrypt.c deleted file mode 100644 index 1ccd47567..000000000 --- a/crypto/bcrypt.c +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 'bcrypt' password hash function, for PuTTY's import/export of - * OpenSSH encrypted private key files. - * - * This is not really the same as the original bcrypt; OpenSSH has - * modified it in various ways, and of course we have to do the same. - */ - -#include -#include -#include "ssh.h" -#include "blowfish.h" - -static BlowfishContext *bcrypt_setup(const unsigned char *key, int keybytes, - const unsigned char *salt, int saltbytes) -{ - int i; - BlowfishContext *ctx; - - ctx = blowfish_make_context(); - blowfish_initkey(ctx); - blowfish_expandkey(ctx, key, keybytes, salt, saltbytes); - - /* Original bcrypt replaces this fixed loop count with the - * variable cost. OpenSSH instead iterates the whole thing more - * than once if it wants extra rounds. */ - for (i = 0; i < 64; i++) { - blowfish_expandkey(ctx, salt, saltbytes, NULL, 0); - blowfish_expandkey(ctx, key, keybytes, NULL, 0); - } - - return ctx; -} - -static void bcrypt_hash(const unsigned char *key, int keybytes, - const unsigned char *salt, int saltbytes, - unsigned char output[32]) -{ - BlowfishContext *ctx; - int i; - - ctx = bcrypt_setup(key, keybytes, salt, saltbytes); - /* This was quite a nice starting string until it ran into - * little-endian Blowfish :-/ */ - memcpy(output, "cyxOmorhcitawolBhsiftawSanyDetim", 32); - for (i = 0; i < 64; i++) { - blowfish_lsb_encrypt_ecb(output, 32, ctx); - } - blowfish_free_context(ctx); -} - -static void bcrypt_genblock(int counter, - const unsigned char hashed_passphrase[64], - const unsigned char *salt, int saltbytes, - unsigned char output[32]) -{ - unsigned char hashed_salt[64]; - - /* Hash the input salt with the counter value optionally suffixed - * to get our real 32-byte salt */ - ssh_hash *h = ssh_hash_new(&ssh_sha512); - put_data(h, salt, saltbytes); - if (counter) - put_uint32(h, counter); - ssh_hash_final(h, hashed_salt); - - bcrypt_hash(hashed_passphrase, 64, hashed_salt, 64, output); - - smemclr(&hashed_salt, sizeof(hashed_salt)); -} - -void openssh_bcrypt(ptrlen passphrase, ptrlen salt, - int rounds, unsigned char *out, int outbytes) -{ - unsigned char hashed_passphrase[64]; - unsigned char block[32], outblock[32]; - const unsigned char *thissalt; - int thissaltbytes; - int modulus, residue, i, j, round; - - /* Hash the passphrase to get the bcrypt key material */ - hash_simple(&ssh_sha512, passphrase, hashed_passphrase); - - /* We output key bytes in a scattered fashion to meld all output - * key blocks into all parts of the output. To do this, we pick a - * modulus, and we output the key bytes to indices of out[] in the - * following order: first the indices that are multiples of the - * modulus, then the ones congruent to 1 mod modulus, etc. Each of - * those passes consumes exactly one block output from - * bcrypt_genblock, so we must pick a modulus large enough that at - * most 32 bytes are used in the pass. */ - modulus = (outbytes + 31) / 32; - - for (residue = 0; residue < modulus; residue++) { - /* Our output block of data is the XOR of all blocks generated - * by bcrypt in the following loop */ - memset(outblock, 0, sizeof(outblock)); - - thissalt = salt.ptr; - thissaltbytes = salt.len; - for (round = 0; round < rounds; round++) { - bcrypt_genblock(round == 0 ? residue+1 : 0, - hashed_passphrase, - thissalt, thissaltbytes, block); - /* Each subsequent bcrypt call reuses the previous one's - * output as its salt */ - thissalt = block; - thissaltbytes = 32; - - for (i = 0; i < 32; i++) - outblock[i] ^= block[i]; - } - - for (i = residue, j = 0; i < outbytes; i += modulus, j++) - out[i] = outblock[j]; - } - smemclr(&hashed_passphrase, sizeof(hashed_passphrase)); -} diff --git a/crypto/blake2.c b/crypto/blake2.c deleted file mode 100644 index a4d42f210..000000000 --- a/crypto/blake2.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - * BLAKE2 (RFC 7693) implementation for PuTTY. - * - * The BLAKE2 hash family includes BLAKE2s, in which the hash state is - * operated on as a collection of 32-bit integers, and BLAKE2b, based - * on 64-bit integers. At present this code implements BLAKE2b only. - */ - -#include -#include "ssh.h" - -static inline uint64_t ror(uint64_t x, unsigned rotation) -{ - unsigned lshift = 63 & -rotation, rshift = 63 & rotation; - return (x << lshift) | (x >> rshift); -} - -/* RFC 7963 section 2.1 */ -enum { R1 = 32, R2 = 24, R3 = 16, R4 = 63 }; - -/* RFC 7693 section 2.6 */ -static const uint64_t iv[] = { - 0x6a09e667f3bcc908, /* floor(2^64 * frac(sqrt(2))) */ - 0xbb67ae8584caa73b, /* floor(2^64 * frac(sqrt(3))) */ - 0x3c6ef372fe94f82b, /* floor(2^64 * frac(sqrt(5))) */ - 0xa54ff53a5f1d36f1, /* floor(2^64 * frac(sqrt(7))) */ - 0x510e527fade682d1, /* floor(2^64 * frac(sqrt(11))) */ - 0x9b05688c2b3e6c1f, /* floor(2^64 * frac(sqrt(13))) */ - 0x1f83d9abfb41bd6b, /* floor(2^64 * frac(sqrt(17))) */ - 0x5be0cd19137e2179, /* floor(2^64 * frac(sqrt(19))) */ -}; - -/* RFC 7693 section 2.7 */ -static const unsigned char sigma[][16] = { - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, - {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, - { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, - { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, - { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, - {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, - {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, - { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, - {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, - /* This array recycles if you have more than 10 rounds. BLAKE2b - * has 12, so we repeat the first two rows again. */ - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, -}; - -static inline void g_half(uint64_t v[16], unsigned a, unsigned b, unsigned c, - unsigned d, uint64_t x, unsigned r1, unsigned r2) -{ - v[a] += v[b] + x; - v[d] ^= v[a]; - v[d] = ror(v[d], r1); - v[c] += v[d]; - v[b] ^= v[c]; - v[b] = ror(v[b], r2); -} - -static inline void g(uint64_t v[16], unsigned a, unsigned b, unsigned c, - unsigned d, uint64_t x, uint64_t y) -{ - g_half(v, a, b, c, d, x, R1, R2); - g_half(v, a, b, c, d, y, R3, R4); -} - -static inline void f(uint64_t h[8], uint64_t m[16], uint64_t offset_hi, - uint64_t offset_lo, unsigned final) -{ - uint64_t v[16]; - memcpy(v, h, 8 * sizeof(*v)); - memcpy(v + 8, iv, 8 * sizeof(*v)); - v[12] ^= offset_lo; - v[13] ^= offset_hi; - v[14] ^= -(uint64_t)final; - for (unsigned round = 0; round < 12; round++) { - const unsigned char *s = sigma[round]; - g(v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]]); - g(v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]]); - g(v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]]); - g(v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]]); - g(v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]]); - g(v, 1, 6, 11, 12, m[s[10]], m[s[11]]); - g(v, 2, 7, 8, 13, m[s[12]], m[s[13]]); - g(v, 3, 4, 9, 14, m[s[14]], m[s[15]]); - } - for (unsigned i = 0; i < 8; i++) - h[i] ^= v[i] ^ v[i+8]; - smemclr(v, sizeof(v)); -} - -static inline void f_outer(uint64_t h[8], uint8_t blk[128], uint64_t offset_hi, - uint64_t offset_lo, unsigned final) -{ - uint64_t m[16]; - for (unsigned i = 0; i < 16; i++) - m[i] = GET_64BIT_LSB_FIRST(blk + 8*i); - f(h, m, offset_hi, offset_lo, final); - smemclr(m, sizeof(m)); -} - -typedef struct blake2b { - uint64_t h[8]; - unsigned hashlen; - - uint8_t block[128]; - size_t used; - uint64_t lenhi, lenlo; - - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} blake2b; - -static void blake2b_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *blake2b_new_inner(unsigned hashlen) -{ - assert(hashlen <= ssh_blake2b.hlen); - - blake2b *s = snew(blake2b); - s->hash.vt = &ssh_blake2b; - s->hashlen = hashlen; - BinarySink_INIT(s, blake2b_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static ssh_hash *blake2b_new(const ssh_hashalg *alg) -{ - return blake2b_new_inner(alg->hlen); -} - -ssh_hash *blake2b_new_general(unsigned hashlen) -{ - ssh_hash *h = blake2b_new_inner(hashlen); - ssh_hash_reset(h); - return h; -} - -static void blake2b_reset(ssh_hash *hash) -{ - blake2b *s = container_of(hash, blake2b, hash); - - /* Initialise the hash to the standard IV */ - memcpy(s->h, iv, sizeof(s->h)); - - /* XOR in the parameters: secret key length (here always 0) in - * byte 1, and hash length in byte 0. */ - s->h[0] ^= 0x01010000 ^ s->hashlen; - - s->used = 0; - s->lenhi = s->lenlo = 0; -} - -static void blake2b_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - blake2b *copy = container_of(hcopy, blake2b, hash); - blake2b *orig = container_of(horig, blake2b, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void blake2b_free(ssh_hash *hash) -{ - blake2b *s = container_of(hash, blake2b, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void blake2b_write(BinarySink *bs, const void *vp, size_t len) -{ - blake2b *s = BinarySink_DOWNCAST(bs, blake2b); - const uint8_t *p = vp; - - while (len > 0) { - if (s->used == sizeof(s->block)) { - f_outer(s->h, s->block, s->lenhi, s->lenlo, 0); - s->used = 0; - } - - size_t chunk = sizeof(s->block) - s->used; - if (chunk > len) - chunk = len; - - memcpy(s->block + s->used, p, chunk); - s->used += chunk; - p += chunk; - len -= chunk; - - s->lenlo += chunk; - s->lenhi += (s->lenlo < chunk); - } -} - -static void blake2b_digest(ssh_hash *hash, uint8_t *digest) -{ - blake2b *s = container_of(hash, blake2b, hash); - - memset(s->block + s->used, 0, sizeof(s->block) - s->used); - f_outer(s->h, s->block, s->lenhi, s->lenlo, 1); - - uint8_t hash_pre[128]; - for (unsigned i = 0; i < 8; i++) - PUT_64BIT_LSB_FIRST(hash_pre + 8*i, s->h[i]); - memcpy(digest, hash_pre, s->hashlen); - smemclr(hash_pre, sizeof(hash_pre)); -} - -const ssh_hashalg ssh_blake2b = { - .new = blake2b_new, - .reset = blake2b_reset, - .copyfrom = blake2b_copyfrom, - .digest = blake2b_digest, - .free = blake2b_free, - .hlen = 64, - .blocklen = 128, - HASHALG_NAMES_BARE("BLAKE2b-64"), -}; diff --git a/crypto/blowfish.c b/crypto/blowfish.c deleted file mode 100644 index a4e446528..000000000 --- a/crypto/blowfish.c +++ /dev/null @@ -1,702 +0,0 @@ -/* - * Blowfish implementation for PuTTY. - * - * Coded from scratch from the algorithm description. - */ - -#include -#include -#include "ssh.h" -#include "blowfish.h" - -struct BlowfishContext { - uint32_t S0[256], S1[256], S2[256], S3[256], P[18]; - uint32_t iv0, iv1; /* for CBC mode */ -}; - -/* - * 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 uint32_t ${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 uint32_t parray[] = { - 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, - 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, - 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B, -}; - -static const uint32_t 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 uint32_t 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 uint32_t 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 uint32_t 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(uint32_t xL, uint32_t xR, uint32_t *output, - BlowfishContext *ctx) -{ - uint32_t *S0 = ctx->S0; - uint32_t *S1 = ctx->S1; - uint32_t *S2 = ctx->S2; - uint32_t *S3 = ctx->S3; - uint32_t *P = ctx->P; - uint32_t 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(uint32_t xL, uint32_t xR, uint32_t *output, - BlowfishContext *ctx) -{ - uint32_t *S0 = ctx->S0; - uint32_t *S1 = ctx->S1; - uint32_t *S2 = ctx->S2; - uint32_t *S3 = ctx->S3; - uint32_t *P = ctx->P; - uint32_t 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) -{ - uint32_t 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; -} - -void blowfish_lsb_encrypt_ecb(void *vblk, int len, BlowfishContext *ctx) -{ - unsigned char *blk = (unsigned char *)vblk; - uint32_t xL, xR, out[2]; - - assert((len & 7) == 0); - - while (len > 0) { - xL = GET_32BIT_LSB_FIRST(blk); - xR = GET_32BIT_LSB_FIRST(blk + 4); - blowfish_encrypt(xL, xR, out, ctx); - PUT_32BIT_LSB_FIRST(blk, out[0]); - PUT_32BIT_LSB_FIRST(blk + 4, out[1]); - blk += 8; - len -= 8; - } -} - -static void blowfish_lsb_decrypt_cbc(unsigned char *blk, int len, - BlowfishContext *ctx) -{ - uint32_t 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) -{ - uint32_t 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) -{ - uint32_t 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) -{ - uint32_t 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; -} - -void blowfish_initkey(BlowfishContext *ctx) -{ - int i; - - for (i = 0; i < 18; i++) { - ctx->P[i] = parray[i]; - } - - for (i = 0; i < 256; i++) { - ctx->S0[i] = sbox0[i]; - ctx->S1[i] = sbox1[i]; - ctx->S2[i] = sbox2[i]; - ctx->S3[i] = sbox3[i]; - } -} - -void blowfish_expandkey(BlowfishContext *ctx, - const void *vkey, short keybytes, - const void *vsalt, short saltbytes) -{ - const unsigned char *key = (const unsigned char *)vkey; - const unsigned char *salt = (const unsigned char *)vsalt; - uint32_t *S0 = ctx->S0; - uint32_t *S1 = ctx->S1; - uint32_t *S2 = ctx->S2; - uint32_t *S3 = ctx->S3; - uint32_t *P = ctx->P; - uint32_t str[2]; - int i, j; - int saltpos; - unsigned char dummysalt[1]; - - saltpos = 0; - if (!salt) { - saltbytes = 1; - salt = dummysalt; - dummysalt[0] = 0; - } - - for (i = 0; i < 18; i++) { - P[i] ^= - ((uint32_t) (unsigned char) (key[(i * 4 + 0) % keybytes])) << 24; - P[i] ^= - ((uint32_t) (unsigned char) (key[(i * 4 + 1) % keybytes])) << 16; - P[i] ^= - ((uint32_t) (unsigned char) (key[(i * 4 + 2) % keybytes])) << 8; - P[i] ^= ((uint32_t) (unsigned char) (key[(i * 4 + 3) % keybytes])); - } - - str[0] = str[1] = 0; - - for (i = 0; i < 18; i += 2) { - for (j = 0; j < 8; j++) - str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); - - blowfish_encrypt(str[0], str[1], str, ctx); - P[i] = str[0]; - P[i + 1] = str[1]; - } - - for (i = 0; i < 256; i += 2) { - for (j = 0; j < 8; j++) - str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); - blowfish_encrypt(str[0], str[1], str, ctx); - S0[i] = str[0]; - S0[i + 1] = str[1]; - } - for (i = 0; i < 256; i += 2) { - for (j = 0; j < 8; j++) - str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); - blowfish_encrypt(str[0], str[1], str, ctx); - S1[i] = str[0]; - S1[i + 1] = str[1]; - } - for (i = 0; i < 256; i += 2) { - for (j = 0; j < 8; j++) - str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); - blowfish_encrypt(str[0], str[1], str, ctx); - S2[i] = str[0]; - S2[i + 1] = str[1]; - } - for (i = 0; i < 256; i += 2) { - for (j = 0; j < 8; j++) - str[j/4] ^= ((uint32_t)salt[saltpos++ % saltbytes]) << (24-8*(j%4)); - blowfish_encrypt(str[0], str[1], str, ctx); - S3[i] = str[0]; - S3[i + 1] = str[1]; - } -} - -static void blowfish_setkey(BlowfishContext *ctx, - const unsigned char *key, short keybytes) -{ - blowfish_initkey(ctx); - blowfish_expandkey(ctx, key, keybytes, NULL, 0); -} - -/* -- Interface with PuTTY -- */ - -#define SSH1_SESSION_KEY_LENGTH 32 - -BlowfishContext *blowfish_make_context(void) -{ - return snew(BlowfishContext); -} - -void blowfish_free_context(BlowfishContext *ctx) -{ - sfree(ctx); -} - -static void blowfish_iv_be(BlowfishContext *ctx, const void *viv) -{ - const unsigned char *iv = (const unsigned char *)viv; - ctx->iv0 = GET_32BIT_MSB_FIRST(iv); - ctx->iv1 = GET_32BIT_MSB_FIRST(iv + 4); -} - -static void blowfish_iv_le(BlowfishContext *ctx, const void *viv) -{ - const unsigned char *iv = (const unsigned char *)viv; - ctx->iv0 = GET_32BIT_LSB_FIRST(iv); - ctx->iv1 = GET_32BIT_LSB_FIRST(iv + 4); -} - -struct blowfish_ctx { - BlowfishContext context; - ssh_cipher ciph; -}; - -static ssh_cipher *blowfish_new(const ssh_cipheralg *alg) -{ - struct blowfish_ctx *ctx = snew(struct blowfish_ctx); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void blowfish_free(ssh_cipher *cipher) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void blowfish_ssh_setkey(ssh_cipher *cipher, const void *key) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_setkey(&ctx->context, key, ctx->ciph.vt->padded_keybytes); -} - -static void blowfish_ssh1_setiv(ssh_cipher *cipher, const void *iv) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_iv_le(&ctx->context, iv); -} - -static void blowfish_ssh2_setiv(ssh_cipher *cipher, const void *iv) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_iv_be(&ctx->context, iv); -} - -static void blowfish_ssh1_encrypt_blk(ssh_cipher *cipher, void *blk, int len) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_lsb_encrypt_cbc(blk, len, &ctx->context); -} - -static void blowfish_ssh1_decrypt_blk(ssh_cipher *cipher, void *blk, int len) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_lsb_decrypt_cbc(blk, len, &ctx->context); -} - -static void blowfish_ssh2_encrypt_blk(ssh_cipher *cipher, void *blk, int len) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_msb_encrypt_cbc(blk, len, &ctx->context); -} - -static void blowfish_ssh2_decrypt_blk(ssh_cipher *cipher, void *blk, int len) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_msb_decrypt_cbc(blk, len, &ctx->context); -} - -static void blowfish_ssh2_sdctr(ssh_cipher *cipher, void *blk, int len) -{ - struct blowfish_ctx *ctx = container_of(cipher, struct blowfish_ctx, ciph); - blowfish_msb_sdctr(blk, len, &ctx->context); -} - -const ssh_cipheralg ssh_blowfish_ssh1 = { - .new = blowfish_new, - .free = blowfish_free, - .setiv = blowfish_ssh1_setiv, - .setkey = blowfish_ssh_setkey, - .encrypt = blowfish_ssh1_encrypt_blk, - .decrypt = blowfish_ssh1_decrypt_blk, - .next_message = nullcipher_next_message, - .blksize = 8, - .real_keybits = 128, - .padded_keybytes = SSH1_SESSION_KEY_LENGTH, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "Blowfish-256 CBC", -}; - -const ssh_cipheralg ssh_blowfish_ssh2 = { - .new = blowfish_new, - .free = blowfish_free, - .setiv = blowfish_ssh2_setiv, - .setkey = blowfish_ssh_setkey, - .encrypt = blowfish_ssh2_encrypt_blk, - .decrypt = blowfish_ssh2_decrypt_blk, - .next_message = nullcipher_next_message, - .ssh2_id = "blowfish-cbc", - .blksize = 8, - .real_keybits = 128, - .padded_keybytes = 16, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "Blowfish-128 CBC", -}; - -const ssh_cipheralg ssh_blowfish_ssh2_ctr = { - .new = blowfish_new, - .free = blowfish_free, - .setiv = blowfish_ssh2_setiv, - .setkey = blowfish_ssh_setkey, - .encrypt = blowfish_ssh2_sdctr, - .decrypt = blowfish_ssh2_sdctr, - .next_message = nullcipher_next_message, - .ssh2_id = "blowfish-ctr", - .blksize = 8, - .real_keybits = 256, - .padded_keybytes = 32, - .flags = 0, - .text_name = "Blowfish-256 SDCTR", -}; - -static const ssh_cipheralg *const blowfish_list[] = { - &ssh_blowfish_ssh2_ctr, - &ssh_blowfish_ssh2 -}; - -const ssh2_ciphers ssh2_blowfish = { lenof(blowfish_list), blowfish_list }; diff --git a/crypto/blowfish.h b/crypto/blowfish.h deleted file mode 100644 index 54158922b..000000000 --- a/crypto/blowfish.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Header file shared between blowfish.c and bcrypt.c. Exposes the - * internal Blowfish routines needed by bcrypt. - */ - -typedef struct BlowfishContext BlowfishContext; - -BlowfishContext *blowfish_make_context(void); -void blowfish_free_context(BlowfishContext *ctx); -void blowfish_initkey(BlowfishContext *ctx); -void blowfish_expandkey(BlowfishContext *ctx, - const void *key, short keybytes, - const void *salt, short saltbytes); -void blowfish_lsb_encrypt_ecb(void *blk, int len, BlowfishContext *ctx); diff --git a/crypto/chacha20-poly1305.c b/crypto/chacha20-poly1305.c deleted file mode 100644 index 4216a64d6..000000000 --- a/crypto/chacha20-poly1305.c +++ /dev/null @@ -1,1079 +0,0 @@ -/* - * ChaCha20-Poly1305 Implementation for SSH-2 - * - * Protocol spec: - * http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?rev=1.2&content-type=text/x-cvsweb-markup - * - * ChaCha20 spec: - * http://cr.yp.to/chacha/chacha-20080128.pdf - * - * Salsa20 spec: - * http://cr.yp.to/snuffle/spec.pdf - * - * Poly1305-AES spec: - * http://cr.yp.to/mac/poly1305-20050329.pdf - * - * The nonce for the Poly1305 is the second part of the key output - * from the first round of ChaCha20. This removes the AES requirement. - * This is undocumented! - * - * This has an intricate link between the cipher and the MAC. The - * keying of both is done in by the cipher and setting of the IV is - * done by the MAC. One cannot operate without the other. The - * configuration of the ssh_cipheralg structure ensures that the MAC is - * set (and others ignored) if this cipher is chosen. - * - * This cipher also encrypts the length using a different - * instantiation of the cipher using a different key and IV made from - * the sequence number which is passed in addition when calling - * encrypt/decrypt on it. - */ - -#include "ssh.h" -#include "mpint_i.h" - -#ifndef INLINE -#define INLINE -#endif - -/* ChaCha20 implementation, only supporting 256-bit keys */ - -/* State for each ChaCha20 instance */ -struct chacha20 { - /* Current context, usually with the count incremented - * 0-3 are the static constant - * 4-11 are the key - * 12-13 are the counter - * 14-15 are the IV */ - uint32_t state[16]; - /* The output of the state above ready to xor */ - unsigned char current[64]; - /* The index of the above currently used to allow a true streaming cipher */ - int currentIndex; -}; - -static INLINE void chacha20_round(struct chacha20 *ctx) -{ - int i; - uint32_t copy[16]; - - /* Take a copy */ - memcpy(copy, ctx->state, sizeof(copy)); - - /* A circular rotation for a 32bit number */ -#define rotl(x, shift) x = ((x << shift) | (x >> (32 - shift))) - - /* What to do for each quarter round operation */ -#define qrop(a, b, c, d) \ - copy[a] += copy[b]; \ - copy[c] ^= copy[a]; \ - rotl(copy[c], d) - - /* A quarter round */ -#define quarter(a, b, c, d) \ - qrop(a, b, d, 16); \ - qrop(c, d, b, 12); \ - qrop(a, b, d, 8); \ - qrop(c, d, b, 7) - - /* Do 20 rounds, in pairs because every other is different */ - for (i = 0; i < 20; i += 2) { - /* A round */ - quarter(0, 4, 8, 12); - quarter(1, 5, 9, 13); - quarter(2, 6, 10, 14); - quarter(3, 7, 11, 15); - /* Another slightly different round */ - quarter(0, 5, 10, 15); - quarter(1, 6, 11, 12); - quarter(2, 7, 8, 13); - quarter(3, 4, 9, 14); - } - - /* Dump the macros, don't need them littering */ -#undef rotl -#undef qrop -#undef quarter - - /* Add the initial state */ - for (i = 0; i < 16; ++i) { - copy[i] += ctx->state[i]; - } - - /* Update the content of the xor buffer */ - for (i = 0; i < 16; ++i) { - ctx->current[i * 4 + 0] = copy[i] >> 0; - ctx->current[i * 4 + 1] = copy[i] >> 8; - ctx->current[i * 4 + 2] = copy[i] >> 16; - ctx->current[i * 4 + 3] = copy[i] >> 24; - } - /* State full, reset pointer to beginning */ - ctx->currentIndex = 0; - smemclr(copy, sizeof(copy)); - - /* Increment round counter */ - ++ctx->state[12]; - /* Check for overflow, not done in one line so the 32 bits are chopped by the type */ - if (!(uint32_t)(ctx->state[12])) { - ++ctx->state[13]; - } -} - -/* Initialise context with 256bit key */ -static void chacha20_key(struct chacha20 *ctx, const unsigned char *key) -{ - static const char constant[16] = "expand 32-byte k"; - - /* Add the fixed string to the start of the state */ - ctx->state[0] = GET_32BIT_LSB_FIRST(constant + 0); - ctx->state[1] = GET_32BIT_LSB_FIRST(constant + 4); - ctx->state[2] = GET_32BIT_LSB_FIRST(constant + 8); - ctx->state[3] = GET_32BIT_LSB_FIRST(constant + 12); - - /* Add the key */ - ctx->state[4] = GET_32BIT_LSB_FIRST(key + 0); - ctx->state[5] = GET_32BIT_LSB_FIRST(key + 4); - ctx->state[6] = GET_32BIT_LSB_FIRST(key + 8); - ctx->state[7] = GET_32BIT_LSB_FIRST(key + 12); - ctx->state[8] = GET_32BIT_LSB_FIRST(key + 16); - ctx->state[9] = GET_32BIT_LSB_FIRST(key + 20); - ctx->state[10] = GET_32BIT_LSB_FIRST(key + 24); - ctx->state[11] = GET_32BIT_LSB_FIRST(key + 28); - - /* New key, dump context */ - ctx->currentIndex = 64; -} - -static void chacha20_iv(struct chacha20 *ctx, const unsigned char *iv) -{ - ctx->state[12] = 0; - ctx->state[13] = 0; - ctx->state[14] = GET_32BIT_MSB_FIRST(iv); - ctx->state[15] = GET_32BIT_MSB_FIRST(iv + 4); - - /* New IV, dump context */ - ctx->currentIndex = 64; -} - -static void chacha20_encrypt(struct chacha20 *ctx, unsigned char *blk, int len) -{ - while (len) { - /* If we don't have any state left, then cycle to the next */ - if (ctx->currentIndex >= 64) { - chacha20_round(ctx); - } - - /* Do the xor while there's some state left and some plaintext left */ - while (ctx->currentIndex < 64 && len) { - *blk++ ^= ctx->current[ctx->currentIndex++]; - --len; - } - } -} - -/* Decrypt is encrypt... It's xor against a PRNG... */ -static INLINE void chacha20_decrypt(struct chacha20 *ctx, - unsigned char *blk, int len) -{ - chacha20_encrypt(ctx, blk, len); -} - -/* Poly1305 implementation (no AES, nonce is not encrypted) */ - -#define NWORDS ((130 + BIGNUM_INT_BITS-1) / BIGNUM_INT_BITS) -typedef struct bigval { - BignumInt w[NWORDS]; -} bigval; - -static void bigval_clear(bigval *r) -{ - int i; - for (i = 0; i < NWORDS; i++) - r->w[i] = 0; -} - -static void bigval_import_le(bigval *r, const void *vdata, int len) -{ - const unsigned char *data = (const unsigned char *)vdata; - int i; - bigval_clear(r); - for (i = 0; i < len; i++) - r->w[i / BIGNUM_INT_BYTES] |= - (BignumInt)data[i] << (8 * (i % BIGNUM_INT_BYTES)); -} - -static void bigval_export_le(const bigval *r, void *vdata, int len) -{ - unsigned char *data = (unsigned char *)vdata; - int i; - for (i = 0; i < len; i++) - data[i] = r->w[i / BIGNUM_INT_BYTES] >> (8 * (i % BIGNUM_INT_BYTES)); -} - -/* - * Core functions to do arithmetic mod p = 2^130-5. The whole - * collection of these, up to and including the surrounding #if, are - * generated automatically for various sizes of BignumInt by - * contrib/make1305.py. - */ - -#if BIGNUM_INT_BITS == 16 - -static void bigval_add(bigval *r, const bigval *a, const bigval *b) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; - BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26; - BignumCarry carry; - - v0 = a->w[0]; - v1 = a->w[1]; - v2 = a->w[2]; - v3 = a->w[3]; - v4 = a->w[4]; - v5 = a->w[5]; - v6 = a->w[6]; - v7 = a->w[7]; - v8 = a->w[8]; - v9 = b->w[0]; - v10 = b->w[1]; - v11 = b->w[2]; - v12 = b->w[3]; - v13 = b->w[4]; - v14 = b->w[5]; - v15 = b->w[6]; - v16 = b->w[7]; - v17 = b->w[8]; - BignumADC(v18, carry, v0, v9, 0); - BignumADC(v19, carry, v1, v10, carry); - BignumADC(v20, carry, v2, v11, carry); - BignumADC(v21, carry, v3, v12, carry); - BignumADC(v22, carry, v4, v13, carry); - BignumADC(v23, carry, v5, v14, carry); - BignumADC(v24, carry, v6, v15, carry); - BignumADC(v25, carry, v7, v16, carry); - v26 = v8 + v17 + carry; - r->w[0] = v18; - r->w[1] = v19; - r->w[2] = v20; - r->w[3] = v21; - r->w[4] = v22; - r->w[5] = v23; - r->w[6] = v24; - r->w[7] = v25; - r->w[8] = v26; -} - -static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; - BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27; - BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40; - BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53; - BignumInt v54, v55, v56, v57, v58, v59, v60, v61, v62, v63, v64, v65, v66; - BignumInt v67, v68, v69, v70, v71, v72, v73, v74, v75, v76, v77, v78, v79; - BignumInt v80, v81, v82, v83, v84, v85, v86, v87, v88, v89, v90, v91, v92; - BignumInt v93, v94, v95, v96, v97, v98, v99, v100, v101, v102, v103, v104; - BignumInt v105, v106, v107, v108, v109, v110, v111, v112, v113, v114; - BignumInt v115, v116, v117, v118, v119, v120, v121, v122, v123, v124; - BignumInt v125, v126, v127, v128, v129, v130, v131, v132, v133, v134; - BignumInt v135, v136, v137, v138, v139, v140, v141, v142, v143, v144; - BignumInt v145, v146, v147, v148, v149, v150, v151, v152, v153, v154; - BignumInt v155, v156, v157, v158, v159, v160, v161, v162, v163, v164; - BignumInt v165, v166, v167, v168, v169, v170, v171, v172, v173, v174; - BignumInt v175, v176, v177, v178, v180, v181, v182, v183, v184, v185; - BignumInt v186, v187, v188, v189, v190, v191, v192, v193, v194, v195; - BignumInt v196, v197, v198, v199, v200, v201, v202, v203, v204, v205; - BignumInt v206, v207, v208, v210, v212, v213, v214, v215, v216, v217; - BignumInt v218, v219, v220, v221, v222, v223, v224, v225, v226, v227; - BignumInt v228, v229; - BignumCarry carry; - - v0 = a->w[0]; - v1 = a->w[1]; - v2 = a->w[2]; - v3 = a->w[3]; - v4 = a->w[4]; - v5 = a->w[5]; - v6 = a->w[6]; - v7 = a->w[7]; - v8 = a->w[8]; - v9 = b->w[0]; - v10 = b->w[1]; - v11 = b->w[2]; - v12 = b->w[3]; - v13 = b->w[4]; - v14 = b->w[5]; - v15 = b->w[6]; - v16 = b->w[7]; - v17 = b->w[8]; - BignumMUL(v19, v18, v0, v9); - BignumMULADD(v21, v20, v0, v10, v19); - BignumMULADD(v23, v22, v0, v11, v21); - BignumMULADD(v25, v24, v0, v12, v23); - BignumMULADD(v27, v26, v0, v13, v25); - BignumMULADD(v29, v28, v0, v14, v27); - BignumMULADD(v31, v30, v0, v15, v29); - BignumMULADD(v33, v32, v0, v16, v31); - BignumMULADD(v35, v34, v0, v17, v33); - BignumMULADD(v37, v36, v1, v9, v20); - BignumMULADD2(v39, v38, v1, v10, v22, v37); - BignumMULADD2(v41, v40, v1, v11, v24, v39); - BignumMULADD2(v43, v42, v1, v12, v26, v41); - BignumMULADD2(v45, v44, v1, v13, v28, v43); - BignumMULADD2(v47, v46, v1, v14, v30, v45); - BignumMULADD2(v49, v48, v1, v15, v32, v47); - BignumMULADD2(v51, v50, v1, v16, v34, v49); - BignumMULADD2(v53, v52, v1, v17, v35, v51); - BignumMULADD(v55, v54, v2, v9, v38); - BignumMULADD2(v57, v56, v2, v10, v40, v55); - BignumMULADD2(v59, v58, v2, v11, v42, v57); - BignumMULADD2(v61, v60, v2, v12, v44, v59); - BignumMULADD2(v63, v62, v2, v13, v46, v61); - BignumMULADD2(v65, v64, v2, v14, v48, v63); - BignumMULADD2(v67, v66, v2, v15, v50, v65); - BignumMULADD2(v69, v68, v2, v16, v52, v67); - BignumMULADD2(v71, v70, v2, v17, v53, v69); - BignumMULADD(v73, v72, v3, v9, v56); - BignumMULADD2(v75, v74, v3, v10, v58, v73); - BignumMULADD2(v77, v76, v3, v11, v60, v75); - BignumMULADD2(v79, v78, v3, v12, v62, v77); - BignumMULADD2(v81, v80, v3, v13, v64, v79); - BignumMULADD2(v83, v82, v3, v14, v66, v81); - BignumMULADD2(v85, v84, v3, v15, v68, v83); - BignumMULADD2(v87, v86, v3, v16, v70, v85); - BignumMULADD2(v89, v88, v3, v17, v71, v87); - BignumMULADD(v91, v90, v4, v9, v74); - BignumMULADD2(v93, v92, v4, v10, v76, v91); - BignumMULADD2(v95, v94, v4, v11, v78, v93); - BignumMULADD2(v97, v96, v4, v12, v80, v95); - BignumMULADD2(v99, v98, v4, v13, v82, v97); - BignumMULADD2(v101, v100, v4, v14, v84, v99); - BignumMULADD2(v103, v102, v4, v15, v86, v101); - BignumMULADD2(v105, v104, v4, v16, v88, v103); - BignumMULADD2(v107, v106, v4, v17, v89, v105); - BignumMULADD(v109, v108, v5, v9, v92); - BignumMULADD2(v111, v110, v5, v10, v94, v109); - BignumMULADD2(v113, v112, v5, v11, v96, v111); - BignumMULADD2(v115, v114, v5, v12, v98, v113); - BignumMULADD2(v117, v116, v5, v13, v100, v115); - BignumMULADD2(v119, v118, v5, v14, v102, v117); - BignumMULADD2(v121, v120, v5, v15, v104, v119); - BignumMULADD2(v123, v122, v5, v16, v106, v121); - BignumMULADD2(v125, v124, v5, v17, v107, v123); - BignumMULADD(v127, v126, v6, v9, v110); - BignumMULADD2(v129, v128, v6, v10, v112, v127); - BignumMULADD2(v131, v130, v6, v11, v114, v129); - BignumMULADD2(v133, v132, v6, v12, v116, v131); - BignumMULADD2(v135, v134, v6, v13, v118, v133); - BignumMULADD2(v137, v136, v6, v14, v120, v135); - BignumMULADD2(v139, v138, v6, v15, v122, v137); - BignumMULADD2(v141, v140, v6, v16, v124, v139); - BignumMULADD2(v143, v142, v6, v17, v125, v141); - BignumMULADD(v145, v144, v7, v9, v128); - BignumMULADD2(v147, v146, v7, v10, v130, v145); - BignumMULADD2(v149, v148, v7, v11, v132, v147); - BignumMULADD2(v151, v150, v7, v12, v134, v149); - BignumMULADD2(v153, v152, v7, v13, v136, v151); - BignumMULADD2(v155, v154, v7, v14, v138, v153); - BignumMULADD2(v157, v156, v7, v15, v140, v155); - BignumMULADD2(v159, v158, v7, v16, v142, v157); - BignumMULADD2(v161, v160, v7, v17, v143, v159); - BignumMULADD(v163, v162, v8, v9, v146); - BignumMULADD2(v165, v164, v8, v10, v148, v163); - BignumMULADD2(v167, v166, v8, v11, v150, v165); - BignumMULADD2(v169, v168, v8, v12, v152, v167); - BignumMULADD2(v171, v170, v8, v13, v154, v169); - BignumMULADD2(v173, v172, v8, v14, v156, v171); - BignumMULADD2(v175, v174, v8, v15, v158, v173); - BignumMULADD2(v177, v176, v8, v16, v160, v175); - v178 = v8 * v17 + v161 + v177; - v180 = (v162) & ((((BignumInt)1) << 2)-1); - v181 = ((v162) >> 2) | ((v164) << 14); - v182 = ((v164) >> 2) | ((v166) << 14); - v183 = ((v166) >> 2) | ((v168) << 14); - v184 = ((v168) >> 2) | ((v170) << 14); - v185 = ((v170) >> 2) | ((v172) << 14); - v186 = ((v172) >> 2) | ((v174) << 14); - v187 = ((v174) >> 2) | ((v176) << 14); - v188 = ((v176) >> 2) | ((v178) << 14); - v189 = (v178) >> 2; - v190 = (v189) & ((((BignumInt)1) << 2)-1); - v191 = (v178) >> 4; - BignumMUL(v193, v192, 5, v181); - BignumMULADD(v195, v194, 5, v182, v193); - BignumMULADD(v197, v196, 5, v183, v195); - BignumMULADD(v199, v198, 5, v184, v197); - BignumMULADD(v201, v200, 5, v185, v199); - BignumMULADD(v203, v202, 5, v186, v201); - BignumMULADD(v205, v204, 5, v187, v203); - BignumMULADD(v207, v206, 5, v188, v205); - v208 = 5 * v190 + v207; - v210 = 25 * v191; - BignumADC(v212, carry, v18, v192, 0); - BignumADC(v213, carry, v36, v194, carry); - BignumADC(v214, carry, v54, v196, carry); - BignumADC(v215, carry, v72, v198, carry); - BignumADC(v216, carry, v90, v200, carry); - BignumADC(v217, carry, v108, v202, carry); - BignumADC(v218, carry, v126, v204, carry); - BignumADC(v219, carry, v144, v206, carry); - v220 = v180 + v208 + carry; - BignumADC(v221, carry, v212, v210, 0); - BignumADC(v222, carry, v213, 0, carry); - BignumADC(v223, carry, v214, 0, carry); - BignumADC(v224, carry, v215, 0, carry); - BignumADC(v225, carry, v216, 0, carry); - BignumADC(v226, carry, v217, 0, carry); - BignumADC(v227, carry, v218, 0, carry); - BignumADC(v228, carry, v219, 0, carry); - v229 = v220 + 0 + carry; - r->w[0] = v221; - r->w[1] = v222; - r->w[2] = v223; - r->w[3] = v224; - r->w[4] = v225; - r->w[5] = v226; - r->w[6] = v227; - r->w[7] = v228; - r->w[8] = v229; -} - -static void bigval_final_reduce(bigval *n) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v13, v14, v15; - BignumInt v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28; - BignumInt v29, v30, v31, v32, v34, v35, v36, v37, v38, v39, v40, v41, v42; - BignumInt v43; - BignumCarry carry; - - v0 = n->w[0]; - v1 = n->w[1]; - v2 = n->w[2]; - v3 = n->w[3]; - v4 = n->w[4]; - v5 = n->w[5]; - v6 = n->w[6]; - v7 = n->w[7]; - v8 = n->w[8]; - v9 = (v8) >> 2; - v10 = (v8) & ((((BignumInt)1) << 2)-1); - v11 = 5 * v9; - BignumADC(v13, carry, v0, v11, 0); - BignumADC(v14, carry, v1, 0, carry); - BignumADC(v15, carry, v2, 0, carry); - BignumADC(v16, carry, v3, 0, carry); - BignumADC(v17, carry, v4, 0, carry); - BignumADC(v18, carry, v5, 0, carry); - BignumADC(v19, carry, v6, 0, carry); - BignumADC(v20, carry, v7, 0, carry); - v21 = v10 + 0 + carry; - BignumADC(v22, carry, v13, 5, 0); - (void)v22; - BignumADC(v23, carry, v14, 0, carry); - (void)v23; - BignumADC(v24, carry, v15, 0, carry); - (void)v24; - BignumADC(v25, carry, v16, 0, carry); - (void)v25; - BignumADC(v26, carry, v17, 0, carry); - (void)v26; - BignumADC(v27, carry, v18, 0, carry); - (void)v27; - BignumADC(v28, carry, v19, 0, carry); - (void)v28; - BignumADC(v29, carry, v20, 0, carry); - (void)v29; - v30 = v21 + 0 + carry; - v31 = (v30) >> 2; - v32 = 5 * v31; - BignumADC(v34, carry, v13, v32, 0); - BignumADC(v35, carry, v14, 0, carry); - BignumADC(v36, carry, v15, 0, carry); - BignumADC(v37, carry, v16, 0, carry); - BignumADC(v38, carry, v17, 0, carry); - BignumADC(v39, carry, v18, 0, carry); - BignumADC(v40, carry, v19, 0, carry); - BignumADC(v41, carry, v20, 0, carry); - v42 = v21 + 0 + carry; - v43 = (v42) & ((((BignumInt)1) << 2)-1); - n->w[0] = v34; - n->w[1] = v35; - n->w[2] = v36; - n->w[3] = v37; - n->w[4] = v38; - n->w[5] = v39; - n->w[6] = v40; - n->w[7] = v41; - n->w[8] = v43; -} - -#elif BIGNUM_INT_BITS == 32 - -static void bigval_add(bigval *r, const bigval *a, const bigval *b) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; - BignumCarry carry; - - v0 = a->w[0]; - v1 = a->w[1]; - v2 = a->w[2]; - v3 = a->w[3]; - v4 = a->w[4]; - v5 = b->w[0]; - v6 = b->w[1]; - v7 = b->w[2]; - v8 = b->w[3]; - v9 = b->w[4]; - BignumADC(v10, carry, v0, v5, 0); - BignumADC(v11, carry, v1, v6, carry); - BignumADC(v12, carry, v2, v7, carry); - BignumADC(v13, carry, v3, v8, carry); - v14 = v4 + v9 + carry; - r->w[0] = v10; - r->w[1] = v11; - r->w[2] = v12; - r->w[3] = v13; - r->w[4] = v14; -} - -static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; - BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27; - BignumInt v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40; - BignumInt v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53; - BignumInt v54, v55, v56, v57, v58, v60, v61, v62, v63, v64, v65, v66, v67; - BignumInt v68, v69, v70, v71, v72, v73, v74, v75, v76, v78, v80, v81, v82; - BignumInt v83, v84, v85, v86, v87, v88, v89; - BignumCarry carry; - - v0 = a->w[0]; - v1 = a->w[1]; - v2 = a->w[2]; - v3 = a->w[3]; - v4 = a->w[4]; - v5 = b->w[0]; - v6 = b->w[1]; - v7 = b->w[2]; - v8 = b->w[3]; - v9 = b->w[4]; - BignumMUL(v11, v10, v0, v5); - BignumMULADD(v13, v12, v0, v6, v11); - BignumMULADD(v15, v14, v0, v7, v13); - BignumMULADD(v17, v16, v0, v8, v15); - BignumMULADD(v19, v18, v0, v9, v17); - BignumMULADD(v21, v20, v1, v5, v12); - BignumMULADD2(v23, v22, v1, v6, v14, v21); - BignumMULADD2(v25, v24, v1, v7, v16, v23); - BignumMULADD2(v27, v26, v1, v8, v18, v25); - BignumMULADD2(v29, v28, v1, v9, v19, v27); - BignumMULADD(v31, v30, v2, v5, v22); - BignumMULADD2(v33, v32, v2, v6, v24, v31); - BignumMULADD2(v35, v34, v2, v7, v26, v33); - BignumMULADD2(v37, v36, v2, v8, v28, v35); - BignumMULADD2(v39, v38, v2, v9, v29, v37); - BignumMULADD(v41, v40, v3, v5, v32); - BignumMULADD2(v43, v42, v3, v6, v34, v41); - BignumMULADD2(v45, v44, v3, v7, v36, v43); - BignumMULADD2(v47, v46, v3, v8, v38, v45); - BignumMULADD2(v49, v48, v3, v9, v39, v47); - BignumMULADD(v51, v50, v4, v5, v42); - BignumMULADD2(v53, v52, v4, v6, v44, v51); - BignumMULADD2(v55, v54, v4, v7, v46, v53); - BignumMULADD2(v57, v56, v4, v8, v48, v55); - v58 = v4 * v9 + v49 + v57; - v60 = (v50) & ((((BignumInt)1) << 2)-1); - v61 = ((v50) >> 2) | ((v52) << 30); - v62 = ((v52) >> 2) | ((v54) << 30); - v63 = ((v54) >> 2) | ((v56) << 30); - v64 = ((v56) >> 2) | ((v58) << 30); - v65 = (v58) >> 2; - v66 = (v65) & ((((BignumInt)1) << 2)-1); - v67 = (v58) >> 4; - BignumMUL(v69, v68, 5, v61); - BignumMULADD(v71, v70, 5, v62, v69); - BignumMULADD(v73, v72, 5, v63, v71); - BignumMULADD(v75, v74, 5, v64, v73); - v76 = 5 * v66 + v75; - v78 = 25 * v67; - BignumADC(v80, carry, v10, v68, 0); - BignumADC(v81, carry, v20, v70, carry); - BignumADC(v82, carry, v30, v72, carry); - BignumADC(v83, carry, v40, v74, carry); - v84 = v60 + v76 + carry; - BignumADC(v85, carry, v80, v78, 0); - BignumADC(v86, carry, v81, 0, carry); - BignumADC(v87, carry, v82, 0, carry); - BignumADC(v88, carry, v83, 0, carry); - v89 = v84 + 0 + carry; - r->w[0] = v85; - r->w[1] = v86; - r->w[2] = v87; - r->w[3] = v88; - r->w[4] = v89; -} - -static void bigval_final_reduce(bigval *n) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v9, v10, v11, v12, v13, v14; - BignumInt v15, v16, v17, v18, v19, v20, v22, v23, v24, v25, v26, v27; - BignumCarry carry; - - v0 = n->w[0]; - v1 = n->w[1]; - v2 = n->w[2]; - v3 = n->w[3]; - v4 = n->w[4]; - v5 = (v4) >> 2; - v6 = (v4) & ((((BignumInt)1) << 2)-1); - v7 = 5 * v5; - BignumADC(v9, carry, v0, v7, 0); - BignumADC(v10, carry, v1, 0, carry); - BignumADC(v11, carry, v2, 0, carry); - BignumADC(v12, carry, v3, 0, carry); - v13 = v6 + 0 + carry; - BignumADC(v14, carry, v9, 5, 0); - (void)v14; - BignumADC(v15, carry, v10, 0, carry); - (void)v15; - BignumADC(v16, carry, v11, 0, carry); - (void)v16; - BignumADC(v17, carry, v12, 0, carry); - (void)v17; - v18 = v13 + 0 + carry; - v19 = (v18) >> 2; - v20 = 5 * v19; - BignumADC(v22, carry, v9, v20, 0); - BignumADC(v23, carry, v10, 0, carry); - BignumADC(v24, carry, v11, 0, carry); - BignumADC(v25, carry, v12, 0, carry); - v26 = v13 + 0 + carry; - v27 = (v26) & ((((BignumInt)1) << 2)-1); - n->w[0] = v22; - n->w[1] = v23; - n->w[2] = v24; - n->w[3] = v25; - n->w[4] = v27; -} - -#elif BIGNUM_INT_BITS == 64 - -static void bigval_add(bigval *r, const bigval *a, const bigval *b) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8; - BignumCarry carry; - - v0 = a->w[0]; - v1 = a->w[1]; - v2 = a->w[2]; - v3 = b->w[0]; - v4 = b->w[1]; - v5 = b->w[2]; - BignumADC(v6, carry, v0, v3, 0); - BignumADC(v7, carry, v1, v4, carry); - v8 = v2 + v5 + carry; - r->w[0] = v6; - r->w[1] = v7; - r->w[2] = v8; -} - -static void bigval_mul_mod_p(bigval *r, const bigval *a, const bigval *b) -{ - BignumInt v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14; - BignumInt v15, v16, v17, v18, v19, v20, v21, v22, v24, v25, v26, v27, v28; - BignumInt v29, v30, v31, v32, v33, v34, v36, v38, v39, v40, v41, v42, v43; - BignumCarry carry; - - v0 = a->w[0]; - v1 = a->w[1]; - v2 = a->w[2]; - v3 = b->w[0]; - v4 = b->w[1]; - v5 = b->w[2]; - BignumMUL(v7, v6, v0, v3); - BignumMULADD(v9, v8, v0, v4, v7); - BignumMULADD(v11, v10, v0, v5, v9); - BignumMULADD(v13, v12, v1, v3, v8); - BignumMULADD2(v15, v14, v1, v4, v10, v13); - BignumMULADD2(v17, v16, v1, v5, v11, v15); - BignumMULADD(v19, v18, v2, v3, v14); - BignumMULADD2(v21, v20, v2, v4, v16, v19); - v22 = v2 * v5 + v17 + v21; - v24 = (v18) & ((((BignumInt)1) << 2)-1); - v25 = ((v18) >> 2) | ((v20) << 62); - v26 = ((v20) >> 2) | ((v22) << 62); - v27 = (v22) >> 2; - v28 = (v27) & ((((BignumInt)1) << 2)-1); - v29 = (v22) >> 4; - BignumMUL(v31, v30, 5, v25); - BignumMULADD(v33, v32, 5, v26, v31); - v34 = 5 * v28 + v33; - v36 = 25 * v29; - BignumADC(v38, carry, v6, v30, 0); - BignumADC(v39, carry, v12, v32, carry); - v40 = v24 + v34 + carry; - BignumADC(v41, carry, v38, v36, 0); - BignumADC(v42, carry, v39, 0, carry); - v43 = v40 + 0 + carry; - r->w[0] = v41; - r->w[1] = v42; - r->w[2] = v43; -} - -static void bigval_final_reduce(bigval *n) -{ - BignumInt v0, v1, v2, v3, v4, v5, v7, v8, v9, v10, v11, v12, v13, v14; - BignumInt v16, v17, v18, v19; - BignumCarry carry; - - v0 = n->w[0]; - v1 = n->w[1]; - v2 = n->w[2]; - v3 = (v2) >> 2; - v4 = (v2) & ((((BignumInt)1) << 2)-1); - v5 = 5 * v3; - BignumADC(v7, carry, v0, v5, 0); - BignumADC(v8, carry, v1, 0, carry); - v9 = v4 + 0 + carry; - BignumADC(v10, carry, v7, 5, 0); - (void)v10; - BignumADC(v11, carry, v8, 0, carry); - (void)v11; - v12 = v9 + 0 + carry; - v13 = (v12) >> 2; - v14 = 5 * v13; - BignumADC(v16, carry, v7, v14, 0); - BignumADC(v17, carry, v8, 0, carry); - v18 = v9 + 0 + carry; - v19 = (v18) & ((((BignumInt)1) << 2)-1); - n->w[0] = v16; - n->w[1] = v17; - n->w[2] = v19; -} - -#else -#error Add another bit count to contrib/make1305.py and rerun it -#endif - -struct poly1305 { - unsigned char nonce[16]; - bigval r; - bigval h; - - /* Buffer in case we get less that a multiple of 16 bytes */ - unsigned char buffer[16]; - int bufferIndex; -}; - -static void poly1305_init(struct poly1305 *ctx) -{ - memset(ctx->nonce, 0, 16); - ctx->bufferIndex = 0; - bigval_clear(&ctx->h); -} - -static void poly1305_key(struct poly1305 *ctx, ptrlen key) -{ - assert(key.len == 32); /* Takes a 256 bit key */ - - unsigned char key_copy[16]; - memcpy(key_copy, key.ptr, 16); - - /* Key the MAC itself - * bytes 4, 8, 12 and 16 are required to have their top four bits clear */ - key_copy[3] &= 0x0f; - key_copy[7] &= 0x0f; - key_copy[11] &= 0x0f; - key_copy[15] &= 0x0f; - /* bytes 5, 9 and 13 are required to have their bottom two bits clear */ - key_copy[4] &= 0xfc; - key_copy[8] &= 0xfc; - key_copy[12] &= 0xfc; - bigval_import_le(&ctx->r, key_copy, 16); - smemclr(key_copy, sizeof(key_copy)); - - /* Use second 128 bits as the nonce */ - memcpy(ctx->nonce, (const char *)key.ptr + 16, 16); -} - -/* Feed up to 16 bytes (should only be less for the last chunk) */ -static void poly1305_feed_chunk(struct poly1305 *ctx, - const unsigned char *chunk, int len) -{ - bigval c; - bigval_import_le(&c, chunk, len); - c.w[len / BIGNUM_INT_BYTES] |= - (BignumInt)1 << (8 * (len % BIGNUM_INT_BYTES)); - bigval_add(&c, &c, &ctx->h); - bigval_mul_mod_p(&ctx->h, &c, &ctx->r); -} - -static void poly1305_feed(struct poly1305 *ctx, - const unsigned char *buf, int len) -{ - /* Check for stuff left in the buffer from last time */ - if (ctx->bufferIndex) { - /* Try to fill up to 16 */ - while (ctx->bufferIndex < 16 && len) { - ctx->buffer[ctx->bufferIndex++] = *buf++; - --len; - } - if (ctx->bufferIndex == 16) { - poly1305_feed_chunk(ctx, ctx->buffer, 16); - ctx->bufferIndex = 0; - } - } - - /* Process 16 byte whole chunks */ - while (len >= 16) { - poly1305_feed_chunk(ctx, buf, 16); - len -= 16; - buf += 16; - } - - /* Cache stuff that's left over */ - if (len) { - memcpy(ctx->buffer, buf, len); - ctx->bufferIndex = len; - } -} - -/* Finalise and populate buffer with 16 byte with MAC */ -static void poly1305_finalise(struct poly1305 *ctx, unsigned char *mac) -{ - bigval tmp; - - if (ctx->bufferIndex) { - poly1305_feed_chunk(ctx, ctx->buffer, ctx->bufferIndex); - } - - bigval_import_le(&tmp, ctx->nonce, 16); - bigval_final_reduce(&ctx->h); - bigval_add(&tmp, &tmp, &ctx->h); - bigval_export_le(&tmp, mac, 16); -} - -/* SSH-2 wrapper */ - -struct ccp_context { - struct chacha20 a_cipher; /* Used for length */ - struct chacha20 b_cipher; /* Used for content */ - - /* Cache of the first 4 bytes because they are the sequence number */ - /* Kept in 8 bytes with the top as zero to allow easy passing to setiv */ - int mac_initialised; /* Where we have got to in filling mac_iv */ - unsigned char mac_iv[8]; - - struct poly1305 mac; - - BinarySink_IMPLEMENTATION; - ssh_cipher ciph; - ssh2_mac mac_if; - bool ciph_allocated, mac_allocated; -}; - -static ssh2_mac *poly_ssh2_new( - const ssh2_macalg *alg, ssh_cipher *cipher) -{ - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - ctx->mac_if.vt = alg; - ctx->mac_allocated = true; - BinarySink_DELEGATE_INIT(&ctx->mac_if, ctx); - return &ctx->mac_if; -} - -static void ccp_common_free(struct ccp_context *ctx) -{ - if (ctx->ciph_allocated || ctx->mac_allocated) - return; - - smemclr(&ctx->a_cipher, sizeof(ctx->a_cipher)); - smemclr(&ctx->b_cipher, sizeof(ctx->b_cipher)); - smemclr(&ctx->mac, sizeof(ctx->mac)); - sfree(ctx); -} - -static void poly_ssh2_free(ssh2_mac *mac) -{ - struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if); - ctx->mac_allocated = false; - ccp_common_free(ctx); -} - -static void poly_setkey(ssh2_mac *mac, ptrlen key) -{ - /* Uses the same context as ChaCha20, so ignore */ -} - -static void poly_start(ssh2_mac *mac) -{ - struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if); - - ctx->mac_initialised = 0; - memset(ctx->mac_iv, 0, 8); - poly1305_init(&ctx->mac); -} - -static void poly_BinarySink_write(BinarySink *bs, const void *blkv, size_t len) -{ - struct ccp_context *ctx = BinarySink_DOWNCAST(bs, struct ccp_context); - const unsigned char *blk = (const unsigned char *)blkv; - - /* First 4 bytes are the IV */ - while (ctx->mac_initialised < 4 && len) { - ctx->mac_iv[7 - ctx->mac_initialised] = *blk++; - ++ctx->mac_initialised; - --len; - } - - /* Initialise the IV if needed */ - if (ctx->mac_initialised == 4) { - chacha20_iv(&ctx->b_cipher, ctx->mac_iv); - ++ctx->mac_initialised; /* Don't do it again */ - - /* Do first rotation */ - chacha20_round(&ctx->b_cipher); - - /* Set the poly key */ - poly1305_key(&ctx->mac, make_ptrlen(ctx->b_cipher.current, 32)); - - /* Set the first round as used */ - ctx->b_cipher.currentIndex = 64; - } - - /* Update the MAC with anything left */ - if (len) { - poly1305_feed(&ctx->mac, blk, len); - } -} - -static void poly_genresult(ssh2_mac *mac, unsigned char *blk) -{ - struct ccp_context *ctx = container_of(mac, struct ccp_context, mac_if); - poly1305_finalise(&ctx->mac, blk); -} - -static const char *poly_text_name(ssh2_mac *mac) -{ - return "Poly1305"; -} - -const ssh2_macalg ssh2_poly1305 = { - .new = poly_ssh2_new, - .free = poly_ssh2_free, - .setkey = poly_setkey, - .start = poly_start, - .genresult = poly_genresult, - .next_message = nullmac_next_message, - .text_name = poly_text_name, - .name = "", - .etm_name = "", /* Not selectable individually, just part of - * ChaCha20-Poly1305 */ - .len = 16, - .keylen = 0, -}; - -static ssh_cipher *ccp_new(const ssh_cipheralg *alg) -{ - struct ccp_context *ctx = snew(struct ccp_context); - BinarySink_INIT(ctx, poly_BinarySink_write); - poly1305_init(&ctx->mac); - ctx->ciph.vt = alg; - ctx->ciph_allocated = true; - ctx->mac_allocated = false; - return &ctx->ciph; -} - -static void ccp_free(ssh_cipher *cipher) -{ - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - ctx->ciph_allocated = false; - ccp_common_free(ctx); -} - -static void ccp_iv(ssh_cipher *cipher, const void *iv) -{ - /* struct ccp_context *ctx = - container_of(cipher, struct ccp_context, ciph); */ - /* IV is set based on the sequence number */ -} - -static void ccp_key(ssh_cipher *cipher, const void *vkey) -{ - const unsigned char *key = (const unsigned char *)vkey; - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - /* Initialise the a_cipher (for decrypting lengths) with the first 256 bits */ - chacha20_key(&ctx->a_cipher, key + 32); - /* Initialise the b_cipher (for content and MAC) with the second 256 bits */ - chacha20_key(&ctx->b_cipher, key); -} - -static void ccp_encrypt(ssh_cipher *cipher, void *blk, int len) -{ - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - chacha20_encrypt(&ctx->b_cipher, blk, len); -} - -static void ccp_decrypt(ssh_cipher *cipher, void *blk, int len) -{ - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - chacha20_decrypt(&ctx->b_cipher, blk, len); -} - -static void ccp_length_op(struct ccp_context *ctx, void *blk, int len, - unsigned long seq) -{ - unsigned char iv[8]; - /* - * According to RFC 4253 (section 6.4), the packet sequence number wraps - * at 2^32, so its 32 high-order bits will always be zero. - */ - PUT_32BIT_LSB_FIRST(iv, 0); - PUT_32BIT_LSB_FIRST(iv + 4, seq); - chacha20_iv(&ctx->a_cipher, iv); - chacha20_iv(&ctx->b_cipher, iv); - /* Reset content block count to 1, as the first is the key for Poly1305 */ - ++ctx->b_cipher.state[12]; - smemclr(iv, sizeof(iv)); -} - -static void ccp_encrypt_length(ssh_cipher *cipher, void *blk, int len, - unsigned long seq) -{ - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - ccp_length_op(ctx, blk, len, seq); - chacha20_encrypt(&ctx->a_cipher, blk, len); -} - -static void ccp_decrypt_length(ssh_cipher *cipher, void *blk, int len, - unsigned long seq) -{ - struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); - ccp_length_op(ctx, blk, len, seq); - chacha20_decrypt(&ctx->a_cipher, blk, len); -} - -const ssh_cipheralg ssh2_chacha20_poly1305 = { - .new = ccp_new, - .free = ccp_free, - .setiv = ccp_iv, - .setkey = ccp_key, - .encrypt = ccp_encrypt, - .decrypt = ccp_decrypt, - .encrypt_length = ccp_encrypt_length, - .decrypt_length = ccp_decrypt_length, - .next_message = nullcipher_next_message, - .ssh2_id = "chacha20-poly1305@openssh.com", - .blksize = 1, - .real_keybits = 512, - .padded_keybytes = 64, - .flags = SSH_CIPHER_SEPARATE_LENGTH, - .text_name = "ChaCha20", - .required_mac = &ssh2_poly1305, -}; - -static const ssh_cipheralg *const ccp_list[] = { - &ssh2_chacha20_poly1305 -}; - -const ssh2_ciphers ssh2_ccp = { lenof(ccp_list), ccp_list }; diff --git a/crypto/crc32.c b/crypto/crc32.c deleted file mode 100644 index bd8744331..000000000 --- a/crypto/crc32.c +++ /dev/null @@ -1,113 +0,0 @@ -/* - * CRC32 implementation, as used in SSH-1. - * - * (This is not, of course, a cryptographic function! It lives in the - * 'crypto' directory because SSH-1 uses it _as if_ it was crypto: it - * handles sensitive data, and we implement it with care for side - * channels.) - * - * This particular form of the CRC uses the polynomial - * P(x) = 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+1 - * and represents polynomials in bit-reversed form, so that the x^0 - * coefficient (constant term) appears in the bit with place value - * 2^31, and the x^31 coefficient in the bit with place value 2^0. In - * this representation, (x^32 mod P) = 0xEDB88320, so multiplying the - * current state by x is done by shifting right by one bit, and XORing - * that constant into the result if the bit shifted out was 1. - * - * There's a bewildering array of subtly different variants of CRC out - * there, using different polynomials, both bit orders, and varying - * the start and end conditions. There are catalogue websites such as - * http://reveng.sourceforge.net/crc-catalogue/ , which generally seem - * to have the convention of indexing CRCs by their 'check value', - * defined as whatever you get if you hash the 9-byte test string - * "123456789". - * - * The crc32_rfc1662() function below, which starts off the CRC state - * at 0xFFFFFFFF and complements it after feeding all the data, gives - * the check value 0xCBF43926, and matches the hash function that the - * above catalogue refers to as "CRC-32/ISO-HDLC"; among other things, - * it's also the "FCS-32" checksum described in RFC 1662 section C.3 - * (hence the name I've given it here). - * - * The crc32_ssh1() function implements the variant form used by - * SSH-1, which uses the same update function, but starts the state at - * zero and doesn't complement it at the end of the computation. The - * check value for that version is 0x2DFD2D88, which that CRC - * catalogue doesn't list at all. - */ - -#include -#include - -#include "ssh.h" - -/* - * Multiply a CRC value by x^4. This implementation strategy avoids - * using a lookup table (which would be a side-channel hazard, since - * SSH-1 applies this CRC to decrypted session data). - * - * The basic idea is that you'd like to "multiply" the shifted-out 4 - * bits by the CRC polynomial value 0xEDB88320, or rather by that - * value shifted right 3 bits (since you want the _last_ bit shifted - * out, i.e. the one originally at the 2^3 position, to generate - * 0xEDB88320 itself). But the scare-quoted "multiply" would have to - * be a multiplication of polynomials over GF(2), which differs from - * integer multiplication in that you don't have any carries. In other - * words, you make a copy of one input shifted left by the index of - * each set bit in the other, so that adding them all together would - * give you the ordinary integer product, and then you XOR them - * together instead. - * - * With a 4-bit multiplier, the two kinds of multiplication coincide - * provided the multiplicand has no two set bits at positions - * differing by less than 4, because then no two copies of the - * multiplier can overlap to generate a carry. So I break up the - * intended multiplicand K = 0xEDB88320 >> 3 into three sub-constants - * a,b,c with that property, such that a^b^c = K. Then I can multiply - * m by each of them separately, and XOR together the results. - */ -static inline uint32_t crc32_shift_4(uint32_t v) -{ - const uint32_t a = 0x11111044, b = 0x08840020, c = 0x04220000; - uint32_t m = v & 0xF; - return (v >> 4) ^ (a*m) ^ (b*m) ^ (c*m); -} - -/* - * The 8-bit shift you need every time you absorb an input byte, - * implemented simply by iterating the 4-bit shift twice. - */ -static inline uint32_t crc32_shift_8(uint32_t v) -{ - return crc32_shift_4(crc32_shift_4(v)); -} - -/* - * Update an existing hash value with extra bytes of data. - */ -uint32_t crc32_update(uint32_t crc, ptrlen data) -{ - const uint8_t *p = (const uint8_t *)data.ptr; - for (size_t len = data.len; len-- > 0 ;) - crc = crc32_shift_8(crc ^ *p++); - return crc; -} - -/* - * The SSH-1 variant of CRC-32. - */ -uint32_t crc32_ssh1(ptrlen data) -{ - return crc32_update(0, data); -} - -/* - * The official version of CRC-32. Nothing in PuTTY proper uses this, - * but it's useful to expose it to testcrypt so that we can implement - * standard test vectors. - */ -uint32_t crc32_rfc1662(ptrlen data) -{ - return crc32_update(0xFFFFFFFF, data) ^ 0xFFFFFFFF; -} diff --git a/crypto/des.c b/crypto/des.c deleted file mode 100644 index 1cbec8fac..000000000 --- a/crypto/des.c +++ /dev/null @@ -1,1053 +0,0 @@ -/* - * Implementation of DES. - */ - -/* - * Background - * ---------- - * - * The basic structure of DES is a Feistel network: the 64-bit cipher - * block is divided into two 32-bit halves L and R, and in each round, - * a mixing function is applied to one of them, the result is XORed - * into the other, and then the halves are swapped so that the other - * one will be the input to the mixing function next time. (This - * structure guarantees reversibility no matter whether the mixing - * function itself is bijective.) - * - * The mixing function for DES goes like this: - * + Extract eight contiguous 6-bit strings from the 32-bit word. - * They start at positions 4 bits apart, so each string overlaps - * the next one by one bit. At least one has to wrap cyclically - * round the end of the word. - * + XOR each of those strings with 6 bits of data from the key - * schedule (which consists of 8 x 6-bit strings per round). - * + Use the resulting 6-bit numbers as the indices into eight - * different lookup tables ('S-boxes'), each of which delivers a - * 4-bit output. - * + Concatenate those eight 4-bit values into a 32-bit word. - * + Finally, apply a fixed permutation P to that word. - * - * DES adds one more wrinkle on top of this structure, which is to - * conjugate it by a bitwise permutation of the cipher block. That is, - * before starting the main cipher rounds, the input bits are permuted - * according to a 64-bit permutation called IP, and after the rounds - * are finished, the output bits are permuted back again by applying - * the inverse of IP. - * - * This gives a lot of leeway to redefine the components of the cipher - * without actually changing the input and output. You could permute - * the bits in the output of any or all of the S-boxes, or reorder the - * S-boxes among themselves, and adjust the following permutation P to - * compensate. And you could adjust IP by post-composing a rotation of - * each 32-bit half, and adjust the starting offsets of the 6-bit - * S-box indices to compensate. - * - * test/desref.py demonstrates this by providing two equivalent forms - * of the cipher, called DES and SGTDES, which give the same output. - * DES is the form described in the original spec: if you make it - * print diagnostic output during the cipher and check it against the - * original, you should recognise the S-box outputs as matching the - * ones you expect. But SGTDES, which I egotistically name after - * myself, is much closer to the form implemented here: I've changed - * the permutation P to suit my implementation strategy and - * compensated by permuting the S-boxes, and also I've added a - * rotation right by 1 bit to IP so that only one S-box index has to - * wrap round the word and also so that the indices are nicely aligned - * for the constant-time selection system I'm using. - */ - -#include - -#include "ssh.h" -#include "mpint_i.h" /* we reuse the BignumInt system */ - -/* If you compile with -DDES_DIAGNOSTICS, intermediate results will be - * sent to debug() (so you also need to compile with -DDEBUG). - * Otherwise this ifdef will condition away all the debug() calls. */ -#ifndef DES_DIAGNOSTICS -#undef debug -#define debug(...) ((void)0) -#endif - -/* - * General utility functions. - */ -static inline uint32_t rol(uint32_t x, unsigned c) -{ - return (x << (31 & c)) | (x >> (31 & -c)); -} -static inline uint32_t ror(uint32_t x, unsigned c) -{ - return rol(x, -c); -} - -/* - * The hard part of doing DES in constant time is the S-box lookup. - * - * My strategy is to iterate over the whole lookup table! That's slow, - * but I don't see any way to avoid _something_ along those lines: in - * every round, every entry in every S-box is potentially needed, and - * if you can't change your memory access pattern based on the input - * data, it follows that you have to read a quantity of information - * equal to the size of all the S-boxes. (Unless they were to turn out - * to be significantly compressible, but I for one couldn't show them - * to be.) - * - * In more detail, I construct a sort of counter-based 'selection - * gadget', which is 15 bits wide and starts off with the top bit - * zero, the next eight bits all 1, and the bottom six set to the - * input S-box index: - * - * 011111111xxxxxx - * - * Now if you add 1 in the lowest bit position, then either it carries - * into the top section (resetting it to 100000000), or it doesn't do - * that yet. If you do that 64 times, then it will _guarantee_ to have - * ticked over into 100000000. In between those increments, the eight - * bits that started off as 11111111 will have stayed that way for - * some number of iterations and then become 00000000, and exactly how - * many iterations depends on the input index. - * - * The purpose of the 0 bit at the top is to absorb the carry when the - * switch happens, which means you can pack more than one gadget into - * the same machine word and have them all work in parallel without - * each one intefering with the next. - * - * The next step is to use each of those 8-bit segments as a bit mask: - * each one is ANDed with a lookup table entry, and all the results - * are XORed together. So you end up with the bitwise XOR of some - * initial segment of the table entries. And the stored S-box tables - * are transformed in such a way that the real S-box values are given - * not by the individual entries, but by the cumulative XORs - * constructed in this way. - * - * A refinement is that I increment each gadget by 2 rather than 1 - * each time, so I only iterate 32 times instead of 64. That's why - * there are 8 selection bits instead of 4: each gadget selects enough - * bits to reconstruct _two_ S-box entries, for a pair of indices - * (2n,2n+1), and then finally I use the low bit of the index to do a - * parallel selection between each of those pairs. - * - * The selection gadget is not quite 16 bits wide. So you can fit four - * of them across a 64-bit word at 16-bit intervals, which is also - * convenient because the place the S-box indices are coming from also - * has pairs of them separated by 16-bit distances, so it's easy to - * copy them into the gadgets in the first place. - */ - -/* - * The S-box data. Each pair of nonzero columns here describes one of - * the S-boxes, corresponding to the SGTDES tables in test/desref.py, - * under the following transformation. - * - * Take S-box #3 as an example. Its values in successive rows of this - * table are eb,e8,54,3d, ... So the cumulative XORs of initial - * sequences of those values are eb,(eb^e8),(eb^e8^54), ... which - * comes to eb,03,57,... Of _those_ values, the top nibble (e,0,5,...) - * gives the even-numbered entries in the S-box, in _reverse_ order - * (because a lower input index selects the XOR of a longer - * subsequence). The odd-numbered entries are given by XORing the two - * digits together: (e^b),(0^3),(5^7),... = 5,3,2,... And indeed, if - * you check SGTDES.sboxes[3] you find it ends ... 52 03 e5. - */ -#define SBOX_ITERATION(X) \ - /* 66 22 44 00 77 33 55 11 */ \ - X(0xf600970083008500, 0x0e00eb007b002e00) \ - X(0xda00e4009000e000, 0xad00e800a700b400) \ - X(0x1a009d003f003600, 0xf60054004300cd00) \ - X(0xaf00c500e900a900, 0x63003d00f2005900) \ - X(0xf300750079001400, 0x80005000a2008900) \ - X(0xa100d400d6007b00, 0xd3009000d300e100) \ - X(0x450087002600ac00, 0xae003c0031009c00) \ - X(0xd000b100b6003600, 0x3e006f0092005900) \ - X(0x4d008a0026001000, 0x89007a00b8004a00) \ - X(0xca00f5003f00ac00, 0x6f00f0003c009400) \ - X(0x92008d0090001000, 0x8c00c600ce004a00) \ - X(0xe2005900e9006d00, 0x790078007800fa00) \ - X(0x1300b10090008d00, 0xa300170027001800) \ - X(0xc70058005f006a00, 0x9c00c100e0006300) \ - X(0x9b002000f000f000, 0xf70057001600f900) \ - X(0xeb00b0009000af00, 0xa9006300b0005800) \ - X(0xa2001d00cf000000, 0x3800b00066000000) \ - X(0xf100da007900d000, 0xbc00790094007900) \ - X(0x570015001900ad00, 0x6f00ef005100cb00) \ - X(0xc3006100e9006d00, 0xc000b700f800f200) \ - X(0x1d005800b600d000, 0x67004d00cd002c00) \ - X(0xf400b800d600e000, 0x5e00a900b000e700) \ - X(0x5400d1003f009c00, 0xc90069002c005300) \ - X(0xe200e50060005900, 0x6a00b800c500f200) \ - X(0xdf0047007900d500, 0x7000ec004c00ea00) \ - X(0x7100d10060009c00, 0x3f00b10095005e00) \ - X(0x82008200f0002000, 0x87001d00cd008000) \ - X(0xd0007000af00c000, 0xe200be006100f200) \ - X(0x8000930060001000, 0x36006e0081001200) \ - X(0x6500a300d600ac00, 0xcf003d007d00c000) \ - X(0x9000700060009800, 0x62008100ad009200) \ - X(0xe000e4003f00f400, 0x5a00ed009000f200) \ - /* end of list */ - -/* - * The S-box mapping function. Expects two 32-bit input words: si6420 - * contains the table indices for S-boxes 0,2,4,6 with their low bits - * starting at position 2 (for S-box 0) and going up in steps of 8. - * si7531 has indices 1,3,5,7 in the same bit positions. - */ -static inline uint32_t des_S(uint32_t si6420, uint32_t si7531) -{ - debug("sindices: %02x %02x %02x %02x %02x %02x %02x %02x\n", - 0x3F & (si6420 >> 2), 0x3F & (si7531 >> 2), - 0x3F & (si6420 >> 10), 0x3F & (si7531 >> 10), - 0x3F & (si6420 >> 18), 0x3F & (si7531 >> 18), - 0x3F & (si6420 >> 26), 0x3F & (si7531 >> 26)); - -#ifdef SIXTY_FOUR_BIT - /* - * On 64-bit machines, we store the table in exactly the form - * shown above, and make two 64-bit words containing four - * selection gadgets each. - */ - - /* Set up the gadgets. The 'cNNNN' variables will be gradually - * incremented, and the bits in positions FF00FF00FF00FF00 will - * act as selectors for the words in the table. - * - * A side effect of moving the input indices further apart is that - * they change order, because it's easier to keep a pair that were - * originally 16 bits apart still 16 bits apart, which now makes - * them adjacent instead of separated by one. So the fact that - * si6420 turns into c6240 (with the 2,4 reversed) is not a typo! - * This will all be undone when we rebuild the output word later. - */ - uint64_t c6240 = ((si6420 | ((uint64_t)si6420 << 24)) - & 0x00FC00FC00FC00FC) | 0xFF00FF00FF00FF00; - uint64_t c7351 = ((si7531 | ((uint64_t)si7531 << 24)) - & 0x00FC00FC00FC00FC) | 0xFF00FF00FF00FF00; - debug("S in: c6240=%016"PRIx64" c7351=%016"PRIx64"\n", c6240, c7351); - - /* Iterate over the table. The 'sNNNN' variables accumulate the - * XOR of all the table entries not masked out. */ - static const struct tbl { uint64_t t6240, t7351; } tbl[32] = { -#define TABLE64(a, b) { a, b }, - SBOX_ITERATION(TABLE64) -#undef TABLE64 - }; - uint64_t s6240 = 0, s7351 = 0; - for (const struct tbl *t = tbl, *limit = tbl + 32; t < limit; t++) { - s6240 ^= c6240 & t->t6240; c6240 += 0x0008000800080008; - s7351 ^= c7351 & t->t7351; c7351 += 0x0008000800080008; - } - debug("S out: s6240=%016"PRIx64" s7351=%016"PRIx64"\n", s6240, s7351); - - /* Final selection between each even/odd pair: mask off the low - * bits of all the input indices (which haven't changed throughout - * the iteration), and multiply by a bit mask that will turn each - * set bit into a mask covering the upper nibble of the selected - * pair. Then use those masks to control which set of lower - * nibbles is XORed into the upper nibbles. */ - s6240 ^= (s6240 << 4) & ((0xf000/0x004) * (c6240 & 0x0004000400040004)); - s7351 ^= (s7351 << 4) & ((0xf000/0x004) * (c7351 & 0x0004000400040004)); - - /* Now the eight final S-box outputs are in the upper nibble of - * each selection position. Mask away the rest of the clutter. */ - s6240 &= 0xf000f000f000f000; - s7351 &= 0xf000f000f000f000; - debug("s0=%x s1=%x s2=%x s3=%x s4=%x s5=%x s6=%x s7=%x\n", - (unsigned)(0xF & (s6240 >> 12)), - (unsigned)(0xF & (s7351 >> 12)), - (unsigned)(0xF & (s6240 >> 44)), - (unsigned)(0xF & (s7351 >> 44)), - (unsigned)(0xF & (s6240 >> 28)), - (unsigned)(0xF & (s7351 >> 28)), - (unsigned)(0xF & (s6240 >> 60)), - (unsigned)(0xF & (s7351 >> 60))); - - /* Combine them all into a single 32-bit output word, which will - * come out in the order 76543210. */ - uint64_t combined = (s6240 >> 12) | (s7351 >> 8); - return combined | (combined >> 24); - -#else /* SIXTY_FOUR_BIT */ - /* - * For 32-bit platforms, we do the same thing but in four 32-bit - * words instead of two 64-bit ones, so the CPU doesn't have to - * waste time propagating carries or shifted bits between the two - * halves of a uint64 that weren't needed anyway. - */ - - /* Set up the gadgets */ - uint32_t c40 = ((si6420 ) & 0x00FC00FC) | 0xFF00FF00; - uint32_t c62 = ((si6420 >> 8) & 0x00FC00FC) | 0xFF00FF00; - uint32_t c51 = ((si7531 ) & 0x00FC00FC) | 0xFF00FF00; - uint32_t c73 = ((si7531 >> 8) & 0x00FC00FC) | 0xFF00FF00; - debug("S in: c40=%08"PRIx32" c62=%08"PRIx32 - " c51=%08"PRIx32" c73=%08"PRIx32"\n", c40, c62, c51, c73); - - /* Iterate over the table */ - static const struct tbl { uint32_t t40, t62, t51, t73; } tbl[32] = { -#define TABLE32(a, b) { ((uint32_t)a), (a>>32), ((uint32_t)b), (b>>32) }, - SBOX_ITERATION(TABLE32) -#undef TABLE32 - }; - uint32_t s40 = 0, s62 = 0, s51 = 0, s73 = 0; - for (const struct tbl *t = tbl, *limit = tbl + 32; t < limit; t++) { - s40 ^= c40 & t->t40; c40 += 0x00080008; - s62 ^= c62 & t->t62; c62 += 0x00080008; - s51 ^= c51 & t->t51; c51 += 0x00080008; - s73 ^= c73 & t->t73; c73 += 0x00080008; - } - debug("S out: s40=%08"PRIx32" s62=%08"PRIx32 - " s51=%08"PRIx32" s73=%08"PRIx32"\n", s40, s62, s51, s73); - - /* Final selection within each pair */ - s40 ^= (s40 << 4) & ((0xf000/0x004) * (c40 & 0x00040004)); - s62 ^= (s62 << 4) & ((0xf000/0x004) * (c62 & 0x00040004)); - s51 ^= (s51 << 4) & ((0xf000/0x004) * (c51 & 0x00040004)); - s73 ^= (s73 << 4) & ((0xf000/0x004) * (c73 & 0x00040004)); - - /* Clean up the clutter */ - s40 &= 0xf000f000; - s62 &= 0xf000f000; - s51 &= 0xf000f000; - s73 &= 0xf000f000; - debug("s0=%x s1=%x s2=%x s3=%x s4=%x s5=%x s6=%x s7=%x\n", - (unsigned)(0xF & (s40 >> 12)), - (unsigned)(0xF & (s51 >> 12)), - (unsigned)(0xF & (s62 >> 12)), - (unsigned)(0xF & (s73 >> 12)), - (unsigned)(0xF & (s40 >> 28)), - (unsigned)(0xF & (s51 >> 28)), - (unsigned)(0xF & (s62 >> 28)), - (unsigned)(0xF & (s73 >> 28))); - - /* Recombine and return */ - return (s40 >> 12) | (s62 >> 4) | (s51 >> 8) | (s73); - -#endif /* SIXTY_FOUR_BIT */ - -} - -/* - * Now for the permutation P. The basic strategy here is to use a - * Benes network: in each stage, the bit at position i is allowed to - * either stay where it is or swap with i ^ D, where D is a power of 2 - * that varies with each phase. (So when D=1, pairs of the form - * {2n,2n+1} can swap; when D=2, the pairs are {4n+j,4n+j+2} for - * j={0,1}, and so on.) - * - * You can recursively construct a Benes network for an arbitrary - * permutation, in which the values of D iterate across all the powers - * of 2 less than the permutation size and then go back again. For - * example, the typical presentation for 32 bits would have D iterate - * over 16,8,4,2,1,2,4,8,16, and there's an easy algorithm that can - * express any permutation in that form by deciding which pairs of - * bits to swap in the outer pair of stages and then recursing to do - * all the stages in between. - * - * Actually implementing the swaps is easy when they're all between - * bits at the same separation: make the value x ^ (x >> D), mask out - * just the bits in the low position of a pair that needs to swap, and - * then use the resulting value y to make x ^ y ^ (y << D) which is - * the swapped version. - * - * In this particular case, I processed the bit indices in the other - * order (going 1,2,4,8,16,8,4,2,1), which makes no significant - * difference to the construction algorithm (it's just a relabelling), - * but it now means that the first two steps only permute entries - * within the output of each S-box - and therefore we can leave them - * completely out, in favour of just defining the S-boxes so that - * those permutation steps are already applied. Furthermore, by - * exhaustive search over the rest of the possible bit-orders for each - * S-box, I was able to find a version of P which could be represented - * in such a way that two further phases had all their control bits - * zero and could be skipped. So the number of swap stages is reduced - * to 5 from the 9 that might have been needed. - */ - -static inline uint32_t des_benes_step(uint32_t v, unsigned D, uint32_t mask) -{ - uint32_t diff = (v ^ (v >> D)) & mask; - return v ^ diff ^ (diff << D); -} - -static inline uint32_t des_P(uint32_t v_orig) -{ - uint32_t v = v_orig; - - /* initial stages with distance 1,2 are part of the S-box data table */ - v = des_benes_step(v, 4, 0x07030702); - v = des_benes_step(v, 8, 0x004E009E); - v = des_benes_step(v, 16, 0x0000D9D3); -/* v = des_benes_step(v, 8, 0x00000000); no-op, so we can skip it */ - v = des_benes_step(v, 4, 0x05040004); -/* v = des_benes_step(v, 2, 0x00000000); no-op, so we can skip it */ - v = des_benes_step(v, 1, 0x04045015); - - debug("P(%08"PRIx32") = %08"PRIx32"\n", v_orig, v); - - return v; -} - -/* - * Putting the S and P functions together, and adding in the round key - * as well, gives us the full mixing function f. - */ - -static inline uint32_t des_f(uint32_t R, uint32_t K7531, uint32_t K6420) -{ - uint32_t s7531 = R ^ K7531, s6420 = rol(R, 4) ^ K6420; - return des_P(des_S(s6420, s7531)); -} - -/* - * The key schedule, and the function to set it up. - */ - -typedef struct des_keysched des_keysched; -struct des_keysched { - uint32_t k7531[16], k6420[16]; -}; - -/* - * Simplistic function to select an arbitrary sequence of bits from - * one value and glue them together into another value. bitnums[] - * gives the sequence of bit indices of the input, from the highest - * output bit downwards. An index of -1 means that output bit is left - * at zero. - * - * This function is only used during key setup, so it doesn't need to - * be highly optimised. - */ -static inline uint64_t bitsel( - uint64_t input, const int8_t *bitnums, size_t size) -{ - uint64_t ret = 0; - while (size-- > 0) { - int bitpos = *bitnums++; - ret <<= 1; - if (bitpos >= 0) - ret |= 1 & (input >> bitpos); - } - return ret; -} - -static void des_key_setup(uint64_t key, des_keysched *sched) -{ - static const int8_t PC1[] = { - 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, - -1, -1, -1, -1, - 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, - }; - static const int8_t PC2_7531[] = { - 46, 43, 49, 36, 59, 55, -1, -1, /* index into S-box 7 */ - 37, 41, 48, 56, 34, 52, -1, -1, /* index into S-box 5 */ - 15, 4, 25, 19, 9, 1, -1, -1, /* index into S-box 3 */ - 12, 7, 17, 0, 22, 3, -1, -1, /* index into S-box 1 */ - }; - static const int8_t PC2_6420[] = { - 57, 32, 45, 54, 39, 50, -1, -1, /* index into S-box 6 */ - 44, 53, 33, 40, 47, 58, -1, -1, /* index into S-box 4 */ - 26, 16, 5, 11, 23, 8, -1, -1, /* index into S-box 2 */ - 10, 14, 6, 20, 27, 24, -1, -1, /* index into S-box 0 */ - }; - static const int leftshifts[] = {1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1}; - - /* Select 56 bits from the 64-bit input key integer (the low bit - * of each input byte is unused), into a word consisting of two - * 28-bit integers starting at bits 0 and 32. */ - uint64_t CD = bitsel(key, PC1, lenof(PC1)); - - for (size_t i = 0; i < 16; i++) { - /* Rotate each 28-bit half of CD left by 1 or 2 bits (varying - * between rounds) */ - CD <<= leftshifts[i]; - CD = (CD & 0x0FFFFFFF0FFFFFFF) | ((CD & 0xF0000000F0000000) >> 28); - - /* Select key bits from the rotated word to use during the - * actual cipher */ - sched->k7531[i] = bitsel(CD, PC2_7531, lenof(PC2_7531)); - sched->k6420[i] = bitsel(CD, PC2_6420, lenof(PC2_6420)); - } -} - -/* - * Helper routines for dealing with 64-bit blocks in the form of an L - * and R word. - */ - -typedef struct LR LR; -struct LR { uint32_t L, R; }; - -static inline LR des_load_lr(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - LR out; - out.L = GET_32BIT_MSB_FIRST(p); - out.R = GET_32BIT_MSB_FIRST(p+4); - return out; -} - -static inline void des_store_lr(void *vp, LR lr) -{ - uint8_t *p = (uint8_t *)vp; - PUT_32BIT_MSB_FIRST(p, lr.L); - PUT_32BIT_MSB_FIRST(p+4, lr.R); -} - -static inline LR des_xor_lr(LR a, LR b) -{ - a.L ^= b.L; - a.R ^= b.R; - return a; -} - -static inline LR des_swap_lr(LR in) -{ - LR out; - out.L = in.R; - out.R = in.L; - return out; -} - -/* - * The initial and final permutations of official DES are in a - * restricted form, in which the 'before' and 'after' positions of a - * given data bit are derived from each other by permuting the bits of - * the _index_ and flipping some of them. This allows the permutation - * to be performed effectively by a method that looks rather like - * _half_ of a general Benes network, because the restricted form - * means only half of it is actually needed. - * - * _Our_ initial and final permutations include a rotation by 1 bit, - * but it's still easier to just suffix that to the standard IP/FP - * than to regenerate everything using a more general method. - * - * Because we're permuting 64 bits in this case, between two 32-bit - * words, there's a separate helper function for this code that - * doesn't look quite like des_benes_step() above. - */ - -static inline void des_bitswap_IP_FP(uint32_t *L, uint32_t *R, - unsigned D, uint32_t mask) -{ - uint32_t diff = mask & ((*R >> D) ^ *L); - *R ^= diff << D; - *L ^= diff; -} - -static inline LR des_IP(LR lr) -{ - des_bitswap_IP_FP(&lr.R, &lr.L, 4, 0x0F0F0F0F); - des_bitswap_IP_FP(&lr.R, &lr.L, 16, 0x0000FFFF); - des_bitswap_IP_FP(&lr.L, &lr.R, 2, 0x33333333); - des_bitswap_IP_FP(&lr.L, &lr.R, 8, 0x00FF00FF); - des_bitswap_IP_FP(&lr.R, &lr.L, 1, 0x55555555); - - lr.L = ror(lr.L, 1); - lr.R = ror(lr.R, 1); - - return lr; -} - -static inline LR des_FP(LR lr) -{ - lr.L = rol(lr.L, 1); - lr.R = rol(lr.R, 1); - - des_bitswap_IP_FP(&lr.R, &lr.L, 1, 0x55555555); - des_bitswap_IP_FP(&lr.L, &lr.R, 8, 0x00FF00FF); - des_bitswap_IP_FP(&lr.L, &lr.R, 2, 0x33333333); - des_bitswap_IP_FP(&lr.R, &lr.L, 16, 0x0000FFFF); - des_bitswap_IP_FP(&lr.R, &lr.L, 4, 0x0F0F0F0F); - - return lr; -} - -/* - * The main cipher functions, which are identical except that they use - * the key schedule in opposite orders. - * - * We provide a version without the initial and final permutations, - * for use in triple-DES mode (no sense undoing and redoing it in - * between the phases). - */ - -static inline LR des_round(LR in, const des_keysched *sched, size_t round) -{ - LR out; - out.L = in.R; - out.R = in.L ^ des_f(in.R, sched->k7531[round], sched->k6420[round]); - return out; -} - -static inline LR des_inner_cipher(LR lr, const des_keysched *sched, - size_t start, size_t step) -{ - lr = des_round(lr, sched, start+0x0*step); - lr = des_round(lr, sched, start+0x1*step); - lr = des_round(lr, sched, start+0x2*step); - lr = des_round(lr, sched, start+0x3*step); - lr = des_round(lr, sched, start+0x4*step); - lr = des_round(lr, sched, start+0x5*step); - lr = des_round(lr, sched, start+0x6*step); - lr = des_round(lr, sched, start+0x7*step); - lr = des_round(lr, sched, start+0x8*step); - lr = des_round(lr, sched, start+0x9*step); - lr = des_round(lr, sched, start+0xa*step); - lr = des_round(lr, sched, start+0xb*step); - lr = des_round(lr, sched, start+0xc*step); - lr = des_round(lr, sched, start+0xd*step); - lr = des_round(lr, sched, start+0xe*step); - lr = des_round(lr, sched, start+0xf*step); - return des_swap_lr(lr); -} - -static inline LR des_full_cipher(LR lr, const des_keysched *sched, - size_t start, size_t step) -{ - lr = des_IP(lr); - lr = des_inner_cipher(lr, sched, start, step); - lr = des_FP(lr); - return lr; -} - -/* - * Parameter pairs for the start,step arguments to the cipher routines - * above, causing them to use the same key schedule in opposite orders. - */ -#define ENCIPHER 0, 1 /* for encryption */ -#define DECIPHER 15, -1 /* for decryption */ - -/* ---------------------------------------------------------------------- - * Single-DES - */ - -struct des_cbc_ctx { - des_keysched sched; - LR iv; - ssh_cipher ciph; -}; - -static ssh_cipher *des_cbc_new(const ssh_cipheralg *alg) -{ - struct des_cbc_ctx *ctx = snew(struct des_cbc_ctx); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void des_cbc_free(ssh_cipher *ciph) -{ - struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void des_cbc_setkey(ssh_cipher *ciph, const void *vkey) -{ - struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); - const uint8_t *key = (const uint8_t *)vkey; - des_key_setup(GET_64BIT_MSB_FIRST(key), &ctx->sched); -} - -static void des_cbc_setiv(ssh_cipher *ciph, const void *iv) -{ - struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); - ctx->iv = des_load_lr(iv); -} - -static void des_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - for (; len > 0; len -= 8, data += 8) { - LR plaintext = des_load_lr(data); - LR cipher_in = des_xor_lr(plaintext, ctx->iv); - LR ciphertext = des_full_cipher(cipher_in, &ctx->sched, ENCIPHER); - des_store_lr(data, ciphertext); - ctx->iv = ciphertext; - } -} - -static void des_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des_cbc_ctx *ctx = container_of(ciph, struct des_cbc_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - for (; len > 0; len -= 8, data += 8) { - LR ciphertext = des_load_lr(data); - LR cipher_out = des_full_cipher(ciphertext, &ctx->sched, DECIPHER); - LR plaintext = des_xor_lr(cipher_out, ctx->iv); - des_store_lr(data, plaintext); - ctx->iv = ciphertext; - } -} - -const ssh_cipheralg ssh_des = { - .new = des_cbc_new, - .free = des_cbc_free, - .setiv = des_cbc_setiv, - .setkey = des_cbc_setkey, - .encrypt = des_cbc_encrypt, - .decrypt = des_cbc_decrypt, - .next_message = nullcipher_next_message, - .ssh2_id = "des-cbc", - .blksize = 8, - .real_keybits = 56, - .padded_keybytes = 8, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "single-DES CBC", -}; - -const ssh_cipheralg ssh_des_sshcom_ssh2 = { - /* Same as ssh_des_cbc, but with a different SSH-2 ID */ - .new = des_cbc_new, - .free = des_cbc_free, - .setiv = des_cbc_setiv, - .setkey = des_cbc_setkey, - .encrypt = des_cbc_encrypt, - .decrypt = des_cbc_decrypt, - .next_message = nullcipher_next_message, - .ssh2_id = "des-cbc@ssh.com", - .blksize = 8, - .real_keybits = 56, - .padded_keybytes = 8, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "single-DES CBC", -}; - -static const ssh_cipheralg *const des_list[] = { - &ssh_des, - &ssh_des_sshcom_ssh2 -}; - -const ssh2_ciphers ssh2_des = { lenof(des_list), des_list }; - -/* ---------------------------------------------------------------------- - * Triple-DES CBC, SSH-2 style. The CBC mode treats the three - * invocations of DES as a single unified cipher, and surrounds it - * with just one layer of CBC, so only one IV is needed. - */ - -struct des3_cbc1_ctx { - des_keysched sched[3]; - LR iv; - ssh_cipher ciph; -}; - -static ssh_cipher *des3_cbc1_new(const ssh_cipheralg *alg) -{ - struct des3_cbc1_ctx *ctx = snew(struct des3_cbc1_ctx); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void des3_cbc1_free(ssh_cipher *ciph) -{ - struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void des3_cbc1_setkey(ssh_cipher *ciph, const void *vkey) -{ - struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); - const uint8_t *key = (const uint8_t *)vkey; - for (size_t i = 0; i < 3; i++) - des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]); -} - -static void des3_cbc1_setiv(ssh_cipher *ciph, const void *iv) -{ - struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); - ctx->iv = des_load_lr(iv); -} - -static void des3_cbc1_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - for (; len > 0; len -= 8, data += 8) { - LR plaintext = des_load_lr(data); - LR cipher_in = des_xor_lr(plaintext, ctx->iv); - - /* Run three copies of the cipher, without undoing and redoing - * IP/FP in between. */ - LR lr = des_IP(cipher_in); - lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER); - lr = des_inner_cipher(lr, &ctx->sched[1], DECIPHER); - lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER); - LR ciphertext = des_FP(lr); - - des_store_lr(data, ciphertext); - ctx->iv = ciphertext; - } -} - -static void des3_cbc1_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des3_cbc1_ctx *ctx = container_of(ciph, struct des3_cbc1_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - for (; len > 0; len -= 8, data += 8) { - LR ciphertext = des_load_lr(data); - - /* Similarly to encryption, but with the order reversed. */ - LR lr = des_IP(ciphertext); - lr = des_inner_cipher(lr, &ctx->sched[2], DECIPHER); - lr = des_inner_cipher(lr, &ctx->sched[1], ENCIPHER); - lr = des_inner_cipher(lr, &ctx->sched[0], DECIPHER); - LR cipher_out = des_FP(lr); - - LR plaintext = des_xor_lr(cipher_out, ctx->iv); - des_store_lr(data, plaintext); - ctx->iv = ciphertext; - } -} - -const ssh_cipheralg ssh_3des_ssh2 = { - .new = des3_cbc1_new, - .free = des3_cbc1_free, - .setiv = des3_cbc1_setiv, - .setkey = des3_cbc1_setkey, - .encrypt = des3_cbc1_cbc_encrypt, - .decrypt = des3_cbc1_cbc_decrypt, - .next_message = nullcipher_next_message, - .ssh2_id = "3des-cbc", - .blksize = 8, - .real_keybits = 168, - .padded_keybytes = 24, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "triple-DES CBC", -}; - -/* ---------------------------------------------------------------------- - * Triple-DES in SDCTR mode. Again, the three DES instances are - * treated as one big cipher, with a single counter encrypted through - * all three. - */ - -#define SDCTR_WORDS (8 / BIGNUM_INT_BYTES) - -struct des3_sdctr_ctx { - des_keysched sched[3]; - BignumInt counter[SDCTR_WORDS]; - ssh_cipher ciph; -}; - -static ssh_cipher *des3_sdctr_new(const ssh_cipheralg *alg) -{ - struct des3_sdctr_ctx *ctx = snew(struct des3_sdctr_ctx); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void des3_sdctr_free(ssh_cipher *ciph) -{ - struct des3_sdctr_ctx *ctx = container_of( - ciph, struct des3_sdctr_ctx, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void des3_sdctr_setkey(ssh_cipher *ciph, const void *vkey) -{ - struct des3_sdctr_ctx *ctx = container_of( - ciph, struct des3_sdctr_ctx, ciph); - const uint8_t *key = (const uint8_t *)vkey; - for (size_t i = 0; i < 3; i++) - des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]); -} - -static void des3_sdctr_setiv(ssh_cipher *ciph, const void *viv) -{ - struct des3_sdctr_ctx *ctx = container_of( - ciph, struct des3_sdctr_ctx, ciph); - const uint8_t *iv = (const uint8_t *)viv; - - /* Import the initial counter value into the internal representation */ - for (unsigned i = 0; i < SDCTR_WORDS; i++) - ctx->counter[i] = GET_BIGNUMINT_MSB_FIRST( - iv + 8 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES); -} - -static void des3_sdctr_encrypt_decrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des3_sdctr_ctx *ctx = container_of( - ciph, struct des3_sdctr_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - uint8_t iv_buf[8]; - for (; len > 0; len -= 8, data += 8) { - /* Format the counter value into the buffer. */ - for (unsigned i = 0; i < SDCTR_WORDS; i++) - PUT_BIGNUMINT_MSB_FIRST( - iv_buf + 8 - BIGNUM_INT_BYTES - i*BIGNUM_INT_BYTES, - ctx->counter[i]); - - /* Increment the counter. */ - BignumCarry carry = 1; - for (unsigned i = 0; i < SDCTR_WORDS; i++) - BignumADC(ctx->counter[i], carry, ctx->counter[i], 0, carry); - - /* Triple-encrypt the counter value from the IV. */ - LR lr = des_IP(des_load_lr(iv_buf)); - lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER); - lr = des_inner_cipher(lr, &ctx->sched[1], DECIPHER); - lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER); - LR keystream = des_FP(lr); - - LR input = des_load_lr(data); - LR output = des_xor_lr(input, keystream); - des_store_lr(data, output); - } - smemclr(iv_buf, sizeof(iv_buf)); -} - -const ssh_cipheralg ssh_3des_ssh2_ctr = { - .new = des3_sdctr_new, - .free = des3_sdctr_free, - .setiv = des3_sdctr_setiv, - .setkey = des3_sdctr_setkey, - .encrypt = des3_sdctr_encrypt_decrypt, - .decrypt = des3_sdctr_encrypt_decrypt, - .next_message = nullcipher_next_message, - .ssh2_id = "3des-ctr", - .blksize = 8, - .real_keybits = 168, - .padded_keybytes = 24, - .flags = 0, - .text_name = "triple-DES SDCTR", -}; - -static const ssh_cipheralg *const des3_list[] = { - &ssh_3des_ssh2_ctr, - &ssh_3des_ssh2 -}; - -const ssh2_ciphers ssh2_3des = { lenof(des3_list), des3_list }; - -/* ---------------------------------------------------------------------- - * Triple-DES, SSH-1 style. SSH-1 replicated the whole CBC structure - * three times, so there have to be three separate IVs, one in each - * layer. - */ - -struct des3_cbc3_ctx { - des_keysched sched[3]; - LR iv[3]; - ssh_cipher ciph; -}; - -static ssh_cipher *des3_cbc3_new(const ssh_cipheralg *alg) -{ - struct des3_cbc3_ctx *ctx = snew(struct des3_cbc3_ctx); - ctx->ciph.vt = alg; - return &ctx->ciph; -} - -static void des3_cbc3_free(ssh_cipher *ciph) -{ - struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -static void des3_cbc3_setkey(ssh_cipher *ciph, const void *vkey) -{ - struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); - const uint8_t *key = (const uint8_t *)vkey; - for (size_t i = 0; i < 3; i++) - des_key_setup(GET_64BIT_MSB_FIRST(key + 8*i), &ctx->sched[i]); -} - -static void des3_cbc3_setiv(ssh_cipher *ciph, const void *viv) -{ - struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); - - /* - * In principle, we ought to provide an interface for the user to - * input 24 instead of 8 bytes of IV. But that would make this an - * ugly exception to the otherwise universal rule that IV size = - * cipher block size, and there's really no need to violate that - * rule given that this is a historical one-off oddity and SSH-1 - * always initialises all three IVs to zero anyway. So we fudge it - * by just setting all the IVs to the same value. - */ - - LR iv = des_load_lr(viv); - - /* But we store the IVs in permuted form, so that we can handle - * all three CBC layers without having to do IP/FP in between. */ - iv = des_IP(iv); - for (size_t i = 0; i < 3; i++) - ctx->iv[i] = iv; -} - -static void des3_cbc3_cbc_encrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - for (; len > 0; len -= 8, data += 8) { - /* Load and IP the input. */ - LR plaintext = des_IP(des_load_lr(data)); - LR lr = plaintext; - - /* Do three passes of CBC, with the middle one inverted. */ - - lr = des_xor_lr(lr, ctx->iv[0]); - lr = des_inner_cipher(lr, &ctx->sched[0], ENCIPHER); - ctx->iv[0] = lr; - - LR ciphertext = lr; - lr = des_inner_cipher(ciphertext, &ctx->sched[1], DECIPHER); - lr = des_xor_lr(lr, ctx->iv[1]); - ctx->iv[1] = ciphertext; - - lr = des_xor_lr(lr, ctx->iv[2]); - lr = des_inner_cipher(lr, &ctx->sched[2], ENCIPHER); - ctx->iv[2] = lr; - - des_store_lr(data, des_FP(lr)); - } -} - -static void des3_cbc3_cbc_decrypt(ssh_cipher *ciph, void *vdata, int len) -{ - struct des3_cbc3_ctx *ctx = container_of(ciph, struct des3_cbc3_ctx, ciph); - uint8_t *data = (uint8_t *)vdata; - for (; len > 0; len -= 8, data += 8) { - /* Load and IP the input */ - LR lr = des_IP(des_load_lr(data)); - LR ciphertext; - - /* Do three passes of CBC, with the middle one inverted. */ - ciphertext = lr; - lr = des_inner_cipher(ciphertext, &ctx->sched[2], DECIPHER); - lr = des_xor_lr(lr, ctx->iv[2]); - ctx->iv[2] = ciphertext; - - lr = des_xor_lr(lr, ctx->iv[1]); - lr = des_inner_cipher(lr, &ctx->sched[1], ENCIPHER); - ctx->iv[1] = lr; - - ciphertext = lr; - lr = des_inner_cipher(ciphertext, &ctx->sched[0], DECIPHER); - lr = des_xor_lr(lr, ctx->iv[0]); - ctx->iv[0] = ciphertext; - - des_store_lr(data, des_FP(lr)); - } -} - -const ssh_cipheralg ssh_3des_ssh1 = { - .new = des3_cbc3_new, - .free = des3_cbc3_free, - .setiv = des3_cbc3_setiv, - .setkey = des3_cbc3_setkey, - .encrypt = des3_cbc3_cbc_encrypt, - .decrypt = des3_cbc3_cbc_decrypt, - .next_message = nullcipher_next_message, - .blksize = 8, - .real_keybits = 168, - .padded_keybytes = 24, - .flags = SSH_CIPHER_IS_CBC, - .text_name = "triple-DES inner-CBC", -}; diff --git a/crypto/diffie-hellman.c b/crypto/diffie-hellman.c deleted file mode 100644 index 4da2d4714..000000000 --- a/crypto/diffie-hellman.c +++ /dev/null @@ -1,439 +0,0 @@ -/* - * Diffie-Hellman implementation for PuTTY. - */ - -#include - -#include "ssh.h" -#include "misc.h" -#include "mpint.h" - -struct dh_ctx { - mp_int *x, *e, *p, *q, *g; -}; - -struct dh_extra { - bool gex; - void (*construct)(dh_ctx *ctx); -}; - -static void dh_group1_construct(dh_ctx *ctx) -{ - /* Command to recompute, from the expression in RFC 2412 section E.2: -spigot -B16 '2^1024 - 2^960 - 1 + 2^64 * ( floor(2^894 pi) + 129093 )' - */ - ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF); - ctx->g = mp_from_integer(2); -} - -static void dh_group14_construct(dh_ctx *ctx) -{ - /* Command to recompute, from the expression in RFC 3526 section 3: -spigot -B16 '2^2048 - 2^1984 - 1 + 2^64 * ( floor(2^1918 pi) + 124476 )' - */ - ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF); - ctx->g = mp_from_integer(2); -} - -static void dh_group15_construct(dh_ctx *ctx) -{ - /* Command to recompute, from the expression in RFC 3526 section 4: -spigot -B16 '2^3072 - 2^3008 - 1 + 2^64 * ( floor(2^2942 pi) + 1690314 )' - */ - ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF); - ctx->g = mp_from_integer(2); -} - -static void dh_group16_construct(dh_ctx *ctx) -{ - /* Command to recompute, from the expression in RFC 3526 section 5: -spigot -B16 '2^4096 - 2^4032 - 1 + 2^64 * ( floor(2^3966 pi) + 240904 )' - */ - ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF); - ctx->g = mp_from_integer(2); -} - -static void dh_group17_construct(dh_ctx *ctx) -{ - /* Command to recompute, from the expression in RFC 3526 section 6: -spigot -B16 '2^6144 - 2^6080 - 1 + 2^64 * ( floor(2^6014 pi) + 929484 )' - */ - ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF); - ctx->g = mp_from_integer(2); -} - -static void dh_group18_construct(dh_ctx *ctx) -{ - /* Command to recompute, from the expression in RFC 3526 section 7: -spigot -B16 '2^8192 - 2^8128 - 1 + 2^64 * ( floor(2^8062 pi) + 4743158 )' - */ - ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF); - ctx->g = mp_from_integer(2); -} - -static const struct dh_extra extra_group1 = { - false, dh_group1_construct, -}; - -const ssh_kex ssh_diffiehellman_group1_sha1 = { - .name = "diffie-hellman-group1-sha1", - .groupname = "group1", - .main_type = KEXTYPE_DH, - .hash = &ssh_sha1, - .extra = &extra_group1, -}; - -static const ssh_kex *const group1_list[] = { - &ssh_diffiehellman_group1_sha1 -}; - -const ssh_kexes ssh_diffiehellman_group1 = { lenof(group1_list), group1_list }; - -static const struct dh_extra extra_group18 = { - false, dh_group18_construct, -}; - -const ssh_kex ssh_diffiehellman_group18_sha512 = { - .name = "diffie-hellman-group18-sha512", - .groupname = "group18", - .main_type = KEXTYPE_DH, - .hash = &ssh_sha512, - .extra = &extra_group18, -}; - -static const ssh_kex *const group18_list[] = { - &ssh_diffiehellman_group18_sha512, -}; - -const ssh_kexes ssh_diffiehellman_group18 = { - lenof(group18_list), group18_list -}; - -static const struct dh_extra extra_group17 = { - false, dh_group17_construct, -}; - -const ssh_kex ssh_diffiehellman_group17_sha512 = { - .name = "diffie-hellman-group17-sha512", - .groupname = "group17", - .main_type = KEXTYPE_DH, - .hash = &ssh_sha512, - .extra = &extra_group17, -}; - -static const ssh_kex *const group17_list[] = { - &ssh_diffiehellman_group17_sha512, -}; - -const ssh_kexes ssh_diffiehellman_group17 = { - lenof(group17_list), group17_list -}; - -static const struct dh_extra extra_group16 = { - false, dh_group16_construct, -}; - -const ssh_kex ssh_diffiehellman_group16_sha512 = { - .name = "diffie-hellman-group16-sha512", - .groupname = "group16", - .main_type = KEXTYPE_DH, - .hash = &ssh_sha512, - .extra = &extra_group16, -}; - -static const ssh_kex *const group16_list[] = { - &ssh_diffiehellman_group16_sha512, -}; - -const ssh_kexes ssh_diffiehellman_group16 = { - lenof(group16_list), group16_list -}; - -static const struct dh_extra extra_group15 = { - false, dh_group15_construct, -}; - -const ssh_kex ssh_diffiehellman_group15_sha512 = { - .name = "diffie-hellman-group15-sha512", - .groupname = "group15", - .main_type = KEXTYPE_DH, - .hash = &ssh_sha512, - .extra = &extra_group15, -}; - -static const ssh_kex *const group15_list[] = { - &ssh_diffiehellman_group15_sha512, -}; - -const ssh_kexes ssh_diffiehellman_group15 = { - lenof(group15_list), group15_list -}; - -static const struct dh_extra extra_group14 = { - false, dh_group14_construct, -}; - -const ssh_kex ssh_diffiehellman_group14_sha256 = { - .name = "diffie-hellman-group14-sha256", - .groupname = "group14", - .main_type = KEXTYPE_DH, - .hash = &ssh_sha256, - .extra = &extra_group14, -}; - -const ssh_kex ssh_diffiehellman_group14_sha1 = { - .name = "diffie-hellman-group14-sha1", - .groupname = "group14", - .main_type = KEXTYPE_DH, - .hash = &ssh_sha1, - .extra = &extra_group14, -}; - -static const ssh_kex *const group14_list[] = { - &ssh_diffiehellman_group14_sha256, - &ssh_diffiehellman_group14_sha1 -}; - -const ssh_kexes ssh_diffiehellman_group14 = { - lenof(group14_list), group14_list -}; - -static const struct dh_extra extra_gex = { true }; - -static const ssh_kex ssh_diffiehellman_gex_sha256 = { - .name = "diffie-hellman-group-exchange-sha256", - .groupname = NULL, - .main_type = KEXTYPE_DH, - .hash = &ssh_sha256, - .extra = &extra_gex, -}; - -static const ssh_kex ssh_diffiehellman_gex_sha1 = { - .name = "diffie-hellman-group-exchange-sha1", - .groupname = NULL, - .main_type = KEXTYPE_DH, - .hash = &ssh_sha1, - .extra = &extra_gex, -}; - -static const ssh_kex *const gex_list[] = { - &ssh_diffiehellman_gex_sha256, - &ssh_diffiehellman_gex_sha1 -}; - -const ssh_kexes ssh_diffiehellman_gex = { lenof(gex_list), gex_list }; - -static const ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = { - .name = "gss-gex-sha1-" GSS_KRB5_OID_HASH, - .groupname = NULL, - .main_type = KEXTYPE_GSS, - .hash = &ssh_sha1, - .extra = &extra_gex, -}; - -static const ssh_kex ssh_gssk5_diffiehellman_group18_sha512 = { - .name = "gss-group18-sha512-" GSS_KRB5_OID_HASH, - .groupname = "group18", - .main_type = KEXTYPE_GSS, - .hash = &ssh_sha512, - .extra = &extra_group18, -}; - -static const ssh_kex ssh_gssk5_diffiehellman_group17_sha512 = { - .name = "gss-group17-sha512-" GSS_KRB5_OID_HASH, - .groupname = "group17", - .main_type = KEXTYPE_GSS, - .hash = &ssh_sha512, - .extra = &extra_group17, -}; - -static const ssh_kex ssh_gssk5_diffiehellman_group16_sha512 = { - .name = "gss-group16-sha512-" GSS_KRB5_OID_HASH, - .groupname = "group16", - .main_type = KEXTYPE_GSS, - .hash = &ssh_sha512, - .extra = &extra_group16, -}; - -static const ssh_kex ssh_gssk5_diffiehellman_group15_sha512 = { - .name = "gss-group15-sha512-" GSS_KRB5_OID_HASH, - .groupname = "group15", - .main_type = KEXTYPE_GSS, - .hash = &ssh_sha512, - .extra = &extra_group15, -}; - -static const ssh_kex ssh_gssk5_diffiehellman_group14_sha256 = { - .name = "gss-group14-sha256-" GSS_KRB5_OID_HASH, - .groupname = "group14", - .main_type = KEXTYPE_GSS, - .hash = &ssh_sha256, - .extra = &extra_group14, -}; - -static const ssh_kex *const gssk5_sha2_kex_list[] = { - &ssh_gssk5_diffiehellman_group16_sha512, - &ssh_gssk5_diffiehellman_group17_sha512, - &ssh_gssk5_diffiehellman_group18_sha512, - &ssh_gssk5_diffiehellman_group15_sha512, - &ssh_gssk5_diffiehellman_group14_sha256, -}; - -const ssh_kexes ssh_gssk5_sha2_kex = { - lenof(gssk5_sha2_kex_list), gssk5_sha2_kex_list -}; - -static const ssh_kex ssh_gssk5_diffiehellman_group14_sha1 = { - .name = "gss-group14-sha1-" GSS_KRB5_OID_HASH, - .groupname = "group14", - .main_type = KEXTYPE_GSS, - .hash = &ssh_sha1, - .extra = &extra_group14, -}; - -static const ssh_kex ssh_gssk5_diffiehellman_group1_sha1 = { - .name = "gss-group1-sha1-" GSS_KRB5_OID_HASH, - .groupname = "group1", - .main_type = KEXTYPE_GSS, - .hash = &ssh_sha1, - .extra = &extra_group1, -}; - -static const ssh_kex *const gssk5_sha1_kex_list[] = { - &ssh_gssk5_diffiehellman_gex_sha1, - &ssh_gssk5_diffiehellman_group14_sha1, - &ssh_gssk5_diffiehellman_group1_sha1 -}; - -const ssh_kexes ssh_gssk5_sha1_kex = { - lenof(gssk5_sha1_kex_list), gssk5_sha1_kex_list -}; - -/* - * Common DH initialisation. - */ -static void dh_init(dh_ctx *ctx) -{ - ctx->q = mp_rshift_fixed(ctx->p, 1); - ctx->x = ctx->e = NULL; -} - -bool dh_is_gex(const ssh_kex *kex) -{ - const struct dh_extra *extra = (const struct dh_extra *)kex->extra; - return extra->gex; -} - -/* - * Initialise DH for a standard group. - */ -dh_ctx *dh_setup_group(const ssh_kex *kex) -{ - const struct dh_extra *extra = (const struct dh_extra *)kex->extra; - assert(!extra->gex); - dh_ctx *ctx = snew(dh_ctx); - extra->construct(ctx); - dh_init(ctx); - return ctx; -} - -/* - * Initialise DH for a server-supplied group. - */ -dh_ctx *dh_setup_gex(mp_int *pval, mp_int *gval) -{ - dh_ctx *ctx = snew(dh_ctx); - ctx->p = mp_copy(pval); - ctx->g = mp_copy(gval); - dh_init(ctx); - return ctx; -} - -/* - * Return size of DH modulus p. - */ -int dh_modulus_bit_size(const dh_ctx *ctx) -{ - return mp_get_nbits(ctx->p); -} - -/* - * Clean up and free a context. - */ -void dh_cleanup(dh_ctx *ctx) -{ - if (ctx->x) - mp_free(ctx->x); - if (ctx->e) - mp_free(ctx->e); - if (ctx->p) - mp_free(ctx->p); - if (ctx->g) - mp_free(ctx->g); - if (ctx->q) - mp_free(ctx->q); - sfree(ctx); -} - -/* - * DH stage 1: invent a number x between 1 and q, and compute e = - * g^x mod p. Return e. - */ -mp_int *dh_create_e(dh_ctx *ctx) -{ - /* - * Lower limit is just 2. - */ - mp_int *lo = mp_from_integer(2); - - /* - * Upper limit. - */ - mp_int *hi = mp_copy(ctx->q); - mp_sub_integer_into(hi, hi, 1); - - /* - * Make a random number in that range. - */ - ctx->x = mp_random_in_range(lo, hi); - mp_free(lo); - mp_free(hi); - - /* - * Now compute e = g^x mod p. - */ - ctx->e = mp_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(dh_ctx *ctx, mp_int *f) -{ - if (!mp_hs_integer(f, 2)) { - return "f value received is too small"; - } else { - mp_int *pm1 = mp_copy(ctx->p); - mp_sub_integer_into(pm1, pm1, 1); - unsigned cmp = mp_cmp_hs(f, pm1); - mp_free(pm1); - if (cmp) - return "f value received is too large"; - } - return NULL; -} - -/* - * DH stage 2: given a number f, compute K = f^x mod p. - */ -mp_int *dh_find_K(dh_ctx *ctx, mp_int *f) -{ - return mp_modpow(f, ctx->x, ctx->p); -} diff --git a/crypto/dsa.c b/crypto/dsa.c deleted file mode 100644 index 71fcd94af..000000000 --- a/crypto/dsa.c +++ /dev/null @@ -1,517 +0,0 @@ -/* - * Digital Signature Algorithm implementation for PuTTY. - */ - -#include -#include -#include - -#include "ssh.h" -#include "mpint.h" -#include "misc.h" - -static void dsa_freekey(ssh_key *key); /* forward reference */ - -static ssh_key *dsa_new_pub(const ssh_keyalg *self, ptrlen data) -{ - BinarySource src[1]; - struct dsa_key *dsa; - - BinarySource_BARE_INIT_PL(src, data); - if (!ptrlen_eq_string(get_string(src), "ssh-dss")) - return NULL; - - dsa = snew(struct dsa_key); - dsa->sshk.vt = &ssh_dsa; - dsa->p = get_mp_ssh2(src); - dsa->q = get_mp_ssh2(src); - dsa->g = get_mp_ssh2(src); - dsa->y = get_mp_ssh2(src); - dsa->x = NULL; - - if (get_err(src) || - mp_eq_integer(dsa->p, 0) || mp_eq_integer(dsa->q, 0)) { - /* Invalid key. */ - dsa_freekey(&dsa->sshk); - return NULL; - } - - return &dsa->sshk; -} - -static void dsa_freekey(ssh_key *key) -{ - struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); - if (dsa->p) - mp_free(dsa->p); - if (dsa->q) - mp_free(dsa->q); - if (dsa->g) - mp_free(dsa->g); - if (dsa->y) - mp_free(dsa->y); - if (dsa->x) - mp_free(dsa->x); - sfree(dsa); -} - -static void append_hex_to_strbuf(strbuf *sb, mp_int *x) -{ - if (sb->len > 0) - put_byte(sb, ','); - put_data(sb, "0x", 2); - char *hex = mp_get_hex(x); - size_t hexlen = strlen(hex); - put_data(sb, hex, hexlen); - smemclr(hex, hexlen); - sfree(hex); -} - -static char *dsa_cache_str(ssh_key *key) -{ - struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); - strbuf *sb = strbuf_new(); - - if (!dsa->p) { - strbuf_free(sb); - return NULL; - } - - append_hex_to_strbuf(sb, dsa->p); - append_hex_to_strbuf(sb, dsa->q); - append_hex_to_strbuf(sb, dsa->g); - append_hex_to_strbuf(sb, dsa->y); - - return strbuf_to_str(sb); -} - -static key_components *dsa_components(ssh_key *key) -{ - struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); - key_components *kc = key_components_new(); - - key_components_add_text(kc, "key_type", "DSA"); - assert(dsa->p); - key_components_add_mp(kc, "p", dsa->p); - key_components_add_mp(kc, "q", dsa->q); - key_components_add_mp(kc, "g", dsa->g); - key_components_add_mp(kc, "public_y", dsa->y); - if (dsa->x) - key_components_add_mp(kc, "private_x", dsa->x); - - return kc; -} - -static char *dsa_invalid(ssh_key *key, unsigned flags) -{ - /* No validity criterion will stop us from using a DSA key at all */ - return NULL; -} - -static bool dsa_verify(ssh_key *key, ptrlen sig, ptrlen data) -{ - struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); - BinarySource src[1]; - unsigned char hash[20]; - bool toret; - - if (!dsa->p) - return false; - - BinarySource_BARE_INIT_PL(src, sig); - - /* - * 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 (sig.len != 40) { /* bug not present; read admin fields */ - ptrlen type = get_string(src); - sig = get_string(src); - - if (get_err(src) || !ptrlen_eq_string(type, "ssh-dss") || - sig.len != 40) - return false; - } - - /* Now we're sitting on a 40-byte string for sure. */ - mp_int *r = mp_from_bytes_be(make_ptrlen(sig.ptr, 20)); - mp_int *s = mp_from_bytes_be(make_ptrlen((const char *)sig.ptr + 20, 20)); - if (!r || !s) { - if (r) - mp_free(r); - if (s) - mp_free(s); - return false; - } - - /* Basic sanity checks: 0 < r,s < q */ - unsigned invalid = 0; - invalid |= mp_eq_integer(r, 0); - invalid |= mp_eq_integer(s, 0); - invalid |= mp_cmp_hs(r, dsa->q); - invalid |= mp_cmp_hs(s, dsa->q); - if (invalid) { - mp_free(r); - mp_free(s); - return false; - } - - /* - * Step 1. w <- s^-1 mod q. - */ - mp_int *w = mp_invert(s, dsa->q); - if (!w) { - mp_free(r); - mp_free(s); - return false; - } - - /* - * Step 2. u1 <- SHA(message) * w mod q. - */ - hash_simple(&ssh_sha1, data, hash); - mp_int *sha = mp_from_bytes_be(make_ptrlen(hash, 20)); - mp_int *u1 = mp_modmul(sha, w, dsa->q); - - /* - * Step 3. u2 <- r * w mod q. - */ - mp_int *u2 = mp_modmul(r, w, dsa->q); - - /* - * Step 4. v <- (g^u1 * y^u2 mod p) mod q. - */ - mp_int *gu1p = mp_modpow(dsa->g, u1, dsa->p); - mp_int *yu2p = mp_modpow(dsa->y, u2, dsa->p); - mp_int *gu1yu2p = mp_modmul(gu1p, yu2p, dsa->p); - mp_int *v = mp_mod(gu1yu2p, dsa->q); - - /* - * Step 5. v should now be equal to r. - */ - - toret = mp_cmp_eq(v, r); - - mp_free(w); - mp_free(sha); - mp_free(u1); - mp_free(u2); - mp_free(gu1p); - mp_free(yu2p); - mp_free(gu1yu2p); - mp_free(v); - mp_free(r); - mp_free(s); - - return toret; -} - -static void dsa_public_blob(ssh_key *key, BinarySink *bs) -{ - struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); - - put_stringz(bs, "ssh-dss"); - put_mp_ssh2(bs, dsa->p); - put_mp_ssh2(bs, dsa->q); - put_mp_ssh2(bs, dsa->g); - put_mp_ssh2(bs, dsa->y); -} - -static void dsa_private_blob(ssh_key *key, BinarySink *bs) -{ - struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); - - put_mp_ssh2(bs, dsa->x); -} - -static ssh_key *dsa_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv) -{ - BinarySource src[1]; - ssh_key *sshk; - struct dsa_key *dsa; - ptrlen hash; - unsigned char digest[20]; - mp_int *ytest; - - sshk = dsa_new_pub(self, pub); - if (!sshk) - return NULL; - - dsa = container_of(sshk, struct dsa_key, sshk); - BinarySource_BARE_INIT_PL(src, priv); - dsa->x = get_mp_ssh2(src); - if (get_err(src)) { - dsa_freekey(&dsa->sshk); - return NULL; - } - - /* - * Check the obsolete hash in the old DSA key format. - */ - hash = get_string(src); - if (hash.len == 20) { - ssh_hash *h = ssh_hash_new(&ssh_sha1); - put_mp_ssh2(h, dsa->p); - put_mp_ssh2(h, dsa->q); - put_mp_ssh2(h, dsa->g); - ssh_hash_final(h, digest); - if (!smemeq(hash.ptr, digest, 20)) { - dsa_freekey(&dsa->sshk); - return NULL; - } - } - - /* - * Now ensure g^x mod p really is y. - */ - ytest = mp_modpow(dsa->g, dsa->x, dsa->p); - if (!mp_cmp_eq(ytest, dsa->y)) { - mp_free(ytest); - dsa_freekey(&dsa->sshk); - return NULL; - } - mp_free(ytest); - - return &dsa->sshk; -} - -static ssh_key *dsa_new_priv_openssh(const ssh_keyalg *self, - BinarySource *src) -{ - struct dsa_key *dsa; - - dsa = snew(struct dsa_key); - dsa->sshk.vt = &ssh_dsa; - - dsa->p = get_mp_ssh2(src); - dsa->q = get_mp_ssh2(src); - dsa->g = get_mp_ssh2(src); - dsa->y = get_mp_ssh2(src); - dsa->x = get_mp_ssh2(src); - - if (get_err(src) || - mp_eq_integer(dsa->q, 0) || mp_eq_integer(dsa->p, 0)) { - /* Invalid key. */ - dsa_freekey(&dsa->sshk); - return NULL; - } - - return &dsa->sshk; -} - -static void dsa_openssh_blob(ssh_key *key, BinarySink *bs) -{ - struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); - - put_mp_ssh2(bs, dsa->p); - put_mp_ssh2(bs, dsa->q); - put_mp_ssh2(bs, dsa->g); - put_mp_ssh2(bs, dsa->y); - put_mp_ssh2(bs, dsa->x); -} - -static bool dsa_has_private(ssh_key *key) -{ - struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); - return dsa->x != NULL; -} - -static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub) -{ - ssh_key *sshk; - struct dsa_key *dsa; - int ret; - - sshk = dsa_new_pub(self, pub); - if (!sshk) - return -1; - - dsa = container_of(sshk, struct dsa_key, sshk); - ret = mp_get_nbits(dsa->p); - dsa_freekey(&dsa->sshk); - - return ret; -} - -mp_int *dsa_gen_k(const char *id_string, mp_int *modulus, - mp_int *private_key, - unsigned char *digest, int digest_len) -{ - /* - * The basic DSA 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. - */ - ssh_hash *h; - unsigned char digest512[64]; - - /* - * Hash some identifying text plus x. - */ - h = ssh_hash_new(&ssh_sha512); - put_asciz(h, id_string); - put_mp_ssh2(h, private_key); - ssh_hash_digest(h, digest512); - - /* - * Now hash that digest plus the message hash. - */ - ssh_hash_reset(h); - put_data(h, digest512, sizeof(digest512)); - put_data(h, digest, digest_len); - ssh_hash_final(h, digest512); - - /* - * Now convert the result into a bignum, and coerce it to the - * range [2,q), which we do by reducing it mod q-2 and adding 2. - */ - mp_int *modminus2 = mp_copy(modulus); - mp_sub_integer_into(modminus2, modminus2, 2); - mp_int *proto_k = mp_from_bytes_be(make_ptrlen(digest512, 64)); - mp_int *k = mp_mod(proto_k, modminus2); - mp_free(proto_k); - mp_free(modminus2); - mp_add_integer_into(k, k, 2); - - smemclr(digest512, sizeof(digest512)); - - return k; -} - -static void dsa_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) -{ - struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); - unsigned char digest[20]; - int i; - - hash_simple(&ssh_sha1, data, digest); - - mp_int *k = dsa_gen_k("DSA deterministic k generator", dsa->q, dsa->x, - digest, sizeof(digest)); - mp_int *kinv = mp_invert(k, dsa->q); /* k^-1 mod q */ - - /* - * Now we have k, so just go ahead and compute the signature. - */ - mp_int *gkp = mp_modpow(dsa->g, k, dsa->p); /* g^k mod p */ - mp_int *r = mp_mod(gkp, dsa->q); /* r = (g^k mod p) mod q */ - mp_free(gkp); - - mp_int *hash = mp_from_bytes_be(make_ptrlen(digest, 20)); - mp_int *xr = mp_mul(dsa->x, r); - mp_int *hxr = mp_add(xr, hash); /* hash + x*r */ - mp_int *s = mp_modmul(kinv, hxr, dsa->q); /* s = k^-1 * (hash+x*r) mod q */ - mp_free(hxr); - mp_free(xr); - mp_free(kinv); - mp_free(k); - mp_free(hash); - - put_stringz(bs, "ssh-dss"); - put_uint32(bs, 40); - for (i = 0; i < 20; i++) - put_byte(bs, mp_get_byte(r, 19 - i)); - for (i = 0; i < 20; i++) - put_byte(bs, mp_get_byte(s, 19 - i)); - mp_free(r); - mp_free(s); -} - -static char *dsa_alg_desc(const ssh_keyalg *self) { return dupstr("DSA"); } - -const ssh_keyalg ssh_dsa = { - .new_pub = dsa_new_pub, - .new_priv = dsa_new_priv, - .new_priv_openssh = dsa_new_priv_openssh, - .freekey = dsa_freekey, - .invalid = dsa_invalid, - .sign = dsa_sign, - .verify = dsa_verify, - .public_blob = dsa_public_blob, - .private_blob = dsa_private_blob, - .openssh_blob = dsa_openssh_blob, - .has_private = dsa_has_private, - .cache_str = dsa_cache_str, - .components = dsa_components, - .base_key = nullkey_base_key, - .pubkey_bits = dsa_pubkey_bits, - .supported_flags = nullkey_supported_flags, - .alternate_ssh_id = nullkey_alternate_ssh_id, - .alg_desc = dsa_alg_desc, - .variable_size = nullkey_variable_size_yes, - .ssh_id = "ssh-dss", - .cache_id = "dss", -}; diff --git a/crypto/ecc-arithmetic.c b/crypto/ecc-arithmetic.c deleted file mode 100644 index 6a896f924..000000000 --- a/crypto/ecc-arithmetic.c +++ /dev/null @@ -1,1171 +0,0 @@ -/* - * Basic arithmetic for elliptic curves, implementing ecc.h. - */ - -#include - -#include "ssh.h" -#include "mpint.h" -#include "ecc.h" - -/* ---------------------------------------------------------------------- - * Weierstrass curves. - */ - -struct WeierstrassPoint { - /* - * Internally, we represent a point using 'Jacobian coordinates', - * which are three values X,Y,Z whose relation to the affine - * coordinates x,y is that x = X/Z^2 and y = Y/Z^3. - * - * This allows us to do most of our calculations without having to - * take an inverse mod p: every time the obvious affine formulae - * would need you to divide by something, you instead multiply it - * into the 'denominator' coordinate Z. You only have to actually - * take the inverse of Z when you need to get the affine - * coordinates back out, which means you do it once after your - * entire computation instead of at every intermediate step. - * - * The point at infinity is represented by setting all three - * coordinates to zero. - * - * These values are also stored in the Montgomery-multiplication - * transformed representation. - */ - mp_int *X, *Y, *Z; - - WeierstrassCurve *wc; -}; - -struct WeierstrassCurve { - /* Prime modulus of the finite field. */ - mp_int *p; - - /* Persistent Montgomery context for doing arithmetic mod p. */ - MontyContext *mc; - - /* Modsqrt context for point decompression. NULL if this curve was - * constructed without providing nonsquare_mod_p. */ - ModsqrtContext *sc; - - /* Parameters of the curve, in Montgomery-multiplication - * transformed form. */ - mp_int *a, *b; -}; - -WeierstrassCurve *ecc_weierstrass_curve( - mp_int *p, mp_int *a, mp_int *b, mp_int *nonsquare_mod_p) -{ - WeierstrassCurve *wc = snew(WeierstrassCurve); - wc->p = mp_copy(p); - wc->mc = monty_new(p); - wc->a = monty_import(wc->mc, a); - wc->b = monty_import(wc->mc, b); - - if (nonsquare_mod_p) - wc->sc = modsqrt_new(p, nonsquare_mod_p); - else - wc->sc = NULL; - - return wc; -} - -void ecc_weierstrass_curve_free(WeierstrassCurve *wc) -{ - mp_free(wc->p); - mp_free(wc->a); - mp_free(wc->b); - monty_free(wc->mc); - if (wc->sc) - modsqrt_free(wc->sc); - sfree(wc); -} - -static WeierstrassPoint *ecc_weierstrass_point_new_empty(WeierstrassCurve *wc) -{ - WeierstrassPoint *wp = snew(WeierstrassPoint); - wp->wc = wc; - wp->X = wp->Y = wp->Z = NULL; - return wp; -} - -static WeierstrassPoint *ecc_weierstrass_point_new_imported( - WeierstrassCurve *wc, mp_int *monty_x, mp_int *monty_y) -{ - WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(wc); - wp->X = monty_x; - wp->Y = monty_y; - wp->Z = mp_copy(monty_identity(wc->mc)); - return wp; -} - -WeierstrassPoint *ecc_weierstrass_point_new( - WeierstrassCurve *wc, mp_int *x, mp_int *y) -{ - return ecc_weierstrass_point_new_imported( - wc, monty_import(wc->mc, x), monty_import(wc->mc, y)); -} - -WeierstrassPoint *ecc_weierstrass_point_new_identity(WeierstrassCurve *wc) -{ - WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(wc); - size_t bits = mp_max_bits(wc->p); - wp->X = mp_new(bits); - wp->Y = mp_new(bits); - wp->Z = mp_new(bits); - return wp; -} - -void ecc_weierstrass_point_copy_into( - WeierstrassPoint *dest, WeierstrassPoint *src) -{ - mp_copy_into(dest->X, src->X); - mp_copy_into(dest->Y, src->Y); - mp_copy_into(dest->Z, src->Z); -} - -WeierstrassPoint *ecc_weierstrass_point_copy(WeierstrassPoint *orig) -{ - WeierstrassPoint *wp = ecc_weierstrass_point_new_empty(orig->wc); - wp->X = mp_copy(orig->X); - wp->Y = mp_copy(orig->Y); - wp->Z = mp_copy(orig->Z); - return wp; -} - -void ecc_weierstrass_point_free(WeierstrassPoint *wp) -{ - mp_free(wp->X); - mp_free(wp->Y); - mp_free(wp->Z); - smemclr(wp, sizeof(*wp)); - sfree(wp); -} - -WeierstrassPoint *ecc_weierstrass_point_new_from_x( - WeierstrassCurve *wc, mp_int *xorig, unsigned desired_y_parity) -{ - assert(wc->sc); - - /* - * The curve equation is y^2 = x^3 + ax + b, which is already - * conveniently in a form where we can compute the RHS and take - * the square root of it to get y. - */ - unsigned success; - - mp_int *x = monty_import(wc->mc, xorig); - - /* - * Compute the RHS of the curve equation. We don't need to take - * account of z here, because we're constructing the point from - * scratch. So it really is just x^3 + ax + b. - */ - mp_int *x2 = monty_mul(wc->mc, x, x); - mp_int *x2_plus_a = monty_add(wc->mc, x2, wc->a); - mp_int *x3_plus_ax = monty_mul(wc->mc, x2_plus_a, x); - mp_int *rhs = monty_add(wc->mc, x3_plus_ax, wc->b); - mp_free(x2); - mp_free(x2_plus_a); - mp_free(x3_plus_ax); - - mp_int *y = monty_modsqrt(wc->sc, rhs, &success); - mp_free(rhs); - - if (!success) { - /* Failure! x^3+ax+b worked out to be a number that has no - * square root mod p. In this situation there's no point in - * trying to be time-constant, since the protocol sequence is - * going to diverge anyway when we complain to whoever gave us - * this bogus value. */ - mp_free(x); - mp_free(y); - return NULL; - } - - /* - * Choose whichever of y and p-y has the specified parity (of its - * lowest positive residue mod p). - */ - mp_int *tmp = monty_export(wc->mc, y); - unsigned flip = (mp_get_bit(tmp, 0) ^ desired_y_parity) & 1; - mp_sub_into(tmp, wc->p, y); - mp_select_into(y, y, tmp, flip); - mp_free(tmp); - - return ecc_weierstrass_point_new_imported(wc, x, y); -} - -static void ecc_weierstrass_cond_overwrite( - WeierstrassPoint *dest, WeierstrassPoint *src, unsigned overwrite) -{ - mp_select_into(dest->X, dest->X, src->X, overwrite); - mp_select_into(dest->Y, dest->Y, src->Y, overwrite); - mp_select_into(dest->Z, dest->Z, src->Z, overwrite); -} - -static void ecc_weierstrass_cond_swap( - WeierstrassPoint *P, WeierstrassPoint *Q, unsigned swap) -{ - mp_cond_swap(P->X, Q->X, swap); - mp_cond_swap(P->Y, Q->Y, swap); - mp_cond_swap(P->Z, Q->Z, swap); -} - -/* - * Shared code between all three of the basic arithmetic functions: - * once we've determined the slope of the line that we're intersecting - * the curve with, this takes care of finding the coordinates of the - * third intersection point (given the two input x-coordinates and one - * of the y-coords) and negating it to generate the output. - */ -static inline void ecc_weierstrass_epilogue( - mp_int *Px, mp_int *Qx, mp_int *Py, mp_int *common_Z, - mp_int *lambda_n, mp_int *lambda_d, WeierstrassPoint *out) -{ - WeierstrassCurve *wc = out->wc; - - /* Powers of the numerator and denominator of the slope lambda */ - mp_int *lambda_n2 = monty_mul(wc->mc, lambda_n, lambda_n); - mp_int *lambda_d2 = monty_mul(wc->mc, lambda_d, lambda_d); - mp_int *lambda_d3 = monty_mul(wc->mc, lambda_d, lambda_d2); - - /* Make the output x-coordinate */ - mp_int *xsum = monty_add(wc->mc, Px, Qx); - mp_int *lambda_d2_xsum = monty_mul(wc->mc, lambda_d2, xsum); - out->X = monty_sub(wc->mc, lambda_n2, lambda_d2_xsum); - - /* Make the output y-coordinate */ - mp_int *lambda_d2_Px = monty_mul(wc->mc, lambda_d2, Px); - mp_int *xdiff = monty_sub(wc->mc, lambda_d2_Px, out->X); - mp_int *lambda_n_xdiff = monty_mul(wc->mc, lambda_n, xdiff); - mp_int *lambda_d3_Py = monty_mul(wc->mc, lambda_d3, Py); - out->Y = monty_sub(wc->mc, lambda_n_xdiff, lambda_d3_Py); - - /* Make the output z-coordinate */ - out->Z = monty_mul(wc->mc, common_Z, lambda_d); - - mp_free(lambda_n2); - mp_free(lambda_d2); - mp_free(lambda_d3); - mp_free(xsum); - mp_free(xdiff); - mp_free(lambda_d2_xsum); - mp_free(lambda_n_xdiff); - mp_free(lambda_d2_Px); - mp_free(lambda_d3_Py); -} - -/* - * Shared code between add and add_general: put the two input points - * over a common denominator, and determine the slope lambda of the - * line through both of them. If the points have the same - * x-coordinate, then the slope will be returned with a zero - * denominator. - */ -static inline void ecc_weierstrass_add_prologue( - WeierstrassPoint *P, WeierstrassPoint *Q, - mp_int **Px, mp_int **Py, mp_int **Qx, mp_int **denom, - mp_int **lambda_n, mp_int **lambda_d) -{ - WeierstrassCurve *wc = P->wc; - - /* Powers of the points' denominators */ - mp_int *Pz2 = monty_mul(wc->mc, P->Z, P->Z); - mp_int *Pz3 = monty_mul(wc->mc, Pz2, P->Z); - mp_int *Qz2 = monty_mul(wc->mc, Q->Z, Q->Z); - mp_int *Qz3 = monty_mul(wc->mc, Qz2, Q->Z); - - /* Points' x,y coordinates scaled by the other one's denominator - * (raised to the appropriate power) */ - *Px = monty_mul(wc->mc, P->X, Qz2); - *Py = monty_mul(wc->mc, P->Y, Qz3); - *Qx = monty_mul(wc->mc, Q->X, Pz2); - mp_int *Qy = monty_mul(wc->mc, Q->Y, Pz3); - - /* Common denominator */ - *denom = monty_mul(wc->mc, P->Z, Q->Z); - - /* Slope of the line through the two points, if P != Q */ - *lambda_n = monty_sub(wc->mc, Qy, *Py); - *lambda_d = monty_sub(wc->mc, *Qx, *Px); - - mp_free(Pz2); - mp_free(Pz3); - mp_free(Qz2); - mp_free(Qz3); - mp_free(Qy); -} - -WeierstrassPoint *ecc_weierstrass_add(WeierstrassPoint *P, WeierstrassPoint *Q) -{ - WeierstrassCurve *wc = P->wc; - assert(Q->wc == wc); - - WeierstrassPoint *S = ecc_weierstrass_point_new_empty(wc); - - mp_int *Px, *Py, *Qx, *denom, *lambda_n, *lambda_d; - ecc_weierstrass_add_prologue( - P, Q, &Px, &Py, &Qx, &denom, &lambda_n, &lambda_d); - - /* Never expect to have received two mutually inverse inputs, or - * two identical ones (which would make this a doubling). In other - * words, the two input x-coordinates (after putting over a common - * denominator) should never have been equal. */ - assert(!mp_eq_integer(lambda_n, 0)); - - /* Now go to the common epilogue code. */ - ecc_weierstrass_epilogue(Px, Qx, Py, denom, lambda_n, lambda_d, S); - - mp_free(Px); - mp_free(Py); - mp_free(Qx); - mp_free(denom); - mp_free(lambda_n); - mp_free(lambda_d); - - return S; -} - -/* - * Code to determine the slope of the line you need to intersect with - * the curve in the case where you're adding a point to itself. In - * this situation you can't just say "the line through both input - * points" because that's under-determined; instead, you have to take - * the _tangent_ to the curve at the given point, by differentiating - * the curve equation y^2=x^3+ax+b to get 2y dy/dx = 3x^2+a. - */ -static inline void ecc_weierstrass_tangent_slope( - WeierstrassPoint *P, mp_int **lambda_n, mp_int **lambda_d) -{ - WeierstrassCurve *wc = P->wc; - - mp_int *X2 = monty_mul(wc->mc, P->X, P->X); - mp_int *twoX2 = monty_add(wc->mc, X2, X2); - mp_int *threeX2 = monty_add(wc->mc, twoX2, X2); - mp_int *Z2 = monty_mul(wc->mc, P->Z, P->Z); - mp_int *Z4 = monty_mul(wc->mc, Z2, Z2); - mp_int *aZ4 = monty_mul(wc->mc, wc->a, Z4); - - *lambda_n = monty_add(wc->mc, threeX2, aZ4); - *lambda_d = monty_add(wc->mc, P->Y, P->Y); - - mp_free(X2); - mp_free(twoX2); - mp_free(threeX2); - mp_free(Z2); - mp_free(Z4); - mp_free(aZ4); -} - -WeierstrassPoint *ecc_weierstrass_double(WeierstrassPoint *P) -{ - WeierstrassCurve *wc = P->wc; - WeierstrassPoint *D = ecc_weierstrass_point_new_empty(wc); - - mp_int *lambda_n, *lambda_d; - ecc_weierstrass_tangent_slope(P, &lambda_n, &lambda_d); - ecc_weierstrass_epilogue(P->X, P->X, P->Y, P->Z, lambda_n, lambda_d, D); - mp_free(lambda_n); - mp_free(lambda_d); - - return D; -} - -static inline void ecc_weierstrass_select_into( - WeierstrassPoint *dest, WeierstrassPoint *P, WeierstrassPoint *Q, - unsigned choose_Q) -{ - mp_select_into(dest->X, P->X, Q->X, choose_Q); - mp_select_into(dest->Y, P->Y, Q->Y, choose_Q); - mp_select_into(dest->Z, P->Z, Q->Z, choose_Q); -} - -WeierstrassPoint *ecc_weierstrass_add_general( - WeierstrassPoint *P, WeierstrassPoint *Q) -{ - WeierstrassCurve *wc = P->wc; - assert(Q->wc == wc); - - WeierstrassPoint *S = ecc_weierstrass_point_new_empty(wc); - - /* Parameters for the epilogue, and slope of the line if P != Q */ - mp_int *Px, *Py, *Qx, *denom, *lambda_n, *lambda_d; - ecc_weierstrass_add_prologue( - P, Q, &Px, &Py, &Qx, &denom, &lambda_n, &lambda_d); - - /* Slope if P == Q */ - mp_int *lambda_n_tangent, *lambda_d_tangent; - ecc_weierstrass_tangent_slope(P, &lambda_n_tangent, &lambda_d_tangent); - - /* Select between those slopes depending on whether P == Q */ - unsigned same_x_coord = mp_eq_integer(lambda_d, 0); - unsigned same_y_coord = mp_eq_integer(lambda_n, 0); - unsigned equality = same_x_coord & same_y_coord; - mp_select_into(lambda_n, lambda_n, lambda_n_tangent, equality); - mp_select_into(lambda_d, lambda_d, lambda_d_tangent, equality); - - /* Now go to the common code between addition and doubling */ - ecc_weierstrass_epilogue(Px, Qx, Py, denom, lambda_n, lambda_d, S); - - /* Check for the input identity cases, and overwrite the output if - * necessary. */ - ecc_weierstrass_select_into(S, S, Q, mp_eq_integer(P->Z, 0)); - ecc_weierstrass_select_into(S, S, P, mp_eq_integer(Q->Z, 0)); - - /* - * In the case where P == -Q and so the output is the identity, - * we'll have calculated lambda_d = 0 and so the output will have - * z==0 already. Detect that and use it to normalise the other two - * coordinates to zero. - */ - unsigned output_id = mp_eq_integer(S->Z, 0); - mp_cond_clear(S->X, output_id); - mp_cond_clear(S->Y, output_id); - - mp_free(Px); - mp_free(Py); - mp_free(Qx); - mp_free(denom); - mp_free(lambda_n); - mp_free(lambda_d); - mp_free(lambda_n_tangent); - mp_free(lambda_d_tangent); - - return S; -} - -WeierstrassPoint *ecc_weierstrass_multiply(WeierstrassPoint *B, mp_int *n) -{ - WeierstrassPoint *two_B = ecc_weierstrass_double(B); - WeierstrassPoint *k_B = ecc_weierstrass_point_copy(B); - WeierstrassPoint *kplus1_B = ecc_weierstrass_point_copy(two_B); - - /* - * This multiply routine more or less follows the shape of the - * 'Montgomery ladder' technique that you have to use under the - * extra constraint on addition in Montgomery curves, because it - * was fresh in my mind and easier to just do it the same way. See - * the comment in ecc_montgomery_multiply. - */ - - unsigned not_started_yet = 1; - for (size_t bitindex = mp_max_bits(n); bitindex-- > 0 ;) { - unsigned nbit = mp_get_bit(n, bitindex); - - WeierstrassPoint *sum = ecc_weierstrass_add(k_B, kplus1_B); - ecc_weierstrass_cond_swap(k_B, kplus1_B, nbit); - WeierstrassPoint *other = ecc_weierstrass_double(k_B); - ecc_weierstrass_point_free(k_B); - ecc_weierstrass_point_free(kplus1_B); - k_B = other; - kplus1_B = sum; - ecc_weierstrass_cond_swap(k_B, kplus1_B, nbit); - - ecc_weierstrass_cond_overwrite(k_B, B, not_started_yet); - ecc_weierstrass_cond_overwrite(kplus1_B, two_B, not_started_yet); - not_started_yet &= ~nbit; - } - - ecc_weierstrass_point_free(two_B); - ecc_weierstrass_point_free(kplus1_B); - return k_B; -} - -unsigned ecc_weierstrass_is_identity(WeierstrassPoint *wp) -{ - return mp_eq_integer(wp->Z, 0); -} - -/* - * Normalise a point by scaling its Jacobian coordinates so that Z=1. - * This doesn't change what point is represented by the triple, but it - * means the affine x,y can now be easily recovered from X and Y. - */ -static void ecc_weierstrass_normalise(WeierstrassPoint *wp) -{ - WeierstrassCurve *wc = wp->wc; - mp_int *zinv = monty_invert(wc->mc, wp->Z); - mp_int *zinv2 = monty_mul(wc->mc, zinv, zinv); - mp_int *zinv3 = monty_mul(wc->mc, zinv2, zinv); - monty_mul_into(wc->mc, wp->X, wp->X, zinv2); - monty_mul_into(wc->mc, wp->Y, wp->Y, zinv3); - monty_mul_into(wc->mc, wp->Z, wp->Z, zinv); - mp_free(zinv); - mp_free(zinv2); - mp_free(zinv3); -} - -void ecc_weierstrass_get_affine( - WeierstrassPoint *wp, mp_int **x, mp_int **y) -{ - WeierstrassCurve *wc = wp->wc; - - ecc_weierstrass_normalise(wp); - - if (x) - *x = monty_export(wc->mc, wp->X); - if (y) - *y = monty_export(wc->mc, wp->Y); -} - -unsigned ecc_weierstrass_point_valid(WeierstrassPoint *P) -{ - WeierstrassCurve *wc = P->wc; - - /* - * The projective version of the curve equation is - * Y^2 = X^3 + a X Z^4 + b Z^6 - */ - mp_int *lhs = monty_mul(P->wc->mc, P->Y, P->Y); - mp_int *x2 = monty_mul(wc->mc, P->X, P->X); - mp_int *x3 = monty_mul(wc->mc, x2, P->X); - mp_int *z2 = monty_mul(wc->mc, P->Z, P->Z); - mp_int *z4 = monty_mul(wc->mc, z2, z2); - mp_int *az4 = monty_mul(wc->mc, wc->a, z4); - mp_int *axz4 = monty_mul(wc->mc, az4, P->X); - mp_int *x3_plus_axz4 = monty_add(wc->mc, x3, axz4); - mp_int *z6 = monty_mul(wc->mc, z2, z4); - mp_int *bz6 = monty_mul(wc->mc, wc->b, z6); - mp_int *rhs = monty_add(wc->mc, x3_plus_axz4, bz6); - - unsigned valid = mp_cmp_eq(lhs, rhs); - - mp_free(lhs); - mp_free(x2); - mp_free(x3); - mp_free(z2); - mp_free(z4); - mp_free(az4); - mp_free(axz4); - mp_free(x3_plus_axz4); - mp_free(z6); - mp_free(bz6); - mp_free(rhs); - - return valid; -} - -/* ---------------------------------------------------------------------- - * Montgomery curves. - */ - -struct MontgomeryPoint { - /* XZ coordinates. These represent the affine x coordinate by the - * relationship x = X/Z. */ - mp_int *X, *Z; - - MontgomeryCurve *mc; -}; - -struct MontgomeryCurve { - /* Prime modulus of the finite field. */ - mp_int *p; - - /* Montgomery context for arithmetic mod p. */ - MontyContext *mc; - - /* Parameters of the curve, in Montgomery-multiplication - * transformed form. */ - mp_int *a, *b; - - /* (a+2)/4, also in Montgomery-multiplication form. */ - mp_int *aplus2over4; -}; - -MontgomeryCurve *ecc_montgomery_curve( - mp_int *p, mp_int *a, mp_int *b) -{ - MontgomeryCurve *mc = snew(MontgomeryCurve); - mc->p = mp_copy(p); - mc->mc = monty_new(p); - mc->a = monty_import(mc->mc, a); - mc->b = monty_import(mc->mc, b); - - mp_int *four = mp_from_integer(4); - mp_int *fourinverse = mp_invert(four, mc->p); - mp_int *aplus2 = mp_copy(a); - mp_add_integer_into(aplus2, aplus2, 2); - mp_int *aplus2over4 = mp_modmul(aplus2, fourinverse, mc->p); - mc->aplus2over4 = monty_import(mc->mc, aplus2over4); - mp_free(four); - mp_free(fourinverse); - mp_free(aplus2); - mp_free(aplus2over4); - - return mc; -} - -void ecc_montgomery_curve_free(MontgomeryCurve *mc) -{ - mp_free(mc->p); - mp_free(mc->a); - mp_free(mc->b); - mp_free(mc->aplus2over4); - monty_free(mc->mc); - sfree(mc); -} - -static MontgomeryPoint *ecc_montgomery_point_new_empty(MontgomeryCurve *mc) -{ - MontgomeryPoint *mp = snew(MontgomeryPoint); - mp->mc = mc; - mp->X = mp->Z = NULL; - return mp; -} - -MontgomeryPoint *ecc_montgomery_point_new(MontgomeryCurve *mc, mp_int *x) -{ - MontgomeryPoint *mp = ecc_montgomery_point_new_empty(mc); - mp->X = monty_import(mc->mc, x); - mp->Z = mp_copy(monty_identity(mc->mc)); - return mp; -} - -void ecc_montgomery_point_copy_into( - MontgomeryPoint *dest, MontgomeryPoint *src) -{ - mp_copy_into(dest->X, src->X); - mp_copy_into(dest->Z, src->Z); -} - -MontgomeryPoint *ecc_montgomery_point_copy(MontgomeryPoint *orig) -{ - MontgomeryPoint *mp = ecc_montgomery_point_new_empty(orig->mc); - mp->X = mp_copy(orig->X); - mp->Z = mp_copy(orig->Z); - return mp; -} - -void ecc_montgomery_point_free(MontgomeryPoint *mp) -{ - mp_free(mp->X); - mp_free(mp->Z); - smemclr(mp, sizeof(*mp)); - sfree(mp); -} - -static void ecc_montgomery_cond_overwrite( - MontgomeryPoint *dest, MontgomeryPoint *src, unsigned overwrite) -{ - mp_select_into(dest->X, dest->X, src->X, overwrite); - mp_select_into(dest->Z, dest->Z, src->Z, overwrite); -} - -static void ecc_montgomery_cond_swap( - MontgomeryPoint *P, MontgomeryPoint *Q, unsigned swap) -{ - mp_cond_swap(P->X, Q->X, swap); - mp_cond_swap(P->Z, Q->Z, swap); -} - -MontgomeryPoint *ecc_montgomery_diff_add( - MontgomeryPoint *P, MontgomeryPoint *Q, MontgomeryPoint *PminusQ) -{ - MontgomeryCurve *mc = P->mc; - assert(Q->mc == mc); - assert(PminusQ->mc == mc); - - /* - * Differential addition is achieved using the following formula - * that relates the affine x-coordinates of P, Q, P+Q and P-Q: - * - * x(P+Q) x(P-Q) (x(Q)-x(P))^2 = (x(P)x(Q) - 1)^2 - * - * As with the Weierstrass coordinates, the code below transforms - * that affine relation into a projective one to avoid having to - * do a division during the main arithmetic. - */ - - MontgomeryPoint *S = ecc_montgomery_point_new_empty(mc); - - mp_int *Px_m_Pz = monty_sub(mc->mc, P->X, P->Z); - mp_int *Px_p_Pz = monty_add(mc->mc, P->X, P->Z); - mp_int *Qx_m_Qz = monty_sub(mc->mc, Q->X, Q->Z); - mp_int *Qx_p_Qz = monty_add(mc->mc, Q->X, Q->Z); - mp_int *PmQp = monty_mul(mc->mc, Px_m_Pz, Qx_p_Qz); - mp_int *PpQm = monty_mul(mc->mc, Px_p_Pz, Qx_m_Qz); - mp_int *Xpre = monty_add(mc->mc, PmQp, PpQm); - mp_int *Zpre = monty_sub(mc->mc, PmQp, PpQm); - mp_int *Xpre2 = monty_mul(mc->mc, Xpre, Xpre); - mp_int *Zpre2 = monty_mul(mc->mc, Zpre, Zpre); - S->X = monty_mul(mc->mc, Xpre2, PminusQ->Z); - S->Z = monty_mul(mc->mc, Zpre2, PminusQ->X); - - mp_free(Px_m_Pz); - mp_free(Px_p_Pz); - mp_free(Qx_m_Qz); - mp_free(Qx_p_Qz); - mp_free(PmQp); - mp_free(PpQm); - mp_free(Xpre); - mp_free(Zpre); - mp_free(Xpre2); - mp_free(Zpre2); - - return S; -} - -MontgomeryPoint *ecc_montgomery_double(MontgomeryPoint *P) -{ - MontgomeryCurve *mc = P->mc; - MontgomeryPoint *D = ecc_montgomery_point_new_empty(mc); - - /* - * To double a point in affine coordinates, in principle you can - * use the same technique as for Weierstrass: differentiate the - * curve equation to get the tangent line at the input point, use - * that to get an expression for y which you substitute back into - * the curve equation, and subtract the known two roots (in this - * case both the same) from the x^2 coefficient of the resulting - * cubic. - * - * In this case, we don't have an input y-coordinate, so you have - * to do a bit of extra transformation to find a formula that can - * work without it. The tangent formula is (3x^2 + 2ax + 1)/(2y), - * and when that appears in the final formula it will be squared - - * so we can substitute the y^2 in the denominator for the RHS of - * the curve equation. Put together, that gives - * - * x_out = (x+1)^2 (x-1)^2 / 4(x^3+ax^2+x) - * - * and, as usual, the code below transforms that into projective - * form to avoid the division. - */ - - mp_int *Px_m_Pz = monty_sub(mc->mc, P->X, P->Z); - mp_int *Px_p_Pz = monty_add(mc->mc, P->X, P->Z); - mp_int *Px_m_Pz_2 = monty_mul(mc->mc, Px_m_Pz, Px_m_Pz); - mp_int *Px_p_Pz_2 = monty_mul(mc->mc, Px_p_Pz, Px_p_Pz); - D->X = monty_mul(mc->mc, Px_m_Pz_2, Px_p_Pz_2); - mp_int *XZ = monty_mul(mc->mc, P->X, P->Z); - mp_int *twoXZ = monty_add(mc->mc, XZ, XZ); - mp_int *fourXZ = monty_add(mc->mc, twoXZ, twoXZ); - mp_int *fourXZ_scaled = monty_mul(mc->mc, fourXZ, mc->aplus2over4); - mp_int *Zpre = monty_add(mc->mc, Px_m_Pz_2, fourXZ_scaled); - D->Z = monty_mul(mc->mc, fourXZ, Zpre); - - mp_free(Px_m_Pz); - mp_free(Px_p_Pz); - mp_free(Px_m_Pz_2); - mp_free(Px_p_Pz_2); - mp_free(XZ); - mp_free(twoXZ); - mp_free(fourXZ); - mp_free(fourXZ_scaled); - mp_free(Zpre); - - return D; -} - -static void ecc_montgomery_normalise(MontgomeryPoint *mp) -{ - MontgomeryCurve *mc = mp->mc; - mp_int *zinv = monty_invert(mc->mc, mp->Z); - monty_mul_into(mc->mc, mp->X, mp->X, zinv); - monty_mul_into(mc->mc, mp->Z, mp->Z, zinv); - mp_free(zinv); -} - -MontgomeryPoint *ecc_montgomery_multiply(MontgomeryPoint *B, mp_int *n) -{ - /* - * 'Montgomery ladder' technique, to compute an arbitrary integer - * multiple of B under the constraint that you can only add two - * unequal points if you also know their difference. - * - * The setup is that you maintain two curve points one of which is - * always the other one plus B. Call them kB and (k+1)B, where k - * is some integer that evolves as we go along. We begin by - * doubling the input B, to initialise those points to B and 2B, - * so that k=1. - * - * At each stage, we add kB and (k+1)B together - which we can do - * under the differential-addition constraint because we know - * their difference is always just B - to give us (2k+1)B. Then we - * double one of kB or (k+1)B, and depending on which one we - * choose, we end up with (2k)B or (2k+2)B. Either way, that - * differs by B from the other value we've just computed. So in - * each iteration, we do one diff-add and one doubling, plus a - * couple of conditional swaps to choose which value we double and - * which way round we put the output points, and the effect is to - * replace k with either 2k or 2k+1, which we choose based on the - * appropriate bit of the desired exponent. - * - * This routine doesn't assume we know the exact location of the - * topmost set bit of the exponent. So to maintain constant time - * it does an iteration for every _potential_ bit, starting from - * the top downwards; after each iteration in which we haven't - * seen a set exponent bit yet, we just overwrite the two points - * with B and 2B again, - */ - - MontgomeryPoint *two_B = ecc_montgomery_double(B); - MontgomeryPoint *k_B = ecc_montgomery_point_copy(B); - MontgomeryPoint *kplus1_B = ecc_montgomery_point_copy(two_B); - - unsigned not_started_yet = 1; - for (size_t bitindex = mp_max_bits(n); bitindex-- > 0 ;) { - unsigned nbit = mp_get_bit(n, bitindex); - - MontgomeryPoint *sum = ecc_montgomery_diff_add(k_B, kplus1_B, B); - ecc_montgomery_cond_swap(k_B, kplus1_B, nbit); - MontgomeryPoint *other = ecc_montgomery_double(k_B); - ecc_montgomery_point_free(k_B); - ecc_montgomery_point_free(kplus1_B); - k_B = other; - kplus1_B = sum; - ecc_montgomery_cond_swap(k_B, kplus1_B, nbit); - - ecc_montgomery_cond_overwrite(k_B, B, not_started_yet); - ecc_montgomery_cond_overwrite(kplus1_B, two_B, not_started_yet); - not_started_yet &= ~nbit; - } - - ecc_montgomery_point_free(two_B); - ecc_montgomery_point_free(kplus1_B); - return k_B; -} - -void ecc_montgomery_get_affine(MontgomeryPoint *mp, mp_int **x) -{ - MontgomeryCurve *mc = mp->mc; - - ecc_montgomery_normalise(mp); - - if (x) - *x = monty_export(mc->mc, mp->X); -} - -unsigned ecc_montgomery_is_identity(MontgomeryPoint *mp) -{ - return mp_eq_integer(mp->Z, 0); -} - -/* ---------------------------------------------------------------------- - * Twisted Edwards curves. - */ - -struct EdwardsPoint { - /* - * We represent an Edwards curve point in 'extended coordinates'. - * There's more than one coordinate system going by that name, - * unfortunately. These ones have the semantics that X,Y,Z are - * ordinary projective coordinates (so x=X/Z and y=Y/Z), but also, - * we store the extra value T = xyZ = XY/Z. - */ - mp_int *X, *Y, *Z, *T; - - EdwardsCurve *ec; -}; - -struct EdwardsCurve { - /* Prime modulus of the finite field. */ - mp_int *p; - - /* Montgomery context for arithmetic mod p. */ - MontyContext *mc; - - /* Modsqrt context for point decompression. */ - ModsqrtContext *sc; - - /* Parameters of the curve, in Montgomery-multiplication - * transformed form. */ - mp_int *d, *a; -}; - -EdwardsCurve *ecc_edwards_curve(mp_int *p, mp_int *d, mp_int *a, - mp_int *nonsquare_mod_p) -{ - EdwardsCurve *ec = snew(EdwardsCurve); - ec->p = mp_copy(p); - ec->mc = monty_new(p); - ec->d = monty_import(ec->mc, d); - ec->a = monty_import(ec->mc, a); - - if (nonsquare_mod_p) - ec->sc = modsqrt_new(p, nonsquare_mod_p); - else - ec->sc = NULL; - - return ec; -} - -void ecc_edwards_curve_free(EdwardsCurve *ec) -{ - mp_free(ec->p); - mp_free(ec->d); - mp_free(ec->a); - monty_free(ec->mc); - if (ec->sc) - modsqrt_free(ec->sc); - sfree(ec); -} - -static EdwardsPoint *ecc_edwards_point_new_empty(EdwardsCurve *ec) -{ - EdwardsPoint *ep = snew(EdwardsPoint); - ep->ec = ec; - ep->X = ep->Y = ep->Z = ep->T = NULL; - return ep; -} - -static EdwardsPoint *ecc_edwards_point_new_imported( - EdwardsCurve *ec, mp_int *monty_x, mp_int *monty_y) -{ - EdwardsPoint *ep = ecc_edwards_point_new_empty(ec); - ep->X = monty_x; - ep->Y = monty_y; - ep->T = monty_mul(ec->mc, ep->X, ep->Y); - ep->Z = mp_copy(monty_identity(ec->mc)); - return ep; -} - -EdwardsPoint *ecc_edwards_point_new( - EdwardsCurve *ec, mp_int *x, mp_int *y) -{ - return ecc_edwards_point_new_imported( - ec, monty_import(ec->mc, x), monty_import(ec->mc, y)); -} - -void ecc_edwards_point_copy_into(EdwardsPoint *dest, EdwardsPoint *src) -{ - mp_copy_into(dest->X, src->X); - mp_copy_into(dest->Y, src->Y); - mp_copy_into(dest->Z, src->Z); - mp_copy_into(dest->T, src->T); -} - -EdwardsPoint *ecc_edwards_point_copy(EdwardsPoint *orig) -{ - EdwardsPoint *ep = ecc_edwards_point_new_empty(orig->ec); - ep->X = mp_copy(orig->X); - ep->Y = mp_copy(orig->Y); - ep->Z = mp_copy(orig->Z); - ep->T = mp_copy(orig->T); - return ep; -} - -void ecc_edwards_point_free(EdwardsPoint *ep) -{ - mp_free(ep->X); - mp_free(ep->Y); - mp_free(ep->Z); - mp_free(ep->T); - smemclr(ep, sizeof(*ep)); - sfree(ep); -} - -EdwardsPoint *ecc_edwards_point_new_from_y( - EdwardsCurve *ec, mp_int *yorig, unsigned desired_x_parity) -{ - assert(ec->sc); - - /* - * The curve equation is ax^2 + y^2 = 1 + dx^2y^2, which - * rearranges to x^2(dy^2-a) = y^2-1. So we compute - * (y^2-1)/(dy^2-a) and take its square root. - */ - unsigned success; - - mp_int *y = monty_import(ec->mc, yorig); - mp_int *y2 = monty_mul(ec->mc, y, y); - mp_int *dy2 = monty_mul(ec->mc, ec->d, y2); - mp_int *dy2ma = monty_sub(ec->mc, dy2, ec->a); - mp_int *y2m1 = monty_sub(ec->mc, y2, monty_identity(ec->mc)); - mp_int *recip_denominator = monty_invert(ec->mc, dy2ma); - mp_int *radicand = monty_mul(ec->mc, y2m1, recip_denominator); - mp_int *x = monty_modsqrt(ec->sc, radicand, &success); - mp_free(y2); - mp_free(dy2); - mp_free(dy2ma); - mp_free(y2m1); - mp_free(recip_denominator); - mp_free(radicand); - - if (!success) { - /* Failure! x^2 worked out to be a number that has no square - * root mod p. In this situation there's no point in trying to - * be time-constant, since the protocol sequence is going to - * diverge anyway when we complain to whoever gave us this - * bogus value. */ - mp_free(x); - mp_free(y); - return NULL; - } - - /* - * Choose whichever of x and p-x has the specified parity (of its - * lowest positive residue mod p). - */ - mp_int *tmp = monty_export(ec->mc, x); - unsigned flip = (mp_get_bit(tmp, 0) ^ desired_x_parity) & 1; - mp_sub_into(tmp, ec->p, x); - mp_select_into(x, x, tmp, flip); - mp_free(tmp); - - return ecc_edwards_point_new_imported(ec, x, y); -} - -static void ecc_edwards_cond_overwrite( - EdwardsPoint *dest, EdwardsPoint *src, unsigned overwrite) -{ - mp_select_into(dest->X, dest->X, src->X, overwrite); - mp_select_into(dest->Y, dest->Y, src->Y, overwrite); - mp_select_into(dest->Z, dest->Z, src->Z, overwrite); - mp_select_into(dest->T, dest->T, src->T, overwrite); -} - -static void ecc_edwards_cond_swap( - EdwardsPoint *P, EdwardsPoint *Q, unsigned swap) -{ - mp_cond_swap(P->X, Q->X, swap); - mp_cond_swap(P->Y, Q->Y, swap); - mp_cond_swap(P->Z, Q->Z, swap); - mp_cond_swap(P->T, Q->T, swap); -} - -EdwardsPoint *ecc_edwards_add(EdwardsPoint *P, EdwardsPoint *Q) -{ - EdwardsCurve *ec = P->ec; - assert(Q->ec == ec); - - EdwardsPoint *S = ecc_edwards_point_new_empty(ec); - - /* - * The affine rule for Edwards addition of (x1,y1) and (x2,y2) is - * - * x_out = (x1 y2 + y1 x2) / (1 + d x1 x2 y1 y2) - * y_out = (y1 y2 - a x1 x2) / (1 - d x1 x2 y1 y2) - * - * The formulae below are listed as 'add-2008-hwcd' in - * https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html - * - * and if you undo the careful optimisation to find out what - * they're actually computing, it comes out to - * - * X_out = (X1 Y2 + Y1 X2) (Z1 Z2 - d T1 T2) - * Y_out = (Y1 Y2 - a X1 X2) (Z1 Z2 + d T1 T2) - * Z_out = (Z1 Z2 - d T1 T2) (Z1 Z2 + d T1 T2) - * T_out = (X1 Y2 + Y1 X2) (Y1 Y2 - a X1 X2) - */ - mp_int *PxQx = monty_mul(ec->mc, P->X, Q->X); - mp_int *PyQy = monty_mul(ec->mc, P->Y, Q->Y); - mp_int *PtQt = monty_mul(ec->mc, P->T, Q->T); - mp_int *PzQz = monty_mul(ec->mc, P->Z, Q->Z); - mp_int *Psum = monty_add(ec->mc, P->X, P->Y); - mp_int *Qsum = monty_add(ec->mc, Q->X, Q->Y); - mp_int *aPxQx = monty_mul(ec->mc, ec->a, PxQx); - mp_int *dPtQt = monty_mul(ec->mc, ec->d, PtQt); - mp_int *sumprod = monty_mul(ec->mc, Psum, Qsum); - mp_int *xx_p_yy = monty_add(ec->mc, PxQx, PyQy); - mp_int *E = monty_sub(ec->mc, sumprod, xx_p_yy); - mp_int *F = monty_sub(ec->mc, PzQz, dPtQt); - mp_int *G = monty_add(ec->mc, PzQz, dPtQt); - mp_int *H = monty_sub(ec->mc, PyQy, aPxQx); - S->X = monty_mul(ec->mc, E, F); - S->Z = monty_mul(ec->mc, F, G); - S->Y = monty_mul(ec->mc, G, H); - S->T = monty_mul(ec->mc, H, E); - - mp_free(PxQx); - mp_free(PyQy); - mp_free(PtQt); - mp_free(PzQz); - mp_free(Psum); - mp_free(Qsum); - mp_free(aPxQx); - mp_free(dPtQt); - mp_free(sumprod); - mp_free(xx_p_yy); - mp_free(E); - mp_free(F); - mp_free(G); - mp_free(H); - - return S; -} - -static void ecc_edwards_normalise(EdwardsPoint *ep) -{ - EdwardsCurve *ec = ep->ec; - mp_int *zinv = monty_invert(ec->mc, ep->Z); - monty_mul_into(ec->mc, ep->X, ep->X, zinv); - monty_mul_into(ec->mc, ep->Y, ep->Y, zinv); - monty_mul_into(ec->mc, ep->Z, ep->Z, zinv); - mp_free(zinv); - monty_mul_into(ec->mc, ep->T, ep->X, ep->Y); -} - -EdwardsPoint *ecc_edwards_multiply(EdwardsPoint *B, mp_int *n) -{ - EdwardsPoint *two_B = ecc_edwards_add(B, B); - EdwardsPoint *k_B = ecc_edwards_point_copy(B); - EdwardsPoint *kplus1_B = ecc_edwards_point_copy(two_B); - - /* - * Another copy of the same exponentiation routine following the - * pattern of the Montgomery ladder, because it works as well as - * any other technique and this way I didn't have to debug two of - * them. - */ - - unsigned not_started_yet = 1; - for (size_t bitindex = mp_max_bits(n); bitindex-- > 0 ;) { - unsigned nbit = mp_get_bit(n, bitindex); - - EdwardsPoint *sum = ecc_edwards_add(k_B, kplus1_B); - ecc_edwards_cond_swap(k_B, kplus1_B, nbit); - EdwardsPoint *other = ecc_edwards_add(k_B, k_B); - ecc_edwards_point_free(k_B); - ecc_edwards_point_free(kplus1_B); - k_B = other; - kplus1_B = sum; - ecc_edwards_cond_swap(k_B, kplus1_B, nbit); - - ecc_edwards_cond_overwrite(k_B, B, not_started_yet); - ecc_edwards_cond_overwrite(kplus1_B, two_B, not_started_yet); - not_started_yet &= ~nbit; - } - - ecc_edwards_point_free(two_B); - ecc_edwards_point_free(kplus1_B); - return k_B; -} - -/* - * Helper routine to determine whether two values each given as a pair - * of projective coordinates represent the same affine value. - */ -static inline unsigned projective_eq( - MontyContext *mc, mp_int *An, mp_int *Ad, - mp_int *Bn, mp_int *Bd) -{ - mp_int *AnBd = monty_mul(mc, An, Bd); - mp_int *BnAd = monty_mul(mc, Bn, Ad); - unsigned toret = mp_cmp_eq(AnBd, BnAd); - mp_free(AnBd); - mp_free(BnAd); - return toret; -} - -unsigned ecc_edwards_eq(EdwardsPoint *P, EdwardsPoint *Q) -{ - EdwardsCurve *ec = P->ec; - assert(Q->ec == ec); - - return (projective_eq(ec->mc, P->X, P->Z, Q->X, Q->Z) & - projective_eq(ec->mc, P->Y, P->Z, Q->Y, Q->Z)); -} - -void ecc_edwards_get_affine(EdwardsPoint *ep, mp_int **x, mp_int **y) -{ - EdwardsCurve *ec = ep->ec; - - ecc_edwards_normalise(ep); - - if (x) - *x = monty_export(ec->mc, ep->X); - if (y) - *y = monty_export(ec->mc, ep->Y); -} diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c deleted file mode 100644 index 10ed68e61..000000000 --- a/crypto/ecc-ssh.c +++ /dev/null @@ -1,1802 +0,0 @@ -/* - * Elliptic-curve signing and key exchange for PuTTY's SSH layer. - */ - -/* - * References: - * - * Elliptic curves in SSH are specified in RFC 5656: - * https://www.rfc-editor.org/rfc/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" -#include "mpint.h" -#include "ecc.h" - -/* ---------------------------------------------------------------------- - * Elliptic curve definitions - */ - -static void initialise_common( - struct ec_curve *curve, EllipticCurveType type, mp_int *p, - unsigned extrabits) -{ - curve->type = type; - curve->p = mp_copy(p); - curve->fieldBits = mp_get_nbits(p); - curve->fieldBytes = (curve->fieldBits + extrabits + 7) / 8; -} - -static void initialise_wcurve( - struct ec_curve *curve, mp_int *p, mp_int *a, mp_int *b, - mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order) -{ - initialise_common(curve, EC_WEIERSTRASS, p, 0); - - curve->w.wc = ecc_weierstrass_curve(p, a, b, nonsquare); - - curve->w.G = ecc_weierstrass_point_new(curve->w.wc, G_x, G_y); - curve->w.G_order = mp_copy(G_order); -} - -static void initialise_mcurve( - struct ec_curve *curve, mp_int *p, mp_int *a, mp_int *b, - mp_int *G_x, unsigned log2_cofactor) -{ - initialise_common(curve, EC_MONTGOMERY, p, 0); - - curve->m.mc = ecc_montgomery_curve(p, a, b); - curve->m.log2_cofactor = log2_cofactor; - - curve->m.G = ecc_montgomery_point_new(curve->m.mc, G_x); -} - -static void initialise_ecurve( - struct ec_curve *curve, mp_int *p, mp_int *d, mp_int *a, - mp_int *nonsquare, mp_int *G_x, mp_int *G_y, mp_int *G_order, - unsigned log2_cofactor) -{ - /* Ensure curve->fieldBytes is long enough to store an extra bit - * for a compressed point */ - initialise_common(curve, EC_EDWARDS, p, 1); - - curve->e.ec = ecc_edwards_curve(p, d, a, nonsquare); - curve->e.log2_cofactor = log2_cofactor; - - curve->e.G = ecc_edwards_point_new(curve->e.ec, G_x, G_y); - curve->e.G_order = mp_copy(G_order); -} - -static struct ec_curve *ec_p256(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) { - mp_int *p = MP_LITERAL(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff); - mp_int *a = MP_LITERAL(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc); - mp_int *b = MP_LITERAL(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b); - mp_int *G_x = MP_LITERAL(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296); - mp_int *G_y = MP_LITERAL(0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5); - mp_int *G_order = MP_LITERAL(0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551); - mp_int *nonsquare_mod_p = mp_from_integer(3); - initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order); - mp_free(p); - mp_free(a); - mp_free(b); - mp_free(G_x); - mp_free(G_y); - mp_free(G_order); - mp_free(nonsquare_mod_p); - - curve.textname = curve.name = "nistp256"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -static struct ec_curve *ec_p384(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) { - mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff); - mp_int *a = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc); - mp_int *b = MP_LITERAL(0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef); - mp_int *G_x = MP_LITERAL(0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7); - mp_int *G_y = MP_LITERAL(0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f); - mp_int *G_order = MP_LITERAL(0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973); - mp_int *nonsquare_mod_p = mp_from_integer(19); - initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order); - mp_free(p); - mp_free(a); - mp_free(b); - mp_free(G_x); - mp_free(G_y); - mp_free(G_order); - mp_free(nonsquare_mod_p); - - curve.textname = curve.name = "nistp384"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -static struct ec_curve *ec_p521(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) { - mp_int *p = MP_LITERAL(0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - mp_int *a = MP_LITERAL(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc); - mp_int *b = MP_LITERAL(0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00); - mp_int *G_x = MP_LITERAL(0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66); - mp_int *G_y = MP_LITERAL(0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650); - mp_int *G_order = MP_LITERAL(0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409); - mp_int *nonsquare_mod_p = mp_from_integer(3); - initialise_wcurve(&curve, p, a, b, nonsquare_mod_p, G_x, G_y, G_order); - mp_free(p); - mp_free(a); - mp_free(b); - mp_free(G_x); - mp_free(G_y); - mp_free(G_order); - mp_free(nonsquare_mod_p); - - curve.textname = curve.name = "nistp521"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -static struct ec_curve *ec_curve25519(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) { - mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed); - mp_int *a = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000076d06); - mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000001); - mp_int *G_x = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000009); - initialise_mcurve(&curve, p, a, b, G_x, 3); - mp_free(p); - mp_free(a); - mp_free(b); - mp_free(G_x); - - /* 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 = true; - } - - return &curve; -} - -static struct ec_curve *ec_curve448(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) { - mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - mp_int *a = MP_LITERAL(0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000262a6); - mp_int *b = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001); - mp_int *G_x = MP_LITERAL(0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005); - initialise_mcurve(&curve, p, a, b, G_x, 2); - mp_free(p); - mp_free(a); - mp_free(b); - mp_free(G_x); - - /* 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 = "Curve448"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -static struct ec_curve *ec_ed25519(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) { - mp_int *p = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed); - mp_int *d = MP_LITERAL(0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3); - mp_int *a = MP_LITERAL(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec); /* == p-1 */ - mp_int *G_x = MP_LITERAL(0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a); - mp_int *G_y = MP_LITERAL(0x6666666666666666666666666666666666666666666666666666666666666658); - mp_int *G_order = MP_LITERAL(0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed); - mp_int *nonsquare_mod_p = mp_from_integer(2); - initialise_ecurve(&curve, p, d, a, nonsquare_mod_p, - G_x, G_y, G_order, 3); - mp_free(p); - mp_free(d); - mp_free(a); - mp_free(G_x); - mp_free(G_y); - mp_free(G_order); - mp_free(nonsquare_mod_p); - - /* 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 = "Ed25519"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -static struct ec_curve *ec_ed448(void) -{ - static struct ec_curve curve = { 0 }; - static bool initialised = false; - - if (!initialised) { - mp_int *p = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - mp_int *d = MP_LITERAL(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff6756); /* = p - 39081 */ - mp_int *a = MP_LITERAL(0x1); - mp_int *G_x = MP_LITERAL(0x4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e); - mp_int *G_y = MP_LITERAL(0x693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14); - mp_int *G_order = MP_LITERAL(0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3); - mp_int *nonsquare_mod_p = mp_from_integer(7); - initialise_ecurve(&curve, p, d, a, nonsquare_mod_p, - G_x, G_y, G_order, 2); - mp_free(p); - mp_free(d); - mp_free(a); - mp_free(G_x); - mp_free(G_y); - mp_free(G_order); - mp_free(nonsquare_mod_p); - - /* 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 = "Ed448"; - - /* Now initialised, no need to do it again */ - initialised = true; - } - - return &curve; -} - -/* ---------------------------------------------------------------------- - * Public point from private - */ - -struct ecsign_extra { - struct ec_curve *(*curve)(void); - const ssh_hashalg *hash; - - /* These fields are used by the OpenSSH PEM format importer/exporter */ - const unsigned char *oid; - int oidlen; - - /* Human-readable algorithm description */ - const char *alg_desc; - - /* Some EdDSA instances prefix a string to all hash preimages, to - * disambiguate which signature variant they're being used with */ - ptrlen hash_prefix; -}; - -WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - assert(curve->type == EC_WEIERSTRASS); - - mp_int *priv_reduced = mp_mod(private_key, curve->p); - WeierstrassPoint *toret = ecc_weierstrass_multiply( - curve->w.G, priv_reduced); - mp_free(priv_reduced); - return toret; -} - -static mp_int *eddsa_exponent_from_hash( - ptrlen hash, const struct ec_curve *curve) -{ - /* - * Make an integer out of the hash data, little-endian. - */ - assert(hash.len >= curve->fieldBytes); - mp_int *e = mp_from_bytes_le(make_ptrlen(hash.ptr, curve->fieldBytes)); - - /* - * Set the highest bit that fits in the modulus, and clear any - * above that. - */ - mp_set_bit(e, curve->fieldBits - 1, 1); - mp_reduce_mod_2to(e, curve->fieldBits); - - /* - * Clear a curve-specific number of low bits. - */ - for (unsigned bit = 0; bit < curve->e.log2_cofactor; bit++) - mp_set_bit(e, bit, 0); - - return e; -} - -EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - assert(curve->type == EC_EDWARDS); - - ssh_hash *h = ssh_hash_new(extra->hash); - for (size_t i = 0; i < curve->fieldBytes; ++i) - put_byte(h, mp_get_byte(private_key, i)); - - unsigned char hash[MAX_HASH_LEN]; - ssh_hash_final(h, hash); - - mp_int *exponent = eddsa_exponent_from_hash( - make_ptrlen(hash, extra->hash->hlen), curve); - - EdwardsPoint *toret = ecc_edwards_multiply(curve->e.G, exponent); - mp_free(exponent); - - return toret; -} - -/* ---------------------------------------------------------------------- - * Marshalling and unmarshalling functions - */ - -static mp_int *BinarySource_get_mp_le(BinarySource *src) -{ - return mp_from_bytes_le(get_string(src)); -} -#define get_mp_le(src) BinarySource_get_mp_le(BinarySource_UPCAST(src)) - -static void BinarySink_put_mp_le_fixedlen(BinarySink *bs, mp_int *x, - size_t bytes) -{ - put_uint32(bs, bytes); - for (size_t i = 0; i < bytes; ++i) - put_byte(bs, mp_get_byte(x, i)); -} -#define put_mp_le_fixedlen(bs, x, bytes) \ - BinarySink_put_mp_le_fixedlen(BinarySink_UPCAST(bs), x, bytes) - -static WeierstrassPoint *ecdsa_decode( - ptrlen encoded, const struct ec_curve *curve) -{ - assert(curve->type == EC_WEIERSTRASS); - BinarySource src[1]; - - BinarySource_BARE_INIT_PL(src, encoded); - unsigned char format_type = get_byte(src); - - WeierstrassPoint *P; - - size_t len = get_avail(src); - mp_int *x; - mp_int *y; - - switch (format_type) { - case 0: - /* The identity. */ - P = ecc_weierstrass_point_new_identity(curve->w.wc); - break; - case 2: - case 3: - /* A compressed point, in which the x-coordinate is stored in - * full, and y is deduced from that and a single bit - * indicating its parity (stored in the format type byte). */ - x = mp_from_bytes_be(get_data(src, len)); - P = ecc_weierstrass_point_new_from_x(curve->w.wc, x, format_type & 1); - mp_free(x); - if (!P) /* this can fail if the input is invalid */ - return NULL; - break; - case 4: - /* An uncompressed point: the x,y coordinates are stored in - * full. We expect the rest of the string to have even length, - * and be divided half and half between the two values. */ - if (len % 2 != 0) - return NULL; - len /= 2; - x = mp_from_bytes_be(get_data(src, len)); - y = mp_from_bytes_be(get_data(src, len)); - P = ecc_weierstrass_point_new(curve->w.wc, x, y); - mp_free(x); - mp_free(y); - break; - default: - /* An unrecognised type byte. */ - return NULL; - } - - /* Verify the point is on the curve */ - if (!ecc_weierstrass_point_valid(P)) { - ecc_weierstrass_point_free(P); - return NULL; - } - - return P; -} - -static WeierstrassPoint *BinarySource_get_wpoint( - BinarySource *src, const struct ec_curve *curve) -{ - ptrlen str = get_string(src); - if (get_err(src)) - return NULL; - return ecdsa_decode(str, curve); -} -#define get_wpoint(src, curve) \ - BinarySource_get_wpoint(BinarySource_UPCAST(src), curve) - -static void BinarySink_put_wpoint( - BinarySink *bs, WeierstrassPoint *point, const struct ec_curve *curve, - bool bare) -{ - strbuf *sb; - BinarySink *bs_inner; - - if (!bare) { - /* - * Encapsulate the raw data inside an outermost string layer. - */ - sb = strbuf_new(); - bs_inner = BinarySink_UPCAST(sb); - } else { - /* - * Just write the data directly to the output. - */ - bs_inner = bs; - } - - if (ecc_weierstrass_is_identity(point)) { - put_byte(bs_inner, 0); - } else { - mp_int *x, *y; - ecc_weierstrass_get_affine(point, &x, &y); - - /* - * For ECDSA, we only ever output uncompressed points. - */ - put_byte(bs_inner, 0x04); - for (size_t i = curve->fieldBytes; i--;) - put_byte(bs_inner, mp_get_byte(x, i)); - for (size_t i = curve->fieldBytes; i--;) - put_byte(bs_inner, mp_get_byte(y, i)); - - mp_free(x); - mp_free(y); - } - - if (!bare) - put_stringsb(bs, sb); -} -#define put_wpoint(bs, point, curve, bare) \ - BinarySink_put_wpoint(BinarySink_UPCAST(bs), point, curve, bare) - -static EdwardsPoint *eddsa_decode(ptrlen encoded, const struct ec_curve *curve) -{ - assert(curve->type == EC_EDWARDS); - - mp_int *y = mp_from_bytes_le(encoded); - - /* The topmost bit of the encoding isn't part of y, so it stores - * the bottom bit of x. Extract it, and zero that bit in y. */ - unsigned desired_x_parity = mp_get_bit(y, curve->fieldBytes * 8 - 1); - mp_set_bit(y, curve->fieldBytes * 8 - 1, 0); - - /* What's left should now be within the range of the curve's modulus */ - if (mp_cmp_hs(y, curve->p)) { - mp_free(y); - return NULL; - } - - EdwardsPoint *P = ecc_edwards_point_new_from_y( - curve->e.ec, y, desired_x_parity); - mp_free(y); - - /* A point constructed in this way will always satisfy the curve - * equation, unless ecc-arithmetic.c wasn't able to construct one - * at all, in which case P is now NULL. Either way, return it. */ - return P; -} - -static EdwardsPoint *BinarySource_get_epoint( - BinarySource *src, const struct ec_curve *curve) -{ - ptrlen str = get_string(src); - if (get_err(src)) - return NULL; - return eddsa_decode(str, curve); -} -#define get_epoint(src, curve) \ - BinarySource_get_epoint(BinarySource_UPCAST(src), curve) - -static void BinarySink_put_epoint( - BinarySink *bs, EdwardsPoint *point, const struct ec_curve *curve, - bool bare) -{ - mp_int *x, *y; - ecc_edwards_get_affine(point, &x, &y); - - assert(curve->fieldBytes >= 2); - - /* - * EdDSA requires point compression. We store a single integer, - * with bytes in little-endian order, which mostly contains y but - * in which the topmost bit is the low bit of x. - */ - if (!bare) - put_uint32(bs, curve->fieldBytes); /* string length field */ - for (size_t i = 0; i < curve->fieldBytes - 1; i++) - put_byte(bs, mp_get_byte(y, i)); - put_byte(bs, (mp_get_byte(y, curve->fieldBytes - 1) & 0x7F) | - (mp_get_bit(x, 0) << 7)); - - mp_free(x); - mp_free(y); -} -#define put_epoint(bs, point, curve, bare) \ - BinarySink_put_epoint(BinarySink_UPCAST(bs), point, curve, bare) - -/* ---------------------------------------------------------------------- - * Exposed ECDSA interface - */ - -static void ecdsa_freekey(ssh_key *key) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - - if (ek->publicKey) - ecc_weierstrass_point_free(ek->publicKey); - if (ek->privateKey) - mp_free(ek->privateKey); - sfree(ek); -} - -static void eddsa_freekey(ssh_key *key) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - - if (ek->publicKey) - ecc_edwards_point_free(ek->publicKey); - if (ek->privateKey) - mp_free(ek->privateKey); - sfree(ek); -} - -static char *ec_signkey_invalid(ssh_key *key, unsigned flags) -{ - /* All validity criteria for both ECDSA and EdDSA were checked - * when we loaded the key in the first place */ - return NULL; -} - -static ssh_key *ecdsa_new_pub(const ssh_keyalg *alg, ptrlen data) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - assert(curve->type == EC_WEIERSTRASS); - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, data); - get_string(src); - - /* Curve name is duplicated for Weierstrass form */ - if (!ptrlen_eq_string(get_string(src), curve->name)) - return NULL; - - struct ecdsa_key *ek = snew(struct ecdsa_key); - ek->sshk.vt = alg; - ek->curve = curve; - ek->privateKey = NULL; - - ek->publicKey = get_wpoint(src, curve); - if (!ek->publicKey) { - ecdsa_freekey(&ek->sshk); - return NULL; - } - - return &ek->sshk; -} - -static ssh_key *eddsa_new_pub(const ssh_keyalg *alg, ptrlen data) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - assert(curve->type == EC_EDWARDS); - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, data); - get_string(src); - - struct eddsa_key *ek = snew(struct eddsa_key); - ek->sshk.vt = alg; - ek->curve = curve; - ek->privateKey = NULL; - - ek->publicKey = get_epoint(src, curve); - if (!ek->publicKey) { - eddsa_freekey(&ek->sshk); - return NULL; - } - - return &ek->sshk; -} - -static char *ecc_cache_str_shared( - const char *curve_name, mp_int *x, mp_int *y) -{ - strbuf *sb = strbuf_new(); - - if (curve_name) - put_fmt(sb, "%s,", curve_name); - - char *hx = mp_get_hex(x); - char *hy = mp_get_hex(y); - put_fmt(sb, "0x%s,0x%s", hx, hy); - sfree(hx); - sfree(hy); - - return strbuf_to_str(sb); -} - -static char *ecdsa_cache_str(ssh_key *key) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - mp_int *x, *y; - - ecc_weierstrass_get_affine(ek->publicKey, &x, &y); - char *toret = ecc_cache_str_shared(ek->curve->name, x, y); - mp_free(x); - mp_free(y); - return toret; -} - -static key_components *ecdsa_components(ssh_key *key) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - key_components *kc = key_components_new(); - - key_components_add_text(kc, "key_type", "ECDSA"); - key_components_add_text(kc, "curve_name", ek->curve->textname); - - mp_int *x, *y; - ecc_weierstrass_get_affine(ek->publicKey, &x, &y); - key_components_add_mp(kc, "public_affine_x", x); - key_components_add_mp(kc, "public_affine_y", y); - mp_free(x); - mp_free(y); - - if (ek->privateKey) - key_components_add_mp(kc, "private_exponent", ek->privateKey); - - return kc; -} - -static char *eddsa_cache_str(ssh_key *key) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - mp_int *x, *y; - - ecc_edwards_get_affine(ek->publicKey, &x, &y); - char *toret = ecc_cache_str_shared(ek->curve->name, x, y); - mp_free(x); - mp_free(y); - return toret; -} - -static key_components *eddsa_components(ssh_key *key) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - key_components *kc = key_components_new(); - - key_components_add_text(kc, "key_type", "EdDSA"); - key_components_add_text(kc, "curve_name", ek->curve->textname); - - mp_int *x, *y; - ecc_edwards_get_affine(ek->publicKey, &x, &y); - key_components_add_mp(kc, "public_affine_x", x); - key_components_add_mp(kc, "public_affine_y", y); - mp_free(x); - mp_free(y); - - if (ek->privateKey) - key_components_add_mp(kc, "private_exponent", ek->privateKey); - - return kc; -} - -static void ecdsa_public_blob(ssh_key *key, BinarySink *bs) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - - put_stringz(bs, ek->sshk.vt->ssh_id); - put_stringz(bs, ek->curve->name); - put_wpoint(bs, ek->publicKey, ek->curve, false); -} - -static void eddsa_public_blob(ssh_key *key, BinarySink *bs) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - - put_stringz(bs, ek->sshk.vt->ssh_id); - put_epoint(bs, ek->publicKey, ek->curve, false); -} - -static void ecdsa_private_blob(ssh_key *key, BinarySink *bs) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - - /* ECDSA uses ordinary SSH-2 mpint format to store the private key */ - assert(ek->privateKey); - put_mp_ssh2(bs, ek->privateKey); -} - -static bool ecdsa_has_private(ssh_key *key) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - return ek->privateKey != NULL; -} - -static void eddsa_private_blob(ssh_key *key, BinarySink *bs) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - - /* EdDSA stores the private key integer little-endian and unsigned */ - assert(ek->privateKey); - put_mp_le_fixedlen(bs, ek->privateKey, ek->curve->fieldBytes); -} - -static bool eddsa_has_private(ssh_key *key) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - return ek->privateKey != NULL; -} - -static ssh_key *ecdsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv) -{ - ssh_key *sshk = ecdsa_new_pub(alg, pub); - if (!sshk) - return NULL; - struct ecdsa_key *ek = container_of(sshk, struct ecdsa_key, sshk); - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, priv); - ek->privateKey = get_mp_ssh2(src); - - return &ek->sshk; -} - -static ssh_key *eddsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv) -{ - ssh_key *sshk = eddsa_new_pub(alg, pub); - if (!sshk) - return NULL; - struct eddsa_key *ek = container_of(sshk, struct eddsa_key, sshk); - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, priv); - ek->privateKey = get_mp_le(src); - - return &ek->sshk; -} - -static ssh_key *eddsa_new_priv_openssh( - const ssh_keyalg *alg, BinarySource *src) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - assert(curve->type == EC_EDWARDS); - - ptrlen pubkey_pl = get_string(src); - ptrlen privkey_extended_pl = get_string(src); - if (get_err(src) || pubkey_pl.len != curve->fieldBytes) - return NULL; - - /* - * 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. - * - * We assume that Ed448 will work the same way, as and when - * OpenSSH implements it, which at the time of writing this they - * had not. - */ - BinarySource subsrc[1]; - BinarySource_BARE_INIT_PL(subsrc, privkey_extended_pl); - ptrlen privkey_pl = get_data(subsrc, curve->fieldBytes); - ptrlen pubkey_copy_pl = get_data(subsrc, curve->fieldBytes); - if (get_err(subsrc) || get_avail(subsrc)) - return NULL; - if (!ptrlen_eq_ptrlen(pubkey_pl, pubkey_copy_pl)) - return NULL; - - struct eddsa_key *ek = snew(struct eddsa_key); - ek->sshk.vt = alg; - ek->curve = curve; - ek->privateKey = NULL; - - ek->publicKey = eddsa_decode(pubkey_pl, curve); - if (!ek->publicKey) { - eddsa_freekey(&ek->sshk); - return NULL; - } - - ek->privateKey = mp_from_bytes_le(privkey_pl); - - return &ek->sshk; -} - -static void eddsa_openssh_blob(ssh_key *key, BinarySink *bs) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - assert(ek->curve->type == EC_EDWARDS); - - /* Encode the public and private points as strings */ - strbuf *pub_sb = strbuf_new(); - put_epoint(pub_sb, ek->publicKey, ek->curve, false); - ptrlen pub = make_ptrlen(pub_sb->s + 4, pub_sb->len - 4); - - strbuf *priv_sb = strbuf_new_nm(); - put_mp_le_fixedlen(priv_sb, ek->privateKey, ek->curve->fieldBytes); - ptrlen priv = make_ptrlen(priv_sb->s + 4, priv_sb->len - 4); - - put_stringpl(bs, pub); - - /* Encode the private key as the concatenation of the - * little-endian key integer and the public key again */ - put_uint32(bs, priv.len + pub.len); - put_datapl(bs, priv); - put_datapl(bs, pub); - - strbuf_free(pub_sb); - strbuf_free(priv_sb); -} - -static ssh_key *ecdsa_new_priv_openssh( - const ssh_keyalg *alg, BinarySource *src) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - assert(curve->type == EC_WEIERSTRASS); - - get_string(src); - - struct ecdsa_key *ek = snew(struct ecdsa_key); - ek->sshk.vt = alg; - ek->curve = curve; - ek->privateKey = NULL; - - ek->publicKey = get_wpoint(src, curve); - if (!ek->publicKey) { - ecdsa_freekey(&ek->sshk); - return NULL; - } - - ek->privateKey = get_mp_ssh2(src); - - return &ek->sshk; -} - -static void ecdsa_openssh_blob(ssh_key *key, BinarySink *bs) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - put_stringz(bs, ek->curve->name); - put_wpoint(bs, ek->publicKey, ek->curve, false); - put_mp_ssh2(bs, ek->privateKey); -} - -static int ec_shared_pubkey_bits(const ssh_keyalg *alg, ptrlen blob) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)alg->extra; - struct ec_curve *curve = extra->curve(); - return curve->fieldBits; -} - -static mp_int *ecdsa_signing_exponent_from_data( - const struct ec_curve *curve, const struct ecsign_extra *extra, - ptrlen data) -{ - /* Hash the data being signed. */ - unsigned char hash[MAX_HASH_LEN]; - ssh_hash *h = ssh_hash_new(extra->hash); - put_datapl(h, data); - ssh_hash_final(h, hash); - - /* - * Take the leftmost b bits of the hash of the signed data (where - * b is the number of bits in order(G)), interpreted big-endian. - */ - mp_int *z = mp_from_bytes_be(make_ptrlen(hash, extra->hash->hlen)); - size_t zbits = mp_get_nbits(z); - size_t nbits = mp_get_nbits(curve->w.G_order); - size_t shift = zbits - nbits; - /* Bound the shift count below at 0, using bit twiddling to avoid - * a conditional branch */ - shift &= ~-(shift >> (CHAR_BIT * sizeof(size_t) - 1)); - mp_int *toret = mp_rshift_safe(z, shift); - mp_free(z); - - return toret; -} - -static bool ecdsa_verify(ssh_key *key, ptrlen sig, ptrlen data) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - const struct ecsign_extra *extra = - (const struct ecsign_extra *)ek->sshk.vt->extra; - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, sig); - - /* Check the signature starts with the algorithm name */ - if (!ptrlen_eq_string(get_string(src), ek->sshk.vt->ssh_id)) - return false; - - /* Everything else is nested inside a sub-string. Descend into that. */ - ptrlen sigstr = get_string(src); - if (get_err(src)) - return false; - BinarySource_BARE_INIT_PL(src, sigstr); - - /* Extract the signature integers r,s */ - mp_int *r = get_mp_ssh2(src); - mp_int *s = get_mp_ssh2(src); - if (get_err(src)) { - mp_free(r); - mp_free(s); - return false; - } - - /* Basic sanity checks: 0 < r,s < order(G) */ - unsigned invalid = 0; - invalid |= mp_eq_integer(r, 0); - invalid |= mp_eq_integer(s, 0); - invalid |= mp_cmp_hs(r, ek->curve->w.G_order); - invalid |= mp_cmp_hs(s, ek->curve->w.G_order); - - /* Get the hash of the signed data, converted to an integer */ - mp_int *z = ecdsa_signing_exponent_from_data(ek->curve, extra, data); - - /* Verify the signature integers against the hash */ - mp_int *w = mp_invert(s, ek->curve->w.G_order); - mp_int *u1 = mp_modmul(z, w, ek->curve->w.G_order); - mp_free(z); - mp_int *u2 = mp_modmul(r, w, ek->curve->w.G_order); - mp_free(w); - WeierstrassPoint *u1G = ecc_weierstrass_multiply(ek->curve->w.G, u1); - mp_free(u1); - WeierstrassPoint *u2P = ecc_weierstrass_multiply(ek->publicKey, u2); - mp_free(u2); - WeierstrassPoint *sum = ecc_weierstrass_add_general(u1G, u2P); - ecc_weierstrass_point_free(u1G); - ecc_weierstrass_point_free(u2P); - - mp_int *x; - ecc_weierstrass_get_affine(sum, &x, NULL); - ecc_weierstrass_point_free(sum); - - mp_divmod_into(x, ek->curve->w.G_order, NULL, x); - invalid |= (1 ^ mp_cmp_eq(r, x)); - mp_free(x); - - mp_free(r); - mp_free(s); - - return !invalid; -} - -static mp_int *eddsa_signing_exponent_from_data( - struct eddsa_key *ek, const struct ecsign_extra *extra, - ptrlen r_encoded, ptrlen data) -{ - /* Hash (r || public key || message) */ - unsigned char hash[MAX_HASH_LEN]; - ssh_hash *h = ssh_hash_new(extra->hash); - put_datapl(h, extra->hash_prefix); - put_datapl(h, r_encoded); - put_epoint(h, ek->publicKey, ek->curve, true); /* omit string header */ - put_datapl(h, data); - ssh_hash_final(h, hash); - - /* Convert to an integer */ - mp_int *toret = mp_from_bytes_le(make_ptrlen(hash, extra->hash->hlen)); - - smemclr(hash, extra->hash->hlen); - return toret; -} - -static bool eddsa_verify(ssh_key *key, ptrlen sig, ptrlen data) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - const struct ecsign_extra *extra = - (const struct ecsign_extra *)ek->sshk.vt->extra; - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, sig); - - /* Check the signature starts with the algorithm name */ - if (!ptrlen_eq_string(get_string(src), ek->sshk.vt->ssh_id)) - return false; - - /* Now expect a single string which is the concatenation of an - * encoded curve point r and an integer s. */ - ptrlen sigstr = get_string(src); - if (get_err(src)) - return false; - BinarySource_BARE_INIT_PL(src, sigstr); - ptrlen rstr = get_data(src, ek->curve->fieldBytes); - ptrlen sstr = get_data(src, ek->curve->fieldBytes); - if (get_err(src) || get_avail(src)) - return false; - - EdwardsPoint *r = eddsa_decode(rstr, ek->curve); - if (!r) - return false; - mp_int *s = mp_from_bytes_le(sstr); - - mp_int *H = eddsa_signing_exponent_from_data(ek, extra, rstr, data); - - /* Verify that s*G == r + H*publicKey */ - EdwardsPoint *lhs = ecc_edwards_multiply(ek->curve->e.G, s); - mp_free(s); - EdwardsPoint *hpk = ecc_edwards_multiply(ek->publicKey, H); - mp_free(H); - EdwardsPoint *rhs = ecc_edwards_add(r, hpk); - ecc_edwards_point_free(hpk); - unsigned valid = ecc_edwards_eq(lhs, rhs); - ecc_edwards_point_free(lhs); - ecc_edwards_point_free(rhs); - ecc_edwards_point_free(r); - - return valid; -} - -static void ecdsa_sign(ssh_key *key, ptrlen data, - unsigned flags, BinarySink *bs) -{ - struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); - const struct ecsign_extra *extra = - (const struct ecsign_extra *)ek->sshk.vt->extra; - assert(ek->privateKey); - - mp_int *z = ecdsa_signing_exponent_from_data(ek->curve, extra, data); - - /* Generate k between 1 and curve->n, using the same deterministic - * k generation system we use for conventional DSA. */ - mp_int *k; - { - unsigned char digest[20]; - hash_simple(&ssh_sha1, data, digest); - k = dsa_gen_k( - "ECDSA deterministic k generator", ek->curve->w.G_order, - ek->privateKey, digest, sizeof(digest)); - } - - WeierstrassPoint *kG = ecc_weierstrass_multiply(ek->curve->w.G, k); - mp_int *x; - ecc_weierstrass_get_affine(kG, &x, NULL); - ecc_weierstrass_point_free(kG); - - /* r = kG.x mod order(G) */ - mp_int *r = mp_mod(x, ek->curve->w.G_order); - mp_free(x); - - /* s = (z + r * priv)/k mod n */ - mp_int *rPriv = mp_modmul(r, ek->privateKey, ek->curve->w.G_order); - mp_int *numerator = mp_modadd(z, rPriv, ek->curve->w.G_order); - mp_free(z); - mp_free(rPriv); - mp_int *kInv = mp_invert(k, ek->curve->w.G_order); - mp_free(k); - mp_int *s = mp_modmul(numerator, kInv, ek->curve->w.G_order); - mp_free(numerator); - mp_free(kInv); - - /* Format the output */ - put_stringz(bs, ek->sshk.vt->ssh_id); - - strbuf *substr = strbuf_new(); - put_mp_ssh2(substr, r); - put_mp_ssh2(substr, s); - put_stringsb(bs, substr); - - mp_free(r); - mp_free(s); -} - -static void eddsa_sign(ssh_key *key, ptrlen data, - unsigned flags, BinarySink *bs) -{ - struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); - const struct ecsign_extra *extra = - (const struct ecsign_extra *)ek->sshk.vt->extra; - assert(ek->privateKey); - - /* - * EdDSA prescribes a specific method of generating the random - * nonce integer for the signature. (A verifier can't tell - * whether you followed that method, but it's important to - * follow it anyway, because test vectors will want a specific - * signature for a given message, and because this preserves - * determinism of signatures even if the same signature were - * made twice by different software.) - */ - - /* - * First, we hash the private key integer (bare, little-endian) - * into a hash generating 2*fieldBytes of output. - */ - unsigned char hash[MAX_HASH_LEN]; - ssh_hash *h = ssh_hash_new(extra->hash); - for (size_t i = 0; i < ek->curve->fieldBytes; ++i) - put_byte(h, mp_get_byte(ek->privateKey, i)); - ssh_hash_final(h, hash); - - /* - * The first half of the output hash is converted into an - * integer a, by the standard EdDSA transformation. - */ - mp_int *a = eddsa_exponent_from_hash( - make_ptrlen(hash, ek->curve->fieldBytes), ek->curve); - - /* - * The second half of the hash of the private key is hashed again - * with the message to be signed, and used as an exponent to - * generate the signature point r. - */ - h = ssh_hash_new(extra->hash); - put_datapl(h, extra->hash_prefix); - put_data(h, hash + ek->curve->fieldBytes, - extra->hash->hlen - ek->curve->fieldBytes); - put_datapl(h, data); - ssh_hash_final(h, hash); - mp_int *log_r_unreduced = mp_from_bytes_le( - make_ptrlen(hash, extra->hash->hlen)); - mp_int *log_r = mp_mod(log_r_unreduced, ek->curve->e.G_order); - mp_free(log_r_unreduced); - EdwardsPoint *r = ecc_edwards_multiply(ek->curve->e.G, log_r); - - /* - * Encode r now, because we'll need its encoding for the next - * hashing step as well as to write into the actual signature. - */ - strbuf *r_enc = strbuf_new(); - put_epoint(r_enc, r, ek->curve, true); /* omit string header */ - ecc_edwards_point_free(r); - - /* - * Compute the hash of (r || public key || message) just as - * eddsa_verify does. - */ - mp_int *H = eddsa_signing_exponent_from_data( - ek, extra, ptrlen_from_strbuf(r_enc), data); - - /* And then s = (log(r) + H*a) mod order(G). */ - mp_int *Ha = mp_modmul(H, a, ek->curve->e.G_order); - mp_int *s = mp_modadd(log_r, Ha, ek->curve->e.G_order); - mp_free(H); - mp_free(a); - mp_free(Ha); - mp_free(log_r); - - /* Format the output */ - put_stringz(bs, ek->sshk.vt->ssh_id); - put_uint32(bs, r_enc->len + ek->curve->fieldBytes); - put_data(bs, r_enc->u, r_enc->len); - strbuf_free(r_enc); - for (size_t i = 0; i < ek->curve->fieldBytes; ++i) - put_byte(bs, mp_get_byte(s, i)); - mp_free(s); -} - -static char *ec_alg_desc(const ssh_keyalg *self) -{ - const struct ecsign_extra *extra = - (const struct ecsign_extra *)self->extra; - return dupstr(extra->alg_desc); -} - -static const struct ecsign_extra sign_extra_ed25519 = { - ec_ed25519, &ssh_sha512, - NULL, 0, "Ed25519", PTRLEN_DECL_LITERAL(""), -}; -const ssh_keyalg ssh_ecdsa_ed25519 = { - .new_pub = eddsa_new_pub, - .new_priv = eddsa_new_priv, - .new_priv_openssh = eddsa_new_priv_openssh, - .freekey = eddsa_freekey, - .invalid = ec_signkey_invalid, - .sign = eddsa_sign, - .verify = eddsa_verify, - .public_blob = eddsa_public_blob, - .private_blob = eddsa_private_blob, - .openssh_blob = eddsa_openssh_blob, - .has_private = eddsa_has_private, - .cache_str = eddsa_cache_str, - .components = eddsa_components, - .base_key = nullkey_base_key, - .pubkey_bits = ec_shared_pubkey_bits, - .supported_flags = nullkey_supported_flags, - .alternate_ssh_id = nullkey_alternate_ssh_id, - .alg_desc = ec_alg_desc, - .variable_size = nullkey_variable_size_no, - .ssh_id = "ssh-ed25519", - .cache_id = "ssh-ed25519", - .extra = &sign_extra_ed25519, -}; - -static const struct ecsign_extra sign_extra_ed448 = { - ec_ed448, &ssh_shake256_114bytes, - NULL, 0, "Ed448", PTRLEN_DECL_LITERAL("SigEd448\0\0"), -}; -const ssh_keyalg ssh_ecdsa_ed448 = { - .new_pub = eddsa_new_pub, - .new_priv = eddsa_new_priv, - .new_priv_openssh = eddsa_new_priv_openssh, - .freekey = eddsa_freekey, - .invalid = ec_signkey_invalid, - .sign = eddsa_sign, - .verify = eddsa_verify, - .public_blob = eddsa_public_blob, - .private_blob = eddsa_private_blob, - .openssh_blob = eddsa_openssh_blob, - .has_private = eddsa_has_private, - .cache_str = eddsa_cache_str, - .components = eddsa_components, - .base_key = nullkey_base_key, - .pubkey_bits = ec_shared_pubkey_bits, - .supported_flags = nullkey_supported_flags, - .alternate_ssh_id = nullkey_alternate_ssh_id, - .alg_desc = ec_alg_desc, - .variable_size = nullkey_variable_size_no, - .ssh_id = "ssh-ed448", - .cache_id = "ssh-ed448", - .extra = &sign_extra_ed448, -}; - -/* OID: 1.2.840.10045.3.1.7 (ansiX9p256r1) */ -static const unsigned char nistp256_oid[] = { - 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 -}; -static const struct ecsign_extra sign_extra_nistp256 = { - ec_p256, &ssh_sha256, - nistp256_oid, lenof(nistp256_oid), "NIST p256", -}; -const ssh_keyalg ssh_ecdsa_nistp256 = { - .new_pub = ecdsa_new_pub, - .new_priv = ecdsa_new_priv, - .new_priv_openssh = ecdsa_new_priv_openssh, - .freekey = ecdsa_freekey, - .invalid = ec_signkey_invalid, - .sign = ecdsa_sign, - .verify = ecdsa_verify, - .public_blob = ecdsa_public_blob, - .private_blob = ecdsa_private_blob, - .openssh_blob = ecdsa_openssh_blob, - .has_private = ecdsa_has_private, - .cache_str = ecdsa_cache_str, - .components = ecdsa_components, - .base_key = nullkey_base_key, - .pubkey_bits = ec_shared_pubkey_bits, - .supported_flags = nullkey_supported_flags, - .alternate_ssh_id = nullkey_alternate_ssh_id, - .alg_desc = ec_alg_desc, - .variable_size = nullkey_variable_size_no, - .ssh_id = "ecdsa-sha2-nistp256", - .cache_id = "ecdsa-sha2-nistp256", - .extra = &sign_extra_nistp256, -}; - -/* OID: 1.3.132.0.34 (secp384r1) */ -static const unsigned char nistp384_oid[] = { - 0x2b, 0x81, 0x04, 0x00, 0x22 -}; -static const struct ecsign_extra sign_extra_nistp384 = { - ec_p384, &ssh_sha384, - nistp384_oid, lenof(nistp384_oid), "NIST p384", -}; -const ssh_keyalg ssh_ecdsa_nistp384 = { - .new_pub = ecdsa_new_pub, - .new_priv = ecdsa_new_priv, - .new_priv_openssh = ecdsa_new_priv_openssh, - .freekey = ecdsa_freekey, - .invalid = ec_signkey_invalid, - .sign = ecdsa_sign, - .verify = ecdsa_verify, - .public_blob = ecdsa_public_blob, - .private_blob = ecdsa_private_blob, - .openssh_blob = ecdsa_openssh_blob, - .has_private = ecdsa_has_private, - .cache_str = ecdsa_cache_str, - .components = ecdsa_components, - .base_key = nullkey_base_key, - .pubkey_bits = ec_shared_pubkey_bits, - .supported_flags = nullkey_supported_flags, - .alternate_ssh_id = nullkey_alternate_ssh_id, - .alg_desc = ec_alg_desc, - .variable_size = nullkey_variable_size_no, - .ssh_id = "ecdsa-sha2-nistp384", - .cache_id = "ecdsa-sha2-nistp384", - .extra = &sign_extra_nistp384, -}; - -/* OID: 1.3.132.0.35 (secp521r1) */ -static const unsigned char nistp521_oid[] = { - 0x2b, 0x81, 0x04, 0x00, 0x23 -}; -static const struct ecsign_extra sign_extra_nistp521 = { - ec_p521, &ssh_sha512, - nistp521_oid, lenof(nistp521_oid), "NIST p521", -}; -const ssh_keyalg ssh_ecdsa_nistp521 = { - .new_pub = ecdsa_new_pub, - .new_priv = ecdsa_new_priv, - .new_priv_openssh = ecdsa_new_priv_openssh, - .freekey = ecdsa_freekey, - .invalid = ec_signkey_invalid, - .sign = ecdsa_sign, - .verify = ecdsa_verify, - .public_blob = ecdsa_public_blob, - .private_blob = ecdsa_private_blob, - .openssh_blob = ecdsa_openssh_blob, - .has_private = ecdsa_has_private, - .cache_str = ecdsa_cache_str, - .components = ecdsa_components, - .base_key = nullkey_base_key, - .pubkey_bits = ec_shared_pubkey_bits, - .supported_flags = nullkey_supported_flags, - .alternate_ssh_id = nullkey_alternate_ssh_id, - .alg_desc = ec_alg_desc, - .variable_size = nullkey_variable_size_no, - .ssh_id = "ecdsa-sha2-nistp521", - .cache_id = "ecdsa-sha2-nistp521", - .extra = &sign_extra_nistp521, -}; - -/* ---------------------------------------------------------------------- - * Exposed ECDH interfaces - */ - -struct eckex_extra { - struct ec_curve *(*curve)(void); -}; - -typedef struct ecdh_key_w { - const struct eckex_extra *extra; - const struct ec_curve *curve; - mp_int *private; - WeierstrassPoint *w_public; - - ecdh_key ek; -} ecdh_key_w; - -typedef struct ecdh_key_m { - const struct eckex_extra *extra; - const struct ec_curve *curve; - mp_int *private; - MontgomeryPoint *m_public; - - ecdh_key ek; -} ecdh_key_m; - -static ecdh_key *ssh_ecdhkex_w_new(const ssh_kex *kex, bool is_server) -{ - const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; - const struct ec_curve *curve = extra->curve(); - - ecdh_key_w *dhw = snew(ecdh_key_w); - dhw->ek.vt = kex->ecdh_vt; - dhw->extra = extra; - dhw->curve = curve; - - mp_int *one = mp_from_integer(1); - dhw->private = mp_random_in_range(one, dhw->curve->w.G_order); - mp_free(one); - - dhw->w_public = ecc_weierstrass_multiply(dhw->curve->w.G, dhw->private); - - return &dhw->ek; -} - -static ecdh_key *ssh_ecdhkex_m_new(const ssh_kex *kex, bool is_server) -{ - const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; - const struct ec_curve *curve = extra->curve(); - - ecdh_key_m *dhm = snew(ecdh_key_m); - dhm->ek.vt = kex->ecdh_vt; - dhm->extra = extra; - dhm->curve = curve; - - strbuf *bytes = strbuf_new_nm(); - random_read(strbuf_append(bytes, dhm->curve->fieldBytes), - dhm->curve->fieldBytes); - - dhm->private = mp_from_bytes_le(ptrlen_from_strbuf(bytes)); - - /* Ensure the private key has the highest valid bit set, and no - * bits _above_ the highest valid one */ - mp_reduce_mod_2to(dhm->private, dhm->curve->fieldBits); - mp_set_bit(dhm->private, dhm->curve->fieldBits - 1, 1); - - /* Clear a curve-specific number of low bits */ - for (unsigned bit = 0; bit < dhm->curve->m.log2_cofactor; bit++) - mp_set_bit(dhm->private, bit, 0); - - strbuf_free(bytes); - - dhm->m_public = ecc_montgomery_multiply(dhm->curve->m.G, dhm->private); - - return &dhm->ek; -} - -static void ssh_ecdhkex_w_getpublic(ecdh_key *dh, BinarySink *bs) -{ - ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek); - put_wpoint(bs, dhw->w_public, dhw->curve, true); -} - -static void ssh_ecdhkex_m_getpublic(ecdh_key *dh, BinarySink *bs) -{ - ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek); - mp_int *x; - ecc_montgomery_get_affine(dhm->m_public, &x); - for (size_t i = 0; i < dhm->curve->fieldBytes; ++i) - put_byte(bs, mp_get_byte(x, i)); - mp_free(x); -} - -static bool ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey, - BinarySink *bs) -{ - ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek); - - WeierstrassPoint *remote_p = ecdsa_decode(remoteKey, dhw->curve); - if (!remote_p) - return false; - - if (ecc_weierstrass_is_identity(remote_p)) { - /* Not a sensible Diffie-Hellman input value */ - ecc_weierstrass_point_free(remote_p); - return false; - } - - WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dhw->private); - - mp_int *x; - ecc_weierstrass_get_affine(p, &x, NULL); - put_mp_ssh2(bs, x); - mp_free(x); - - ecc_weierstrass_point_free(remote_p); - ecc_weierstrass_point_free(p); - - return true; -} - -static bool ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey, - BinarySink *bs) -{ - ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek); - - mp_int *remote_x = mp_from_bytes_le(remoteKey); - - /* Per RFC 7748 section 5, discard any set bits of the other - * side's public value beyond the minimum number of bits required - * to represent all valid values. However, an overlarge value that - * still fits into the remaining number of bits is accepted, and - * will be reduced mod p. */ - mp_reduce_mod_2to(remote_x, dhm->curve->fieldBits); - - MontgomeryPoint *remote_p = ecc_montgomery_point_new( - dhm->curve->m.mc, remote_x); - mp_free(remote_x); - - MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dhm->private); - - if (ecc_montgomery_is_identity(p)) { - ecc_montgomery_point_free(remote_p); - ecc_montgomery_point_free(p); - return false; - } - - mp_int *x; - ecc_montgomery_get_affine(p, &x); - - ecc_montgomery_point_free(remote_p); - ecc_montgomery_point_free(p); - - /* - * 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 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. - */ - strbuf *sb = strbuf_new(); - for (size_t i = 0; i < dhm->curve->fieldBytes; ++i) - put_byte(sb, mp_get_byte(x, i)); - mp_free(x); - x = mp_from_bytes_be(ptrlen_from_strbuf(sb)); - strbuf_free(sb); - put_mp_ssh2(bs, x); - mp_free(x); - - return true; -} - -static void ssh_ecdhkex_w_free(ecdh_key *dh) -{ - ecdh_key_w *dhw = container_of(dh, ecdh_key_w, ek); - mp_free(dhw->private); - ecc_weierstrass_point_free(dhw->w_public); - sfree(dhw); -} - -static void ssh_ecdhkex_m_free(ecdh_key *dh) -{ - ecdh_key_m *dhm = container_of(dh, ecdh_key_m, ek); - mp_free(dhm->private); - ecc_montgomery_point_free(dhm->m_public); - sfree(dhm); -} - -static char *ssh_ecdhkex_description(const ssh_kex *kex) -{ - const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; - const struct ec_curve *curve = extra->curve(); - return dupprintf("ECDH key exchange with curve %s", curve->textname); -} - -static const struct eckex_extra kex_extra_curve25519 = { ec_curve25519 }; - -static const ecdh_keyalg ssh_ecdhkex_m_alg = { - .new = ssh_ecdhkex_m_new, - .free = ssh_ecdhkex_m_free, - .getpublic = ssh_ecdhkex_m_getpublic, - .getkey = ssh_ecdhkex_m_getkey, - .description = ssh_ecdhkex_description, -}; -const ssh_kex ssh_ec_kex_curve25519 = { - .name = "curve25519-sha256", - .main_type = KEXTYPE_ECDH, - .hash = &ssh_sha256, - .ecdh_vt = &ssh_ecdhkex_m_alg, - .extra = &kex_extra_curve25519, -}; -/* Pre-RFC alias */ -static const ssh_kex ssh_ec_kex_curve25519_libssh = { - .name = "curve25519-sha256@libssh.org", - .main_type = KEXTYPE_ECDH, - .hash = &ssh_sha256, - .ecdh_vt = &ssh_ecdhkex_m_alg, - .extra = &kex_extra_curve25519, -}; -/* GSSAPI variant */ -static const ssh_kex ssh_ec_kex_curve25519_gss = { - .name = "gss-curve25519-sha256-" GSS_KRB5_OID_HASH, - .main_type = KEXTYPE_GSS_ECDH, - .hash = &ssh_sha256, - .ecdh_vt = &ssh_ecdhkex_m_alg, - .extra = &kex_extra_curve25519, -}; - -static const struct eckex_extra kex_extra_curve448 = { ec_curve448 }; -const ssh_kex ssh_ec_kex_curve448 = { - .name = "curve448-sha512", - .main_type = KEXTYPE_ECDH, - .hash = &ssh_sha512, - .ecdh_vt = &ssh_ecdhkex_m_alg, - .extra = &kex_extra_curve448, -}; - -static const ecdh_keyalg ssh_ecdhkex_w_alg = { - .new = ssh_ecdhkex_w_new, - .free = ssh_ecdhkex_w_free, - .getpublic = ssh_ecdhkex_w_getpublic, - .getkey = ssh_ecdhkex_w_getkey, - .description = ssh_ecdhkex_description, -}; -static const struct eckex_extra kex_extra_nistp256 = { ec_p256 }; -const ssh_kex ssh_ec_kex_nistp256 = { - .name = "ecdh-sha2-nistp256", - .main_type = KEXTYPE_ECDH, - .hash = &ssh_sha256, - .ecdh_vt = &ssh_ecdhkex_w_alg, - .extra = &kex_extra_nistp256, -}; -/* GSSAPI variant */ -static const ssh_kex ssh_ec_kex_nistp256_gss = { - .name = "gss-nistp256-sha256-" GSS_KRB5_OID_HASH, - .main_type = KEXTYPE_GSS_ECDH, - .hash = &ssh_sha256, - .ecdh_vt = &ssh_ecdhkex_w_alg, - .extra = &kex_extra_nistp256, -}; - -static const struct eckex_extra kex_extra_nistp384 = { ec_p384 }; -const ssh_kex ssh_ec_kex_nistp384 = { - .name = "ecdh-sha2-nistp384", - .main_type = KEXTYPE_ECDH, - .hash = &ssh_sha384, - .ecdh_vt = &ssh_ecdhkex_w_alg, - .extra = &kex_extra_nistp384, -}; -/* GSSAPI variant */ -static const ssh_kex ssh_ec_kex_nistp384_gss = { - .name = "gss-nistp384-sha384-" GSS_KRB5_OID_HASH, - .main_type = KEXTYPE_GSS_ECDH, - .hash = &ssh_sha384, - .ecdh_vt = &ssh_ecdhkex_w_alg, - .extra = &kex_extra_nistp384, -}; - -static const struct eckex_extra kex_extra_nistp521 = { ec_p521 }; -const ssh_kex ssh_ec_kex_nistp521 = { - .name = "ecdh-sha2-nistp521", - .main_type = KEXTYPE_ECDH, - .hash = &ssh_sha512, - .ecdh_vt = &ssh_ecdhkex_w_alg, - .extra = &kex_extra_nistp521, -}; -/* GSSAPI variant */ -static const ssh_kex ssh_ec_kex_nistp521_gss = { - .name = "gss-nistp521-sha512-" GSS_KRB5_OID_HASH, - .main_type = KEXTYPE_GSS_ECDH, - .hash = &ssh_sha512, - .ecdh_vt = &ssh_ecdhkex_w_alg, - .extra = &kex_extra_nistp521, -}; - -static const ssh_kex *const ec_kex_list[] = { - &ssh_ec_kex_curve448, - &ssh_ec_kex_curve25519, - &ssh_ec_kex_curve25519_libssh, - &ssh_ec_kex_nistp256, - &ssh_ec_kex_nistp384, - &ssh_ec_kex_nistp521, -}; - -const ssh_kexes ssh_ecdh_kex = { lenof(ec_kex_list), ec_kex_list }; - -static const ssh_kex *const ec_gss_kex_list[] = { - &ssh_ec_kex_curve25519_gss, - &ssh_ec_kex_nistp521_gss, - &ssh_ec_kex_nistp384_gss, - &ssh_ec_kex_nistp256_gss, -}; - -const ssh_kexes ssh_gssk5_ecdh_kex = { - lenof(ec_gss_kex_list), ec_gss_kex_list -}; - -/* ---------------------------------------------------------------------- - * Helper functions for finding key algorithms and returning auxiliary - * data. - */ - -const ssh_keyalg *ec_alg_by_oid(int len, const void *oid, - const struct ec_curve **curve) -{ - static const ssh_keyalg *algs_with_oid[] = { - &ssh_ecdsa_nistp256, - &ssh_ecdsa_nistp384, - &ssh_ecdsa_nistp521, - }; - int i; - - for (i = 0; i < lenof(algs_with_oid); i++) { - const ssh_keyalg *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 ssh_keyalg *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_ed_curve_lengths[] = { 255, 448 }; -const int n_ec_ed_curve_lengths = lenof(ec_ed_curve_lengths); - -bool ec_nist_alg_and_curve_by_bits( - int bits, const struct ec_curve **curve, const ssh_keyalg **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; -} - -bool ec_ed_alg_and_curve_by_bits( - int bits, const struct ec_curve **curve, const ssh_keyalg **alg) -{ - switch (bits) { - case 255: case 256: *alg = &ssh_ecdsa_ed25519; break; - case 448: *alg = &ssh_ecdsa_ed448; break; - default: return false; - } - *curve = ((struct ecsign_extra *)(*alg)->extra)->curve(); - return true; -} diff --git a/crypto/ecc.h b/crypto/ecc.h deleted file mode 100644 index a61153290..000000000 --- a/crypto/ecc.h +++ /dev/null @@ -1,243 +0,0 @@ -#ifndef PUTTY_ECC_H -#define PUTTY_ECC_H - -/* - * Arithmetic functions for the various kinds of elliptic curves used - * by PuTTY's public-key cryptography. - * - * All of these elliptic curves are over the finite field whose order - * is a large prime p. (Elliptic curves over a field of order 2^n are - * also known, but PuTTY currently has no need of them.) - */ - -/* ---------------------------------------------------------------------- - * Weierstrass curves (or rather, 'short form' Weierstrass curves). - * - * A curve in this form is defined by two parameters a,b, and the - * non-identity points on the curve are represented by (x,y) (the - * 'affine coordinates') such that y^2 = x^3 + ax + b. - * - * The identity element of the curve's group is an additional 'point - * at infinity', which is considered to be the third point on the - * intersection of the curve with any vertical line. Hence, the - * inverse of the point (x,y) is (x,-y). - */ - -/* - * Create and destroy Weierstrass curve data structures. The mandatory - * parameters to the constructor are the prime modulus p, and the - * curve parameters a,b. - * - * 'nonsquare_mod_p' is an optional extra parameter, only needed by - * ecc_edwards_point_new_from_y which has to take a modular square - * root. You can pass it as NULL if you don't need that function. - */ -WeierstrassCurve *ecc_weierstrass_curve( - mp_int *p, mp_int *a, mp_int *b, mp_int *nonsquare_mod_p); -void ecc_weierstrass_curve_free(WeierstrassCurve *); - -/* - * Create points on a Weierstrass curve, given the curve. - * - * point_new_identity returns the special identity point. - * point_new(x,y) returns the non-identity point with the given affine - * coordinates. - * - * point_new_from_x constructs a non-identity point given only the - * x-coordinate, by using the curve equation to work out what y has to - * be. Of course the equation only tells you y^2, so it only - * determines y up to sign; the parameter desired_y_parity controls - * which of the two values of y you get, by saying whether you'd like - * its minimal non-negative residue mod p to be even or odd. (Of - * course, since p itself is odd, exactly one of y and p-y is odd.) - * This function has to take a modular square root, so it will only - * work if you passed in a non-square mod p when constructing the - * curve. - */ -WeierstrassPoint *ecc_weierstrass_point_new_identity(WeierstrassCurve *curve); -WeierstrassPoint *ecc_weierstrass_point_new( - WeierstrassCurve *curve, mp_int *x, mp_int *y); -WeierstrassPoint *ecc_weierstrass_point_new_from_x( - WeierstrassCurve *curve, mp_int *x, unsigned desired_y_parity); - -/* Memory management: copy and free points. */ -void ecc_weierstrass_point_copy_into( - WeierstrassPoint *dest, WeierstrassPoint *src); -WeierstrassPoint *ecc_weierstrass_point_copy(WeierstrassPoint *orig); -void ecc_weierstrass_point_free(WeierstrassPoint *point); - -/* Check whether a point is actually on the curve. */ -unsigned ecc_weierstrass_point_valid(WeierstrassPoint *); - -/* - * Add two points and return their sum. This function is fully - * general: it should do the right thing if the two inputs are the - * same, or if either (or both) of the input points is the identity, - * or if the two input points are inverses so the output is the - * identity. However, it pays for that generality by being slower than - * the special-purpose functions below.. - */ -WeierstrassPoint *ecc_weierstrass_add_general( - WeierstrassPoint *, WeierstrassPoint *); - -/* - * Fast but less general arithmetic functions: add two points on the - * condition that they are not equal and neither is the identity, and - * add a point to itself. - */ -WeierstrassPoint *ecc_weierstrass_add(WeierstrassPoint *, WeierstrassPoint *); -WeierstrassPoint *ecc_weierstrass_double(WeierstrassPoint *); - -/* - * Compute an integer multiple of a point. Not guaranteed to work - * unless the integer argument is less than the order of the point in - * the group (because it won't cope if an identity element shows up in - * any intermediate product). - */ -WeierstrassPoint *ecc_weierstrass_multiply(WeierstrassPoint *, mp_int *); - -/* - * Query functions to get the value of a point back out. is_identity - * tells you whether the point is the identity; if it isn't, then - * get_affine will retrieve one or both of its affine coordinates. - * (You can pass NULL as either output pointer, if you don't need that - * coordinate as output.) - */ -unsigned ecc_weierstrass_is_identity(WeierstrassPoint *wp); -void ecc_weierstrass_get_affine(WeierstrassPoint *wp, mp_int **x, mp_int **y); - -/* ---------------------------------------------------------------------- - * Montgomery curves. - * - * A curve in this form is defined by two parameters a,b, and the - * curve equation is by^2 = x^3 + ax^2 + x. - * - * As with Weierstrass curves, there's an additional point at infinity - * that is the identity element, and the inverse of (x,y) is (x,-y). - * - * However, we don't actually work with full (x,y) pairs. We just - * store the x-coordinate (so what we're really representing is not a - * specific point on the curve but a two-point set {P,-P}). This means - * you can't quite do point addition, because if you're given {P,-P} - * and {Q,-Q} as input, you can work out a pair of x-coordinates that - * are those of P-Q and P+Q, but you don't know which is which. - * - * Instead, the basic operation is 'differential addition', in which - * you are given three parameters P, Q and P-Q and you return P+Q. (As - * well as disambiguating which of the possible answers you want, that - * extra input also enables a fast formulae for computing it. This - * fast formula is more or less why Montgomery curves are useful in - * the first place.) - * - * Doubling a point is still possible to do unambiguously, so you can - * still compute an integer multiple of P if you start by making 2P - * and then doing a series of differential additions. - */ - -/* - * Create and destroy Montgomery curve data structures. - */ -MontgomeryCurve *ecc_montgomery_curve(mp_int *p, mp_int *a, mp_int *b); -void ecc_montgomery_curve_free(MontgomeryCurve *); - -/* - * Create, copy and free points on the curve. We don't need to - * explicitly represent the identity for this application. - */ -MontgomeryPoint *ecc_montgomery_point_new(MontgomeryCurve *mc, mp_int *x); -void ecc_montgomery_point_copy_into( - MontgomeryPoint *dest, MontgomeryPoint *src); -MontgomeryPoint *ecc_montgomery_point_copy(MontgomeryPoint *orig); -void ecc_montgomery_point_free(MontgomeryPoint *mp); - -/* - * Basic arithmetic routines: differential addition and point- - * doubling. Each of these assumes that no special cases come up - no - * input or output point should be the identity, and in diff_add, P - * and Q shouldn't be the same. - */ -MontgomeryPoint *ecc_montgomery_diff_add( - MontgomeryPoint *P, MontgomeryPoint *Q, MontgomeryPoint *PminusQ); -MontgomeryPoint *ecc_montgomery_double(MontgomeryPoint *P); - -/* - * Compute an integer multiple of a point. - */ -MontgomeryPoint *ecc_montgomery_multiply(MontgomeryPoint *, mp_int *); - -/* - * Return the affine x-coordinate of a point. - */ -void ecc_montgomery_get_affine(MontgomeryPoint *mp, mp_int **x); - -/* - * Test whether a point is the curve identity. - */ -unsigned ecc_montgomery_is_identity(MontgomeryPoint *mp); - -/* ---------------------------------------------------------------------- - * Twisted Edwards curves. - * - * A curve in this form is defined by two parameters d,a, and the - * curve equation is a x^2 + y^2 = 1 + d x^2 y^2. - * - * Apparently if you ask a proper algebraic geometer they'll tell you - * that this is technically not an actual elliptic curve. Certainly it - * doesn't work quite the same way as the other kinds: in this form, - * there is no need for a point at infinity, because the identity - * element is represented by the affine coordinates (0,1). And you - * invert a point by negating its x rather than y coordinate: the - * inverse of (x,y) is (-x,y). - * - * The usefulness of this representation is that the addition formula - * is 'strongly unified', meaning that the same formula works for any - * input and output points, without needing special cases for the - * identity or for doubling. - */ - -/* - * Create and destroy Edwards curve data structures. - * - * Similarly to ecc_weierstrass_curve, you don't have to provide - * nonsquare_mod_p if you don't need ecc_edwards_point_new_from_y. - */ -EdwardsCurve *ecc_edwards_curve( - mp_int *p, mp_int *d, mp_int *a, mp_int *nonsquare_mod_p); -void ecc_edwards_curve_free(EdwardsCurve *); - -/* - * Create points. - * - * There's no need to have a separate function to create the identity - * point, because you can just pass x=0 and y=1 to the usual function. - * - * Similarly to the Weierstrass curve, ecc_edwards_point_new_from_y - * creates a point given only its y-coordinate and the desired parity - * of its x-coordinate, and you can only call it if you provided the - * optional nonsquare_mod_p argument when creating the curve. - */ -EdwardsPoint *ecc_edwards_point_new( - EdwardsCurve *curve, mp_int *x, mp_int *y); -EdwardsPoint *ecc_edwards_point_new_from_y( - EdwardsCurve *curve, mp_int *y, unsigned desired_x_parity); - -/* Copy and free points. */ -void ecc_edwards_point_copy_into(EdwardsPoint *dest, EdwardsPoint *src); -EdwardsPoint *ecc_edwards_point_copy(EdwardsPoint *orig); -void ecc_edwards_point_free(EdwardsPoint *point); - -/* - * Arithmetic: add two points, and calculate an integer multiple of a - * point. - */ -EdwardsPoint *ecc_edwards_add(EdwardsPoint *, EdwardsPoint *); -EdwardsPoint *ecc_edwards_multiply(EdwardsPoint *, mp_int *); - -/* - * Query functions: compare two points for equality, and return the - * affine coordinates of a point. - */ -unsigned ecc_edwards_eq(EdwardsPoint *, EdwardsPoint *); -void ecc_edwards_get_affine(EdwardsPoint *wp, mp_int **x, mp_int **y); - -#endif /* PUTTY_ECC_H */ diff --git a/crypto/hash_simple.c b/crypto/hash_simple.c deleted file mode 100644 index 0115b9207..000000000 --- a/crypto/hash_simple.c +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Convenience function to hash a single piece of data, wrapping up - * the faff of making and freeing an ssh_hash. - */ - -#include "ssh.h" - -void hash_simple(const ssh_hashalg *alg, ptrlen data, void *output) -{ - ssh_hash *hash = ssh_hash_new(alg); - put_datapl(hash, data); - ssh_hash_final(hash, output); -} diff --git a/crypto/hmac.c b/crypto/hmac.c deleted file mode 100644 index cc255829b..000000000 --- a/crypto/hmac.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Implementation of HMAC (RFC 2104) for PuTTY, in a general form that - * can wrap any underlying hash function. - */ - -#include "ssh.h" - -struct hmac { - const ssh_hashalg *hashalg; - ssh_hash *h_outer, *h_inner, *h_live; - uint8_t *digest; - strbuf *text_name; - ssh2_mac mac; -}; - -struct hmac_extra { - const ssh_hashalg *hashalg_base; - const char *suffix, *annotation; -}; - -static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher) -{ - struct hmac *ctx = snew(struct hmac); - const struct hmac_extra *extra = (const struct hmac_extra *)alg->extra; - - ctx->h_outer = ssh_hash_new(extra->hashalg_base); - /* In case that hashalg was a selector vtable, we'll now switch to - * using whatever real one it selected, for all future purposes. */ - ctx->hashalg = ssh_hash_alg(ctx->h_outer); - ctx->h_inner = ssh_hash_new(ctx->hashalg); - ctx->h_live = ssh_hash_new(ctx->hashalg); - - /* - * HMAC is not well defined as a wrapper on an absolutely general - * hash function; it expects that the function it's wrapping will - * consume data in fixed-size blocks, and it's partially defined - * in terms of that block size. So we insist that the hash we're - * given must have defined a meaningful block size. - */ - assert(ctx->hashalg->blocklen); - - ctx->digest = snewn(ctx->hashalg->hlen, uint8_t); - - ctx->text_name = strbuf_new(); - put_fmt(ctx->text_name, "HMAC-%s%s", - ctx->hashalg->text_basename, extra->suffix); - if (extra->annotation || ctx->hashalg->annotation) { - put_fmt(ctx->text_name, " ("); - const char *sep = ""; - if (extra->annotation) { - put_fmt(ctx->text_name, "%s%s", sep, extra->annotation); - sep = ", "; - } - if (ctx->hashalg->annotation) { - put_fmt(ctx->text_name, "%s%s", sep, ctx->hashalg->annotation); - sep = ", "; - } - put_fmt(ctx->text_name, ")"); - } - - ctx->mac.vt = alg; - BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live); - - return &ctx->mac; -} - -static void hmac_free(ssh2_mac *mac) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - - ssh_hash_free(ctx->h_outer); - ssh_hash_free(ctx->h_inner); - ssh_hash_free(ctx->h_live); - smemclr(ctx->digest, ctx->hashalg->hlen); - sfree(ctx->digest); - strbuf_free(ctx->text_name); - - smemclr(ctx, sizeof(*ctx)); - sfree(ctx); -} - -#define PAD_OUTER 0x5C -#define PAD_INNER 0x36 - -static void hmac_key(ssh2_mac *mac, ptrlen key) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - - const uint8_t *kp; - size_t klen; - strbuf *sb = NULL; - - if (key.len > ctx->hashalg->blocklen) { - /* - * RFC 2104 section 2: if the key exceeds the block length of - * the underlying hash, then we start by hashing the key, and - * use that hash as the 'true' key for the HMAC construction. - */ - sb = strbuf_new_nm(); - strbuf_append(sb, ctx->hashalg->hlen); - hash_simple(ctx->hashalg, key, sb->u); - kp = sb->u; - klen = sb->len; - } else { - /* - * A short enough key is used as is. - */ - kp = (const uint8_t *)key.ptr; - klen = key.len; - } - - ssh_hash_reset(ctx->h_outer); - for (size_t i = 0; i < klen; i++) - put_byte(ctx->h_outer, PAD_OUTER ^ kp[i]); - for (size_t i = klen; i < ctx->hashalg->blocklen; i++) - put_byte(ctx->h_outer, PAD_OUTER); - - ssh_hash_reset(ctx->h_inner); - for (size_t i = 0; i < klen; i++) - put_byte(ctx->h_inner, PAD_INNER ^ kp[i]); - for (size_t i = klen; i < ctx->hashalg->blocklen; i++) - put_byte(ctx->h_inner, PAD_INNER); - - if (sb) - strbuf_free(sb); -} - -static void hmac_start(ssh2_mac *mac) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - ssh_hash_copyfrom(ctx->h_live, ctx->h_inner); -} - -static void hmac_genresult(ssh2_mac *mac, unsigned char *output) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - ssh_hash *htmp; - - /* Leave h_live and h_outer in place, so that the SSH-2 BPP can - * continue regenerating test results from different-length - * prefixes of the packet */ - ssh_hash_digest_nondestructive(ctx->h_live, ctx->digest); - - htmp = ssh_hash_copy(ctx->h_outer); - put_data(htmp, ctx->digest, ctx->hashalg->hlen); - ssh_hash_final(htmp, ctx->digest); - - /* - * Some instances of HMAC truncate the output hash, so instead of - * writing it directly to 'output' we wrote it to our own - * full-length buffer, and now we copy the required amount. - */ - memcpy(output, ctx->digest, mac->vt->len); - smemclr(ctx->digest, ctx->hashalg->hlen); -} - -static const char *hmac_text_name(ssh2_mac *mac) -{ - struct hmac *ctx = container_of(mac, struct hmac, mac); - return ctx->text_name->s; -} - -static const struct hmac_extra ssh_hmac_sha512_extra = { &ssh_sha512, "" }; -const ssh2_macalg ssh_hmac_sha512 = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .next_message = nullmac_next_message, - .text_name = hmac_text_name, - .name = "hmac-sha2-512", - .etm_name = "hmac-sha2-512-etm@openssh.com", - .len = 64, - .keylen = 64, - .extra = &ssh_hmac_sha512_extra, -}; - -static const struct hmac_extra ssh_hmac_sha256_extra = { &ssh_sha256, "" }; -const ssh2_macalg ssh_hmac_sha256 = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .next_message = nullmac_next_message, - .text_name = hmac_text_name, - .name = "hmac-sha2-256", - .etm_name = "hmac-sha2-256-etm@openssh.com", - .len = 32, - .keylen = 32, - .extra = &ssh_hmac_sha256_extra, -}; - -static const struct hmac_extra ssh_hmac_md5_extra = { &ssh_md5, "" }; -const ssh2_macalg ssh_hmac_md5 = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .next_message = nullmac_next_message, - .text_name = hmac_text_name, - .name = "hmac-md5", - .etm_name = "hmac-md5-etm@openssh.com", - .len = 16, - .keylen = 16, - .extra = &ssh_hmac_md5_extra, -}; - -static const struct hmac_extra ssh_hmac_sha1_extra = { &ssh_sha1, "" }; - -const ssh2_macalg ssh_hmac_sha1 = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .next_message = nullmac_next_message, - .text_name = hmac_text_name, - .name = "hmac-sha1", - .etm_name = "hmac-sha1-etm@openssh.com", - .len = 20, - .keylen = 20, - .extra = &ssh_hmac_sha1_extra, -}; - -static const struct hmac_extra ssh_hmac_sha1_96_extra = { &ssh_sha1, "-96" }; - -const ssh2_macalg ssh_hmac_sha1_96 = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .next_message = nullmac_next_message, - .text_name = hmac_text_name, - .name = "hmac-sha1-96", - .etm_name = "hmac-sha1-96-etm@openssh.com", - .len = 12, - .keylen = 20, - .extra = &ssh_hmac_sha1_96_extra, -}; - -static const struct hmac_extra ssh_hmac_sha1_buggy_extra = { - &ssh_sha1, "", "bug-compatible" -}; - -const ssh2_macalg ssh_hmac_sha1_buggy = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .next_message = nullmac_next_message, - .text_name = hmac_text_name, - .name = "hmac-sha1", - .len = 20, - .keylen = 16, - .extra = &ssh_hmac_sha1_buggy_extra, -}; - -static const struct hmac_extra ssh_hmac_sha1_96_buggy_extra = { - &ssh_sha1, "-96", "bug-compatible" -}; - -const ssh2_macalg ssh_hmac_sha1_96_buggy = { - .new = hmac_new, - .free = hmac_free, - .setkey = hmac_key, - .start = hmac_start, - .genresult = hmac_genresult, - .next_message = nullmac_next_message, - .text_name = hmac_text_name, - .name = "hmac-sha1-96", - .len = 12, - .keylen = 16, - .extra = &ssh_hmac_sha1_96_buggy_extra, -}; diff --git a/crypto/mac.c b/crypto/mac.c deleted file mode 100644 index c117d90bb..000000000 --- a/crypto/mac.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Centralised parts of the SSH-2 MAC API, which don't need to vary - * with the MAC implementation. - */ - -#include - -#include "ssh.h" - -bool ssh2_mac_verresult(ssh2_mac *mac, const void *candidate) -{ - unsigned char correct[64]; /* at least as big as all known MACs */ - bool toret; - - assert(mac->vt->len <= sizeof(correct)); - ssh2_mac_genresult(mac, correct); - toret = smemeq(correct, candidate, mac->vt->len); - - smemclr(correct, sizeof(correct)); - - return toret; -} - -static void ssh2_mac_prepare(ssh2_mac *mac, const void *blk, int len, - unsigned long seq) -{ - ssh2_mac_start(mac); - put_uint32(mac, seq); - put_data(mac, blk, len); -} - -void ssh2_mac_generate(ssh2_mac *mac, void *blk, int len, unsigned long seq) -{ - ssh2_mac_prepare(mac, blk, len, seq); - ssh2_mac_genresult(mac, (unsigned char *)blk + len); -} - -bool ssh2_mac_verify( - ssh2_mac *mac, const void *blk, int len, unsigned long seq) -{ - ssh2_mac_prepare(mac, blk, len, seq); - return ssh2_mac_verresult(mac, (const unsigned char *)blk + len); -} diff --git a/crypto/mac_simple.c b/crypto/mac_simple.c deleted file mode 100644 index c705fd889..000000000 --- a/crypto/mac_simple.c +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Convenience function to MAC a single piece of data, wrapping up - * the faff of making and freeing an ssh_mac. - */ - -#include "ssh.h" - -void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output) -{ - ssh2_mac *mac = ssh2_mac_new(alg, NULL); - ssh2_mac_setkey(mac, key); - ssh2_mac_start(mac); - put_datapl(mac, data); - ssh2_mac_genresult(mac, output); - ssh2_mac_free(mac); -} diff --git a/crypto/md5.c b/crypto/md5.c deleted file mode 100644 index 9155c99ef..000000000 --- a/crypto/md5.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - * MD5 implementation for PuTTY. Written directly from the spec by - * Simon Tatham. - */ - -#include -#include "ssh.h" - -static const uint32_t md5_initial_state[] = { - 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, -}; - -static const struct md5_round_constant { - uint32_t addition, rotation, msg_index; -} md5_round_constants[] = { - { 0xd76aa478, 7, 0 }, { 0xe8c7b756, 12, 1 }, - { 0x242070db, 17, 2 }, { 0xc1bdceee, 22, 3 }, - { 0xf57c0faf, 7, 4 }, { 0x4787c62a, 12, 5 }, - { 0xa8304613, 17, 6 }, { 0xfd469501, 22, 7 }, - { 0x698098d8, 7, 8 }, { 0x8b44f7af, 12, 9 }, - { 0xffff5bb1, 17, 10 }, { 0x895cd7be, 22, 11 }, - { 0x6b901122, 7, 12 }, { 0xfd987193, 12, 13 }, - { 0xa679438e, 17, 14 }, { 0x49b40821, 22, 15 }, - { 0xf61e2562, 5, 1 }, { 0xc040b340, 9, 6 }, - { 0x265e5a51, 14, 11 }, { 0xe9b6c7aa, 20, 0 }, - { 0xd62f105d, 5, 5 }, { 0x02441453, 9, 10 }, - { 0xd8a1e681, 14, 15 }, { 0xe7d3fbc8, 20, 4 }, - { 0x21e1cde6, 5, 9 }, { 0xc33707d6, 9, 14 }, - { 0xf4d50d87, 14, 3 }, { 0x455a14ed, 20, 8 }, - { 0xa9e3e905, 5, 13 }, { 0xfcefa3f8, 9, 2 }, - { 0x676f02d9, 14, 7 }, { 0x8d2a4c8a, 20, 12 }, - { 0xfffa3942, 4, 5 }, { 0x8771f681, 11, 8 }, - { 0x6d9d6122, 16, 11 }, { 0xfde5380c, 23, 14 }, - { 0xa4beea44, 4, 1 }, { 0x4bdecfa9, 11, 4 }, - { 0xf6bb4b60, 16, 7 }, { 0xbebfbc70, 23, 10 }, - { 0x289b7ec6, 4, 13 }, { 0xeaa127fa, 11, 0 }, - { 0xd4ef3085, 16, 3 }, { 0x04881d05, 23, 6 }, - { 0xd9d4d039, 4, 9 }, { 0xe6db99e5, 11, 12 }, - { 0x1fa27cf8, 16, 15 }, { 0xc4ac5665, 23, 2 }, - { 0xf4292244, 6, 0 }, { 0x432aff97, 10, 7 }, - { 0xab9423a7, 15, 14 }, { 0xfc93a039, 21, 5 }, - { 0x655b59c3, 6, 12 }, { 0x8f0ccc92, 10, 3 }, - { 0xffeff47d, 15, 10 }, { 0x85845dd1, 21, 1 }, - { 0x6fa87e4f, 6, 8 }, { 0xfe2ce6e0, 10, 15 }, - { 0xa3014314, 15, 6 }, { 0x4e0811a1, 21, 13 }, - { 0xf7537e82, 6, 4 }, { 0xbd3af235, 10, 11 }, - { 0x2ad7d2bb, 15, 2 }, { 0xeb86d391, 21, 9 }, -}; - -typedef struct md5_block md5_block; -struct md5_block { - uint8_t block[64]; - size_t used; - uint64_t len; -}; - -static inline void md5_block_setup(md5_block *blk) -{ - blk->used = 0; - blk->len = 0; -} - -static inline bool md5_block_write( - md5_block *blk, const void **vdata, size_t *len) -{ - size_t blkleft = sizeof(blk->block) - blk->used; - size_t chunk = *len < blkleft ? *len : blkleft; - - const uint8_t *p = *vdata; - memcpy(blk->block + blk->used, p, chunk); - *vdata = p + chunk; - *len -= chunk; - blk->used += chunk; - blk->len += chunk; - - if (blk->used == sizeof(blk->block)) { - blk->used = 0; - return true; - } - - return false; -} - -static inline void md5_block_pad(md5_block *blk, BinarySink *bs) -{ - uint64_t final_len = blk->len << 3; - size_t pad = 63 & (55 - blk->used); - - put_byte(bs, 0x80); - put_padding(bs, pad, 0); - - unsigned char buf[8]; - PUT_64BIT_LSB_FIRST(buf, final_len); - put_data(bs, buf, 8); - smemclr(buf, 8); - - assert(blk->used == 0 && "Should have exactly hit a block boundary"); -} - -static inline uint32_t rol(uint32_t x, unsigned y) -{ - return (x << (31 & y)) | (x >> (31 & -y)); -} - -static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) -{ - return if0 ^ (ctrl & (if1 ^ if0)); -} - -/* Parameter functions for the four MD5 round types */ -static inline uint32_t F(uint32_t x, uint32_t y, uint32_t z) -{ return Ch(x, y, z); } -static inline uint32_t G(uint32_t x, uint32_t y, uint32_t z) -{ return Ch(z, x, y); } -static inline uint32_t H(uint32_t x, uint32_t y, uint32_t z) -{ return x ^ y ^ z; } -static inline uint32_t I(uint32_t x, uint32_t y, uint32_t z) -{ return y ^ (x | ~z); } - -static inline void md5_round( - unsigned round_index, const uint32_t *message, - uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, - uint32_t (*f)(uint32_t, uint32_t, uint32_t)) -{ - struct md5_round_constant rc = md5_round_constants[round_index]; - - *a = *b + rol(*a + f(*b, *c, *d) + message[rc.msg_index] + rc.addition, - rc.rotation); -} - -static void md5_do_block(uint32_t *core, const uint8_t *block) -{ - uint32_t message_words[16]; - for (size_t i = 0; i < 16; i++) - message_words[i] = GET_32BIT_LSB_FIRST(block + 4*i); - - uint32_t a = core[0], b = core[1], c = core[2], d = core[3]; - - size_t t = 0; - for (size_t u = 0; u < 4; u++) { - md5_round(t++, message_words, &a, &b, &c, &d, F); - md5_round(t++, message_words, &d, &a, &b, &c, F); - md5_round(t++, message_words, &c, &d, &a, &b, F); - md5_round(t++, message_words, &b, &c, &d, &a, F); - } - for (size_t u = 0; u < 4; u++) { - md5_round(t++, message_words, &a, &b, &c, &d, G); - md5_round(t++, message_words, &d, &a, &b, &c, G); - md5_round(t++, message_words, &c, &d, &a, &b, G); - md5_round(t++, message_words, &b, &c, &d, &a, G); - } - for (size_t u = 0; u < 4; u++) { - md5_round(t++, message_words, &a, &b, &c, &d, H); - md5_round(t++, message_words, &d, &a, &b, &c, H); - md5_round(t++, message_words, &c, &d, &a, &b, H); - md5_round(t++, message_words, &b, &c, &d, &a, H); - } - for (size_t u = 0; u < 4; u++) { - md5_round(t++, message_words, &a, &b, &c, &d, I); - md5_round(t++, message_words, &d, &a, &b, &c, I); - md5_round(t++, message_words, &c, &d, &a, &b, I); - md5_round(t++, message_words, &b, &c, &d, &a, I); - } - - core[0] += a; - core[1] += b; - core[2] += c; - core[3] += d; - - smemclr(message_words, sizeof(message_words)); -} - -typedef struct md5 { - uint32_t core[4]; - md5_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} md5; - -static void md5_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *md5_new(const ssh_hashalg *alg) -{ - md5 *s = snew(md5); - - s->hash.vt = alg; - BinarySink_INIT(s, md5_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void md5_reset(ssh_hash *hash) -{ - md5 *s = container_of(hash, md5, hash); - - memcpy(s->core, md5_initial_state, sizeof(s->core)); - md5_block_setup(&s->blk); -} - -static void md5_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - md5 *copy = container_of(hcopy, md5, hash); - md5 *orig = container_of(horig, md5, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void md5_free(ssh_hash *hash) -{ - md5 *s = container_of(hash, md5, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void md5_write(BinarySink *bs, const void *vp, size_t len) -{ - md5 *s = BinarySink_DOWNCAST(bs, md5); - - while (len > 0) - if (md5_block_write(&s->blk, &vp, &len)) - md5_do_block(s->core, s->blk.block); -} - -static void md5_digest(ssh_hash *hash, uint8_t *digest) -{ - md5 *s = container_of(hash, md5, hash); - - md5_block_pad(&s->blk, BinarySink_UPCAST(s)); - for (size_t i = 0; i < 4; i++) - PUT_32BIT_LSB_FIRST(digest + 4*i, s->core[i]); -} - -const ssh_hashalg ssh_md5 = { - .new = md5_new, - .reset = md5_reset, - .copyfrom = md5_copyfrom, - .digest = md5_digest, - .free = md5_free, - .hlen = 16, - .blocklen = 64, - HASHALG_NAMES_BARE("MD5"), -}; diff --git a/crypto/mpint.c b/crypto/mpint.c deleted file mode 100644 index 437c7e8c3..000000000 --- a/crypto/mpint.c +++ /dev/null @@ -1,2810 +0,0 @@ -/* - * Multiprecision integer arithmetic, implementing mpint.h. - */ - -#include -#include -#include - -#include "defs.h" -#include "misc.h" -#include "puttymem.h" - -#include "mpint.h" -#include "mpint_i.h" - -#define SIZE_T_BITS (CHAR_BIT * sizeof(size_t)) - -/* - * Inline helpers to take min and max of size_t values, used - * throughout this code. - */ -static inline size_t size_t_min(size_t a, size_t b) -{ - return a < b ? a : b; -} -static inline size_t size_t_max(size_t a, size_t b) -{ - return a > b ? a : b; -} - -/* - * Helper to fetch a word of data from x with array overflow checking. - * If x is too short to have that word, 0 is returned. - */ -static inline BignumInt mp_word(mp_int *x, size_t i) -{ - return i < x->nw ? x->w[i] : 0; -} - -/* - * Shift an ordinary C integer by BIGNUM_INT_BITS, in a way that - * avoids writing a shift operator whose RHS is greater or equal to - * the size of the type, because that's undefined behaviour in C. - * - * In fact we must avoid even writing it in a definitely-untaken - * branch of an if, because compilers will sometimes warn about - * that. So you can't just write 'shift too big ? 0 : n >> shift', - * because even if 'shift too big' is a constant-expression - * evaluating to false, you can still get complaints about the - * else clause of the ?:. - * - * So we have to re-check _inside_ that clause, so that the shift - * count is reset to something nonsensical but safe in the case - * where the clause wasn't going to be taken anyway. - */ -static uintmax_t shift_right_by_one_word(uintmax_t n) -{ - bool shift_too_big = BIGNUM_INT_BYTES >= sizeof(n); - return shift_too_big ? 0 : - n >> (shift_too_big ? 0 : BIGNUM_INT_BITS); -} -static uintmax_t shift_left_by_one_word(uintmax_t n) -{ - bool shift_too_big = BIGNUM_INT_BYTES >= sizeof(n); - return shift_too_big ? 0 : - n << (shift_too_big ? 0 : BIGNUM_INT_BITS); -} - -mp_int *mp_make_sized(size_t nw) -{ - mp_int *x = snew_plus(mp_int, nw * sizeof(BignumInt)); - assert(nw); /* we outlaw the zero-word mp_int */ - x->nw = nw; - x->w = snew_plus_get_aux(x); - mp_clear(x); - return x; -} - -mp_int *mp_new(size_t maxbits) -{ - size_t words = (maxbits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - return mp_make_sized(words); -} - -mp_int *mp_resize(mp_int *mp, size_t newmaxbits) -{ - mp_int *copy = mp_new(newmaxbits); - mp_copy_into(copy, mp); - mp_free(mp); - return copy; -} - -mp_int *mp_from_integer(uintmax_t n) -{ - mp_int *x = mp_make_sized( - (sizeof(n) + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES); - for (size_t i = 0; i < x->nw; i++) - x->w[i] = n >> (i * BIGNUM_INT_BITS); - return x; -} - -size_t mp_max_bytes(mp_int *x) -{ - return x->nw * BIGNUM_INT_BYTES; -} - -size_t mp_max_bits(mp_int *x) -{ - return x->nw * BIGNUM_INT_BITS; -} - -void mp_free(mp_int *x) -{ - mp_clear(x); - smemclr(x, sizeof(*x)); - sfree(x); -} - -void mp_dump(FILE *fp, const char *prefix, mp_int *x, const char *suffix) -{ - fprintf(fp, "%s0x", prefix); - for (size_t i = mp_max_bytes(x); i-- > 0 ;) - fprintf(fp, "%02X", mp_get_byte(x, i)); - fputs(suffix, fp); -} - -void mp_copy_into(mp_int *dest, mp_int *src) -{ - size_t copy_nw = size_t_min(dest->nw, src->nw); - memmove(dest->w, src->w, copy_nw * sizeof(BignumInt)); - smemclr(dest->w + copy_nw, (dest->nw - copy_nw) * sizeof(BignumInt)); -} - -void mp_copy_integer_into(mp_int *r, uintmax_t n) -{ - for (size_t i = 0; i < r->nw; i++) { - r->w[i] = n; - n = shift_right_by_one_word(n); - } -} - -/* - * Conditional selection is done by negating 'which', to give a mask - * word which is all 1s if which==1 and all 0s if which==0. Then you - * can select between two inputs a,b without data-dependent control - * flow by XORing them to get their difference; ANDing with the mask - * word to replace that difference with 0 if which==0; and XORing that - * into a, which will either turn it into b or leave it alone. - * - * This trick will be used throughout this code and taken as read the - * rest of the time (or else I'd be here all week typing comments), - * but I felt I ought to explain it in words _once_. - */ -void mp_select_into(mp_int *dest, mp_int *src0, mp_int *src1, - unsigned which) -{ - BignumInt mask = -(BignumInt)(1 & which); - for (size_t i = 0; i < dest->nw; i++) { - BignumInt srcword0 = mp_word(src0, i), srcword1 = mp_word(src1, i); - dest->w[i] = srcword0 ^ ((srcword1 ^ srcword0) & mask); - } -} - -void mp_cond_swap(mp_int *x0, mp_int *x1, unsigned swap) -{ - assert(x0->nw == x1->nw); - volatile BignumInt mask = -(BignumInt)(1 & swap); - for (size_t i = 0; i < x0->nw; i++) { - BignumInt diff = (x0->w[i] ^ x1->w[i]) & mask; - x0->w[i] ^= diff; - x1->w[i] ^= diff; - } -} - -void mp_clear(mp_int *x) -{ - smemclr(x->w, x->nw * sizeof(BignumInt)); -} - -void mp_cond_clear(mp_int *x, unsigned clear) -{ - BignumInt mask = ~-(BignumInt)(1 & clear); - for (size_t i = 0; i < x->nw; i++) - x->w[i] &= mask; -} - -/* - * Common code between mp_from_bytes_{le,be} which reads bytes in an - * arbitrary arithmetic progression. - */ -static mp_int *mp_from_bytes_int(ptrlen bytes, size_t m, size_t c) -{ - size_t nw = (bytes.len + BIGNUM_INT_BYTES - 1) / BIGNUM_INT_BYTES; - nw = size_t_max(nw, 1); - mp_int *n = mp_make_sized(nw); - for (size_t i = 0; i < bytes.len; i++) - n->w[i / BIGNUM_INT_BYTES] |= - (BignumInt)(((const unsigned char *)bytes.ptr)[m*i+c]) << - (8 * (i % BIGNUM_INT_BYTES)); - return n; -} - -mp_int *mp_from_bytes_le(ptrlen bytes) -{ - return mp_from_bytes_int(bytes, 1, 0); -} - -mp_int *mp_from_bytes_be(ptrlen bytes) -{ - return mp_from_bytes_int(bytes, -1, bytes.len - 1); -} - -static mp_int *mp_from_words(size_t nw, const BignumInt *w) -{ - mp_int *x = mp_make_sized(nw); - memcpy(x->w, w, x->nw * sizeof(BignumInt)); - return x; -} - -/* - * Decimal-to-binary conversion: just go through the input string - * adding on the decimal value of each digit, and then multiplying the - * number so far by 10. - */ -mp_int *mp_from_decimal_pl(ptrlen decimal) -{ - /* 196/59 is an upper bound (and also a continued-fraction - * convergent) for log2(10), so this conservatively estimates the - * number of bits that will be needed to store any number that can - * be written in this many decimal digits. */ - assert(decimal.len < (~(size_t)0) / 196); - size_t bits = 196 * decimal.len / 59; - - /* Now round that up to words. */ - size_t words = bits / BIGNUM_INT_BITS + 1; - - mp_int *x = mp_make_sized(words); - for (size_t i = 0; i < decimal.len; i++) { - mp_add_integer_into(x, x, ((const char *)decimal.ptr)[i] - '0'); - - if (i+1 == decimal.len) - break; - - mp_mul_integer_into(x, x, 10); - } - return x; -} - -mp_int *mp_from_decimal(const char *decimal) -{ - return mp_from_decimal_pl(ptrlen_from_asciz(decimal)); -} - -/* - * Hex-to-binary conversion: _algorithmically_ simpler than decimal - * (none of those multiplications by 10), but there's some fiddly - * bit-twiddling needed to process each hex digit without diverging - * control flow depending on whether it's a letter or a number. - */ -mp_int *mp_from_hex_pl(ptrlen hex) -{ - assert(hex.len <= (~(size_t)0) / 4); - size_t bits = hex.len * 4; - size_t words = (bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - words = size_t_max(words, 1); - mp_int *x = mp_make_sized(words); - for (size_t nibble = 0; nibble < hex.len; nibble++) { - BignumInt digit = ((const char *)hex.ptr)[hex.len-1 - nibble]; - - BignumInt lmask = ~-((BignumInt)((digit-'a')|('f'-digit)) - >> (BIGNUM_INT_BITS-1)); - BignumInt umask = ~-((BignumInt)((digit-'A')|('F'-digit)) - >> (BIGNUM_INT_BITS-1)); - - BignumInt digitval = digit - '0'; - digitval ^= (digitval ^ (digit - 'a' + 10)) & lmask; - digitval ^= (digitval ^ (digit - 'A' + 10)) & umask; - digitval &= 0xF; /* at least be _slightly_ nice about weird input */ - - size_t word_idx = nibble / (BIGNUM_INT_BYTES*2); - size_t nibble_within_word = nibble % (BIGNUM_INT_BYTES*2); - x->w[word_idx] |= digitval << (nibble_within_word * 4); - } - return x; -} - -mp_int *mp_from_hex(const char *hex) -{ - return mp_from_hex_pl(ptrlen_from_asciz(hex)); -} - -mp_int *mp_copy(mp_int *x) -{ - return mp_from_words(x->nw, x->w); -} - -uint8_t mp_get_byte(mp_int *x, size_t byte) -{ - return 0xFF & (mp_word(x, byte / BIGNUM_INT_BYTES) >> - (8 * (byte % BIGNUM_INT_BYTES))); -} - -unsigned mp_get_bit(mp_int *x, size_t bit) -{ - return 1 & (mp_word(x, bit / BIGNUM_INT_BITS) >> - (bit % BIGNUM_INT_BITS)); -} - -uintmax_t mp_get_integer(mp_int *x) -{ - uintmax_t toret = 0; - for (size_t i = x->nw; i-- > 0 ;) - toret = shift_left_by_one_word(toret) | x->w[i]; - return toret; -} - -void mp_set_bit(mp_int *x, size_t bit, unsigned val) -{ - size_t word = bit / BIGNUM_INT_BITS; - assert(word < x->nw); - - unsigned shift = (bit % BIGNUM_INT_BITS); - - x->w[word] &= ~((BignumInt)1 << shift); - x->w[word] |= (BignumInt)(val & 1) << shift; -} - -/* - * Helper function used here and there to normalise any nonzero input - * value to 1. - */ -static inline unsigned normalise_to_1(BignumInt n) -{ - n = (n >> 1) | (n & 1); /* ensure top bit is clear */ - n = (BignumInt)(-n) >> (BIGNUM_INT_BITS - 1); /* normalise to 0 or 1 */ - return n; -} -static inline unsigned normalise_to_1_u64(uint64_t n) -{ - n = (n >> 1) | (n & 1); /* ensure top bit is clear */ - n = (-n) >> 63; /* normalise to 0 or 1 */ - return n; -} - -/* - * Find the highest nonzero word in a number. Returns the index of the - * word in x->w, and also a pair of output uint64_t in which that word - * appears in the high one shifted left by 'shift_wanted' bits, the - * words immediately below it occupy the space to the right, and the - * words below _that_ fill up the low one. - * - * If there is no nonzero word at all, the passed-by-reference output - * variables retain their original values. - */ -static inline void mp_find_highest_nonzero_word_pair( - mp_int *x, size_t shift_wanted, size_t *index, - uint64_t *hi, uint64_t *lo) -{ - uint64_t curr_hi = 0, curr_lo = 0; - - for (size_t curr_index = 0; curr_index < x->nw; curr_index++) { - BignumInt curr_word = x->w[curr_index]; - unsigned indicator = normalise_to_1(curr_word); - - curr_lo = (BIGNUM_INT_BITS < 64 ? (curr_lo >> BIGNUM_INT_BITS) : 0) | - (curr_hi << (64 - BIGNUM_INT_BITS)); - curr_hi = (BIGNUM_INT_BITS < 64 ? (curr_hi >> BIGNUM_INT_BITS) : 0) | - ((uint64_t)curr_word << shift_wanted); - - if (hi) *hi ^= (curr_hi ^ *hi ) & -(uint64_t)indicator; - if (lo) *lo ^= (curr_lo ^ *lo ) & -(uint64_t)indicator; - if (index) *index ^= (curr_index ^ *index) & -(size_t) indicator; - } -} - -size_t mp_get_nbits(mp_int *x) -{ - /* Sentinel values in case there are no bits set at all: we - * imagine that there's a word at position -1 (i.e. the topmost - * fraction word) which is all 1s, because that way, we handle a - * zero input by considering its highest set bit to be the top one - * of that word, i.e. just below the units digit, i.e. at bit - * index -1, i.e. so we'll return 0 on output. */ - size_t hiword_index = -(size_t)1; - uint64_t hiword64 = ~(BignumInt)0; - - /* - * Find the highest nonzero word and its index. - */ - mp_find_highest_nonzero_word_pair(x, 0, &hiword_index, &hiword64, NULL); - BignumInt hiword = hiword64; /* in case BignumInt is a narrower type */ - - /* - * Find the index of the highest set bit within hiword. - */ - BignumInt hibit_index = 0; - for (size_t i = (1 << (BIGNUM_INT_BITS_BITS-1)); i != 0; i >>= 1) { - BignumInt shifted_word = hiword >> i; - BignumInt indicator = - (BignumInt)(-shifted_word) >> (BIGNUM_INT_BITS-1); - hiword ^= (shifted_word ^ hiword ) & -indicator; - hibit_index += i & -(size_t)indicator; - } - - /* - * Put together the result. - */ - return (hiword_index << BIGNUM_INT_BITS_BITS) + hibit_index + 1; -} - -/* - * Shared code between the hex and decimal output functions to get rid - * of leading zeroes on the output string. The idea is that we wrote - * out a fixed number of digits and a trailing \0 byte into 'buf', and - * now we want to shift it all left so that the first nonzero digit - * moves to buf[0] (or, if there are no nonzero digits at all, we move - * up by 'maxtrim', so that we return 0 as "0" instead of ""). - */ -static void trim_leading_zeroes(char *buf, size_t bufsize, size_t maxtrim) -{ - size_t trim = maxtrim; - - /* - * Look for the first character not equal to '0', to find the - * shift count. - */ - if (trim > 0) { - for (size_t pos = trim; pos-- > 0 ;) { - uint8_t diff = buf[pos] ^ '0'; - size_t mask = -((((size_t)diff) - 1) >> (SIZE_T_BITS - 1)); - trim ^= (trim ^ pos) & ~mask; - } - } - - /* - * Now do the shift, in log n passes each of which does a - * conditional shift by 2^i bytes if bit i is set in the shift - * count. - */ - uint8_t *ubuf = (uint8_t *)buf; - for (size_t logd = 0; bufsize >> logd; logd++) { - uint8_t mask = -(uint8_t)((trim >> logd) & 1); - size_t d = (size_t)1 << logd; - for (size_t i = 0; i+d < bufsize; i++) { - uint8_t diff = mask & (ubuf[i] ^ ubuf[i+d]); - ubuf[i] ^= diff; - ubuf[i+d] ^= diff; - } - } -} - -/* - * Binary to decimal conversion. Our strategy here is to extract each - * decimal digit by finding the input number's residue mod 10, then - * subtract that off to give an exact multiple of 10, which then means - * you can safely divide by 10 by means of shifting right one bit and - * then multiplying by the inverse of 5 mod 2^n. - */ -char *mp_get_decimal(mp_int *x_orig) -{ - mp_int *x = mp_copy(x_orig), *y = mp_make_sized(x->nw); - - /* - * The inverse of 5 mod 2^lots is 0xccccccccccccccccccccd, for an - * appropriate number of 'c's. Manually construct an integer the - * right size. - */ - mp_int *inv5 = mp_make_sized(x->nw); - assert(BIGNUM_INT_BITS % 8 == 0); - for (size_t i = 0; i < inv5->nw; i++) - inv5->w[i] = BIGNUM_INT_MASK / 5 * 4; - inv5->w[0]++; - - /* - * 146/485 is an upper bound (and also a continued-fraction - * convergent) of log10(2), so this is a conservative estimate of - * the number of decimal digits needed to store a value that fits - * in this many binary bits. - */ - assert(x->nw < (~(size_t)1) / (146 * BIGNUM_INT_BITS)); - size_t bufsize = size_t_max(x->nw * (146 * BIGNUM_INT_BITS) / 485, 1) + 2; - char *outbuf = snewn(bufsize, char); - outbuf[bufsize - 1] = '\0'; - - /* - * Loop over the number generating digits from the least - * significant upwards, so that we write to outbuf in reverse - * order. - */ - for (size_t pos = bufsize - 1; pos-- > 0 ;) { - /* - * Find the current residue mod 10. We do this by first - * summing the bytes of the number, with all but the lowest - * one multiplied by 6 (because 256^i == 6 mod 10 for all - * i>0). That gives us a single word congruent mod 10 to the - * input number, and then we reduce it further by manual - * multiplication and shifting, just in case the compiler - * target implements the C division operator in a way that has - * input-dependent timing. - */ - uint32_t low_digit = 0, maxval = 0, mult = 1; - for (size_t i = 0; i < x->nw; i++) { - for (unsigned j = 0; j < BIGNUM_INT_BYTES; j++) { - low_digit += mult * (0xFF & (x->w[i] >> (8*j))); - maxval += mult * 0xFF; - mult = 6; - } - /* - * For _really_ big numbers, prevent overflow of t by - * periodically folding the top half of the accumulator - * into the bottom half, using the same rule 'multiply by - * 6 when shifting down by one or more whole bytes'. - */ - if (maxval > UINT32_MAX - (6 * 0xFF * BIGNUM_INT_BYTES)) { - low_digit = (low_digit & 0xFFFF) + 6 * (low_digit >> 16); - maxval = (maxval & 0xFFFF) + 6 * (maxval >> 16); - } - } - - /* - * Final reduction of low_digit. We multiply by 2^32 / 10 - * (that's the constant 0x19999999) to get a 64-bit value - * whose top 32 bits are the approximate quotient - * low_digit/10; then we subtract off 10 times that; and - * finally we do one last trial subtraction of 10 by adding 6 - * (which sets bit 4 if the number was just over 10) and then - * testing bit 4. - */ - low_digit -= 10 * ((0x19999999ULL * low_digit) >> 32); - low_digit -= 10 * ((low_digit + 6) >> 4); - - assert(low_digit < 10); /* make sure we did reduce fully */ - outbuf[pos] = '0' + low_digit; - - /* - * Now subtract off that digit, divide by 2 (using a right - * shift) and by 5 (using the modular inverse), to get the - * next output digit into the units position. - */ - mp_sub_integer_into(x, x, low_digit); - mp_rshift_fixed_into(y, x, 1); - mp_mul_into(x, y, inv5); - } - - mp_free(x); - mp_free(y); - mp_free(inv5); - - trim_leading_zeroes(outbuf, bufsize, bufsize - 2); - return outbuf; -} - -/* - * Binary to hex conversion. Reasonably simple (only a spot of bit - * twiddling to choose whether to output a digit or a letter for each - * nibble). - */ -static char *mp_get_hex_internal(mp_int *x, uint8_t letter_offset) -{ - size_t nibbles = x->nw * BIGNUM_INT_BYTES * 2; - size_t bufsize = nibbles + 1; - char *outbuf = snewn(bufsize, char); - outbuf[nibbles] = '\0'; - - for (size_t nibble = 0; nibble < nibbles; nibble++) { - size_t word_idx = nibble / (BIGNUM_INT_BYTES*2); - size_t nibble_within_word = nibble % (BIGNUM_INT_BYTES*2); - uint8_t digitval = 0xF & (x->w[word_idx] >> (nibble_within_word * 4)); - - uint8_t mask = -((digitval + 6) >> 4); - char digit = digitval + '0' + (letter_offset & mask); - outbuf[nibbles-1 - nibble] = digit; - } - - trim_leading_zeroes(outbuf, bufsize, nibbles - 1); - return outbuf; -} - -char *mp_get_hex(mp_int *x) -{ - return mp_get_hex_internal(x, 'a' - ('0'+10)); -} - -char *mp_get_hex_uppercase(mp_int *x) -{ - return mp_get_hex_internal(x, 'A' - ('0'+10)); -} - -/* - * Routines for reading and writing the SSH-1 and SSH-2 wire formats - * for multiprecision integers, declared in marshal.h. - * - * These can't avoid having control flow dependent on the true bit - * size of the number, because the wire format requires the number of - * output bytes to depend on that. - */ -void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x) -{ - size_t bits = mp_get_nbits(x); - size_t bytes = (bits + 7) / 8; - - assert(bits < 0x10000); - put_uint16(bs, bits); - for (size_t i = bytes; i-- > 0 ;) - put_byte(bs, mp_get_byte(x, i)); -} - -void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x) -{ - size_t bytes = (mp_get_nbits(x) + 8) / 8; - - put_uint32(bs, bytes); - for (size_t i = bytes; i-- > 0 ;) - put_byte(bs, mp_get_byte(x, i)); -} - -mp_int *BinarySource_get_mp_ssh1(BinarySource *src) -{ - unsigned bitc = get_uint16(src); - ptrlen bytes = get_data(src, (bitc + 7) / 8); - if (get_err(src)) { - return mp_from_integer(0); - } else { - mp_int *toret = mp_from_bytes_be(bytes); - /* SSH-1.5 spec says that it's OK for the prefix uint16 to be - * _greater_ than the actual number of bits */ - if (mp_get_nbits(toret) > bitc) { - src->err = BSE_INVALID; - mp_free(toret); - toret = mp_from_integer(0); - } - return toret; - } -} - -mp_int *BinarySource_get_mp_ssh2(BinarySource *src) -{ - ptrlen bytes = get_string(src); - if (get_err(src)) { - return mp_from_integer(0); - } else { - const unsigned char *p = bytes.ptr; - if ((bytes.len > 0 && - ((p[0] & 0x80) || - (p[0] == 0 && (bytes.len <= 1 || !(p[1] & 0x80)))))) { - src->err = BSE_INVALID; - return mp_from_integer(0); - } - return mp_from_bytes_be(bytes); - } -} - -/* - * Make an mp_int structure whose words array aliases a subinterval of - * some other mp_int. This makes it easy to read or write just the low - * or high words of a number, e.g. to add a number starting from a - * high bit position, or to reduce mod 2^{n*BIGNUM_INT_BITS}. - * - * The convention throughout this code is that when we store an mp_int - * directly by value, we always expect it to be an alias of some kind, - * so its words array won't ever need freeing. Whereas an 'mp_int *' - * has an owner, who knows whether it needs freeing or whether it was - * created by address-taking an alias. - */ -static mp_int mp_make_alias(mp_int *in, size_t offset, size_t len) -{ - /* - * Bounds-check the offset and length so that we always return - * something valid, even if it's not necessarily the length the - * caller asked for. - */ - if (offset > in->nw) - offset = in->nw; - if (len > in->nw - offset) - len = in->nw - offset; - - mp_int toret; - toret.nw = len; - toret.w = in->w + offset; - return toret; -} - -/* - * A special case of mp_make_alias: in some cases we preallocate a - * large mp_int to use as scratch space (to avoid pointless - * malloc/free churn in recursive or iterative work). - * - * mp_alloc_from_scratch creates an alias of size 'len' to part of - * 'pool', and adjusts 'pool' itself so that further allocations won't - * overwrite that space. - * - * There's no free function to go with this. Typically you just copy - * the pool mp_int by value, allocate from the copy, and when you're - * done with those allocations, throw the copy away and go back to the - * original value of pool. (A mark/release system.) - */ -static mp_int mp_alloc_from_scratch(mp_int *pool, size_t len) -{ - assert(len <= pool->nw); - mp_int toret = mp_make_alias(pool, 0, len); - *pool = mp_make_alias(pool, len, pool->nw); - return toret; -} - -/* - * Internal component common to lots of assorted add/subtract code. - * Reads words from a,b; writes into w_out (which might be NULL if the - * output isn't even needed). Takes an input carry flag in 'carry', - * and returns the output carry. Each word read from b is ANDed with - * b_and and then XORed with b_xor. - * - * So you can implement addition by setting b_and to all 1s and b_xor - * to 0; you can subtract by making b_xor all 1s too (effectively - * bit-flipping b) and also passing 1 as the input carry (to turn - * one's complement into two's complement). And you can do conditional - * add/subtract by choosing b_and to be all 1s or all 0s based on a - * condition, because the value of b will be totally ignored if b_and - * == 0. - */ -static BignumCarry mp_add_masked_into( - BignumInt *w_out, size_t rw, mp_int *a, mp_int *b, - BignumInt b_and, BignumInt b_xor, BignumCarry carry) -{ - for (size_t i = 0; i < rw; i++) { - BignumInt aword = mp_word(a, i), bword = mp_word(b, i), out; - bword = (bword & b_and) ^ b_xor; - BignumADC(out, carry, aword, bword, carry); - if (w_out) - w_out[i] = out; - } - return carry; -} - -/* - * Like the public mp_add_into except that it returns the output carry. - */ -static inline BignumCarry mp_add_into_internal(mp_int *r, mp_int *a, mp_int *b) -{ - return mp_add_masked_into(r->w, r->nw, a, b, ~(BignumInt)0, 0, 0); -} - -void mp_add_into(mp_int *r, mp_int *a, mp_int *b) -{ - mp_add_into_internal(r, a, b); -} - -void mp_sub_into(mp_int *r, mp_int *a, mp_int *b) -{ - mp_add_masked_into(r->w, r->nw, a, b, ~(BignumInt)0, ~(BignumInt)0, 1); -} - -void mp_and_into(mp_int *r, mp_int *a, mp_int *b) -{ - for (size_t i = 0; i < r->nw; i++) { - BignumInt aword = mp_word(a, i), bword = mp_word(b, i); - r->w[i] = aword & bword; - } -} - -void mp_or_into(mp_int *r, mp_int *a, mp_int *b) -{ - for (size_t i = 0; i < r->nw; i++) { - BignumInt aword = mp_word(a, i), bword = mp_word(b, i); - r->w[i] = aword | bword; - } -} - -void mp_xor_into(mp_int *r, mp_int *a, mp_int *b) -{ - for (size_t i = 0; i < r->nw; i++) { - BignumInt aword = mp_word(a, i), bword = mp_word(b, i); - r->w[i] = aword ^ bword; - } -} - -void mp_bic_into(mp_int *r, mp_int *a, mp_int *b) -{ - for (size_t i = 0; i < r->nw; i++) { - BignumInt aword = mp_word(a, i), bword = mp_word(b, i); - r->w[i] = aword & ~bword; - } -} - -static void mp_cond_negate(mp_int *r, mp_int *x, unsigned yes) -{ - BignumCarry carry = yes; - BignumInt flip = -(BignumInt)yes; - for (size_t i = 0; i < r->nw; i++) { - BignumInt xword = mp_word(x, i); - xword ^= flip; - BignumADC(r->w[i], carry, 0, xword, carry); - } -} - -/* - * Similar to mp_add_masked_into, but takes a C integer instead of an - * mp_int as the masked operand. - */ -static BignumCarry mp_add_masked_integer_into( - BignumInt *w_out, size_t rw, mp_int *a, uintmax_t b, - BignumInt b_and, BignumInt b_xor, BignumCarry carry) -{ - for (size_t i = 0; i < rw; i++) { - BignumInt aword = mp_word(a, i); - BignumInt bword = b; - b = shift_right_by_one_word(b); - BignumInt out; - bword = (bword ^ b_xor) & b_and; - BignumADC(out, carry, aword, bword, carry); - if (w_out) - w_out[i] = out; - } - return carry; -} - -void mp_add_integer_into(mp_int *r, mp_int *a, uintmax_t n) -{ - mp_add_masked_integer_into(r->w, r->nw, a, n, ~(BignumInt)0, 0, 0); -} - -void mp_sub_integer_into(mp_int *r, mp_int *a, uintmax_t n) -{ - mp_add_masked_integer_into(r->w, r->nw, a, n, - ~(BignumInt)0, ~(BignumInt)0, 1); -} - -/* - * Sets r to a + n << (word_index * BIGNUM_INT_BITS), treating - * word_index as secret data. - */ -static void mp_add_integer_into_shifted_by_words( - mp_int *r, mp_int *a, uintmax_t n, size_t word_index) -{ - unsigned indicator = 0; - BignumCarry carry = 0; - - for (size_t i = 0; i < r->nw; i++) { - /* indicator becomes 1 when we reach the index that the least - * significant bits of n want to be placed at, and it stays 1 - * thereafter. */ - indicator |= 1 ^ normalise_to_1(i ^ word_index); - - /* If indicator is 1, we add the low bits of n into r, and - * shift n down. If it's 0, we add zero bits into r, and - * leave n alone. */ - BignumInt bword = n & -(BignumInt)indicator; - uintmax_t new_n = shift_right_by_one_word(n); - n ^= (n ^ new_n) & -(uintmax_t)indicator; - - BignumInt aword = mp_word(a, i); - BignumInt out; - BignumADC(out, carry, aword, bword, carry); - r->w[i] = out; - } -} - -void mp_mul_integer_into(mp_int *r, mp_int *a, uint16_t n) -{ - BignumInt carry = 0, mult = n; - for (size_t i = 0; i < r->nw; i++) { - BignumInt aword = mp_word(a, i); - BignumMULADD(carry, r->w[i], aword, mult, carry); - } - assert(!carry); -} - -void mp_cond_add_into(mp_int *r, mp_int *a, mp_int *b, unsigned yes) -{ - BignumInt mask = -(BignumInt)(yes & 1); - mp_add_masked_into(r->w, r->nw, a, b, mask, 0, 0); -} - -void mp_cond_sub_into(mp_int *r, mp_int *a, mp_int *b, unsigned yes) -{ - BignumInt mask = -(BignumInt)(yes & 1); - mp_add_masked_into(r->w, r->nw, a, b, mask, mask, 1 & mask); -} - -/* - * Ordered comparison between unsigned numbers is done by subtracting - * one from the other and looking at the output carry. - */ -unsigned mp_cmp_hs(mp_int *a, mp_int *b) -{ - size_t rw = size_t_max(a->nw, b->nw); - return mp_add_masked_into(NULL, rw, a, b, ~(BignumInt)0, ~(BignumInt)0, 1); -} - -unsigned mp_hs_integer(mp_int *x, uintmax_t n) -{ - BignumInt carry = 1; - size_t nwords = sizeof(n)/BIGNUM_INT_BYTES; - for (size_t i = 0, e = size_t_max(x->nw, nwords); i < e; i++) { - BignumInt nword = n; - n = shift_right_by_one_word(n); - BignumInt dummy_out; - BignumADC(dummy_out, carry, mp_word(x, i), ~nword, carry); - (void)dummy_out; - } - return carry; -} - -/* - * Equality comparison is done by bitwise XOR of the input numbers, - * ORing together all the output words, and normalising the result - * using our careful normalise_to_1 helper function. - */ -unsigned mp_cmp_eq(mp_int *a, mp_int *b) -{ - BignumInt diff = 0; - for (size_t i = 0, limit = size_t_max(a->nw, b->nw); i < limit; i++) - diff |= mp_word(a, i) ^ mp_word(b, i); - return 1 ^ normalise_to_1(diff); /* return 1 if diff _is_ zero */ -} - -unsigned mp_eq_integer(mp_int *x, uintmax_t n) -{ - BignumInt diff = 0; - size_t nwords = sizeof(n)/BIGNUM_INT_BYTES; - for (size_t i = 0, e = size_t_max(x->nw, nwords); i < e; i++) { - BignumInt nword = n; - n = shift_right_by_one_word(n); - diff |= mp_word(x, i) ^ nword; - } - return 1 ^ normalise_to_1(diff); /* return 1 if diff _is_ zero */ -} - -static void mp_neg_into(mp_int *r, mp_int *a) -{ - mp_int zero; - zero.nw = 0; - mp_sub_into(r, &zero, a); -} - -mp_int *mp_add(mp_int *x, mp_int *y) -{ - mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw) + 1); - mp_add_into(r, x, y); - return r; -} - -mp_int *mp_sub(mp_int *x, mp_int *y) -{ - mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw)); - mp_sub_into(r, x, y); - return r; -} - -/* - * Internal routine: multiply and accumulate in the trivial O(N^2) - * way. Sets r <- r + a*b. - */ -static void mp_mul_add_simple(mp_int *r, mp_int *a, mp_int *b) -{ - BignumInt *aend = a->w + a->nw, *bend = b->w + b->nw, *rend = r->w + r->nw; - - for (BignumInt *ap = a->w, *rp = r->w; - ap < aend && rp < rend; ap++, rp++) { - - BignumInt adata = *ap, carry = 0, *rq = rp; - - for (BignumInt *bp = b->w; bp < bend && rq < rend; bp++, rq++) { - BignumInt bdata = bp < bend ? *bp : 0; - BignumMULADD2(carry, *rq, adata, bdata, *rq, carry); - } - - for (; rq < rend; rq++) - BignumADC(*rq, carry, carry, *rq, 0); - } -} - -#ifndef KARATSUBA_THRESHOLD /* allow redefinition via -D for testing */ -#define KARATSUBA_THRESHOLD 24 -#endif - -static inline size_t mp_mul_scratchspace_unary(size_t n) -{ - /* - * Simplistic and overcautious bound on the amount of scratch - * space that the recursive multiply function will need. - * - * The rationale is: on the main Karatsuba branch of - * mp_mul_internal, which is the most space-intensive one, we - * allocate space for (a0+a1) and (b0+b1) (each just over half the - * input length n) and their product (the sum of those sizes, i.e. - * just over n itself). Then in order to actually compute the - * product, we do a recursive multiplication of size just over n. - * - * If all those 'just over' weren't there, and everything was - * _exactly_ half the length, you'd get the amount of space for a - * size-n multiply defined by the recurrence M(n) = 2n + M(n/2), - * which is satisfied by M(n) = 4n. But instead it's (2n plus a - * word or two) and M(n/2 plus a word or two). On the assumption - * that there's still some constant k such that M(n) <= kn, this - * gives us kn = 2n + w + k(n/2 + w), where w is a small constant - * (one or two words). That simplifies to kn/2 = 2n + (k+1)w, and - * since we don't even _start_ needing scratch space until n is at - * least 50, we can bound 2n + (k+1)w above by 3n, giving k=6. - * - * So I claim that 6n words of scratch space will suffice, and I - * check that by assertion at every stage of the recursion. - */ - return n * 6; -} - -static size_t mp_mul_scratchspace(size_t rw, size_t aw, size_t bw) -{ - size_t inlen = size_t_min(rw, size_t_max(aw, bw)); - return mp_mul_scratchspace_unary(inlen); -} - -static void mp_mul_internal(mp_int *r, mp_int *a, mp_int *b, mp_int scratch) -{ - size_t inlen = size_t_min(r->nw, size_t_max(a->nw, b->nw)); - assert(scratch.nw >= mp_mul_scratchspace_unary(inlen)); - - mp_clear(r); - - if (inlen < KARATSUBA_THRESHOLD || a->nw == 0 || b->nw == 0) { - /* - * The input numbers are too small to bother optimising. Go - * straight to the simple primitive approach. - */ - mp_mul_add_simple(r, a, b); - return; - } - - /* - * Karatsuba divide-and-conquer algorithm. We 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. - */ - - /* Break up the input as botlen + toplen, with botlen >= toplen. - * The 'base' D is equal to 2^{botlen * BIGNUM_INT_BITS}. */ - size_t toplen = inlen / 2; - size_t botlen = inlen - toplen; - - /* Alias bignums that address the two halves of a,b, and useful - * pieces of r. */ - mp_int a0 = mp_make_alias(a, 0, botlen); - mp_int b0 = mp_make_alias(b, 0, botlen); - mp_int a1 = mp_make_alias(a, botlen, toplen); - mp_int b1 = mp_make_alias(b, botlen, toplen); - mp_int r0 = mp_make_alias(r, 0, botlen*2); - mp_int r1 = mp_make_alias(r, botlen, r->nw); - mp_int r2 = mp_make_alias(r, botlen*2, r->nw); - - /* Recurse to compute a0*b0 and a1*b1, in their correct positions - * in the output bignum. They can't overlap. */ - mp_mul_internal(&r0, &a0, &b0, scratch); - mp_mul_internal(&r2, &a1, &b1, scratch); - - if (r->nw < inlen*2) { - /* - * The output buffer isn't large enough to require the whole - * product, so some of a1*b1 won't have been stored. In that - * case we won't try to do the full Karatsuba optimisation; - * we'll just recurse again to compute a0*b1 and a1*b0 - or at - * least as much of them as the output buffer size requires - - * and add each one in. - */ - mp_int s = mp_alloc_from_scratch( - &scratch, size_t_min(botlen+toplen, r1.nw)); - - mp_mul_internal(&s, &a0, &b1, scratch); - mp_add_into(&r1, &r1, &s); - mp_mul_internal(&s, &a1, &b0, scratch); - mp_add_into(&r1, &r1, &s); - return; - } - - /* a0+a1 and b0+b1 */ - mp_int asum = mp_alloc_from_scratch(&scratch, botlen+1); - mp_int bsum = mp_alloc_from_scratch(&scratch, botlen+1); - mp_add_into(&asum, &a0, &a1); - mp_add_into(&bsum, &b0, &b1); - - /* Their product */ - mp_int product = mp_alloc_from_scratch(&scratch, botlen*2+1); - mp_mul_internal(&product, &asum, &bsum, scratch); - - /* Subtract off the outer terms we already have */ - mp_sub_into(&product, &product, &r0); - mp_sub_into(&product, &product, &r2); - - /* And add it in with the right offset. */ - mp_add_into(&r1, &r1, &product); -} - -void mp_mul_into(mp_int *r, mp_int *a, mp_int *b) -{ - mp_int *scratch = mp_make_sized(mp_mul_scratchspace(r->nw, a->nw, b->nw)); - mp_mul_internal(r, a, b, *scratch); - mp_free(scratch); -} - -mp_int *mp_mul(mp_int *x, mp_int *y) -{ - mp_int *r = mp_make_sized(x->nw + y->nw); - mp_mul_into(r, x, y); - return r; -} - -void mp_lshift_fixed_into(mp_int *r, mp_int *a, size_t bits) -{ - size_t words = bits / BIGNUM_INT_BITS; - size_t bitoff = bits % BIGNUM_INT_BITS; - - for (size_t i = r->nw; i-- > 0 ;) { - if (i < words) { - r->w[i] = 0; - } else { - r->w[i] = mp_word(a, i - words); - if (bitoff != 0) { - r->w[i] <<= bitoff; - if (i > words) - r->w[i] |= mp_word(a, i - words - 1) >> - (BIGNUM_INT_BITS - bitoff); - } - } - } -} - -void mp_rshift_fixed_into(mp_int *r, mp_int *a, size_t bits) -{ - size_t words = bits / BIGNUM_INT_BITS; - size_t bitoff = bits % BIGNUM_INT_BITS; - - for (size_t i = 0; i < r->nw; i++) { - r->w[i] = mp_word(a, i + words); - if (bitoff != 0) { - r->w[i] >>= bitoff; - r->w[i] |= mp_word(a, i + words + 1) << (BIGNUM_INT_BITS - bitoff); - } - } -} - -mp_int *mp_lshift_fixed(mp_int *x, size_t bits) -{ - size_t words = (bits + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - mp_int *r = mp_make_sized(x->nw + words); - mp_lshift_fixed_into(r, x, bits); - return r; -} - -mp_int *mp_rshift_fixed(mp_int *x, size_t bits) -{ - size_t words = bits / BIGNUM_INT_BITS; - size_t nw = x->nw - size_t_min(x->nw, words); - mp_int *r = mp_make_sized(size_t_max(nw, 1)); - mp_rshift_fixed_into(r, x, bits); - return r; -} - -/* - * Safe right shift is done using the same technique as - * trim_leading_zeroes above: you make an n-word left shift by - * composing an appropriate subset of power-of-2-sized shifts, so it - * takes log_2(n) loop iterations each of which does a different shift - * by a power of 2 words, using the usual bit twiddling to make the - * whole shift conditional on the appropriate bit of n. - */ -static void mp_rshift_safe_in_place(mp_int *r, size_t bits) -{ - size_t wordshift = bits / BIGNUM_INT_BITS; - size_t bitshift = bits % BIGNUM_INT_BITS; - - unsigned clear = (r->nw - wordshift) >> (CHAR_BIT * sizeof(size_t) - 1); - mp_cond_clear(r, clear); - - for (unsigned bit = 0; r->nw >> bit; bit++) { - size_t word_offset = (size_t)1 << bit; - BignumInt mask = -(BignumInt)((wordshift >> bit) & 1); - for (size_t i = 0; i < r->nw; i++) { - BignumInt w = mp_word(r, i + word_offset); - r->w[i] ^= (r->w[i] ^ w) & mask; - } - } - - /* - * That's done the shifting by words; now we do the shifting by - * bits. - */ - for (unsigned bit = 0; bit < BIGNUM_INT_BITS_BITS; bit++) { - unsigned shift = 1 << bit, upshift = BIGNUM_INT_BITS - shift; - BignumInt mask = -(BignumInt)((bitshift >> bit) & 1); - for (size_t i = 0; i < r->nw; i++) { - BignumInt w = ((r->w[i] >> shift) | (mp_word(r, i+1) << upshift)); - r->w[i] ^= (r->w[i] ^ w) & mask; - } - } -} - -mp_int *mp_rshift_safe(mp_int *x, size_t bits) -{ - mp_int *r = mp_copy(x); - mp_rshift_safe_in_place(r, bits); - return r; -} - -void mp_rshift_safe_into(mp_int *r, mp_int *x, size_t bits) -{ - mp_copy_into(r, x); - mp_rshift_safe_in_place(r, bits); -} - -static void mp_lshift_safe_in_place(mp_int *r, size_t bits) -{ - size_t wordshift = bits / BIGNUM_INT_BITS; - size_t bitshift = bits % BIGNUM_INT_BITS; - - /* - * Same strategy as mp_rshift_safe_in_place, but of course the - * other way up. - */ - - unsigned clear = (r->nw - wordshift) >> (CHAR_BIT * sizeof(size_t) - 1); - mp_cond_clear(r, clear); - - for (unsigned bit = 0; r->nw >> bit; bit++) { - size_t word_offset = (size_t)1 << bit; - BignumInt mask = -(BignumInt)((wordshift >> bit) & 1); - for (size_t i = r->nw; i-- > 0 ;) { - BignumInt w = mp_word(r, i - word_offset); - r->w[i] ^= (r->w[i] ^ w) & mask; - } - } - - size_t downshift = BIGNUM_INT_BITS - bitshift; - size_t no_shift = (downshift >> BIGNUM_INT_BITS_BITS); - downshift &= ~-(size_t)no_shift; - BignumInt downshifted_mask = ~-(BignumInt)no_shift; - - for (size_t i = r->nw; i-- > 0 ;) { - r->w[i] = (r->w[i] << bitshift) | - ((mp_word(r, i-1) >> downshift) & downshifted_mask); - } -} - -void mp_lshift_safe_into(mp_int *r, mp_int *x, size_t bits) -{ - mp_copy_into(r, x); - mp_lshift_safe_in_place(r, bits); -} - -void mp_reduce_mod_2to(mp_int *x, size_t p) -{ - size_t word = p / BIGNUM_INT_BITS; - size_t mask = ((size_t)1 << (p % BIGNUM_INT_BITS)) - 1; - for (; word < x->nw; word++) { - x->w[word] &= mask; - mask = 0; - } -} - -/* - * Inverse mod 2^n is computed by an iterative technique which doubles - * the number of bits at each step. - */ -mp_int *mp_invert_mod_2to(mp_int *x, size_t p) -{ - /* Input checks: x must be coprime to the modulus, i.e. odd, and p - * can't be zero */ - assert(x->nw > 0); - assert(x->w[0] & 1); - assert(p > 0); - - size_t rw = (p + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - rw = size_t_max(rw, 1); - mp_int *r = mp_make_sized(rw); - - size_t mul_scratchsize = mp_mul_scratchspace(2*rw, rw, rw); - mp_int *scratch_orig = mp_make_sized(6 * rw + mul_scratchsize); - mp_int scratch_per_iter = *scratch_orig; - mp_int mul_scratch = mp_alloc_from_scratch( - &scratch_per_iter, mul_scratchsize); - - r->w[0] = 1; - - for (size_t b = 1; b < p; b <<= 1) { - /* - * In each step of this iteration, we have the inverse of x - * mod 2^b, and we want the inverse of x mod 2^{2b}. - * - * Write B = 2^b for convenience, so we want x^{-1} mod B^2. - * Let x = x_0 + B x_1 + k B^2, with 0 <= x_0,x_1 < B. - * - * We want to find r_0 and r_1 such that - * (r_1 B + r_0) (x_1 B + x_0) == 1 (mod B^2) - * - * To begin with, we know r_0 must be the inverse mod B of - * x_0, i.e. of x, i.e. it is the inverse we computed in the - * previous iteration. So now all we need is r_1. - * - * Multiplying out, neglecting multiples of B^2, and writing - * x_0 r_0 = K B + 1, we have - * - * r_1 x_0 B + r_0 x_1 B + K B == 0 (mod B^2) - * => r_1 x_0 B == - r_0 x_1 B - K B (mod B^2) - * => r_1 x_0 == - r_0 x_1 - K (mod B) - * => r_1 == r_0 (- r_0 x_1 - K) (mod B) - * - * (the last step because we multiply through by the inverse - * of x_0, which we already know is r_0). - */ - - mp_int scratch_this_iter = scratch_per_iter; - size_t Bw = (b + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - size_t B2w = (2*b + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - - /* Start by finding K: multiply x_0 by r_0, and shift down. */ - mp_int x0 = mp_alloc_from_scratch(&scratch_this_iter, Bw); - mp_copy_into(&x0, x); - mp_reduce_mod_2to(&x0, b); - mp_int r0 = mp_make_alias(r, 0, Bw); - mp_int Kshift = mp_alloc_from_scratch(&scratch_this_iter, B2w); - mp_mul_internal(&Kshift, &x0, &r0, mul_scratch); - mp_int K = mp_alloc_from_scratch(&scratch_this_iter, Bw); - mp_rshift_fixed_into(&K, &Kshift, b); - - /* Now compute the product r_0 x_1, reusing the space of Kshift. */ - mp_int x1 = mp_alloc_from_scratch(&scratch_this_iter, Bw); - mp_rshift_fixed_into(&x1, x, b); - mp_reduce_mod_2to(&x1, b); - mp_int r0x1 = mp_make_alias(&Kshift, 0, Bw); - mp_mul_internal(&r0x1, &r0, &x1, mul_scratch); - - /* Add K to that. */ - mp_add_into(&r0x1, &r0x1, &K); - - /* Negate it. */ - mp_neg_into(&r0x1, &r0x1); - - /* Multiply by r_0. */ - mp_int r1 = mp_alloc_from_scratch(&scratch_this_iter, Bw); - mp_mul_internal(&r1, &r0, &r0x1, mul_scratch); - mp_reduce_mod_2to(&r1, b); - - /* That's our r_1, so add it on to r_0 to get the full inverse - * output from this iteration. */ - mp_lshift_fixed_into(&K, &r1, (b % BIGNUM_INT_BITS)); - size_t Bpos = b / BIGNUM_INT_BITS; - mp_int r1_position = mp_make_alias(r, Bpos, B2w-Bpos); - mp_add_into(&r1_position, &r1_position, &K); - } - - /* Finally, reduce mod the precise desired number of bits. */ - mp_reduce_mod_2to(r, p); - - mp_free(scratch_orig); - return r; -} - -static size_t monty_scratch_size(MontyContext *mc) -{ - return 3*mc->rw + mc->pw + mp_mul_scratchspace(mc->pw, mc->rw, mc->rw); -} - -MontyContext *monty_new(mp_int *modulus) -{ - MontyContext *mc = snew(MontyContext); - - mc->rw = modulus->nw; - mc->rbits = mc->rw * BIGNUM_INT_BITS; - mc->pw = mc->rw * 2 + 1; - - mc->m = mp_copy(modulus); - - mc->minus_minv_mod_r = mp_invert_mod_2to(mc->m, mc->rbits); - mp_neg_into(mc->minus_minv_mod_r, mc->minus_minv_mod_r); - - mp_int *r = mp_make_sized(mc->rw + 1); - r->w[mc->rw] = 1; - mc->powers_of_r_mod_m[0] = mp_mod(r, mc->m); - mp_free(r); - - for (size_t j = 1; j < lenof(mc->powers_of_r_mod_m); j++) - mc->powers_of_r_mod_m[j] = mp_modmul( - mc->powers_of_r_mod_m[0], mc->powers_of_r_mod_m[j-1], mc->m); - - mc->scratch = mp_make_sized(monty_scratch_size(mc)); - - return mc; -} - -void monty_free(MontyContext *mc) -{ - mp_free(mc->m); - for (size_t j = 0; j < 3; j++) - mp_free(mc->powers_of_r_mod_m[j]); - mp_free(mc->minus_minv_mod_r); - mp_free(mc->scratch); - smemclr(mc, sizeof(*mc)); - sfree(mc); -} - -/* - * The main Montgomery reduction step. - */ -static mp_int monty_reduce_internal(MontyContext *mc, mp_int *x, mp_int scratch) -{ - /* - * The trick with Montgomery reduction is that on the one hand we - * want to reduce the size of the input by a factor of about r, - * and on the other hand, the two numbers we just multiplied were - * both stored with an extra factor of r multiplied in. So we - * computed ar*br = ab r^2, but we want to return abr, so we need - * to divide by r - and if we can do that by _actually dividing_ - * by r then this also reduces the size of the number. - * - * But we can only do that if the number we're dividing by r is a - * multiple of r. So first we must add an adjustment to it which - * clears its bottom 'rbits' bits. That adjustment must be a - * multiple of m in order to leave the residue mod n unchanged, so - * the question is, what multiple of m can we add to x to make it - * congruent to 0 mod r? And the answer is, x * (-m)^{-1} mod r. - */ - - /* x mod r */ - mp_int x_lo = mp_make_alias(x, 0, mc->rbits); - - /* x * (-m)^{-1}, i.e. the number we want to multiply by m */ - mp_int k = mp_alloc_from_scratch(&scratch, mc->rw); - mp_mul_internal(&k, &x_lo, mc->minus_minv_mod_r, scratch); - - /* m times that, i.e. the number we want to add to x */ - mp_int mk = mp_alloc_from_scratch(&scratch, mc->pw); - mp_mul_internal(&mk, mc->m, &k, scratch); - - /* Add it to x */ - mp_add_into(&mk, x, &mk); - - /* Reduce mod r, by simply making an alias to the upper words of x */ - mp_int toret = mp_make_alias(&mk, mc->rw, mk.nw - mc->rw); - - /* - * We'll generally be doing this after a multiplication of two - * fully reduced values. So our input could be anything up to m^2, - * and then we added up to rm to it. Hence, the maximum value is - * rm+m^2, and after dividing by r, that becomes r + m(m/r) < 2r. - * So a single trial-subtraction will finish reducing to the - * interval [0,m). - */ - mp_cond_sub_into(&toret, &toret, mc->m, mp_cmp_hs(&toret, mc->m)); - return toret; -} - -void monty_mul_into(MontyContext *mc, mp_int *r, mp_int *x, mp_int *y) -{ - assert(x->nw <= mc->rw); - assert(y->nw <= mc->rw); - - mp_int scratch = *mc->scratch; - mp_int tmp = mp_alloc_from_scratch(&scratch, 2*mc->rw); - mp_mul_into(&tmp, x, y); - mp_int reduced = monty_reduce_internal(mc, &tmp, scratch); - mp_copy_into(r, &reduced); - mp_clear(mc->scratch); -} - -mp_int *monty_mul(MontyContext *mc, mp_int *x, mp_int *y) -{ - mp_int *toret = mp_make_sized(mc->rw); - monty_mul_into(mc, toret, x, y); - return toret; -} - -mp_int *monty_modulus(MontyContext *mc) -{ - return mc->m; -} - -mp_int *monty_identity(MontyContext *mc) -{ - return mc->powers_of_r_mod_m[0]; -} - -mp_int *monty_invert(MontyContext *mc, mp_int *x) -{ - /* Given xr, we want to return x^{-1}r = (xr)^{-1} r^2 = - * monty_reduce((xr)^{-1} r^3) */ - mp_int *tmp = mp_invert(x, mc->m); - mp_int *toret = monty_mul(mc, tmp, mc->powers_of_r_mod_m[2]); - mp_free(tmp); - return toret; -} - -/* - * Importing a number into Montgomery representation involves - * multiplying it by r and reducing mod m. We use the general-purpose - * mp_modmul for this, in case the input number is out of range. - */ -mp_int *monty_import(MontyContext *mc, mp_int *x) -{ - return mp_modmul(x, mc->powers_of_r_mod_m[0], mc->m); -} - -void monty_import_into(MontyContext *mc, mp_int *r, mp_int *x) -{ - mp_int *imported = monty_import(mc, x); - mp_copy_into(r, imported); - mp_free(imported); -} - -/* - * Exporting a number means multiplying it by r^{-1}, which is exactly - * what monty_reduce does anyway, so we just do that. - */ -void monty_export_into(MontyContext *mc, mp_int *r, mp_int *x) -{ - assert(x->nw <= 2*mc->rw); - mp_int reduced = monty_reduce_internal(mc, x, *mc->scratch); - mp_copy_into(r, &reduced); - mp_clear(mc->scratch); -} - -mp_int *monty_export(MontyContext *mc, mp_int *x) -{ - mp_int *toret = mp_make_sized(mc->rw); - monty_export_into(mc, toret, x); - return toret; -} - -#define MODPOW_LOG2_WINDOW_SIZE 5 -#define MODPOW_WINDOW_SIZE (1 << MODPOW_LOG2_WINDOW_SIZE) -mp_int *monty_pow(MontyContext *mc, mp_int *base, mp_int *exponent) -{ - /* - * Modular exponentiation is done from the top down, using a - * fixed-window technique. - * - * We have a table storing every power of the base from base^0 up - * to base^{w-1}, where w is a small power of 2, say 2^k. (k is - * defined above as MODPOW_LOG2_WINDOW_SIZE, and w = 2^k is - * defined as MODPOW_WINDOW_SIZE.) - * - * We break the exponent up into k-bit chunks, from the bottom up, - * that is - * - * exponent = c_0 + 2^k c_1 + 2^{2k} c_2 + ... + 2^{nk} c_n - * - * and we compute base^exponent by computing in turn - * - * base^{c_n} - * base^{2^k c_n + c_{n-1}} - * base^{2^{2k} c_n + 2^k c_{n-1} + c_{n-2}} - * ... - * - * where each line is obtained by raising the previous line to the - * power 2^k (i.e. squaring it k times) and then multiplying in - * a value base^{c_i}, which we can look up in our table. - * - * Side-channel considerations: the exponent is secret, so - * actually doing a single table lookup by using a chunk of - * exponent bits as an array index would be an obvious leak of - * secret information into the cache. So instead, in each - * iteration, we read _all_ the table entries, and do a sequence - * of mp_select operations to leave just the one we wanted in the - * variable that will go into the multiplication. In other - * contexts (like software AES) that technique is so prohibitively - * slow that it makes you choose a strategy that doesn't use table - * lookups at all (we do bitslicing in preference); but here, this - * iteration through 2^k table elements is replacing k-1 bignum - * _multiplications_ that you'd have to use instead if you did - * simple square-and-multiply, and that makes it still a win. - */ - - /* Table that holds base^0, ..., base^{w-1} */ - mp_int *table[MODPOW_WINDOW_SIZE]; - table[0] = mp_copy(monty_identity(mc)); - for (size_t i = 1; i < MODPOW_WINDOW_SIZE; i++) - table[i] = monty_mul(mc, table[i-1], base); - - /* out accumulates the output value */ - mp_int *out = mp_make_sized(mc->rw); - mp_copy_into(out, monty_identity(mc)); - - /* table_entry will hold each value we get out of the table */ - mp_int *table_entry = mp_make_sized(mc->rw); - - /* Bit index of the chunk of bits we're working on. Start with the - * highest multiple of k strictly less than the size of our - * bignum, i.e. the highest-index chunk of bits that might - * conceivably contain any nonzero bit. */ - size_t i = (exponent->nw * BIGNUM_INT_BITS) - 1; - i -= i % MODPOW_LOG2_WINDOW_SIZE; - - bool first_iteration = true; - - while (true) { - /* Construct the table index */ - unsigned table_index = 0; - for (size_t j = 0; j < MODPOW_LOG2_WINDOW_SIZE; j++) - table_index |= mp_get_bit(exponent, i+j) << j; - - /* Iterate through the table to do a side-channel-safe lookup, - * ending up with table_entry = table[table_index] */ - mp_copy_into(table_entry, table[0]); - for (size_t j = 1; j < MODPOW_WINDOW_SIZE; j++) { - unsigned not_this_one = - ((table_index ^ j) + MODPOW_WINDOW_SIZE - 1) - >> MODPOW_LOG2_WINDOW_SIZE; - mp_select_into(table_entry, table[j], table_entry, not_this_one); - } - - if (!first_iteration) { - /* Multiply into the output */ - monty_mul_into(mc, out, out, table_entry); - } else { - /* On the first iteration, we can save one multiplication - * by just copying */ - mp_copy_into(out, table_entry); - first_iteration = false; - } - - /* If that was the bottommost chunk of bits, we're done */ - if (i == 0) - break; - - /* Otherwise, square k times and go round again. */ - for (size_t j = 0; j < MODPOW_LOG2_WINDOW_SIZE; j++) - monty_mul_into(mc, out, out, out); - - i-= MODPOW_LOG2_WINDOW_SIZE; - } - - for (size_t i = 0; i < MODPOW_WINDOW_SIZE; i++) - mp_free(table[i]); - mp_free(table_entry); - mp_clear(mc->scratch); - return out; -} - -mp_int *mp_modpow(mp_int *base, mp_int *exponent, mp_int *modulus) -{ - assert(modulus->nw > 0); - assert(modulus->w[0] & 1); - - MontyContext *mc = monty_new(modulus); - mp_int *m_base = monty_import(mc, base); - mp_int *m_out = monty_pow(mc, m_base, exponent); - mp_int *out = monty_export(mc, m_out); - mp_free(m_base); - mp_free(m_out); - monty_free(mc); - return out; -} - -/* - * Given two input integers a,b which are not both even, computes d = - * gcd(a,b) and also two integers A,B such that A*a - B*b = d. A,B - * will be the minimal non-negative pair satisfying that criterion, - * which is equivalent to saying that 0 <= A < b/d and 0 <= B < a/d. - * - * This algorithm is an adapted form of Stein's algorithm, which - * computes gcd(a,b) using only addition and bit shifts (i.e. without - * needing general division), using the following rules: - * - * - if both of a,b are even, divide off a common factor of 2 - * - if one of a,b (WLOG a) is even, then gcd(a,b) = gcd(a/2,b), so - * just divide a by 2 - * - if both of a,b are odd, then WLOG a>b, and gcd(a,b) = - * gcd(b,(a-b)/2). - * - * Sometimes this function is used for modular inversion, in which - * case we already know we expect the two inputs to be coprime, so to - * save time the 'both even' initial case is assumed not to arise (or - * to have been handled already by the caller). So this function just - * performs a sequence of reductions in the following form: - * - * - if a,b are both odd, sort them so that a > b, and replace a with - * b-a; otherwise sort them so that a is the even one - * - either way, now a is even and b is odd, so divide a by 2. - * - * The big change to Stein's algorithm is that we need the Bezout - * coefficients as output, not just the gcd. So we need to know how to - * generate those in each case, based on the coefficients from the - * reduced pair of numbers: - * - * - If a is even, and u,v are such that u*(a/2) + v*b = d: - * + if u is also even, then this is just (u/2)*a + v*b = d - * + otherwise, (u+b)*(a/2) + (v-a/2)*b is also equal to d, and - * since u and b are both odd, (u+b)/2 is an integer, so we have - * ((u+b)/2)*a + (v-a/2)*b = d. - * - * - If a,b are both odd, and u,v are such that u*b + v*(a-b) = d, - * then v*a + (u-v)*b = d. - * - * In the case where we passed from (a,b) to (b,(a-b)/2), we regard it - * as having first subtracted b from a and then halved a, so both of - * these transformations must be done in sequence. - * - * The code below transforms this from a recursive to an iterative - * algorithm. We first reduce a,b to 0,1, recording at each stage - * whether we did the initial subtraction, and whether we had to swap - * the two values; then we iterate backwards over that record of what - * we did, applying the above rules for building up the Bezout - * coefficients as we go. Of course, all the case analysis is done by - * the usual bit-twiddling conditionalisation to avoid data-dependent - * control flow. - * - * Also, since these mp_ints are generally treated as unsigned, we - * store the coefficients by absolute value, with the semantics that - * they always have opposite sign, and in the unwinding loop we keep a - * bit indicating whether Aa-Bb is currently expected to be +d or -d, - * so that we can do one final conditional adjustment if it's -d. - * - * Once the reduction rules have managed to reduce the input numbers - * to (0,d), then they are stable (the next reduction will always - * divide the even one by 2, which maps 0 to 0). So it doesn't matter - * if we do more steps of the algorithm than necessary; hence, for - * constant time, we just need to find the maximum number we could - * _possibly_ require, and do that many. - * - * If a,b < 2^n, at most 2n iterations are required. Proof: consider - * the quantity Q = log_2(a) + log_2(b). Every step halves one of the - * numbers (and may also reduce one of them further by doing a - * subtraction beforehand, but in the worst case, not by much or not - * at all). So Q reduces by at least 1 per iteration, and it starts - * off with a value at most 2n. - * - * The worst case inputs (I think) are where x=2^{n-1} and y=2^n-1 - * (i.e. x is a power of 2 and y is all 1s). In that situation, the - * first n-1 steps repeatedly halve x until it's 1, and then there are - * n further steps each of which subtracts 1 from y and halves it. - */ -static void mp_bezout_into(mp_int *a_coeff_out, mp_int *b_coeff_out, - mp_int *gcd_out, mp_int *a_in, mp_int *b_in) -{ - size_t nw = size_t_max(1, size_t_max(a_in->nw, b_in->nw)); - - /* Make mutable copies of the input numbers */ - mp_int *a = mp_make_sized(nw), *b = mp_make_sized(nw); - mp_copy_into(a, a_in); - mp_copy_into(b, b_in); - - /* Space to build up the output coefficients, with an extra word - * so that intermediate values can overflow off the top and still - * right-shift back down to the correct value */ - mp_int *ac = mp_make_sized(nw + 1), *bc = mp_make_sized(nw + 1); - - /* And a general-purpose temp register */ - mp_int *tmp = mp_make_sized(nw); - - /* Space to record the sequence of reduction steps to unwind. We - * make it a BignumInt for no particular reason except that (a) - * mp_make_sized conveniently zeroes the allocation and mp_free - * wipes it, and (b) this way I can use mp_dump() if I have to - * debug this code. */ - size_t steps = 2 * nw * BIGNUM_INT_BITS; - mp_int *record = mp_make_sized( - (steps*2 + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS); - - for (size_t step = 0; step < steps; step++) { - /* - * If a and b are both odd, we want to sort them so that a is - * larger. But if one is even, we want to sort them so that a - * is the even one. - */ - unsigned swap_if_both_odd = mp_cmp_hs(b, a); - unsigned swap_if_one_even = a->w[0] & 1; - unsigned both_odd = a->w[0] & b->w[0] & 1; - unsigned swap = swap_if_one_even ^ ( - (swap_if_both_odd ^ swap_if_one_even) & both_odd); - - mp_cond_swap(a, b, swap); - - /* - * If a,b are both odd, then a is the larger number, so - * subtract the smaller one from it. - */ - mp_cond_sub_into(a, a, b, both_odd); - - /* - * Now a is even, so divide it by two. - */ - mp_rshift_fixed_into(a, a, 1); - - /* - * Record the two 1-bit values both_odd and swap. - */ - mp_set_bit(record, step*2, both_odd); - mp_set_bit(record, step*2+1, swap); - } - - /* - * Now we expect to have reduced the two numbers to 0 and d, - * although we don't know which way round. (But we avoid checking - * this by assertion; sometimes we'll need to do this computation - * without giving away that we already know the inputs were bogus. - * So we'd prefer to just press on and return nonsense.) - */ - - if (gcd_out) { - /* - * At this point we can return the actual gcd. Since one of - * a,b is it and the other is zero, the easiest way to get it - * is to add them together. - */ - mp_add_into(gcd_out, a, b); - } - - /* - * If the caller _only_ wanted the gcd, and neither Bezout - * coefficient is even required, we can skip the entire unwind - * stage. - */ - if (a_coeff_out || b_coeff_out) { - - /* - * The Bezout coefficients of a,b at this point are simply 0 - * for whichever of a,b is zero, and 1 for whichever is - * nonzero. The nonzero number equals gcd(a,b), which by - * assumption is odd, so we can do this by just taking the low - * bit of each one. - */ - ac->w[0] = mp_get_bit(a, 0); - bc->w[0] = mp_get_bit(b, 0); - - /* - * Overwrite a,b themselves with those same numbers. This has - * the effect of dividing both of them by d, which will - * arrange that during the unwind stage we generate the - * minimal coefficients instead of a larger pair. - */ - mp_copy_into(a, ac); - mp_copy_into(b, bc); - - /* - * We'll maintain the invariant as we unwind that ac * a - bc - * * b is either +d or -d (or rather, +1/-1 after scaling by - * d), and we'll remember which. (We _could_ keep it at +d the - * whole time, but it would cost more work every time round - * the loop, so it's cheaper to fix that up once at the end.) - * - * Initially, the result is +d if a was the nonzero value after - * reduction, and -d if b was. - */ - unsigned minus_d = b->w[0]; - - for (size_t step = steps; step-- > 0 ;) { - /* - * Recover the data from the step we're unwinding. - */ - unsigned both_odd = mp_get_bit(record, step*2); - unsigned swap = mp_get_bit(record, step*2+1); - - /* - * Unwind the division: if our coefficient of a is odd, we - * adjust the coefficients by +b and +a respectively. - */ - unsigned adjust = ac->w[0] & 1; - mp_cond_add_into(ac, ac, b, adjust); - mp_cond_add_into(bc, bc, a, adjust); - - /* - * Now ac is definitely even, so we divide it by two. - */ - mp_rshift_fixed_into(ac, ac, 1); - - /* - * Now unwind the subtraction, if there was one, by adding - * ac to bc. - */ - mp_cond_add_into(bc, bc, ac, both_odd); - - /* - * Undo the transformation of the input numbers, by - * multiplying a by 2 and then adding b to a (the latter - * only if both_odd). - */ - mp_lshift_fixed_into(a, a, 1); - mp_cond_add_into(a, a, b, both_odd); - - /* - * Finally, undo the swap. If we do swap, this also - * reverses the sign of the current result ac*a+bc*b. - */ - mp_cond_swap(a, b, swap); - mp_cond_swap(ac, bc, swap); - minus_d ^= swap; - } - - /* - * Now we expect to have recovered the input a,b (or rather, - * the versions of them divided by d). But we might find that - * our current result is -d instead of +d, that is, we have - * A',B' such that A'a - B'b = -d. - * - * In that situation, we set A = b-A' and B = a-B', giving us - * Aa-Bb = ab - A'a - ab + B'b = +1. - */ - mp_sub_into(tmp, b, ac); - mp_select_into(ac, ac, tmp, minus_d); - mp_sub_into(tmp, a, bc); - mp_select_into(bc, bc, tmp, minus_d); - - /* - * Now we really are done. Return the outputs. - */ - if (a_coeff_out) - mp_copy_into(a_coeff_out, ac); - if (b_coeff_out) - mp_copy_into(b_coeff_out, bc); - - } - - mp_free(a); - mp_free(b); - mp_free(ac); - mp_free(bc); - mp_free(tmp); - mp_free(record); -} - -mp_int *mp_invert(mp_int *x, mp_int *m) -{ - mp_int *result = mp_make_sized(m->nw); - mp_bezout_into(result, NULL, NULL, x, m); - return result; -} - -void mp_gcd_into(mp_int *a, mp_int *b, mp_int *gcd, mp_int *A, mp_int *B) -{ - /* - * Identify shared factors of 2. To do this we OR the two numbers - * to get something whose lowest set bit is in the right place, - * remove all higher bits by ANDing it with its own negation, and - * use mp_get_nbits to find the location of the single remaining - * set bit. - */ - mp_int *tmp = mp_make_sized(size_t_max(a->nw, b->nw)); - for (size_t i = 0; i < tmp->nw; i++) - tmp->w[i] = mp_word(a, i) | mp_word(b, i); - BignumCarry carry = 1; - for (size_t i = 0; i < tmp->nw; i++) { - BignumInt negw; - BignumADC(negw, carry, 0, ~tmp->w[i], carry); - tmp->w[i] &= negw; - } - size_t shift = mp_get_nbits(tmp) - 1; - mp_free(tmp); - - /* - * Make copies of a,b with those shared factors of 2 divided off, - * so that at least one is odd (which is the precondition for - * mp_bezout_into). Compute the gcd of those. - */ - mp_int *as = mp_rshift_safe(a, shift); - mp_int *bs = mp_rshift_safe(b, shift); - mp_bezout_into(A, B, gcd, as, bs); - mp_free(as); - mp_free(bs); - - /* - * And finally shift the gcd back up (unless the caller didn't - * even ask for it), to put the shared factors of 2 back in. - */ - if (gcd) - mp_lshift_safe_in_place(gcd, shift); -} - -mp_int *mp_gcd(mp_int *a, mp_int *b) -{ - mp_int *gcd = mp_make_sized(size_t_min(a->nw, b->nw)); - mp_gcd_into(a, b, gcd, NULL, NULL); - return gcd; -} - -unsigned mp_coprime(mp_int *a, mp_int *b) -{ - mp_int *gcd = mp_gcd(a, b); - unsigned toret = mp_eq_integer(gcd, 1); - mp_free(gcd); - return toret; -} - -static uint32_t recip_approx_32(uint32_t x) -{ - /* - * Given an input x in [2^31,2^32), i.e. a uint32_t with its high - * bit set, this function returns an approximation to 2^63/x, - * computed using only multiplications and bit shifts just in case - * the C divide operator has non-constant time (either because the - * underlying machine instruction does, or because the operator - * expands to a library function on a CPU without hardware - * division). - * - * The coefficients are derived from those of the degree-9 - * polynomial which is the minimax-optimal approximation to that - * function on the given interval (generated using the Remez - * algorithm), converted into integer arithmetic with shifts used - * to maximise the number of significant bits at every state. (A - * sort of 'static floating point' - the exponent is statically - * known at every point in the code, so it never needs to be - * stored at run time or to influence runtime decisions.) - * - * Exhaustive iteration over the whole input space shows the - * largest possible error to be 1686.54. (The input value - * attaining that bound is 4226800006 == 0xfbefd986, whose true - * reciprocal is 2182116973.540... == 0x8210766d.8a6..., whereas - * this function returns 2182115287 == 0x82106fd7.) - */ - uint64_t r = 0x92db03d6ULL; - r = 0xf63e71eaULL - ((r*x) >> 34); - r = 0xb63721e8ULL - ((r*x) >> 34); - r = 0x9c2da00eULL - ((r*x) >> 33); - r = 0xaada0bb8ULL - ((r*x) >> 32); - r = 0xf75cd403ULL - ((r*x) >> 31); - r = 0xecf97a41ULL - ((r*x) >> 31); - r = 0x90d876cdULL - ((r*x) >> 31); - r = 0x6682799a0ULL - ((r*x) >> 26); - return r; -} - -void mp_divmod_into(mp_int *n, mp_int *d, mp_int *q_out, mp_int *r_out) -{ - assert(!mp_eq_integer(d, 0)); - - /* - * We do division by using Newton-Raphson iteration to converge to - * the reciprocal of d (or rather, R/d for R a sufficiently large - * power of 2); then we multiply that reciprocal by n; and we - * finish up with conditional subtraction. - * - * But we have to do it in a fixed number of N-R iterations, so we - * need some error analysis to know how many we might need. - * - * The iteration is derived by defining f(r) = d - R/r. - * Differentiating gives f'(r) = R/r^2, and the Newton-Raphson - * formula applied to those functions gives - * - * r_{i+1} = r_i - f(r_i) / f'(r_i) - * = r_i - (d - R/r_i) r_i^2 / R - * = r_i (2 R - d r_i) / R - * - * Now let e_i be the error in a given iteration, in the sense - * that - * - * d r_i = R + e_i - * i.e. e_i/R = (r_i - r_true) / r_true - * - * so e_i is the _relative_ error in r_i. - * - * We must also introduce a rounding-error term, because the - * division by R always gives an integer. This might make the - * output off by up to 1 (in the negative direction, because - * right-shifting gives floor of the true quotient). So when we - * divide by R, we must imagine adding some f in [0,1). Then we - * have - * - * d r_{i+1} = d r_i (2 R - d r_i) / R - d f - * = (R + e_i) (R - e_i) / R - d f - * = (R^2 - e_i^2) / R - d f - * = R - (e_i^2 / R + d f) - * => e_{i+1} = - (e_i^2 / R + d f) - * - * The sum of two positive quantities is bounded above by twice - * their max, and max |f| = 1, so we can bound this as follows: - * - * |e_{i+1}| <= 2 max (e_i^2/R, d) - * |e_{i+1}/R| <= 2 max ((e_i/R)^2, d/R) - * log2 |R/e_{i+1}| <= min (2 log2 |R/e_i|, log2 |R/d|) - 1 - * - * which tells us that the number of 'good' bits - i.e. - * log2(R/e_i) - very nearly doubles at every iteration (apart - * from that subtraction of 1), until it gets to the same size as - * log2(R/d). In other words, the size of R in bits has to be the - * size of denominator we're putting in, _plus_ the amount of - * precision we want to get back out. - * - * So when we multiply n (the input numerator) by our final - * reciprocal approximation r, but actually r differs from R/d by - * up to 2, then it follows that - * - * n/d - nr/R = n/d - [ n (R/d + e) ] / R - * = n/d - [ (n/d) R + n e ] / R - * = -ne/R - * => 0 <= n/d - nr/R < 2n/R - * - * so our computed quotient can differ from the true n/d by up to - * 2n/R. Hence, as long as we also choose R large enough that 2n/R - * is bounded above by a constant, we can guarantee a bounded - * number of final conditional-subtraction steps. - */ - - /* - * Get at least 32 of the most significant bits of the input - * number. - */ - size_t hiword_index = 0; - uint64_t hibits = 0, lobits = 0; - mp_find_highest_nonzero_word_pair(d, 64 - BIGNUM_INT_BITS, - &hiword_index, &hibits, &lobits); - - /* - * Make a shifted combination of those two words which puts the - * topmost bit of the number at bit 63. - */ - size_t shift_up = 0; - for (size_t i = BIGNUM_INT_BITS_BITS; i-- > 0;) { - size_t sl = (size_t)1 << i; /* left shift count */ - size_t sr = 64 - sl; /* complementary right-shift count */ - - /* Should we shift up? */ - unsigned indicator = 1 ^ normalise_to_1_u64(hibits >> sr); - - /* If we do, what will we get? */ - uint64_t new_hibits = (hibits << sl) | (lobits >> sr); - uint64_t new_lobits = lobits << sl; - size_t new_shift_up = shift_up + sl; - - /* Conditionally swap those values in. */ - hibits ^= (hibits ^ new_hibits ) & -(uint64_t)indicator; - lobits ^= (lobits ^ new_lobits ) & -(uint64_t)indicator; - shift_up ^= (shift_up ^ new_shift_up ) & -(size_t) indicator; - } - - /* - * So now we know the most significant 32 bits of d are at the top - * of hibits. Approximate the reciprocal of those bits. - */ - lobits = (uint64_t)recip_approx_32(hibits >> 32) << 32; - hibits = 0; - - /* - * And shift that up by as many bits as the input was shifted up - * just now, so that the product of this approximation and the - * actual input will be close to a fixed power of two regardless - * of where the MSB was. - * - * I do this in another log n individual passes, partly in case - * the CPU's register-controlled shift operation isn't - * time-constant, and also in case the compiler code-generates - * uint64_t shifts out of a variable number of smaller-word shift - * instructions, e.g. by splitting up into cases. - */ - for (size_t i = BIGNUM_INT_BITS_BITS; i-- > 0;) { - size_t sl = (size_t)1 << i; /* left shift count */ - size_t sr = 64 - sl; /* complementary right-shift count */ - - /* Should we shift up? */ - unsigned indicator = 1 & (shift_up >> i); - - /* If we do, what will we get? */ - uint64_t new_hibits = (hibits << sl) | (lobits >> sr); - uint64_t new_lobits = lobits << sl; - - /* Conditionally swap those values in. */ - hibits ^= (hibits ^ new_hibits ) & -(uint64_t)indicator; - lobits ^= (lobits ^ new_lobits ) & -(uint64_t)indicator; - } - - /* - * The product of the 128-bit value now in hibits:lobits with the - * 128-bit value we originally retrieved in the same variables - * will be in the vicinity of 2^191. So we'll take log2(R) to be - * 191, plus a multiple of BIGNUM_INT_BITS large enough to allow R - * to hold the combined sizes of n and d. - */ - size_t log2_R; - { - size_t max_log2_n = (n->nw + d->nw) * BIGNUM_INT_BITS; - log2_R = max_log2_n + 3; - log2_R -= size_t_min(191, log2_R); - log2_R = (log2_R + BIGNUM_INT_BITS - 1) & ~(BIGNUM_INT_BITS - 1); - log2_R += 191; - } - - /* Number of words in a bignum capable of holding numbers the size - * of twice R. */ - size_t rw = ((log2_R+2) + BIGNUM_INT_BITS - 1) / BIGNUM_INT_BITS; - - /* - * Now construct our full-sized starting reciprocal approximation. - */ - mp_int *r_approx = mp_make_sized(rw); - size_t output_bit_index; - { - /* Where in the input number did the input 128-bit value come from? */ - size_t input_bit_index = - (hiword_index * BIGNUM_INT_BITS) - (128 - BIGNUM_INT_BITS); - - /* So how far do we need to shift our 64-bit output, if the - * product of those two fixed-size values is 2^191 and we want - * to make it 2^log2_R instead? */ - output_bit_index = log2_R - 191 - input_bit_index; - - /* If we've done all that right, it should be a whole number - * of words. */ - assert(output_bit_index % BIGNUM_INT_BITS == 0); - size_t output_word_index = output_bit_index / BIGNUM_INT_BITS; - - mp_add_integer_into_shifted_by_words( - r_approx, r_approx, lobits, output_word_index); - mp_add_integer_into_shifted_by_words( - r_approx, r_approx, hibits, - output_word_index + 64 / BIGNUM_INT_BITS); - } - - /* - * Make the constant 2*R, which we'll need in the iteration. - */ - mp_int *two_R = mp_make_sized(rw); - BignumInt top_word = (BignumInt)1 << ((log2_R+1) % BIGNUM_INT_BITS); - mp_add_integer_into_shifted_by_words( - two_R, two_R, top_word, (log2_R+1) / BIGNUM_INT_BITS); - - /* - * Scratch space. - */ - mp_int *dr = mp_make_sized(rw + d->nw); - mp_int *diff = mp_make_sized(size_t_max(rw, dr->nw)); - mp_int *product = mp_make_sized(rw + diff->nw); - size_t scratchsize = size_t_max( - mp_mul_scratchspace(dr->nw, r_approx->nw, d->nw), - mp_mul_scratchspace(product->nw, r_approx->nw, diff->nw)); - mp_int *scratch = mp_make_sized(scratchsize); - mp_int product_shifted = mp_make_alias( - product, log2_R / BIGNUM_INT_BITS, product->nw); - - /* - * Initial error estimate: the 32-bit output of recip_approx_32 - * differs by less than 2048 (== 2^11) from the true top 32 bits - * of the reciprocal, so the relative error is at most 2^11 - * divided by the 32-bit reciprocal, which at worst is 2^11/2^31 = - * 2^-20. So even in the worst case, we have 20 good bits of - * reciprocal to start with. - */ - size_t good_bits = 31 - 11; - size_t good_bits_needed = BIGNUM_INT_BITS * n->nw + 4; /* add a few */ - - /* - * Now do Newton-Raphson iterations until we have reason to think - * they're not converging any more. - */ - while (good_bits < good_bits_needed) { - /* - * Compute the next iterate. - */ - mp_mul_internal(dr, r_approx, d, *scratch); - mp_sub_into(diff, two_R, dr); - mp_mul_internal(product, r_approx, diff, *scratch); - mp_rshift_fixed_into(r_approx, &product_shifted, - log2_R % BIGNUM_INT_BITS); - - /* - * Adjust the error estimate. - */ - good_bits = good_bits * 2 - 1; - } - - mp_free(dr); - mp_free(diff); - mp_free(product); - mp_free(scratch); - - /* - * Now we've got our reciprocal, we can compute the quotient, by - * multiplying in n and then shifting down by log2_R bits. - */ - mp_int *quotient_full = mp_mul(r_approx, n); - mp_int quotient_alias = mp_make_alias( - quotient_full, log2_R / BIGNUM_INT_BITS, quotient_full->nw); - mp_int *quotient = mp_make_sized(n->nw); - mp_rshift_fixed_into(quotient, "ient_alias, log2_R % BIGNUM_INT_BITS); - - /* - * Next, compute the remainder. - */ - mp_int *remainder = mp_make_sized(d->nw); - mp_mul_into(remainder, quotient, d); - mp_sub_into(remainder, n, remainder); - - /* - * Finally, two conditional subtractions to fix up any remaining - * rounding error. (I _think_ one should be enough, but this - * routine isn't time-critical enough to take chances.) - */ - unsigned q_correction = 0; - for (unsigned iter = 0; iter < 2; iter++) { - unsigned need_correction = mp_cmp_hs(remainder, d); - mp_cond_sub_into(remainder, remainder, d, need_correction); - q_correction += need_correction; - } - mp_add_integer_into(quotient, quotient, q_correction); - - /* - * Now we should have a perfect answer, i.e. 0 <= r < d. - */ - assert(!mp_cmp_hs(remainder, d)); - - if (q_out) - mp_copy_into(q_out, quotient); - if (r_out) - mp_copy_into(r_out, remainder); - - mp_free(r_approx); - mp_free(two_R); - mp_free(quotient_full); - mp_free(quotient); - mp_free(remainder); -} - -mp_int *mp_div(mp_int *n, mp_int *d) -{ - mp_int *q = mp_make_sized(n->nw); - mp_divmod_into(n, d, q, NULL); - return q; -} - -mp_int *mp_mod(mp_int *n, mp_int *d) -{ - mp_int *r = mp_make_sized(d->nw); - mp_divmod_into(n, d, NULL, r); - return r; -} - -uint32_t mp_mod_known_integer(mp_int *x, uint32_t m) -{ - uint64_t reciprocal = ((uint64_t)1 << 48) / m; - uint64_t accumulator = 0; - for (size_t i = mp_max_bytes(x); i-- > 0 ;) { - accumulator = 0x100 * accumulator + mp_get_byte(x, i); - /* - * Let A be the value in 'accumulator' at this point, and let - * R be the value it will have after we subtract quot*m below. - * - * Lemma 1: if A < 2^48, then R < 2m. - * - * Proof: - * - * By construction, we have 2^48/m - 1 < reciprocal <= 2^48/m. - * Multiplying that by the accumulator gives - * - * A/m * 2^48 - A < unshifted_quot <= A/m * 2^48 - * i.e. 0 <= (A/m * 2^48) - unshifted_quot < A - * i.e. 0 <= A/m - unshifted_quot/2^48 < A/2^48 - * - * So when we shift this quotient right by 48 bits, i.e. take - * the floor of (unshifted_quot/2^48), the value we take the - * floor of is at most A/2^48 less than the true rational - * value A/m that we _wanted_ to take the floor of. - * - * Provided A < 2^48, this is less than 1. So the quotient - * 'quot' that we've just produced is either the true quotient - * floor(A/m), or one less than it. Hence, the output value R - * is less than 2m. [] - * - * Lemma 2: if A < 2^16 m, then the multiplication of - * accumulator*reciprocal does not overflow. - * - * Proof: as above, we have reciprocal <= 2^48/m. Multiplying - * by A gives unshifted_quot <= 2^48 * A / m < 2^48 * 2^16 = - * 2^64. [] - */ - uint64_t unshifted_quot = accumulator * reciprocal; - uint64_t quot = unshifted_quot >> 48; - accumulator -= quot * m; - } - - /* - * Theorem 1: accumulator < 2m at the end of every iteration of - * this loop. - * - * Proof: induction on the above loop. - * - * Base case: at the start of the first loop iteration, the - * accumulator is 0, which is certainly < 2m. - * - * Inductive step: in each loop iteration, we take a value at most - * 2m-1, multiply it by 2^8, and add another byte less than 2^8 to - * generate the input value A to the reduction process above. So - * we have A < 2m * 2^8 - 1. We know m < 2^32 (because it was - * passed in as a uint32_t), so A < 2^41, which is enough to allow - * us to apply Lemma 1, showing that the value of 'accumulator' at - * the end of the loop is still < 2m. [] - * - * Corollary: we need at most one final subtraction of m to - * produce the canonical residue of x mod m, i.e. in the range - * [0,m). - * - * Theorem 2: no multiplication in the inner loop overflows. - * - * Proof: in Theorem 1 we established A < 2m * 2^8 - 1 in every - * iteration. That is less than m * 2^16, so Lemma 2 applies. - * - * The other multiplication, of quot * m, cannot overflow because - * quot is at most A/m, so quot*m <= A < 2^64. [] - */ - - uint32_t result = accumulator; - uint32_t reduced = result - m; - uint32_t select = -(reduced >> 31); - result = reduced ^ ((result ^ reduced) & select); - assert(result < m); - return result; -} - -mp_int *mp_nthroot(mp_int *y, unsigned n, mp_int *remainder_out) -{ - /* - * Allocate scratch space. - */ - mp_int **alloc, **powers, **newpowers, *scratch; - size_t nalloc = 2*(n+1)+1; - alloc = snewn(nalloc, mp_int *); - for (size_t i = 0; i < nalloc; i++) - alloc[i] = mp_make_sized(y->nw + 1); - powers = alloc; - newpowers = alloc + (n+1); - scratch = alloc[2*n+2]; - - /* - * We're computing the rounded-down nth root of y, i.e. the - * maximal x such that x^n <= y. We try to add 2^i to it for each - * possible value of i, starting from the largest one that might - * fit (i.e. such that 2^{n*i} fits in the size of y) downwards to - * i=0. - * - * We track all the smaller powers of x in the array 'powers'. In - * each iteration, if we update x, we update all of those values - * to match. - */ - mp_copy_integer_into(powers[0], 1); - for (size_t s = mp_max_bits(y) / n + 1; s-- > 0 ;) { - /* - * Let b = 2^s. We need to compute the powers (x+b)^i for each - * i, starting from our recorded values of x^i. - */ - for (size_t i = 0; i < n+1; i++) { - /* - * (x+b)^i = x^i - * + (i choose 1) x^{i-1} b - * + (i choose 2) x^{i-2} b^2 - * + ... - * + b^i - */ - uint16_t binom = 1; /* coefficient of b^i */ - mp_copy_into(newpowers[i], powers[i]); - for (size_t j = 0; j < i; j++) { - /* newpowers[i] += binom * powers[j] * 2^{(i-j)*s} */ - mp_mul_integer_into(scratch, powers[j], binom); - mp_lshift_fixed_into(scratch, scratch, (i-j) * s); - mp_add_into(newpowers[i], newpowers[i], scratch); - - uint32_t binom_mul = binom; - binom_mul *= (i-j); - binom_mul /= (j+1); - assert(binom_mul < 0x10000); - binom = binom_mul; - } - } - - /* - * Now, is the new value of x^n still <= y? If so, update. - */ - unsigned newbit = mp_cmp_hs(y, newpowers[n]); - for (size_t i = 0; i < n+1; i++) - mp_select_into(powers[i], powers[i], newpowers[i], newbit); - } - - if (remainder_out) - mp_sub_into(remainder_out, y, powers[n]); - - mp_int *root = mp_new(mp_max_bits(y) / n); - mp_copy_into(root, powers[1]); - - for (size_t i = 0; i < nalloc; i++) - mp_free(alloc[i]); - sfree(alloc); - - return root; -} - -mp_int *mp_modmul(mp_int *x, mp_int *y, mp_int *modulus) -{ - mp_int *product = mp_mul(x, y); - mp_int *reduced = mp_mod(product, modulus); - mp_free(product); - return reduced; -} - -mp_int *mp_modadd(mp_int *x, mp_int *y, mp_int *modulus) -{ - mp_int *sum = mp_add(x, y); - mp_int *reduced = mp_mod(sum, modulus); - mp_free(sum); - return reduced; -} - -mp_int *mp_modsub(mp_int *x, mp_int *y, mp_int *modulus) -{ - mp_int *diff = mp_make_sized(size_t_max(x->nw, y->nw)); - mp_sub_into(diff, x, y); - unsigned negate = mp_cmp_hs(y, x); - mp_cond_negate(diff, diff, negate); - mp_int *residue = mp_mod(diff, modulus); - mp_cond_negate(residue, residue, negate); - /* If we've just negated the residue, then it will be < 0 and need - * the modulus adding to it to make it positive - *except* if the - * residue was zero when we negated it. */ - unsigned make_positive = negate & ~mp_eq_integer(residue, 0); - mp_cond_add_into(residue, residue, modulus, make_positive); - mp_free(diff); - return residue; -} - -static mp_int *mp_modadd_in_range(mp_int *x, mp_int *y, mp_int *modulus) -{ - mp_int *sum = mp_make_sized(modulus->nw); - unsigned carry = mp_add_into_internal(sum, x, y); - mp_cond_sub_into(sum, sum, modulus, carry | mp_cmp_hs(sum, modulus)); - return sum; -} - -static mp_int *mp_modsub_in_range(mp_int *x, mp_int *y, mp_int *modulus) -{ - mp_int *diff = mp_make_sized(modulus->nw); - mp_sub_into(diff, x, y); - mp_cond_add_into(diff, diff, modulus, 1 ^ mp_cmp_hs(x, y)); - return diff; -} - -mp_int *monty_add(MontyContext *mc, mp_int *x, mp_int *y) -{ - return mp_modadd_in_range(x, y, mc->m); -} - -mp_int *monty_sub(MontyContext *mc, mp_int *x, mp_int *y) -{ - return mp_modsub_in_range(x, y, mc->m); -} - -void mp_min_into(mp_int *r, mp_int *x, mp_int *y) -{ - mp_select_into(r, x, y, mp_cmp_hs(x, y)); -} - -void mp_max_into(mp_int *r, mp_int *x, mp_int *y) -{ - mp_select_into(r, y, x, mp_cmp_hs(x, y)); -} - -mp_int *mp_min(mp_int *x, mp_int *y) -{ - mp_int *r = mp_make_sized(size_t_min(x->nw, y->nw)); - mp_min_into(r, x, y); - return r; -} - -mp_int *mp_max(mp_int *x, mp_int *y) -{ - mp_int *r = mp_make_sized(size_t_max(x->nw, y->nw)); - mp_max_into(r, x, y); - return r; -} - -mp_int *mp_power_2(size_t power) -{ - mp_int *x = mp_new(power + 1); - mp_set_bit(x, power, 1); - return x; -} - -struct ModsqrtContext { - mp_int *p; /* the prime */ - MontyContext *mc; /* for doing arithmetic mod p */ - - /* Decompose p-1 as 2^e k, for positive integer e and odd k */ - size_t e; - mp_int *k; - mp_int *km1o2; /* (k-1)/2 */ - - /* The user-provided value z which is not a quadratic residue mod - * p, and its kth power. Both in Montgomery form. */ - mp_int *z, *zk; -}; - -ModsqrtContext *modsqrt_new(mp_int *p, mp_int *any_nonsquare_mod_p) -{ - ModsqrtContext *sc = snew(ModsqrtContext); - memset(sc, 0, sizeof(ModsqrtContext)); - - sc->p = mp_copy(p); - sc->mc = monty_new(sc->p); - sc->z = monty_import(sc->mc, any_nonsquare_mod_p); - - /* Find the lowest set bit in p-1. Since this routine expects p to - * be non-secret (typically a well-known standard elliptic curve - * parameter), for once we don't need clever bit tricks. */ - for (sc->e = 1; sc->e < BIGNUM_INT_BITS * p->nw; sc->e++) - if (mp_get_bit(p, sc->e)) - break; - - sc->k = mp_rshift_fixed(p, sc->e); - sc->km1o2 = mp_rshift_fixed(sc->k, 1); - - /* Leave zk to be filled in lazily, since it's more expensive to - * compute. If this context turns out never to be needed, we can - * save the bulk of the setup time this way. */ - - return sc; -} - -static void modsqrt_lazy_setup(ModsqrtContext *sc) -{ - if (!sc->zk) - sc->zk = monty_pow(sc->mc, sc->z, sc->k); -} - -void modsqrt_free(ModsqrtContext *sc) -{ - monty_free(sc->mc); - mp_free(sc->p); - mp_free(sc->z); - mp_free(sc->k); - mp_free(sc->km1o2); - - if (sc->zk) - mp_free(sc->zk); - - sfree(sc); -} - -mp_int *mp_modsqrt(ModsqrtContext *sc, mp_int *x, unsigned *success) -{ - mp_int *mx = monty_import(sc->mc, x); - mp_int *mroot = monty_modsqrt(sc, mx, success); - mp_free(mx); - mp_int *root = monty_export(sc->mc, mroot); - mp_free(mroot); - return root; -} - -/* - * Modular square root, using an algorithm more or less similar to - * Tonelli-Shanks but adapted for constant time. - * - * The basic idea is to write p-1 = k 2^e, where k is odd and e > 0. - * Then the multiplicative group mod p (call it G) has a sequence of - * e+1 nested subgroups G = G_0 > G_1 > G_2 > ... > G_e, where each - * G_i is exactly half the size of G_{i-1} and consists of all the - * squares of elements in G_{i-1}. So the innermost group G_e has - * order k, which is odd, and hence within that group you can take a - * square root by raising to the power (k+1)/2. - * - * Our strategy is to iterate over these groups one by one and make - * sure the number x we're trying to take the square root of is inside - * each one, by adjusting it if it isn't. - * - * Suppose g is a primitive root of p, i.e. a generator of G_0. (We - * don't actually need to know what g _is_; we just imagine it for the - * sake of understanding.) Then G_i consists of precisely the (2^i)th - * powers of g, and hence, you can tell if a number is in G_i if - * raising it to the power k 2^{e-i} gives 1. So the conceptual - * algorithm goes: for each i, test whether x is in G_i by that - * method. If it isn't, then the previous iteration ensured it's in - * G_{i-1}, so it will be an odd power of g^{2^{i-1}}, and hence - * multiplying by any other odd power of g^{2^{i-1}} will give x' in - * G_i. And we have one of those, because our non-square z is an odd - * power of g, so z^{2^{i-1}} is an odd power of g^{2^{i-1}}. - * - * (There's a special case in the very first iteration, where we don't - * have a G_{i-1}. If it turns out that x is not even in G_1, that - * means it's not a square, so we set *success to 0. We still run the - * rest of the algorithm anyway, for the sake of constant time, but we - * don't give a hoot what it returns.) - * - * When we get to the end and have x in G_e, then we can take its - * square root by raising to (k+1)/2. But of course that's not the - * square root of the original input - it's only the square root of - * the adjusted version we produced during the algorithm. To get the - * true output answer we also have to multiply by a power of z, - * namely, z to the power of _half_ whatever we've been multiplying in - * as we go along. (The power of z we multiplied in must have been - * even, because the case in which we would have multiplied in an odd - * power of z is the i=0 case, in which we instead set the failure - * flag.) - * - * The code below is an optimised version of that basic idea, in which - * we _start_ by computing x^k so as to be able to test membership in - * G_i by only a few squarings rather than a full from-scratch modpow - * every time; we also start by computing our candidate output value - * x^{(k+1)/2}. So when the above description says 'adjust x by z^i' - * for some i, we have to adjust our running values of x^k and - * x^{(k+1)/2} by z^{ik} and z^{ik/2} respectively (the latter is safe - * because, as above, i is always even). And it turns out that we - * don't actually have to store the adjusted version of x itself at - * all - we _only_ keep those two powers of it. - */ -mp_int *monty_modsqrt(ModsqrtContext *sc, mp_int *x, unsigned *success) -{ - modsqrt_lazy_setup(sc); - - mp_int *scratch_to_free = mp_make_sized(3 * sc->mc->rw); - mp_int scratch = *scratch_to_free; - - /* - * Compute toret = x^{(k+1)/2}, our starting point for the output - * square root, and also xk = x^k which we'll use as we go along - * for knowing when to apply correction factors. We do this by - * first computing x^{(k-1)/2}, then multiplying it by x, then - * multiplying the two together. - */ - mp_int *toret = monty_pow(sc->mc, x, sc->km1o2); - mp_int xk = mp_alloc_from_scratch(&scratch, sc->mc->rw); - mp_copy_into(&xk, toret); - monty_mul_into(sc->mc, toret, toret, x); - monty_mul_into(sc->mc, &xk, toret, &xk); - - mp_int tmp = mp_alloc_from_scratch(&scratch, sc->mc->rw); - - mp_int power_of_zk = mp_alloc_from_scratch(&scratch, sc->mc->rw); - mp_copy_into(&power_of_zk, sc->zk); - - for (size_t i = 0; i < sc->e; i++) { - mp_copy_into(&tmp, &xk); - for (size_t j = i+1; j < sc->e; j++) - monty_mul_into(sc->mc, &tmp, &tmp, &tmp); - unsigned eq1 = mp_cmp_eq(&tmp, monty_identity(sc->mc)); - - if (i == 0) { - /* One special case: if x=0, then no power of x will ever - * equal 1, but we should still report success on the - * grounds that 0 does have a square root mod p. */ - *success = eq1 | mp_eq_integer(x, 0); - } else { - monty_mul_into(sc->mc, &tmp, toret, &power_of_zk); - mp_select_into(toret, &tmp, toret, eq1); - - monty_mul_into(sc->mc, &power_of_zk, - &power_of_zk, &power_of_zk); - - monty_mul_into(sc->mc, &tmp, &xk, &power_of_zk); - mp_select_into(&xk, &tmp, &xk, eq1); - } - } - - mp_free(scratch_to_free); - - return toret; -} - -mp_int *mp_random_bits_fn(size_t bits, random_read_fn_t random_read) -{ - size_t bytes = (bits + 7) / 8; - uint8_t *randbuf = snewn(bytes, uint8_t); - random_read(randbuf, bytes); - if (bytes) - randbuf[0] &= (2 << ((bits-1) & 7)) - 1; - mp_int *toret = mp_from_bytes_be(make_ptrlen(randbuf, bytes)); - smemclr(randbuf, bytes); - sfree(randbuf); - return toret; -} - -mp_int *mp_random_upto_fn(mp_int *limit, random_read_fn_t rf) -{ - /* - * It would be nice to generate our random numbers in such a way - * as to make every possible outcome literally equiprobable. But - * we can't do that in constant time, so we have to go for a very - * close approximation instead. I'm going to take the view that a - * factor of (1+2^-128) between the probabilities of two outcomes - * is acceptable on the grounds that you'd have to examine so many - * outputs to even detect it. - */ - mp_int *unreduced = mp_random_bits_fn(mp_max_bits(limit) + 128, rf); - mp_int *reduced = mp_mod(unreduced, limit); - mp_free(unreduced); - return reduced; -} - -mp_int *mp_random_in_range_fn(mp_int *lo, mp_int *hi, random_read_fn_t rf) -{ - mp_int *n_outcomes = mp_sub(hi, lo); - mp_int *addend = mp_random_upto_fn(n_outcomes, rf); - mp_int *result = mp_make_sized(hi->nw); - mp_add_into(result, addend, lo); - mp_free(addend); - mp_free(n_outcomes); - return result; -} diff --git a/crypto/mpint_i.h b/crypto/mpint_i.h deleted file mode 100644 index fb2b367cc..000000000 --- a/crypto/mpint_i.h +++ /dev/null @@ -1,324 +0,0 @@ -/* - * mpint_i.h: definitions used internally by the bignum code, and - * also a few other vaguely-bignum-like places. - */ - -/* ---------------------------------------------------------------------- - * 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 code 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). - * - * - five constant macros: - * + BIGNUM_INT_BITS, the number of bits in BignumInt, - * + BIGNUM_INT_BYTES, the number of bytes that works out to - * + BIGNUM_TOP_BIT, the BignumInt value consisting of only the top bit - * + BIGNUM_INT_MASK, the BignumInt value with all bits set - * + BIGNUM_INT_BITS_BITS, log to the base 2 of BIGNUM_INT_BITS. - * - * - 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_BITS. The other 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. - */ - -/* You can lower the BignumInt size by defining BIGNUM_OVERRIDE on the - * command line to be your chosen max value of BIGNUM_INT_BITS_BITS */ -#if defined BIGNUM_OVERRIDE -#define BB_OK(b) ((b) <= BIGNUM_OVERRIDE) -#else -#define BB_OK(b) (1) -#endif - -#if defined __SIZEOF_INT128__ && BB_OK(6) - - /* - * 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_BITS 6 - #define DEFINE_BIGNUMDBLINT typedef __uint128_t BignumDblInt - -#elif defined _MSC_VER && defined _M_AMD64 && BB_OK(6) - - /* - * 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_BITS 6 - #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) && BB_OK(5) - - /* 32-bit BignumInt, using C99 unsigned long long as BignumDblInt */ - - typedef unsigned int BignumInt; - #define BIGNUM_INT_BITS_BITS 5 - #define DEFINE_BIGNUMDBLINT typedef unsigned long long BignumDblInt - -#elif defined _MSC_VER && BB_OK(5) - - /* 32-bit BignumInt, using Visual Studio __int64 as BignumDblInt */ - - typedef unsigned int BignumInt; - #define BIGNUM_INT_BITS_BITS 5 - #define DEFINE_BIGNUMDBLINT typedef unsigned __int64 BignumDblInt - -#elif defined _LP64 && BB_OK(5) - - /* - * 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_BITS 5 - #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt - -#elif BB_OK(4) - - /* - * 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_BITS 4 - #define DEFINE_BIGNUMDBLINT typedef unsigned long BignumDblInt - -#else - - /* Should only get here if BB_OK(4) evaluated false, i.e. the - * command line defined BIGNUM_OVERRIDE to an absurdly small - * value. */ - #error Must define BIGNUM_OVERRIDE to at least 4 - -#endif - -#undef BB_OK - -/* - * Common code across all branches of that ifdef: define all the - * easy constant macros in terms of BIGNUM_INT_BITS_BITS. - */ -#define BIGNUM_INT_BITS (1 << BIGNUM_INT_BITS_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)) - -/* - * Just occasionally, we might need a GET_nnBIT_xSB_FIRST macro to - * operate on whatever BignumInt is. - */ -#if BIGNUM_INT_BITS_BITS == 4 -#define GET_BIGNUMINT_MSB_FIRST GET_16BIT_MSB_FIRST -#define GET_BIGNUMINT_LSB_FIRST GET_16BIT_LSB_FIRST -#define PUT_BIGNUMINT_MSB_FIRST PUT_16BIT_MSB_FIRST -#define PUT_BIGNUMINT_LSB_FIRST PUT_16BIT_LSB_FIRST -#elif BIGNUM_INT_BITS_BITS == 5 -#define GET_BIGNUMINT_MSB_FIRST GET_32BIT_MSB_FIRST -#define GET_BIGNUMINT_LSB_FIRST GET_32BIT_LSB_FIRST -#define PUT_BIGNUMINT_MSB_FIRST PUT_32BIT_MSB_FIRST -#define PUT_BIGNUMINT_LSB_FIRST PUT_32BIT_LSB_FIRST -#elif BIGNUM_INT_BITS_BITS == 6 -#define GET_BIGNUMINT_MSB_FIRST GET_64BIT_MSB_FIRST -#define GET_BIGNUMINT_LSB_FIRST GET_64BIT_LSB_FIRST -#define PUT_BIGNUMINT_MSB_FIRST PUT_64BIT_MSB_FIRST -#define PUT_BIGNUMINT_LSB_FIRST PUT_64BIT_LSB_FIRST -#else - #error Ran out of options for GET_BIGNUMINT_xSB_FIRST -#endif - -/* - * 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 */ - -/* ---------------------------------------------------------------------- - * Data structures used inside mpint.c. - */ - -struct mp_int { - size_t nw; - BignumInt *w; -}; - -struct MontyContext { - /* - * The actual modulus. - */ - mp_int *m; - - /* - * Montgomery multiplication works by selecting a value r > m, - * coprime to m, which is really easy to divide by. In binary - * arithmetic, that means making it a power of 2; in fact we make - * it a whole number of BignumInt. - * - * We don't store r directly as an mp_int (there's no need). But - * its value is 2^rbits; we also store rw = rbits/BIGNUM_INT_BITS - * (the corresponding word offset within an mp_int). - * - * pw is the number of words needed to store an mp_int you're - * doing reduction on: it has to be big enough to hold the sum of - * an input value up to m^2 plus an extra addend up to m*r. - */ - size_t rbits, rw, pw; - - /* - * The key step in Montgomery reduction requires the inverse of -m - * mod r. - */ - mp_int *minus_minv_mod_r; - - /* - * r^1, r^2 and r^3 mod m, which are used for various purposes. - * - * (Annoyingly, this is one of the rare cases where it would have - * been nicer to have a Pascal-style 1-indexed array. I couldn't - * _quite_ bring myself to put a gratuitous zero element in here. - * So you just have to live with getting r^k by taking the [k-1]th - * element of this array.) - */ - mp_int *powers_of_r_mod_m[3]; - - /* - * Persistent scratch space from which monty_* functions can - * allocate storage for intermediate values. - */ - mp_int *scratch; -}; - -/* Functions shared between mpint.c and mpunsafe.c */ -mp_int *mp_make_sized(size_t nw); diff --git a/crypto/ntru.c b/crypto/ntru.c deleted file mode 100644 index 6a02d80d9..000000000 --- a/crypto/ntru.c +++ /dev/null @@ -1,1883 +0,0 @@ -/* - * Implementation of OpenSSH 9.x's hybrid key exchange protocol - * sntrup761x25519-sha512@openssh.com . - * - * This consists of the 'Streamlined NTRU Prime' quantum-resistant - * cryptosystem, run in parallel with ordinary Curve25519 to generate - * a shared secret combining the output of both systems. - * - * (Hence, even if you don't trust this newfangled NTRU Prime thing at - * all, it's at least no _less_ secure than the kex you were using - * already.) - * - * References for the NTRU Prime cryptosystem, up to and including - * binary encodings of public and private keys and the exact preimages - * of the hashes used in key exchange: - * - * https://ntruprime.cr.yp.to/ - * https://ntruprime.cr.yp.to/nist/ntruprime-20201007.pdf - * - * The SSH protocol layer is not documented anywhere I could find (as - * of 2022-04-15, not even in OpenSSH's PROTOCOL.* files). I had to - * read OpenSSH's source code to find out how it worked, and the - * answer is as follows: - * - * This hybrid kex method is treated for SSH purposes as a form of - * elliptic-curve Diffie-Hellman, and shares the same SSH message - * sequence: client sends SSH2_MSG_KEX_ECDH_INIT containing its public - * half, server responds with SSH2_MSG_KEX_ECDH_REPLY containing _its_ - * public half plus the host key and signature on the shared secret. - * - * (This is a bit of a fudge, because unlike actual ECDH, this kex - * method is asymmetric: one side sends a public key, and the other - * side encrypts something with it and sends the ciphertext back. So - * while the normal ECDH implementations can compute the two sides - * independently in parallel, this system reusing the same messages - * has to be serial. But the order of the messages _is_ firmly - * specified in SSH ECDH, so it works anyway.) - * - * For this kex method, SSH2_MSG_KEX_ECDH_INIT still contains a single - * SSH 'string', which consists of the concatenation of a Streamlined - * NTRU Prime public key with the Curve25519 public value. (Both of - * these have fixed length in bytes, so there's no ambiguity in the - * concatenation.) - * - * SSH2_MSG_KEX_ECDH_REPLY is mostly the same as usual. The only - * string in the packet that varies is the second one, which would - * normally contain the server's public elliptic curve point. Instead, - * it now contains the concatenation of - * - * - a Streamlined NTRU Prime ciphertext - * - the 'confirmation hash' specified in ntruprime-20201007.pdf, - * hashing the plaintext of that ciphertext together with the - * public key - * - the Curve25519 public point as usual. - * - * Again, all three of those elements have fixed lengths. - * - * The client decrypts the ciphertext, checks the confirmation hash, - * and if successful, generates the 'session hash' specified in - * ntruprime-20201007.pdf, which is 32 bytes long and is the ultimate - * output of the Streamlined NTRU Prime key exchange. - * - * The output of the hybrid kex method as a whole is an SSH 'string' - * of length 64 containing the SHA-512 hash of the concatenatio of - * - * - the Streamlined NTRU Prime session hash (32 bytes) - * - the Curve25519 shared secret (32 bytes). - * - * That string is included directly into the SSH exchange hash and key - * derivation hashes, in place of the mpint that comes out of most - * other kex methods. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "mpint.h" -#include "ntru.h" - -/* ---------------------------------------------------------------------- - * Preliminaries: we're going to need to do modular arithmetic on - * small values (considerably smaller than 2^16), and we need to do it - * without using integer division which might not be time-safe. - * - * The strategy for this is the same as I used in - * mp_mod_known_integer: see there for the proofs. The basic idea is - * that we precompute the reciprocal of our modulus as a fixed-point - * number, and use that to get an approximate quotient which we - * subtract off. For these integer sizes, precomputing a fixed-point - * reciprocal of the form (2^48 / modulus) leaves us at most off by 1 - * in the quotient, so there's a single (time-safe) trial subtraction - * at the end. - * - * (It's possible that some speed could be gained by not reducing - * fully at every step. But then you'd have to carefully identify all - * the places in the algorithm where things are compared to zero. This - * was the easiest way to get it all working in the first place.) - */ - -/* Precompute the reciprocal */ -static uint64_t reciprocal_for_reduction(uint16_t q) -{ - return ((uint64_t)1 << 48) / q; -} - -/* Reduce x mod q, assuming qrecip == reciprocal_for_reduction(q) */ -static uint16_t reduce(uint32_t x, uint16_t q, uint64_t qrecip) -{ - uint64_t unshifted_quot = x * qrecip; - uint64_t quot = unshifted_quot >> 48; - uint16_t reduced = x - quot * q; - reduced -= q * (1 & ((q-1 - reduced) >> 15)); - return reduced; -} - -/* Reduce x mod q as above, but also return the quotient */ -static uint16_t reduce_with_quot(uint32_t x, uint32_t *quot_out, - uint16_t q, uint64_t qrecip) -{ - uint64_t unshifted_quot = x * qrecip; - uint64_t quot = unshifted_quot >> 48; - uint16_t reduced = x - quot * q; - uint64_t extraquot = (1 & ((q-1 - reduced) >> 15)); - reduced -= extraquot * q; - *quot_out = quot + extraquot; - return reduced; -} - -/* Invert x mod q, assuming it's nonzero. (For time-safety, no check - * is made for zero; it just returns 0.) */ -static uint16_t invert(uint16_t x, uint16_t q, uint64_t qrecip) -{ - /* Fermat inversion: compute x^(q-2), since x^(q-1) == 1. */ - uint32_t sq = x, bit = 1, acc = 1, exp = q-2; - while (1) { - if (exp & bit) { - acc = reduce(acc * sq, q, qrecip); - exp &= ~bit; - if (!exp) - return acc; - } - sq = reduce(sq * sq, q, qrecip); - bit <<= 1; - } -} - -/* Check whether x == 0, time-safely, and return 1 if it is or 0 otherwise. */ -static unsigned iszero(uint16_t x) -{ - return 1 & ~((x + 0xFFFF) >> 16); -} - -/* - * Handy macros to cut down on all those extra function parameters. In - * the common case where a function is working mod the same modulus - * throughout (and has called it q), you can just write 'SETUP;' at - * the top and then call REDUCE(...) and INVERT(...) without having to - * write out q and qrecip every time. - */ -#define SETUP uint64_t qrecip = reciprocal_for_reduction(q) -#define REDUCE(x) reduce(x, q, qrecip) -#define INVERT(x) invert(x, q, qrecip) - -/* ---------------------------------------------------------------------- - * Quotient-ring functions. - * - * NTRU Prime works with two similar but different quotient rings: - * - * Z_q[x] / where p,q are the prime parameters of the system - * Z_3[x] / with the same p, but coefficients mod 3. - * - * The former is a field (every nonzero element is invertible), - * because the system parameters are chosen such that x^p-x-1 is - * invertible over Z_q. The latter is not a field (or not necessarily, - * and in particular, not for the value of p we use here). - * - * In these core functions, you pass in the modulus you want as the - * parameter q, which is either the 'real' q specified in the system - * parameters, or 3 if you're doing one of the mod-3 parts of the - * algorithm. - */ - -/* - * Multiply two elements of a quotient ring. - * - * 'a' and 'b' are arrays of exactly p coefficients, with constant - * term first. 'out' is an array the same size to write the inverse - * into. - */ -void ntru_ring_multiply(uint16_t *out, const uint16_t *a, const uint16_t *b, - unsigned p, unsigned q) -{ - SETUP; - - /* - * Strategy: just compute the full product with 2p coefficients, - * and then reduce it mod x^p-x-1 by working downwards from the - * top coefficient replacing x^{p+k} with (x+1)x^k for k = ...,1,0. - * - * Possibly some speed could be gained here by doing the recursive - * Karatsuba optimisation for the initial multiplication? But I - * haven't tried it. - */ - uint32_t *unreduced = snewn(2*p, uint32_t); - for (unsigned i = 0; i < 2*p; i++) - unreduced[i] = 0; - for (unsigned i = 0; i < p; i++) - for (unsigned j = 0; j < p; j++) - unreduced[i+j] = REDUCE(unreduced[i+j] + a[i] * b[j]); - - for (unsigned i = 2*p - 1; i >= p; i--) { - unreduced[i-p] += unreduced[i]; - unreduced[i-p+1] += unreduced[i]; - unreduced[i] = 0; - } - - for (unsigned i = 0; i < p; i++) - out[i] = REDUCE(unreduced[i]); - - smemclr(unreduced, 2*p * sizeof(*unreduced)); - sfree(unreduced); -} - -/* - * Invert an element of the quotient ring. - * - * 'in' is an array of exactly p coefficients, with constant term - * first. 'out' is an array the same size to write the inverse into. - * - * Method: essentially Stein's gcd algorithm, taking the gcd of the - * input (regarded as an element of Z_q[x] proper) and x^p-x-1. Given - * two polynomials over a field which are not both divisible by x, you - * can find their gcd by iterating the following procedure: - * - * - if one is divisible by x, divide off x - * - otherwise, subtract from the higher-degree one whatever scalar - * multiple of the lower-degree one will make it divisible by x, - * and _then_ divide off x - * - * Neither of these types of step changes the gcd of the two - * polynomials. - * - * Each step reduces the sum of the two polynomials' degree by at - * least one, as long as at least one of the degrees is positive. - * (Maybe more than one if all the stars align in the second case, if - * the subtraction cancels the leading term as well as the constant - * term.) So in at most deg A + deg B steps, we must have reached the - * situation where both polys are constants; in one more step after - * that, one of them will be zero; and in one step after _that_, the - * zero one will reliably be the one we're dividing by x. Or rather, - * that's what happens in the case where A,B are coprime; if not, then - * one hits zero while the other is still nonzero. - * - * In a normal gcd algorithm, you'd track a linear combination of the - * two original polynomials that yields each working value, and end up - * with a linear combination of the inputs that yields the gcd. In - * this algorithm, the 'divide off x' step makes that awkward - but we - * can solve that by instead multiplying by the inverse of x in the - * ring that we want our answer to be valid in! And since the modulus - * polynomial of the ring is x^p-x-1, the inverse of x is easy to - * calculate, because it's always just x^{p-1} - 1, which is also very - * easy to multiply by. - */ -unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, - unsigned p, unsigned q) -{ - SETUP; - - /* Size of the polynomial arrays we'll work with */ - const size_t SIZE = p+1; - - /* Number of steps of the algorithm is the max possible value of - * deg A + deg B + 2, where deg A <= p-1 and deg B = p */ - const size_t STEPS = 2*p + 1; - - /* Our two working polynomials */ - uint16_t *A = snewn(SIZE, uint16_t); - uint16_t *B = snewn(SIZE, uint16_t); - - /* Coefficient of the input value in each one */ - uint16_t *Ac = snewn(SIZE, uint16_t); - uint16_t *Bc = snewn(SIZE, uint16_t); - - /* Initialise A to the input, and Ac correspondingly to 1 */ - memcpy(A, in, p*sizeof(uint16_t)); - A[p] = 0; - Ac[0] = 1; - for (size_t i = 1; i < SIZE; i++) - Ac[i] = 0; - - /* Initialise B to the quotient polynomial of the ring, x^p-x-1 - * And Bc = 0 */ - B[0] = B[1] = q-1; - for (size_t i = 2; i < p; i++) - B[i] = 0; - B[p] = 1; - for (size_t i = 0; i < SIZE; i++) - Bc[i] = 0; - - /* Run the gcd-finding algorithm. */ - for (size_t i = 0; i < STEPS; i++) { - /* - * First swap round so that A is the one we'll be dividing by x. - * - * In the case where one of the two polys has a zero constant - * term, it's that one. In the other case, it's the one of - * smaller degree. We must compute both, and choose between - * them in a side-channel-safe way. - */ - unsigned x_divides_A = iszero(A[0]); - unsigned x_divides_B = iszero(B[0]); - unsigned B_is_bigger = 0; - { - unsigned not_seen_top_term_of_A = 1, not_seen_top_term_of_B = 1; - for (size_t j = SIZE; j-- > 0 ;) { - not_seen_top_term_of_A &= iszero(A[j]); - not_seen_top_term_of_B &= iszero(B[j]); - B_is_bigger |= (~not_seen_top_term_of_B & - not_seen_top_term_of_A); - } - } - unsigned need_swap = x_divides_B | (~x_divides_A & B_is_bigger); - uint16_t swap_mask = -need_swap; - for (size_t j = 0; j < SIZE; j++) { - uint16_t diff = (A[j] ^ B[j]) & swap_mask; - A[j] ^= diff; - B[j] ^= diff; - } - for (size_t j = 0; j < SIZE; j++) { - uint16_t diff = (Ac[j] ^ Bc[j]) & swap_mask; - Ac[j] ^= diff; - Bc[j] ^= diff; - } - - /* - * Replace A with a linear combination of both A and B that - * has constant term zero, which we do by calculating - * - * (constant term of B) * A - (constant term of A) * B - * - * In one of the two cases, A's constant term is already zero, - * so the coefficient of B will be zero too; hence, this will - * do nothing useful (it will merely scale A by some scalar - * value), but it will take the same length of time as doing - * something, which is just what we want. - */ - uint16_t Amult = B[0], Bmult = q - A[0]; - for (size_t j = 0; j < SIZE; j++) - A[j] = REDUCE(Amult * A[j] + Bmult * B[j]); - /* And do the same transformation to Ac */ - for (size_t j = 0; j < SIZE; j++) - Ac[j] = REDUCE(Amult * Ac[j] + Bmult * Bc[j]); - - /* - * Now divide A by x, and compensate by multiplying Ac by - * x^{p-1}-1 mod x^p-x-1. - * - * That multiplication is particularly easy, precisely because - * x^{p-1}-1 is the multiplicative inverse of x! Each x^n term - * for n>0 just moves down to the x^{n-1} term, and only the - * constant term has to be dealt with in an interesting way. - */ - for (size_t j = 1; j < SIZE; j++) - A[j-1] = A[j]; - A[SIZE-1] = 0; - uint16_t Ac0 = Ac[0]; - for (size_t j = 1; j < p; j++) - Ac[j-1] = Ac[j]; - Ac[p-1] = Ac0; - Ac[0] = REDUCE(Ac[0] + q - Ac0); - } - - /* - * Now we expect that A is 0, and B is a constant. If so, then - * they are coprime, and we're going to return success. If not, - * they have a common factor. - */ - unsigned success = iszero(A[0]) & (1 ^ iszero(B[0])); - for (size_t j = 1; j < SIZE; j++) - success &= iszero(A[j]) & iszero(B[j]); - - /* - * So we're going to return Bc, but first, scale it by the - * multiplicative inverse of the constant we ended up with in - * B[0]. - */ - uint16_t scale = INVERT(B[0]); - for (size_t i = 0; i < p; i++) - out[i] = REDUCE(scale * Bc[i]); - - smemclr(A, SIZE * sizeof(*A)); - sfree(A); - smemclr(B, SIZE * sizeof(*B)); - sfree(B); - smemclr(Ac, SIZE * sizeof(*Ac)); - sfree(Ac); - smemclr(Bc, SIZE * sizeof(*Bc)); - sfree(Bc); - - return success; -} - -/* - * Given an array of values mod q, convert each one to its - * minimum-absolute-value representative, and then reduce mod 3. - * - * Output values are 0, 1 and 0xFFFF, representing -1. - * - * (Normally our arrays of uint16_t are in 'minimal non-negative - * residue' form, so the output of this function is unusual. But it's - * useful to have it in this form so that it can be reused by - * ntru_round3. You can put it back to the usual representation using - * ntru_normalise, below.) - */ -void ntru_mod3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) -{ - uint64_t qrecip = reciprocal_for_reduction(q); - uint64_t recip3 = reciprocal_for_reduction(3); - - unsigned bias = q/2; - uint16_t adjust = 3 - reduce(bias-1, 3, recip3); - - for (unsigned i = 0; i < p; i++) { - uint16_t val = reduce(in[i] + bias, q, qrecip); - uint16_t residue = reduce(val + adjust, 3, recip3); - out[i] = residue - 1; - } -} - -/* - * Given an array of values mod q, round each one to the nearest - * multiple of 3 to its minimum-absolute-value representative. - * - * Output values are signed integers coerced to uint16_t, so again, - * use ntru_normalise afterwards to put them back to normal. - */ -void ntru_round3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q) -{ - SETUP; - unsigned bias = q/2; - ntru_mod3(out, in, p, q); - for (unsigned i = 0; i < p; i++) - out[i] = REDUCE(in[i] + bias) - bias - out[i]; -} - -/* - * Given an array of signed integers coerced to uint16_t in the range - * [-q/2,+q/2], normalise them back to mod q values. - */ -static void ntru_normalise(uint16_t *out, const uint16_t *in, - unsigned p, unsigned q) -{ - for (unsigned i = 0; i < p; i++) - out[i] = in[i] + q * (in[i] >> 15); -} - -/* - * Given an array of values mod q, add a constant to each one. - */ -void ntru_bias(uint16_t *out, const uint16_t *in, unsigned bias, - unsigned p, unsigned q) -{ - SETUP; - for (unsigned i = 0; i < p; i++) - out[i] = REDUCE(in[i] + bias); -} - -/* - * Given an array of values mod q, multiply each one by a constant. - */ -void ntru_scale(uint16_t *out, const uint16_t *in, uint16_t scale, - unsigned p, unsigned q) -{ - SETUP; - for (unsigned i = 0; i < p; i++) - out[i] = REDUCE(in[i] * scale); -} - -/* - * Given an array of values mod 3, convert them to values mod q in a - * way that maps -1,0,+1 to -1,0,+1. - */ -static void ntru_expand( - uint16_t *out, const uint16_t *in, unsigned p, unsigned q) -{ - for (size_t i = 0; i < p; i++) { - uint16_t v = in[i]; - /* Map 2 to q-1, and leave 0 and 1 unchanged */ - v += (v >> 1) * (q-3); - out[i] = v; - } -} - -/* ---------------------------------------------------------------------- - * Implement the binary encoding from ntruprime-20201007.pdf, which is - * used to encode public keys and ciphertexts (though not plaintexts, - * which are done in a much simpler way). - * - * The general idea is that your encoder takes as input a list of - * small non-negative integers (r_i), and a sequence of limits (m_i) - * such that 0 <= r_i < m_i, and emits a sequence of bytes that encode - * all of these as tightly as reasonably possible. - * - * That's more general than is really needed, because in both the - * actual uses of this encoding, the input m_i are all the same! But - * the array of (r_i,m_i) pairs evolves during encoding, so they don't - * _stay_ all the same, so you still have to have all the generality. - * - * The encoding process makes a number of passes along the list of - * inputs. In each step, pairs of adjacent numbers are combined into - * one larger one by turning (r_i,m_i) and (r_{i+1},m_{i+1}) into the - * pair (r_i + m_i r_{i+1}, m_i m_{i+1}), i.e. so that the original - * numbers could be recovered by taking the quotient and remaiinder of - * the new r value by m_i. Then, if the new m_i is at least 2^14, we - * emit the low 8 bits of r_i to the output stream and reduce r_i and - * its limit correspondingly. So at the end of the pass, we've got - * half as many numbers still to encode, they're all still not too - * big, and we've emitted some amount of data into the output. Then do - * another pass, keep going until there's only one number left, and - * emit it little-endian. - * - * That's all very well, but how do you decode it again? DJB exhibits - * a pair of recursive functions that are supposed to be mutually - * inverse, but I didn't have any confidence that I'd be able to debug - * them sensibly if they turned out not to be (or rather, if I - * implemented one of them wrong). So I came up with my own strategy - * instead. - * - * In my strategy, we start by processing just the (m_i) into an - * 'encoding schedule' consisting of a sequence of simple - * instructions. The instructions operate on a FIFO queue of numbers, - * initialised to the original (r_i). The three instruction types are: - * - * - 'COMBINE': consume two numbers a,b from the head of the queue, - * combine them by calculating a + m*b for some specified m, and - * push the result on the tail of the queue. - * - * - 'BYTE': divide the tail element of the queue by 2^8 and emit the - * low bits into the output stream. - * - * - 'COPY': pop a number from the head of the queue and push it - * straight back on the tail. (Used for handling the leftover - * element at the end of a pass if the input to the pass was a list - * of odd length.) - * - * So we effectively implement DJB's encoding process in simulation, - * and instead of actually processing a set of (r_i), we 'compile' the - * process into a sequence of instructions that can be handed just the - * (r_i) later and encode them in the right way. At the end of the - * instructions, the queue is expected to have been reduced to length - * 1 and contain the single integer 0. - * - * The nice thing about this system is that each of those three - * instructions is easy to reverse. So you can also use the same - * instructions for decoding: start with a queue containing 0, and - * process the instructions in reverse order and reverse sense. So - * BYTE means to _consume_ a byte from the encoded data (starting from - * the rightmost end) and use it to make a queue element bigger; and - * COMBINE run in reverse pops a single element from one end of the - * queue, divides it by m, and pushes the quotient and remainder on - * the other end. - * - * (So it's easy to debug, because the queue passes through the exact - * same sequence of states during decoding that it did during - * encoding, just in reverse order.) - * - * Also, the encoding schedule comes with information about the - * expected size of the encoded data, because you can find that out - * easily by just counting the BYTE commands. - */ - -enum { - /* - * Command values appearing in the 'ops' array. ENC_COPY and - * ENC_BYTE are single values; values of the form - * (ENC_COMBINE_BASE + m) represent a COMBINE command with - * parameter m. - */ - ENC_COPY, ENC_BYTE, ENC_COMBINE_BASE -}; -struct NTRUEncodeSchedule { - /* - * Object representing a compiled set of encoding instructions. - * - * 'nvals' is the number of r_i we expect to encode. 'nops' is the - * number of encoding commands in the 'ops' list; 'opsize' is the - * physical size of the array, used during construction. - * - * 'endpos' is used to avoid a last-minute faff during decoding. - * We implement our FIFO of integers as a ring buffer of size - * 'nvals'. Encoding cycles round it some number of times, and the - * final 0 element ends up at some random location in the array. - * If we know _where_ the 0 ends up during encoding, we can put - * the initial 0 there at the start of decoding, and then when we - * finish reversing all the instructions, we'll end up with the - * output numbers already arranged at their correct positions, so - * that there's no need to rotate the array at the last minute. - */ - size_t nvals, endpos, nops, opsize; - uint32_t *ops; -}; -static inline void sched_append(NTRUEncodeSchedule *sched, uint16_t op) -{ - /* Helper function to append an operation to the schedule, and - * update endpos. */ - sgrowarray(sched->ops, sched->opsize, sched->nops); - sched->ops[sched->nops++] = op; - if (op != ENC_BYTE) - sched->endpos = (sched->endpos + 1) % sched->nvals; -} - -/* - * Take in the list of limit values (m_i) and compute the encoding - * schedule. - */ -NTRUEncodeSchedule *ntru_encode_schedule(const uint16_t *ms_in, size_t n) -{ - NTRUEncodeSchedule *sched = snew(NTRUEncodeSchedule); - sched->nvals = n; - sched->endpos = n-1; - sched->nops = sched->opsize = 0; - sched->ops = NULL; - - assert(n != 0); - - /* - * 'ms' is the list of (m_i) on input to the current pass. - * 'ms_new' is the list output from the current pass. After each - * pass we swap the arrays round. - */ - uint32_t *ms = snewn(n, uint32_t); - uint32_t *msnew = snewn(n, uint32_t); - for (size_t i = 0; i < n; i++) - ms[i] = ms_in[i]; - - while (n > 1) { - size_t nnew = 0; - for (size_t i = 0; i < n; i += 2) { - if (i+1 == n) { - /* - * Odd element at the end of the input list: just copy - * it unchanged to the output. - */ - sched_append(sched, ENC_COPY); - msnew[nnew++] = ms[i]; - break; - } - - /* - * Normal case: consume two elements from the input list - * and combine them. - */ - uint32_t m1 = ms[i], m2 = ms[i+1], m = m1*m2; - sched_append(sched, ENC_COMBINE_BASE + m1); - - /* - * And then, as long as the combined limit is big enough, - * emit an output byte from the bottom of it. - */ - while (m >= (1<<14)) { - sched_append(sched, ENC_BYTE); - m = (m + 0xFF) >> 8; - } - - /* - * Whatever is left after that, we emit into the output - * list and append to the fifo. - */ - msnew[nnew++] = m; - } - - /* - * End of pass. The output list of (m_i) now becomes the input - * list. - */ - uint32_t *tmp = ms; - ms = msnew; - n = nnew; - msnew = tmp; - } - - /* - * When that loop terminates, it's because there's exactly one - * number left to encode. (Or, technically, _at most_ one - but we - * don't support encoding a completely empty list in this - * implementation, because what would be the point?) That number - * is just emitted little-endian until its limit is 1 (meaning its - * only possible actual value is 0). - */ - assert(n == 1); - uint32_t m = ms[0]; - while (m > 1) { - sched_append(sched, ENC_BYTE); - m = (m + 0xFF) >> 8; - } - - sfree(ms); - sfree(msnew); - - return sched; -} - -void ntru_encode_schedule_free(NTRUEncodeSchedule *sched) -{ - sfree(sched->ops); - sfree(sched); -} - -/* - * Calculate the output length of the encoded data in bytes. - */ -size_t ntru_encode_schedule_length(NTRUEncodeSchedule *sched) -{ - size_t len = 0; - for (size_t i = 0; i < sched->nops; i++) - if (sched->ops[i] == ENC_BYTE) - len++; - return len; -} - -/* - * Retrieve the number of items encoded. (Used by testcrypt.) - */ -size_t ntru_encode_schedule_nvals(NTRUEncodeSchedule *sched) -{ - return sched->nvals; -} - -/* - * Actually encode a sequence of (r_i), emitting the output bytes to - * an arbitrary BinarySink. - */ -void ntru_encode(NTRUEncodeSchedule *sched, const uint16_t *rs_in, - BinarySink *bs) -{ - size_t n = sched->nvals; - uint32_t *rs = snewn(n, uint32_t); - for (size_t i = 0; i < n; i++) - rs[i] = rs_in[i]; - - /* - * The head and tail pointers of the queue are both 'full'. That - * is, rs[head] is the first element actually in the queue, and - * rs[tail] is the last element. - * - * So you append to the queue by first advancing 'tail' and then - * writing to rs[tail], whereas you consume from the queue by - * first reading rs[head] and _then_ advancing 'head'. - * - * The more normal thing would be to make 'tail' point to the - * first empty slot instead of the last full one. But then you'd - * have to faff about with modular arithmetic to find the last - * full slot for the BYTE command, so in this case, it's easier to - * do it the less usual way. - */ - size_t head = 0, tail = n-1; - - for (size_t i = 0; i < sched->nops; i++) { - uint16_t op = sched->ops[i]; - switch (op) { - case ENC_BYTE: - put_byte(bs, rs[tail] & 0xFF); - rs[tail] >>= 8; - break; - case ENC_COPY: { - uint32_t r = rs[head]; - head = (head + 1) % n; - tail = (tail + 1) % n; - rs[tail] = r; - break; - } - default: { - uint32_t r1 = rs[head]; - head = (head + 1) % n; - uint32_t r2 = rs[head]; - head = (head + 1) % n; - tail = (tail + 1) % n; - rs[tail] = r1 + (op - ENC_COMBINE_BASE) * r2; - break; - } - } - } - - /* - * Expect that we've ended up with a single zero in the queue, at - * exactly the position that the setup-time analysis predicted it. - */ - assert(head == sched->endpos); - assert(tail == sched->endpos); - assert(rs[head] == 0); - - smemclr(rs, n * sizeof(*rs)); - sfree(rs); -} - -/* - * Decode a ptrlen of binary data into a sequence of (r_i). The data - * is expected to be of exactly the right length (on pain of assertion - * failure). - */ -void ntru_decode(NTRUEncodeSchedule *sched, uint16_t *rs_out, ptrlen data) -{ - size_t n = sched->nvals; - const uint8_t *base = (const uint8_t *)data.ptr; - const uint8_t *pos = base + data.len; - - /* - * Initialise the queue to a single zero, at the 'endpos' position - * that will mean the final output is correctly aligned. - * - * 'head' and 'tail' have the same meanings as in encoding. So - * 'tail' is the location that BYTE modifies and COPY and COMBINE - * consume from, and 'head' is the location that COPY and COMBINE - * push on to. As in encoding, they both point at the extremal - * full slots in the array. - */ - uint32_t *rs = snewn(n, uint32_t); - size_t head = sched->endpos, tail = head; - rs[tail] = 0; - - for (size_t i = sched->nops; i-- > 0 ;) { - uint16_t op = sched->ops[i]; - switch (op) { - case ENC_BYTE: { - assert(pos > base); - uint8_t byte = *--pos; - rs[tail] = (rs[tail] << 8) | byte; - break; - } - case ENC_COPY: { - uint32_t r = rs[tail]; - tail = (tail + n - 1) % n; - head = (head + n - 1) % n; - rs[head] = r; - break; - } - default: { - uint32_t r = rs[tail]; - tail = (tail + n - 1) % n; - - uint32_t m = op - ENC_COMBINE_BASE; - uint64_t mrecip = reciprocal_for_reduction(m); - - uint32_t r1, r2; - r1 = reduce_with_quot(r, &r2, m, mrecip); - - head = (head + n - 1) % n; - rs[head] = r2; - head = (head + n - 1) % n; - rs[head] = r1; - break; - } - } - } - - assert(pos == base); - assert(head == 0); - assert(tail == n-1); - - for (size_t i = 0; i < n; i++) - rs_out[i] = rs[i]; - smemclr(rs, n * sizeof(*rs)); - sfree(rs); -} - -/* ---------------------------------------------------------------------- - * The actual public-key cryptosystem. - */ - -struct NTRUKeyPair { - unsigned p, q, w; - uint16_t *h; /* public key */ - uint16_t *f3, *ginv; /* private key */ - uint16_t *rho; /* for implicit rejection */ -}; - -/* Helper function to free an array of uint16_t containing a ring - * element, clearing it on the way since some of them are sensitive. */ -static void ring_free(uint16_t *val, unsigned p) -{ - smemclr(val, p*sizeof(*val)); - sfree(val); -} - -void ntru_keypair_free(NTRUKeyPair *keypair) -{ - ring_free(keypair->h, keypair->p); - ring_free(keypair->f3, keypair->p); - ring_free(keypair->ginv, keypair->p); - ring_free(keypair->rho, keypair->p); - sfree(keypair); -} - -/* Trivial accessors used by test programs. */ -unsigned ntru_keypair_p(NTRUKeyPair *keypair) { return keypair->p; } -const uint16_t *ntru_pubkey(NTRUKeyPair *keypair) { return keypair->h; } - -/* - * Generate a value of the class DJB describes as 'Short': it consists - * of p terms that are all either 0 or +1 or -1, and exactly w of them - * are not zero. - * - * Values of this kind are used for several purposes: part of the - * private key, a plaintext, and the 'rho' fake-plaintext value used - * for deliberately returning a duff but non-revealing session hash if - * things go wrong. - * - * -1 is represented as 2 in the output array. So if you want these - * numbers mod 3, then they come out already in the right form. - * Otherwise, use ntru_expand. - */ -void ntru_gen_short(uint16_t *v, unsigned p, unsigned w) -{ - /* - * Get enough random data to generate a polynomial all of whose p - * terms are in {0,+1,-1}, and exactly w of them are nonzero. - * We'll do this by making up a completely random sequence of - * {+1,-1} and then setting a random subset of them to 0. - * - * So we'll need p random bits to choose the nonzero values, and - * then (doing it the simplest way) log2(p!) bits to shuffle them, - * plus say 128 bits to ensure any fluctuations in uniformity are - * negligible. - * - * log2(p!) is a pain to calculate, so we'll bound it above by - * p*log2(p), which we bound in turn by p*16. - */ - size_t randbitpos = 17 * p + 128; - mp_int *randdata = mp_resize(mp_random_bits(randbitpos), randbitpos + 32); - - /* - * Initial value before zeroing out some terms: p randomly chosen - * values in {1,2}. - */ - for (size_t i = 0; i < p; i++) - v[i] = 1 + mp_get_bit(randdata, --randbitpos); - - /* - * Hereafter we're going to extract random bits by multiplication, - * treating randdata as a large fixed-point number. - */ - mp_reduce_mod_2to(randdata, randbitpos); - - /* - * Zero out some terms, leaving a randomly selected w of them - * nonzero. - */ - uint32_t nonzeros_left = w; - mp_int *x = mp_new(64); - for (size_t i = p; i-- > 0 ;) { - /* - * Pick a random number out of the number of terms remaning. - */ - mp_mul_integer_into(randdata, randdata, i+1); - mp_rshift_fixed_into(x, randdata, randbitpos); - mp_reduce_mod_2to(randdata, randbitpos); - size_t j = mp_get_integer(x); - - /* - * If that's less than nonzeros_left, then we're leaving this - * number nonzero. Otherwise we're zeroing it out. - */ - uint32_t keep = (uint32_t)(j - nonzeros_left) >> 31; - v[i] &= -keep; /* clear this field if keep == 0 */ - nonzeros_left -= keep; /* decrement counter if keep == 1 */ - } - - mp_free(x); - mp_free(randdata); -} - -/* - * Make a single attempt at generating a key pair. This involves - * inventing random elements of both our quotient rings and hoping - * they're both invertible. - * - * They may not be, if you're unlucky. The element of Z_q/ - * will _almost_ certainly be invertible, because that is a field, so - * invertibility can only fail if you were so unlucky as to choose the - * all-0s element. But the element of Z_3/ may fail to be - * invertible because it has a common factor with x^p-x-1 (which, over - * Z_3, is not irreducible). - * - * So we can't guarantee to generate a key pair in constant time, - * because there's no predicting how many retries we'll need. However, - * this isn't a failure of side-channel safety, because we completely - * discard all the random numbers and state from each failed attempt. - * So if there were a side-channel leakage from a failure, the only - * thing it would give away would be a bunch of random numbers that - * turned out not to be used anyway. - * - * But a _successful_ call to this function should execute in a - * secret-independent manner, and this 'make a single attempt' - * function is exposed in the API so that 'testsc' can check that. - */ -NTRUKeyPair *ntru_keygen_attempt(unsigned p, unsigned q, unsigned w) -{ - /* - * First invent g, which is the one more likely to fail to invert. - * This is simply a uniformly random polynomial with p terms over - * Z_3. So we need p*log2(3) random bits for it, plus 128 for - * uniformity. It's easiest to bound log2(3) above by 2. - */ - size_t randbitpos = 2 * p + 128; - mp_int *randdata = mp_resize(mp_random_bits(randbitpos), randbitpos + 32); - - /* - * Select p random values from {0,1,2}. - */ - uint16_t *g = snewn(p, uint16_t); - mp_int *x = mp_new(64); - for (size_t i = 0; i < p; i++) { - mp_mul_integer_into(randdata, randdata, 3); - mp_rshift_fixed_into(x, randdata, randbitpos); - mp_reduce_mod_2to(randdata, randbitpos); - g[i] = mp_get_integer(x); - } - mp_free(x); - mp_free(randdata); - - /* - * Try to invert g over Z_3, and fail if it isn't invertible. - */ - uint16_t *ginv = snewn(p, uint16_t); - if (!ntru_ring_invert(ginv, g, p, 3)) { - ring_free(g, p); - ring_free(ginv, p); - return NULL; - } - - /* - * Fine; we have g. Now make up an f, and convert it to a - * polynomial over q. - */ - uint16_t *f = snewn(p, uint16_t); - ntru_gen_short(f, p, w); - ntru_expand(f, f, p, q); - - /* - * Multiply f by 3. - */ - uint16_t *f3 = snewn(p, uint16_t); - ntru_scale(f3, f, 3, p, q); - - /* - * Invert 3*f over Z_q. This is guaranteed to succeed, since - * Z_q/ is a field, so the only non-invertible value is - * 0. And f is nonzero because it came from ntru_gen_short (hence, - * w of its components are nonzero), hence so is 3*f. - */ - uint16_t *f3inv = snewn(p, uint16_t); - bool expect_always_success = ntru_ring_invert(f3inv, f3, p, q); - assert(expect_always_success); - - /* - * Make the public key, by converting g to a polynomial over q and - * then multiplying by f3inv. - */ - uint16_t *g_q = snewn(p, uint16_t); - ntru_expand(g_q, g, p, q); - uint16_t *h = snewn(p, uint16_t); - ntru_ring_multiply(h, g_q, f3inv, p, q); - - /* - * Make up rho, used to substitute for the plaintext in the - * session hash in case of confirmation failure. - */ - uint16_t *rho = snewn(p, uint16_t); - ntru_gen_short(rho, p, w); - - /* - * And we're done! Free everything except the pieces we're - * returning. - */ - NTRUKeyPair *keypair = snew(NTRUKeyPair); - keypair->p = p; - keypair->q = q; - keypair->w = w; - keypair->h = h; - keypair->f3 = f3; - keypair->ginv = ginv; - keypair->rho = rho; - ring_free(f, p); - ring_free(f3inv, p); - ring_free(g, p); - ring_free(g_q, p); - return keypair; -} - -/* - * The top-level key generation function for real use (as opposed to - * testsc): keep trying to make a key until you succeed. - */ -NTRUKeyPair *ntru_keygen(unsigned p, unsigned q, unsigned w) -{ - while (1) { - NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w); - if (keypair) - return keypair; - } -} - -/* - * Public-key encryption. - */ -void ntru_encrypt(uint16_t *ciphertext, const uint16_t *plaintext, - uint16_t *pubkey, unsigned p, unsigned q) -{ - uint16_t *r_q = snewn(p, uint16_t); - ntru_expand(r_q, plaintext, p, q); - - uint16_t *unrounded = snewn(p, uint16_t); - ntru_ring_multiply(unrounded, r_q, pubkey, p, q); - - ntru_round3(ciphertext, unrounded, p, q); - ntru_normalise(ciphertext, ciphertext, p, q); - - ring_free(r_q, p); - ring_free(unrounded, p); -} - -/* - * Public-key decryption. - */ -void ntru_decrypt(uint16_t *plaintext, const uint16_t *ciphertext, - NTRUKeyPair *keypair) -{ - unsigned p = keypair->p, q = keypair->q, w = keypair->w; - uint16_t *tmp = snewn(p, uint16_t); - - ntru_ring_multiply(tmp, ciphertext, keypair->f3, p, q); - - ntru_mod3(tmp, tmp, p, q); - ntru_normalise(tmp, tmp, p, 3); - - ntru_ring_multiply(plaintext, tmp, keypair->ginv, p, 3); - ring_free(tmp, p); - - /* - * With luck, this should have recovered exactly the original - * plaintext. But, as per the spec, we check whether it has - * exactly w nonzero coefficients, and if not, then something has - * gone wrong - and in that situation we time-safely substitute a - * different output. - * - * (I don't know exactly why we do this, but I assume it's because - * otherwise the mis-decoded output could be made to disgorge a - * secret about the private key in some way.) - */ - - unsigned weight = p; - for (size_t i = 0; i < p; i++) - weight -= iszero(plaintext[i]); - unsigned ok = iszero(weight ^ w); - - /* - * The default failure return value consists of w 1s followed by - * 0s. - */ - unsigned mask = ok - 1; - for (size_t i = 0; i < w; i++) { - uint16_t diff = (1 ^ plaintext[i]) & mask; - plaintext[i] ^= diff; - } - for (size_t i = w; i < p; i++) { - uint16_t diff = (0 ^ plaintext[i]) & mask; - plaintext[i] ^= diff; - } -} - -/* ---------------------------------------------------------------------- - * Encode and decode public keys, ciphertexts and plaintexts. - * - * Public keys and ciphertexts use the complicated binary encoding - * system implemented above. In both cases, the inputs are regarded as - * symmetric about zero, and are first biased to map their most - * negative permitted value to 0, so that they become non-negative and - * hence suitable as inputs to the encoding system. In the case of a - * ciphertext, where the input coefficients have also been coerced to - * be multiples of 3, we divide by 3 as well, saving space by reducing - * the upper bounds (m_i) on all the encoded numbers. - */ - -/* - * Compute the encoding schedule for a public key. - */ -static NTRUEncodeSchedule *ntru_encode_pubkey_schedule(unsigned p, unsigned q) -{ - uint16_t *ms = snewn(p, uint16_t); - for (size_t i = 0; i < p; i++) - ms[i] = q; - NTRUEncodeSchedule *sched = ntru_encode_schedule(ms, p); - sfree(ms); - return sched; -} - -/* - * Encode a public key. - */ -void ntru_encode_pubkey(const uint16_t *pubkey, unsigned p, unsigned q, - BinarySink *bs) -{ - /* Compute the biased version for encoding */ - uint16_t *biased_pubkey = snewn(p, uint16_t); - ntru_bias(biased_pubkey, pubkey, q / 2, p, q); - - /* Encode it */ - NTRUEncodeSchedule *sched = ntru_encode_pubkey_schedule(p, q); - ntru_encode(sched, biased_pubkey, bs); - ntru_encode_schedule_free(sched); - - ring_free(biased_pubkey, p); -} - -/* - * Decode a public key and write it into 'pubkey'. We also return a - * ptrlen pointing at the chunk of data we removed from the - * BinarySource. - */ -ptrlen ntru_decode_pubkey(uint16_t *pubkey, unsigned p, unsigned q, - BinarySource *src) -{ - NTRUEncodeSchedule *sched = ntru_encode_pubkey_schedule(p, q); - - /* Retrieve the right number of bytes from the source */ - size_t len = ntru_encode_schedule_length(sched); - ptrlen encoded = get_data(src, len); - if (get_err(src)) { - /* If there wasn't enough data, give up and return all-zeroes - * purely for determinism. But that value should never be - * used, because the caller will also check get_err(src). */ - memset(pubkey, 0, p*sizeof(*pubkey)); - } else { - /* Do the decoding */ - ntru_decode(sched, pubkey, encoded); - - /* Unbias the coefficients */ - ntru_bias(pubkey, pubkey, q-q/2, p, q); - } - - ntru_encode_schedule_free(sched); - return encoded; -} - -/* - * For ciphertext biasing: work out the largest absolute value a - * ciphertext element can take, which is given by taking q/2 and - * rounding it to the nearest multiple of 3. - */ -static inline unsigned ciphertext_bias(unsigned q) -{ - return (q/2+1) / 3; -} - -/* - * The number of possible values of a ciphertext coefficient (for use - * as the m_i in encoding) ranges from +ciphertext_bias(q) to - * -ciphertext_bias(q) inclusive. - */ -static inline unsigned ciphertext_m(unsigned q) -{ - return 1 + 2 * ciphertext_bias(q); -} - -/* - * Compute the encoding schedule for a ciphertext. - */ -static NTRUEncodeSchedule *ntru_encode_ciphertext_schedule( - unsigned p, unsigned q) -{ - unsigned m = ciphertext_m(q); - uint16_t *ms = snewn(p, uint16_t); - for (size_t i = 0; i < p; i++) - ms[i] = m; - NTRUEncodeSchedule *sched = ntru_encode_schedule(ms, p); - sfree(ms); - return sched; -} - -/* - * Encode a ciphertext. - */ -void ntru_encode_ciphertext(const uint16_t *ciphertext, unsigned p, unsigned q, - BinarySink *bs) -{ - SETUP; - - /* - * Bias the ciphertext, and scale down by 1/3, which we do by - * modular multiplication by the inverse of 3 mod q. (That only - * works if we know the inputs are all _exact_ multiples of 3 - * - but we do!) - */ - uint16_t *biased_ciphertext = snewn(p, uint16_t); - ntru_bias(biased_ciphertext, ciphertext, 3 * ciphertext_bias(q), p, q); - ntru_scale(biased_ciphertext, biased_ciphertext, INVERT(3), p, q); - - /* Encode. */ - NTRUEncodeSchedule *sched = ntru_encode_ciphertext_schedule(p, q); - ntru_encode(sched, biased_ciphertext, bs); - ntru_encode_schedule_free(sched); - - ring_free(biased_ciphertext, p); -} - -ptrlen ntru_decode_ciphertext(uint16_t *ct, NTRUKeyPair *keypair, - BinarySource *src) -{ - unsigned p = keypair->p, q = keypair->q; - - NTRUEncodeSchedule *sched = ntru_encode_ciphertext_schedule(p, q); - - /* Retrieve the right number of bytes from the source */ - size_t len = ntru_encode_schedule_length(sched); - ptrlen encoded = get_data(src, len); - if (get_err(src)) { - /* As above, return deterministic nonsense on failure */ - memset(ct, 0, p*sizeof(*ct)); - } else { - /* Do the decoding */ - ntru_decode(sched, ct, encoded); - - /* Undo the scaling and bias */ - ntru_scale(ct, ct, 3, p, q); - ntru_bias(ct, ct, q - 3 * ciphertext_bias(q), p, q); - } - - ntru_encode_schedule_free(sched); - return encoded; /* also useful to the caller, optionally */ -} - -/* - * Encode a plaintext. - * - * This is a much simpler encoding than the NTRUEncodeSchedule system: - * since elements of a plaintext are mod 3, we just encode each one in - * 2 bits, applying the usual bias so that {-1,0,+1} map to {0,1,2} - * respectively. - * - * There's no corresponding decode function, because plaintexts are - * never transmitted on the wire (the whole point is that they're too - * secret!). Plaintexts are only encoded in order to put them into - * hash preimages. - */ -void ntru_encode_plaintext(const uint16_t *plaintext, unsigned p, - BinarySink *bs) -{ - unsigned byte = 0, bitpos = 0; - for (size_t i = 0; i < p; i++) { - unsigned encoding = (plaintext[i] + 1) * iszero(plaintext[i] >> 1); - byte |= encoding << bitpos; - bitpos += 2; - if (bitpos == 8 || i+1 == p) { - put_byte(bs, byte); - byte = 0; - bitpos = 0; - } - } -} - -/* ---------------------------------------------------------------------- - * Compute the hashes required by the key exchange layer of NTRU Prime. - * - * There are two of these. The 'confirmation hash' is sent by the - * server along with the ciphertext, and the client can recalculate it - * to check whether the ciphertext was decrypted correctly. Then, the - * 'session hash' is the actual output of key exchange, and if the - * confirmation hash doesn't match, it gets deliberately corrupted. - */ - -/* - * Make the confirmation hash, whose inputs are the plaintext and the - * public key. - * - * This is defined as H(2 || H(3 || r) || H(4 || K)), where r is the - * plaintext and K is the public key (as encoded by the above - * functions), and the constants 2,3,4 are single bytes. The choice of - * hash function (H itself) is SHA-512 truncated to 256 bits. - * - * (To be clear: that is _not_ the thing that FIPS 180-4 6.7 defines - * as "SHA-512/256", which varies the initialisation vector of the - * SHA-512 algorithm as well as truncating the output. _This_ - * algorithm uses the standard SHA-512 IV, and _just_ truncates the - * output, in the manner suggested by FIPS 180-4 section 7.) - * - * 'out' should therefore expect to receive 32 bytes of data. - */ -static void ntru_confirmation_hash( - uint8_t *out, const uint16_t *plaintext, - const uint16_t *pubkey, unsigned p, unsigned q) -{ - /* The outer hash object */ - ssh_hash *hconfirm = ssh_hash_new(&ssh_sha512); - put_byte(hconfirm, 2); /* initial byte 2 */ - - uint8_t hashdata[64]; - - /* Compute H(3 || r) and add it to the main hash */ - ssh_hash *h3r = ssh_hash_new(&ssh_sha512); - put_byte(h3r, 3); - ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(h3r)); - ssh_hash_final(h3r, hashdata); - put_data(hconfirm, hashdata, 32); - - /* Compute H(4 || K) and add it to the main hash */ - ssh_hash *h4K = ssh_hash_new(&ssh_sha512); - put_byte(h4K, 4); - ntru_encode_pubkey(pubkey, p, q, BinarySink_UPCAST(h4K)); - ssh_hash_final(h4K, hashdata); - put_data(hconfirm, hashdata, 32); - - /* Compute the full output of the main SHA-512 hash */ - ssh_hash_final(hconfirm, hashdata); - - /* And copy the first 32 bytes into the caller's output array */ - memcpy(out, hashdata, 32); - smemclr(hashdata, sizeof(hashdata)); -} - -/* - * Make the session hash, whose inputs are the plaintext, the - * ciphertext, and the confirmation hash (hence, transitively, a - * dependence on the public key as well). - * - * As computed by the server, and by the client if the confirmation - * hash matched, this is defined as - * - * H(1 || H(3 || r) || ciphertext || confirmation hash) - * - * but if the confirmation hash _didn't_ match, then the plaintext r - * is replaced with the dummy plaintext-shaped value 'rho' we invented - * during key generation (presumably to avoid leaking any information - * about our secrets), and the initial byte 1 is replaced with 0 (to - * ensure that the resulting hash preimage can't match any legitimate - * preimage). So in that case, you instead get - * - * H(0 || H(3 || rho) || ciphertext || confirmation hash) - * - * The inputs to this function include 'ok', which is the value to use - * as the initial byte (1 on success, 0 on failure), and 'plaintext' - * which should already have been substituted with rho in case of - * failure. - * - * The ciphertext is provided in already-encoded form. - */ -static void ntru_session_hash( - uint8_t *out, unsigned ok, const uint16_t *plaintext, - unsigned p, ptrlen ciphertext, ptrlen confirmation_hash) -{ - /* The outer hash object */ - ssh_hash *hsession = ssh_hash_new(&ssh_sha512); - put_byte(hsession, ok); /* initial byte 1 or 0 */ - - uint8_t hashdata[64]; - - /* Compute H(3 || r), or maybe H(3 || rho), and add it to the main hash */ - ssh_hash *h3r = ssh_hash_new(&ssh_sha512); - put_byte(h3r, 3); - ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(h3r)); - ssh_hash_final(h3r, hashdata); - put_data(hsession, hashdata, 32); - - /* Put the ciphertext and confirmation hash in */ - put_datapl(hsession, ciphertext); - put_datapl(hsession, confirmation_hash); - - /* Compute the full output of the main SHA-512 hash */ - ssh_hash_final(hsession, hashdata); - - /* And copy the first 32 bytes into the caller's output array */ - memcpy(out, hashdata, 32); - smemclr(hashdata, sizeof(hashdata)); -} - -/* ---------------------------------------------------------------------- - * Top-level key exchange and SSH integration. - * - * Although this system borrows the ECDH packet structure, it's unlike - * true ECDH in that it is completely asymmetric between client and - * server. So we have two separate vtables of methods for the two - * sides of the system, and a third vtable containing only the class - * methods, in particular a constructor which chooses which one to - * instantiate. - */ - -/* - * The parameters p,q,w for the system. There are other choices of - * these, but OpenSSH only specifies this set. (If that ever changes, - * we'll need to turn these into elements of the state structures.) - */ -#define p_LIVE 761 -#define q_LIVE 4591 -#define w_LIVE 286 - -static char *ssh_ntru_description(const ssh_kex *kex) -{ - return dupprintf("NTRU Prime / Curve25519 hybrid key exchange"); -} - -/* - * State structure for the client, which takes the role of inventing a - * key pair and decrypting a secret plaintext sent to it by the server. - */ -typedef struct ntru_client_key { - NTRUKeyPair *keypair; - ecdh_key *curve25519; - - ecdh_key ek; -} ntru_client_key; - -static void ssh_ntru_client_free(ecdh_key *dh); -static void ssh_ntru_client_getpublic(ecdh_key *dh, BinarySink *bs); -static bool ssh_ntru_client_getkey(ecdh_key *dh, ptrlen remoteKey, - BinarySink *bs); - -static const ecdh_keyalg ssh_ntru_client_vt = { - /* This vtable has no 'new' method, because it's constructed via - * the selector vt below */ - .free = ssh_ntru_client_free, - .getpublic = ssh_ntru_client_getpublic, - .getkey = ssh_ntru_client_getkey, - .description = ssh_ntru_description, -}; - -static ecdh_key *ssh_ntru_client_new(void) -{ - ntru_client_key *nk = snew(ntru_client_key); - nk->ek.vt = &ssh_ntru_client_vt; - - nk->keypair = ntru_keygen(p_LIVE, q_LIVE, w_LIVE); - nk->curve25519 = ecdh_key_new(&ssh_ec_kex_curve25519, false); - - return &nk->ek; -} - -static void ssh_ntru_client_free(ecdh_key *dh) -{ - ntru_client_key *nk = container_of(dh, ntru_client_key, ek); - ntru_keypair_free(nk->keypair); - ecdh_key_free(nk->curve25519); - sfree(nk); -} - -static void ssh_ntru_client_getpublic(ecdh_key *dh, BinarySink *bs) -{ - ntru_client_key *nk = container_of(dh, ntru_client_key, ek); - - /* - * The client's public information is a single SSH string - * containing the NTRU public key and the Curve25519 public point - * concatenated. So write both of those into the output - * BinarySink. - */ - ntru_encode_pubkey(nk->keypair->h, p_LIVE, q_LIVE, bs); - ecdh_key_getpublic(nk->curve25519, bs); -} - -static bool ssh_ntru_client_getkey(ecdh_key *dh, ptrlen remoteKey, - BinarySink *bs) -{ - ntru_client_key *nk = container_of(dh, ntru_client_key, ek); - - /* - * We expect the server to have sent us a string containing a - * ciphertext, a confirmation hash, and a Curve25519 public point. - * Extract all three. - */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, remoteKey); - - uint16_t *ciphertext = snewn(p_LIVE, uint16_t); - ptrlen ciphertext_encoded = ntru_decode_ciphertext( - ciphertext, nk->keypair, src); - ptrlen confirmation_hash = get_data(src, 32); - ptrlen curve25519_remoteKey = get_data(src, 32); - - if (get_err(src) || get_avail(src)) { - /* Hard-fail if the input wasn't exactly the right length */ - ring_free(ciphertext, p_LIVE); - return false; - } - - /* - * Main hash object which will combine the NTRU and Curve25519 - * outputs. - */ - ssh_hash *h = ssh_hash_new(&ssh_sha512); - - /* Reusable buffer for storing various hash outputs. */ - uint8_t hashdata[64]; - - /* - * NTRU side. - */ - { - /* Decrypt the ciphertext to recover the server's plaintext */ - uint16_t *plaintext = snewn(p_LIVE, uint16_t); - ntru_decrypt(plaintext, ciphertext, nk->keypair); - - /* Make the confirmation hash */ - ntru_confirmation_hash(hashdata, plaintext, nk->keypair->h, - p_LIVE, q_LIVE); - - /* Check it matches the one the server sent */ - unsigned ok = smemeq(hashdata, confirmation_hash.ptr, 32); - - /* If not, substitute in rho for the plaintext in the session hash */ - unsigned mask = ok-1; - for (size_t i = 0; i < p_LIVE; i++) - plaintext[i] ^= mask & (plaintext[i] ^ nk->keypair->rho[i]); - - /* Compute the session hash, whether or not we did that */ - ntru_session_hash(hashdata, ok, plaintext, p_LIVE, ciphertext_encoded, - confirmation_hash); - - /* Free temporary values */ - ring_free(plaintext, p_LIVE); - ring_free(ciphertext, p_LIVE); - - /* And put the NTRU session hash into the main hash object. */ - put_data(h, hashdata, 32); - } - - /* - * Curve25519 side. - */ - { - strbuf *otherkey = strbuf_new_nm(); - - /* Call out to Curve25519 to compute the shared secret from that - * kex method */ - bool ok = ecdh_key_getkey(nk->curve25519, curve25519_remoteKey, - BinarySink_UPCAST(otherkey)); - - /* If that failed (which only happens if the other end does - * something wrong, like sending a low-order curve point - * outside the subgroup it's supposed to), we might as well - * just abort and return failure. That's what we'd have done - * in standalone Curve25519. */ - if (!ok) { - ssh_hash_free(h); - smemclr(hashdata, sizeof(hashdata)); - strbuf_free(otherkey); - return false; - } - - /* - * ecdh_key_getkey will have returned us a chunk of data - * containing an encoded mpint, which is how the Curve25519 - * output normally goes into the exchange hash. But in this - * context we want to treat it as a fixed big-endian 32 bytes, - * so extract it from its encoding and put it into the main - * hash object in the new format. - */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(otherkey)); - mp_int *curvekey = get_mp_ssh2(src); - - for (unsigned i = 32; i-- > 0 ;) - put_byte(h, mp_get_byte(curvekey, i)); - - mp_free(curvekey); - strbuf_free(otherkey); - } - - /* - * Finish up: compute the final output hash (full 64 bytes of - * SHA-512 this time), and return it encoded as a string. - */ - ssh_hash_final(h, hashdata); - put_stringpl(bs, make_ptrlen(hashdata, sizeof(hashdata))); - smemclr(hashdata, sizeof(hashdata)); - - return true; -} - -/* - * State structure for the server, which takes the role of inventing a - * secret plaintext and sending it to the client encrypted with the - * public key the client sent. - */ -typedef struct ntru_server_key { - uint16_t *plaintext; - strbuf *ciphertext_encoded, *confirmation_hash; - ecdh_key *curve25519; - - ecdh_key ek; -} ntru_server_key; - -static void ssh_ntru_server_free(ecdh_key *dh); -static void ssh_ntru_server_getpublic(ecdh_key *dh, BinarySink *bs); -static bool ssh_ntru_server_getkey(ecdh_key *dh, ptrlen remoteKey, - BinarySink *bs); - -static const ecdh_keyalg ssh_ntru_server_vt = { - /* This vtable has no 'new' method, because it's constructed via - * the selector vt below */ - .free = ssh_ntru_server_free, - .getpublic = ssh_ntru_server_getpublic, - .getkey = ssh_ntru_server_getkey, - .description = ssh_ntru_description, -}; - -static ecdh_key *ssh_ntru_server_new(void) -{ - ntru_server_key *nk = snew(ntru_server_key); - nk->ek.vt = &ssh_ntru_server_vt; - - nk->plaintext = snewn(p_LIVE, uint16_t); - nk->ciphertext_encoded = strbuf_new_nm(); - nk->confirmation_hash = strbuf_new_nm(); - ntru_gen_short(nk->plaintext, p_LIVE, w_LIVE); - - nk->curve25519 = ecdh_key_new(&ssh_ec_kex_curve25519, false); - - return &nk->ek; -} - -static void ssh_ntru_server_free(ecdh_key *dh) -{ - ntru_server_key *nk = container_of(dh, ntru_server_key, ek); - ring_free(nk->plaintext, p_LIVE); - strbuf_free(nk->ciphertext_encoded); - strbuf_free(nk->confirmation_hash); - ecdh_key_free(nk->curve25519); - sfree(nk); -} - -static bool ssh_ntru_server_getkey(ecdh_key *dh, ptrlen remoteKey, - BinarySink *bs) -{ - ntru_server_key *nk = container_of(dh, ntru_server_key, ek); - - /* - * In the server, getkey is called first, with the public - * information received from the client. We expect the client to - * have sent us a string containing a public key and a Curve25519 - * public point. - */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, remoteKey); - - uint16_t *pubkey = snewn(p_LIVE, uint16_t); - ntru_decode_pubkey(pubkey, p_LIVE, q_LIVE, src); - ptrlen curve25519_remoteKey = get_data(src, 32); - - if (get_err(src) || get_avail(src)) { - /* Hard-fail if the input wasn't exactly the right length */ - ring_free(pubkey, p_LIVE); - return false; - } - - /* - * Main hash object which will combine the NTRU and Curve25519 - * outputs. - */ - ssh_hash *h = ssh_hash_new(&ssh_sha512); - - /* Reusable buffer for storing various hash outputs. */ - uint8_t hashdata[64]; - - /* - * NTRU side. - */ - { - /* Encrypt the plaintext we generated at construction time, - * and encode the ciphertext into a strbuf so we can reuse it - * for both the session hash and sending to the client. */ - uint16_t *ciphertext = snewn(p_LIVE, uint16_t); - ntru_encrypt(ciphertext, nk->plaintext, pubkey, p_LIVE, q_LIVE); - ntru_encode_ciphertext(ciphertext, p_LIVE, q_LIVE, - BinarySink_UPCAST(nk->ciphertext_encoded)); - ring_free(ciphertext, p_LIVE); - - /* Compute the confirmation hash, and write it into another - * strbuf. */ - ntru_confirmation_hash(hashdata, nk->plaintext, pubkey, - p_LIVE, q_LIVE); - put_data(nk->confirmation_hash, hashdata, 32); - - /* Compute the session hash (which is easy on the server side, - * requiring no conditional substitution). */ - ntru_session_hash(hashdata, 1, nk->plaintext, p_LIVE, - ptrlen_from_strbuf(nk->ciphertext_encoded), - ptrlen_from_strbuf(nk->confirmation_hash)); - - /* And put the NTRU session hash into the main hash object. */ - put_data(h, hashdata, 32); - - /* Now we can free the public key */ - ring_free(pubkey, p_LIVE); - } - - /* - * Curve25519 side. - */ - { - strbuf *otherkey = strbuf_new_nm(); - - /* Call out to Curve25519 to compute the shared secret from that - * kex method */ - bool ok = ecdh_key_getkey(nk->curve25519, curve25519_remoteKey, - BinarySink_UPCAST(otherkey)); - /* As on the client side, abort if Curve25519 reported failure */ - if (!ok) { - ssh_hash_free(h); - smemclr(hashdata, sizeof(hashdata)); - strbuf_free(otherkey); - return false; - } - - /* As on the client side, decode Curve25519's mpint so we can - * re-encode it appropriately for our hash preimage */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(otherkey)); - mp_int *curvekey = get_mp_ssh2(src); - - for (unsigned i = 32; i-- > 0 ;) - put_byte(h, mp_get_byte(curvekey, i)); - - mp_free(curvekey); - strbuf_free(otherkey); - } - - /* - * Finish up: compute the final output hash (full 64 bytes of - * SHA-512 this time), and return it encoded as a string. - */ - ssh_hash_final(h, hashdata); - put_stringpl(bs, make_ptrlen(hashdata, sizeof(hashdata))); - smemclr(hashdata, sizeof(hashdata)); - - return true; -} - -static void ssh_ntru_server_getpublic(ecdh_key *dh, BinarySink *bs) -{ - ntru_server_key *nk = container_of(dh, ntru_server_key, ek); - - /* - * In the server, this function is called after getkey, so we - * already have all our pieces prepared. Just concatenate them all - * into the 'server's public data' string to go in ECDH_REPLY. - */ - put_datapl(bs, ptrlen_from_strbuf(nk->ciphertext_encoded)); - put_datapl(bs, ptrlen_from_strbuf(nk->confirmation_hash)); - ecdh_key_getpublic(nk->curve25519, bs); -} - -/* ---------------------------------------------------------------------- - * Selector vtable that instantiates the appropriate one of the above, - * depending on is_server. - */ -static ecdh_key *ssh_ntru_new(const ssh_kex *kex, bool is_server) -{ - if (is_server) - return ssh_ntru_server_new(); - else - return ssh_ntru_client_new(); -} - -static const ecdh_keyalg ssh_ntru_selector_vt = { - /* This is a never-instantiated vtable which only implements the - * functions that don't require an instance. */ - .new = ssh_ntru_new, - .description = ssh_ntru_description, -}; - -static const ssh_kex ssh_ntru_curve25519 = { - .name = "sntrup761x25519-sha512@openssh.com", - .main_type = KEXTYPE_ECDH, - .hash = &ssh_sha512, - .ecdh_vt = &ssh_ntru_selector_vt, -}; - -static const ssh_kex *const hybrid_list[] = { - &ssh_ntru_curve25519, -}; - -const ssh_kexes ssh_ntru_hybrid_kex = { lenof(hybrid_list), hybrid_list }; diff --git a/crypto/ntru.h b/crypto/ntru.h deleted file mode 100644 index 4789491be..000000000 --- a/crypto/ntru.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Internal functions for the NTRU cryptosystem, exposed in a header - * that is expected to be included only by ntru.c and test programs. - */ - -#ifndef PUTTY_CRYPTO_NTRU_H -#define PUTTY_CRYPTO_NTRU_H - -unsigned ntru_ring_invert(uint16_t *out, const uint16_t *in, - unsigned p, unsigned q); -void ntru_ring_multiply(uint16_t *out, const uint16_t *a, const uint16_t *b, - unsigned p, unsigned q); -void ntru_mod3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q); -void ntru_round3(uint16_t *out, const uint16_t *in, unsigned p, unsigned q); -void ntru_bias(uint16_t *out, const uint16_t *in, unsigned bias, - unsigned p, unsigned q); -void ntru_scale(uint16_t *out, const uint16_t *in, uint16_t scale, - unsigned p, unsigned q); - -NTRUEncodeSchedule *ntru_encode_schedule(const uint16_t *ms_in, size_t n); -void ntru_encode_schedule_free(NTRUEncodeSchedule *sched); -size_t ntru_encode_schedule_length(NTRUEncodeSchedule *sched); -size_t ntru_encode_schedule_nvals(NTRUEncodeSchedule *sched); -void ntru_encode(NTRUEncodeSchedule *sched, const uint16_t *rs_in, - BinarySink *bs); -void ntru_decode(NTRUEncodeSchedule *sched, uint16_t *rs_out, ptrlen data); - -void ntru_gen_short(uint16_t *v, unsigned p, unsigned w); - -NTRUKeyPair *ntru_keygen_attempt(unsigned p, unsigned q, unsigned w); -NTRUKeyPair *ntru_keygen(unsigned p, unsigned q, unsigned w); -void ntru_keypair_free(NTRUKeyPair *keypair); - -void ntru_encrypt(uint16_t *ciphertext, const uint16_t *plaintext, - uint16_t *pubkey, unsigned p, unsigned q); -void ntru_decrypt(uint16_t *plaintext, const uint16_t *ciphertext, - NTRUKeyPair *keypair); - -void ntru_encode_pubkey(const uint16_t *pubkey, unsigned p, unsigned q, - BinarySink *bs); -ptrlen ntru_decode_pubkey(uint16_t *pubkey, unsigned p, unsigned q, - BinarySource *src); -void ntru_encode_ciphertext(const uint16_t *ciphertext, unsigned p, unsigned q, - BinarySink *bs); -ptrlen ntru_decode_ciphertext(uint16_t *ct, NTRUKeyPair *keypair, - BinarySource *src); -void ntru_encode_plaintext(const uint16_t *plaintext, unsigned p, - BinarySink *bs); - -unsigned ntru_keypair_p(NTRUKeyPair *keypair); -const uint16_t *ntru_pubkey(NTRUKeyPair *keypair); - -#endif /* PUTTY_CRYPTO_NTRU_H */ diff --git a/crypto/openssh-certs.c b/crypto/openssh-certs.c deleted file mode 100644 index 4cd984e83..000000000 --- a/crypto/openssh-certs.c +++ /dev/null @@ -1,1162 +0,0 @@ -/* - * Public key type for OpenSSH certificates. - */ - -#include "ssh.h" -#include "putty.h" - -enum { - SSH_CERT_TYPE_USER = 1, - SSH_CERT_TYPE_HOST = 2, -}; - -typedef struct opensshcert_key { - strbuf *nonce; - uint64_t serial; - uint32_t type; - strbuf *key_id; - strbuf *valid_principals; - uint64_t valid_after, valid_before; - strbuf *critical_options; - strbuf *extensions; - strbuf *reserved; - strbuf *signature_key; - strbuf *signature; - - ssh_key *basekey; - - ssh_key sshk; -} opensshcert_key; - -typedef struct blob_fmt { - const unsigned *fmt; - size_t len; -} blob_fmt; - -typedef struct opensshcert_extra { - /* - * OpenSSH certificate formats aren't completely consistent about - * the relationship between the public+private blob uploaded to - * the agent for the certified key type, and the one for the base - * key type. Here we specify the mapping. - * - * Each of these foo_fmt strings indicates the layout of a - * particular version of the key, in the form of an array of - * integers together with a length, with each integer describing - * one of the components of the key. The integers are defined by - * enums, so that they're tightly packed; the general idea is that - * if you're converting from one form to another, then you use the - * format list for the source format to read out a succession of - * SSH strings from the source data and put them in an array - * indexed by the integer ids, and then use the list for the - * destination format to write the strings out to the destination - * in the right (maybe different) order. - * - * pub_fmt describes the format of the public-key blob for the - * base key type, not counting the initial string giving the key - * type identifier itself. As far as I know, this always matches - * the format of the public-key data appearing in the middle of - * the certificate. - * - * base_ossh_fmt describes the format of the full OpenSSH blob - * appearing in the ssh-agent protocol for the base key, - * containing the public and private key data. - * - * cert_ossh_fmt describes the format of the OpenSSH blob for the - * certificate key format, beginning just after the certificate - * string itself. - */ - blob_fmt pub_fmt, base_ossh_fmt, cert_ossh_fmt; - - /* - * The RSA-SHA2 algorithm names have their SSH id set to names - * like "rsa-sha2-512-cert-...", which is what will be received in - * the KEXINIT algorithm list if a host key in one of those - * algorithms is presented. But the _key_ type id that will appear - * in the public key blob is "ssh-rsa-cert-...". So we need a - * separate field to indicate the key type id we expect to see in - * certified public keys, and also the one we want to put back - * into the artificial public blob we make to pass to the - * constructor for the underlying key. - * - * (In rsa.c this is managed much more simply, because everything - * sharing the same vtable wants the same key type id.) - */ - const char *cert_key_ssh_id, *base_key_ssh_id; -} opensshcert_extra; - -/* - * The actual integer arrays defining the per-key blob formats. - */ - -/* DSA is the most orthodox: only the obviously necessary public key - * info appears at all, it's in the same order everywhere, and none of - * it is repeated unnecessarily */ -enum { DSA_p, DSA_q, DSA_g, DSA_y, DSA_x }; -static const unsigned dsa_pub_fmt[] = { DSA_p, DSA_q, DSA_g, DSA_y }; -static const unsigned dsa_base_ossh_fmt[] = { - DSA_p, DSA_q, DSA_g, DSA_y, DSA_x }; -static const unsigned dsa_cert_ossh_fmt[] = { DSA_x }; - -/* ECDSA is almost as nice, except that it pointlessly mentions the - * curve name in the public data, which shouldn't be necessary given - * that the SSH key id has already implied it. But at least that's - * consistent everywhere. */ -enum { ECDSA_curve, ECDSA_point, ECDSA_exp }; -static const unsigned ecdsa_pub_fmt[] = { ECDSA_curve, ECDSA_point }; -static const unsigned ecdsa_base_ossh_fmt[] = { - ECDSA_curve, ECDSA_point, ECDSA_exp }; -static const unsigned ecdsa_cert_ossh_fmt[] = { ECDSA_exp }; - -/* Ed25519 has the oddity that the private data following the - * certificate in the OpenSSH blob is preceded by an extra copy of the - * public data, for no obviously necessary reason since that doesn't - * happen in any of the rest of these formats */ -enum { EDDSA_point, EDDSA_exp }; -static const unsigned eddsa_pub_fmt[] = { EDDSA_point }; -static const unsigned eddsa_base_ossh_fmt[] = { EDDSA_point, EDDSA_exp }; -static const unsigned eddsa_cert_ossh_fmt[] = { EDDSA_point, EDDSA_exp }; - -/* And RSA has the quirk that the modulus and exponent are reversed in - * the base key type's OpenSSH blob! */ -enum { RSA_e, RSA_n, RSA_d, RSA_p, RSA_q, RSA_iqmp }; -static const unsigned rsa_pub_fmt[] = { RSA_e, RSA_n }; -static const unsigned rsa_base_ossh_fmt[] = { - RSA_n, RSA_e, RSA_d, RSA_p, RSA_q, RSA_iqmp }; -static const unsigned rsa_cert_ossh_fmt[] = { RSA_d, RSA_p, RSA_q, RSA_iqmp }; - -/* - * Routines to transform one kind of blob into another based on those - * foo_fmt integer arrays. - */ -typedef struct BlobTransformer { - ptrlen *parts; - size_t nparts; -} BlobTransformer; - -#define BLOBTRANS_DECLARE(bt) BlobTransformer bt[1] = { { NULL, 0 } } - -static inline void blobtrans_clear(BlobTransformer *bt) -{ - sfree(bt->parts); - bt->parts = NULL; - bt->nparts = 0; -} - -static inline bool blobtrans_read(BlobTransformer *bt, BinarySource *src, - blob_fmt blob) -{ - size_t nparts = bt->nparts; - for (size_t i = 0; i < blob.len; i++) - if (nparts < blob.fmt[i]+1) - nparts = blob.fmt[i]+1; - - if (nparts > bt->nparts) { - bt->parts = sresize(bt->parts, nparts, ptrlen); - while (bt->nparts < nparts) - bt->parts[bt->nparts++] = make_ptrlen(NULL, 0); - } - - for (size_t i = 0; i < blob.len; i++) { - size_t j = blob.fmt[i]; - ptrlen part = get_string(src); - if (bt->parts[j].ptr) { - /* - * If the same string appears in both the public blob and - * the private data, check they match. (This happens in - * Ed25519: an extra copy of the public point string - * appears in the certified OpenSSH data after the - * certificate and before the private key.) - */ - if (!ptrlen_eq_ptrlen(bt->parts[j], part)) - return false; - } - bt->parts[j] = part; - } - - return true; -} - -static inline void blobtrans_write(BlobTransformer *bt, BinarySink *bs, - blob_fmt blob) -{ - for (size_t i = 0; i < blob.len; i++) { - assert(i < bt->nparts); - ptrlen part = bt->parts[blob.fmt[i]]; - assert(part.ptr); - put_stringpl(bs, part); - } -} - -/* - * Forward declarations. - */ -static ssh_key *opensshcert_new_pub(const ssh_keyalg *self, ptrlen pub); -static ssh_key *opensshcert_new_priv( - const ssh_keyalg *self, ptrlen pub, ptrlen priv); -static ssh_key *opensshcert_new_priv_openssh( - const ssh_keyalg *self, BinarySource *src); -static void opensshcert_freekey(ssh_key *key); -static char *opensshcert_invalid(ssh_key *key, unsigned flags); -static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags, - BinarySink *bs); -static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data); -static void opensshcert_public_blob(ssh_key *key, BinarySink *bs); -static void opensshcert_private_blob(ssh_key *key, BinarySink *bs); -static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs); -static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs); -static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs); -static SeatDialogText *opensshcert_cert_info(ssh_key *key); -static bool opensshcert_has_private(ssh_key *key); -static char *opensshcert_cache_str(ssh_key *key); -static key_components *opensshcert_components(ssh_key *key); -static ssh_key *opensshcert_base_key(ssh_key *key); -static bool opensshcert_check_cert( - ssh_key *key, bool host, ptrlen principal, uint64_t time, - const ca_options *opts, BinarySink *error); -static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob); -static unsigned opensshcert_supported_flags(const ssh_keyalg *self); -static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, - unsigned flags); -static char *opensshcert_alg_desc(const ssh_keyalg *self); -static bool opensshcert_variable_size(const ssh_keyalg *self); -static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, - const ssh_keyalg *base); - -/* - * Top-level vtables for the certified key formats, defined via a list - * macro so I can also make an array of them all. - */ - -#define KEYALG_LIST(X) \ - X(ssh_dsa, "ssh-dss", "ssh-dss", dsa) \ - X(ssh_rsa, "ssh-rsa", "ssh-rsa", rsa) \ - X(ssh_rsa_sha256, "rsa-sha2-256", "ssh-rsa", rsa) \ - X(ssh_rsa_sha512, "rsa-sha2-512", "ssh-rsa", rsa) \ - X(ssh_ecdsa_ed25519, "ssh-ed25519", "ssh-ed25519", eddsa) \ - X(ssh_ecdsa_nistp256, "ecdsa-sha2-nistp256","ecdsa-sha2-nistp256", ecdsa) \ - X(ssh_ecdsa_nistp384, "ecdsa-sha2-nistp384","ecdsa-sha2-nistp384", ecdsa) \ - X(ssh_ecdsa_nistp521, "ecdsa-sha2-nistp521","ecdsa-sha2-nistp521", ecdsa) \ - /* end of list */ - -#define KEYALG_DEF(name, ssh_alg_id_prefix, ssh_key_id_prefix, fmt_prefix) \ - static const struct opensshcert_extra opensshcert_##name##_extra = { \ - .pub_fmt = { .fmt = fmt_prefix ## _pub_fmt, \ - .len = lenof(fmt_prefix ## _pub_fmt) }, \ - .base_ossh_fmt = { .fmt = fmt_prefix ## _base_ossh_fmt, \ - .len = lenof(fmt_prefix ## _base_ossh_fmt) }, \ - .cert_ossh_fmt = { .fmt = fmt_prefix ## _cert_ossh_fmt, \ - .len = lenof(fmt_prefix ## _cert_ossh_fmt) }, \ - .cert_key_ssh_id = ssh_key_id_prefix "-cert-v01@openssh.com", \ - .base_key_ssh_id = ssh_key_id_prefix, \ - }; \ - \ - const ssh_keyalg opensshcert_##name = { \ - .new_pub = opensshcert_new_pub, \ - .new_priv = opensshcert_new_priv, \ - .new_priv_openssh = opensshcert_new_priv_openssh, \ - .freekey = opensshcert_freekey, \ - .invalid = opensshcert_invalid, \ - .sign = opensshcert_sign, \ - .verify = opensshcert_verify, \ - .public_blob = opensshcert_public_blob, \ - .private_blob = opensshcert_private_blob, \ - .openssh_blob = opensshcert_openssh_blob, \ - .has_private = opensshcert_has_private, \ - .cache_str = opensshcert_cache_str, \ - .components = opensshcert_components, \ - .base_key = opensshcert_base_key, \ - .ca_public_blob = opensshcert_ca_public_blob, \ - .check_cert = opensshcert_check_cert, \ - .cert_id_string = opensshcert_cert_id_string, \ - .cert_info = opensshcert_cert_info, \ - .pubkey_bits = opensshcert_pubkey_bits, \ - .supported_flags = opensshcert_supported_flags, \ - .alternate_ssh_id = opensshcert_alternate_ssh_id, \ - .alg_desc = opensshcert_alg_desc, \ - .variable_size = opensshcert_variable_size, \ - .related_alg = opensshcert_related_alg, \ - .ssh_id = ssh_alg_id_prefix "-cert-v01@openssh.com", \ - .cache_id = "opensshcert-" ssh_key_id_prefix, \ - .extra = &opensshcert_##name##_extra, \ - .is_certificate = true, \ - .base_alg = &name, \ - }; -KEYALG_LIST(KEYALG_DEF) -#undef KEYALG_DEF - -#define KEYALG_LIST_ENTRY(name, algid, keyid, fmt) &opensshcert_##name, -static const ssh_keyalg *const opensshcert_all_keyalgs[] = { - KEYALG_LIST(KEYALG_LIST_ENTRY) -}; -#undef KEYALG_LIST_ENTRY - -static strbuf *get_base_public_blob(BinarySource *src, - const opensshcert_extra *extra) -{ - strbuf *basepub = strbuf_new(); - put_stringz(basepub, extra->base_key_ssh_id); - - /* Make the base public key blob out of the public key - * material in the certificate. This invocation of the - * blobtrans system doesn't do any format translation, but it - * does ensure that the right amount of data is copied so that - * src ends up in the right position to read the remaining - * certificate fields. */ - BLOBTRANS_DECLARE(bt); - blobtrans_read(bt, src, extra->pub_fmt); - blobtrans_write(bt, BinarySink_UPCAST(basepub), extra->pub_fmt); - blobtrans_clear(bt); - - return basepub; -} - -static opensshcert_key *opensshcert_new_shared( - const ssh_keyalg *self, ptrlen blob, strbuf **basepub_out) -{ - const opensshcert_extra *extra = self->extra; - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, blob); - - /* Check the initial key-type string */ - if (!ptrlen_eq_string(get_string(src), extra->cert_key_ssh_id)) - return NULL; - - opensshcert_key *ck = snew(opensshcert_key); - memset(ck, 0, sizeof(*ck)); - ck->sshk.vt = self; - - ck->nonce = strbuf_dup(get_string(src)); - strbuf *basepub = get_base_public_blob(src, extra); - ck->serial = get_uint64(src); - ck->type = get_uint32(src); - ck->key_id = strbuf_dup(get_string(src)); - ck->valid_principals = strbuf_dup(get_string(src)); - ck->valid_after = get_uint64(src); - ck->valid_before = get_uint64(src); - ck->critical_options = strbuf_dup(get_string(src)); - ck->extensions = strbuf_dup(get_string(src)); - ck->reserved = strbuf_dup(get_string(src)); - ck->signature_key = strbuf_dup(get_string(src)); - ck->signature = strbuf_dup(get_string(src)); - - - if (get_err(src)) { - ssh_key_free(&ck->sshk); - strbuf_free(basepub); - return NULL; - } - - *basepub_out = basepub; - return ck; -} - -static ssh_key *opensshcert_new_pub(const ssh_keyalg *self, ptrlen pub) -{ - strbuf *basepub; - opensshcert_key *ck = opensshcert_new_shared(self, pub, &basepub); - if (!ck) - return NULL; - - ck->basekey = ssh_key_new_pub(self->base_alg, ptrlen_from_strbuf(basepub)); - strbuf_free(basepub); - - if (!ck->basekey) { - ssh_key_free(&ck->sshk); - return NULL; - } - - return &ck->sshk; -} - -static ssh_key *opensshcert_new_priv( - const ssh_keyalg *self, ptrlen pub, ptrlen priv) -{ - strbuf *basepub; - opensshcert_key *ck = opensshcert_new_shared(self, pub, &basepub); - if (!ck) - return NULL; - - ck->basekey = ssh_key_new_priv(self->base_alg, - ptrlen_from_strbuf(basepub), priv); - strbuf_free(basepub); - - if (!ck->basekey) { - ssh_key_free(&ck->sshk); - return NULL; - } - - return &ck->sshk; -} - -static ssh_key *opensshcert_new_priv_openssh( - const ssh_keyalg *self, BinarySource *src) -{ - const opensshcert_extra *extra = self->extra; - - ptrlen cert = get_string(src); - - strbuf *basepub; - opensshcert_key *ck = opensshcert_new_shared(self, cert, &basepub); - if (!ck) - return NULL; - - strbuf *baseossh = strbuf_new(); - - /* Make the base OpenSSH key blob out of the public key blob - * returned from opensshcert_new_shared, and the trailing - * private data following the certificate */ - BLOBTRANS_DECLARE(bt); - - BinarySource pubsrc[1]; - BinarySource_BARE_INIT_PL(pubsrc, ptrlen_from_strbuf(basepub)); - get_string(pubsrc); /* skip key type id */ - - /* blobtrans_read might fail in this case, because we're reading - * from two sources and they might fail to match */ - bool success = blobtrans_read(bt, pubsrc, extra->pub_fmt) && - blobtrans_read(bt, src, extra->cert_ossh_fmt); - - blobtrans_write(bt, BinarySink_UPCAST(baseossh), extra->base_ossh_fmt); - blobtrans_clear(bt); - - if (!success) { - ssh_key_free(&ck->sshk); - strbuf_free(basepub); - strbuf_free(baseossh); - return NULL; - } - - strbuf_free(basepub); - - BinarySource osshsrc[1]; - BinarySource_BARE_INIT_PL(osshsrc, ptrlen_from_strbuf(baseossh)); - ck->basekey = ssh_key_new_priv_openssh(self->base_alg, osshsrc); - strbuf_free(baseossh); - - if (!ck->basekey) { - ssh_key_free(&ck->sshk); - return NULL; - } - - return &ck->sshk; -} - -static void opensshcert_freekey(ssh_key *key) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - - /* If this function is called from one of the above constructors - * because it failed part way through, we might not have managed - * to construct ck->basekey, so it might be NULL. */ - if (ck->basekey) - ssh_key_free(ck->basekey); - - strbuf_free(ck->nonce); - strbuf_free(ck->key_id); - strbuf_free(ck->valid_principals); - strbuf_free(ck->critical_options); - strbuf_free(ck->extensions); - strbuf_free(ck->reserved); - strbuf_free(ck->signature_key); - strbuf_free(ck->signature); - - sfree(ck); -} - -static ssh_key *opensshcert_base_key(ssh_key *key) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - return ck->basekey; -} - -/* - * Make a public key object from the CA public blob, potentially - * taking into account that the signature might override the algorithm - * name - */ -static ssh_key *opensshcert_ca_pub_key( - opensshcert_key *ck, ptrlen sig, ptrlen *algname) -{ - ptrlen ca_keyblob = ptrlen_from_strbuf(ck->signature_key); - - ptrlen alg_source = sig.ptr ? sig : ca_keyblob; - if (algname) - *algname = pubkey_blob_to_alg_name(alg_source); - - const ssh_keyalg *ca_alg = pubkey_blob_to_alg(alg_source); - if (!ca_alg) - return NULL; /* don't even recognise the certifying key type */ - - return ssh_key_new_pub(ca_alg, ca_keyblob); -} - -static void opensshcert_signature_preimage(opensshcert_key *ck, BinarySink *bs) -{ - const opensshcert_extra *extra = ck->sshk.vt->extra; - put_stringz(bs, extra->cert_key_ssh_id); - put_stringpl(bs, ptrlen_from_strbuf(ck->nonce)); - - strbuf *basepub = strbuf_new(); - ssh_key_public_blob(ck->basekey, BinarySink_UPCAST(basepub)); - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(basepub)); - get_string(src); /* skip initial key type string */ - put_data(bs, get_ptr(src), get_avail(src)); - strbuf_free(basepub); - - put_uint64(bs, ck->serial); - put_uint32(bs, ck->type); - put_stringpl(bs, ptrlen_from_strbuf(ck->key_id)); - put_stringpl(bs, ptrlen_from_strbuf(ck->valid_principals)); - put_uint64(bs, ck->valid_after); - put_uint64(bs, ck->valid_before); - put_stringpl(bs, ptrlen_from_strbuf(ck->critical_options)); - put_stringpl(bs, ptrlen_from_strbuf(ck->extensions)); - put_stringpl(bs, ptrlen_from_strbuf(ck->reserved)); - put_stringpl(bs, ptrlen_from_strbuf(ck->signature_key)); -} - -static void opensshcert_public_blob(ssh_key *key, BinarySink *bs) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - - opensshcert_signature_preimage(ck, bs); - put_stringpl(bs, ptrlen_from_strbuf(ck->signature)); -} - -static void opensshcert_private_blob(ssh_key *key, BinarySink *bs) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - ssh_key_private_blob(ck->basekey, bs); -} - -static void opensshcert_openssh_blob(ssh_key *key, BinarySink *bs) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - const opensshcert_extra *extra = key->vt->extra; - - strbuf *cert = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(cert)); - put_stringsb(bs, cert); - - strbuf *baseossh = strbuf_new_nm(); - ssh_key_openssh_blob(ck->basekey, BinarySink_UPCAST(baseossh)); - BinarySource basesrc[1]; - BinarySource_BARE_INIT_PL(basesrc, ptrlen_from_strbuf(baseossh)); - - BLOBTRANS_DECLARE(bt); - blobtrans_read(bt, basesrc, extra->base_ossh_fmt); - blobtrans_write(bt, bs, extra->cert_ossh_fmt); - blobtrans_clear(bt); - - strbuf_free(baseossh); -} - -static void opensshcert_ca_public_blob(ssh_key *key, BinarySink *bs) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - put_datapl(bs, ptrlen_from_strbuf(ck->signature_key)); -} - -static void opensshcert_cert_id_string(ssh_key *key, BinarySink *bs) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - put_datapl(bs, ptrlen_from_strbuf(ck->key_id)); -} - -static bool opensshcert_has_private(ssh_key *key) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - return ssh_key_has_private(ck->basekey); -} - -static char *opensshcert_cache_str(ssh_key *key) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - return ssh_key_cache_str(ck->basekey); -} - -static void opensshcert_time_to_iso8601(BinarySink *bs, uint64_t time) -{ - time_t t = time; - char buf[256]; - put_data(bs, buf, strftime(buf, sizeof(buf), - "%Y-%m-%d %H:%M:%S UTC", gmtime(&t))); -} - -static void opensshcert_string_list_key_components( - key_components *kc, strbuf *input, const char *title, const char *title2) -{ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(input)); - - const char *titles[2] = { title, title2 }; - size_t ntitles = (title2 ? 2 : 1); - - unsigned index = 0; - while (get_avail(src)) { - for (size_t ti = 0; ti < ntitles; ti++) { - ptrlen value = get_string(src); - if (get_err(src)) - break; - char *name = dupprintf("%s_%u", titles[ti], index); - key_components_add_text_pl(kc, name, value); - sfree(name); - } - index++; - } -} - -static key_components *opensshcert_components(ssh_key *key) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - key_components *kc = ssh_key_components(ck->basekey); - key_components_add_binary(kc, "cert_nonce", ptrlen_from_strbuf(ck->nonce)); - key_components_add_uint(kc, "cert_serial", ck->serial); - switch (ck->type) { - case SSH_CERT_TYPE_HOST: - key_components_add_text(kc, "cert_type", "host"); - break; - case SSH_CERT_TYPE_USER: - key_components_add_text(kc, "cert_type", "user"); - break; - default: - key_components_add_uint(kc, "cert_type", ck->type); - break; - } - key_components_add_text(kc, "cert_key_id", ck->key_id->s); - opensshcert_string_list_key_components(kc, ck->valid_principals, - "cert_valid_principal", NULL); - key_components_add_uint(kc, "cert_valid_after", ck->valid_after); - key_components_add_uint(kc, "cert_valid_before", ck->valid_before); - /* Translate the validity period into human-legible dates, but - * only if they're not the min/max integer. Rationale: if you see - * "584554051223-11-09 07:00:15 UTC" as the expiry time you'll be - * as likely to think it's a weird buffer overflow as half a - * trillion years in the future! */ - if (ck->valid_after != 0) { - strbuf *date = strbuf_new(); - opensshcert_time_to_iso8601(BinarySink_UPCAST(date), ck->valid_after); - key_components_add_text_pl(kc, "cert_valid_after_date", - ptrlen_from_strbuf(date)); - strbuf_free(date); - } - if (ck->valid_before != 0xFFFFFFFFFFFFFFFF) { - strbuf *date = strbuf_new(); - opensshcert_time_to_iso8601(BinarySink_UPCAST(date), ck->valid_before); - key_components_add_text_pl(kc, "cert_valid_before_date", - ptrlen_from_strbuf(date)); - strbuf_free(date); - } - opensshcert_string_list_key_components(kc, ck->critical_options, - "cert_critical_option", - "cert_critical_option_data"); - opensshcert_string_list_key_components(kc, ck->extensions, - "cert_extension", - "cert_extension_data"); - key_components_add_binary(kc, "cert_ca_key", ptrlen_from_strbuf( - ck->signature_key)); - - ptrlen ca_algname; - ssh_key *ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0), - &ca_algname); - key_components_add_text_pl(kc, "cert_ca_key_algorithm_id", ca_algname); - - if (ca_key) { - key_components *kc_ca_key = ssh_key_components(ca_key); - for (size_t i = 0; i < kc_ca_key->ncomponents; i++) { - key_component *comp = &kc_ca_key->components[i]; - char *subname = dupcat("cert_ca_key_", comp->name); - key_components_add_copy(kc, subname, comp); - sfree(subname); - } - key_components_free(kc_ca_key); - ssh_key_free(ca_key); - } - - key_components_add_binary(kc, "cert_ca_sig", ptrlen_from_strbuf( - ck->signature)); - return kc; -} - -static SeatDialogText *opensshcert_cert_info(ssh_key *key) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - SeatDialogText *text = seat_dialog_text_new(); - strbuf *tmp = strbuf_new(); - - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Certificate type"); - switch (ck->type) { - case SSH_CERT_TYPE_HOST: - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, - "host key"); - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Valid host names"); - break; - case SSH_CERT_TYPE_USER: - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, - "user authentication key"); - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Valid user names"); - break; - default: - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, - "unknown type %" PRIu32, ck->type); - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Valid principals"); - break; - } - - { - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( - ck->valid_principals)); - const char *sep = ""; - strbuf_clear(tmp); - while (get_avail(src)) { - ptrlen principal = get_string(src); - if (get_err(src)) - break; - put_dataz(tmp, sep); - sep = ","; - put_datapl(tmp, principal); - } - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, - "%s", tmp->s); - } - - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Validity period"); - strbuf_clear(tmp); - if (ck->valid_after == 0) { - if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) { - put_dataz(tmp, "forever"); - } else { - put_dataz(tmp, "until "); - opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp), - ck->valid_before); - } - } else { - if (ck->valid_before == 0xFFFFFFFFFFFFFFFF) { - put_dataz(tmp, "after "); - opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp), - ck->valid_after); - } else { - opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp), - ck->valid_after); - put_dataz(tmp, " - "); - opensshcert_time_to_iso8601(BinarySink_UPCAST(tmp), - ck->valid_before); - } - } - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", tmp->s); - - /* - * List critical options we know about. (This is everything listed - * in PROTOCOL.certkeys that isn't specific to U2F/FIDO key types - * that PuTTY doesn't currently support.) - */ - { - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( - ck->critical_options)); - strbuf_clear(tmp); - while (get_avail(src)) { - ptrlen key = get_string(src); - ptrlen value = get_string(src); - if (get_err(src)) - break; - if (ck->type == SSH_CERT_TYPE_USER && - ptrlen_eq_string(key, "source-address")) { - BinarySource src2[1]; - BinarySource_BARE_INIT_PL(src2, value); - ptrlen addresslist = get_string(src2); - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Permitted client IP addresses"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, - "%.*s", PTRLEN_PRINTF(addresslist)); - } else if (ck->type == SSH_CERT_TYPE_USER && - ptrlen_eq_string(key, "force-command")) { - BinarySource src2[1]; - BinarySource_BARE_INIT_PL(src2, value); - ptrlen command = get_string(src2); - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Forced remote command"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, - "%.*s", PTRLEN_PRINTF(command)); - } - } - } - - /* - * List certificate extensions. Again, we go through everything in - * PROTOCOL.certkeys that isn't specific to U2F/FIDO key types. - * But we also flip the sense round for user-readability: I think - * it's more likely that the typical key will permit all these - * things, so we emit no output in that case, and only mention the - * things that _aren't_ enabled. - */ - - bool x11_ok = false, agent_ok = false, portfwd_ok = false; - bool pty_ok = false, user_rc_ok = false; - - { - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( - ck->extensions)); - while (get_avail(src)) { - ptrlen key = get_string(src); - /* ptrlen value = */ get_string(src); // nothing needs this yet - if (get_err(src)) - break; - if (ptrlen_eq_string(key, "permit-X11-forwarding")) { - x11_ok = true; - } else if (ptrlen_eq_string(key, "permit-agent-forwarding")) { - agent_ok = true; - } else if (ptrlen_eq_string(key, "permit-port-forwarding")) { - portfwd_ok = true; - } else if (ptrlen_eq_string(key, "permit-pty")) { - pty_ok = true; - } else if (ptrlen_eq_string(key, "permit-user-rc")) { - user_rc_ok = true; - } - } - } - - if (ck->type == SSH_CERT_TYPE_USER) { - if (!x11_ok) { - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "X11 forwarding permitted"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); - } - if (!agent_ok) { - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Agent forwarding permitted"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); - } - if (!portfwd_ok) { - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Port forwarding permitted"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); - } - if (!pty_ok) { - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "PTY allocation permitted"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); - } - if (!user_rc_ok) { - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Running user ~/.ssh.rc permitted"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "no"); - } - } - - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Certificate ID string"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, - "%s", ck->key_id->s); - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Certificate serial number"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, - "%" PRIu64, ck->serial); - - char *fp = ssh2_fingerprint_blob(ptrlen_from_strbuf(ck->signature_key), - SSH_FPTYPE_DEFAULT); - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Fingerprint of signing CA key"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp); - sfree(fp); - - fp = ssh2_fingerprint(key, ssh_fptype_to_cert(SSH_FPTYPE_DEFAULT)); - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Fingerprint including certificate"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", fp); - sfree(fp); - - strbuf_free(tmp); - return text; -} - -static int opensshcert_pubkey_bits(const ssh_keyalg *self, ptrlen blob) -{ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, blob); - - get_string(src); /* key type */ - get_string(src); /* nonce */ - strbuf *basepub = get_base_public_blob(src, self->extra); - int bits = ssh_key_public_bits( - self->base_alg, ptrlen_from_strbuf(basepub)); - strbuf_free(basepub); - return bits; -} - -static unsigned opensshcert_supported_flags(const ssh_keyalg *self) -{ - return ssh_keyalg_supported_flags(self->base_alg); -} - -static const char *opensshcert_alternate_ssh_id(const ssh_keyalg *self, - unsigned flags) -{ - const char *base_id = ssh_keyalg_alternate_ssh_id(self->base_alg, flags); - - for (size_t i = 0; i < lenof(opensshcert_all_keyalgs); i++) { - const ssh_keyalg *alg_i = opensshcert_all_keyalgs[i]; - if (!strcmp(base_id, alg_i->base_alg->ssh_id)) - return alg_i->ssh_id; - } - - return self->ssh_id; -} - -static char *opensshcert_alg_desc(const ssh_keyalg *self) -{ - char *base_desc = ssh_keyalg_desc(self->base_alg); - char *our_desc = dupcat(base_desc, " cert"); - sfree(base_desc); - return our_desc; -} - -static bool opensshcert_variable_size(const ssh_keyalg *self) -{ - return ssh_keyalg_variable_size(self->base_alg); -} - -static const ssh_keyalg *opensshcert_related_alg(const ssh_keyalg *self, - const ssh_keyalg *base) -{ - for (size_t i = 0; i < lenof(opensshcert_all_keyalgs); i++) { - const ssh_keyalg *alg_i = opensshcert_all_keyalgs[i]; - if (base == alg_i->base_alg) - return alg_i; - } - - return self; -} - -static char *opensshcert_invalid(ssh_key *key, unsigned flags) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - return ssh_key_invalid(ck->basekey, flags); -} - -static bool opensshcert_check_cert( - ssh_key *key, bool host, ptrlen principal, uint64_t time, - const ca_options *opts, BinarySink *error) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - bool result = false; - ssh_key *ca_key = NULL; - strbuf *preimage = strbuf_new(); - BinarySource src[1]; - - ptrlen signature = ptrlen_from_strbuf(ck->signature); - - /* - * The OpenSSH certificate spec is one-layer only: it explicitly - * forbids using a certified key in turn as the CA. - * - * If it did not, then we'd also have to recursively verify - * everything up the CA chain until we reached the ultimate root, - * and then make sure _that_ was something we trusted. (Not to - * mention that there'd probably be an additional SSH_CERT_TYPE_CA - * or some such, and certificate options saying what kinds of - * certificate a CA was trusted to sign for, and ...) - */ - ca_key = opensshcert_ca_pub_key(ck, make_ptrlen(NULL, 0), NULL); - if (!ca_key) { - put_fmt(error, "Certificate's signing key is invalid"); - goto out; - } - if (ssh_key_alg(ca_key)->is_certificate) { - put_fmt(error, "Certificate is signed with a certified key " - "(forbidden by OpenSSH certificate specification)"); - goto out; - } - - /* - * Now re-instantiate the key in a way that matches the signature - * (i.e. so that if the key is an RSA one we get the right subtype - * of RSA). - */ - ssh_key_free(ca_key); - ca_key = opensshcert_ca_pub_key(ck, signature, NULL); - if (!ca_key) { - put_fmt(error, "Certificate's signing key does not match " - "signature type"); - goto out; - } - - /* Check which signature algorithm is actually in use, because - * that might be a reason to reject the certificate (e.g. ssh-rsa - * when we wanted rsa-sha2-*). */ - const ssh_keyalg *sig_alg = ssh_key_alg(ca_key); - if ((sig_alg == &ssh_rsa && !opts->permit_rsa_sha1) || - (sig_alg == &ssh_rsa_sha256 && !opts->permit_rsa_sha256) || - (sig_alg == &ssh_rsa_sha512 && !opts->permit_rsa_sha512)) { - put_fmt(error, "Certificate signature uses '%s' signature type " - "(forbidden by user configuration)", sig_alg->ssh_id); - goto out; - } - - opensshcert_signature_preimage(ck, BinarySink_UPCAST(preimage)); - - if (!ssh_key_verify(ca_key, signature, ptrlen_from_strbuf(preimage))) { - put_fmt(error, "Certificate's signature is invalid"); - goto out; - } - - uint32_t expected_type = host ? SSH_CERT_TYPE_HOST : SSH_CERT_TYPE_USER; - if (ck->type != expected_type) { - put_fmt(error, "Certificate type is "); - switch (ck->type) { - case SSH_CERT_TYPE_HOST: - put_fmt(error, "host"); - break; - case SSH_CERT_TYPE_USER: - put_fmt(error, "user"); - break; - default: - put_fmt(error, "unknown value %" PRIu32, ck->type); - break; - } - put_fmt(error, "; expected %s", host ? "host" : "user"); - goto out; - } - - /* - * Check the time bounds on the certificate. - */ - if (time < ck->valid_after) { - put_fmt(error, "Certificate is not valid until "); - opensshcert_time_to_iso8601(BinarySink_UPCAST(error), - ck->valid_after); - goto out; - } - if (time >= ck->valid_before) { - put_fmt(error, "Certificate expired at "); - opensshcert_time_to_iso8601(BinarySink_UPCAST(error), - ck->valid_before); - goto out; - } - - /* - * Check that this certificate is for the right thing. - * - * If valid_principals is a zero-length string then this is - * specified to be a carte-blanche certificate valid for any - * principal (at least, provided you trust the CA that issued it). - */ - if (ck->valid_principals->len != 0) { - BinarySource_BARE_INIT_PL( - src, ptrlen_from_strbuf(ck->valid_principals)); - - while (get_avail(src)) { - ptrlen valid_principal = get_string(src); - if (get_err(src)) { - put_fmt(error, "Certificate's valid principals list is " - "incorrectly formatted"); - goto out; - } - if (ptrlen_eq_ptrlen(valid_principal, principal)) - goto principal_ok; - } - - /* - * No valid principal matched. Now go through the list a - * second time writing the cert contents into the error - * message, so that the user can see at a glance what went - * wrong. - * - * (If you've typed the wrong spelling of the host name, you - * really need to see "This cert is for 'foo.example.com' and - * I was trying to match it against 'foo'", rather than just - * "Computer says no".) - */ - put_fmt(error, "Certificate's %s list [", - host ? "hostname" : "username"); - BinarySource_BARE_INIT_PL( - src, ptrlen_from_strbuf(ck->valid_principals)); - const char *sep = ""; - while (get_avail(src)) { - ptrlen valid_principal = get_string(src); - put_fmt(error, "%s\"", sep); - put_c_string_literal(error, valid_principal); - put_fmt(error, "\""); - sep = ", "; - } - put_fmt(error, "] does not contain expected %s \"", - host ? "hostname" : "username"); - put_c_string_literal(error, principal); - put_fmt(error, "\""); - goto out; - principal_ok:; - } - - /* - * Check for critical options. - */ - { - BinarySource_BARE_INIT_PL( - src, ptrlen_from_strbuf(ck->critical_options)); - - while (get_avail(src)) { - ptrlen option = get_string(src); - ptrlen data = get_string(src); - if (get_err(src)) { - put_fmt(error, "Certificate's critical options list is " - "incorrectly formatted"); - goto out; - } - - /* - * If we ever do support any options, this will be where - * we insert code to recognise and validate them. - * - * At present, we implement no critical options at all. - * (For host certs, as of 2022-04-20, OpenSSH hasn't - * defined any. For user certs, the only SSH server using - * this is Uppity, which doesn't support key restrictions - * in general.) - */ - (void)data; /* no options supported => no use made of the data */ - - /* - * Report an unrecognised literal. - */ - put_fmt(error, "Certificate specifies an unsupported critical " - "option \""); - put_c_string_literal(error, option); - put_fmt(error, "\""); - goto out; - } - } - - /* If we get here without failing any check, accept the certificate! */ - result = true; - - out: - if (ca_key) - ssh_key_free(ca_key); - strbuf_free(preimage); - return result; -} - -static bool opensshcert_verify(ssh_key *key, ptrlen sig, ptrlen data) -{ - /* This method is pure *signature* verification; checking the - * certificate is done elsewhere. */ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - return ssh_key_verify(ck->basekey, sig, data); -} - -static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags, - BinarySink *bs) -{ - opensshcert_key *ck = container_of(key, opensshcert_key, sshk); - ssh_key_sign(ck->basekey, data, flags, bs); -} diff --git a/crypto/prng.c b/crypto/prng.c deleted file mode 100644 index 247d1dcff..000000000 --- a/crypto/prng.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * PuTTY's cryptographic pseudorandom number generator. - * - * This module just defines the PRNG object type and its methods. The - * usual global instance of it is managed by sshrand.c. - */ - -#include "putty.h" -#include "ssh.h" -#include "mpint_i.h" - -#ifdef PRNG_DIAGNOSTICS -#define prngdebug debug -#else -#define prngdebug(...) ((void)0) -#endif - -/* - * This random number generator is based on the 'Fortuna' design by - * Niels Ferguson and Bruce Schneier. The biggest difference is that I - * use SHA-256 in place of a block cipher: the generator side of the - * system works by computing HASH(key || counter) instead of - * ENCRYPT(counter, key). - * - * Rationale: the Fortuna description itself suggests that using - * SHA-256 would be nice but people wouldn't accept it because it's - * too slow - but PuTTY isn't a heavy enough user of random numbers to - * make that a serious worry. In fact even with SHA-256 this generator - * is faster than the one we previously used. Also the Fortuna - * description worries about periodic rekeying to avoid the barely - * detectable pattern of never repeating a cipher block - but with - * SHA-256, even that shouldn't be a worry, because the output - * 'blocks' are twice the size, and also SHA-256 has no guarantee of - * bijectivity, so it surely _could_ be possible to generate the same - * block from two counter values. Thirdly, Fortuna has to have a hash - * function anyway, for reseeding and entropy collection, so reusing - * the same one means it only depends on one underlying primitive and - * can be easily reinstantiated with a larger hash function if you - * decide you'd like to do that on a particular occasion. - */ - -#define NCOLLECTORS 32 -#define RESEED_DATA_SIZE 64 - -typedef struct prng_impl prng_impl; -struct prng_impl { - prng Prng; - - const ssh_hashalg *hashalg; - - /* - * Generation side: - * - * 'generator' is a hash object with the current key preloaded - * into it. The counter-mode generation is achieved by copying - * that hash object, appending the counter value to the copy, and - * calling ssh_hash_final. - */ - ssh_hash *generator; - BignumInt counter[128 / BIGNUM_INT_BITS]; - - /* - * When re-seeding the generator, you call prng_seed_begin(), - * which sets up a hash object in 'keymaker'. You write your new - * seed data into it (which you can do by calling put_data on the - * PRNG object itself) and then call prng_seed_finish(), which - * finalises this hash and uses the output to set up the new - * generator. - * - * The keymaker hash preimage includes the previous key, so if you - * just want to change keys for the sake of not keeping the same - * one for too long, you don't have to put any extra seed data in - * at all. - */ - ssh_hash *keymaker; - - /* - * Collection side: - * - * There are NCOLLECTORS hash objects collecting entropy. Each - * separately numbered entropy source puts its output into those - * hash objects in the order 0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,..., - * that is to say, each entropy source has a separate counter - * which is incremented every time that source generates an event, - * and the event data is added to the collector corresponding to - * the index of the lowest set bit in the current counter value. - * - * Whenever collector #0 has at least RESEED_DATA_SIZE bytes (and - * it's not at least 100ms since the last reseed), the PRNG is - * reseeded, with seed data on reseed #n taken from the first j - * collectors, where j is one more than the number of factors of 2 - * in n. That is, collector #0 is used in every reseed; #1 in - * every other one, #2 in every fourth, etc. - * - * 'until_reseed' counts the amount of data that still needs to be - * added to collector #0 before a reseed will be triggered. - */ - uint32_t source_counters[NOISE_MAX_SOURCES]; - ssh_hash *collectors[NCOLLECTORS]; - size_t until_reseed; - uint32_t reseeds; - uint64_t last_reseed_time; -}; - -static void prng_seed_BinarySink_write( - BinarySink *bs, const void *data, size_t len); - -prng *prng_new(const ssh_hashalg *hashalg) -{ - prng_impl *pi = snew(prng_impl); - - memset(pi, 0, sizeof(prng_impl)); - pi->hashalg = hashalg; - pi->keymaker = NULL; - pi->generator = NULL; - memset(pi->counter, 0, sizeof(pi->counter)); - for (size_t i = 0; i < NCOLLECTORS; i++) - pi->collectors[i] = ssh_hash_new(pi->hashalg); - pi->until_reseed = 0; - BinarySink_INIT(&pi->Prng, prng_seed_BinarySink_write); - - pi->Prng.savesize = pi->hashalg->hlen * 4; - - return &pi->Prng; -} - -void prng_free(prng *pr) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - - smemclr(pi->counter, sizeof(pi->counter)); - for (size_t i = 0; i < NCOLLECTORS; i++) - ssh_hash_free(pi->collectors[i]); - if (pi->generator) - ssh_hash_free(pi->generator); - if (pi->keymaker) - ssh_hash_free(pi->keymaker); - smemclr(pi, sizeof(*pi)); - sfree(pi); -} - -void prng_seed_begin(prng *pr) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - - assert(!pi->keymaker); - - prngdebug("prng: reseed begin\n"); - - /* - * Make a hash instance that will generate the key for the new one. - */ - if (pi->generator) { - pi->keymaker = pi->generator; - pi->generator = NULL; - } else { - pi->keymaker = ssh_hash_new(pi->hashalg); - } - - put_byte(pi->keymaker, 'R'); -} - -static void prng_seed_BinarySink_write( - BinarySink *bs, const void *data, size_t len) -{ - prng *pr = BinarySink_DOWNCAST(bs, prng); - prng_impl *pi = container_of(pr, prng_impl, Prng); - assert(pi->keymaker); - prngdebug("prng: got %"SIZEu" bytes of seed\n", len); - put_data(pi->keymaker, data, len); -} - -void prng_seed_finish(prng *pr) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - unsigned char buf[MAX_HASH_LEN]; - - assert(pi->keymaker); - - prngdebug("prng: reseed finish\n"); - - /* - * Actually generate the key. - */ - ssh_hash_final(pi->keymaker, buf); - pi->keymaker = NULL; - - /* - * Load that key into a fresh hash instance, which will become the - * new generator. - */ - assert(!pi->generator); - pi->generator = ssh_hash_new(pi->hashalg); - put_data(pi->generator, buf, pi->hashalg->hlen); - - pi->until_reseed = RESEED_DATA_SIZE; - pi->last_reseed_time = prng_reseed_time_ms(); - - smemclr(buf, sizeof(buf)); -} - -static inline void prng_generate(prng_impl *pi, void *outbuf) -{ - ssh_hash *h = ssh_hash_copy(pi->generator); - - prngdebug("prng_generate\n"); - put_byte(h, 'G'); - for (unsigned i = 0; i < 128; i += 8) - put_byte(h, pi->counter[i/BIGNUM_INT_BITS] >> (i%BIGNUM_INT_BITS)); - BignumCarry c = 1; - for (unsigned i = 0; i < lenof(pi->counter); i++) - BignumADC(pi->counter[i], c, pi->counter[i], 0, c); - ssh_hash_final(h, outbuf); -} - -void prng_read(prng *pr, void *vout, size_t size) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - unsigned char buf[MAX_HASH_LEN]; - - assert(!pi->keymaker); - - prngdebug("prng_read %"SIZEu"\n", size); - - uint8_t *out = (uint8_t *)vout; - while (size > 0) { - prng_generate(pi, buf); - size_t to_use = size > pi->hashalg->hlen ? pi->hashalg->hlen : size; - memcpy(out, buf, to_use); - out += to_use; - size -= to_use; - } - - smemclr(buf, sizeof(buf)); - - prng_seed_begin(&pi->Prng); - prng_seed_finish(&pi->Prng); -} - -void prng_add_entropy(prng *pr, unsigned source_id, ptrlen data) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - - assert(source_id < NOISE_MAX_SOURCES); - uint32_t counter = ++pi->source_counters[source_id]; - - size_t index = 0; - while (index+1 < NCOLLECTORS && !(counter & 1)) { - counter >>= 1; - index++; - } - - prngdebug("prng_add_entropy source=%u size=%"SIZEu" -> collector %zi\n", - source_id, data.len, index); - - put_datapl(pi->collectors[index], data); - - if (index == 0) - pi->until_reseed = (pi->until_reseed < data.len ? 0 : - pi->until_reseed - data.len); - - if (pi->until_reseed == 0 && - prng_reseed_time_ms() - pi->last_reseed_time >= 100) { - prng_seed_begin(&pi->Prng); - - unsigned char buf[MAX_HASH_LEN]; - uint32_t reseed_index = ++pi->reseeds; - prngdebug("prng entropy reseed #%"PRIu32"\n", reseed_index); - for (size_t i = 0; i < NCOLLECTORS; i++) { - prngdebug("emptying collector %"SIZEu"\n", i); - ssh_hash_digest(pi->collectors[i], buf); - put_data(&pi->Prng, buf, pi->hashalg->hlen); - ssh_hash_reset(pi->collectors[i]); - if (reseed_index & 1) - break; - reseed_index >>= 1; - } - smemclr(buf, sizeof(buf)); - prng_seed_finish(&pi->Prng); - } -} - -size_t prng_seed_bits(prng *pr) -{ - prng_impl *pi = container_of(pr, prng_impl, Prng); - return pi->hashalg->hlen * 8; -} diff --git a/crypto/pubkey-pem.c b/crypto/pubkey-pem.c deleted file mode 100644 index 3eaa16aad..000000000 --- a/crypto/pubkey-pem.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Convenience functions to encrypt and decrypt OpenSSH PEM format for - * SSH-2 private key files. This uses triple-DES in SSH-2 style (one - * CBC layer), with three distinct keys, and an IV also generated from - * the passphrase. - */ - -#include "ssh.h" - -static ssh_cipher *des3_pubkey_ossh_cipher(const void *vkey, const void *viv) -{ - ssh_cipher *c = ssh_cipher_new(&ssh_3des_ssh2); - ssh_cipher_setkey(c, vkey); - ssh_cipher_setiv(c, viv); - return c; -} - -void des3_decrypt_pubkey_ossh(const void *vkey, const void *viv, - void *vblk, int len) -{ - ssh_cipher *c = des3_pubkey_ossh_cipher(vkey, viv); - ssh_cipher_decrypt(c, vblk, len); - ssh_cipher_free(c); -} - -void des3_encrypt_pubkey_ossh(const void *vkey, const void *viv, - void *vblk, int len) -{ - ssh_cipher *c = des3_pubkey_ossh_cipher(vkey, viv); - ssh_cipher_encrypt(c, vblk, len); - ssh_cipher_free(c); -} diff --git a/crypto/pubkey-ppk.c b/crypto/pubkey-ppk.c deleted file mode 100644 index 7ffb570a1..000000000 --- a/crypto/pubkey-ppk.c +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Convenience functions to encrypt and decrypt PuTTY's own .PPK - * format for SSH-2 private key files, which uses 256-bit AES in CBC - * mode. - */ - -#include "ssh.h" - -static ssh_cipher *aes256_pubkey_cipher(const void *key, const void *iv) -{ - ssh_cipher *cipher = ssh_cipher_new(&ssh_aes256_cbc); - ssh_cipher_setkey(cipher, key); - ssh_cipher_setiv(cipher, iv); - return cipher; -} - -void aes256_encrypt_pubkey(const void *key, const void *iv, void *blk, int len) -{ - ssh_cipher *c = aes256_pubkey_cipher(key, iv); - ssh_cipher_encrypt(c, blk, len); - ssh_cipher_free(c); -} - -void aes256_decrypt_pubkey(const void *key, const void *iv, void *blk, int len) -{ - ssh_cipher *c = aes256_pubkey_cipher(key, iv); - ssh_cipher_decrypt(c, blk, len); - ssh_cipher_free(c); -} diff --git a/crypto/pubkey-ssh1.c b/crypto/pubkey-ssh1.c deleted file mode 100644 index b3129e296..000000000 --- a/crypto/pubkey-ssh1.c +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Convenience functions to encrypt and decrypt the standard format - * for SSH-1 private key files. This uses triple-DES in SSH-1 style - * (three separate CBC layers), but the same key is used for the first - * and third layers.CBC mode. - */ - -#include "ssh.h" - -static ssh_cipher *des3_pubkey_cipher(const void *vkey) -{ - ssh_cipher *c = ssh_cipher_new(&ssh_3des_ssh1); - uint8_t keys3[24], iv[8]; - - memcpy(keys3, vkey, 16); - memcpy(keys3 + 16, vkey, 8); - ssh_cipher_setkey(c, keys3); - smemclr(keys3, sizeof(keys3)); - - memset(iv, 0, 8); - ssh_cipher_setiv(c, iv); - - return c; -} - -void des3_decrypt_pubkey(const void *vkey, void *vblk, int len) -{ - ssh_cipher *c = des3_pubkey_cipher(vkey); - ssh_cipher_decrypt(c, vblk, len); - ssh_cipher_free(c); -} - -void des3_encrypt_pubkey(const void *vkey, void *vblk, int len) -{ - ssh_cipher *c = des3_pubkey_cipher(vkey); - ssh_cipher_encrypt(c, vblk, len); - ssh_cipher_free(c); -} diff --git a/crypto/rsa.c b/crypto/rsa.c deleted file mode 100644 index b92fbeaf4..000000000 --- a/crypto/rsa.c +++ /dev/null @@ -1,1158 +0,0 @@ -/* - * RSA implementation for PuTTY. - */ - -#include -#include -#include -#include - -#include "ssh.h" -#include "mpint.h" -#include "misc.h" - -void BinarySource_get_rsa_ssh1_pub( - BinarySource *src, RSAKey *rsa, RsaSsh1Order order) -{ - unsigned bits; - mp_int *e, *m; - - bits = get_uint32(src); - if (order == RSA_SSH1_EXPONENT_FIRST) { - e = get_mp_ssh1(src); - m = get_mp_ssh1(src); - } else { - m = get_mp_ssh1(src); - e = get_mp_ssh1(src); - } - - if (rsa) { - rsa->bits = bits; - rsa->exponent = e; - rsa->modulus = m; - rsa->bytes = (mp_get_nbits(m) + 7) / 8; - } else { - mp_free(e); - mp_free(m); - } -} - -void BinarySource_get_rsa_ssh1_priv( - BinarySource *src, RSAKey *rsa) -{ - rsa->private_exponent = get_mp_ssh1(src); -} - -key_components *rsa_components(RSAKey *rsa) -{ - key_components *kc = key_components_new(); - key_components_add_text(kc, "key_type", "RSA"); - key_components_add_mp(kc, "public_modulus", rsa->modulus); - key_components_add_mp(kc, "public_exponent", rsa->exponent); - if (rsa->private_exponent) { - key_components_add_mp(kc, "private_exponent", rsa->private_exponent); - key_components_add_mp(kc, "private_p", rsa->p); - key_components_add_mp(kc, "private_q", rsa->q); - key_components_add_mp(kc, "private_inverse_q_mod_p", rsa->iqmp); - } - return kc; -} - -RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src) -{ - RSAKey *rsa = snew(RSAKey); - memset(rsa, 0, sizeof(RSAKey)); - - get_rsa_ssh1_pub(src, rsa, RSA_SSH1_MODULUS_FIRST); - get_rsa_ssh1_priv(src, rsa); - - /* SSH-1 names p and q the other way round, i.e. we have the - * inverse of p mod q and not of q mod p. We swap the names, - * because our internal RSA wants iqmp. */ - rsa->iqmp = get_mp_ssh1(src); - rsa->q = get_mp_ssh1(src); - rsa->p = get_mp_ssh1(src); - - return rsa; -} - -void duprsakey(RSAKey *dst, const RSAKey *src) -{ - dst->bits = src->bits; - dst->bytes = src->bytes; - dst->modulus = mp_copy(src->modulus); - dst->exponent = mp_copy(src->exponent); - dst->private_exponent = src->private_exponent ? - mp_copy(src->private_exponent) : NULL; - dst->p = mp_copy(src->p); - dst->q = mp_copy(src->q); - dst->iqmp = mp_copy(src->iqmp); - dst->comment = src->comment ? dupstr(src->comment) : NULL; - dst->sshk.vt = src->sshk.vt; -} - -bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key) -{ - mp_int *b1, *b2; - int i; - unsigned char *p; - - if (key->bytes < length + 4) - return false; /* RSA key too short! */ - - memmove(data + key->bytes - length, data, length); - data[0] = 0; - data[1] = 2; - - size_t npad = key->bytes - length - 3; - /* - * Generate a sequence of nonzero padding bytes. We do this in a - * reasonably uniform way and without having to loop round - * retrying the random number generation, by first generating an - * integer in [0,2^n) for an appropriately large n; then we - * repeatedly multiply by 255 to give an integer in [0,255*2^n), - * extract the top 8 bits to give an integer in [0,255), and mask - * those bits off before multiplying up again for the next digit. - * This gives us a sequence of numbers in [0,255), and of course - * adding 1 to each of them gives numbers in [1,256) as we wanted. - * - * (You could imagine this being a sort of fixed-point operation: - * given a uniformly random binary _fraction_, multiplying it by k - * and subtracting off the integer part will yield you a sequence - * of integers each in [0,k). I'm just doing that scaled up by a - * power of 2 to avoid the fractions.) - */ - size_t random_bits = (npad + 16) * 8; - mp_int *randval = mp_new(random_bits + 8); - mp_int *tmp = mp_random_bits(random_bits); - mp_copy_into(randval, tmp); - mp_free(tmp); - for (i = 2; i < key->bytes - length - 1; i++) { - mp_mul_integer_into(randval, randval, 255); - uint8_t byte = mp_get_byte(randval, random_bits / 8); - assert(byte != 255); - data[i] = byte + 1; - mp_reduce_mod_2to(randval, random_bits); - } - mp_free(randval); - data[key->bytes - length - 1] = 0; - - b1 = mp_from_bytes_be(make_ptrlen(data, key->bytes)); - - b2 = mp_modpow(b1, key->exponent, key->modulus); - - p = data; - for (i = key->bytes; i--;) { - *p++ = mp_get_byte(b2, i); - } - - mp_free(b1); - mp_free(b2); - - return true; -} - -/* - * 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. - */ -static mp_int *crt_modpow(mp_int *base, mp_int *exp, mp_int *mod, - mp_int *p, mp_int *q, mp_int *iqmp) -{ - mp_int *pm1, *qm1, *pexp, *qexp, *presult, *qresult; - mp_int *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 = mp_copy(p); - mp_sub_integer_into(pm1, pm1, 1); - qm1 = mp_copy(q); - mp_sub_integer_into(qm1, qm1, 1); - pexp = mp_mod(exp, pm1); - qexp = mp_mod(exp, qm1); - - /* - * Do the two modpows. - */ - mp_int *base_mod_p = mp_mod(base, p); - presult = mp_modpow(base_mod_p, pexp, p); - mp_free(base_mod_p); - mp_int *base_mod_q = mp_mod(base, q); - qresult = mp_modpow(base_mod_q, qexp, q); - mp_free(base_mod_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 presult-qresult < 0, we add p to it to keep it positive.) - */ - unsigned presult_too_small = mp_cmp_hs(qresult, presult); - mp_cond_add_into(presult, presult, p, presult_too_small); - - diff = mp_sub(presult, qresult); - multiplier = mp_mul(iqmp, q); - ret0 = mp_mul(multiplier, diff); - mp_add_into(ret0, ret0, qresult); - - /* - * Finally, reduce the result mod n. - */ - ret = mp_mod(ret0, mod); - - /* - * Free all the intermediate results before returning. - */ - mp_free(pm1); - mp_free(qm1); - mp_free(pexp); - mp_free(qexp); - mp_free(presult); - mp_free(qresult); - mp_free(diff); - mp_free(multiplier); - mp_free(ret0); - - return ret; -} - -/* - * Wrapper on crt_modpow that looks up all the right values from an - * RSAKey. - */ -static mp_int *rsa_privkey_op(mp_int *input, RSAKey *key) -{ - return crt_modpow(input, key->private_exponent, - key->modulus, key->p, key->q, key->iqmp); -} - -mp_int *rsa_ssh1_decrypt(mp_int *input, RSAKey *key) -{ - return rsa_privkey_op(input, key); -} - -bool rsa_ssh1_decrypt_pkcs1(mp_int *input, RSAKey *key, - strbuf *outbuf) -{ - strbuf *data = strbuf_new_nm(); - bool success = false; - BinarySource src[1]; - - { - mp_int *b = rsa_ssh1_decrypt(input, key); - for (size_t i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;) { - put_byte(data, mp_get_byte(b, i)); - } - mp_free(b); - } - - BinarySource_BARE_INIT(src, data->u, data->len); - - /* Check PKCS#1 formatting prefix */ - if (get_byte(src) != 0) goto out; - if (get_byte(src) != 2) goto out; - while (1) { - unsigned char byte = get_byte(src); - if (get_err(src)) goto out; - if (byte == 0) - break; - } - - /* Everything else is the payload */ - success = true; - put_data(outbuf, get_ptr(src), get_avail(src)); - - out: - strbuf_free(data); - return success; -} - -static void append_hex_to_strbuf(strbuf *sb, mp_int *x) -{ - if (sb->len > 0) - put_byte(sb, ','); - put_data(sb, "0x", 2); - char *hex = mp_get_hex(x); - size_t hexlen = strlen(hex); - put_data(sb, hex, hexlen); - smemclr(hex, hexlen); - sfree(hex); -} - -char *rsastr_fmt(RSAKey *key) -{ - strbuf *sb = strbuf_new(); - - append_hex_to_strbuf(sb, key->exponent); - append_hex_to_strbuf(sb, key->modulus); - - return strbuf_to_str(sb); -} - -/* - * Generate a fingerprint string for the key. Compatible with the - * OpenSSH fingerprint code. - */ -char *rsa_ssh1_fingerprint(RSAKey *key) -{ - unsigned char digest[16]; - strbuf *out; - int i; - - /* - * The hash preimage for SSH-1 key fingerprinting consists of the - * modulus and exponent _without_ any preceding length field - - * just the minimum number of bytes to represent each integer, - * stored big-endian, concatenated with no marker at the division - * between them. - */ - - ssh_hash *hash = ssh_hash_new(&ssh_md5); - for (size_t i = (mp_get_nbits(key->modulus) + 7) / 8; i-- > 0 ;) - put_byte(hash, mp_get_byte(key->modulus, i)); - for (size_t i = (mp_get_nbits(key->exponent) + 7) / 8; i-- > 0 ;) - put_byte(hash, mp_get_byte(key->exponent, i)); - ssh_hash_final(hash, digest); - - out = strbuf_new(); - put_fmt(out, "%"SIZEu" ", mp_get_nbits(key->modulus)); - for (i = 0; i < 16; i++) - put_fmt(out, "%s%02x", i ? ":" : "", digest[i]); - if (key->comment) - put_fmt(out, " %s", key->comment); - return strbuf_to_str(out); -} - -/* - * Wrap the output of rsa_ssh1_fingerprint up into the same kind of - * structure that comes from ssh2_all_fingerprints. - */ -char **rsa_ssh1_fake_all_fingerprints(RSAKey *key) -{ - char **fingerprints = snewn(SSH_N_FPTYPES, char *); - for (unsigned i = 0; i < SSH_N_FPTYPES; i++) - fingerprints[i] = NULL; - fingerprints[SSH_FPTYPE_MD5] = rsa_ssh1_fingerprint(key); - return fingerprints; -} - -/* - * 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. - */ -bool rsa_verify(RSAKey *key) -{ - mp_int *n, *ed, *pm1, *qm1; - unsigned ok = 1; - - /* Preliminary checks: p,q can't be 0 or 1. (Of course no other - * very small value is any good either, but these are the values - * we _must_ check for to avoid assertion failures further down - * this function.) */ - if (!(mp_hs_integer(key->p, 2) & mp_hs_integer(key->q, 2))) - return false; - - /* n must equal pq. */ - n = mp_mul(key->p, key->q); - ok &= mp_cmp_eq(n, key->modulus); - mp_free(n); - - /* e * d must be congruent to 1, modulo (p-1) and modulo (q-1). */ - pm1 = mp_copy(key->p); - mp_sub_integer_into(pm1, pm1, 1); - ed = mp_modmul(key->exponent, key->private_exponent, pm1); - mp_free(pm1); - ok &= mp_eq_integer(ed, 1); - mp_free(ed); - - qm1 = mp_copy(key->q); - mp_sub_integer_into(qm1, qm1, 1); - ed = mp_modmul(key->exponent, key->private_exponent, qm1); - mp_free(qm1); - ok &= mp_eq_integer(ed, 1); - mp_free(ed); - - /* - * 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. - */ - mp_int *p_new = mp_max(key->p, key->q); - mp_int *q_new = mp_min(key->p, key->q); - mp_free(key->p); - mp_free(key->q); - mp_free(key->iqmp); - key->p = p_new; - key->q = q_new; - key->iqmp = mp_invert(key->q, key->p); - - return ok; -} - -void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, - RsaSsh1Order order) -{ - put_uint32(bs, mp_get_nbits(key->modulus)); - if (order == RSA_SSH1_EXPONENT_FIRST) { - put_mp_ssh1(bs, key->exponent); - put_mp_ssh1(bs, key->modulus); - } else { - put_mp_ssh1(bs, key->modulus); - put_mp_ssh1(bs, key->exponent); - } -} - -void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key) -{ - rsa_ssh1_public_blob(bs, key, RSA_SSH1_MODULUS_FIRST); - put_mp_ssh1(bs, key->private_exponent); - put_mp_ssh1(bs, key->iqmp); - put_mp_ssh1(bs, key->q); - put_mp_ssh1(bs, key->p); -} - -/* Given an SSH-1 public key blob, determine its length. */ -int rsa_ssh1_public_blob_len(ptrlen data) -{ - BinarySource src[1]; - - BinarySource_BARE_INIT_PL(src, data); - - /* Expect a length word, then exponent and modulus. (It doesn't - * even matter which order.) */ - get_uint32(src); - mp_free(get_mp_ssh1(src)); - mp_free(get_mp_ssh1(src)); - - if (get_err(src)) - return -1; - - /* Return the number of bytes consumed. */ - return src->pos; -} - -void freersapriv(RSAKey *key) -{ - if (key->private_exponent) { - mp_free(key->private_exponent); - key->private_exponent = NULL; - } - if (key->p) { - mp_free(key->p); - key->p = NULL; - } - if (key->q) { - mp_free(key->q); - key->q = NULL; - } - if (key->iqmp) { - mp_free(key->iqmp); - key->iqmp = NULL; - } -} - -void freersakey(RSAKey *key) -{ - freersapriv(key); - if (key->modulus) { - mp_free(key->modulus); - key->modulus = NULL; - } - if (key->exponent) { - mp_free(key->exponent); - key->exponent = NULL; - } - if (key->comment) { - sfree(key->comment); - key->comment = NULL; - } -} - -/* ---------------------------------------------------------------------- - * Implementation of the ssh-rsa signing key type family. - */ - -struct ssh2_rsa_extra { - unsigned signflags; -}; - -static void rsa2_freekey(ssh_key *key); /* forward reference */ - -static ssh_key *rsa2_new_pub(const ssh_keyalg *self, ptrlen data) -{ - BinarySource src[1]; - RSAKey *rsa; - - BinarySource_BARE_INIT_PL(src, data); - if (!ptrlen_eq_string(get_string(src), "ssh-rsa")) - return NULL; - - rsa = snew(RSAKey); - rsa->sshk.vt = self; - rsa->exponent = get_mp_ssh2(src); - rsa->modulus = get_mp_ssh2(src); - rsa->private_exponent = NULL; - rsa->p = rsa->q = rsa->iqmp = NULL; - rsa->comment = NULL; - - if (get_err(src)) { - rsa2_freekey(&rsa->sshk); - return NULL; - } - - return &rsa->sshk; -} - -static void rsa2_freekey(ssh_key *key) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - freersakey(rsa); - sfree(rsa); -} - -static char *rsa2_cache_str(ssh_key *key) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - return rsastr_fmt(rsa); -} - -static key_components *rsa2_components(ssh_key *key) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - return rsa_components(rsa); -} - -static bool rsa2_has_private(ssh_key *key) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - return rsa->private_exponent != NULL; -} - -static void rsa2_public_blob(ssh_key *key, BinarySink *bs) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - - put_stringz(bs, "ssh-rsa"); - put_mp_ssh2(bs, rsa->exponent); - put_mp_ssh2(bs, rsa->modulus); -} - -static void rsa2_private_blob(ssh_key *key, BinarySink *bs) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - - put_mp_ssh2(bs, rsa->private_exponent); - put_mp_ssh2(bs, rsa->p); - put_mp_ssh2(bs, rsa->q); - put_mp_ssh2(bs, rsa->iqmp); -} - -static ssh_key *rsa2_new_priv(const ssh_keyalg *self, - ptrlen pub, ptrlen priv) -{ - BinarySource src[1]; - ssh_key *sshk; - RSAKey *rsa; - - sshk = rsa2_new_pub(self, pub); - if (!sshk) - return NULL; - - rsa = container_of(sshk, RSAKey, sshk); - BinarySource_BARE_INIT_PL(src, priv); - rsa->private_exponent = get_mp_ssh2(src); - rsa->p = get_mp_ssh2(src); - rsa->q = get_mp_ssh2(src); - rsa->iqmp = get_mp_ssh2(src); - - if (get_err(src) || !rsa_verify(rsa)) { - rsa2_freekey(&rsa->sshk); - return NULL; - } - - return &rsa->sshk; -} - -static ssh_key *rsa2_new_priv_openssh(const ssh_keyalg *self, - BinarySource *src) -{ - RSAKey *rsa; - - rsa = snew(RSAKey); - rsa->sshk.vt = &ssh_rsa; - rsa->comment = NULL; - - rsa->modulus = get_mp_ssh2(src); - rsa->exponent = get_mp_ssh2(src); - rsa->private_exponent = get_mp_ssh2(src); - rsa->iqmp = get_mp_ssh2(src); - rsa->p = get_mp_ssh2(src); - rsa->q = get_mp_ssh2(src); - - if (get_err(src) || !rsa_verify(rsa)) { - rsa2_freekey(&rsa->sshk); - return NULL; - } - - return &rsa->sshk; -} - -static void rsa2_openssh_blob(ssh_key *key, BinarySink *bs) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - - put_mp_ssh2(bs, rsa->modulus); - put_mp_ssh2(bs, rsa->exponent); - put_mp_ssh2(bs, rsa->private_exponent); - put_mp_ssh2(bs, rsa->iqmp); - put_mp_ssh2(bs, rsa->p); - put_mp_ssh2(bs, rsa->q); -} - -static int rsa2_pubkey_bits(const ssh_keyalg *self, ptrlen pub) -{ - ssh_key *sshk; - RSAKey *rsa; - int ret; - - sshk = rsa2_new_pub(self, pub); - if (!sshk) - return -1; - - rsa = container_of(sshk, RSAKey, sshk); - ret = mp_get_nbits(rsa->modulus); - rsa2_freekey(&rsa->sshk); - - return ret; -} - -static inline const ssh_hashalg *rsa2_hash_alg_for_flags( - unsigned flags, const char **protocol_id_out) -{ - const ssh_hashalg *halg; - const char *protocol_id; - - if (flags & SSH_AGENT_RSA_SHA2_256) { - halg = &ssh_sha256; - protocol_id = "rsa-sha2-256"; - } else if (flags & SSH_AGENT_RSA_SHA2_512) { - halg = &ssh_sha512; - protocol_id = "rsa-sha2-512"; - } else { - halg = &ssh_sha1; - protocol_id = "ssh-rsa"; - } - - if (protocol_id_out) - *protocol_id_out = protocol_id; - - return halg; -} - -static inline ptrlen rsa_pkcs1_prefix_for_hash(const ssh_hashalg *halg) -{ - if (halg == &ssh_sha1) { - /* - * This is the magic ASN.1/DER prefix that goes in the decoded - * signature, between the string of FFs and the actual SHA-1 - * 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 sha1_asn1_prefix[] = { - 0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, - 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, - }; - return PTRLEN_FROM_CONST_BYTES(sha1_asn1_prefix); - } - - if (halg == &ssh_sha256) { - /* - * A similar piece of ASN.1 used for signatures using SHA-256, - * in the same format but differing only in various length - * fields and OID. - */ - static const unsigned char sha256_asn1_prefix[] = { - 0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, - 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, - 0x05, 0x00, 0x04, 0x20, - }; - return PTRLEN_FROM_CONST_BYTES(sha256_asn1_prefix); - } - - if (halg == &ssh_sha512) { - /* - * And one more for SHA-512. - */ - static const unsigned char sha512_asn1_prefix[] = { - 0x00, 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, - 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, - 0x05, 0x00, 0x04, 0x40, - }; - return PTRLEN_FROM_CONST_BYTES(sha512_asn1_prefix); - } - - unreachable("bad hash algorithm for RSA PKCS#1"); -} - -static inline size_t rsa_pkcs1_length_of_fixed_parts(const ssh_hashalg *halg) -{ - ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg); - return halg->hlen + asn1_prefix.len + 2; -} - -static unsigned char *rsa_pkcs1_signature_string( - size_t nbytes, const ssh_hashalg *halg, ptrlen data) -{ - size_t fixed_parts = rsa_pkcs1_length_of_fixed_parts(halg); - assert(nbytes >= fixed_parts); - size_t padding = nbytes - fixed_parts; - - ptrlen asn1_prefix = rsa_pkcs1_prefix_for_hash(halg); - - unsigned char *bytes = snewn(nbytes, unsigned char); - - bytes[0] = 0; - bytes[1] = 1; - - memset(bytes + 2, 0xFF, padding); - - memcpy(bytes + 2 + padding, asn1_prefix.ptr, asn1_prefix.len); - - ssh_hash *h = ssh_hash_new(halg); - put_datapl(h, data); - ssh_hash_final(h, bytes + 2 + padding + asn1_prefix.len); - - return bytes; -} - -static bool rsa2_verify(ssh_key *key, ptrlen sig, ptrlen data) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - BinarySource src[1]; - ptrlen type, in_pl; - mp_int *in, *out; - - const struct ssh2_rsa_extra *extra = - (const struct ssh2_rsa_extra *)key->vt->extra; - - const ssh_hashalg *halg = rsa2_hash_alg_for_flags(extra->signflags, NULL); - - /* Start by making sure the key is even long enough to encode a - * signature. If not, everything fails to verify. */ - size_t nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8; - if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg)) - return false; - - BinarySource_BARE_INIT_PL(src, sig); - type = get_string(src); - /* - * RFC 4253 section 6.6: the signature integer in an ssh-rsa - * signature is 'without lengths or padding'. That is, we _don't_ - * expect the usual leading zero byte if the topmost bit of the - * first byte is set. (However, because of the possibility of - * BUG_SSH2_RSA_PADDING at the other end, we tolerate it if it's - * there.) So we can't use get_mp_ssh2, which enforces that - * leading-byte scheme; instead we use get_string and - * mp_from_bytes_be, which will tolerate anything. - */ - in_pl = get_string(src); - if (get_err(src) || !ptrlen_eq_string(type, key->vt->ssh_id)) - return false; - - in = mp_from_bytes_be(in_pl); - out = mp_modpow(in, rsa->exponent, rsa->modulus); - mp_free(in); - - unsigned diff = 0; - - unsigned char *bytes = rsa_pkcs1_signature_string(nbytes, halg, data); - for (size_t i = 0; i < nbytes; i++) - diff |= bytes[nbytes-1 - i] ^ mp_get_byte(out, i); - smemclr(bytes, nbytes); - sfree(bytes); - mp_free(out); - - return diff == 0; -} - -static void rsa2_sign(ssh_key *key, ptrlen data, - unsigned flags, BinarySink *bs) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - unsigned char *bytes; - size_t nbytes; - mp_int *in, *out; - const ssh_hashalg *halg; - const char *sign_alg_name; - - const struct ssh2_rsa_extra *extra = - (const struct ssh2_rsa_extra *)key->vt->extra; - flags |= extra->signflags; - - halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name); - - nbytes = (mp_get_nbits(rsa->modulus) + 7) / 8; - - bytes = rsa_pkcs1_signature_string(nbytes, halg, data); - in = mp_from_bytes_be(make_ptrlen(bytes, nbytes)); - smemclr(bytes, nbytes); - sfree(bytes); - - out = rsa_privkey_op(in, rsa); - mp_free(in); - - put_stringz(bs, sign_alg_name); - nbytes = (mp_get_nbits(out) + 7) / 8; - put_uint32(bs, nbytes); - for (size_t i = 0; i < nbytes; i++) - put_byte(bs, mp_get_byte(out, nbytes - 1 - i)); - - mp_free(out); -} - -static char *rsa2_invalid(ssh_key *key, unsigned flags) -{ - RSAKey *rsa = container_of(key, RSAKey, sshk); - size_t bits = mp_get_nbits(rsa->modulus), nbytes = (bits + 7) / 8; - const char *sign_alg_name; - const ssh_hashalg *halg = rsa2_hash_alg_for_flags(flags, &sign_alg_name); - if (nbytes < rsa_pkcs1_length_of_fixed_parts(halg)) { - return dupprintf( - "%"SIZEu"-bit RSA key is too short to generate %s signatures", - bits, sign_alg_name); - } - - return NULL; -} - -static unsigned ssh_rsa_supported_flags(const ssh_keyalg *self) -{ - return SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512; -} - -static const char *ssh_rsa_alternate_ssh_id( - const ssh_keyalg *self, unsigned flags) -{ - if (flags & SSH_AGENT_RSA_SHA2_512) - return ssh_rsa_sha512.ssh_id; - if (flags & SSH_AGENT_RSA_SHA2_256) - return ssh_rsa_sha256.ssh_id; - return self->ssh_id; -} - -static char *rsa2_alg_desc(const ssh_keyalg *self) { return dupstr("RSA"); } - -static const struct ssh2_rsa_extra - rsa_extra = { 0 }, - rsa_sha256_extra = { SSH_AGENT_RSA_SHA2_256 }, - rsa_sha512_extra = { SSH_AGENT_RSA_SHA2_512 }; - -#define COMMON_KEYALG_FIELDS \ - .new_pub = rsa2_new_pub, \ - .new_priv = rsa2_new_priv, \ - .new_priv_openssh = rsa2_new_priv_openssh, \ - .freekey = rsa2_freekey, \ - .invalid = rsa2_invalid, \ - .sign = rsa2_sign, \ - .verify = rsa2_verify, \ - .public_blob = rsa2_public_blob, \ - .private_blob = rsa2_private_blob, \ - .openssh_blob = rsa2_openssh_blob, \ - .has_private = rsa2_has_private, \ - .cache_str = rsa2_cache_str, \ - .components = rsa2_components, \ - .base_key = nullkey_base_key, \ - .pubkey_bits = rsa2_pubkey_bits, \ - .alg_desc = rsa2_alg_desc, \ - .variable_size = nullkey_variable_size_yes, \ - .cache_id = "rsa2" - -const ssh_keyalg ssh_rsa = { - COMMON_KEYALG_FIELDS, - .ssh_id = "ssh-rsa", - .supported_flags = ssh_rsa_supported_flags, - .alternate_ssh_id = ssh_rsa_alternate_ssh_id, - .extra = &rsa_extra, -}; - -const ssh_keyalg ssh_rsa_sha256 = { - COMMON_KEYALG_FIELDS, - .ssh_id = "rsa-sha2-256", - .supported_flags = nullkey_supported_flags, - .alternate_ssh_id = nullkey_alternate_ssh_id, - .extra = &rsa_sha256_extra, -}; - -const ssh_keyalg ssh_rsa_sha512 = { - COMMON_KEYALG_FIELDS, - .ssh_id = "rsa-sha2-512", - .supported_flags = nullkey_supported_flags, - .alternate_ssh_id = nullkey_alternate_ssh_id, - .extra = &rsa_sha512_extra, -}; - -RSAKey *ssh_rsakex_newkey(ptrlen data) -{ - ssh_key *sshk = rsa2_new_pub(&ssh_rsa, data); - if (!sshk) - return NULL; - return container_of(sshk, RSAKey, sshk); -} - -void ssh_rsakex_freekey(RSAKey *key) -{ - rsa2_freekey(&key->sshk); -} - -int ssh_rsakex_klen(RSAKey *rsa) -{ - return mp_get_nbits(rsa->modulus); -} - -static void oaep_mask(const ssh_hashalg *h, void *seed, int seedlen, - void *vdata, int datalen) -{ - unsigned char *data = (unsigned char *)vdata; - unsigned count = 0; - - ssh_hash *s = ssh_hash_new(h); - - while (datalen > 0) { - int i, max = (datalen > h->hlen ? h->hlen : datalen); - unsigned char hash[MAX_HASH_LEN]; - - ssh_hash_reset(s); - assert(h->hlen <= MAX_HASH_LEN); - put_data(s, seed, seedlen); - put_uint32(s, count); - ssh_hash_digest(s, hash); - count++; - - for (i = 0; i < max; i++) - data[i] ^= hash[i]; - - data += max; - datalen -= max; - } - - ssh_hash_free(s); -} - -strbuf *ssh_rsakex_encrypt(RSAKey *rsa, const ssh_hashalg *h, ptrlen in) -{ - mp_int *b1, *b2; - 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 + mp_get_nbits(rsa->modulus)) / 8; - - /* The length of the input data must be at most k - 2hLen - 2. */ - assert(in.len > 0 && in.len <= k - 2*HLEN - 2); - - /* The length of the output data wants to be precisely k. */ - strbuf *toret = strbuf_new_nm(); - int outlen = k; - unsigned char *out = strbuf_append(toret, outlen); - - /* - * 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. */ - random_read(out + 1, HLEN); - /* At position 1+HLEN, the data block DB, consisting of: */ - /* The hash of the label (we only support an empty label here) */ - hash_simple(h, PTRLEN_LITERAL(""), 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 - in.len - 1] = 1; - memcpy(out + outlen - in.len, in.ptr, in.len); - - /* - * 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 = mp_from_bytes_be(make_ptrlen(out, outlen)); - b2 = mp_modpow(b1, rsa->exponent, rsa->modulus); - p = (char *)out; - for (i = outlen; i--;) { - *p++ = mp_get_byte(b2, i); - } - mp_free(b1); - mp_free(b2); - - /* - * And we're done. - */ - return toret; -} - -mp_int *ssh_rsakex_decrypt( - RSAKey *rsa, const ssh_hashalg *h, ptrlen ciphertext) -{ - mp_int *b1, *b2; - int outlen, i; - unsigned char *out; - unsigned char labelhash[64]; - BinarySource src[1]; - const int HLEN = h->hlen; - - /* - * Decryption side of the RSA key exchange operation. - */ - - /* The length of the encrypted data should be exactly the length - * in octets of the RSA modulus.. */ - outlen = (7 + mp_get_nbits(rsa->modulus)) / 8; - if (ciphertext.len != outlen) - return NULL; - - /* Do the RSA decryption, and extract the result into a byte array. */ - b1 = mp_from_bytes_be(ciphertext); - b2 = rsa_privkey_op(b1, rsa); - out = snewn(outlen, unsigned char); - for (i = 0; i < outlen; i++) - out[i] = mp_get_byte(b2, outlen-1-i); - mp_free(b1); - mp_free(b2); - - /* Do the OAEP masking operations, in the reverse order from encryption */ - oaep_mask(h, out+HLEN+1, outlen-HLEN-1, out+1, HLEN); - oaep_mask(h, out+1, HLEN, out+HLEN+1, outlen-HLEN-1); - - /* Check the leading byte is zero. */ - if (out[0] != 0) { - sfree(out); - return NULL; - } - /* Check the label hash at position 1+HLEN */ - assert(HLEN <= lenof(labelhash)); - hash_simple(h, PTRLEN_LITERAL(""), labelhash); - if (memcmp(out + HLEN + 1, labelhash, HLEN)) { - sfree(out); - return NULL; - } - /* Expect zero bytes followed by a 1 byte */ - for (i = 1 + 2 * HLEN; i < outlen; i++) { - if (out[i] == 1) { - i++; /* skip over the 1 byte */ - break; - } else if (out[i] != 0) { - sfree(out); - return NULL; - } - } - /* And what's left is the input message data, which should be - * encoded as an ordinary SSH-2 mpint. */ - BinarySource_BARE_INIT(src, out + i, outlen - i); - b1 = get_mp_ssh2(src); - sfree(out); - if (get_err(src) || get_avail(src) != 0) { - mp_free(b1); - return NULL; - } - - /* Success! */ - return b1; -} - -static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha1 = { 1024 }; -static const struct ssh_rsa_kex_extra ssh_rsa_kex_extra_sha256 = { 2048 }; - -static const ssh_kex ssh_rsa_kex_sha1 = { - .name = "rsa1024-sha1", - .main_type = KEXTYPE_RSA, - .hash = &ssh_sha1, - .extra = &ssh_rsa_kex_extra_sha1, -}; - -static const ssh_kex ssh_rsa_kex_sha256 = { - .name = "rsa2048-sha256", - .main_type = KEXTYPE_RSA, - .hash = &ssh_sha256, - .extra = &ssh_rsa_kex_extra_sha256, -}; - -static const ssh_kex *const rsa_kex_list[] = { - &ssh_rsa_kex_sha256, - &ssh_rsa_kex_sha1 -}; - -const ssh_kexes ssh_rsa_kex = { lenof(rsa_kex_list), rsa_kex_list }; diff --git a/crypto/sha1-common.c b/crypto/sha1-common.c deleted file mode 100644 index bf1db67ac..000000000 --- a/crypto/sha1-common.c +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Common variable definitions across all the SHA-1 implementations. - */ - -#include "ssh.h" -#include "sha1.h" - -const uint32_t sha1_initial_state[5] = { - 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0, -}; diff --git a/crypto/sha1-neon.c b/crypto/sha1-neon.c deleted file mode 100644 index 99045714e..000000000 --- a/crypto/sha1-neon.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Hardware-accelerated implementation of SHA-1 using Arm NEON. - */ - -#include "ssh.h" -#include "sha1.h" - -#if USE_ARM64_NEON_H -#include -#else -#include -#endif - -static bool sha1_neon_available(void) -{ - /* - * For Arm, we delegate to a per-platform detection function (see - * explanation in aes-neon.c). - */ - return platform_sha1_neon_available(); -} - -typedef struct sha1_neon_core sha1_neon_core; -struct sha1_neon_core { - uint32x4_t abcd; - uint32_t e; -}; - -static inline uint32x4_t sha1_neon_load_input(const uint8_t *p) -{ - return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p))); -} - -static inline uint32x4_t sha1_neon_schedule_update( - uint32x4_t m4, uint32x4_t m3, uint32x4_t m2, uint32x4_t m1) -{ - return vsha1su1q_u32(vsha1su0q_u32(m4, m3, m2), m1); -} - -/* - * SHA-1 has three different kinds of round, differing in whether they - * use the Ch, Maj or Par functions defined above. Each one uses a - * separate NEON instruction, so we define three inline functions for - * the different round types using this macro. - * - * The two batches of Par-type rounds also use a different constant, - * but that's passed in as an operand, so we don't need a fourth - * inline function just for that. - */ -#define SHA1_NEON_ROUND_FN(type) \ - static inline sha1_neon_core sha1_neon_round4_##type( \ - sha1_neon_core old, uint32x4_t sched, uint32x4_t constant) \ - { \ - sha1_neon_core new; \ - uint32x4_t round_input = vaddq_u32(sched, constant); \ - new.abcd = vsha1##type##q_u32(old.abcd, old.e, round_input); \ - new.e = vsha1h_u32(vget_lane_u32(vget_low_u32(old.abcd), 0)); \ - return new; \ - } -SHA1_NEON_ROUND_FN(c) -SHA1_NEON_ROUND_FN(p) -SHA1_NEON_ROUND_FN(m) - -static inline void sha1_neon_block(sha1_neon_core *core, const uint8_t *p) -{ - uint32x4_t constant, s0, s1, s2, s3; - sha1_neon_core cr = *core; - - constant = vdupq_n_u32(SHA1_STAGE0_CONSTANT); - s0 = sha1_neon_load_input(p); - cr = sha1_neon_round4_c(cr, s0, constant); - s1 = sha1_neon_load_input(p + 16); - cr = sha1_neon_round4_c(cr, s1, constant); - s2 = sha1_neon_load_input(p + 32); - cr = sha1_neon_round4_c(cr, s2, constant); - s3 = sha1_neon_load_input(p + 48); - cr = sha1_neon_round4_c(cr, s3, constant); - s0 = sha1_neon_schedule_update(s0, s1, s2, s3); - cr = sha1_neon_round4_c(cr, s0, constant); - - constant = vdupq_n_u32(SHA1_STAGE1_CONSTANT); - s1 = sha1_neon_schedule_update(s1, s2, s3, s0); - cr = sha1_neon_round4_p(cr, s1, constant); - s2 = sha1_neon_schedule_update(s2, s3, s0, s1); - cr = sha1_neon_round4_p(cr, s2, constant); - s3 = sha1_neon_schedule_update(s3, s0, s1, s2); - cr = sha1_neon_round4_p(cr, s3, constant); - s0 = sha1_neon_schedule_update(s0, s1, s2, s3); - cr = sha1_neon_round4_p(cr, s0, constant); - s1 = sha1_neon_schedule_update(s1, s2, s3, s0); - cr = sha1_neon_round4_p(cr, s1, constant); - - constant = vdupq_n_u32(SHA1_STAGE2_CONSTANT); - s2 = sha1_neon_schedule_update(s2, s3, s0, s1); - cr = sha1_neon_round4_m(cr, s2, constant); - s3 = sha1_neon_schedule_update(s3, s0, s1, s2); - cr = sha1_neon_round4_m(cr, s3, constant); - s0 = sha1_neon_schedule_update(s0, s1, s2, s3); - cr = sha1_neon_round4_m(cr, s0, constant); - s1 = sha1_neon_schedule_update(s1, s2, s3, s0); - cr = sha1_neon_round4_m(cr, s1, constant); - s2 = sha1_neon_schedule_update(s2, s3, s0, s1); - cr = sha1_neon_round4_m(cr, s2, constant); - - constant = vdupq_n_u32(SHA1_STAGE3_CONSTANT); - s3 = sha1_neon_schedule_update(s3, s0, s1, s2); - cr = sha1_neon_round4_p(cr, s3, constant); - s0 = sha1_neon_schedule_update(s0, s1, s2, s3); - cr = sha1_neon_round4_p(cr, s0, constant); - s1 = sha1_neon_schedule_update(s1, s2, s3, s0); - cr = sha1_neon_round4_p(cr, s1, constant); - s2 = sha1_neon_schedule_update(s2, s3, s0, s1); - cr = sha1_neon_round4_p(cr, s2, constant); - s3 = sha1_neon_schedule_update(s3, s0, s1, s2); - cr = sha1_neon_round4_p(cr, s3, constant); - - core->abcd = vaddq_u32(core->abcd, cr.abcd); - core->e += cr.e; -} - -typedef struct sha1_neon { - sha1_neon_core core; - sha1_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha1_neon; - -static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha1_neon_new(const ssh_hashalg *alg) -{ - const struct sha1_extra *extra = (const struct sha1_extra *)alg->extra; - if (!check_availability(extra)) - return NULL; - - sha1_neon *s = snew(sha1_neon); - - s->hash.vt = alg; - BinarySink_INIT(s, sha1_neon_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha1_neon_reset(ssh_hash *hash) -{ - sha1_neon *s = container_of(hash, sha1_neon, hash); - - s->core.abcd = vld1q_u32(sha1_initial_state); - s->core.e = sha1_initial_state[4]; - - sha1_block_setup(&s->blk); -} - -static void sha1_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha1_neon *copy = container_of(hcopy, sha1_neon, hash); - sha1_neon *orig = container_of(horig, sha1_neon, hash); - - *copy = *orig; /* structure copy */ - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha1_neon_free(ssh_hash *hash) -{ - sha1_neon *s = container_of(hash, sha1_neon, hash); - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha1_neon_write(BinarySink *bs, const void *vp, size_t len) -{ - sha1_neon *s = BinarySink_DOWNCAST(bs, sha1_neon); - - while (len > 0) - if (sha1_block_write(&s->blk, &vp, &len)) - sha1_neon_block(&s->core, s->blk.block); -} - -static void sha1_neon_digest(ssh_hash *hash, uint8_t *digest) -{ - sha1_neon *s = container_of(hash, sha1_neon, hash); - - sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); - vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd))); - PUT_32BIT_MSB_FIRST(digest + 16, s->core.e); -} - -SHA1_VTABLE(neon, "NEON accelerated"); diff --git a/crypto/sha1-ni.c b/crypto/sha1-ni.c deleted file mode 100644 index 04e6386be..000000000 --- a/crypto/sha1-ni.c +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Hardware-accelerated implementation of SHA-1 using x86 SHA-NI. - */ - -#include "ssh.h" -#include "sha1.h" - -#include -#include -#include -#if HAVE_SHAINTRIN_H -#include -#endif - -#if defined(__clang__) || defined(__GNUC__) -#include -#define GET_CPU_ID_0(out) \ - __cpuid(0, (out)[0], (out)[1], (out)[2], (out)[3]) -#define GET_CPU_ID_7(out) \ - __cpuid_count(7, 0, (out)[0], (out)[1], (out)[2], (out)[3]) -#else -#define GET_CPU_ID_0(out) __cpuid(out, 0) -#define GET_CPU_ID_7(out) __cpuidex(out, 7, 0) -#endif - -static bool sha1_ni_available(void) -{ - unsigned int CPUInfo[4]; - GET_CPU_ID_0(CPUInfo); - if (CPUInfo[0] < 7) - return false; - - GET_CPU_ID_7(CPUInfo); - return CPUInfo[1] & (1 << 29); /* Check SHA */ -} - -/* SHA1 implementation using new instructions - The code is based on Jeffrey Walton's SHA1 implementation: - https://github.com/noloader/SHA-Intrinsics -*/ -static inline void sha1_ni_block(__m128i *core, const uint8_t *p) -{ - __m128i ABCD, E0, E1, MSG0, MSG1, MSG2, MSG3; - const __m128i MASK = _mm_set_epi64x( - 0x0001020304050607ULL, 0x08090a0b0c0d0e0fULL); - - const __m128i *block = (const __m128i *)p; - - /* Load initial values */ - ABCD = core[0]; - E0 = core[1]; - - /* Rounds 0-3 */ - MSG0 = _mm_loadu_si128(block); - MSG0 = _mm_shuffle_epi8(MSG0, MASK); - E0 = _mm_add_epi32(E0, MSG0); - E1 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); - - /* Rounds 4-7 */ - MSG1 = _mm_loadu_si128(block + 1); - MSG1 = _mm_shuffle_epi8(MSG1, MASK); - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - - /* Rounds 8-11 */ - MSG2 = _mm_loadu_si128(block + 2); - MSG2 = _mm_shuffle_epi8(MSG2, MASK); - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - /* Rounds 12-15 */ - MSG3 = _mm_loadu_si128(block + 3); - MSG3 = _mm_shuffle_epi8(MSG3, MASK); - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 0); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - /* Rounds 16-19 */ - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 0); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - /* Rounds 20-23 */ - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - /* Rounds 24-27 */ - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - /* Rounds 28-31 */ - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - /* Rounds 32-35 */ - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 1); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - /* Rounds 36-39 */ - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 1); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - /* Rounds 40-43 */ - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - /* Rounds 44-47 */ - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - /* Rounds 48-51 */ - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - /* Rounds 52-55 */ - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 2); - MSG0 = _mm_sha1msg1_epu32(MSG0, MSG1); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - /* Rounds 56-59 */ - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 2); - MSG1 = _mm_sha1msg1_epu32(MSG1, MSG2); - MSG0 = _mm_xor_si128(MSG0, MSG2); - - /* Rounds 60-63 */ - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - MSG0 = _mm_sha1msg2_epu32(MSG0, MSG3); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); - MSG2 = _mm_sha1msg1_epu32(MSG2, MSG3); - MSG1 = _mm_xor_si128(MSG1, MSG3); - - /* Rounds 64-67 */ - E0 = _mm_sha1nexte_epu32(E0, MSG0); - E1 = ABCD; - MSG1 = _mm_sha1msg2_epu32(MSG1, MSG0); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3); - MSG3 = _mm_sha1msg1_epu32(MSG3, MSG0); - MSG2 = _mm_xor_si128(MSG2, MSG0); - - /* Rounds 68-71 */ - E1 = _mm_sha1nexte_epu32(E1, MSG1); - E0 = ABCD; - MSG2 = _mm_sha1msg2_epu32(MSG2, MSG1); - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); - MSG3 = _mm_xor_si128(MSG3, MSG1); - - /* Rounds 72-75 */ - E0 = _mm_sha1nexte_epu32(E0, MSG2); - E1 = ABCD; - MSG3 = _mm_sha1msg2_epu32(MSG3, MSG2); - ABCD = _mm_sha1rnds4_epu32(ABCD, E0, 3); - - /* Rounds 76-79 */ - E1 = _mm_sha1nexte_epu32(E1, MSG3); - E0 = ABCD; - ABCD = _mm_sha1rnds4_epu32(ABCD, E1, 3); - - /* Combine state */ - core[0] = _mm_add_epi32(ABCD, core[0]); - core[1] = _mm_sha1nexte_epu32(E0, core[1]); -} - -typedef struct sha1_ni { - /* - * core[0] stores the first four words of the SHA-1 state. core[1] - * stores just the fifth word, in the vector lane at the highest - * address. - */ - __m128i core[2]; - sha1_block blk; - void *pointer_to_free; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha1_ni; - -static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len); - -static sha1_ni *sha1_ni_alloc(void) -{ - /* - * The __m128i variables in the context structure need to be - * 16-byte aligned, but not all malloc implementations that this - * code has to work with will guarantee to return a 16-byte - * aligned pointer. So we over-allocate, manually realign the - * pointer ourselves, and store the original one inside the - * context so we know how to free it later. - */ - void *allocation = smalloc(sizeof(sha1_ni) + 15); - uintptr_t alloc_address = (uintptr_t)allocation; - uintptr_t aligned_address = (alloc_address + 15) & ~15; - sha1_ni *s = (sha1_ni *)aligned_address; - s->pointer_to_free = allocation; - return s; -} - -static ssh_hash *sha1_ni_new(const ssh_hashalg *alg) -{ - const struct sha1_extra *extra = (const struct sha1_extra *)alg->extra; - if (!check_availability(extra)) - return NULL; - - sha1_ni *s = sha1_ni_alloc(); - - s->hash.vt = alg; - BinarySink_INIT(s, sha1_ni_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha1_ni_reset(ssh_hash *hash) -{ - sha1_ni *s = container_of(hash, sha1_ni, hash); - - /* Initialise the core vectors in their storage order */ - s->core[0] = _mm_set_epi64x( - 0x67452301efcdab89ULL, 0x98badcfe10325476ULL); - s->core[1] = _mm_set_epi32(0xc3d2e1f0, 0, 0, 0); - - sha1_block_setup(&s->blk); -} - -static void sha1_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha1_ni *copy = container_of(hcopy, sha1_ni, hash); - sha1_ni *orig = container_of(horig, sha1_ni, hash); - - void *ptf_save = copy->pointer_to_free; - *copy = *orig; /* structure copy */ - copy->pointer_to_free = ptf_save; - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha1_ni_free(ssh_hash *hash) -{ - sha1_ni *s = container_of(hash, sha1_ni, hash); - - void *ptf = s->pointer_to_free; - smemclr(s, sizeof(*s)); - sfree(ptf); -} - -static void sha1_ni_write(BinarySink *bs, const void *vp, size_t len) -{ - sha1_ni *s = BinarySink_DOWNCAST(bs, sha1_ni); - - while (len > 0) - if (sha1_block_write(&s->blk, &vp, &len)) - sha1_ni_block(s->core, s->blk.block); -} - -static void sha1_ni_digest(ssh_hash *hash, uint8_t *digest) -{ - sha1_ni *s = container_of(hash, sha1_ni, hash); - - sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); - - /* Rearrange the first vector into its output order */ - __m128i abcd = _mm_shuffle_epi32(s->core[0], 0x1B); - - /* Byte-swap it into the output endianness */ - const __m128i mask = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12); - abcd = _mm_shuffle_epi8(abcd, mask); - - /* And store it */ - _mm_storeu_si128((__m128i *)digest, abcd); - - /* Finally, store the leftover word */ - uint32_t e = _mm_extract_epi32(s->core[1], 3); - PUT_32BIT_MSB_FIRST(digest + 16, e); -} - -SHA1_VTABLE(ni, "SHA-NI accelerated"); diff --git a/crypto/sha1-select.c b/crypto/sha1-select.c deleted file mode 100644 index 1e8a6ce9f..000000000 --- a/crypto/sha1-select.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Top-level vtables to select a SHA-1 implementation. - */ - -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sha1.h" - -static ssh_hash *sha1_select(const ssh_hashalg *alg) -{ - static const ssh_hashalg *const real_algs[] = { -#if HAVE_SHA_NI - &ssh_sha1_ni, -#endif -#if HAVE_NEON_CRYPTO - &ssh_sha1_neon, -#endif - &ssh_sha1_sw, - NULL, - }; - - for (size_t i = 0; real_algs[i]; i++) { - const ssh_hashalg *alg = real_algs[i]; - const struct sha1_extra *alg_extra = - (const struct sha1_extra *)alg->extra; - if (check_availability(alg_extra)) - return ssh_hash_new(alg); - } - - /* We should never reach the NULL at the end of the list, because - * the last non-NULL entry should be software-only SHA-1, which - * is always available. */ - unreachable("sha1_select ran off the end of its list"); -} - -const ssh_hashalg ssh_sha1 = { - .new = sha1_select, - .hlen = 20, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-1", "dummy selector vtable"), -}; diff --git a/crypto/sha1-sw.c b/crypto/sha1-sw.c deleted file mode 100644 index 905d97f31..000000000 --- a/crypto/sha1-sw.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Software implementation of SHA-1. - */ - -#include "ssh.h" -#include "sha1.h" - -static bool sha1_sw_available(void) -{ - /* Software SHA-1 is always available */ - return true; -} - -static inline uint32_t rol(uint32_t x, unsigned y) -{ - return (x << (31 & y)) | (x >> (31 & -y)); -} - -static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) -{ - return if0 ^ (ctrl & (if1 ^ if0)); -} - -static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) -{ - return (x & y) | (z & (x | y)); -} - -static inline uint32_t Par(uint32_t x, uint32_t y, uint32_t z) -{ - return (x ^ y ^ z); -} - -static inline void sha1_sw_round( - unsigned round_index, const uint32_t *schedule, - uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, - uint32_t f, uint32_t constant) -{ - *e = rol(*a, 5) + f + *e + schedule[round_index] + constant; - *b = rol(*b, 30); -} - -static void sha1_sw_block(uint32_t *core, const uint8_t *block) -{ - uint32_t w[SHA1_ROUNDS]; - uint32_t a,b,c,d,e; - - for (size_t t = 0; t < 16; t++) - w[t] = GET_32BIT_MSB_FIRST(block + 4*t); - - for (size_t t = 16; t < SHA1_ROUNDS; t++) - w[t] = rol(w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16], 1); - - a = core[0]; b = core[1]; c = core[2]; d = core[3]; - e = core[4]; - - size_t t = 0; - for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { - sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Ch(b,c,d), SHA1_STAGE0_CONSTANT); - sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Ch(a,b,c), SHA1_STAGE0_CONSTANT); - sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Ch(e,a,b), SHA1_STAGE0_CONSTANT); - sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Ch(d,e,a), SHA1_STAGE0_CONSTANT); - sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Ch(c,d,e), SHA1_STAGE0_CONSTANT); - } - for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { - sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE1_CONSTANT); - sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE1_CONSTANT); - sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE1_CONSTANT); - sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE1_CONSTANT); - sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE1_CONSTANT); - } - for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { - sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Maj(b,c,d), SHA1_STAGE2_CONSTANT); - sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Maj(a,b,c), SHA1_STAGE2_CONSTANT); - sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Maj(e,a,b), SHA1_STAGE2_CONSTANT); - sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Maj(d,e,a), SHA1_STAGE2_CONSTANT); - sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Maj(c,d,e), SHA1_STAGE2_CONSTANT); - } - for (size_t u = 0; u < SHA1_ROUNDS_PER_STAGE/5; u++) { - sha1_sw_round(t++,w, &a,&b,&c,&d,&e, Par(b,c,d), SHA1_STAGE3_CONSTANT); - sha1_sw_round(t++,w, &e,&a,&b,&c,&d, Par(a,b,c), SHA1_STAGE3_CONSTANT); - sha1_sw_round(t++,w, &d,&e,&a,&b,&c, Par(e,a,b), SHA1_STAGE3_CONSTANT); - sha1_sw_round(t++,w, &c,&d,&e,&a,&b, Par(d,e,a), SHA1_STAGE3_CONSTANT); - sha1_sw_round(t++,w, &b,&c,&d,&e,&a, Par(c,d,e), SHA1_STAGE3_CONSTANT); - } - - core[0] += a; core[1] += b; core[2] += c; core[3] += d; core[4] += e; - - smemclr(w, sizeof(w)); -} - -typedef struct sha1_sw { - uint32_t core[5]; - sha1_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha1_sw; - -static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha1_sw_new(const ssh_hashalg *alg) -{ - sha1_sw *s = snew(sha1_sw); - - s->hash.vt = alg; - BinarySink_INIT(s, sha1_sw_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha1_sw_reset(ssh_hash *hash) -{ - sha1_sw *s = container_of(hash, sha1_sw, hash); - - memcpy(s->core, sha1_initial_state, sizeof(s->core)); - sha1_block_setup(&s->blk); -} - -static void sha1_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha1_sw *copy = container_of(hcopy, sha1_sw, hash); - sha1_sw *orig = container_of(horig, sha1_sw, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha1_sw_free(ssh_hash *hash) -{ - sha1_sw *s = container_of(hash, sha1_sw, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha1_sw_write(BinarySink *bs, const void *vp, size_t len) -{ - sha1_sw *s = BinarySink_DOWNCAST(bs, sha1_sw); - - while (len > 0) - if (sha1_block_write(&s->blk, &vp, &len)) - sha1_sw_block(s->core, s->blk.block); -} - -static void sha1_sw_digest(ssh_hash *hash, uint8_t *digest) -{ - sha1_sw *s = container_of(hash, sha1_sw, hash); - - sha1_block_pad(&s->blk, BinarySink_UPCAST(s)); - for (size_t i = 0; i < 5; i++) - PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]); -} - -SHA1_VTABLE(sw, "unaccelerated"); diff --git a/crypto/sha1.h b/crypto/sha1.h deleted file mode 100644 index 2cdba0d4e..000000000 --- a/crypto/sha1.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Definitions likely to be helpful to multiple SHA-1 implementations. - */ - -/* - * The 'extra' structure used by SHA-1 implementations is used to - * include information about how to check if a given implementation is - * available at run time, and whether we've already checked. - */ -struct sha1_extra_mutable; -struct sha1_extra { - /* Function to check availability. Might be expensive, so we don't - * want to call it more than once. */ - bool (*check_available)(void); - - /* Point to a writable substructure. */ - struct sha1_extra_mutable *mut; -}; -struct sha1_extra_mutable { - bool checked_availability; - bool is_available; -}; -static inline bool check_availability(const struct sha1_extra *extra) -{ - if (!extra->mut->checked_availability) { - extra->mut->is_available = extra->check_available(); - extra->mut->checked_availability = true; - } - - return extra->mut->is_available; -} - -/* - * Macro to define a SHA-1 vtable together with its 'extra' - * structure. - */ -#define SHA1_VTABLE(impl_c, impl_display) \ - static struct sha1_extra_mutable sha1_ ## impl_c ## _extra_mut; \ - static const struct sha1_extra sha1_ ## impl_c ## _extra = { \ - .check_available = sha1_ ## impl_c ## _available, \ - .mut = &sha1_ ## impl_c ## _extra_mut, \ - }; \ - const ssh_hashalg ssh_sha1_ ## impl_c = { \ - .new = sha1_ ## impl_c ## _new, \ - .reset = sha1_ ## impl_c ## _reset, \ - .copyfrom = sha1_ ## impl_c ## _copyfrom, \ - .digest = sha1_ ## impl_c ## _digest, \ - .free = sha1_ ## impl_c ## _free, \ - .hlen = 20, \ - .blocklen = 64, \ - HASHALG_NAMES_ANNOTATED("SHA-1", impl_display), \ - .extra = &sha1_ ## impl_c ## _extra, \ - } - -extern const uint32_t sha1_initial_state[5]; - -#define SHA1_ROUNDS_PER_STAGE 20 -#define SHA1_STAGE0_CONSTANT 0x5a827999 -#define SHA1_STAGE1_CONSTANT 0x6ed9eba1 -#define SHA1_STAGE2_CONSTANT 0x8f1bbcdc -#define SHA1_STAGE3_CONSTANT 0xca62c1d6 -#define SHA1_ROUNDS (4 * SHA1_ROUNDS_PER_STAGE) - -typedef struct sha1_block sha1_block; -struct sha1_block { - uint8_t block[64]; - size_t used; - uint64_t len; -}; - -static inline void sha1_block_setup(sha1_block *blk) -{ - blk->used = 0; - blk->len = 0; -} - -static inline bool sha1_block_write( - sha1_block *blk, const void **vdata, size_t *len) -{ - size_t blkleft = sizeof(blk->block) - blk->used; - size_t chunk = *len < blkleft ? *len : blkleft; - - const uint8_t *p = *vdata; - memcpy(blk->block + blk->used, p, chunk); - *vdata = p + chunk; - *len -= chunk; - blk->used += chunk; - blk->len += chunk; - - if (blk->used == sizeof(blk->block)) { - blk->used = 0; - return true; - } - - return false; -} - -static inline void sha1_block_pad(sha1_block *blk, BinarySink *bs) -{ - uint64_t final_len = blk->len << 3; - size_t pad = 1 + (63 & (55 - blk->used)); - - put_byte(bs, 0x80); - for (size_t i = 1; i < pad; i++) - put_byte(bs, 0); - put_uint64(bs, final_len); - - assert(blk->used == 0 && "Should have exactly hit a block boundary"); -} diff --git a/crypto/sha256-common.c b/crypto/sha256-common.c deleted file mode 100644 index 52904c08e..000000000 --- a/crypto/sha256-common.c +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Common variable definitions across all the SHA-256 implementations. - */ - -#include "ssh.h" -#include "sha256.h" - -const uint32_t sha256_initial_state[8] = { - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, - 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, -}; - -const uint32_t sha256_round_constants[64] = { - 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, -}; diff --git a/crypto/sha256-neon.c b/crypto/sha256-neon.c deleted file mode 100644 index 87d24d0c2..000000000 --- a/crypto/sha256-neon.c +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Hardware-accelerated implementation of SHA-256 using Arm NEON. - */ - -#include "ssh.h" -#include "sha256.h" - -#if USE_ARM64_NEON_H -#include -#else -#include -#endif - -static bool sha256_neon_available(void) -{ - /* - * For Arm, we delegate to a per-platform detection function (see - * explanation in aes-neon.c). - */ - return platform_sha256_neon_available(); -} - -typedef struct sha256_neon_core sha256_neon_core; -struct sha256_neon_core { - uint32x4_t abcd, efgh; -}; - -static inline uint32x4_t sha256_neon_load_input(const uint8_t *p) -{ - return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p))); -} - -static inline uint32x4_t sha256_neon_schedule_update( - uint32x4_t m4, uint32x4_t m3, uint32x4_t m2, uint32x4_t m1) -{ - return vsha256su1q_u32(vsha256su0q_u32(m4, m3), m2, m1); -} - -static inline sha256_neon_core sha256_neon_round4( - sha256_neon_core old, uint32x4_t sched, unsigned round) -{ - sha256_neon_core new; - - uint32x4_t round_input = vaddq_u32( - sched, vld1q_u32(sha256_round_constants + round)); - new.abcd = vsha256hq_u32 (old.abcd, old.efgh, round_input); - new.efgh = vsha256h2q_u32(old.efgh, old.abcd, round_input); - return new; -} - -static inline void sha256_neon_block(sha256_neon_core *core, const uint8_t *p) -{ - uint32x4_t s0, s1, s2, s3; - sha256_neon_core cr = *core; - - s0 = sha256_neon_load_input(p); - cr = sha256_neon_round4(cr, s0, 0); - s1 = sha256_neon_load_input(p+16); - cr = sha256_neon_round4(cr, s1, 4); - s2 = sha256_neon_load_input(p+32); - cr = sha256_neon_round4(cr, s2, 8); - s3 = sha256_neon_load_input(p+48); - cr = sha256_neon_round4(cr, s3, 12); - s0 = sha256_neon_schedule_update(s0, s1, s2, s3); - cr = sha256_neon_round4(cr, s0, 16); - s1 = sha256_neon_schedule_update(s1, s2, s3, s0); - cr = sha256_neon_round4(cr, s1, 20); - s2 = sha256_neon_schedule_update(s2, s3, s0, s1); - cr = sha256_neon_round4(cr, s2, 24); - s3 = sha256_neon_schedule_update(s3, s0, s1, s2); - cr = sha256_neon_round4(cr, s3, 28); - s0 = sha256_neon_schedule_update(s0, s1, s2, s3); - cr = sha256_neon_round4(cr, s0, 32); - s1 = sha256_neon_schedule_update(s1, s2, s3, s0); - cr = sha256_neon_round4(cr, s1, 36); - s2 = sha256_neon_schedule_update(s2, s3, s0, s1); - cr = sha256_neon_round4(cr, s2, 40); - s3 = sha256_neon_schedule_update(s3, s0, s1, s2); - cr = sha256_neon_round4(cr, s3, 44); - s0 = sha256_neon_schedule_update(s0, s1, s2, s3); - cr = sha256_neon_round4(cr, s0, 48); - s1 = sha256_neon_schedule_update(s1, s2, s3, s0); - cr = sha256_neon_round4(cr, s1, 52); - s2 = sha256_neon_schedule_update(s2, s3, s0, s1); - cr = sha256_neon_round4(cr, s2, 56); - s3 = sha256_neon_schedule_update(s3, s0, s1, s2); - cr = sha256_neon_round4(cr, s3, 60); - - core->abcd = vaddq_u32(core->abcd, cr.abcd); - core->efgh = vaddq_u32(core->efgh, cr.efgh); -} - -typedef struct sha256_neon { - sha256_neon_core core; - sha256_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha256_neon; - -static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha256_neon_new(const ssh_hashalg *alg) -{ - const struct sha256_extra *extra = (const struct sha256_extra *)alg->extra; - if (!check_availability(extra)) - return NULL; - - sha256_neon *s = snew(sha256_neon); - - s->hash.vt = alg; - BinarySink_INIT(s, sha256_neon_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha256_neon_reset(ssh_hash *hash) -{ - sha256_neon *s = container_of(hash, sha256_neon, hash); - - s->core.abcd = vld1q_u32(sha256_initial_state); - s->core.efgh = vld1q_u32(sha256_initial_state + 4); - - sha256_block_setup(&s->blk); -} - -static void sha256_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha256_neon *copy = container_of(hcopy, sha256_neon, hash); - sha256_neon *orig = container_of(horig, sha256_neon, hash); - - *copy = *orig; /* structure copy */ - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha256_neon_free(ssh_hash *hash) -{ - sha256_neon *s = container_of(hash, sha256_neon, hash); - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha256_neon_write(BinarySink *bs, const void *vp, size_t len) -{ - sha256_neon *s = BinarySink_DOWNCAST(bs, sha256_neon); - - while (len > 0) - if (sha256_block_write(&s->blk, &vp, &len)) - sha256_neon_block(&s->core, s->blk.block); -} - -static void sha256_neon_digest(ssh_hash *hash, uint8_t *digest) -{ - sha256_neon *s = container_of(hash, sha256_neon, hash); - - sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); - vst1q_u8(digest, vrev32q_u8(vreinterpretq_u8_u32(s->core.abcd))); - vst1q_u8(digest + 16, vrev32q_u8(vreinterpretq_u8_u32(s->core.efgh))); -} - -SHA256_VTABLE(neon, "NEON accelerated"); diff --git a/crypto/sha256-ni.c b/crypto/sha256-ni.c deleted file mode 100644 index 530fa4332..000000000 --- a/crypto/sha256-ni.c +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Hardware-accelerated implementation of SHA-256 using x86 SHA-NI. - */ - -#include "ssh.h" -#include "sha256.h" - -#include -#include -#include -#if HAVE_SHAINTRIN_H -#include -#endif - -#if defined(__clang__) || defined(__GNUC__) -#include -#define GET_CPU_ID_0(out) \ - __cpuid(0, (out)[0], (out)[1], (out)[2], (out)[3]) -#define GET_CPU_ID_7(out) \ - __cpuid_count(7, 0, (out)[0], (out)[1], (out)[2], (out)[3]) -#else -#define GET_CPU_ID_0(out) __cpuid(out, 0) -#define GET_CPU_ID_7(out) __cpuidex(out, 7, 0) -#endif - -static bool sha256_ni_available(void) -{ - unsigned int CPUInfo[4]; - GET_CPU_ID_0(CPUInfo); - if (CPUInfo[0] < 7) - return false; - - GET_CPU_ID_7(CPUInfo); - return CPUInfo[1] & (1 << 29); /* Check SHA */ -} - -/* SHA256 implementation using new instructions - The code is based on Jeffrey Walton's SHA256 implementation: - https://github.com/noloader/SHA-Intrinsics -*/ -static inline void sha256_ni_block(__m128i *core, const uint8_t *p) -{ - __m128i STATE0, STATE1; - __m128i MSG, TMP; - __m128i MSG0, MSG1, MSG2, MSG3; - const __m128i *block = (const __m128i *)p; - const __m128i MASK = _mm_set_epi64x( - 0x0c0d0e0f08090a0bULL, 0x0405060700010203ULL); - - /* Load initial values */ - STATE0 = core[0]; - STATE1 = core[1]; - - /* Rounds 0-3 */ - MSG = _mm_loadu_si128(block); - MSG0 = _mm_shuffle_epi8(MSG, MASK); - MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( - 0xE9B5DBA5B5C0FBCFULL, 0x71374491428A2F98ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - - /* Rounds 4-7 */ - MSG1 = _mm_loadu_si128(block + 1); - MSG1 = _mm_shuffle_epi8(MSG1, MASK); - MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( - 0xAB1C5ED5923F82A4ULL, 0x59F111F13956C25BULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); - - /* Rounds 8-11 */ - MSG2 = _mm_loadu_si128(block + 2); - MSG2 = _mm_shuffle_epi8(MSG2, MASK); - MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( - 0x550C7DC3243185BEULL, 0x12835B01D807AA98ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); - - /* Rounds 12-15 */ - MSG3 = _mm_loadu_si128(block + 3); - MSG3 = _mm_shuffle_epi8(MSG3, MASK); - MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( - 0xC19BF1749BDC06A7ULL, 0x80DEB1FE72BE5D74ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG3, MSG2, 4); - MSG0 = _mm_add_epi32(MSG0, TMP); - MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); - - /* Rounds 16-19 */ - MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( - 0x240CA1CC0FC19DC6ULL, 0xEFBE4786E49B69C1ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG0, MSG3, 4); - MSG1 = _mm_add_epi32(MSG1, TMP); - MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); - - /* Rounds 20-23 */ - MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( - 0x76F988DA5CB0A9DCULL, 0x4A7484AA2DE92C6FULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG1, MSG0, 4); - MSG2 = _mm_add_epi32(MSG2, TMP); - MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); - - /* Rounds 24-27 */ - MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( - 0xBF597FC7B00327C8ULL, 0xA831C66D983E5152ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG2, MSG1, 4); - MSG3 = _mm_add_epi32(MSG3, TMP); - MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); - - /* Rounds 28-31 */ - MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( - 0x1429296706CA6351ULL, 0xD5A79147C6E00BF3ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG3, MSG2, 4); - MSG0 = _mm_add_epi32(MSG0, TMP); - MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); - - /* Rounds 32-35 */ - MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( - 0x53380D134D2C6DFCULL, 0x2E1B213827B70A85ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG0, MSG3, 4); - MSG1 = _mm_add_epi32(MSG1, TMP); - MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); - - /* Rounds 36-39 */ - MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( - 0x92722C8581C2C92EULL, 0x766A0ABB650A7354ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG1, MSG0, 4); - MSG2 = _mm_add_epi32(MSG2, TMP); - MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1); - - /* Rounds 40-43 */ - MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( - 0xC76C51A3C24B8B70ULL, 0xA81A664BA2BFE8A1ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG2, MSG1, 4); - MSG3 = _mm_add_epi32(MSG3, TMP); - MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2); - - /* Rounds 44-47 */ - MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( - 0x106AA070F40E3585ULL, 0xD6990624D192E819ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG3, MSG2, 4); - MSG0 = _mm_add_epi32(MSG0, TMP); - MSG0 = _mm_sha256msg2_epu32(MSG0, MSG3); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3); - - /* Rounds 48-51 */ - MSG = _mm_add_epi32(MSG0, _mm_set_epi64x( - 0x34B0BCB52748774CULL, 0x1E376C0819A4C116ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG0, MSG3, 4); - MSG1 = _mm_add_epi32(MSG1, TMP); - MSG1 = _mm_sha256msg2_epu32(MSG1, MSG0); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - MSG3 = _mm_sha256msg1_epu32(MSG3, MSG0); - - /* Rounds 52-55 */ - MSG = _mm_add_epi32(MSG1, _mm_set_epi64x( - 0x682E6FF35B9CCA4FULL, 0x4ED8AA4A391C0CB3ULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG1, MSG0, 4); - MSG2 = _mm_add_epi32(MSG2, TMP); - MSG2 = _mm_sha256msg2_epu32(MSG2, MSG1); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - - /* Rounds 56-59 */ - MSG = _mm_add_epi32(MSG2, _mm_set_epi64x( - 0x8CC7020884C87814ULL, 0x78A5636F748F82EEULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - TMP = _mm_alignr_epi8(MSG2, MSG1, 4); - MSG3 = _mm_add_epi32(MSG3, TMP); - MSG3 = _mm_sha256msg2_epu32(MSG3, MSG2); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - - /* Rounds 60-63 */ - MSG = _mm_add_epi32(MSG3, _mm_set_epi64x( - 0xC67178F2BEF9A3F7ULL, 0xA4506CEB90BEFFFAULL)); - STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); - MSG = _mm_shuffle_epi32(MSG, 0x0E); - STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, MSG); - - /* Combine state */ - core[0] = _mm_add_epi32(STATE0, core[0]); - core[1] = _mm_add_epi32(STATE1, core[1]); -} - -typedef struct sha256_ni { - /* - * These two vectors store the 8 words of the SHA-256 state, but - * not in the same order they appear in the spec: the first word - * holds A,B,E,F and the second word C,D,G,H. - */ - __m128i core[2]; - sha256_block blk; - void *pointer_to_free; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha256_ni; - -static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len); - -static sha256_ni *sha256_ni_alloc(void) -{ - /* - * The __m128i variables in the context structure need to be - * 16-byte aligned, but not all malloc implementations that this - * code has to work with will guarantee to return a 16-byte - * aligned pointer. So we over-allocate, manually realign the - * pointer ourselves, and store the original one inside the - * context so we know how to free it later. - */ - void *allocation = smalloc(sizeof(sha256_ni) + 15); - uintptr_t alloc_address = (uintptr_t)allocation; - uintptr_t aligned_address = (alloc_address + 15) & ~15; - sha256_ni *s = (sha256_ni *)aligned_address; - s->pointer_to_free = allocation; - return s; -} - -static ssh_hash *sha256_ni_new(const ssh_hashalg *alg) -{ - const struct sha256_extra *extra = (const struct sha256_extra *)alg->extra; - if (!check_availability(extra)) - return NULL; - - sha256_ni *s = sha256_ni_alloc(); - - s->hash.vt = alg; - BinarySink_INIT(s, sha256_ni_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - - return &s->hash; -} - -static void sha256_ni_reset(ssh_hash *hash) -{ - sha256_ni *s = container_of(hash, sha256_ni, hash); - - /* Initialise the core vectors in their storage order */ - s->core[0] = _mm_set_epi64x( - 0x6a09e667bb67ae85ULL, 0x510e527f9b05688cULL); - s->core[1] = _mm_set_epi64x( - 0x3c6ef372a54ff53aULL, 0x1f83d9ab5be0cd19ULL); - - sha256_block_setup(&s->blk); -} - -static void sha256_ni_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha256_ni *copy = container_of(hcopy, sha256_ni, hash); - sha256_ni *orig = container_of(horig, sha256_ni, hash); - - void *ptf_save = copy->pointer_to_free; - *copy = *orig; /* structure copy */ - copy->pointer_to_free = ptf_save; - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha256_ni_free(ssh_hash *hash) -{ - sha256_ni *s = container_of(hash, sha256_ni, hash); - - void *ptf = s->pointer_to_free; - smemclr(s, sizeof(*s)); - sfree(ptf); -} - -static void sha256_ni_write(BinarySink *bs, const void *vp, size_t len) -{ - sha256_ni *s = BinarySink_DOWNCAST(bs, sha256_ni); - - while (len > 0) - if (sha256_block_write(&s->blk, &vp, &len)) - sha256_ni_block(s->core, s->blk.block); -} - -static void sha256_ni_digest(ssh_hash *hash, uint8_t *digest) -{ - sha256_ni *s = container_of(hash, sha256_ni, hash); - - sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); - - /* Rearrange the words into the output order */ - __m128i feba = _mm_shuffle_epi32(s->core[0], 0x1B); - __m128i dchg = _mm_shuffle_epi32(s->core[1], 0xB1); - __m128i dcba = _mm_blend_epi16(feba, dchg, 0xF0); - __m128i hgfe = _mm_alignr_epi8(dchg, feba, 8); - - /* Byte-swap them into the output endianness */ - const __m128i mask = _mm_setr_epi8(3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12); - dcba = _mm_shuffle_epi8(dcba, mask); - hgfe = _mm_shuffle_epi8(hgfe, mask); - - /* And store them */ - __m128i *output = (__m128i *)digest; - _mm_storeu_si128(output, dcba); - _mm_storeu_si128(output+1, hgfe); -} - -SHA256_VTABLE(ni, "SHA-NI accelerated"); diff --git a/crypto/sha256-select.c b/crypto/sha256-select.c deleted file mode 100644 index 78e5b7e4f..000000000 --- a/crypto/sha256-select.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Top-level vtables to select a SHA-256 implementation. - */ - -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sha256.h" - -static ssh_hash *sha256_select(const ssh_hashalg *alg) -{ - static const ssh_hashalg *const real_algs[] = { -#if HAVE_SHA_NI - &ssh_sha256_ni, -#endif -#if HAVE_NEON_CRYPTO - &ssh_sha256_neon, -#endif - &ssh_sha256_sw, - NULL, - }; - - for (size_t i = 0; real_algs[i]; i++) { - const ssh_hashalg *alg = real_algs[i]; - const struct sha256_extra *alg_extra = - (const struct sha256_extra *)alg->extra; - if (check_availability(alg_extra)) - return ssh_hash_new(alg); - } - - /* We should never reach the NULL at the end of the list, because - * the last non-NULL entry should be software-only SHA-256, which - * is always available. */ - unreachable("sha256_select ran off the end of its list"); -} - -const ssh_hashalg ssh_sha256 = { - .new = sha256_select, - .hlen = 32, - .blocklen = 64, - HASHALG_NAMES_ANNOTATED("SHA-256", "dummy selector vtable"), -}; diff --git a/crypto/sha256-sw.c b/crypto/sha256-sw.c deleted file mode 100644 index 82a116c62..000000000 --- a/crypto/sha256-sw.c +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Software implementation of SHA-256. - */ - -#include "ssh.h" -#include "sha256.h" - -static bool sha256_sw_available(void) -{ - /* Software SHA-256 is always available */ - return true; -} - -static inline uint32_t ror(uint32_t x, unsigned y) -{ - return (x << (31 & -y)) | (x >> (31 & y)); -} - -static inline uint32_t Ch(uint32_t ctrl, uint32_t if1, uint32_t if0) -{ - return if0 ^ (ctrl & (if1 ^ if0)); -} - -static inline uint32_t Maj(uint32_t x, uint32_t y, uint32_t z) -{ - return (x & y) | (z & (x | y)); -} - -static inline uint32_t Sigma_0(uint32_t x) -{ - return ror(x,2) ^ ror(x,13) ^ ror(x,22); -} - -static inline uint32_t Sigma_1(uint32_t x) -{ - return ror(x,6) ^ ror(x,11) ^ ror(x,25); -} - -static inline uint32_t sigma_0(uint32_t x) -{ - return ror(x,7) ^ ror(x,18) ^ (x >> 3); -} - -static inline uint32_t sigma_1(uint32_t x) -{ - return ror(x,17) ^ ror(x,19) ^ (x >> 10); -} - -static inline void sha256_sw_round( - unsigned round_index, const uint32_t *schedule, - uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, - uint32_t *e, uint32_t *f, uint32_t *g, uint32_t *h) -{ - uint32_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) + - sha256_round_constants[round_index] + schedule[round_index]; - - uint32_t t2 = Sigma_0(*a) + Maj(*a,*b,*c); - - *d += t1; - *h = t1 + t2; -} - -static void sha256_sw_block(uint32_t *core, const uint8_t *block) -{ - uint32_t w[SHA256_ROUNDS]; - uint32_t a,b,c,d,e,f,g,h; - - for (size_t t = 0; t < 16; t++) - w[t] = GET_32BIT_MSB_FIRST(block + 4*t); - - for (size_t t = 16; t < SHA256_ROUNDS; t++) - w[t] = sigma_1(w[t-2]) + w[t-7] + sigma_0(w[t-15]) + w[t-16]; - - a = core[0]; b = core[1]; c = core[2]; d = core[3]; - e = core[4]; f = core[5]; g = core[6]; h = core[7]; - - for (size_t t = 0; t < SHA256_ROUNDS; t += 8) { - sha256_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h); - sha256_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g); - sha256_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f); - sha256_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e); - sha256_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d); - sha256_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c); - sha256_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b); - sha256_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a); - } - - core[0] += a; core[1] += b; core[2] += c; core[3] += d; - core[4] += e; core[5] += f; core[6] += g; core[7] += h; - - smemclr(w, sizeof(w)); -} - -typedef struct sha256_sw { - uint32_t core[8]; - sha256_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha256_sw; - -static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha256_sw_new(const ssh_hashalg *alg) -{ - sha256_sw *s = snew(sha256_sw); - - s->hash.vt = alg; - BinarySink_INIT(s, sha256_sw_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha256_sw_reset(ssh_hash *hash) -{ - sha256_sw *s = container_of(hash, sha256_sw, hash); - - memcpy(s->core, sha256_initial_state, sizeof(s->core)); - sha256_block_setup(&s->blk); -} - -static void sha256_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha256_sw *copy = container_of(hcopy, sha256_sw, hash); - sha256_sw *orig = container_of(horig, sha256_sw, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha256_sw_free(ssh_hash *hash) -{ - sha256_sw *s = container_of(hash, sha256_sw, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha256_sw_write(BinarySink *bs, const void *vp, size_t len) -{ - sha256_sw *s = BinarySink_DOWNCAST(bs, sha256_sw); - - while (len > 0) - if (sha256_block_write(&s->blk, &vp, &len)) - sha256_sw_block(s->core, s->blk.block); -} - -static void sha256_sw_digest(ssh_hash *hash, uint8_t *digest) -{ - sha256_sw *s = container_of(hash, sha256_sw, hash); - - sha256_block_pad(&s->blk, BinarySink_UPCAST(s)); - for (size_t i = 0; i < 8; i++) - PUT_32BIT_MSB_FIRST(digest + 4*i, s->core[i]); -} - -SHA256_VTABLE(sw, "unaccelerated"); diff --git a/crypto/sha256.h b/crypto/sha256.h deleted file mode 100644 index e6ca75640..000000000 --- a/crypto/sha256.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Definitions likely to be helpful to multiple SHA-256 implementations. - */ - -/* - * The 'extra' structure used by SHA-256 implementations is used to - * include information about how to check if a given implementation is - * available at run time, and whether we've already checked. - */ -struct sha256_extra_mutable; -struct sha256_extra { - /* Function to check availability. Might be expensive, so we don't - * want to call it more than once. */ - bool (*check_available)(void); - - /* Point to a writable substructure. */ - struct sha256_extra_mutable *mut; -}; -struct sha256_extra_mutable { - bool checked_availability; - bool is_available; -}; -static inline bool check_availability(const struct sha256_extra *extra) -{ - if (!extra->mut->checked_availability) { - extra->mut->is_available = extra->check_available(); - extra->mut->checked_availability = true; - } - - return extra->mut->is_available; -} - -/* - * Macro to define a SHA-256 vtable together with its 'extra' - * structure. - */ -#define SHA256_VTABLE(impl_c, impl_display) \ - static struct sha256_extra_mutable sha256_ ## impl_c ## _extra_mut; \ - static const struct sha256_extra sha256_ ## impl_c ## _extra = { \ - .check_available = sha256_ ## impl_c ## _available, \ - .mut = &sha256_ ## impl_c ## _extra_mut, \ - }; \ - const ssh_hashalg ssh_sha256_ ## impl_c = { \ - .new = sha256_ ## impl_c ## _new, \ - .reset = sha256_ ## impl_c ## _reset, \ - .copyfrom = sha256_ ## impl_c ## _copyfrom, \ - .digest = sha256_ ## impl_c ## _digest, \ - .free = sha256_ ## impl_c ## _free, \ - .hlen = 32, \ - .blocklen = 64, \ - HASHALG_NAMES_ANNOTATED("SHA-256", impl_display), \ - .extra = &sha256_ ## impl_c ## _extra, \ - } - -extern const uint32_t sha256_initial_state[8]; -extern const uint32_t sha256_round_constants[64]; - -#define SHA256_ROUNDS 64 - -typedef struct sha256_block sha256_block; -struct sha256_block { - uint8_t block[64]; - size_t used; - uint64_t len; -}; - -static inline void sha256_block_setup(sha256_block *blk) -{ - blk->used = 0; - blk->len = 0; -} - -static inline bool sha256_block_write( - sha256_block *blk, const void **vdata, size_t *len) -{ - size_t blkleft = sizeof(blk->block) - blk->used; - size_t chunk = *len < blkleft ? *len : blkleft; - - const uint8_t *p = *vdata; - memcpy(blk->block + blk->used, p, chunk); - *vdata = p + chunk; - *len -= chunk; - blk->used += chunk; - blk->len += chunk; - - if (blk->used == sizeof(blk->block)) { - blk->used = 0; - return true; - } - - return false; -} - -static inline void sha256_block_pad(sha256_block *blk, BinarySink *bs) -{ - uint64_t final_len = blk->len << 3; - size_t pad = 1 + (63 & (55 - blk->used)); - - put_byte(bs, 0x80); - for (size_t i = 1; i < pad; i++) - put_byte(bs, 0); - put_uint64(bs, final_len); - - assert(blk->used == 0 && "Should have exactly hit a block boundary"); -} diff --git a/crypto/sha3.c b/crypto/sha3.c deleted file mode 100644 index 83d136bff..000000000 --- a/crypto/sha3.c +++ /dev/null @@ -1,329 +0,0 @@ -/* - * SHA-3, as defined in FIPS PUB 202. - */ - -#include -#include -#include "ssh.h" - -static inline uint64_t rol(uint64_t x, unsigned shift) -{ - unsigned L = (+shift) & 63; - unsigned R = (-shift) & 63; - return (x << L) | (x >> R); -} - -/* - * General Keccak is defined such that its state is a 5x5 array of - * words which can be any power-of-2 size from 1 up to 64. SHA-3 fixes - * on 64, and so do we. - * - * The number of rounds is defined as 12 + 2k if the word size is 2^k. - * Here we have 64-bit words only, so k=6, so 24 rounds always. - */ -typedef uint64_t keccak_core_state[5][5]; -#define NROUNDS 24 /* would differ for other word sizes */ -static const uint64_t round_constants[NROUNDS]; -static const unsigned rotation_counts[5][5]; - -/* - * Core Keccak transform: just squodge the state around internally, - * without adding or extracting any data from it. - */ -static void keccak_transform(keccak_core_state A) -{ - union { - uint64_t C[5]; - uint64_t B[5][5]; - } u; - - for (unsigned round = 0; round < NROUNDS; round++) { - /* theta step */ - for (unsigned x = 0; x < 5; x++) - u.C[x] = A[x][0] ^ A[x][1] ^ A[x][2] ^ A[x][3] ^ A[x][4]; - for (unsigned x = 0; x < 5; x++) { - uint64_t D = rol(u.C[(x+1) % 5], 1) ^ u.C[(x+4) % 5]; - for (unsigned y = 0; y < 5; y++) - A[x][y] ^= D; - } - - /* rho and pi steps */ - for (unsigned x = 0; x < 5; x++) - for (unsigned y = 0; y < 5; y++) - u.B[y][(2*x+3*y) % 5] = rol(A[x][y], rotation_counts[x][y]); - - /* chi step */ - for (unsigned x = 0; x < 5; x++) - for (unsigned y = 0; y < 5; y++) - A[x][y] = u.B[x][y] ^ (u.B[(x+2)%5][y] & ~u.B[(x+1)%5][y]); - - /* iota step */ - A[0][0] ^= round_constants[round]; - } - - smemclr(&u, sizeof(u)); -} - -typedef struct { - keccak_core_state A; - unsigned char bytes[25*8]; - unsigned char first_pad_byte; - size_t bytes_got, bytes_wanted, hash_bytes; -} keccak_state; - -/* - * Keccak accumulation function: given a piece of message, add it to - * the hash. - */ -static void keccak_accumulate(keccak_state *s, const void *vdata, size_t len) -{ - const unsigned char *data = (const unsigned char *)vdata; - - while (len >= s->bytes_wanted - s->bytes_got) { - size_t b = s->bytes_wanted - s->bytes_got; - memcpy(s->bytes + s->bytes_got, data, b); - len -= b; - data += b; - - size_t n = 0; - for (unsigned y = 0; y < 5; y++) { - for (unsigned x = 0; x < 5; x++) { - if (n >= s->bytes_wanted) - break; - - s->A[x][y] ^= GET_64BIT_LSB_FIRST(s->bytes + n); - n += 8; - } - } - keccak_transform(s->A); - - s->bytes_got = 0; - } - - memcpy(s->bytes + s->bytes_got, data, len); - s->bytes_got += len; -} - -/* - * Keccak output function. - */ -static void keccak_output(keccak_state *s, void *voutput) -{ - unsigned char *output = (unsigned char *)voutput; - - /* - * Add message padding. - */ - { - unsigned char padding[25*8]; - size_t len = s->bytes_wanted - s->bytes_got; - if (len == 0) - len = s->bytes_wanted; - memset(padding, 0, len); - padding[0] |= s->first_pad_byte; - padding[len-1] |= 0x80; - keccak_accumulate(s, padding, len); - } - - size_t n = 0; - for (unsigned y = 0; y < 5; y++) { - for (unsigned x = 0; x < 5; x++) { - size_t to_copy = s->hash_bytes - n; - if (to_copy == 0) - break; - if (to_copy > 8) - to_copy = 8; - unsigned char outbytes[8]; - PUT_64BIT_LSB_FIRST(outbytes, s->A[x][y]); - memcpy(output + n, outbytes, to_copy); - n += to_copy; - } - } -} - -static void keccak_init(keccak_state *s, unsigned hashbits, unsigned ratebits, - unsigned char first_pad_byte) -{ - int x, y; - - assert(hashbits % 8 == 0); - assert(ratebits % 8 == 0); - - s->hash_bytes = hashbits / 8; - s->bytes_wanted = (25 * 64 - ratebits) / 8; - s->bytes_got = 0; - s->first_pad_byte = first_pad_byte; - - assert(s->bytes_wanted % 8 == 0); - - for (y = 0; y < 5; y++) - for (x = 0; x < 5; x++) - s->A[x][y] = 0; -} - -static void keccak_sha3_init(keccak_state *s, int hashbits) -{ - keccak_init(s, hashbits, hashbits * 2, 0x06); -} - -static void keccak_shake_init(keccak_state *s, int parambits, int hashbits) -{ - keccak_init(s, hashbits, parambits * 2, 0x1f); -} - -/* - * Keccak round constants, generated via the LFSR specified in the - * Keccak reference by the following piece of Python: - -import textwrap -from functools import reduce - -rbytes = [1] -while len(rbytes) < 7*24: - k = rbytes[-1] * 2 - rbytes.append(k ^ (0x171 * (k >> 8))) - -rbits = [byte & 1 for byte in rbytes] - -rwords = [sum(rbits[i+j] << ((1 << j) - 1) for j in range(7)) - for i in range(0, len(rbits), 7)] - -print(textwrap.indent("\n".join(textwrap.wrap(", ".join( - map("0x{:016x}".format, rwords)))), " "*4)) - -*/ - -static const uint64_t round_constants[24] = { - 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, - 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, - 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, - 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, - 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, - 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, - 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, - 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 -}; - -/* - * Keccak per-element rotation counts, generated from the matrix - * formula in the Keccak reference by the following piece of Python: - -coords = [1, 0] -while len(coords) < 26: - coords.append((2*coords[-2] + 3*coords[-1]) % 5) - -matrix = { (coords[i], coords[i+1]) : i for i in range(24) } -matrix[0,0] = -1 - -f = lambda t: (t+1) * (t+2) // 2 % 64 - -for y in range(5): - print(" {{{}}},".format(", ".join("{:2d}".format(f(matrix[y,x])) - for x in range(5)))) - -*/ -static const unsigned rotation_counts[5][5] = { - { 0, 36, 3, 41, 18}, - { 1, 44, 10, 45, 2}, - {62, 6, 43, 15, 61}, - {28, 55, 25, 21, 56}, - {27, 20, 39, 8, 14}, -}; - -/* - * The PuTTY ssh_hashalg abstraction. - */ -struct keccak_hash { - keccak_state state; - ssh_hash hash; - BinarySink_IMPLEMENTATION; -}; - -static void keccak_BinarySink_write(BinarySink *bs, const void *p, size_t len) -{ - struct keccak_hash *kh = BinarySink_DOWNCAST(bs, struct keccak_hash); - keccak_accumulate(&kh->state, p, len); -} - -static ssh_hash *keccak_new(const ssh_hashalg *alg) -{ - struct keccak_hash *kh = snew(struct keccak_hash); - kh->hash.vt = alg; - BinarySink_INIT(kh, keccak_BinarySink_write); - BinarySink_DELEGATE_INIT(&kh->hash, kh); - return ssh_hash_reset(&kh->hash); -} - -static void keccak_free(ssh_hash *hash) -{ - struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash); - smemclr(kh, sizeof(*kh)); - sfree(kh); -} - -static void keccak_copyfrom(ssh_hash *hnew, ssh_hash *hold) -{ - struct keccak_hash *khold = container_of(hold, struct keccak_hash, hash); - struct keccak_hash *khnew = container_of(hnew, struct keccak_hash, hash); - khnew->state = khold->state; -} - -static void keccak_digest(ssh_hash *hash, unsigned char *output) -{ - struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash); - keccak_output(&kh->state, output); -} - -static void sha3_reset(ssh_hash *hash) -{ - struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash); - keccak_sha3_init(&kh->state, hash->vt->hlen * 8); -} - -#define DEFINE_SHA3(bits) \ - const ssh_hashalg ssh_sha3_##bits = { \ - .new = keccak_new, \ - .reset = sha3_reset, \ - .copyfrom = keccak_copyfrom, \ - .digest = keccak_digest, \ - .free = keccak_free, \ - .hlen = bits/8, \ - .blocklen = 200 - 2*(bits/8), \ - HASHALG_NAMES_BARE("SHA3-" #bits), \ - } - -DEFINE_SHA3(224); -DEFINE_SHA3(256); -DEFINE_SHA3(384); -DEFINE_SHA3(512); - -static void shake256_reset(ssh_hash *hash) -{ - struct keccak_hash *kh = container_of(hash, struct keccak_hash, hash); - keccak_shake_init(&kh->state, 256, hash->vt->hlen * 8); -} - -/* - * There is some confusion over the output length parameter for the - * SHAKE functions. By my reading, FIPS PUB 202 defines SHAKE256(M,d) - * to generate d _bits_ of output. But RFC 8032 (defining Ed448) talks - * about "SHAKE256(x,114)" in a context where it definitely means - * generating 114 _bytes_ of output. - * - * Our internal ID therefore suffixes the output length with "bytes", - * to be clear which we're talking about - */ - -#define DEFINE_SHAKE(param, hashbytes) \ - const ssh_hashalg ssh_shake##param##_##hashbytes##bytes = { \ - .new = keccak_new, \ - .reset = shake##param##_reset, \ - .copyfrom = keccak_copyfrom, \ - .digest = keccak_digest, \ - .free = keccak_free, \ - .hlen = hashbytes, \ - .blocklen = 0, \ - HASHALG_NAMES_BARE("SHAKE" #param), \ - } - -DEFINE_SHAKE(256, 114); diff --git a/crypto/sha512-common.c b/crypto/sha512-common.c deleted file mode 100644 index 89ac136c9..000000000 --- a/crypto/sha512-common.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Common variable definitions across all the SHA-512 implementations. - */ - -#include "ssh.h" -#include "sha512.h" - -const uint64_t sha512_initial_state[8] = { - 0x6a09e667f3bcc908ULL, - 0xbb67ae8584caa73bULL, - 0x3c6ef372fe94f82bULL, - 0xa54ff53a5f1d36f1ULL, - 0x510e527fade682d1ULL, - 0x9b05688c2b3e6c1fULL, - 0x1f83d9abfb41bd6bULL, - 0x5be0cd19137e2179ULL, -}; - -const uint64_t sha384_initial_state[8] = { - 0xcbbb9d5dc1059ed8ULL, - 0x629a292a367cd507ULL, - 0x9159015a3070dd17ULL, - 0x152fecd8f70e5939ULL, - 0x67332667ffc00b31ULL, - 0x8eb44a8768581511ULL, - 0xdb0c2e0d64f98fa7ULL, - 0x47b5481dbefa4fa4ULL, -}; - -const uint64_t sha512_round_constants[80] = { - 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, - 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, - 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, - 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, - 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, - 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, - 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, - 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, - 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, - 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, - 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, - 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, - 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, - 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, - 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, - 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, - 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, - 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, - 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, - 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, - 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, - 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, - 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, - 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, - 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, - 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, - 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, - 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, - 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, - 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, - 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, - 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, - 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, - 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, - 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, - 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, - 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, - 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, - 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, - 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL, -}; diff --git a/crypto/sha512-neon.c b/crypto/sha512-neon.c deleted file mode 100644 index 849a79d7f..000000000 --- a/crypto/sha512-neon.c +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Hardware-accelerated implementation of SHA-512 using Arm NEON. - */ - -#include "ssh.h" -#include "sha512.h" - -#if USE_ARM64_NEON_H -#include -#else -#include -#endif - -static bool sha512_neon_available(void) -{ - /* - * For Arm, we delegate to a per-platform detection function (see - * explanation in aes-neon.c). - */ - return platform_sha512_neon_available(); -} - -#if !HAVE_NEON_SHA512_INTRINSICS -/* - * clang 12 and before do not provide the SHA-512 NEON intrinsics, but - * do provide assembler support for the underlying instructions. So I - * define the intrinsic functions myself, using inline assembler. - */ -static inline uint64x2_t vsha512su0q_u64(uint64x2_t x, uint64x2_t y) -{ - __asm__("sha512su0 %0.2D,%1.2D" : "+w" (x) : "w" (y)); - return x; -} -static inline uint64x2_t vsha512su1q_u64(uint64x2_t x, uint64x2_t y, - uint64x2_t z) -{ - __asm__("sha512su1 %0.2D,%1.2D,%2.2D" : "+w" (x) : "w" (y), "w" (z)); - return x; -} -static inline uint64x2_t vsha512hq_u64(uint64x2_t x, uint64x2_t y, - uint64x2_t z) -{ - __asm__("sha512h %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z)); - return x; -} -static inline uint64x2_t vsha512h2q_u64(uint64x2_t x, uint64x2_t y, - uint64x2_t z) -{ - __asm__("sha512h2 %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z)); - return x; -} -#endif /* HAVE_NEON_SHA512_INTRINSICS */ - -typedef struct sha512_neon_core sha512_neon_core; -struct sha512_neon_core { - uint64x2_t ab, cd, ef, gh; -}; - -static inline uint64x2_t sha512_neon_load_input(const uint8_t *p) -{ - return vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p))); -} - -static inline uint64x2_t sha512_neon_schedule_update( - uint64x2_t m8, uint64x2_t m7, uint64x2_t m4, uint64x2_t m3, uint64x2_t m1) -{ - /* - * vsha512su0q_u64() takes words from a long way back in the - * schedule and performs the sigma_0 half of the computation of - * the next two 64-bit message-schedule words. - * - * vsha512su1q_u64() combines the result of that with the sigma_1 - * steps, to output the finished version of those two words. The - * total amount of input data it requires fits nicely into three - * 128-bit vector registers, but one of those registers is - * misaligned compared to the 128-bit chunks that the message - * schedule is stored in. So we use vextq_u64 to make one of its - * input words out of the second half of m4 and the first half of - * m3. - */ - return vsha512su1q_u64(vsha512su0q_u64(m8, m7), m1, vextq_u64(m4, m3, 1)); -} - -static inline void sha512_neon_round2( - unsigned round_index, uint64x2_t schedule_words, - uint64x2_t *ab, uint64x2_t *cd, uint64x2_t *ef, uint64x2_t *gh) -{ - /* - * vsha512hq_u64 performs the Sigma_1 and Ch half of the - * computation of two rounds of SHA-512 (including feeding back - * one of the outputs from the first of those half-rounds into the - * second one). - * - * vsha512h2q_u64 combines the result of that with the Sigma_0 and - * Maj steps, and outputs one 128-bit vector that replaces the gh - * piece of the input hash state, and a second that updates cd by - * addition. - * - * Similarly to vsha512su1q_u64 above, some of the input registers - * expected by these instructions are misaligned by 64 bits - * relative to the chunks we've divided the hash state into, so we - * have to start by making 'de' and 'fg' words out of our input - * cd,ef,gh, using vextq_u64. - * - * Also, one of the inputs to vsha512hq_u64 is expected to contain - * the results of summing gh + two round constants + two words of - * message schedule, but the two words of the message schedule - * have to be the opposite way round in the vector register from - * the way that vsha512su1q_u64 output them. Hence, there's - * another vextq_u64 in here that swaps the two halves of the - * initial_sum vector register. - * - * (This also means that I don't have to prepare a specially - * reordered version of the sha512_round_constants[] array: as - * long as I'm unavoidably doing a swap at run time _anyway_, I - * can load from the normally ordered version of that array, and - * just take care to fold in that data _before_ the swap rather - * than after.) - */ - - /* Load two round constants, with the first one in the low half */ - uint64x2_t round_constants = vld1q_u64( - sha512_round_constants + round_index); - - /* Add schedule words to round constants */ - uint64x2_t initial_sum = vaddq_u64(schedule_words, round_constants); - - /* Swap that sum around so the word used in the first of the two - * rounds is in the _high_ half of the vector, matching where h - * lives in the gh vector */ - uint64x2_t swapped_initial_sum = vextq_u64(initial_sum, initial_sum, 1); - - /* Add gh to that, now that they're matching ways round */ - uint64x2_t sum = vaddq_u64(swapped_initial_sum, *gh); - - /* Make the misaligned de and fg words */ - uint64x2_t de = vextq_u64(*cd, *ef, 1); - uint64x2_t fg = vextq_u64(*ef, *gh, 1); - - /* Now we're ready to put all the pieces together. The output from - * vsha512h2q_u64 can be used directly as the new gh, and the - * output from vsha512hq_u64 is simultaneously the intermediate - * value passed to h2 and the thing you have to add on to cd. */ - uint64x2_t intermed = vsha512hq_u64(sum, fg, de); - *gh = vsha512h2q_u64(intermed, *cd, *ab); - *cd = vaddq_u64(*cd, intermed); -} - -static inline void sha512_neon_block(sha512_neon_core *core, const uint8_t *p) -{ - uint64x2_t s0, s1, s2, s3, s4, s5, s6, s7; - - uint64x2_t ab = core->ab, cd = core->cd, ef = core->ef, gh = core->gh; - - s0 = sha512_neon_load_input(p + 16*0); - sha512_neon_round2(0, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_load_input(p + 16*1); - sha512_neon_round2(2, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_load_input(p + 16*2); - sha512_neon_round2(4, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_load_input(p + 16*3); - sha512_neon_round2(6, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_load_input(p + 16*4); - sha512_neon_round2(8, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_load_input(p + 16*5); - sha512_neon_round2(10, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_load_input(p + 16*6); - sha512_neon_round2(12, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_load_input(p + 16*7); - sha512_neon_round2(14, s7, &cd, &ef, &gh, &ab); - s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); - sha512_neon_round2(16, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); - sha512_neon_round2(18, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); - sha512_neon_round2(20, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); - sha512_neon_round2(22, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); - sha512_neon_round2(24, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); - sha512_neon_round2(26, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); - sha512_neon_round2(28, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); - sha512_neon_round2(30, s7, &cd, &ef, &gh, &ab); - s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); - sha512_neon_round2(32, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); - sha512_neon_round2(34, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); - sha512_neon_round2(36, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); - sha512_neon_round2(38, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); - sha512_neon_round2(40, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); - sha512_neon_round2(42, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); - sha512_neon_round2(44, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); - sha512_neon_round2(46, s7, &cd, &ef, &gh, &ab); - s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); - sha512_neon_round2(48, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); - sha512_neon_round2(50, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); - sha512_neon_round2(52, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); - sha512_neon_round2(54, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); - sha512_neon_round2(56, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); - sha512_neon_round2(58, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); - sha512_neon_round2(60, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); - sha512_neon_round2(62, s7, &cd, &ef, &gh, &ab); - s0 = sha512_neon_schedule_update(s0, s1, s4, s5, s7); - sha512_neon_round2(64, s0, &ab, &cd, &ef, &gh); - s1 = sha512_neon_schedule_update(s1, s2, s5, s6, s0); - sha512_neon_round2(66, s1, &gh, &ab, &cd, &ef); - s2 = sha512_neon_schedule_update(s2, s3, s6, s7, s1); - sha512_neon_round2(68, s2, &ef, &gh, &ab, &cd); - s3 = sha512_neon_schedule_update(s3, s4, s7, s0, s2); - sha512_neon_round2(70, s3, &cd, &ef, &gh, &ab); - s4 = sha512_neon_schedule_update(s4, s5, s0, s1, s3); - sha512_neon_round2(72, s4, &ab, &cd, &ef, &gh); - s5 = sha512_neon_schedule_update(s5, s6, s1, s2, s4); - sha512_neon_round2(74, s5, &gh, &ab, &cd, &ef); - s6 = sha512_neon_schedule_update(s6, s7, s2, s3, s5); - sha512_neon_round2(76, s6, &ef, &gh, &ab, &cd); - s7 = sha512_neon_schedule_update(s7, s0, s3, s4, s6); - sha512_neon_round2(78, s7, &cd, &ef, &gh, &ab); - - core->ab = vaddq_u64(core->ab, ab); - core->cd = vaddq_u64(core->cd, cd); - core->ef = vaddq_u64(core->ef, ef); - core->gh = vaddq_u64(core->gh, gh); -} - -typedef struct sha512_neon { - sha512_neon_core core; - sha512_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha512_neon; - -static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha512_neon_new(const ssh_hashalg *alg) -{ - const struct sha512_extra *extra = (const struct sha512_extra *)alg->extra; - if (!check_availability(extra)) - return NULL; - - sha512_neon *s = snew(sha512_neon); - - s->hash.vt = alg; - BinarySink_INIT(s, sha512_neon_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha512_neon_reset(ssh_hash *hash) -{ - sha512_neon *s = container_of(hash, sha512_neon, hash); - const struct sha512_extra *extra = - (const struct sha512_extra *)hash->vt->extra; - - s->core.ab = vld1q_u64(extra->initial_state); - s->core.cd = vld1q_u64(extra->initial_state+2); - s->core.ef = vld1q_u64(extra->initial_state+4); - s->core.gh = vld1q_u64(extra->initial_state+6); - - sha512_block_setup(&s->blk); -} - -static void sha512_neon_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha512_neon *copy = container_of(hcopy, sha512_neon, hash); - sha512_neon *orig = container_of(horig, sha512_neon, hash); - - *copy = *orig; /* structure copy */ - - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha512_neon_free(ssh_hash *hash) -{ - sha512_neon *s = container_of(hash, sha512_neon, hash); - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha512_neon_write(BinarySink *bs, const void *vp, size_t len) -{ - sha512_neon *s = BinarySink_DOWNCAST(bs, sha512_neon); - - while (len > 0) - if (sha512_block_write(&s->blk, &vp, &len)) - sha512_neon_block(&s->core, s->blk.block); -} - -static void sha512_neon_digest(ssh_hash *hash, uint8_t *digest) -{ - sha512_neon *s = container_of(hash, sha512_neon, hash); - - sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); - - vst1q_u8(digest, vrev64q_u8(vreinterpretq_u8_u64(s->core.ab))); - vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd))); - vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef))); - vst1q_u8(digest+48, vrev64q_u8(vreinterpretq_u8_u64(s->core.gh))); -} - -static void sha384_neon_digest(ssh_hash *hash, uint8_t *digest) -{ - sha512_neon *s = container_of(hash, sha512_neon, hash); - - sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); - - vst1q_u8(digest, vrev64q_u8(vreinterpretq_u8_u64(s->core.ab))); - vst1q_u8(digest+16, vrev64q_u8(vreinterpretq_u8_u64(s->core.cd))); - vst1q_u8(digest+32, vrev64q_u8(vreinterpretq_u8_u64(s->core.ef))); -} - -SHA512_VTABLES(neon, "NEON accelerated"); diff --git a/crypto/sha512-select.c b/crypto/sha512-select.c deleted file mode 100644 index ecd567bd7..000000000 --- a/crypto/sha512-select.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Top-level vtables to select a SHA-512 implementation. - */ - -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sha512.h" - -static const ssh_hashalg *const real_sha512_algs[] = { -#if HAVE_NEON_SHA512 - &ssh_sha512_neon, -#endif - &ssh_sha512_sw, - NULL, -}; - -static const ssh_hashalg *const real_sha384_algs[] = { -#if HAVE_NEON_SHA512 - &ssh_sha384_neon, -#endif - &ssh_sha384_sw, - NULL, -}; - -static ssh_hash *sha512_select(const ssh_hashalg *alg) -{ - const ssh_hashalg *const *real_algs = - (const ssh_hashalg *const *)alg->extra; - - for (size_t i = 0; real_algs[i]; i++) { - const ssh_hashalg *alg = real_algs[i]; - const struct sha512_extra *alg_extra = - (const struct sha512_extra *)alg->extra; - if (check_availability(alg_extra)) - return ssh_hash_new(alg); - } - - /* We should never reach the NULL at the end of the list, because - * the last non-NULL entry should be software-only SHA-512, which - * is always available. */ - unreachable("sha512_select ran off the end of its list"); -} - -const ssh_hashalg ssh_sha512 = { - .new = sha512_select, - .hlen = 64, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-512", "dummy selector vtable"), - .extra = real_sha512_algs, -}; - -const ssh_hashalg ssh_sha384 = { - .new = sha512_select, - .hlen = 48, - .blocklen = 128, - HASHALG_NAMES_ANNOTATED("SHA-384", "dummy selector vtable"), - .extra = real_sha384_algs, -}; diff --git a/crypto/sha512-sw.c b/crypto/sha512-sw.c deleted file mode 100644 index 9e47bbb93..000000000 --- a/crypto/sha512-sw.c +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Software implementation of SHA-512. - */ - -#include "ssh.h" -#include "sha512.h" - -static bool sha512_sw_available(void) -{ - /* Software SHA-512 is always available */ - return true; -} - -static inline uint64_t ror(uint64_t x, unsigned y) -{ - return (x << (63 & -y)) | (x >> (63 & y)); -} - -static inline uint64_t Ch(uint64_t ctrl, uint64_t if1, uint64_t if0) -{ - return if0 ^ (ctrl & (if1 ^ if0)); -} - -static inline uint64_t Maj(uint64_t x, uint64_t y, uint64_t z) -{ - return (x & y) | (z & (x | y)); -} - -static inline uint64_t Sigma_0(uint64_t x) -{ - return ror(x,28) ^ ror(x,34) ^ ror(x,39); -} - -static inline uint64_t Sigma_1(uint64_t x) -{ - return ror(x,14) ^ ror(x,18) ^ ror(x,41); -} - -static inline uint64_t sigma_0(uint64_t x) -{ - return ror(x,1) ^ ror(x,8) ^ (x >> 7); -} - -static inline uint64_t sigma_1(uint64_t x) -{ - return ror(x,19) ^ ror(x,61) ^ (x >> 6); -} - -static inline void sha512_sw_round( - unsigned round_index, const uint64_t *schedule, - uint64_t *a, uint64_t *b, uint64_t *c, uint64_t *d, - uint64_t *e, uint64_t *f, uint64_t *g, uint64_t *h) -{ - uint64_t t1 = *h + Sigma_1(*e) + Ch(*e,*f,*g) + - sha512_round_constants[round_index] + schedule[round_index]; - - uint64_t t2 = Sigma_0(*a) + Maj(*a,*b,*c); - - *d += t1; - *h = t1 + t2; -} - -static void sha512_sw_block(uint64_t *core, const uint8_t *block) -{ - uint64_t w[SHA512_ROUNDS]; - uint64_t a,b,c,d,e,f,g,h; - - int t; - - for (t = 0; t < 16; t++) - w[t] = GET_64BIT_MSB_FIRST(block + 8*t); - - for (t = 16; t < SHA512_ROUNDS; t++) - w[t] = w[t-16] + w[t-7] + sigma_0(w[t-15]) + sigma_1(w[t-2]); - - a = core[0]; b = core[1]; c = core[2]; d = core[3]; - e = core[4]; f = core[5]; g = core[6]; h = core[7]; - - for (t = 0; t < SHA512_ROUNDS; t+=8) { - sha512_sw_round(t+0, w, &a,&b,&c,&d,&e,&f,&g,&h); - sha512_sw_round(t+1, w, &h,&a,&b,&c,&d,&e,&f,&g); - sha512_sw_round(t+2, w, &g,&h,&a,&b,&c,&d,&e,&f); - sha512_sw_round(t+3, w, &f,&g,&h,&a,&b,&c,&d,&e); - sha512_sw_round(t+4, w, &e,&f,&g,&h,&a,&b,&c,&d); - sha512_sw_round(t+5, w, &d,&e,&f,&g,&h,&a,&b,&c); - sha512_sw_round(t+6, w, &c,&d,&e,&f,&g,&h,&a,&b); - sha512_sw_round(t+7, w, &b,&c,&d,&e,&f,&g,&h,&a); - } - - core[0] += a; core[1] += b; core[2] += c; core[3] += d; - core[4] += e; core[5] += f; core[6] += g; core[7] += h; - - smemclr(w, sizeof(w)); -} - -typedef struct sha512_sw { - uint64_t core[8]; - sha512_block blk; - BinarySink_IMPLEMENTATION; - ssh_hash hash; -} sha512_sw; - -static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len); - -static ssh_hash *sha512_sw_new(const ssh_hashalg *alg) -{ - sha512_sw *s = snew(sha512_sw); - - s->hash.vt = alg; - BinarySink_INIT(s, sha512_sw_write); - BinarySink_DELEGATE_INIT(&s->hash, s); - return &s->hash; -} - -static void sha512_sw_reset(ssh_hash *hash) -{ - sha512_sw *s = container_of(hash, sha512_sw, hash); - const struct sha512_extra *extra = - (const struct sha512_extra *)hash->vt->extra; - - memcpy(s->core, extra->initial_state, sizeof(s->core)); - sha512_block_setup(&s->blk); -} - -static void sha512_sw_copyfrom(ssh_hash *hcopy, ssh_hash *horig) -{ - sha512_sw *copy = container_of(hcopy, sha512_sw, hash); - sha512_sw *orig = container_of(horig, sha512_sw, hash); - - memcpy(copy, orig, sizeof(*copy)); - BinarySink_COPIED(copy); - BinarySink_DELEGATE_INIT(©->hash, copy); -} - -static void sha512_sw_free(ssh_hash *hash) -{ - sha512_sw *s = container_of(hash, sha512_sw, hash); - - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void sha512_sw_write(BinarySink *bs, const void *vp, size_t len) -{ - sha512_sw *s = BinarySink_DOWNCAST(bs, sha512_sw); - - while (len > 0) - if (sha512_block_write(&s->blk, &vp, &len)) - sha512_sw_block(s->core, s->blk.block); -} - -static void sha512_sw_digest(ssh_hash *hash, uint8_t *digest) -{ - sha512_sw *s = container_of(hash, sha512_sw, hash); - - sha512_block_pad(&s->blk, BinarySink_UPCAST(s)); - for (size_t i = 0; i < hash->vt->hlen / 8; i++) - PUT_64BIT_MSB_FIRST(digest + 8*i, s->core[i]); -} - -/* - * This implementation doesn't need separate digest methods for - * SHA-384 and SHA-512, because the above implementation reads the - * hash length out of the vtable. - */ -#define sha384_sw_digest sha512_sw_digest - -SHA512_VTABLES(sw, "unaccelerated"); diff --git a/crypto/sha512.h b/crypto/sha512.h deleted file mode 100644 index 98145558e..000000000 --- a/crypto/sha512.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Definitions likely to be helpful to multiple SHA-512 implementations. - */ - -/* - * The 'extra' structure used by SHA-512 implementations is used to - * include information about how to check if a given implementation is - * available at run time, and whether we've already checked. - */ -struct sha512_extra_mutable; -struct sha512_extra { - /* Pointer to the initial state (distinguishes SHA-384 from -512) */ - const uint64_t *initial_state; - - /* Function to check availability. Might be expensive, so we don't - * want to call it more than once. */ - bool (*check_available)(void); - - /* Point to a writable substructure. */ - struct sha512_extra_mutable *mut; -}; -struct sha512_extra_mutable { - bool checked_availability; - bool is_available; -}; -static inline bool check_availability(const struct sha512_extra *extra) -{ - if (!extra->mut->checked_availability) { - extra->mut->is_available = extra->check_available(); - extra->mut->checked_availability = true; - } - - return extra->mut->is_available; -} - -/* - * Macro to define a pair of SHA-{384,512} vtables together with their - * 'extra' structure. - */ -#define SHA512_VTABLES(impl_c, impl_display) \ - static struct sha512_extra_mutable sha512_ ## impl_c ## _extra_mut; \ - static const struct sha512_extra sha384_ ## impl_c ## _extra = { \ - .initial_state = sha384_initial_state, \ - .check_available = sha512_ ## impl_c ## _available, \ - .mut = &sha512_ ## impl_c ## _extra_mut, \ - }; \ - static const struct sha512_extra sha512_ ## impl_c ## _extra = { \ - .initial_state = sha512_initial_state, \ - .check_available = sha512_ ## impl_c ## _available, \ - .mut = &sha512_ ## impl_c ## _extra_mut, \ - }; \ - const ssh_hashalg ssh_sha384_ ## impl_c = { \ - .new = sha512_ ## impl_c ## _new, \ - .reset = sha512_ ## impl_c ## _reset, \ - .copyfrom = sha512_ ## impl_c ## _copyfrom, \ - .digest = sha384_ ## impl_c ## _digest, \ - .free = sha512_ ## impl_c ## _free, \ - .hlen = 48, \ - .blocklen = 128, \ - HASHALG_NAMES_ANNOTATED("SHA-384", impl_display), \ - .extra = &sha384_ ## impl_c ## _extra, \ - }; \ - const ssh_hashalg ssh_sha512_ ## impl_c = { \ - .new = sha512_ ## impl_c ## _new, \ - .reset = sha512_ ## impl_c ## _reset, \ - .copyfrom = sha512_ ## impl_c ## _copyfrom, \ - .digest = sha512_ ## impl_c ## _digest, \ - .free = sha512_ ## impl_c ## _free, \ - .hlen = 64, \ - .blocklen = 128, \ - HASHALG_NAMES_ANNOTATED("SHA-512", impl_display), \ - .extra = &sha512_ ## impl_c ## _extra, \ - } - -extern const uint64_t sha512_initial_state[8]; -extern const uint64_t sha384_initial_state[8]; -extern const uint64_t sha512_round_constants[80]; - -#define SHA512_ROUNDS 80 - -typedef struct sha512_block sha512_block; -struct sha512_block { - uint8_t block[128]; - size_t used; - uint64_t lenhi, lenlo; -}; - -static inline void sha512_block_setup(sha512_block *blk) -{ - blk->used = 0; - blk->lenhi = blk->lenlo = 0; -} - -static inline bool sha512_block_write( - sha512_block *blk, const void **vdata, size_t *len) -{ - size_t blkleft = sizeof(blk->block) - blk->used; - size_t chunk = *len < blkleft ? *len : blkleft; - - const uint8_t *p = *vdata; - memcpy(blk->block + blk->used, p, chunk); - *vdata = p + chunk; - *len -= chunk; - blk->used += chunk; - - size_t chunkbits = chunk << 3; - - blk->lenlo += chunkbits; - blk->lenhi += (blk->lenlo < chunkbits); - - if (blk->used == sizeof(blk->block)) { - blk->used = 0; - return true; - } - - return false; -} - -static inline void sha512_block_pad(sha512_block *blk, BinarySink *bs) -{ - uint64_t final_lenhi = blk->lenhi; - uint64_t final_lenlo = blk->lenlo; - size_t pad = 127 & (111 - blk->used); - - put_byte(bs, 0x80); - put_padding(bs, pad, 0); - put_uint64(bs, final_lenhi); - put_uint64(bs, final_lenlo); - - assert(blk->used == 0 && "Should have exactly hit a block boundary"); -} diff --git a/crypto/xdmauth.c b/crypto/xdmauth.c deleted file mode 100644 index 86339b85a..000000000 --- a/crypto/xdmauth.c +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Convenience functions to encrypt and decrypt the cookies used in - * XDM-AUTHORIZATION-1. - */ - -#include "ssh.h" - -static ssh_cipher *des_xdmauth_cipher(const void *vkeydata) -{ - /* - * XDM-AUTHORIZATION-1 uses single-DES, but packs the key into 7 - * bytes, so here we have to repack it manually into the canonical - * form where it occupies 8 bytes each with the low bit unused. - */ - const unsigned char *keydata = (const unsigned char *)vkeydata; - 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; - } - - ssh_cipher *c = ssh_cipher_new(&ssh_des); - ssh_cipher_setkey(c, key); - smemclr(key, sizeof(key)); - ssh_cipher_setiv(c, key); - return c; -} - -void des_encrypt_xdmauth(const void *keydata, void *blk, int len) -{ - ssh_cipher *c = des_xdmauth_cipher(keydata); - ssh_cipher_encrypt(c, blk, len); - ssh_cipher_free(c); -} - -void des_decrypt_xdmauth(const void *keydata, void *blk, int len) -{ - ssh_cipher *c = des_xdmauth_cipher(keydata); - ssh_cipher_decrypt(c, blk, len); - ssh_cipher_free(c); -} diff --git a/defs.h b/defs.h deleted file mode 100644 index 286e0c962..000000000 --- a/defs.h +++ /dev/null @@ -1,268 +0,0 @@ -/* - * defs.h: initial definitions for PuTTY. - * - * The rule about this header file is that it can't depend on any - * other header file in this code base. This is where we define - * things, as much as we can, that other headers will want to refer - * to, such as opaque structure types and their associated typedefs, - * or macros that are used by other headers. - */ - -#ifndef PUTTY_DEFS_H -#define PUTTY_DEFS_H - -#ifdef NDEBUG -/* - * PuTTY is a security project, so assertions are important - if an - * assumption is violated, proceeding anyway may have far worse - * consequences than simple program termination. This check and #error - * should arrange that we don't ever accidentally compile assertions - * out. - */ -#error Do not compile this code base with NDEBUG defined! -#endif - -#if HAVE_CMAKE_H -#include "cmake.h" -#endif - -#include -#include -#include /* for __MINGW_PRINTF_FORMAT */ -#include - -#if defined _MSC_VER && _MSC_VER < 1800 -/* Work around lack of inttypes.h and strtoumax in older MSVC */ -#define PRIx32 "x" -#define PRIu32 "u" -#define PRIu64 "I64u" -#define PRIdMAX "I64d" -#define PRIXMAX "I64X" -#define SCNu64 "I64u" -#define SIZEx "Ix" -#define SIZEu "Iu" -uintmax_t strtoumax(const char *nptr, char **endptr, int base); -/* Also, define a LEGACY_WINDOWS flag to enable other workarounds */ -#define LEGACY_WINDOWS -#else -#include -/* Because we still support older MSVC libraries which don't recognise the - * standard C "z" modifier for size_t-sized integers, we must use an - * inttypes.h-style macro for those */ -#define SIZEx "zx" -#define SIZEu "zu" -#endif - -#if defined __GNUC__ || defined __clang__ -/* - * On MinGW, the correct compiler format checking for vsnprintf() etc - * can depend on compile-time flags; these control whether you get - * ISO C or Microsoft's non-standard format strings. - * We sometimes use __attribute__ ((format)) for our own printf-like - * functions, which are ultimately interpreted by the toolchain-chosen - * printf, so we need to take that into account to get correct warnings. - */ -#ifdef __MINGW_PRINTF_FORMAT -#define PRINTF_LIKE(fmt_index, ellipsis_index) \ - __attribute__ ((format (__MINGW_PRINTF_FORMAT, fmt_index, ellipsis_index))) -#else -#define PRINTF_LIKE(fmt_index, ellipsis_index) \ - __attribute__ ((format (printf, fmt_index, ellipsis_index))) -#endif -#else /* __GNUC__ */ -#define PRINTF_LIKE(fmt_index, ellipsis_index) -#endif /* __GNUC__ */ - -typedef struct conf_tag Conf; -typedef struct terminal_tag Terminal; -typedef struct term_utf8_decode term_utf8_decode; - -typedef struct Filename Filename; -typedef struct FontSpec FontSpec; - -typedef struct bufchain_tag bufchain; - -typedef struct strbuf strbuf; -typedef struct LoadedFile LoadedFile; - -typedef struct RSAKey RSAKey; - -typedef struct BinarySink BinarySink; -typedef struct BinarySource BinarySource; -typedef struct stdio_sink stdio_sink; -typedef struct bufchain_sink bufchain_sink; -typedef struct handle_sink handle_sink; - -typedef struct IdempotentCallback IdempotentCallback; - -typedef struct SockAddr SockAddr; - -typedef struct Socket Socket; -typedef struct Plug Plug; -typedef struct SocketPeerInfo SocketPeerInfo; -typedef struct DeferredSocketOpener DeferredSocketOpener; -typedef struct DeferredSocketOpenerVtable DeferredSocketOpenerVtable; - -typedef struct Backend Backend; -typedef struct BackendVtable BackendVtable; -typedef struct Interactor Interactor; -typedef struct InteractorVtable InteractorVtable; -typedef struct InteractionReadySeat InteractionReadySeat; - -typedef struct Ldisc_tag Ldisc; -typedef struct LogContext LogContext; -typedef struct LogPolicy LogPolicy; -typedef struct LogPolicyVtable LogPolicyVtable; - -typedef struct Seat Seat; -typedef struct SeatVtable SeatVtable; -typedef struct SeatDialogText SeatDialogText; -typedef struct SeatDialogTextItem SeatDialogTextItem; -typedef struct SeatDialogPromptDescriptions SeatDialogPromptDescriptions; -typedef struct SeatPromptResult SeatPromptResult; - -typedef struct cmdline_get_passwd_input_state cmdline_get_passwd_input_state; - -typedef struct TermWin TermWin; -typedef struct TermWinVtable TermWinVtable; - -typedef struct Ssh Ssh; - -typedef struct mp_int mp_int; -typedef struct MontyContext MontyContext; - -typedef struct WeierstrassCurve WeierstrassCurve; -typedef struct WeierstrassPoint WeierstrassPoint; -typedef struct MontgomeryCurve MontgomeryCurve; -typedef struct MontgomeryPoint MontgomeryPoint; -typedef struct EdwardsCurve EdwardsCurve; -typedef struct EdwardsPoint EdwardsPoint; - -typedef struct SshServerConfig SshServerConfig; -typedef struct SftpServer SftpServer; -typedef struct SftpServerVtable SftpServerVtable; - -typedef struct Channel Channel; -typedef struct SshChannel SshChannel; -typedef struct mainchan mainchan; - -typedef struct CertExprBuilder CertExprBuilder; - -typedef struct ssh_sharing_state ssh_sharing_state; -typedef struct ssh_sharing_connstate ssh_sharing_connstate; -typedef struct share_channel share_channel; - -typedef struct PortFwdManager PortFwdManager; -typedef struct PortFwdRecord PortFwdRecord; -typedef struct ConnectionLayer ConnectionLayer; - -typedef struct prng prng; -typedef struct ssh_hashalg ssh_hashalg; -typedef struct ssh_hash ssh_hash; -typedef struct ssh_kex ssh_kex; -typedef struct ssh_kexes ssh_kexes; -typedef struct ssh_keyalg ssh_keyalg; -typedef struct ssh_key ssh_key; -typedef struct ssh_compressor ssh_compressor; -typedef struct ssh_decompressor ssh_decompressor; -typedef struct ssh_compression_alg ssh_compression_alg; -typedef struct ssh2_userkey ssh2_userkey; -typedef struct ssh2_macalg ssh2_macalg; -typedef struct ssh2_mac ssh2_mac; -typedef struct ssh_cipheralg ssh_cipheralg; -typedef struct ssh_cipher ssh_cipher; -typedef struct ssh2_ciphers ssh2_ciphers; -typedef struct dh_ctx dh_ctx; -typedef struct ecdh_key ecdh_key; -typedef struct ecdh_keyalg ecdh_keyalg; -typedef struct NTRUKeyPair NTRUKeyPair; -typedef struct NTRUEncodeSchedule NTRUEncodeSchedule; - -typedef struct dlgparam dlgparam; -typedef struct dlgcontrol dlgcontrol; - -typedef struct settings_w settings_w; -typedef struct settings_r settings_r; -typedef struct settings_e settings_e; -typedef struct ca_options ca_options; -typedef struct host_ca host_ca; -typedef struct host_ca_enum host_ca_enum; - -typedef struct SessionSpecial SessionSpecial; - -typedef struct StripCtrlChars StripCtrlChars; - -typedef struct BidiContext BidiContext; - -/* - * A small structure wrapping up a (pointer, length) pair so that it - * can be conveniently passed to or from a function. - */ -typedef struct ptrlen { - const void *ptr; - size_t len; -} ptrlen; - -typedef struct logblank_t logblank_t; - -typedef struct BinaryPacketProtocol BinaryPacketProtocol; -typedef struct PacketProtocolLayer PacketProtocolLayer; - -struct unicode_data; - -/* Do a compile-time type-check of 'to_check' (without evaluating it), - * as a side effect of returning the value 'to_return'. Note that - * although this macro double-*expands* to_return, it always - * *evaluates* exactly one copy of it, so it's side-effect safe. */ -#define TYPECHECK(to_check, to_return) \ - (sizeof(to_check) ? (to_return) : (to_return)) - -/* Return a pointer to the object of structure type 'type' whose field - * with name 'field' is pointed at by 'object'. */ -#define container_of(object, type, field) \ - TYPECHECK(object == &((type *)0)->field, \ - ((type *)(((char *)(object)) - offsetof(type, field)))) - -#if defined __GNUC__ || defined __clang__ -#define NORETURN __attribute__((__noreturn__)) -#elif defined _MSC_VER -#define NORETURN __declspec(noreturn) -#else -#define NORETURN -#endif - -/* - * Standard macro definitions. STR() behaves like the preprocessor - * stringification # operator, and CAT() behaves like the token paste - * ## operator, except that each one macro-expands its argument(s) - * first, unlike the raw version. E.g. - * - * #__LINE__ -> "__LINE__" - * STR(__LINE__) -> "1234" (or whatever) - * - * and similarly, - * - * foo ## __LINE__ -> foo__LINE__ - * CAT(foo, __LINE__) -> foo1234 (or whatever) - * - * The expansion is achieved by having each macro pass its arguments - * to a secondary inner macro, because parameter lists of a macro call - * get expanded before the called macro is invoked. So STR(__LINE__) - * -> STR_INNER(1234) -> #1234 -> "1234", and similarly for CAT. - */ -#define STR_INNER(x) #x -#define STR(x) STR_INNER(x) -#define CAT_INNER(x,y) x ## y -#define CAT(x,y) CAT_INNER(x,y) - -/* - * Structure shared between ssh.h and storage.h, giving strictness - * options relating to checking of an OpenSSH certificate. It's a bit - * cheaty to put something so specific in here, but more painful to - * put it in putty.h. - */ -struct ca_options { - bool permit_rsa_sha1, permit_rsa_sha256, permit_rsa_sha512; -}; - -#endif /* PUTTY_DEFS_H */ diff --git a/dialog.c b/dialog.c deleted file mode 100644 index b9306982d..000000000 --- a/dialog.c +++ /dev/null @@ -1,469 +0,0 @@ -/* - * 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(const 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(const char *p1, const 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, const char *path, bool 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, const char *path, int index) -{ - if (index < 0) - index = ctrl_find_set(b, path, true); - 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, - const char *path, const char *title) -{ - - struct controlset *s = snew(struct controlset); - int index = ctrl_find_set(b, path, true); - 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; - sgrowarray(b->ctrlsets, b->ctrlsetsize, b->nctrlsets); - 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, const char *path, - const char *name, const char *boxtitle) -{ - struct controlset *s; - int index = ctrl_find_set(b, path, true); - 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; - sgrowarray(b->ctrlsets, b->ctrlsetsize, b->nctrlsets); - 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); - sgrowarray(b->frees, b->freesize, b->nfrees); - 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 dlgcontrol *ctrl_new(struct controlset *s, int type, - HelpCtx helpctx, handler_fn handler, - intorptr context) -{ - dlgcontrol *c = snew(dlgcontrol); - sgrowarray(s->ctrls, s->ctrlsize, s->ncontrols); - s->ctrls[s->ncontrols++] = c; - /* - * Fill in the standard fields. - */ - c->type = type; - c->delay_taborder = false; - c->column = COLUMN_FIELD(0, s->ncolumns); - c->helpctx = helpctx; - c->handler = handler; - c->context = context; - c->label = NULL; - c->align_next_to = NULL; - return c; -} - -/* `ncolumns' is followed by that many percentages, as integers. */ -dlgcontrol *ctrl_columns(struct controlset *s, int ncolumns, ...) -{ - dlgcontrol *c = ctrl_new(s, CTRL_COLUMNS, NULL_HELPCTX, 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; -} - -dlgcontrol *ctrl_editbox(struct controlset *s, const char *label, - char shortcut, int percentage, - HelpCtx helpctx, handler_fn handler, - intorptr context, intorptr context2) -{ - dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); - c->label = label ? dupstr(label) : NULL; - c->editbox.shortcut = shortcut; - c->editbox.percentwidth = percentage; - c->editbox.password = false; - c->editbox.has_list = false; - c->context2 = context2; - return c; -} - -dlgcontrol *ctrl_combobox(struct controlset *s, const char *label, - char shortcut, int percentage, - HelpCtx helpctx, handler_fn handler, - intorptr context, intorptr context2) -{ - dlgcontrol *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); - c->label = label ? dupstr(label) : NULL; - c->editbox.shortcut = shortcut; - c->editbox.percentwidth = percentage; - c->editbox.password = false; - c->editbox.has_list = true; - c->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. - */ -dlgcontrol *ctrl_radiobuttons_fn(struct controlset *s, const char *label, - char shortcut, int ncolumns, HelpCtx helpctx, - handler_fn handler, intorptr context, ...) -{ - va_list ap; - int i; - dlgcontrol *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context); - c->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; -} - -dlgcontrol *ctrl_pushbutton(struct controlset *s, const char *label, - char shortcut, HelpCtx helpctx, - handler_fn handler, intorptr context) -{ - dlgcontrol *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context); - c->label = label ? dupstr(label) : NULL; - c->button.shortcut = shortcut; - c->button.isdefault = false; - c->button.iscancel = false; - return c; -} - -dlgcontrol *ctrl_listbox(struct controlset *s, const char *label, - char shortcut, HelpCtx helpctx, - handler_fn handler, intorptr context) -{ - dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); - c->label = label ? dupstr(label) : NULL; - c->listbox.shortcut = shortcut; - c->listbox.height = 5; /* *shrug* a plausible default */ - c->listbox.draglist = false; - c->listbox.multisel = 0; - c->listbox.percentwidth = 100; - c->listbox.ncols = 0; - c->listbox.percentages = NULL; - c->listbox.hscroll = true; - return c; -} - -dlgcontrol *ctrl_droplist(struct controlset *s, const char *label, - char shortcut, int percentage, HelpCtx helpctx, - handler_fn handler, intorptr context) -{ - dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); - c->label = label ? dupstr(label) : NULL; - c->listbox.shortcut = shortcut; - c->listbox.height = 0; /* means it's a drop-down list */ - c->listbox.draglist = false; - c->listbox.multisel = 0; - c->listbox.percentwidth = percentage; - c->listbox.ncols = 0; - c->listbox.percentages = NULL; - c->listbox.hscroll = false; - return c; -} - -dlgcontrol *ctrl_draglist(struct controlset *s, const char *label, - char shortcut, HelpCtx helpctx, - handler_fn handler, intorptr context) -{ - dlgcontrol *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); - c->label = label ? dupstr(label) : NULL; - c->listbox.shortcut = shortcut; - c->listbox.height = 5; /* *shrug* a plausible default */ - c->listbox.draglist = true; - c->listbox.multisel = 0; - c->listbox.percentwidth = 100; - c->listbox.ncols = 0; - c->listbox.percentages = NULL; - c->listbox.hscroll = false; - return c; -} - -dlgcontrol *ctrl_filesel(struct controlset *s, const char *label, - char shortcut, const char *filter, bool write, - const char *title, HelpCtx helpctx, - handler_fn handler, intorptr context) -{ - dlgcontrol *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context); - c->label = label ? dupstr(label) : NULL; - c->fileselect.shortcut = shortcut; - c->fileselect.filter = filter; - c->fileselect.for_writing = write; - c->fileselect.title = dupstr(title); - c->fileselect.just_button = false; - return c; -} - -dlgcontrol *ctrl_fontsel(struct controlset *s, const char *label, - char shortcut, HelpCtx helpctx, - handler_fn handler, intorptr context) -{ - dlgcontrol *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context); - c->label = label ? dupstr(label) : NULL; - c->fontselect.shortcut = shortcut; - return c; -} - -dlgcontrol *ctrl_tabdelay(struct controlset *s, dlgcontrol *ctrl) -{ - dlgcontrol *c = ctrl_new(s, CTRL_TABDELAY, NULL_HELPCTX, NULL, P(NULL)); - c->tabdelay.ctrl = ctrl; - return c; -} - -dlgcontrol *ctrl_text(struct controlset *s, const char *text, - HelpCtx helpctx) -{ - dlgcontrol *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL)); - c->label = dupstr(text); - c->text.wrap = true; - return c; -} - -dlgcontrol *ctrl_checkbox(struct controlset *s, const char *label, - char shortcut, HelpCtx helpctx, - handler_fn handler, intorptr context) -{ - dlgcontrol *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context); - c->label = label ? dupstr(label) : NULL; - c->checkbox.shortcut = shortcut; - return c; -} - -void ctrl_free(dlgcontrol *ctrl) -{ - int i; - - sfree(ctrl->label); - switch (ctrl->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/dialog.h b/dialog.h deleted file mode 100644 index ef9a9dfe8..000000000 --- a/dialog.h +++ /dev/null @@ -1,695 +0,0 @@ -/* - * Exports and types from dialog.c. - */ - -/* - * 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; const void *cp; int i; } intorptr; - -#ifndef INLINE -intorptr I(int i); -intorptr P(void *p); -intorptr CP(const 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; } -PREFIX intorptr CP(const void *p) { intorptr ret; ret.cp = 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 ) - -/* - * 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)(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event); - -struct dlgcontrol { - /* - * Generic fields shared by all the control types. - */ - 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 `delay_taborder' is true, it indicates that this particular - * control should not yet appear in the tab order. A subsequent - * CTRL_TABDELAY entry will place it. - */ - bool delay_taborder; - /* - * 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 one or two pieces of `void *' or `int' data. - */ - intorptr context, context2; - /* - * 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. - */ - HelpCtx helpctx; - /* - * Setting this to non-NULL coerces two or more controls to have - * their y-coordinates adjusted so that they can sit alongside - * each other and look nicely aligned, even if they're different - * heights. - * - * Set this field on later controls (in terms of order in the data - * structure), pointing back to earlier ones, so that when each - * control is instantiated, the referred-to one is already there - * to be referred to. - * - * Don't expect this to change the position of the _first_ - * control. Currently, the layout is done one control at a time, - * so that once the first control has been placed, the second one - * can't cause the first one to be retrospectively moved. - */ - dlgcontrol *align_next_to; - - /* - * Union of further fields specific to each control type. - */ - union { - struct { /* for CTRL_TABDELAY */ - dlgcontrol *ctrl; - } tabdelay; - struct { /* for CTRL_EDITBOX */ - 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; - bool 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. - */ - bool has_list; - } editbox; - struct { /* for CTRL_RADIO */ - /* - * `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 { /* for CTRL_CHECKBOX */ - char shortcut; - } checkbox; - struct { /* for CTRL_BUTTON */ - 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. - */ - bool isdefault; - /* - * Also, the reverse of this: a default cancel-type - * button, which is implicitly pressed when you hit - * Escape. - */ - bool iscancel; - } button; - struct { /* for CTRL_LISTBOX */ - 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! - */ - bool 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. - */ - bool hscroll; - } listbox; - struct { /* for CTRL_FILESELECT */ - 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). - */ - bool 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; - /* - * Reduce the file selector to just a single browse - * button. - * - * Normally, a file selector is used to set a config - * option that consists of a file name, so that that file - * will be read or written at run time. In that situation, - * it makes sense to have an edit box showing the - * currently selected file name, and a button to change it - * interactively. - * - * But occasionally a file selector is used to load a file - * _during_ configuration. For example, host CA public - * keys are entered directly into the configuration as - * strings, not stored by reference to a filename; but if - * you have one in a file, you want to be able to load it - * during the lifetime of the CA config box rather than - * awkwardly copy-pasting it. So in that case you just - * want a 'pop up a file chooser' button, and when that - * delivers a file name, you'll deal with it there and - * then and write some other thing (like the file's - * contents) into a nearby edit box. - * - * If you set this flag, then you may not call - * dlg_filesel_set on the file selector at all, because it - * doesn't store a filename. And you can only call - * dlg_filesel_get on it in the handler for EVENT_ACTION, - * which is what will be sent to you when the user has - * used it to choose a filename. - */ - bool just_button; - } fileselect; - struct { /* for CTRL_COLUMNS */ - /* In this variant, `label' MUST be NULL. */ - 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 { /* for CTRL_FONTSELECT */ - char shortcut; - } fontselect; - struct { /* for CTRL_TEXT */ - /* - * If this is true (the default), the text will wrap on to - * multiple lines. If false, it will stay on the same - * line, with a horizontal scrollbar if necessary. - */ - bool wrap; - } text; - }; -}; - -#undef STANDARD_PREFIX - -/* - * `controlset' is a container holding an array of `dlgcontrol' - * 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 */ - size_t ncontrols; /* number of `dlgcontrol' in array */ - size_t ctrlsize; /* allocated size of array */ - dlgcontrol **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 { - size_t nctrlsets; /* number of ctrlsets */ - size_t ctrlsetsize; /* ctrlset size */ - struct controlset **ctrlsets; /* actual array of ctrlsets */ - size_t nfrees; - size_t 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 *, - const char *path, const char *title); -/* Retrieve a pointer to a controlset, creating it if absent. */ -struct controlset *ctrl_getset(struct controlbox *, const char *path, - const char *name, const char *boxtitle); -void ctrl_free_set(struct controlset *); - -void ctrl_free(dlgcontrol *); - -/* - * 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 `dlgcontrol' 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 `dlgcontrol' it created, so that final tweaks - * can be made. - */ - -/* `ncolumns' is followed by that many percentages, as integers. */ -dlgcontrol *ctrl_columns(struct controlset *, int ncolumns, ...); -dlgcontrol *ctrl_editbox(struct controlset *, const char *label, - char shortcut, int percentage, HelpCtx helpctx, - handler_fn handler, - intorptr context, intorptr context2); -dlgcontrol *ctrl_combobox(struct controlset *, const char *label, - char shortcut, int percentage, HelpCtx 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. - */ -dlgcontrol *ctrl_radiobuttons_fn(struct controlset *, const char *label, - char shortcut, int ncolumns, HelpCtx helpctx, - handler_fn handler, intorptr context, ...); -#define ctrl_radiobuttons(...) \ - ctrl_radiobuttons_fn(__VA_ARGS__, (const char *)NULL) -dlgcontrol *ctrl_pushbutton(struct controlset *, const char *label, - char shortcut, HelpCtx helpctx, - handler_fn handler, intorptr context); -dlgcontrol *ctrl_listbox(struct controlset *, const char *label, - char shortcut, HelpCtx helpctx, - handler_fn handler, intorptr context); -dlgcontrol *ctrl_droplist(struct controlset *, const char *label, - char shortcut, int percentage, HelpCtx helpctx, - handler_fn handler, intorptr context); -dlgcontrol *ctrl_draglist(struct controlset *, const char *label, - char shortcut, HelpCtx helpctx, - handler_fn handler, intorptr context); -dlgcontrol *ctrl_filesel(struct controlset *, const char *label, - char shortcut, const char *filter, bool write, - const char *title, HelpCtx helpctx, - handler_fn handler, intorptr context); -dlgcontrol *ctrl_fontsel(struct controlset *, const char *label, - char shortcut, HelpCtx helpctx, - handler_fn handler, intorptr context); -dlgcontrol *ctrl_text(struct controlset *, const char *text, - HelpCtx helpctx); -dlgcontrol *ctrl_checkbox(struct controlset *, const char *label, - char shortcut, HelpCtx helpctx, - handler_fn handler, intorptr context); -dlgcontrol *ctrl_tabdelay(struct controlset *, dlgcontrol *); - -/* - * Routines the platform-independent dialog code can call to read - * and write the values of controls. - */ -void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton); -int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp); -void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked); -bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp); -void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text); -char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp); /* result must be freed by caller */ -void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, - size_t start, size_t len); -/* The `listbox' functions can also apply to combo boxes. */ -void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp); -void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index); -void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, 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(dlgcontrol *ctrl, dlgparam *dp, - char const *text, int id); -int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index); -/* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp); -bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index); -void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index); -void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text); -void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn); -Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp); -void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fn); -FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp); -/* - * 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(dlgcontrol *ctrl, dlgparam *dp); -void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp); -/* - * Set input focus into a particular control. - */ -void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp); -/* - * Change the label text on a control. - */ -void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, 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! - */ -dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp); -/* - * Find out whether a particular control is currently visible. - */ -bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp); -/* - * 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(dlgparam *dp); -void dlg_error_msg(dlgparam *dp, const 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(dlgparam *dp, 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(dlgcontrol *ctrl, dlgparam *dp, - int r, int g, int b); -bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, - 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(dlgcontrol *ctrl, dlgparam *dp); - -/* - * 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, const char *path, int index); -int ctrl_path_elements(const 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(const char *p1, const char *p2); - -/* - * Normalise the align_next_to fields in a controlset so that they - * form a backwards linked list. - */ -void ctrlset_normalise_aligns(struct controlset *s); diff --git a/doc/CMakeLists.txt.terabox.uploading.cfg b/doc/CMakeLists.txt.terabox.uploading.cfg deleted file mode 100644 index e69de29bb..000000000 diff --git a/doc/authplugin.but b/doc/authplugin.but deleted file mode 100644 index 41bc0daac..000000000 --- a/doc/authplugin.but +++ /dev/null @@ -1,519 +0,0 @@ -\A{authplugin} PuTTY authentication plugin protocol - -This appendix contains the specification for the protocol spoken over -local IPC between PuTTY and an authentication helper plugin. - -If you already have an authentication plugin and want to configure -PuTTY to use it, see \k{config-ssh-authplugin} for how to do that. -This appendix is for people writing new authentication plugins. - -\H{authplugin-req} Requirements - -The following requirements informed the specification of this protocol. - -\s{Automate keyboard-interactive authentication.} We're motivated in -the first place by the observation that the general SSH userauth -method \cq{keyboard-interactive} (defined in \k{authplugin-ref-ki}) -can be used for many kinds of challenge/response or one-time-password -styles of authentication, and in more than one of those, the necessary -responses might be obtained from an auxiliary network connection, such -as an HTTPS transaction. So it's useful if a user doesn't have to -manually copy-type or copy-paste from their web browser into their SSH -client, but instead, the process can be automated. - -\s{Be able to pass prompts on to the user.} On the other hand, some -userauth methods can be only \e{partially} automated; some of the -server's prompts might still require human input. Also, the plugin -automating the authentication might need to ask its own questions that -are not provided by the SSH server. (For example, \q{please enter the -master key that the real response will be generated by hashing}.) So -after the plugin intercepts the server's questions, it needs to be -able to ask its own questions of the user, which may or may not be the -same questions sent by the server. - -\s{Allow automatic generation of the username.} Sometimes, the -authentication method comes with a mechanism for discovering the -username to be used in the SSH login. So the plugin has to start up -early enough that the client hasn't committed to a username yet. - -\s{Future expansion route to other SSH userauth flavours.} The initial -motivation for this protocol is specific to keyboard-interactive. But -other SSH authentication methods exist, and they may also benefit from -automation in future. We're making no attempt here to predict what -those methods might be or how they might be automated, but we do need -to leave a space where they can be slotted in later if necessary. - -\s{Minimal information loss.} Keyboard-interactive prompts and replies -should be passed to and from the plugin in a form as close as possible -to the way they look on the wire in SSH itself. Therefore, the -protocol resembles SSH in its data formats and marshalling (instead -of, for example, translating from SSH binary packet style to another -well-known format such as JSON, which would introduce edge cases in -character encoding). - -\s{Half-duplex.} Simultaneously trying to read one I/O stream and -write another adds a lot of complexity to software. It becomes -necessary to have an organised event loop containing \cw{select} or -\cw{WaitForMultipleObjects} or similar, which can invoke the handler -for whichever event happens soonest. There's no need to add that -complexity in an application like this, which isn't transferring large -amounts of bulk data or multiplexing unrelated activities. So, to keep -life simple for plugin authors, we set the ground rule that it must -always be 100% clear which side is supposed to be sending a message -next. That way, the plugin can be written as sequential code -progressing through the protocol, making simple read and write calls -to receive or send each message. - -\s{Communicate success/failure, to facilitate caching in the plugin.} -A plugin might want to cache recently used data for next time, but -only in the case where authentication using that data was actually -successful. So the client has to tell the plugin what the outcome was, -if it's known. (But this is best-effort only. Obviously the plugin -cannot \e{depend} on hearing the answer, because any IPC protocol at -all carries the risk that the other end might crash or be killed by -things outside its control.) - -\H{authplugin-transport} Transport and configuration - -Plugins are executable programs on the client platform. - -The SSH client must be manually configured to use a plugin for a -particular connection. The configuration takes the form of a command -line, including the location of the plugin executable, and optionally -command-line arguments that are meaningful to the particular plugin. - -The client invokes the plugin as a subprocess, passing it a pair of -8-bit-clean pipes as its standard input and output. On those pipes, -the client and plugin will communicate via the protocol specified -below. - -\H{authplugin-formats} Data formats and marshalling - -This protocol borrows the low-level data formatting from SSH itself, -in particular the following wire encodings from -\k{authplugin-ref-arch} section 5: - -\dt \s{byte} - -\dd An integer between 0 and 0xFF inclusive, transmitted as a single -byte of binary data. - -\dt \s{boolean} - -\dd The values \q{true} or \q{false}, transmitted as the bytes 1 and 0 -respectively. - -\dt \s{uint32} - -\dd An integer between 0 and 0xFFFFFFFF inclusive, transmitted as 4 -bytes of binary data, in big-endian (\q{network}) byte order. - -\dt \s{string} - -\dd A sequence of bytes, preceded by a \s{uint32} giving the number of -bytes in the sequence. The length field does not include itself. For -example, the empty string is represented by four zero bytes (the -\s{uint32} encoding of 0); the string "AB" is represented by the six -bytes 0,0,0,2,'A','B'. - -Unlike SSH itself, the protocol spoken between the client and the -plugin is unencrypted, because local inter-process pipes are assumed -to be secured by the OS kernel. So the binary packet protocol is much -simpler than SSH proper, and is similar to SFTP and the OpenSSH agent -protocol. - -The data sent in each direction of the conversation consists of a -sequence of \s{messages} exchanged between the SSH client and the -plugin. Each message is encoded as a \s{string}. The contents of the -string begin with a \s{byte} giving the message type, which determines -the format of the rest of the message. - -\H{authplugin-version} Protocol versioning - -This protocol itself is versioned. At connection setup, the client -states the highest version number it knows how to speak, and then the -plugin responds by choosing the version number that will actually be -spoken (which may not be higher than the client's value). - -Including a version number makes it possible to make breaking changes -to the protocol later. - -Even version numbers represent released versions of this spec. Odd -numbers represent drafts or development versions in between releases. -A client and plugin negotiating an odd version number are not -guaranteed to interoperate; the developer testing the combination is -responsible for ensuring the two are compatible. - -This document describes version 2 of the protocol, the first released -version. (The initial drafts had version 1.) - -\H{authplugin-overview} Overview and sequence of events - -At the very beginning of the user authentication phase of SSH, the -client launches the plugin subprocess, if one is configured. It -immediately sends the \cw{PLUGIN_INIT} message, telling the plugin -some initial information about where the SSH connection is to. - -The plugin responds with \cw{PLUGIN_INIT_RESPONSE}, which may -optionally tell the SSH client what username to use. - -The client begins trying to authenticate with the SSH server in the -usual way, using the username provided by the plugin (if any) or -alternatively one obtained via its normal (non-plugin) policy. - -The client follows its normal policy for selecting authentication -methods to attempt. If it chooses a method that this protocol does not -cover, then the client will perform that method in its own way without -consulting the plugin. - -However, if the client and server decide to attempt a method that this -protocol \e{does} cover, then the client sends \cw{PLUGIN_PROTOCOL} -specifying the SSH protocol id for the authentication method being -used. The plugin responds with \cw{PLUGIN_PROTOCOL_ACCEPT} if it's -willing to assist with this auth method, or -\cw{PLUGIN_PROTOCOL_REJECT} if it isn't. - -If the plugin sends \cw{PLUGIN_PROTOCOL_REJECT}, then the client will -proceed as if the plugin were not present. Later, if another auth -method is negotiated (either because this one failed, or because it -succeeded but the server wants multiple auth methods), the client may -send a further \cw{PLUGIN_PROTOCOL} and try again. - -If the plugin sends \cw{PLUGIN_PROTOCOL_ACCEPT}, then a protocol -segment begins that is specific to that auth method, terminating in -either \cw{PLUGIN_AUTH_SUCCESS} or \cw{PLUGIN_AUTH_FAILURE}. After -that, again, the client may send a further \cw{PLUGIN_PROTOCOL}. - -Currently the only supported method is \cq{keyboard-interactive}, -defined in \k{authplugin-ref-ki}. Once the client has announced this -to the server, the followup protocol is as follows: - -Each time the server sends an \cw{SSH_MSG_USERAUTH_INFO_REQUEST} -message requesting authentication responses from the user, the SSH -client translates the message into \cw{PLUGIN_KI_SERVER_REQUEST} and -passes it on to the plugin. - -At this point, the plugin may optionally send back -\cw{PLUGIN_KI_USER_REQUEST} containing prompts to be presented to the -actual user. The client will reply with a matching -\cw{PLUGIN_KI_USER_RESPONSE} after asking the user to reply to the -question(s) in the request message. The plugin can repeat this cycle -multiple times. - -Once the plugin has all the information it needs to respond to the -server's authentication prompts, it sends \cw{PLUGIN_KI_SERVER_RESPONSE} -back to the client, which translates it into -\cw{SSH_MSG_USERAUTH_INFO_RESPONSE} to send on to the server. - -After that, as described in \k{authplugin-ref-ki}, the server is free -to accept authentication, reject it, or send another -\cw{SSH_MSG_USERAUTH_INFO_REQUEST}. Each -\cw{SSH_MSG_USERAUTH_INFO_REQUEST} is dealt with in the same way as -above. - -If the server terminates keyboard-interactive authentication with -\cw{SSH_MSG_USERAUTH_SUCCESS} or \cw{SSH_MSG_USERAUTH_FAILURE}, the -client informs the plugin by sending either \cw{PLUGIN_AUTH_SUCCESS} -or \cw{PLUGIN_AUTH_FAILURE}. \cw{PLUGIN_AUTH_SUCCESS} is sent when -\e{that particular authentication method} was successful, regardless -of whether the SSH server chooses to request further authentication -afterwards: in particular, \cw{SSH_MSG_USERAUTH_FAILURE} with the -\q{partial success} flag (see \k{authplugin-ref-userauth} section 5.1) translates -into \cw{PLUGIN_AUTH_SUCCESS}. - -The plugin's standard input will close when the client no longer -requires the plugin's services, for any reason. This could be because -authentication is complete (with overall success or overall failure), -or because the user has manually aborted the session in -mid-authentication, or because the client crashed. - -\H{authplugin-messages} Message formats - -This section describes the format of every message in the protocol. - -As described in \k{authplugin-formats}, every message starts with the same two -fields: - -\b \s{uint32}: overall length of the message - -\b \s{byte}: message type. - -The length field does not include itself, but does include the type -code. - -The following subsections each give the format of the remainder of the -message, after the type code. - -The type codes themselves are defined here: - -\c #define PLUGIN_INIT 1 -\c #define PLUGIN_INIT_RESPONSE 2 -\c #define PLUGIN_PROTOCOL 3 -\c #define PLUGIN_PROTOCOL_ACCEPT 4 -\c #define PLUGIN_PROTOCOL_REJECT 5 -\c #define PLUGIN_AUTH_SUCCESS 6 -\c #define PLUGIN_AUTH_FAILURE 7 -\c #define PLUGIN_INIT_FAILURE 8 -\c -\c #define PLUGIN_KI_SERVER_REQUEST 20 -\c #define PLUGIN_KI_SERVER_RESPONSE 21 -\c #define PLUGIN_KI_USER_REQUEST 22 -\c #define PLUGIN_KI_USER_RESPONSE 23 - -If this protocol is extended to be able to assist with further auth -methods, their message type codes will also begin from 20, overlapping -the codes for keyboard-interactive. - -\S{PLUGIN_INIT} \cw{PLUGIN_INIT} - -\s{Direction}: client to plugin - -\s{When}: the first message sent at connection startup - -\s{What happens next}: the plugin will send \cw{PLUGIN_INIT_RESPONSE} -or \cw{PLUGIN_INIT_FAILURE} - -\s{Message contents after the type code}: - -\b \s{uint32}: the highest version number of this protocol that the -client knows how to speak. - -\b \s{string}: the hostname of the server. This will be the \e{logical} -hostname, in cases where it differs from the physical destination of -the network connection. Whatever name would be used by the SSH client -to cache the server's host key, that's the same name passed in this -message. - -\b \s{uint32}: the port number on the server. (Together with the host -name, this forms a primary key identifying a particular server. Port -numbers may be vital because a single host can run two unrelated SSH -servers with completely different authentication requirements, e.g. -system sshd on port 22 and Gerrit on port 29418.) - -\b \s{string}: the username that the client will use to log in, if the -plugin chooses not to override it. An empty string means that the -client has no opinion about this (and might, for example, prompt the -user). - -\S{PLUGIN_INIT_RESPONSE} \cw{PLUGIN_INIT_RESPONSE} - -\s{Direction}: plugin to client - -\s{When}: response to \cw{PLUGIN_INIT} - -\s{What happens next}: the client will send \cw{PLUGIN_PROTOCOL}, or -perhaps terminate the session (if no auth method is ever negotiated -that the plugin can help with) - -\s{Message contents after the type code}: - -\b \s{uint32}: the version number of this protocol that the connection -will use. Must be no greater than the max version number sent by the -client in \cw{PLUGIN_INIT}. - -\b \s{string}: the username that the plugin suggests the client use. An -empty string means that the plugin has no opinion and the client -should stick with the username it already had (or prompt the user, if -it had none). - -\S{PLUGIN_INIT_FAILURE} \cw{PLUGIN_INIT_FAILURE} - -\s{Direction}: plugin to client - -\s{When}: response to \cw{PLUGIN_INIT} - -\s{What happens next}: the session is over - -\s{Message contents after the type code}: - -\b \s{string}: an error message to present to the user indicating why -the plugin was unable to start up. - -\S{PLUGIN_PROTOCOL} \cw{PLUGIN_PROTOCOL} - -\s{Direction}: client to plugin - -\s{When}: sent after \cw{PLUGIN_INIT_RESPONSE}, or after a previous -auth phase terminates with \cw{PLUGIN_AUTH_SUCCESS} or -\cw{PLUGIN_AUTH_FAILURE} - -\s{What happens next}: the plugin will send -\cw{PLUGIN_PROTOCOL_ACCEPT} or \cw{PLUGIN_PROTOCOL_REJECT} - -\s{Message contents after the type code}: - -\b \s{string}: the SSH protocol id of the auth method the client -intends to attempt. Currently the only method specified for use in -this protocol is \cq{keyboard-interactive}. - -\S{PLUGIN_PROTOCOL_REJECT} \cw{PLUGIN_PROTOCOL_REJECT} - -\s{Direction}: plugin to client - -\s{When}: sent after \cw{PLUGIN_PROTOCOL} - -\s{What happens next}: the client will either send another -\cw{PLUGIN_PROTOCOL} or terminate the session - -\s{Message contents after the type code}: - -\b \s{string}: an error message to present to the user, explaining why -the plugin cannot help with this authentication protocol. - -\lcont{ - -An example might be \q{unable to open : }, if the plugin depends on some configuration that the user -has not set up. - -If the plugin does not support this this particular authentication -protocol at all, this string should be left blank, so that no message -will be presented to the user at all. - -} - -\S{PLUGIN_PROTOCOL_ACCEPT} \cw{PLUGIN_PROTOCOL_ACCEPT} - -\s{Direction}: plugin to client - -\s{When}: sent after \cw{PLUGIN_PROTOCOL} - -\s{What happens next}: depends on the auth protocol agreed on. For -keyboard-interactive, the client will send -\cw{PLUGIN_KI_SERVER_REQUEST} or \cw{PLUGIN_AUTH_SUCCESS} or -\cw{PLUGIN_AUTH_FAILURE}. No other method is specified. - -\s{Message contents after the type code}: none. - -\S{PLUGIN_KI_SERVER_REQUEST} \cw{PLUGIN_KI_SERVER_REQUEST} - -\s{Direction}: client to plugin - -\s{When}: sent after \cw{PLUGIN_PROTOCOL}, or after a previous -\cw{PLUGIN_KI_SERVER_RESPONSE}, when the SSH server has sent -\cw{SSH_MSG_USERAUTH_INFO_REQUEST} - -\s{What happens next}: the plugin will send either -\cw{PLUGIN_KI_USER_REQUEST} or \cw{PLUGIN_KI_SERVER_RESPONSE} - -\s{Message contents after the type code}: the exact contents of the -\cw{SSH_MSG_USERAUTH_INFO_REQUEST} just sent by the server. See -\k{authplugin-ref-ki} section 3.2 for details. The summary: - -\b \s{string}: name of this prompt collection (e.g. to use as a -dialog-box title) - -\b \s{string}: instructions to be displayed before this prompt -collection - -\b \s{string}: language tag (deprecated) - -\b \s{uint32}: number of prompts in this collection - -\b That many copies of: - -\lcont{ - -\b \s{string}: prompt (in UTF-8) - -\b \s{boolean}: whether the response to this prompt is safe to echo to -the screen - -} - -\S{PLUGIN_KI_SERVER_RESPONSE} \cw{PLUGIN_KI_SERVER_RESPONSE} - -\s{Direction}: plugin to client - -\s{When}: response to \cw{PLUGIN_KI_SERVER_REQUEST}, perhaps after one -or more intervening pairs of \cw{PLUGIN_KI_USER_REQUEST} and -\cw{PLUGIN_KI_USER_RESPONSE} - -\s{What happens next}: the client will send a further -\cw{PLUGIN_KI_SERVER_REQUEST}, or \cw{PLUGIN_AUTH_SUCCESS} or -\cw{PLUGIN_AUTH_FAILURE} - -\s{Message contents after the type code}: the exact contents of the -\cw{SSH_MSG_USERAUTH_INFO_RESPONSE} that the client should send back -to the server. See \k{authplugin-ref-ki} section 3.4 for details. The -summary: - -\b \s{uint32}: number of responses (must match the \q{number of -prompts} field from the corresponding server request) - -\b That many copies of: - -\lcont{ - -\b \s{string}: response to the \e{n}th prompt (in UTF-8) - -} - -\S{PLUGIN_KI_USER_REQUEST} \cw{PLUGIN_KI_USER_REQUEST} - -\s{Direction}: plugin to client - -\s{When}: response to \cw{PLUGIN_KI_SERVER_REQUEST}, if the plugin -cannot answer the server's auth prompts without presenting prompts of -its own to the user - -\s{What happens next}: the client will send \cw{PLUGIN_KI_USER_RESPONSE} - -\s{Message contents after the type code}: exactly the same as in -\cw{PLUGIN_KI_SERVER_REQUEST} (see \k{PLUGIN_KI_SERVER_REQUEST}). - -\S{PLUGIN_KI_USER_RESPONSE} \cw{PLUGIN_KI_USER_RESPONSE} - -\s{Direction}: client to plugin - -\s{When}: response to \cw{PLUGIN_KI_USER_REQUEST} - -\s{What happens next}: the plugin will send -\cw{PLUGIN_KI_SERVER_RESPONSE}, or another \cw{PLUGIN_KI_USER_REQUEST} - -\s{Message contents after the type code}: exactly the same as in -\cw{PLUGIN_KI_SERVER_RESPONSE} (see \k{PLUGIN_KI_SERVER_RESPONSE}). - -\S{PLUGIN_AUTH_SUCCESS} \cw{PLUGIN_AUTH_SUCCESS} - -\s{Direction}: client to plugin - -\s{When}: sent after \cw{PLUGIN_KI_SERVER_RESPONSE}, or (in unusual -cases) after \cw{PLUGIN_PROTOCOL_ACCEPT} - -\s{What happens next}: the client will either send another -\cw{PLUGIN_PROTOCOL} or terminate the session - -\s{Message contents after the type code}: none - -\S{PLUGIN_AUTH_FAILURE} \cw{PLUGIN_AUTH_FAILURE} - -\s{Direction}: client to plugin - -\s{When}: sent after \cw{PLUGIN_KI_SERVER_RESPONSE}, or (in unusual -cases) after \cw{PLUGIN_PROTOCOL_ACCEPT} - -\s{What happens next}: the client will either send another -\cw{PLUGIN_PROTOCOL} or terminate the session - -\s{Message contents after the type code}: none - -\H{authplugin-refs} References - -\B{authplugin-ref-arch} \W{https://www.rfc-editor.org/rfc/rfc4251}{RFC 4251}, \q{The Secure Shell (SSH) Protocol -Architecture}. - -\B{authplugin-ref-userauth} \W{https://www.rfc-editor.org/rfc/rfc4252}{RFC -4252}, \q{The Secure Shell (SSH) Authentication Protocol}. - -\B{authplugin-ref-ki} -\W{https://www.rfc-editor.org/rfc/rfc4256}{RFC 4256}, -\q{Generic Message Exchange Authentication for the Secure Shell -Protocol (SSH)} (better known by its wire id -\q{keyboard-interactive}). - -\BR{authplugin-ref-arch} [RFC4251] - -\BR{authplugin-ref-userauth} [RFC4252] - -\BR{authplugin-ref-ki} [RFC4256] diff --git a/doc/blurb.but b/doc/blurb.but deleted file mode 100644 index 84105d0f3..000000000 --- a/doc/blurb.but +++ /dev/null @@ -1,42 +0,0 @@ -\define{dash} \u2013{-} - -\title PuTTY User Manual - -\cfg{xhtml-leaf-level}{1} -\cfg{xhtml-leaf-smallest-contents}{2} -\cfg{xhtml-leaf-contains-contents}{true} -\cfg{xhtml-body-end}{

If you want to provide feedback on this manual -or on the PuTTY tools themselves, see the -Feedback -page.

} - -\cfg{html-template-fragment}{%k}{%b} - -\cfg{info-max-file-size}{0} - -\cfg{chm-contents-filename}{index.html} -\cfg{chm-template-filename}{%k.html} -\cfg{chm-head-end}{} - -\cfg{xhtml-contents-filename}{index.html} -\cfg{text-filename}{puttydoc.txt} -\cfg{winhelp-filename}{putty.hlp} -\cfg{info-filename}{putty.info} -\cfg{chm-filename}{putty.chm} -\cfg{pdf-filename}{putty.pdf} - -PuTTY is a free (MIT-licensed) Windows Telnet and SSH client. This -manual documents PuTTY, and its companion utilities PSCP, PSFTP, -Plink, Pageant and PuTTYgen. - -\e{Note to Unix users:} this manual currently primarily documents the -Windows versions of the PuTTY utilities. Some options are therefore -mentioned that are absent from the \i{Unix version}; the Unix version has -features not described here; and the \i\cw{pterm} and command-line -\cw{puttygen} and \cw{pageant} utilities are not described at all. The -only Unix-specific documentation that currently exists is the -\I{man pages for PuTTY tools}man pages. - -\copyright This manual is copyright \shortcopyrightdetails. All -rights reserved. You may distribute this documentation under the MIT -licence. See \k{licence} for the licence text in full. diff --git a/doc/chm.css b/doc/chm.css deleted file mode 100644 index d8c316bfc..000000000 --- a/doc/chm.css +++ /dev/null @@ -1,7 +0,0 @@ -/* Stylesheet for a Windows .CHM help file */ - -body { font-size: 75%; font-family: Verdana, Arial, Helvetica, Sans-Serif; } - -h1 { font-weight: bold; font-size: 150%; } -h2 { font-weight: bold; font-size: 130%; } -h3 { font-weight: bold; font-size: 120%; } diff --git a/doc/chmextra.but b/doc/chmextra.but deleted file mode 100644 index 8b8780c91..000000000 --- a/doc/chmextra.but +++ /dev/null @@ -1,6 +0,0 @@ -\# If you want to do a Halibut build of the CHM file by hand, without -\# the help of the CMake edifice, then include this file which will -\# refer to chm.css. The CMake edifice builds its own with a different -\# pathname in it, for the sake of out-of-tree builds. - -\cfg{chm-extra-file}{chm.css} diff --git a/doc/config.but b/doc/config.but deleted file mode 100644 index 8b3191fa5..000000000 --- a/doc/config.but +++ /dev/null @@ -1,4147 +0,0 @@ -\C{config} Configuring PuTTY - -This chapter describes all the \i{configuration options} in PuTTY. - -PuTTY is configured using the control panel that comes up before you -start a session. Some options can also be changed in the middle of a -session, by selecting \q{Change Settings} from the window menu. - -\H{config-session} The Session panel - -The Session configuration panel contains the basic options you need -to specify in order to open a session at all, and also allows you to -save your settings to be reloaded later. - -\S{config-hostname} The \i{host name} section - -The top box on the Session panel, labelled \q{Specify the destination -you want to connect to}, contains the details that need to be filled -in before PuTTY can open a session at all. - -\b The \q{Host Name} box is where you type the name, or the \i{IP -address}, of the server you want to connect to. - -\b The \q{Connection type} controls let you choose what type of -connection you want to make: an \i{SSH} network connection, a -connection to a local \i{serial line}, or various other kinds of -network connection. - -\lcont{ - -\b See \k{which-one} for a summary of the -differences between the network remote login protocols SSH, Telnet, -Rlogin, and SUPDUP. - -\b See \k{using-serial} for information about using a serial line. - -\b See \k{using-rawprot} for an explanation of \q{raw} -connections. - -\b See \k{using-telnet} for a little information about Telnet. - -\b See \k{using-rlogin} for information about using Rlogin. - -\b See \k{using-supdup} for information about using SUPDUP. - -\b The \q{Bare ssh-connection} option in the \q{Connection type} -control is intended for specialist uses not involving network -connections. See \k{config-psusan} for some information about it. - -} - -\b The \q{Port} box lets you specify which \i{port number} on the -server to connect to. If you select Telnet, Rlogin, SUPDUP, or SSH, -this box will be filled in automatically to the usual value, and you -will only need to change it if you have an unusual server. If you -select Raw mode, you will almost certainly need to fill in the -\q{Port} box yourself. - -If you select \q{Serial} from the \q{Connection type} radio buttons, -the \q{Host Name} and \q{Port} boxes are replaced by \q{Serial line} -and \q{Speed}; see \k{config-serial} for more details of these. - -\S{config-saving} \ii{Loading and storing saved sessions} - -The next part of the Session configuration panel allows you to save -your preferred PuTTY options so they will appear automatically the -next time you start PuTTY. It also allows you to create \e{saved -sessions}, which contain a full set of configuration options plus a -host name and protocol. A saved session contains all the information -PuTTY needs to start exactly the session you want. - -\b To save your default settings: first set up the settings the way -you want them saved. Then come back to the Session panel. Select the -\q{\i{Default Settings}} entry in the saved sessions list, with a single -click. Then press the \q{Save} button. - -If there is a specific host you want to store the details of how to -connect to, you should create a saved session, which will be -separate from the Default Settings. - -\b To save a session: first go through the rest of the configuration -box setting up all the options you want. Then come back to the -Session panel. Enter a name for the saved session in the \q{Saved -Sessions} input box. (The server name is often a good choice for a -saved session name.) Then press the \q{Save} button. Your saved -session name should now appear in the list box. - -\lcont{ -You can also save settings in mid-session, from the \q{Change Settings} -dialog. Settings changed since the start of the session will be saved -with their current values; as well as settings changed through the -dialog, this includes changes in window size, window title changes -sent by the server, and so on. -} - -\b To reload a saved session: single-click to select the session -name in the list box, and then press the \q{Load} button. Your saved -settings should all appear in the configuration panel. - -\b To modify a saved session: first load it as described above. Then -make the changes you want. Come back to the Session panel, and press -the \q{Save} button. The new settings will be saved over the top of -the old ones. - -\lcont{ -To save the new settings under a different name, you can enter the new -name in the \q{Saved Sessions} box, or single-click to select a -session name in the list box to overwrite that session. To save -\q{Default Settings}, you must single-click the name before saving. -} - -\b To start a saved session immediately: double-click on the session -name in the list box. - -\b To delete a saved session: single-click to select the session -name in the list box, and then press the \q{Delete} button. - -Each saved session is independent of the Default Settings -configuration. If you change your preferences and update Default -Settings, you must also update every saved session separately. - -Saved sessions are stored in the \i{Registry}, at the location - -\c HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions - -If you need to store them in a file, you could try the method -described in \k{config-file}. - -\S{config-closeonexit} \q{\ii{Close window} on exit} - -Finally in the Session panel, there is an option labelled \q{Close -window on exit}. This controls whether the PuTTY \i{terminal window} -disappears as soon as the session inside it terminates. If you are -likely to want to copy and paste text out of the session after it -has terminated, or restart the session, you should arrange for this -option to be off. - -\q{Close window on exit} has three settings. \q{Always} means always -close the window on exit; \q{Never} means never close on exit -(always leave the window open, but \I{inactive window}inactive). The -third setting, and the default one, is \q{Only on clean exit}. In this -mode, a session which terminates normally will cause its window to -close, but one which is aborted unexpectedly by network trouble or a -confusing message from the server will leave the window up. - -\H{config-logging} The Logging panel - -The Logging configuration panel allows you to save \i{log file}s of your -PuTTY sessions, for debugging, analysis or future reference. - -The main option is a radio-button set that specifies whether PuTTY -will log anything at all. The options are: - -\b \q{None}. This is the default option; in this mode PuTTY will not -create a log file at all. - -\b \q{Printable output}. In this mode, a log file will be -created and written to, but only printable text will be saved into -it. The various terminal control codes that are typically sent down -an interactive session alongside the printable text will be omitted. -This might be a useful mode if you want to read a log file in a text -editor and hope to be able to make sense of it. - -\b \q{All session output}. In this mode, \e{everything} sent by -the server into your terminal session is logged. If you view the log -file in a text editor, therefore, you may well find it full of -strange control characters. This is a particularly useful mode if -you are experiencing problems with PuTTY's terminal handling: you -can record everything that went to the terminal, so that someone -else can replay the session later in slow motion and watch to see -what went wrong. - -\b \I{SSH packet log}\q{SSH packets}. In this mode (which is only used -by SSH connections), the SSH message packets sent over the encrypted -connection are written to the log file (as well as \i{Event Log} -entries). You might need this to debug a network-level problem, or -more likely to send to the PuTTY authors as part of a bug report. -\e{BE WARNED} that if you log in using a password, the password can -appear in the log file; see \k{config-logssh} for options that may -help to remove sensitive material from the log file before you send it -to anyone else. - -\b \q{SSH packets and raw data}. In this mode, as well as the -decrypted packets (as in the previous mode), the \e{raw} (encrypted, -compressed, etc) packets are \e{also} logged. This could be useful to -diagnose corruption in transit. (The same caveats as the previous mode -apply, of course.) - -Note that the non-SSH logging options (\q{Printable output} and -\q{All session output}) only work with PuTTY proper; in programs -without terminal emulation (such as Plink), they will have no effect, -even if enabled via saved settings. - -\S{config-logfilename} \q{Log file name} - -In this edit box you enter the name of the file you want to log the -session to. The \q{Browse} button will let you look around your file -system to find the right place to put the file; or if you already -know exactly where you want it to go, you can just type a pathname -into the edit box. - -There are a few special features in this box. If you use the \c{&} -character in the file name box, PuTTY will insert details of the -current session in the name of the file it actually opens. The -precise replacements it will do are: - -\b \c{&Y} will be replaced by the current year, as four digits. - -\b \c{&M} will be replaced by the current month, as two digits. - -\b \c{&D} will be replaced by the current day of the month, as two -digits. - -\b \c{&T} will be replaced by the current time, as six digits -(HHMMSS) with no punctuation. - -\b \c{&H} will be replaced by the host name you are connecting to -(or the serial line, for a serial connection). - -\b \c{&P} will be replaced by the port number you are connecting to on -the target host. - -(These are all case-insensitive.) - -For example, if you enter the file name -\c{c:\\puttylogs\\log-&h-&y&m&d-&t.dat}, you will end up with files looking -like - -\c log-server1.example.com-20010528-110859.dat -\c log-unixbox.somewhere.org-20010611-221001.dat - -\S{config-logfileexists} \q{What to do if the log file already exists} - -This control allows you to specify what PuTTY should do if it tries -to start writing to a log file and it finds the file already exists. -You might want to automatically destroy the existing log file and -start a new one with the same name. Alternatively, you might want to -open the existing log file and add data to the \e{end} of it. -Finally (the default option), you might not want to have any -automatic behaviour, but to ask the user every time the problem -comes up. - -\S{config-logflush} \I{log file, flushing}\q{Flush log file frequently} - -This option allows you to control how frequently logged data is -flushed to disc. By default, PuTTY will flush data as soon as it is -displayed, so that if you view the log file while a session is still -open, it will be up to date; and if the client system crashes, there's -a greater chance that the data will be preserved. - -However, this can incur a performance penalty. If PuTTY is running -slowly with logging enabled, you could try unchecking this option. Be -warned that the log file may not always be up to date as a result -(although it will of course be flushed when it is closed, for instance -at the end of a session). - -\S{config-logheader} \I{log file, header}\q{Include header} - -This option allows you to choose whether to include a header line -with the date and time when the log file is opened. It may be useful to -disable this if the log file is being used as realtime input to other -programs that don't expect the header line. - -\S{config-logssh} Options specific to \i{SSH packet log}ging - -These options only apply if SSH packet data is being logged. - -The following options allow particularly sensitive portions of -unencrypted packets to be automatically left out of the log file. -They are only intended to deter casual nosiness; an attacker could -glean a lot of useful information from even these obfuscated logs -(e.g., length of password). - -\S2{config-logssh-omitpw} \q{Omit known password fields} - -When checked, decrypted password fields are removed from the log of -transmitted packets. (This includes any user responses to -challenge-response authentication methods such as -\q{keyboard-interactive}.) This does not include X11 authentication -data if using X11 forwarding. - -Note that this will only omit data that PuTTY \e{knows} to be a -password. However, if you start another login session within your -PuTTY session, for instance, any password used will appear in the -clear in the packet log. The next option may be of use to protect -against this. - -This option is enabled by default. - -\S2{config-logssh-omitdata} \q{Omit session data} - -When checked, all decrypted \q{session data} is omitted; this is -defined as data in terminal sessions and in forwarded channels (TCP, -X11, and authentication agent). This will usually substantially reduce -the size of the resulting log file. - -This option is disabled by default. - -\H{config-terminal} The Terminal panel - -The Terminal configuration panel allows you to control the behaviour -of PuTTY's \i{terminal emulation}. - -\S{config-autowrap} \q{Auto wrap mode initially on} - -\ii{Auto wrap mode} controls what happens when text printed in a PuTTY -window reaches the right-hand edge of the window. - -With auto wrap mode on, if a long line of text reaches the -right-hand edge, it will wrap over on to the next line so you can -still see all the text. With auto wrap mode off, the cursor will -stay at the right-hand edge of the screen, and all the characters in -the line will be printed on top of each other. - -If you are running a full-screen application and you occasionally -find the screen scrolling up when it looks as if it shouldn't, you -could try turning this option off. - -Auto wrap mode can be turned on and off by \i{control sequence}s sent by -the server. This configuration option controls the \e{default} -state, which will be restored when you reset the terminal (see -\k{reset-terminal}). However, if you modify this option in -mid-session using \q{Change Settings}, it will take effect -immediately. - -\S{config-decom} \q{DEC Origin Mode initially on} - -\i{DEC Origin Mode} is a minor option which controls how PuTTY -interprets cursor-position \i{control sequence}s sent by the server. - -The server can send a control sequence that restricts the \i{scrolling -region} of the display. For example, in an editor, the server might -reserve a line at the top of the screen and a line at the bottom, -and might send a control sequence that causes scrolling operations -to affect only the remaining lines. - -With DEC Origin Mode on, \i{cursor coordinates} are counted from the top -of the scrolling region. With it turned off, cursor coordinates are -counted from the top of the whole screen regardless of the scrolling -region. - -It is unlikely you would need to change this option, but if you find -a full-screen application is displaying pieces of text in what looks -like the wrong part of the screen, you could try turning DEC Origin -Mode on to see whether that helps. - -DEC Origin Mode can be turned on and off by control sequences sent -by the server. This configuration option controls the \e{default} -state, which will be restored when you reset the terminal (see -\k{reset-terminal}). However, if you modify this option in -mid-session using \q{Change Settings}, it will take effect -immediately. - -\S{config-crlf} \q{Implicit CR in every LF} - -Most servers send two control characters, \i{CR} and \i{LF}, to start a -\i{new line} of the screen. The CR character makes the cursor return to the -left-hand side of the screen. The LF character makes the cursor move -one line down (and might make the screen scroll). - -Some servers only send LF, and expect the terminal to move the -cursor over to the left automatically. If you come across a server -that does this, you will see a \I{stair-stepping}stepped effect on the -screen, like this: - -\c First line of text -\c Second line -\c Third line - -If this happens to you, try enabling the \q{Implicit CR in every LF} -option, and things might go back to normal: - -\c First line of text -\c Second line -\c Third line - -\S{config-lfcr} \q{Implicit LF in every CR} - -Most servers send two control characters, \i{CR} and \i{LF}, to start a -\i{new line} of the screen. The CR character makes the cursor return to the -left-hand side of the screen. The LF character makes the cursor move -one line down (and might make the screen scroll). - -Some servers only send CR, and so the newly -written line is overwritten by the following line. This option causes -a line feed so that all lines are displayed. - -\S{config-erase} \q{Use \i{background colour} to erase screen} - -Not all terminals agree on what colour to turn the screen when the -server sends a \q{\i{clear screen}} sequence. Some terminals believe the -screen should always be cleared to the \e{default} background -colour. Others believe the screen should be cleared to whatever the -server has selected as a background colour. - -There exist applications that expect both kinds of behaviour. -Therefore, PuTTY can be configured to do either. - -With this option disabled, screen clearing is always done in the -default background colour. With this option enabled, it is done in -the \e{current} background colour. - -Background-colour erase can be turned on and off by \i{control -sequences} sent by the server. This configuration option controls the -\e{default} state, which will be restored when you reset the -terminal (see \k{reset-terminal}). However, if you modify this -option in mid-session using \q{Change Settings}, it will take effect -immediately. - -\S{config-blink} \q{Enable \i{blinking text}} - -The server can ask PuTTY to display text that blinks on and off. -This is very distracting, so PuTTY allows you to turn blinking text -off completely. - -When blinking text is disabled and the server attempts to make some -text blink, PuTTY will instead display the text with a \I{background -colour, bright}bolded background colour. - -Blinking text can be turned on and off by \i{control sequence}s sent by -the server. This configuration option controls the \e{default} -state, which will be restored when you reset the terminal (see -\k{reset-terminal}). However, if you modify this option in -mid-session using \q{Change Settings}, it will take effect -immediately. - -\S{config-answerback} \q{\ii{Answerback} to ^E} - -This option controls what PuTTY will send back to the server if the -server sends it the ^E \i{enquiry character}. Normally it just sends -the string \q{PuTTY}. - -If you accidentally write the contents of a binary file to your -terminal, you will probably find that it contains more than one ^E -character, and as a result your next command line will probably read -\q{PuTTYPuTTYPuTTY...} as if you had typed the answerback string -multiple times at the keyboard. If you set the answerback string to -be empty, this problem should go away, but doing so might cause -other problems. - -Note that this is \e{not} the feature of PuTTY which the server will -typically use to determine your terminal type. That feature is the -\q{\ii{Terminal-type} string} in the Connection panel; see -\k{config-termtype} for details. - -You can include control characters in the answerback string using -\c{^C} notation. (Use \c{^~} to get a literal \c{^}.) - -\S{config-localecho} \q{\ii{Local echo}} - -With local echo disabled, characters you type into the PuTTY window -are not echoed in the window \e{by PuTTY}. They are simply sent to -the server. (The \e{server} might choose to \I{remote echo}echo them -back to you; this can't be controlled from the PuTTY control panel.) - -Some types of session need local echo, and many do not. In its -default mode, PuTTY will automatically attempt to deduce whether or -not local echo is appropriate for the session you are working in. If -you find it has made the wrong decision, you can use this -configuration option to override its choice: you can force local -echo to be turned on, or force it to be turned off, instead of -relying on the automatic detection. - -\S{config-localedit} \q{\ii{Local line editing}} - -Normally, every character you type into the PuTTY window is sent -immediately to the server the moment you type it. - -If you enable local line editing, this changes. PuTTY will let you -edit a whole line at a time locally, and the line will only be sent -to the server when you press Return. If you make a mistake, you can -use the Backspace key to correct it before you press Return, and the -server will never see the mistake. - -Since it is hard to edit a line locally without being able to see -it, local line editing is mostly used in conjunction with \i{local echo} -(\k{config-localecho}). This makes it ideal for use in raw mode -\#{FIXME} or when connecting to \i{MUD}s or \i{talker}s. (Although some more -advanced MUDs do occasionally turn local line editing on and turn -local echo off, in order to accept a password from the user.) - -Some types of session need local line editing, and many do not. In -its default mode, PuTTY will automatically attempt to deduce whether -or not local line editing is appropriate for the session you are -working in. If you find it has made the wrong decision, you can use -this configuration option to override its choice: you can force -local line editing to be turned on, or force it to be turned off, -instead of relying on the automatic detection. - -\S{config-printing} \ii{Remote-controlled printing} - -A lot of VT100-compatible terminals support printing under control -of the remote server (sometimes called \q{passthrough printing}). -PuTTY supports this feature as well, but it is turned off by default. - -To enable remote-controlled printing, choose a printer from the -\q{Printer to send ANSI printer output to} drop-down list box. This -should allow you to select from all the printers you have installed -drivers for on your computer. Alternatively, you can type the -network name of a networked printer (for example, -\c{\\\\printserver\\printer1}) even if you haven't already -installed a driver for it on your own machine. - -When the remote server attempts to print some data, PuTTY will send -that data to the printer \e{raw} - without translating it, -attempting to format it, or doing anything else to it. It is up to -you to ensure your remote server knows what type of printer it is -talking to. - -Since PuTTY sends data to the printer raw, it cannot offer options -such as portrait versus landscape, print quality, or paper tray -selection. All these things would be done by your PC printer driver -(which PuTTY bypasses); if you need them done, you will have to find -a way to configure your remote server to do them. - -To disable remote printing again, choose \q{None (printing -disabled)} from the printer selection list. This is the default -state. - -\H{config-keyboard} The Keyboard panel - -The Keyboard configuration panel allows you to control the behaviour -of the \i{keyboard} in PuTTY. The correct state for many of these -settings depends on what the server to which PuTTY is connecting -expects. With a \i{Unix} server, this is likely to depend on the -\i\c{termcap} or \i\c{terminfo} entry it uses, which in turn is likely to -be controlled by the \q{\ii{Terminal-type} string} setting in the Connection -panel; see \k{config-termtype} for details. If none of the settings here -seems to help, you may find \k{faq-keyboard} to be useful. - -\S{config-backspace} Changing the action of the \ii{Backspace key} - -Some terminals believe that the Backspace key should send the same -thing to the server as \i{Control-H} (ASCII code 8). Other terminals -believe that the Backspace key should send ASCII code 127 (usually -known as \i{Control-?}) so that it can be distinguished from Control-H. -This option allows you to choose which code PuTTY generates when you -press Backspace. - -If you are connecting over SSH, PuTTY by default tells the server -the value of this option (see \k{config-ttymodes}), so you may find -that the Backspace key does the right thing either way. Similarly, -if you are connecting to a \i{Unix} system, you will probably find that -the Unix \i\c{stty} command lets you configure which the server -expects to see, so again you might not need to change which one PuTTY -generates. On other systems, the server's expectation might be fixed -and you might have no choice but to configure PuTTY. - -If you do have the choice, we recommend configuring PuTTY to -generate Control-? and configuring the server to expect it, because -that allows applications such as \c{emacs} to use Control-H for -help. - -(Typing \i{Shift-Backspace} will cause PuTTY to send whichever code -isn't configured here as the default.) - -\S{config-homeend} Changing the action of the \i{Home and End keys} - -The Unix terminal emulator \i\c{rxvt} disagrees with the rest of the -world about what character sequences should be sent to the server by -the Home and End keys. - -\i\c{xterm}, and other terminals, send \c{ESC [1~} for the Home key, -and \c{ESC [4~} for the End key. \c{rxvt} sends \c{ESC [H} for the -Home key and \c{ESC [Ow} for the End key. - -If you find an application on which the Home and End keys aren't -working, you could try switching this option to see if it helps. - -\S{config-funkeys} Changing the action of the \i{function keys} and -\i{keypad} - -This option affects the function keys (F1 to F12) and the top row of -the numeric keypad. - -\b In the default mode, labelled \c{ESC [n~}, the function keys -generate sequences like \c{ESC [11~}, \c{ESC [12~} and so on. This -matches the general behaviour of Digital's terminals. - -\b In Linux mode, F6 to F12 behave just like the default mode, but -F1 to F5 generate \c{ESC [[A} through to \c{ESC [[E}. This mimics the -\i{Linux virtual console}. - -\b In \I{xterm}Xterm R6 mode, F5 to F12 behave like the default mode, but F1 -to F4 generate \c{ESC OP} through to \c{ESC OS}, which are the -sequences produced by the top row of the \e{keypad} on Digital's -terminals. - -\b In \i{VT400} mode, all the function keys behave like the default -mode, but the actual top row of the numeric keypad generates \c{ESC -OP} through to \c{ESC OS}. - -\b In \i{VT100+} mode, the function keys generate \c{ESC OP} through to -\c{ESC O[} - -\b In \i{SCO} mode, the function keys F1 to F12 generate \c{ESC [M} -through to \c{ESC [X}. Together with shift, they generate \c{ESC [Y} -through to \c{ESC [j}. With control they generate \c{ESC [k} through -to \c{ESC [v}, and with shift and control together they generate -\c{ESC [w} through to \c{ESC [\{}. - -\b In \I{xterm}Xterm 216 mode, the unshifted function keys behave the -same as Xterm R6 mode. But pressing a function key together with Shift -or Alt or Ctrl generates a different sequence containing an extra -numeric parameter of the form (1 for Shift) + (2 for Alt) + (4 for -Ctrl) + 1. For F1-F4, the basic sequences like \c{ESC OP} become -\cw{ESC [1;}\e{bitmap}\cw{P} and similar; for F5 and above, -\cw{ESC[}\e{index}\cw{~} becomes -\cw{ESC[}\e{index}\cw{;}\e{bitmap}\cw{~}. - -If you don't know what any of this means, you probably don't need to -fiddle with it. - -\S{config-sharrow} Changing the action of the \i{shifted arrow keys} - -This option affects the arrow keys, if you press one with any of the -modifier keys Shift, Ctrl or Alt held down. - -\b In the default mode, labelled \c{Ctrl toggles app mode}, the Ctrl -key toggles between the default arrow-key sequences like \c{ESC [A} and -\c{ESC [B}, and the sequences Digital's terminals generate in -\q{application cursor keys} mode, i.e. \c{ESC O A} and so on. Shift -and Alt have no effect. - -\b In the \q{xterm-style bitmap} mode, Shift, Ctrl and Alt all -generate different sequences, with a number indicating which set of -modifiers is active. - -If you don't know what any of this means, you probably don't need to -fiddle with it. - -\S{config-appcursor} Controlling \i{Application Cursor Keys} mode - -Application Cursor Keys mode is a way for the server to change the -control sequences sent by the arrow keys. In normal mode, the arrow -keys send \c{ESC [A} through to \c{ESC [D}. In application mode, -they send \c{ESC OA} through to \c{ESC OD}. - -Application Cursor Keys mode can be turned on and off by the server, -depending on the application. PuTTY allows you to configure the -initial state. - -You can also disable application cursor keys mode completely, using -the \q{Features} configuration panel; see -\k{config-features-application}. - -\S{config-appkeypad} Controlling \i{Application Keypad} mode - -Application Keypad mode is a way for the server to change the -behaviour of the numeric keypad. - -In normal mode, the keypad behaves like a normal Windows keypad: -with \i{NumLock} on, the number keys generate numbers, and with NumLock -off they act like the arrow keys and Home, End etc. - -In application mode, all the keypad keys send special control -sequences, \e{including} Num Lock. Num Lock stops behaving like Num -Lock and becomes another function key. - -Depending on which version of Windows you run, you may find the Num -Lock light still flashes on and off every time you press Num Lock, -even when application mode is active and Num Lock is acting like a -function key. This is unavoidable. - -Application keypad mode can be turned on and off by the server, -depending on the application. PuTTY allows you to configure the -initial state. - -You can also disable application keypad mode completely, using the -\q{Features} configuration panel; see -\k{config-features-application}. - -\S{config-nethack} Using \i{NetHack keypad mode} - -PuTTY has a special mode for playing NetHack. You can enable it by -selecting \q{NetHack} in the \q{Initial state of numeric keypad} -control. - -In this mode, the numeric keypad keys 1-9 generate the NetHack -movement commands (\cw{hjklyubn}). The 5 key generates the \c{.} -command (do nothing). - -In addition, pressing Shift or Ctrl with the keypad keys generate -the Shift- or Ctrl-keys you would expect (e.g. keypad-7 generates -\cq{y}, so Shift-keypad-7 generates \cq{Y} and Ctrl-keypad-7 -generates Ctrl-Y); these commands tell NetHack to keep moving you in -the same direction until you encounter something interesting. - -For some reason, this feature only works properly when \i{Num Lock} is -on. We don't know why. - -\S{config-compose} Enabling a DEC-like \ii{Compose key} - -DEC terminals have a Compose key, which provides an easy-to-remember -way of typing \i{accented characters}. You press Compose and then type -two more characters. The two characters are \q{combined} to produce -an accented character. The choices of character are designed to be -easy to remember; for example, composing \q{e} and \q{`} produces -the \q{\u00e8{e-grave}} character. - -If your keyboard has a Windows \i{Application key}, it acts as a Compose -key in PuTTY. Alternatively, if you enable the \q{\i{AltGr} acts as -Compose key} option, the AltGr key will become a Compose key. - -\S{config-ctrlalt} \q{Control-Alt is different from \i{AltGr}} - -Some old keyboards do not have an AltGr key, which can make it -difficult to type some characters. PuTTY can be configured to treat -the key combination Ctrl + Left Alt the same way as the AltGr key. - -By default, this checkbox is checked, and the key combination Ctrl + -Left Alt does something completely different. PuTTY's usual handling -of the left Alt key is to prefix the Escape (Control-\cw{[}) -character to whatever character sequence the rest of the keypress -would generate. For example, Alt-A generates Escape followed by -\c{a}. So Alt-Ctrl-A would generate Escape, followed by Control-A. - -If you uncheck this box, Ctrl-Alt will become a synonym for AltGr, -so you can use it to type extra graphic characters if your keyboard -has any. - -(However, Ctrl-Alt will never act as a Compose key, regardless of the -setting of \q{AltGr acts as Compose key} described in -\k{config-compose}.) - -\H{config-bell} The Bell panel - -The Bell panel controls the \i{terminal bell} feature: the server's -ability to cause PuTTY to beep at you. - -In the default configuration, when the server sends the character -with ASCII code 7 (Control-G), PuTTY will play the \i{Windows Default -Beep} sound. This is not always what you want the terminal bell -feature to do; the Bell panel allows you to configure alternative -actions. - -\S{config-bellstyle} \q{Set the style of bell} - -This control allows you to select various different actions to occur -on a terminal bell: - -\b Selecting \q{None} \I{terminal bell, disabling}disables the bell -completely. In this mode, the server can send as many Control-G -characters as it likes and nothing at all will happen. - -\b \q{Make default system alert sound} is the default setting. It -causes the Windows \q{Default Beep} sound to be played. To change -what this sound is, or to test it if nothing seems to be happening, -use the Sound configurer in the Windows Control Panel. - -\b \q{\ii{Visual bell}} is a silent alternative to a beeping computer. In -this mode, when the server sends a Control-G, the whole PuTTY window -will flash white for a fraction of a second. - -\b \q{Beep using the \i{PC speaker}} is self-explanatory. - -\b \q{Play a custom \i{sound file}} allows you to specify a particular -sound file to be used by PuTTY alone, or even by a particular -individual PuTTY session. This allows you to distinguish your PuTTY -beeps from any other beeps on the system. If you select this option, -you will also need to enter the name of your sound file in the edit -control \q{Custom sound file to play as a bell}. - -\S{config-belltaskbar} \q{\ii{Taskbar}/\I{window caption}caption -indication on bell} - -This feature controls what happens to the PuTTY window's entry in -the Windows Taskbar if a bell occurs while the window does not have -the input focus. - -In the default state (\q{Disabled}) nothing unusual happens. - -If you select \q{Steady}, then when a bell occurs and the window is -not in focus, the window's Taskbar entry and its title bar will -change colour to let you know that PuTTY session is asking for your -attention. The change of colour will persist until you select the -window, so you can leave several PuTTY windows minimised in your -terminal, go away from your keyboard, and be sure not to have missed -any important beeps when you get back. - -\q{Flashing} is even more eye-catching: the Taskbar entry will -continuously flash on and off until you select the window. - -\S{config-bellovl} \q{Control the \i{bell overload} behaviour} - -A common user error in a terminal session is to accidentally run the -Unix command \c{cat} (or equivalent) on an inappropriate file type, -such as an executable, image file, or ZIP file. This produces a huge -stream of non-text characters sent to the terminal, which typically -includes a lot of bell characters. As a result of this the terminal -often doesn't stop beeping for ten minutes, and everybody else in -the office gets annoyed. - -To try to avoid this behaviour, or any other cause of excessive -beeping, PuTTY includes a bell overload management feature. In the -default configuration, receiving more than five bell characters in a -two-second period will cause the overload feature to activate. Once -the overload feature is active, further bells will \I{terminal bell, -disabling} have no effect at all, so the rest of your binary file -will be sent to the screen in silence. After a period of five seconds -during which no further bells are received, the overload feature will -turn itself off again and bells will be re-enabled. - -If you want this feature completely disabled, you can turn it off -using the checkbox \q{Bell is temporarily disabled when over-used}. - -Alternatively, if you like the bell overload feature but don't agree -with the settings, you can configure the details: how many bells -constitute an overload, how short a time period they have to arrive -in to do so, and how much silent time is required before the -overload feature will deactivate itself. - -Bell overload mode is always deactivated by any keypress in the -terminal. This means it can respond to large unexpected streams of -data, but does not interfere with ordinary command-line activities -that generate beeps (such as filename completion). - -\H{config-features} The Features panel - -PuTTY's \i{terminal emulation} is very highly featured, and can do a lot -of things under remote server control. Some of these features can -cause problems due to buggy or strangely configured server -applications. - -The Features configuration panel allows you to disable some of -PuTTY's more advanced terminal features, in case they cause trouble. - -\S{config-features-application} Disabling application keypad and cursor keys - -\I{Application Keypad}Application keypad mode (see -\k{config-appkeypad}) and \I{Application Cursor Keys}application -cursor keys mode (see \k{config-appcursor}) alter the behaviour of -the keypad and cursor keys. Some applications enable these modes but -then do not deal correctly with the modified keys. You can force -these modes to be permanently disabled no matter what the server -tries to do. - -\S{config-features-mouse} Disabling \cw{xterm}-style \i{mouse reporting} - -PuTTY allows the server to send \i{control codes} that let it take over -the mouse and use it for purposes other than \i{copy and paste}. -Applications which use this feature include the text-mode web -browser \i\c{links}, the Usenet newsreader \i\c{trn} version 4, and the -file manager \i\c{mc} (Midnight Commander). - -If you find this feature inconvenient, you can disable it using the -\q{Disable xterm-style mouse reporting} control. With this box -ticked, the mouse will \e{always} do copy and paste in the normal -way. - -Note that even if the application takes over the mouse, you can -still manage PuTTY's copy and paste by holding down the Shift key -while you select and paste, unless you have deliberately turned this -feature off (see \k{config-mouseshift}). - -\S{config-features-resize} Disabling remote \i{terminal resizing} - -PuTTY has the ability to change the terminal's size and position in -response to commands from the server. If you find PuTTY is doing -this unexpectedly or inconveniently, you can tell PuTTY not to -respond to those server commands. - -\S{config-features-altscreen} Disabling switching to the \i{alternate screen} - -Many terminals, including PuTTY, support an \q{alternate screen}. -This is the same size as the ordinary terminal screen, but separate. -Typically a screen-based program such as a text editor might switch -the terminal to the alternate screen before starting up. Then at the -end of the run, it switches back to the primary screen, and you see -the screen contents just as they were before starting the editor. - -Some people prefer this not to happen. If you want your editor to -run in the same screen as the rest of your terminal activity, you -can disable the alternate screen feature completely. - -\S{config-features-retitle} Disabling remote \i{window title} changing - -PuTTY has the ability to change the window title in response to -commands from the server. If you find PuTTY is doing this -unexpectedly or inconveniently, you can tell PuTTY not to respond to -those server commands. - -\S{config-features-qtitle} Response to remote \i{window title} querying - -PuTTY can optionally provide the xterm service of allowing server -applications to find out the local window title. This feature is -disabled by default, but you can turn it on if you really want it. - -NOTE that this feature is a \e{potential \i{security hazard}}. If a -malicious application can write data to your terminal (for example, -if you merely \c{cat} a file owned by someone else on the server -machine), it can change your window title (unless you have disabled -this as mentioned in \k{config-features-retitle}) and then use this -service to have the new window title sent back to the server as if -typed at the keyboard. This allows an attacker to fake keypresses -and potentially cause your server-side applications to do things you -didn't want. Therefore this feature is disabled by default, and we -recommend you do not set it to \q{Window title} unless you \e{really} -know what you are doing. - -There are three settings for this option: - -\dt \q{None} - -\dd PuTTY makes no response whatsoever to the relevant escape -sequence. This may upset server-side software that is expecting some -sort of response. - -\dt \q{Empty string} - -\dd PuTTY makes a well-formed response, but leaves it blank. Thus, -server-side software that expects a response is kept happy, but an -attacker cannot influence the response string. This is probably the -setting you want if you have no better ideas. - -\dt \q{Window title} - -\dd PuTTY responds with the actual window title. This is dangerous for -the reasons described above. - -\S{config-features-clearscroll} Disabling remote \i{scrollback clearing} - -PuTTY has the ability to clear the terminal's scrollback buffer in -response to a command from the server. If you find PuTTY is doing this -unexpectedly or inconveniently, you can tell PuTTY not to respond to -that server command. - -\S{config-features-dbackspace} Disabling \i{destructive backspace} - -Normally, when PuTTY receives character 127 (^?) from the server, it -will perform a \q{destructive backspace}: move the cursor one space -left and delete the character under it. This can apparently cause -problems in some applications, so PuTTY provides the ability to -configure character 127 to perform a normal backspace (without -deleting a character) instead. - -\S{config-features-charset} Disabling remote \i{character set} -configuration - -PuTTY has the ability to change its character set configuration in -response to commands from the server. Some programs send these -commands unexpectedly or inconveniently. In particular, \i{BitchX} (an -IRC client) seems to have a habit of reconfiguring the character set -to something other than the user intended. - -If you find that accented characters are not showing up the way you -expect them to, particularly if you're running BitchX, you could try -disabling the remote character set configuration commands. - -\S{config-features-shaping} Disabling \i{Arabic text shaping} - -PuTTY supports shaping of Arabic text, which means that if your -server sends text written in the basic \i{Unicode} Arabic alphabet then -it will convert it to the correct display forms before printing it -on the screen. - -If you are using full-screen software which was not expecting this -to happen (especially if you are not an Arabic speaker and you -unexpectedly find yourself dealing with Arabic text files in -applications which are not Arabic-aware), you might find that the -\i{display becomes corrupted}. By ticking this box, you can disable -Arabic text shaping so that PuTTY displays precisely the characters -it is told to display. - -You may also find you need to disable bidirectional text display; -see \k{config-features-bidi}. - -\S{config-features-bidi} Disabling \i{bidirectional text} display - -PuTTY supports bidirectional text display, which means that if your -server sends text written in a language which is usually displayed -from right to left (such as \i{Arabic} or \i{Hebrew}) then PuTTY will -automatically flip it round so that it is displayed in the right -direction on the screen. - -If you are using full-screen software which was not expecting this -to happen (especially if you are not an Arabic speaker and you -unexpectedly find yourself dealing with Arabic text files in -applications which are not Arabic-aware), you might find that the -\i{display becomes corrupted}. By ticking this box, you can disable -bidirectional text display, so that PuTTY displays text from left to -right in all situations. - -You may also find you need to disable Arabic text shaping; -see \k{config-features-shaping}. - -\H{config-window} The Window panel - -The Window configuration panel allows you to control aspects of the -\i{PuTTY window}. - -\S{config-winsize} Setting the \I{window size}size of the PuTTY window - -The \q{\ii{Columns}} and \q{\ii{Rows}} boxes let you set the PuTTY -window to a precise size. Of course you can also \I{window resizing}drag -the window to a new size while a session is running. - -\S{config-winsizelock} What to do when the window is resized - -These options allow you to control what happens when the user tries -to \I{window resizing}resize the PuTTY window using its window furniture. - -There are four options here: - -\b \q{Change the number of rows and columns}: the font size will not -change. (This is the default.) - -\b \q{Change the size of the font}: the number of rows and columns in -the terminal will stay the same, and the \i{font size} will change. - -\b \q{Change font size when maximised}: when the window is resized, -the number of rows and columns will change, \e{except} when the window -is \i{maximise}d (or restored), when the font size will change. (In -this mode, holding down the Alt key while resizing will also cause the -font size to change.) - -\b \q{Forbid resizing completely}: the terminal will refuse to be -resized at all. - -\S{config-scrollback} Controlling \i{scrollback} - -These options let you configure the way PuTTY keeps text after it -scrolls off the top of the screen (see \k{using-scrollback}). - -The \q{Lines of scrollback} box lets you configure how many lines of -text PuTTY keeps. The \q{Display scrollbar} options allow you to -hide the \i{scrollbar} (although you can still view the scrollback using -the keyboard as described in \k{using-scrollback}). You can separately -configure whether the scrollbar is shown in \i{full-screen} mode and in -normal modes. - -If you are viewing part of the scrollback when the server sends more -text to PuTTY, the screen will revert to showing the current -terminal contents. You can disable this behaviour by turning off -\q{Reset scrollback on display activity}. You can also make the -screen revert when you press a key, by turning on \q{Reset -scrollback on keypress}. - -\S{config-erasetoscrollback} \q{Push erased text into scrollback} - -When this option is enabled, the contents of the terminal screen -will be pushed into the scrollback when a server-side application -clears the screen, so that your scrollback will contain a better -record of what was on your screen in the past. - -If the application switches to the \i{alternate screen} (see -\k{config-features-altscreen} for more about this), then the -contents of the primary screen will be visible in the scrollback -until the application switches back again. - -This option is enabled by default. - -\H{config-appearance} The Appearance panel - -The Appearance configuration panel allows you to control aspects of -the appearance of \I{PuTTY window}PuTTY's window. - -\S{config-cursor} Controlling the appearance of the \i{cursor} - -The \q{Cursor appearance} option lets you configure the cursor to be -a block, an underline, or a vertical line. A block cursor becomes an -empty box when the window loses focus; an underline or a vertical -line becomes dotted. - -The \q{\ii{Cursor blinks}} option makes the cursor blink on and off. This -works in any of the cursor modes. - -\S{config-font} Controlling the \i{font} used in the terminal window - -This option allows you to choose what font, in what \I{font size}size, -the PuTTY terminal window uses to display the text in the session. - -By default, you will be offered a choice from all the fixed-width -fonts installed on the system, since VT100-style terminal handling -expects a fixed-width font. If you tick the box marked \q{Allow -selection of variable-pitch fonts}, however, PuTTY will offer -variable-width fonts as well: if you select one of these, the font -will be coerced into fixed-size character cells, which will probably -not look very good (but can work OK with some fonts). - -\S{config-mouseptr} \q{Hide \i{mouse pointer} when typing in window} - -If you enable this option, the mouse pointer will disappear if the -PuTTY window is selected and you press a key. This way, it will not -obscure any of the text in the window while you work in your -session. As soon as you move the mouse, the pointer will reappear. - -This option is disabled by default, so the mouse pointer remains -visible at all times. - -\S{config-winborder} Controlling the \i{window border} - -PuTTY allows you to configure the appearance of the window border to -some extent. - -The checkbox marked \q{Sunken-edge border} changes the appearance of -the window border to something more like a DOS box: the inside edge -of the border is highlighted as if it sank down to meet the surface -inside the window. This makes the border a little bit thicker as -well. It's hard to describe well. Try it and see if you like it. - -You can also configure a completely blank gap between the text in -the window and the border, using the \q{Gap between text and window -edge} control. By default this is set at one pixel. You can reduce -it to zero, or increase it further. - -\H{config-behaviour} The Behaviour panel - -The Behaviour configuration panel allows you to control aspects of -the behaviour of \I{PuTTY window}PuTTY's window. - -\S{config-title} Controlling the \i{window title} - -The \q{Window title} edit box allows you to set the title of the -PuTTY window. By default the window title will contain the \i{host name} -followed by \q{PuTTY}, for example \c{server1.example.com - PuTTY}. -If you want a different window title, this is where to set it. - -PuTTY allows the server to send \c{xterm} \i{control sequence}s which -modify the title of the window in mid-session (unless this is disabled - -see \k{config-features-retitle}); the title string set here -is therefore only the \e{initial} window title. - -As well as the \e{window} title, there is also an \c{xterm} -sequence to modify the \I{icon title}title of the window's \e{icon}. -This makes sense in a windowing system where the window becomes an -icon when minimised, such as Windows 3.1 or most X Window System -setups; but in the Windows 95-like user interface it isn't as -applicable. - -By default, PuTTY only uses the server-supplied \e{window} title, and -ignores the icon title entirely. If for some reason you want to see -both titles, check the box marked \q{Separate window and icon titles}. -If you do this, PuTTY's window title and Taskbar \I{window caption}caption will -change into the server-supplied icon title if you \i{minimise} the PuTTY -window, and change back to the server-supplied window title if you -restore it. (If the server has not bothered to supply a window or -icon title, none of this will happen.) - -\S{config-warnonclose} \q{Warn before \i{closing window}} - -If you press the \i{Close button} in a PuTTY window that contains a -running session, PuTTY will put up a warning window asking if you -really meant to close the window. A window whose session has already -terminated can always be closed without a warning. - -If you want to be able to close a window quickly, you can disable -the \q{Warn before closing window} option. - -\S{config-altf4} \q{Window closes on \i{ALT-F4}} - -By default, pressing ALT-F4 causes the \I{closing window}window to -close (or a warning box to appear; see \k{config-warnonclose}). If you -disable the \q{Window closes on ALT-F4} option, then pressing ALT-F4 -will simply send a key sequence to the server. - -\S{config-altspace} \q{\ii{System menu} appears on \i{ALT-Space}} - -If this option is enabled, then pressing ALT-Space will bring up the -PuTTY window's menu, like clicking on the top left corner. If it is -disabled, then pressing ALT-Space will just send \c{ESC SPACE} to -the server. - -Some \i{accessibility} programs for Windows may need this option -enabling to be able to control PuTTY's window successfully. For -instance, \i{Dragon NaturallySpeaking} requires it both to open the -system menu via voice, and to close, minimise, maximise and restore -the window. - -\S{config-altonly} \q{\ii{System menu} appears on \i{Alt} alone} - -If this option is enabled, then pressing and releasing ALT will -bring up the PuTTY window's menu, like clicking on the top left -corner. If it is disabled, then pressing and releasing ALT will have -no effect. - -\S{config-alwaysontop} \q{Ensure window is \i{always on top}} - -If this option is enabled, the PuTTY window will stay on top of all -other windows. - -\S{config-fullscreen} \q{\ii{Full screen} on Alt-Enter} - -If this option is enabled, then pressing Alt-Enter will cause the -PuTTY window to become full-screen. Pressing Alt-Enter again will -restore the previous window size. - -The full-screen feature is also available from the \ii{System menu}, even -when it is configured not to be available on the Alt-Enter key. See -\k{using-fullscreen}. - -\H{config-translation} The Translation panel - -The Translation configuration panel allows you to control the -translation between the \i{character set} understood by the server and -the character set understood by PuTTY. - -\S{config-charset} Controlling character set translation - -During an interactive session, PuTTY receives a stream of 8-bit -bytes from the server, and in order to display them on the screen it -needs to know what character set to interpret them in. Similarly, -PuTTY needs to know how to translate your keystrokes into the encoding -the server expects. Unfortunately, there is no satisfactory -mechanism for PuTTY and the server to communicate this information, -so it must usually be manually configured. - -There are a lot of character sets to choose from. The \q{Remote -character set} option lets you select one. - -By default PuTTY will use the \i{UTF-8} encoding of \i{Unicode}, which -can represent pretty much any character; data coming from the server -is interpreted as UTF-8, and keystrokes are sent UTF-8 encoded. This -is what most modern distributions of Linux will expect by default. -However, if this is wrong for your server, you can select a different -character set using this control. - -A few other notable character sets are: - -\b The \i{ISO-8859} series are all standard character sets that include -various accented characters appropriate for different sets of -languages. - -\b The \i{Win125x} series are defined by Microsoft, for similar -purposes. In particular Win1252 is almost equivalent to ISO-8859-1, -but contains a few extra characters such as matched quotes and the -Euro symbol. - -\b If you want the old IBM PC character set with block graphics and -line-drawing characters, you can select \q{\i{CP437}}. - -If you need support for a numeric \i{code page} which is not listed in -the drop-down list, such as code page 866, then you can try entering -its name manually (\c{\i{CP866}} for example) in the list box. If the -underlying version of Windows has the appropriate translation table -installed, PuTTY will use it. - -\S{config-cjk-ambig-wide} \q{Treat \i{CJK} ambiguous characters as wide} - -There are \I{East Asian Ambiguous characters}some Unicode characters -whose \I{character width}width is not well-defined. In most contexts, such -characters should be treated as single-width for the purposes of \I{wrapping, -terminal}wrapping and so on; however, in some CJK contexts, they are better -treated as double-width for historical reasons, and some server-side -applications may expect them to be displayed as such. Setting this option -will cause PuTTY to take the double-width interpretation. - -If you use legacy CJK applications, and you find your lines are -wrapping in the wrong places, or you are having other display -problems, you might want to play with this setting. - -This option only has any effect in \i{UTF-8} mode (see \k{config-charset}). - -\S{config-cyr} \q{\i{Caps Lock} acts as \i{Cyrillic} switch} - -This feature allows you to switch between a US/UK keyboard layout -and a Cyrillic keyboard layout by using the Caps Lock key, if you -need to type (for example) \i{Russian} and English side by side in the -same document. - -Currently this feature is not expected to work properly if your -native keyboard layout is not US or UK. - -\S{config-linedraw} Controlling display of \i{line-drawing characters} - -VT100-series terminals allow the server to send \i{control sequence}s that -shift temporarily into a separate character set for drawing simple -lines and boxes. However, there are a variety of ways in which PuTTY -can attempt to find appropriate characters, and the right one to use -depends on the locally configured \i{font}. In general you should probably -try lots of options until you find one that your particular font -supports. - -\b \q{Use Unicode line drawing code points} tries to use the box -characters that are present in \i{Unicode}. For good Unicode-supporting -fonts this is probably the most reliable and functional option. - -\b \q{Poor man's line drawing} assumes that the font \e{cannot} -generate the line and box characters at all, so it will use the -\c{+}, \c{-} and \c{|} characters to draw approximations to boxes. -You should use this option if none of the other options works. - -\b \q{Font has XWindows encoding} is for use with fonts that have a -special encoding, where the lowest 32 character positions (below the -ASCII printable range) contain the line-drawing characters. This is -unlikely to be the case with any standard Windows font; it will -probably only apply to custom-built fonts or fonts that have been -automatically converted from the X Window System. - -\b \q{Use font in both ANSI and OEM modes} tries to use the same -font in two different character sets, to obtain a wider range of -characters. This doesn't always work; some fonts claim to be a -different size depending on which character set you try to use. - -\b \q{Use font in OEM mode only} is more reliable than that, but can -miss out other characters from the main character set. - -\S{config-linedrawpaste} Controlling \i{copy and paste} of line drawing -characters - -By default, when you copy and paste a piece of the PuTTY screen that -contains VT100 line and box drawing characters, PuTTY will paste -them in the form they appear on the screen: either \i{Unicode} line -drawing code points, or the \q{poor man's} line-drawing characters -\c{+}, \c{-} and \c{|}. The checkbox \q{Copy and paste VT100 line -drawing chars as lqqqk} disables this feature, so line-drawing -characters will be pasted as the \i{ASCII} characters that were printed -to produce them. This will typically mean they come out mostly as -\c{q} and \c{x}, with a scattering of \c{jklmntuvw} at the corners. -This might be useful if you were trying to recreate the same box -layout in another program, for example. - -Note that this option only applies to line-drawing characters which -\e{were} printed by using the VT100 mechanism. Line-drawing -characters that were received as Unicode code points will paste as -Unicode always. - -\S{config-utf8linedraw} Combining VT100 line-drawing with UTF-8 - -If PuTTY is configured to treat data from the server as encoded in -UTF-8, then by default it disables the older VT100-style system of -control sequences that cause the lower-case letters to be temporarily -replaced by line drawing characters. - -The rationale is that in UTF-8 mode you don't need those control -sequences anyway, because all the line-drawing characters they access -are available as Unicode characters already, so there's no need for -applications to put the terminal into a special state to get at them. - -Also, it removes a risk of the terminal \e{accidentally} getting into -that state: if you accidentally write uncontrolled binary data to a -non-UTF-8 terminal, it can be surprisingly common to find that your -next shell prompt appears as a sequence of line-drawing characters and -then you have to remember or look up how to get out of that mode. So -by default, UTF-8 mode simply doesn't \e{have} a confusing mode like -that to get into, accidentally or on purpose. - -However, not all applications will see it that way. Even UTF-8 -terminal users will still sometimes have to run software that tries to -print line-drawing characters in the old-fashioned way. So the -configuration option \q{Enable VT100 line drawing even in UTF-8 mode} -puts PuTTY into a hybrid mode in which it understands the VT100-style -control sequences that change the meaning of the ASCII lower case -letters, \e{and} understands UTF-8. - -\H{config-selection} The Selection panel - -The Selection panel allows you to control the way \i{copy and paste} -work in the PuTTY window. - -\S{config-mouse} Changing the actions of the mouse buttons - -PuTTY's copy and paste mechanism is by default modelled on the Unix -\i\c{xterm} application. The X Window System uses a three-button mouse, -and the convention in that system is that the \i{left button} -\I{selecting text}selects, the \i{right button} extends an existing -selection, and the \i{middle button} pastes. - -Windows often only has two mouse buttons, so when run on Windows, -PuTTY is configurable. In PuTTY's default configuration -(\q{Compromise}), the \e{right} button pastes, and the \e{middle} -button (if you have one) \I{adjusting a selection}extends a -selection. - -If you have a \i{three-button mouse} and you are already used to the -\c{xterm} arrangement, you can select it using the \q{Action of -mouse buttons} control. - -Alternatively, with the \q{Windows} option selected, the middle -button extends, and the right button brings up a \i{context menu} (on -which one of the options is \q{Paste}). (This context menu is always -available by holding down Ctrl and right-clicking, regardless of the -setting of this option.) - -(When PuTTY itself is running on Unix, it follows the X Window System -convention.) - -\S{config-mouseshift} \q{Shift overrides application's use of mouse} - -PuTTY allows the server to send \i{control codes} that let it -\I{mouse reporting}take over the mouse and use it for purposes other -than \i{copy and paste}. -Applications which use this feature include the text-mode web -browser \c{links}, the Usenet newsreader \c{trn} version 4, and the -file manager \c{mc} (Midnight Commander). - -When running one of these applications, pressing the mouse buttons -no longer performs copy and paste. If you do need to copy and paste, -you can still do so if you hold down Shift while you do your mouse -clicks. - -However, it is possible in theory for applications to even detect -and make use of Shift + mouse clicks. We don't know of any -applications that do this, but in case someone ever writes one, -unchecking the \q{Shift overrides application's use of mouse} -checkbox will cause Shift + mouse clicks to go to the server as well -(so that mouse-driven copy and paste will be completely disabled). - -If you want to prevent the application from taking over the mouse at -all, you can do this using the Features control panel; see -\k{config-features-mouse}. - -\S{config-rectselect} Default selection mode - -As described in \k{using-selection}, PuTTY has two modes of -selecting text to be copied to the clipboard. In the default mode -(\q{Normal}), dragging the mouse from point A to point B selects to -the end of the line containing A, all the lines in between, and from -the very beginning of the line containing B. In the other mode -(\q{Rectangular block}), dragging the mouse between two points -defines a rectangle, and everything within that rectangle is copied. - -Normally, you have to hold down Alt while dragging the mouse to -select a rectangular block. Using the \q{Default selection mode} -control, you can set \i{rectangular selection} as the default, and then -you have to hold down Alt to get the \e{normal} behaviour. - -\S{config-clipboards} Assigning copy and paste actions to clipboards - -Here you can configure which clipboard(s) are written or read by -PuTTY's various copy and paste actions. - -Most platforms, including Windows, have a single system clipboard. -On these platforms, PuTTY provides a second clipboard-like facility by -permitting you to paste the text you last selected in \e{this window}, -whether or not it is currently also in the system clipboard. This is -not enabled by default. - -The X Window System (which underlies most Unix graphical interfaces) -provides multiple clipboards (or \q{\i{selections}}), and many -applications support more than one of them by a different user -interface mechanism. When PuTTY itself is running on Unix, it has -more configurability relating to these selections. - -The two most commonly used selections are called \cq{\i{PRIMARY}} and -\cq{\I{CLIPBOARD selection}CLIPBOARD}; in applications supporting both, -the usual behaviour is that \cw{PRIMARY} is used by mouse-only actions -(selecting text automatically copies it to \cw{PRIMARY}, and -\i{middle-clicking} pastes from \cw{PRIMARY}), whereas \cw{CLIPBOARD} -is used by explicit Copy and Paste menu items or keypresses such as -\i{Ctrl-C} and \i{Ctrl-V}. - -\S2{config-selection-autocopy} \q{Auto-copy selected text} - -The checkbox \q{Auto-copy selected text to system clipboard} controls -whether or not selecting text in the PuTTY terminal window -automatically has the side effect of copying it to the system -clipboard, without requiring a separate user interface action. - -On X, the wording of this option is changed slightly so that -\cq{CLIPBOARD} is mentioned in place of the \q{system clipboard}. Text -selected in the terminal window will \e{always} be automatically -placed in the \cw{PRIMARY} selection, as is conventional, but if you -tick this box, it will \e{also} be placed in \cq{CLIPBOARD} at the -same time. - -\S2{config-selection-clipactions} Choosing a clipboard for UI actions - -PuTTY has three user-interface actions which can be configured to -paste into the terminal (not counting menu items). You can click -whichever mouse button (if any) is configured to paste (see -\k{config-mouse}); you can press \i{Shift-Ins}; or you can press -\i{Ctrl-Shift-V}, although that action is not enabled by default. - -You can configure which of the available clipboards each of these -actions pastes from (including turning the paste action off -completely). On platforms with a single system clipboard (such as -Windows), the available options are to paste from that clipboard or -to paste from PuTTY's internal memory of the \i{last selected text} -within that window. On X, the standard options are \cw{CLIPBOARD} or -\cw{PRIMARY}. - -(\cw{PRIMARY} is conceptually similar in that it \e{also} refers to -the last selected text \dash just across all applications instead of -just this window.) - -The two keyboard options each come with a corresponding key to copy -\e{to} the same clipboard. Whatever you configure Shift-Ins to paste -from, \i{Ctrl-Ins} will copy to the same location; similarly, -\i{Ctrl-Shift-C} will copy to whatever Ctrl-Shift-V pastes from. - -On X, you can also enter a selection name of your choice. For example, -there is a rarely-used standard selection called \cq{\i{SECONDARY}}, which -Emacs (for example) can work with if you hold down the Meta key while -dragging to select or clicking to paste; if you configure a PuTTY -keyboard action to access this clipboard, then you can interoperate -with other applications' use of it. Another thing you could do would -be to invent a clipboard name yourself, to create a special clipboard -shared \e{only} between instances of PuTTY, or between just instances -configured in that particular way. - -\S{config-paste-ctrl-char} \q{Permit control characters in pasted text} - -It is possible for the clipboard to contain not just text (with -newlines and tabs) but also control characters such as ESC which could -have surprising effects if pasted into a terminal session, depending -on what program is running on the server side. Copying text from a -mischievous web page could put such characters onto the clipboard. - -By default, PuTTY filters out the more unusual control characters, -only letting through the more obvious text-formatting characters -(newlines, tab, backspace, and DEL). - -Setting this option stops this filtering; on paste, any character on -the clipboard is sent to the session uncensored. This might be useful -if you are deliberately using control character pasting as a simple -form of scripting, for instance. - -\H{config-selection-copy} The Copy panel - -The Copy configuration panel controls behaviour specifically related to -copying from the terminal window to the clipboard. - -\S{config-charclasses} Character classes - -PuTTY will \I{word-by-word selection}select a word at a time in the -terminal window if you \i{double-click} to begin the drag. This section -allows you to control precisely what is considered to be a word. - -Each character is given a \e{class}, which is a small number -(typically 0, 1 or 2). PuTTY considers a single word to be any -number of adjacent characters in the same class. So by modifying the -assignment of characters to classes, you can modify the word-by-word -selection behaviour. - -In the default configuration, the \i{character classes} are: - -\b Class 0 contains \i{white space} and control characters. - -\b Class 1 contains most \i{punctuation}. - -\b Class 2 contains letters, numbers and a few pieces of punctuation -(the double quote, minus sign, period, forward slash and -underscore). - -So, for example, if you assign the \c{@} symbol into character class -2, you will be able to select an e-mail address with just a double -click. - -In order to adjust these assignments, you start by selecting a group -of characters in the list box. Then enter a class number in the edit -box below, and press the \q{Set} button. - -This mechanism currently only covers ASCII characters, because it -isn't feasible to expand the list to cover the whole of Unicode. - -Character class definitions can be modified by \i{control sequence}s -sent by the server. This configuration option controls the -\e{default} state, which will be restored when you reset the -terminal (see \k{reset-terminal}). However, if you modify this -option in mid-session using \q{Change Settings}, it will take effect -immediately. - -\S{config-rtfcopy} Copying in \i{Rich Text Format} - -If you enable \q{Copy to clipboard in RTF as well as plain text}, -PuTTY will write formatting information to the clipboard as well as -the actual text you copy. The effect of this is -that if you paste into (say) a word processor, the text will appear -in the word processor in the same \i{font}, \i{colour}, and style -(e.g. bold, underline) PuTTY was using to display it. - -This option can easily be inconvenient, so by default it is -disabled. - -\H{config-colours} The Colours panel - -The Colours panel allows you to control PuTTY's use of \i{colour}. - -\S{config-ansicolour} \q{Allow terminal to specify \i{ANSI colours}} - -This option is enabled by default. If it is disabled, PuTTY will -ignore any \i{control sequence}s sent by the server to request coloured -text. - -If you have a particularly garish application, you might want to -turn this option off and make PuTTY only use the default foreground -and background colours. - -\S{config-xtermcolour} \q{Allow terminal to use xterm \i{256-colour mode}} - -This option is enabled by default. If it is disabled, PuTTY will -ignore any control sequences sent by the server which use the -extended 256-colour mode supported by recent versions of \cw{xterm}. - -If you have an application which is supposed to use 256-colour mode -and it isn't working, you may find you need to tell your server that -your terminal supports 256 colours. On Unix, you do this by ensuring -that the setting of \i\cw{TERM} describes a 256-colour-capable -terminal. You can check this using a command such as \c{infocmp}: - -\c $ infocmp | grep colors -\c colors#256, cols#80, it#8, lines#24, pairs#256, -\e bbbbbbbbbb - -If you do not see \cq{colors#256} in the output, you may need to -change your terminal setting. On modern Linux machines, you could -try \cq{xterm-256color}. - -\S{config-truecolour} \q{Allow terminal to use 24-bit colour} - -This option is enabled by default. If it is disabled, PuTTY will -ignore any control sequences sent by the server which use the control -sequences supported by modern terminals to specify arbitrary 24-bit -RGB colour value. - -\S{config-boldcolour} \q{Indicate bolded text by changing...} - -When the server sends a \i{control sequence} indicating that some text -should be displayed in \i{bold}, PuTTY can handle this in several -ways. It can either change the \i{font} for a bold version, or use the -same font in a brighter colour, or it can do both (brighten the colour -\e{and} embolden the font). This control lets you choose which. - -By default bold is indicated by colour, so non-bold text is displayed -in light grey and bold text is displayed in bright white (and -similarly in other colours). If you change the setting to \q{The font} -box, bold and non-bold text will be displayed in the same colour, and -instead the font will change to indicate the difference. If you select -\q{Both}, the font and the colour will both change. - -Some applications rely on \q{\i{bold black}} being distinguishable -from a black background; if you choose \q{The font}, their text may -become invisible. - -\S{config-logpalette} \q{Attempt to use \i{logical palettes}} - -Logical palettes are a mechanism by which a Windows application -running on an \i{8-bit colour} display can select precisely the colours -it wants instead of going with the Windows standard defaults. - -If you are not getting the colours you ask for on an 8-bit display, -you can try enabling this option. However, be warned that it's never -worked very well. - -\S{config-syscolour} \q{Use \i{system colours}} - -Enabling this option will cause PuTTY to ignore the configured colours -for \I{default background}\I{default foreground}\q{Default -Background/Foreground} and \I{cursor colour}\q{Cursor Colour/Text} (see -\k{config-colourcfg}), instead going with the system-wide defaults. - -Note that non-bold and \i{bold text} will be the same colour if this -option is enabled. You might want to change to indicating bold text -by font changes (see \k{config-boldcolour}). - -\S{config-colourcfg} Adjusting the colours in the \i{terminal window} - -The main colour control allows you to specify exactly what colours -things should be displayed in. To modify one of the PuTTY colours, -use the list box to select which colour you want to modify. The \i{RGB -values} for that colour will appear on the right-hand side of the -list box. Now, if you press the \q{Modify} button, you will be -presented with a colour selector, in which you can choose a new -colour to go in place of the old one. (You may also edit the RGB -values directly in the edit boxes, if you wish; each value is an -integer from 0 to 255.) - -PuTTY allows you to set the \i{cursor colour}, the \i{default foreground} -and \I{default background}background, and the precise shades of all the -\I{ANSI colours}ANSI configurable colours (black, red, green, yellow, blue, -magenta, cyan, and white). You can also modify the precise shades used for -the \i{bold} versions of these colours; these are used to display bold text -if you have chosen to indicate that by colour (see \k{config-boldcolour}), -and can also be used if the server asks specifically to use them. (Note -that \q{Default Bold Background} is \e{not} the background colour used for -bold text; it is only used if the server specifically asks for a bold -background.) - -\H{config-connection} The Connection panel - -The Connection panel allows you to configure options that apply to -more than one type of \i{connection}. - -\S{config-keepalive} Using \i{keepalives} to prevent disconnection - -If you find your sessions are closing unexpectedly (most often with -\q{Connection reset by peer}) after they have been idle for a while, -you might want to try using this option. - -Some network \i{routers} and \i{firewalls} need to keep track of all -connections through them. Usually, these firewalls will assume a -connection is dead if no data is transferred in either direction -after a certain time interval. This can cause PuTTY sessions to be -unexpectedly closed by the firewall if no traffic is seen in the -session for some time. - -The keepalive option (\q{Seconds between keepalives}) allows you to -configure PuTTY to send data through the session at regular -intervals, in a way that does not disrupt the actual terminal -session. If you find your firewall is cutting \i{idle connections} off, -you can try entering a non-zero value in this field. The value is -measured in seconds; so, for example, if your firewall cuts -connections off after ten minutes then you might want to enter 300 -seconds (5 minutes) in the box. - -Note that keepalives are not always helpful. They help if you have a -firewall which drops your connection after an idle period; but if -the network between you and the server suffers from \i{breaks in -connectivity} then keepalives can actually make things worse. If a -session is idle, and connectivity is temporarily lost between the -endpoints, but the connectivity is restored before either side tries -to send anything, then there will be no problem - neither endpoint -will notice that anything was wrong. However, if one side does send -something during the break, it will repeatedly try to re-send, and -eventually give up and abandon the connection. Then when -connectivity is restored, the other side will find that the first -side doesn't believe there is an open connection any more. -Keepalives can make this sort of problem worse, because they -increase the probability that PuTTY will attempt to send data during -a break in connectivity. (Other types of periodic network activity -can cause this behaviour; in particular, SSH-2 re-keys can have -this effect. See \k{config-ssh-kex-rekey}.) - -Therefore, you might find that keepalives help -connection loss, or you might find they make it worse, depending on -what \e{kind} of network problems you have between you and the -server. - -Keepalives are only supported in Telnet and SSH; the Rlogin, SUPDUP, and -Raw protocols offer no way of implementing them. (For an alternative, see -\k{config-tcp-keepalives}.) - -Note that if you are using SSH-1 and the server has a bug that makes -it unable to deal with SSH-1 ignore messages (see -\k{config-ssh-bug-ignore1}), enabling keepalives will have no effect. - -\S{config-nodelay} \q{Disable \i{Nagle's algorithm}} - -Nagle's algorithm is a detail of TCP/IP implementations that tries -to minimise the number of small data packets sent down a network -connection. With Nagle's algorithm enabled, PuTTY's \i{bandwidth} usage -will be slightly more efficient; with it disabled, you may find you -get a faster response to your keystrokes when connecting to some -types of server. - -The Nagle algorithm is disabled by default for \i{interactive connections}. - -\S{config-tcp-keepalives} \q{Enable \i{TCP keepalives}} - -\e{NOTE:} TCP keepalives should not be confused with the -application-level keepalives described in \k{config-keepalive}. If in -doubt, you probably want application-level keepalives; TCP keepalives -are provided for completeness. - -The idea of TCP keepalives is similar to application-level keepalives, -and the same caveats apply. The main differences are: - -\b TCP keepalives are available on \e{all} network connection types, -including Raw, Rlogin, and SUPDUP. - -\b The interval between TCP keepalives is usually much longer, -typically two hours; this is set by the operating system, and cannot -be configured within PuTTY. - -\b If the operating system does not receive a response to a keepalive, -it may send out more in quick succession and terminate the connection -if no response is received. - -TCP keepalives may be more useful for ensuring that \i{half-open connections} -are terminated than for keeping a connection alive. - -TCP keepalives are disabled by default. - -\S{config-address-family} \q{\i{Internet protocol version}} - -This option allows the user to select between the old and new -Internet protocols and addressing schemes (\i{IPv4} and \i{IPv6}). -The selected protocol will be used for most outgoing network -connections (including connections to \I{proxy}proxies); however, -tunnels have their own configuration, for which see -\k{config-ssh-portfwd-address-family}. - -The default setting is \q{Auto}, which means PuTTY will do something -sensible and try to guess which protocol you wanted. (If you specify -a literal \i{Internet address}, it will use whichever protocol that -address implies. If you provide a \i{hostname}, it will see what kinds -of address exist for that hostname; it will use IPv6 if there is an -IPv6 address available, and fall back to IPv4 if not.) - -If you need to force PuTTY to use a particular protocol, you can -explicitly set this to \q{IPv4} or \q{IPv6}. - -\S{config-loghost} \I{logical host name}\q{Logical name of remote host} - -This allows you to tell PuTTY that the host it will really end up -connecting to is different from where it thinks it is making a -network connection. - -You might use this, for instance, if you had set up an SSH port -forwarding in one PuTTY session so that connections to some -arbitrary port (say, \cw{localhost} port 10022) were forwarded to a -second machine's SSH port (say, \cw{foovax} port 22), and then -started a second PuTTY connecting to the forwarded port. - -In normal usage, the second PuTTY will access the \i{host key cache} -under the host name and port it actually connected to (i.e. -\cw{localhost} port 10022 in this example). Using the logical host -name option, however, you can configure the second PuTTY to cache -the host key under the name of the host \e{you} know that it's -\e{really} going to end up talking to (here \c{foovax}). - -This can be useful if you expect to connect to the same actual -server through many different channels (perhaps because your port -forwarding arrangements keep changing): by consistently setting the -logical host name, you can arrange that PuTTY will not keep asking -you to reconfirm its host key. Conversely, if you expect to use the -same local port number for port forwardings to lots of different -servers, you probably didn't want any particular server's host key -cached under that local port number. (For this latter case, you -could instead explicitly configure host keys in the relevant sessions; -see \k{config-ssh-kex-manual-hostkeys}.) - -If you just enter a host name for this option, PuTTY will cache the -SSH host key under the default SSH port for that host, irrespective -of the port you really connected to (since the typical scenario is -like the above example: you connect to a silly real port number and -your connection ends up forwarded to the normal port-22 SSH server -of some other machine). To override this, you can append a port -number to the logical host name, separated by a colon. E.g. entering -\cq{foovax:2200} as the logical host name will cause the host key to -be cached as if you had connected to port 2200 of \c{foovax}. - -If you provide a host name using this option, it is also displayed -in other locations which contain the remote host name, such as the -default window title and the default SSH password prompt. This -reflects the fact that this is the host you're \e{really} connecting -to, which is more important than the mere means you happen to be -using to contact that host. (This applies even if you're using a -protocol other than SSH.) - -\H{config-data} The Data panel - -The Data panel allows you to configure various pieces of data which -can be sent to the server to affect your connection at the far end. - -Each option on this panel applies to more than one protocol. -Options which apply to only one protocol appear on that protocol's -configuration panels. - -\S{config-username} \q{\ii{Auto-login username}} - -All three of the SSH, Telnet, and Rlogin protocols allow you to -specify what user name you want to log in as, without having to type -it explicitly every time. (Some Telnet servers don't support this.) - -In this box you can type that user name. - -\S{config-username-from-env} Use of system username - -When the previous box (\k{config-username}) is left blank, by default, -PuTTY will prompt for a username at the time you make a connection. - -In some environments, such as the networks of large organisations -implementing \i{single sign-on}, a more sensible default may be to use -the name of the user logged in to the local operating system (if any); -this is particularly likely to be useful with \i{GSSAPI} key exchange -and user authentication (see \k{config-ssh-auth-gssapi} and -\k{config-ssh-gssapi-kex}). This control allows you to change the default -behaviour. - -The current system username is displayed in the dialog as a -convenience. It is not saved in the configuration; if a saved session -is later used by a different user, that user's name will be used. - -\S{config-termtype} \q{\ii{Terminal-type} string} - -Most servers you might connect to with PuTTY are designed to be -connected to from lots of different types of terminal. In order to -send the right \i{control sequence}s to each one, the server will need -to know what type of terminal it is dealing with. Therefore, each of -the SSH, Telnet, and Rlogin protocols allow a text string to be sent -down the connection describing the terminal. On a \i{Unix} server, -this selects an entry from the \i\c{termcap} or \i\c{terminfo} database -that tells applications what \i{control sequences} to send to the -terminal, and what character sequences to expect the \i{keyboard} -to generate. - -PuTTY attempts to emulate the Unix \i\c{xterm} program, and by default -it reflects this by sending \c{xterm} as a terminal-type string. If -you find this is not doing what you want - perhaps the remote -system reports \q{Unknown terminal type} - you could try setting -this to something different, such as \i\c{vt220}. - -If you're not sure whether a problem is due to the terminal type -setting or not, you probably need to consult the manual for your -application or your server. - -\S{config-termspeed} \q{\ii{Terminal speed}s} - -The Telnet, Rlogin, and SSH protocols allow the client to specify -terminal speeds to the server. - -This parameter does \e{not} affect the actual speed of the connection, -which is always \q{as fast as possible}; it is just a hint that is -sometimes used by server software to modify its behaviour. For -instance, if a slow speed is indicated, the server may switch to a -less \i{bandwidth}-hungry display mode. - -The value is usually meaningless in a network environment, but -PuTTY lets you configure it, in case you find the server is reacting -badly to the default value. - -The format is a pair of numbers separated by a comma, for instance, -\c{38400,38400}. The first number represents the output speed -(\e{from} the server) in bits per second, and the second is the input -speed (\e{to} the server). (Only the first is used in the Rlogin -protocol.) - -This option has no effect on Raw connections. - -\S{config-environ} Setting \i{environment variables} on the server - -The Telnet protocol provides a means for the client to pass -environment variables to the server. Many Telnet servers have -stopped supporting this feature due to security flaws, but PuTTY -still supports it for the benefit of any servers which have found -other ways around the security problems than just disabling the -whole mechanism. - -Version 2 of the SSH protocol also provides a similar mechanism, -which is easier to implement without security flaws. Newer \i{SSH-2} -servers are more likely to support it than older ones. - -This configuration data is not used in the SSH-1, rlogin or raw -protocols. - -To add an environment variable to the list transmitted down the -connection, you enter the variable name in the \q{Variable} box, -enter its value in the \q{Value} box, and press the \q{Add} button. -To remove one from the list, select it in the list box and press -\q{Remove}. - -\H{config-proxy} The Proxy panel - -The \ii{Proxy} panel allows you to configure PuTTY to use various types -of proxy in order to make its network connections. The settings in -this panel affect the primary network connection forming your PuTTY -session, and also any extra connections made as a result of SSH \i{port -forwarding} (see \k{using-port-forwarding}). - -Note that unlike some software (such as web browsers), PuTTY does not -attempt to automatically determine whether to use a proxy and (if so) -which one to use for a given destination. If you need to use a proxy, -it must always be explicitly configured. - -\S{config-proxy-type} Setting the proxy type - -The \q{Proxy type} drop-down allows you to configure what type of -proxy you want PuTTY to use for its network connections. The default -setting is \q{None}; in this mode no proxy is used for any -connection. - -\b Selecting \I{HTTP proxy}\q{HTTP CONNECT} allows you to proxy your -connections through a web server supporting the HTTP \cw{CONNECT} command, -as documented in \W{https://www.rfc-editor.org/rfc/rfc2817}{RFC 2817}. - -\b Selecting \q{SOCKS 4} or \q{SOCKS 5} allows you to proxy your -connections through a \i{SOCKS server}. - -\b Many firewalls implement a less formal type of proxy in which a -user can make a Telnet or TCP connection directly to the firewall machine -and enter a command such as \c{connect myhost.com 22} to connect -through to an external host. Selecting \I{Telnet proxy}\q{Telnet} -allows you to tell PuTTY to use this type of proxy, with the precise -command specified as described in \k{config-proxy-command}. - -\b There are several ways to use a SSH server as a proxy. All of -these cause PuTTY to make a secondary SSH connection to the proxy host -(sometimes called a \q{\i{jump host}} in this context). - -\lcont{ -The \q{Proxy hostname} field will be interpreted as the name of a -PuTTY saved session if one exists, or a hostname if not. This -allows multi-hop jump paths, if the referenced saved session is -itself configured to use an SSH proxy; and it allows combining SSH -and non-SSH proxying. - -\b \q{SSH to proxy and use port forwarding} causes PuTTY to use the -secondary SSH connection to open a port-forwarding channel to the -final destination host (similar to OpenSSH's \cw{-J} option). - -\b \q{SSH to proxy and execute a command} causes PuTTY to run an -arbitrary remote command on the proxy SSH server and use that -command's standard input and output streams to run the primary -connection over. The remote command line is specified as described in -\k{config-proxy-command}. - -\b \q{SSH to proxy and invoke a subsystem} is similar but causes PuTTY -to start an SSH \q{\i{subsystem}} rather than an ordinary command line. -This might be useful with a specially set up SSH proxy server. -} - -\b Selecting \I{Local proxy}\q{Local} allows you to specify an arbitrary -command on the local machine to act as a proxy. When the session is -started, instead of creating a TCP connection, PuTTY runs the command -(specified in \k{config-proxy-command}), and uses its standard input and -output streams. - -\lcont{ -This could be used, for instance, to talk to some kind of network proxy -that PuTTY does not natively support; or you could tunnel a connection -over something other than TCP/IP entirely. - -You can also enable this mode on the command line; see -\k{using-cmdline-proxycmd}. -} - -\S{config-proxy-exclude} Excluding parts of the network from proxying - -Typically you will only need to use a proxy to connect to non-local -parts of your network; for example, your proxy might be required for -connections outside your company's internal network. In the -\q{Exclude Hosts/IPs} box you can enter ranges of IP addresses, or -ranges of DNS names, for which PuTTY will avoid using the proxy and -make a direct connection instead. - -The \q{Exclude Hosts/IPs} box may contain more than one exclusion -range, separated by commas. Each range can be an IP address or a DNS -name, with a \c{*} character allowing wildcards. For example: - -\c *.example.com - -This excludes any host with a name ending in \c{.example.com} from -proxying. - -\c 192.168.88.* - -This excludes any host with an IP address starting with 192.168.88 -from proxying. - -\c 192.168.88.*,*.example.com - -This excludes both of the above ranges at once. - -Connections to the local host (the host name \i\c{localhost}, and any -\i{loopback IP address}) are never proxied, even if the proxy exclude -list does not explicitly contain them. It is very unlikely that this -behaviour would ever cause problems, but if it does you can change -it by enabling \q{Consider proxying local host connections}. - -Note that if you are doing \I{proxy DNS}DNS at the proxy (see -\k{config-proxy-dns}), you should make sure that your proxy -exclusion settings do not depend on knowing the IP address of a -host. If the name is passed on to the proxy without PuTTY looking it -up, it will never know the IP address and cannot check it against -your list. - -\S{config-proxy-dns} \I{proxy DNS}\ii{Name resolution} when using a proxy - -If you are using a proxy to access a private network, it can make a -difference whether \i{DNS} name resolution is performed by PuTTY itself -(on the client machine) or performed by the proxy. - -The \q{Do DNS name lookup at proxy end} configuration option allows -you to control this. If you set it to \q{No}, PuTTY will always do -its own DNS, and will always pass an IP address to the proxy. If you -set it to \q{Yes}, PuTTY will always pass host names straight to the -proxy without trying to look them up first. - -If you set this option to \q{Auto} (the default), PuTTY will do -something it considers appropriate for each type of proxy. Most -types of proxy (HTTP, SOCK5, SSH, Telnet, and local) will have host -names passed straight to them; SOCKS4 proxies will not. - -Note that if you are doing DNS at the proxy, you should make sure -that your proxy exclusion settings (see \k{config-proxy-exclude}) do -not depend on knowing the IP address of a host. If the name is -passed on to the proxy without PuTTY looking it up, it will never -know the IP address and cannot check it against your list. - -The original SOCKS 4 protocol does not support proxy-side DNS. There -is a protocol extension (SOCKS 4A) which does support it, but not -all SOCKS 4 servers provide this extension. If you enable proxy DNS -and your SOCKS 4 server cannot deal with it, this might be why. - -If you want to avoid PuTTY making \e{any} DNS query related to your -destination host name (for example, because your local DNS resolver is -very slow to return a negative response in that situation), then as -well as setting this control to \q{Yes}, you may also need to turn off -GSSAPI authentication and GSSAPI key exchange in SSH (see -\k{config-ssh-auth-gssapi} and \k{config-ssh-gssapi-kex} -respectively). This is because GSSAPI setup also involves a DNS query -for the destination host name, and that query is performed by the -separate GSSAPI library, so PuTTY can't override or reconfigure it. - -\S{config-proxy-auth} \I{proxy username}Username and \I{proxy password}password - -You can enter a username and a password in the \q{Username} and -\q{Password} boxes, which will be used if your proxy requires -\I{proxy authentication}authentication. - -\I{security hazard}Note that if you save your session, the proxy -password will be saved in plain text, so anyone who can access your PuTTY -configuration data will be able to discover it. - -If PuTTY discovers that it needs a proxy username or password and you -have not specified one here, PuTTY will prompt for it interactively in -the terminal window. - -Authentication is not fully supported for all forms of proxy: - -\b Username and password authentication is supported for HTTP -proxies and SOCKS 5 proxies. - -\lcont{ - -\b With SOCKS 5, authentication is via \i{CHAP} if the proxy -supports it (this is not supported in \i{PuTTYtel}); otherwise the -password is sent to the proxy in \I{plaintext password}plain text. - -\b With HTTP proxying, authentication is via \q{\i{HTTP Digest}} if -possible (again, not supported in PuTTYtel), or \q{\i{HTTP Basic}}. In -the latter case, the password is sent to the proxy in \I{plaintext -password}plain text. - -} - -\b SOCKS 4 can use the \q{Username} field, but does not support -passwords. - -\b SSH proxying can use all the same forms of SSH authentication -supported by PuTTY for its main connection. If the SSH server requests -password authentication, any configured proxy password will be used, -but other authentication methods such as public keys and GSSAPI will -be tried first, just as for a primary SSH connection, and if they -require credentials such as a key passphrase, PuTTY will interactively -prompt for these. - -\b You can specify a way to include a username and password in the -Telnet/Local proxy command (see \k{config-proxy-command}). If you do -so, and don't also specify the actual username and/or password in the -configuration, PuTTY will interactively prompt for them. - -\S{config-proxy-command} Specifying the Telnet, SSH, or Local proxy command - -If you are using the \i{Telnet proxy} type, the usual command required -by the firewall's Telnet server is \c{connect}, followed by a host -name and a port number. If your proxy needs a different command, -you can enter an alternative in the \q{Command to send to proxy} box. - -If you are using the \i{Local proxy} type, the local command to run -is specified here. - -If you are using the \q{SSH to proxy and execute a command} type, the -command to run on the SSH proxy server is specified here. Similarly, if -you are using \q{SSH to proxy and invoke a subsystem}, the subsystem -name is constructed as specified here. - -In this string, you can use \c{\\n} to represent a new-line, \c{\\r} -to represent a carriage return, \c{\\t} to represent a tab -character, and \c{\\x} followed by two hex digits to represent any -other character. \c{\\\\} is used to encode the \c{\\} character -itself. - -Also, the special strings \c{%host} and \c{%port} will be replaced -by the host name and port number you want to connect to. For Telnet -and Local proxy types, the strings \c{%user} and \c{%pass} will be -replaced by the proxy username and password (which, if not specified -in the configuration, will be prompted for) \dash this does not happen -with SSH proxy types (because the proxy username/password are used -for SSH authentication). The strings \c{%proxyhost} and \c{%proxyport} -will be replaced by the host details specified on the \e{Proxy} panel, -if any (this is most likely to be useful for proxy types using a -local or remote command). To get a literal \c{%} sign, enter \c{%%}. - -If a Telnet proxy server prompts for a username and password -before commands can be sent, you can use a command such as: - -\c %user\n%pass\nconnect %host %port\n - -This will send your username and password as the first two lines to -the proxy, followed by a command to connect to the desired host and -port. Note that if you do not include the \c{%user} or \c{%pass} -tokens in the Telnet command, then anything specified in \q{Username} -and \q{Password} configuration fields will be ignored. - -\S{config-proxy-logging} Controlling \i{proxy logging} - -Often the proxy interaction has its own diagnostic output; this is -particularly the case for local proxy commands. - -The setting \q{Print proxy diagnostics in the terminal window} lets -you control how much of the proxy's diagnostics are printed to the main -terminal window, along with output from your main session. - -By default (\q{No}), proxy diagnostics are only sent to the Event Log; -with \q{Yes} they are also printed to the terminal, where they may get -mixed up with your main session. \q{Only until session starts} is a -compromise; proxy messages will go to the terminal window until the main -session is deemed to have started (in a protocol-dependent way), which -is when they're most likely to be interesting; any further proxy-related -messages during the session will only go to the Event Log. - -\H{config-ssh} The SSH panel - -The \i{SSH} panel allows you to configure options that only apply to -SSH sessions. - -\S{config-command} Executing a specific command on the server - -In SSH, you don't have to run a general shell session on the server. -Instead, you can choose to run a single specific command (such as a -mail user agent, for example). If you want to do this, enter the -command in the \q{\ii{Remote command}} box. - -Note that most servers will close the session after executing the -command. - -\S{config-ssh-noshell} \q{Don't start a \I{remote shell}shell or -\I{remote command}command at all} - -If you tick this box, PuTTY will not attempt to run a shell or -command after connecting to the remote server. You might want to use -this option if you are only using the SSH connection for \i{port -forwarding}, and your user account on the server does not have the -ability to run a shell. - -This feature is only available in \i{SSH protocol version 2} (since the -version 1 protocol assumes you will always want to run a shell). - -This feature can also be enabled using the \c{-N} command-line -option; see \k{using-cmdline-noshell}. - -If you use this feature in Plink, you will not be able to terminate -the Plink process by any graceful means; the only way to kill it -will be by pressing Control-C or sending a kill signal from another -program. - -\S{config-ssh-comp} \q{Enable \i{compression}} - -This enables data compression in the SSH connection: data sent by -the server is compressed before sending, and decompressed at the -client end. Likewise, data sent by PuTTY to the server is compressed -first and the server decompresses it at the other end. This can help -make the most of a low-\i{bandwidth} connection. - -\S{config-ssh-prot} \q{\i{SSH protocol version}} - -This allows you to select whether to use \i{SSH protocol version 2} -or the older \I{SSH-1}version 1. - -You should normally leave this at the default of \q{2}. As well as -having fewer features, the older SSH-1 protocol is no longer -developed, has many known cryptographic weaknesses, and is generally -not considered to be secure. PuTTY's protocol 1 implementation is -provided mainly for compatibility, and is no longer being enhanced. - -If a server offers both versions, prefer \q{2}. If you have some -server or piece of equipment that only talks SSH-1, select \q{1} -here, and do not treat the resulting connection as secure. - -PuTTY will not automatically fall back to the other version of the -protocol if the server turns out not to match your selection here; -instead, it will put up an error message and abort the connection. -This prevents an active attacker downgrading an intended SSH-2 -connection to SSH-1. - -\S{config-ssh-sharing} Sharing an SSH connection between PuTTY tools - -The controls in this box allow you to configure PuTTY to reuse an -existing SSH connection, where possible. - -The SSH-2 protocol permits you to run multiple data channels over the -same SSH connection, so that you can log in just once (and do the -expensive encryption setup just once) and then have more than one -terminal window open. - -Each instance of PuTTY can still run at most one terminal session, but -using the controls in this box, you can configure PuTTY to check if -another instance of itself has already connected to the target host, -and if so, share that instance's SSH connection instead of starting a -separate new one. - -To enable this feature, just tick the box \q{Share SSH connections if -possible}. Then, whenever you start up a PuTTY session connecting to a -particular host, it will try to reuse an existing SSH connection if -one is available. For example, selecting \q{Duplicate Session} from -the system menu will launch another session on the same host, and if -sharing is enabled then it will reuse the existing SSH connection. - -When this mode is in use, the first PuTTY that connected to a given -server becomes the \q{upstream}, which means that it is the one -managing the real SSH connection. All subsequent PuTTYs which reuse -the connection are referred to as \q{downstreams}: they do not connect -to the real server at all, but instead connect to the upstream PuTTY -via local inter-process communication methods. - -For this system to be activated, \e{both} the upstream and downstream -instances of PuTTY must have the sharing option enabled. - -The upstream PuTTY can therefore not terminate until all its -downstreams have closed. This is similar to the effect you get with -port forwarding or X11 forwarding, in which a PuTTY whose terminal -session has already finished will still remain open so as to keep -serving forwarded connections. - -In case you need to configure this system in more detail, there are -two additional checkboxes which allow you to specify whether a -particular PuTTY can act as an upstream or a downstream or both. -(These boxes only take effect if the main \q{Share SSH connections if -possible} box is also ticked.) By default both of these boxes are -ticked, so that multiple PuTTYs started from the same configuration -will designate one of themselves as the upstream and share a single -connection; but if for some reason you need a particular PuTTY -configuration \e{not} to be an upstream (e.g. because you definitely -need it to close promptly) or not to be a downstream (e.g. because it -needs to do its own authentication using a special private key) then -you can untick one or the other of these boxes. - -I have referred to \q{PuTTY} throughout the above discussion, but all -the other PuTTY tools which make SSH connections can use this -mechanism too. For example, if PSCP or PSFTP loads a configuration -with sharing enabled, then it can act as a downstream and use an -existing SSH connection set up by an instance of GUI PuTTY. The one -special case is that PSCP and PSFTP will \e{never} act as upstreams. - -It is possible to test programmatically for the existence of a live -upstream using Plink. See \k{plink-option-shareexists}. - -\H{config-ssh-kex} The Kex panel - -The Kex panel (short for \q{\i{key exchange}}) allows you to configure -options related to SSH-2 key exchange. - -Key exchange occurs at the start of an SSH connection (and -occasionally thereafter); it establishes a \i{shared secret} that is used -as the basis for all of SSH's security features. It is therefore very -important for the security of the connection that the key exchange is -secure. - -Key exchange is a cryptographically intensive process; if either the -client or the server is a relatively slow machine, the slower methods -may take several tens of seconds to complete. - -If connection startup is too slow, or the connection hangs -periodically, you may want to try changing these settings. - -If you don't understand what any of this means, it's safe to leave -these settings alone. - -This entire panel is only relevant to SSH protocol version 2; none of -these settings affect SSH-1 at all. - -\S{config-ssh-kex-order} \ii{Key exchange algorithm} selection - -PuTTY supports a variety of SSH-2 key exchange methods, and allows you -to choose which one you prefer to use; configuration is similar to -cipher selection (see \k{config-ssh-encryption}). - -PuTTY currently supports the following key exchange methods: - -\b \q{NTRU Prime / Curve25519 hybrid}: \q{\i{Streamlined NTRU Prime}} -is a lattice-based algorithm intended to resist \i{quantum attacks}. -In this key exchange method, it is run in parallel with a conventional -Curve25519-based method (one of those included in \q{ECDH}), in such -a way that it should be no \e{less} secure than that commonly-used -method, and hopefully also resistant to a new class of attacks. - -\b \q{\i{ECDH}}: elliptic curve Diffie-Hellman key exchange, -with a variety of standard curves and hash algorithms. - -\b The original form of \i{Diffie-Hellman key exchange}, with a -variety of well-known groups and hashes: - -\lcont{ -\b \q{Group 18}, a well-known 8192-bit group, used with the SHA-512 -hash function. - -\b \q{Group 17}, a well-known 6144-bit group, used with the SHA-512 -hash function. - -\b \q{Group 16}, a well-known 4096-bit group, used with the SHA-512 -hash function. - -\b \q{Group 15}, a well-known 3072-bit group, used with the SHA-512 -hash function. - -\b \q{Group 14}: a well-known 2048-bit group, used with the SHA-256 -hash function or, if the server doesn't support that, SHA-1. - -\b \q{Group 1}: a well-known 1024-bit group, used with the SHA-1 -hash function. Neither we nor current SSH standards recommend using -this method any longer, and it's not used by default in new -installations; however, it may be the only method supported by very -old server software. -} - -\b \q{Diffie-Hellman \i{group exchange}}: with this method, instead -of using a fixed group, PuTTY requests that the server suggest a group -to use for a subsequent Diffie-Hellman key exchange; the server can -avoid groups known to be weak, and possibly invent new ones over time, -without any changes required to PuTTY's configuration. This key -exchange method uses the SHA-256 hash or, if the server doesn't -support that, SHA-1. - -\b \q{\i{RSA-based key exchange}}: this requires much less computational -effort on the part of the client, and somewhat less on the part of -the server, than Diffie-Hellman key exchange. - -\b \q{GSSAPI key exchange}: see \k{config-ssh-gssapi-kex}. - -If the first algorithm PuTTY finds is below the \q{warn below here} -line, you will see a warning box when you make the connection, similar -to that for cipher selection (see \k{config-ssh-encryption}). - -\S2{config-ssh-gssapi-kex} GSSAPI-based key exchange - -PuTTY supports a set of key exchange methods that also incorporates -GSSAPI-based authentication. They are enabled with the -\q{Attempt GSSAPI key exchange} checkbox (which also appears on the -\q{GSSAPI} panel). - -PuTTY can only perform the GSSAPI-authenticated key exchange methods -when using Kerberos V5, and not other GSSAPI mechanisms. If the user -running PuTTY has current Kerberos V5 credentials, then PuTTY will -select the GSSAPI key exchange methods in preference to any of the -ordinary SSH key exchange methods configured in the preference list. -There's a GSSAPI-based equivalent to most of the ordinary methods -listed in \k{config-ssh-kex-order}; server support determines which -one will be used. (PuTTY's preference order for GSSAPI-authenticated -key exchange methods is fixed, not controlled by the preference list.) - -The advantage of doing GSSAPI authentication as part of the SSH key -exchange is apparent when you are using credential delegation (see -\k{config-ssh-auth-gssapi-delegation}). The SSH key exchange can be -repeated later in the session, and this allows your Kerberos V5 -credentials (which are typically short-lived) to be automatically -re-delegated to the server when they are refreshed on the client. -(This feature is commonly referred to as \q{\i{cascading credentials}}.) - -If your server doesn't support GSSAPI key exchange, it may still -support GSSAPI in the SSH user authentication phase. This will still -let you log in using your Kerberos credentials, but will only allow -you to delegate the credentials that are active at the beginning of -the session; they can't be refreshed automatically later, in a -long-running session. See \k{config-ssh-auth-gssapi} for how to -control GSSAPI user authentication in PuTTY. - -Another effect of GSSAPI key exchange is that it replaces the usual -SSH mechanism of permanent host keys described in \k{gs-hostkey}. -So if you use this method, then you won't be asked any interactive -questions about whether to accept the server's host key. Instead, the -Kerberos exchange will verify the identity of the host you connect to, -at the same time as verifying your identity to it. - -\S{config-ssh-kex-rekey} \ii{Repeat key exchange} - -If the session key negotiated at connection startup is used too much -or for too long, it may become feasible to mount attacks against the -SSH connection. Therefore, the SSH-2 protocol specifies that a new key -exchange should take place every so often; this can be initiated by -either the client or the server. - -While this renegotiation is taking place, no data can pass through -the SSH connection, so it may appear to \q{freeze}. (The occurrence of -repeat key exchange is noted in the Event Log; see -\k{using-eventlog}.) Usually the same algorithm is used as at the -start of the connection, with a similar overhead. - -These options control how often PuTTY will initiate a repeat key -exchange (\q{rekey}). You can also force a key exchange at any time -from the Special Commands menu (see \k{using-specials}). - -\# FIXME: do we have any additions to the SSH-2 specs' advice on -these values? Do we want to enforce any limits? - -\b \q{Max minutes before rekey} specifies the amount of time that is -allowed to elapse before a rekey is initiated. If this is set to zero, -PuTTY will not rekey due to elapsed time. The SSH-2 protocol -specification recommends a timeout of at most 60 minutes. - -You might have a need to disable time-based rekeys completely for the same -reasons that \i{keepalives} aren't always helpful. If you anticipate -suffering a network dropout of several hours in the middle of an SSH -connection, but were not actually planning to send \e{data} down -that connection during those hours, then an attempted rekey in the -middle of the dropout will probably cause the connection to be -abandoned, whereas if rekeys are disabled then the connection should -in principle survive (in the absence of interfering \i{firewalls}). See -\k{config-keepalive} for more discussion of these issues; for these -purposes, rekeys have much the same properties as keepalives. -(Except that rekeys have cryptographic value in themselves, so you -should bear that in mind when deciding whether to turn them off.) -Note, however, the the SSH \e{server} can still initiate rekeys. - -\b \q{Minutes between GSSAPI checks}, if you're using GSSAPI key -exchange, specifies how often the GSSAPI credential cache is checked -to see whether new tickets are available for delegation, or current -ones are near expiration. If forwarding of GSSAPI credentials is -enabled, PuTTY will try to rekey as necessary to keep the delegated -credentials from expiring. Frequent checks are recommended; rekeying -only happens when needed. - -\b \q{Max data before rekey} specifies the amount of data (in bytes) -that is permitted to flow in either direction before a rekey is -initiated. If this is set to zero, PuTTY will not rekey due to -transferred data. The SSH-2 protocol specification recommends a limit -of at most 1 gigabyte. - -\lcont{ - -As well as specifying a value in bytes, the following shorthand can be -used: - -\b \cq{1k} specifies 1 kilobyte (1024 bytes). - -\b \cq{1M} specifies 1 megabyte (1024 kilobytes). - -\b \cq{1G} specifies 1 gigabyte (1024 megabytes). - -} - -Disabling data-based rekeys entirely is a bad idea. The \i{integrity}, -and to a lesser extent, \i{confidentiality} of the SSH-2 protocol depend -in part on rekeys occurring before a 32-bit packet sequence number -wraps around. Unlike time-based rekeys, data-based rekeys won't occur -when the SSH connection is idle, so they shouldn't cause the same -problems. The SSH-1 protocol, incidentally, has even weaker integrity -protection than SSH-2 without rekeys. - -\H{config-ssh-hostkey} The Host Keys panel - -The Host Keys panel allows you to configure options related to -\i{host key management}. - -Host keys are used to prove the server's identity, and assure you that -the server is not being spoofed (either by a man-in-the-middle attack -or by completely replacing it on the network). See \k{gs-hostkey} for -a basic introduction to host keys. - -Much of this panel is only relevant to SSH protocol version 2; SSH-1 -only supports one type of host key. - -\S{config-ssh-hostkey-order} \ii{Host key type} selection - -PuTTY supports a variety of SSH-2 host key types, and allows you to -choose which one you prefer to use to identify the server. -Configuration is similar to cipher selection (see -\k{config-ssh-encryption}). - -PuTTY currently supports the following host key types: - -\b \q{\i{Ed25519}}: \I{EdDSA}Edwards-curve DSA using a twisted Edwards -curve with modulus \cw{2^255-19}. - -\b \q{\i{Ed448}}: another \I{EdDSA}Edwards-curve DSA type, using a -larger elliptic curve with a 448-bit instead of 255-bit modulus (so it -has a higher security level than Ed25519). - -\b \q{ECDSA}: \i{elliptic curve} \i{DSA} using one of the -\i{NIST}-standardised elliptic curves. - -\b \q{DSA}: straightforward \i{DSA} using modular exponentiation. - -\b \q{RSA}: the ordinary \i{RSA} algorithm. - -If PuTTY already has one or more host keys stored for the server, -it will by default prefer to use one of those, even if the server has -a key type that is higher in the preference order. You can add such a -key to PuTTY's cache from within an existing session using the -\q{Special Commands} menu; see \k{using-specials}. - -Otherwise, PuTTY will choose a key type based purely on the -preference order you specify in the configuration. - -If the first key type PuTTY finds is below the \q{warn below here} -line, you will see a warning box when you make the connection, similar -to that for cipher selection (see \k{config-ssh-encryption}). - -\S{config-ssh-prefer-known-hostkeys} Preferring known host keys - -By default, PuTTY will adjust the preference order for SSH-2 host key -algorithms so that any host keys it already knows are moved to the top -of the list. - -This prevents you from having to check and confirm a new host key for -a server you already had one for (e.g. because the server has -generated an alternative key of a type higher in PuTTY's preference -order, or because you changed the preference order itself). - -However, on the other hand, it can leak information to a listener in -the network about \e{whether} you already know a host key for this -server. - -For this reason, this policy is configurable. By turning this checkbox -off, you can reset PuTTY to always use the exact order of host key -algorithms configured in the preference list described in -\k{config-ssh-hostkey-order}, so that a listener will find out nothing -about what keys you had stored. - -\S{config-ssh-kex-manual-hostkeys} \ii{Manually configuring host keys} - -In some situations, if PuTTY's automated host key management is not -doing what you need, you might need to manually configure PuTTY to -accept a specific host key, or one of a specific set of host keys. - -One reason why you might want to do this is because the host name -PuTTY is connecting to is using round-robin DNS to return one of -multiple actual servers, and they all have different host keys. In -that situation, you might need to configure PuTTY to accept any of a -list of host keys for the possible servers, while still rejecting any -key not in that list. - -Another reason is if PuTTY's automated host key management is -completely unavailable, e.g. because PuTTY (or Plink or PSFTP, etc) is -running in a Windows environment without access to the Registry. In -that situation, you will probably want to use the \cw{-hostkey} -command-line option to configure the expected host key(s); see -\k{using-cmdline-hostkey}. - -For situations where PuTTY's automated host key management simply -picks the wrong host name to store a key under, you may want to -consider setting a \q{logical host name} instead; see -\k{config-loghost}. - -To configure manual host keys via the GUI, enter some text describing -the host key into the edit box in the \q{Manually configure host keys -for this connection} container, and press the \q{Add} button. The text -will appear in the \q{Host keys or fingerprints to accept} list box. -You can remove keys again with the \q{Remove} button. - -The text describing a host key can be in one of the following formats: - -\b An \I{SHA256 fingerprint}SHA-256-based host key fingerprint of the -form displayed in PuTTY's Event Log and host key dialog boxes, -i.e. \cq{SHA256:} followed by 43 case-sensitive characters. - -\b An \I{MD5 fingerprint}MD5-based host key fingerprint, i.e. sixteen -2-digit hex numbers separated by colons, optionally preceded by the -prefix \cq{MD5:}. (The case of the characters does not matter.) - -\b A base64-encoded blob describing an SSH-2 public key in -OpenSSH's one-line public key format. How you acquire a public key in -this format is server-dependent; on an OpenSSH server it can typically -be found in a location like \c{/etc/ssh/ssh_host_rsa_key.pub}. - -If this box contains at least one host key or fingerprint when PuTTY -makes an SSH connection, then PuTTY's automated host key management is -completely bypassed: the connection will be permitted if and only if -the host key presented by the server is one of the keys listed in this -box, and the \I{host key cache}host key store in the Registry will be -neither read \e{nor written}, unless you explicitly do so. - -If the box is empty (as it usually is), then PuTTY's automated host -key management will work as normal. - -\S{config-ssh-kex-cert} Configuring PuTTY to accept host \i{certificates} - -In some environments, the SSH host keys for a lot of servers will all -be signed in turn by a central \q{certification authority} (\q{CA} for -short). This simplifies host key configuration for users, because if -they configure their SSH client to accept host keys certified by that -CA, then they don't need to individually confirm each host key the -first time they connect to that server. - -In order to do this, press the \q{Configure host CAs} button in the -\q{Host keys} configuration panel. This will launch a secondary -configuration dialog box where you can configure what CAs PuTTY will -accept signatures from. - -\s{Note that this configuration is common to all saved sessions}. -Everything in the main PuTTY configuration is specific to one saved -session, and you can prepare a separate session with all the -configuration different. But there's only one copy of the host CA -configuration, and it applies to all sessions PuTTY runs, whether -saved or not. - -(Otherwise, it would be useless \dash configuring a CA by hand for -each new host wouldn't be any more convenient than pressing the -\q{confirm} button for each new host's host key.) - -To set up a new CA using this config box: - -First, load the CA's public key from a file, or paste it directly into -the \q{Public key of certification authority} edit box. If your -organisation signs its host keys in this way, they will publish the -public key of their CA so that SSH users can include it in their -configuration. - -Next, in the \q{Valid hosts this key is trusted to certify} box, -configure at least one hostname wildcard to say what servers PuTTY -should trust this CA to speak for. For example, suppose you work for -Example Corporation (\cw{example.com}), and the Example Corporation IT -department has advertised a CA that signs all the Example internal -machines' host keys. Then probably you want to trust that CA to sign -host keys for machines in the domain \cw{example.com}, but not for -anything else. So you might enter \cq{*.example.com} into the \q{Valid -hosts} box. - -\s{It's important to limit what the CA key is allowed to sign}. Don't -just enter \cq{*} in that box! If you do that, you're saying that -Example Corporation IT department is authorised to sign a host key for -\e{anything at all} you might decide to connect to \dash even if -you're connecting out of the company network to a machine somewhere -else, such as your own personal server. So that configuration would -enable the Example IT department to act as a \q{man-in-the-middle} -between your PuTTY process and your server, and listen in to your -communications \dash exactly the thing SSH is supposed to avoid. - -So, if the CA was provided to you by the sysadmins responsible for -\cw{example.com} (or whatever), make sure PuTTY will \e{only} trust it -for machines in the \cw{example.com} domain. - -For the full syntax of the \q{Valid hosts} expression, see -\k{config-ssh-cert-valid-expr}. - -Finally, choose an identifying name for this CA; enter that name in -the \q{Name for this CA} edit box at the top of the window, and press -\q{Save} to record the CA in your configuration. The name you chose -will appear in the list of saved CAs to the left of the \q{Save} -button. - -The identifying name can be anything you like. It's there so that if -you store multiple certificates you can tell which is which later when -you want to edit or delete them. It also appears in the PuTTY Event -Log when a server presents a certificate signed by that CA. - -To reload an existing CA configuration, select it in the list box and -press \q{Load}. Then you can make changes, and save it again. - -To remove a CA from your configuration completely, select it in the -list and press \q{Delete}. - -\S2{config-ssh-cert-valid-expr} Expressions you can enter in \q{Valid -hosts} - -The simplest thing you can enter in the \q{Valid hosts this key is -trusted to certify} edit box is just a hostname wildcard such as -\cq{*.example.com}. This matches any host in any subdomain, so -both \cq{ssh.example.com} and \cq{login.dept.example.com} would -match, but \cq{prod.example.net} would not. - -But you can also enter multiple host name wildcards, and port number -ranges, and make complicated Boolean expressions out of them using the -operators \cq{&&} for \q{and}, \cq{||} for \q{or}, \cq{!} for \q{not}, -and parentheses. - -For example, here are some other things you could enter. - -\b \cq{*.foo.example.com || *.bar.example.com}. This means the CA is -trusted to sign the host key for a connection if the host name matches -\q{*.foo.example.com} \e{or} it matches \q{*.bar.example.com}. In -other words, the CA has authority over those two particular subdomains -of \cw{example.com}, but not for anything else, like -\cw{www.example.com}. - -\b \cq{*.example.com && ! *.extrasecure.example.com}. This means the -CA is trusted to sign the host key for a connection if the host name -matches \q{*.example.com} \e{but does not} match -\q{*.extrasecure.example.com}. (Imagine if there was one top-secret -set of servers in your company that the main IT department didn't have -security clearance to administer.) - -\b \cq{*.example.com && port:22}. This means the CA is trusted to sign -the host key for a connection if the host name matches -\q{*.example.com} \e{and} the port number is 22. SSH servers running -on other ports would not be covered. - -\b \cq{(*.foo.example.com || *.bar.example.com) && port:0-1023}. This -matches two subdomains of \cw{example.com}, as before, but \e{also} -restricts the port number to the range 0-1023. - -A certificate configuration expression consists of one or more -individual requirements which can each be a hostname wildcard, a -single port number, or a port number range, combined together with -these Boolean operators. - -Unlike other languages such as C, there is no implied priority between -\cq{&&} and \cq{||}. If you write \cq{A && B || C} (where \cw{A}, -\cw{B} and \cw{C} are some particular requirements), then PuTTY will -report a syntax error, because you haven't said which of the \cq{&&} -and \cq{||} takes priority tightly. You will have to write either -\cq{(A && B) || C}, meaning \q{both of \cw{A} and \cw{B}, or -alternatively just \cw{C}}, or \cq{A && (B || C)} (\q{\cw{A}, and also -at least one of \cw{B} and \cw{C}}), to make it clear. - -\S2{config-ssh-cert-rsa-hash} RSA signature types in certificates - -RSA keys can be used to generate signatures with a choice of secure -hash function. Typically, any version of OpenSSH new enough to support -certificates at all will also be new enough to avoid using SHA-1, so -the default settings of accepting the more modern SHA-256 and SHA-512 -should be suitable for nearly all cases. For completeness, however, -you can configure which types of RSA signature PuTTY will accept in a -certificate from a CA using an RSA key. - -\H{config-ssh-encryption} The Cipher panel - -PuTTY supports a variety of different \i{encryption algorithm}s, and -allows you to choose which one you prefer to use. You can do this by -dragging the algorithms up and down in the list box (or moving them -using the Up and Down buttons) to specify a preference order. When -you make an SSH connection, PuTTY will search down the list from the -top until it finds an algorithm supported by the server, and then -use that. - -PuTTY currently supports the following algorithms: - -\b \i{ChaCha20-Poly1305}, a combined cipher and \i{MAC} (SSH-2 only) - -\b \i{AES} (Rijndael) - 256, 192, or 128-bit SDCTR or CBC, or -256 or 128-bit GCM (SSH-2 only) - -\b \i{Arcfour} (RC4) - 256 or 128-bit stream cipher (SSH-2 only) - -\b \i{Blowfish} - 256-bit SDCTR (SSH-2 only) or 128-bit CBC - -\b \ii{Triple-DES} - 168-bit SDCTR (SSH-2 only) or CBC - -\b \ii{Single-DES} - 56-bit CBC (see below for SSH-2) - -If the algorithm PuTTY finds is below the \q{warn below here} line, -you will see a warning box when you make the connection: - -\c The first cipher supported by the server -\c is single-DES, which is below the configured -\c warning threshold. -\c Do you want to continue with this connection? - -This warns you that the first available encryption is not a very -secure one. Typically you would put the \q{warn below here} line -between the encryptions you consider secure and the ones you -consider substandard. By default, PuTTY supplies a preference order -intended to reflect a reasonable preference in terms of security and -speed. - -In SSH-2, the encryption algorithm is negotiated independently for -each direction of the connection, although PuTTY does not support -separate configuration of the preference orders. As a result you may -get two warnings similar to the one above, possibly with different -encryptions. - -Single-DES is not recommended in the SSH-2 protocol -standards, but one or two server implementations do support it. -PuTTY can use single-DES to interoperate with -these servers if you enable the \q{Enable legacy use of single-DES in -SSH-2} option; by default this is disabled and PuTTY will stick to -recommended ciphers. - -\H{config-ssh-auth} The Auth panel - -The Auth panel allows you to configure \i{authentication} options for -SSH sessions. - -\S{config-ssh-banner} \q{Display pre-authentication banner} - -SSH-2 servers can provide a message for clients to display to the -prospective user before the user logs in; this is sometimes known as a -pre-authentication \q{\i{banner}}. Typically this is used to provide -information about the server and legal notices. - -By default, PuTTY displays this message before prompting for a -password or similar credentials (although, unfortunately, not before -prompting for a login name, due to the nature of the protocol design). -By unchecking this option, display of the banner can be suppressed -entirely. - -\S{config-ssh-noauth} \q{Bypass authentication entirely} - -In SSH-2, it is in principle possible to establish a connection -without using SSH's mechanisms to identify or prove who you are -to the server. An SSH server could prefer to handle authentication -in the data channel, for instance, or simply require no user -authentication whatsoever. - -By default, PuTTY assumes the server requires authentication (we've -never heard of one that doesn't), and thus must start this process -with a username. If you find you are getting username prompts that -you cannot answer, you could try enabling this option. However, -most SSH servers will reject this. - -This is not the option you want if you have a username and just want -PuTTY to remember it; for that see \k{config-username}. -It's also probably not what if you're trying to set up passwordless -login to a mainstream SSH server; depending on the server, you -probably wanted public-key authentication (\k{pubkey}) -or perhaps GSSAPI authentication (\k{config-ssh-auth-gssapi}). -(These are still forms of authentication, even if you don't have to -interact with them.) - -This option only affects SSH-2 connections. SSH-1 connections always -require an authentication step. - -\S{config-ssh-notrivialauth} \q{Disconnect if authentication succeeds -trivially} - -This option causes PuTTY to abandon an SSH session and disconnect from -the server, if the server accepted authentication without ever having -asked for any kind of password or signature or token. - -This might be used as a security measure. There are some forms of -attack against an SSH client user which work by terminating the SSH -authentication stage early, and then doing something in the main part -of the SSH session which \e{looks} like part of the authentication, -but isn't really. - -For example, instead of demanding a signature from your public key, -for which PuTTY would ask for your key's passphrase, a compromised or -malicious server might allow you to log in with no signature or -password at all, and then print a message that \e{imitates} PuTTY's -request for your passphrase, in the hope that you would type it in. -(In fact, the passphrase for your public key should not be sent to any -server.) - -PuTTY's main defence against attacks of this type is the \q{trust -sigil} system: messages in the PuTTY window that are truly originated -by PuTTY itself are shown next to a small copy of the PuTTY icon, -which the server cannot fake when it tries to imitate the same message -using terminal output. - -However, if you think you might be at risk of this kind of thing -anyway (if you don't watch closely for the trust sigils, or if you -think you're at extra risk of one of your servers being malicious), -then you could enable this option as an extra defence. Then, if the -server tries any of these attacks involving letting you through the -authentication stage, PuTTY will disconnect from the server before it -can send a follow-up fake prompt or other type of attack. - -On the other hand, some servers \e{legitimately} let you through the -SSH authentication phase trivially, either because they are genuinely -public, or because the important authentication step happens during -the terminal session. (An example might be an SSH server that connects -you directly to the terminal login prompt of a legacy mainframe.) So -enabling this option might cause some kinds of session to stop -working. It's up to you. - -\S{config-ssh-tryagent} \q{Attempt authentication using Pageant} - -If this option is enabled, then PuTTY will look for Pageant (the SSH -private-key storage agent) and attempt to authenticate with any -suitable public keys Pageant currently holds. - -This behaviour is almost always desirable, and is therefore enabled -by default. In rare cases you might need to turn it off in order to -force authentication by some non-public-key method such as -passwords. - -This option can also be controlled using the \c{-noagent} -command-line option. See \k{using-cmdline-agentauth}. - -See \k{pageant} for more information about Pageant in general. - -\S{config-ssh-tis} \q{Attempt \I{TIS authentication}TIS or -\i{CryptoCard authentication}} - -TIS and CryptoCard authentication are (despite their names) generic -forms of simple \I{challenge/response authentication}challenge/response -authentication available in SSH protocol version 1 only. You might use -them if you were using \i{S/Key} \i{one-time passwords}, for example, -or if you had a physical \i{security token} that generated responses -to authentication challenges. They can even be used to prompt for -simple passwords. - -With this switch enabled, PuTTY will attempt these forms of -authentication if the server is willing to try them. You will be -presented with a challenge string (which may be different every -time) and must supply the correct response in order to log in. If -your server supports this, you should talk to your system -administrator about precisely what form these challenges and -responses take. - -\S{config-ssh-ki} \q{Attempt \i{keyboard-interactive authentication}} - -The SSH-2 equivalent of TIS authentication is called -\q{keyboard-interactive}. It is a flexible authentication method -using an arbitrary sequence of requests and responses; so it is not -only useful for \I{challenge/response authentication}challenge/response -mechanisms such as \i{S/Key}, but it can also be used for (for example) -asking the user for a \I{password expiry}new password when the old one -has expired. - -PuTTY leaves this option enabled by default, but supplies a switch -to turn it off in case you should have trouble with it. - -\S{config-ssh-agentfwd} \q{Allow \i{agent forwarding}} - -This option allows the SSH server to open forwarded connections back -to your local copy of \i{Pageant}. If you are not running Pageant, this -option will do nothing. - -See \k{pageant} for general information on Pageant, and -\k{pageant-forward} for information on agent forwarding. Note that -there is a security risk involved with enabling this option; see -\k{pageant-security} for details. - -\S{config-ssh-changeuser} \q{Allow attempted \i{changes of username} in SSH-2} - -In the SSH-1 protocol, it is impossible to change username after -failing to authenticate. So if you mis-type your username at the -PuTTY \q{login as:} prompt, you will not be able to change it except -by restarting PuTTY. - -The SSH-2 protocol \e{does} allow changes of username, in principle, -but does not make it mandatory for SSH-2 servers to accept them. In -particular, \i{OpenSSH} does not accept a change of username; once you -have sent one username, it will reject attempts to try to -authenticate as another user. (Depending on the version of OpenSSH, -it may quietly return failure for all login attempts, or it may send -an error message.) - -For this reason, PuTTY will by default not prompt you for your -username more than once, in case the server complains. If you know -your server can cope with it, you can enable the \q{Allow attempted -changes of username} option to modify PuTTY's behaviour. - -\H{config-ssh-auth-creds} The Credentials panel - -This subpane of the Auth panel contains configuration options that -specify actual \e{credentials} to present to the server: key files and -certificates. - -\S{config-ssh-privkey} \q{\ii{Private key} file for authentication} - -This box is where you enter the name of your private key file if you -are using \i{public key authentication}. See \k{pubkey} for information -about public key authentication in SSH. - -This key must be in PuTTY's native format (\c{*.\i{PPK}}). If you have a -private key in another format that you want to use with PuTTY, see -\k{puttygen-conversions}. - -You can use the authentication agent \i{Pageant} so that you do not -need to explicitly configure a key here; see \k{pageant}. - -If a private key file is specified here with Pageant running, PuTTY -will first try asking Pageant to authenticate with that key, and -ignore any other keys Pageant may have. If that fails, PuTTY will ask -for a passphrase as normal. You can also specify a \e{public} key file -in this case (in RFC 4716 or OpenSSH format), as that's sufficient to -identify the key to Pageant, but of course if Pageant isn't present -PuTTY can't fall back to using this file itself. - -\S{config-ssh-cert} \q{\ii{Certificate} to use with the private key} - -(This is optional. If you don't know you need it, you can leave this -blank.) - -In some environments, user authentication keys can be signed in turn -by a \q{certifying authority} (\q{CA} for short), and user accounts on -an SSH server can be configured to automatically trust any key that's -certified by the right signature. - -This can be a convenient setup if you have a very large number of -servers. When you change your key pair, you might otherwise have to -edit the \cw{authorized_keys} file on every server individually, to -make them all accept the new key. But if instead you configure all -those servers \e{once} to accept keys signed as yours by a CA, then -when you change your public key, all you have to do is to get the new -key certified by the same CA as before, and then all your servers will -automatically accept it without needing individual reconfiguration. - -One way to use a certificate is to incorporate it into your private -key file. \K{puttygen-cert} explains how to do that using PuTTYgen. -But another approach is to tell PuTTY itself where to find the public -certificate file, and then it will automatically present that -certificate when authenticating with the corresponding private key. - -To do this, enter the pathname of the certificate file into the -\q{Certificate to use with the private key} file selector. - -When this setting is configured, PuTTY will honour it no matter -whether the private key is found in a file, or loaded into Pageant. - -\S{config-ssh-authplugin} \q{\ii{Plugin} to provide authentication responses} - -An SSH server can use the \q{keyboard-interactive} protocol to present -a series of arbitrary questions and answers. Sometimes this is used -for ordinary passwords, but sometimes the server will use the same -mechanism for something more complicated, such as a one-time password -system. - -Some of these systems can be automated. For this purpose, PuTTY allows -you to provide a separate program to act as a \q{plugin} which will -take over the authentication and send answers to the questions on your -behalf. - -If you have been provided with a plugin of this type, you can -configure it here, by entering a full command line in the \q{Plugin -command to run} box. - -(If you want to \e{write} a plugin of this type, see \k{authplugin} -for the full specification of how the plugin is expected to behave.) - -\H{config-ssh-auth-gssapi} The \i{GSSAPI} panel - -The \q{GSSAPI} subpanel of the \q{Auth} panel controls the use of -GSSAPI authentication. This is a mechanism which delegates the -authentication exchange to a library elsewhere on the client -machine, which in principle can authenticate in many different ways -but in practice is usually used with the \i{Kerberos} \i{single sign-on} -protocol to implement \i{passwordless login}. - -GSSAPI authentication is only available in the SSH-2 protocol. - -PuTTY supports two forms of GSSAPI-based authentication. In one of -them, the SSH key exchange happens in the normal way, and GSSAPI is -only involved in authenticating the user. The checkbox labelled -\q{Attempt GSSAPI authentication} controls this form. - -In the other method, GSSAPI-based authentication is combined with the -SSH key exchange phase. If this succeeds, then the SSH authentication -step has nothing left to do. See \k{config-ssh-gssapi-kex} for more -information about this method. The checkbox labelled \q{Attempt GSSAPI -key exchange} controls this form. (The same checkbox appears on the -\q{Kex} panel.) - -If one or both of these controls is enabled, then GSSAPI -authentication will be attempted in one form or the other, and -(typically) if your client machine has valid Kerberos credentials -loaded, then PuTTY should be able to authenticate automatically to -servers that support Kerberos logins. - -If both of those checkboxes are disabled, PuTTY will not try any form -of GSSAPI at all, and the rest of this panel will be unused. - -\S{config-ssh-auth-gssapi-delegation} \q{Allow GSSAPI credential -delegation} - -\i{GSSAPI credential delegation} is a mechanism for passing on your -Kerberos (or other) identity to the session on the SSH server. If -you enable this option, then not only will PuTTY be able to log in -automatically to a server that accepts your Kerberos credentials, -but also you will be able to connect out from that server to other -Kerberos-supporting services and use the same credentials just as -automatically. - -(This option is the Kerberos analogue of SSH agent forwarding; see -\k{pageant-forward} for some information on that.) - -Note that, like SSH agent forwarding, there is a security -implication in the use of this option: the administrator of the -server you connect to, or anyone else who has cracked the -administrator account on that server, could fake your identity when -connecting to further Kerberos-supporting services. However, -Kerberos sites are typically run by a central authority, so the -administrator of one server is likely to already have access to the -other services too; so this would typically be less of a risk than -SSH agent forwarding. - -If your connection is not using GSSAPI key exchange, it is possible -for the delegation to expire during your session. See -\k{config-ssh-gssapi-kex} for more information. - -\S{config-ssh-auth-gssapi-libraries} Preference order for GSSAPI -libraries - -GSSAPI is a mechanism which allows more than one authentication -method to be accessed through the same interface. Therefore, more -than one authentication library may exist on your system which can -be accessed using GSSAPI. - -PuTTY contains native support for a few well-known such libraries -(including Windows' \i{SSPI}), and will look for all of them on your system -and use whichever it finds. If more than one exists on your system and -you need to use a specific one, you can adjust the order in which it -will search using this preference list control. - -One of the options in the preference list is to use a user-specified -GSSAPI library. If the library you want to use is not mentioned by -name in PuTTY's list of options, you can enter its full pathname in -the \q{User-supplied GSSAPI library path} field, and move the -\q{User-supplied GSSAPI library} option in the preference list to -make sure it is selected before anything else. - -On Windows, such libraries are files with a \I{DLL}\cw{.dll} -extension, and must have been built in the same way as the PuTTY -executable you're running; if you have a 32-bit DLL, you must run a -32-bit version of PuTTY, and the same with 64-bit (see -\k{faq-32bit-64bit}). On Unix, shared libraries generally have a -\cw{.so} extension. - -\H{config-ssh-tty} The TTY panel - -The TTY panel lets you configure the remote pseudo-terminal. - -\S{config-ssh-pty} \I{pseudo-terminal allocation}\q{Don't allocate -a pseudo-terminal} - -When connecting to a \i{Unix} system, most \I{interactive -connections}interactive shell sessions are run in a \e{pseudo-terminal}, -which allows the Unix system to pretend it's talking to a real physical -terminal device but allows the SSH server to catch all the data coming -from that fake device and send it back to the client. - -Occasionally you might find you have a need to run a session \e{not} -in a pseudo-terminal. In PuTTY, this is generally only useful for -very specialist purposes; although in Plink (see \k{plink}) it is -the usual way of working. - -\S{config-ttymodes} Sending \i{terminal modes} - -The SSH protocol allows the client to send \q{terminal modes} for -the remote pseudo-terminal. These usually control the server's -expectation of the local terminal's behaviour. - -If your server does not have sensible defaults for these modes, you -may find that changing them here helps, although the server is at -liberty to ignore your changes. If you don't understand any of this, -it's safe to leave these settings alone. - -(None of these settings will have any effect if no pseudo-terminal -is requested or allocated.) - -You can change what happens for a particular mode by selecting it in -the list, choosing one of the options and specifying the exact value -if necessary, and hitting \q{Set}. The effect of the options is as -follows: - -\b If the \q{Auto} option is selected, the PuTTY tools will decide -whether to specify that mode to the server, and if so, will send -a sensible value. - -\lcont{ - -PuTTY proper will send modes that it has an opinion on (currently only -the code for the Backspace key, \cw{ERASE}, and whether the character -set is UTF-8, \cw{IUTF8}). Plink on Unix will propagate appropriate -modes from the local terminal, if any. - -} - -\b If \q{Nothing} is selected, no value for the mode will be -specified to the server under any circumstances. - -\b If a value is specified, it will be sent to the server under all -circumstances. The precise syntax of the value box depends on the -mode. - -By default, all of the available modes are listed as \q{Auto}, -which should do the right thing in most circumstances. - -The precise effect of each setting, if any, is up to the server. Their -names come from \i{POSIX} and other Unix systems, and they are most -likely to have a useful effect on such systems. (These are the same -settings that can usually be changed using the \i\c{stty} command once -logged in to such servers.) - -Some notable modes are described below; for fuller explanations, see -your server documentation. - -\b \I{ERASE special character}\cw{ERASE} is the character that when typed -by the user will delete one space to the left. When set to \q{Auto} -(the default setting), this follows the setting of the local Backspace -key in PuTTY (see \k{config-backspace}). - -\lcont{ -This and other \i{special character}s are specified using \c{^C} notation -for Ctrl-C, and so on. Use \c{^<27>} or \c{^<0x1B>} to specify a -character numerically, and \c{^~} to get a literal \c{^}. Other -non-control characters are denoted by themselves. Leaving the box -entirely blank indicates that \e{no} character should be assigned to -the specified function, although this may not be supported by all -servers. -} - -\b \I{QUIT special character}\cw{QUIT} is a special character that -usually forcefully ends the current process on the server -(\cw{SIGQUIT}). On many servers its default setting is Ctrl-backslash -(\c{^\\}), which is easy to accidentally invoke on many keyboards. If -this is getting in your way, you may want to change it to another -character or turn it off entirely. - -\b Boolean modes such as \cw{ECHO} and \cw{ICANON} can be specified in -PuTTY in a variety of ways, such as \cw{true}/\cw{false}, -\cw{yes}/\cw{no}, and \cw{0}/\cw{1}. (Explicitly specifying a value of -\cw{no} is different from not sending the mode at all.) - -\b The boolean mode \I{IUTF8 terminal mode}\cw{IUTF8} signals to the -server whether the terminal character set is \i{UTF-8} or not, for -purposes such as basic line editing; if this is set incorrectly, -the backspace key may erase the wrong amount of text, for instance. -However, simply setting this is not usually sufficient for the server -to use UTF-8; POSIX servers will generally also require the locale to -be set (by some server-dependent means), although many newer -installations default to UTF-8. Also, since this mode was added to the -SSH protocol much later than the others, \#{circa 2016} many servers -(particularly older servers) do not honour this mode sent over SSH; -indeed, a few poorly-written servers object to its mere presence, so -you may find you need to set it to not be sent at all. When set to -\q{Auto}, this follows the local configured character set (see -\k{config-charset}). - -\b Terminal speeds are configured elsewhere; see \k{config-termspeed}. - -\H{config-ssh-x11} The X11 panel - -The X11 panel allows you to configure \i{forwarding of X11} over an -SSH connection. - -If your server lets you run X Window System \i{graphical applications}, -X11 forwarding allows you to securely give those applications access to -a local X display on your PC. - -To enable X11 forwarding, check the \q{Enable X11 forwarding} box. -If your X display is somewhere unusual, you will need to enter its -location in the \q{X display location} box; if this is left blank, -PuTTY will try to find a sensible default in the environment, or use the -primary local display (\c{:0}) if that fails. - -See \k{using-x-forwarding} for more information about X11 -forwarding. - -\S{config-ssh-x11auth} Remote \i{X11 authentication} - -If you are using X11 forwarding, the virtual X server created on the -SSH server machine will be protected by authorisation data. This -data is invented, and checked, by PuTTY. - -The usual authorisation method used for this is called -\i\cw{MIT-MAGIC-COOKIE-1}. This is a simple password-style protocol: -the X client sends some cookie data to the server, and the server -checks that it matches the real cookie. The cookie data is sent over -an unencrypted X11 connection; so if you allow a client on a third -machine to access the virtual X server, then the cookie will be sent -in the clear. - -PuTTY offers the alternative protocol \i\cw{XDM-AUTHORIZATION-1}. This -is a cryptographically authenticated protocol: the data sent by the -X client is different every time, and it depends on the IP address -and port of the client's end of the connection and is also stamped -with the current time. So an eavesdropper who captures an -\cw{XDM-AUTHORIZATION-1} string cannot immediately re-use it for -their own X connection. - -PuTTY's support for \cw{XDM-AUTHORIZATION-1} is a somewhat -experimental feature, and may encounter several problems: - -\b Some X clients probably do not even support -\cw{XDM-AUTHORIZATION-1}, so they will not know what to do with the -data PuTTY has provided. - -\b This authentication mechanism will only work in SSH-2. In SSH-1, -the SSH server does not tell the client the source address of -a forwarded connection in a machine-readable format, so it's -impossible to verify the \cw{XDM-AUTHORIZATION-1} data. - -\b You may find this feature causes problems with some SSH servers, -which will not clean up \cw{XDM-AUTHORIZATION-1} data after a -session, so that if you then connect to the same server using -a client which only does \cw{MIT-MAGIC-COOKIE-1} and are allocated -the same remote display number, you might find that out-of-date -authentication data is still present on your server and your X -connections fail. - -PuTTY's default is \cw{MIT-MAGIC-COOKIE-1}. If you change it, you -should be sure you know what you're doing. - -\S{config-ssh-xauthority} X authority file for local display - -If you are using X11 forwarding, the local X server to which your -forwarded connections are eventually directed may itself require -authorisation. - -Some Windows X servers do not require this: they do authorisation by -simpler means, such as accepting any connection from the local -machine but not from anywhere else. However, if your X server does -require authorisation, then PuTTY needs to know what authorisation -is required. - -One way in which this data might be made available is for the X -server to store it somewhere in a file which has the same format -as the Unix \c{.Xauthority} file. If this is how your Windows X -server works, then you can tell PuTTY where to find this file by -configuring this option. By default, PuTTY will not attempt to find -any authorisation for your local display. - -\H{config-ssh-portfwd} \I{port forwarding}The Tunnels panel - -The Tunnels panel allows you to configure tunnelling of arbitrary -connection types through an SSH connection. - -Port forwarding allows you to tunnel other types of \i{network -connection} down an SSH session. See \k{using-port-forwarding} for a -general discussion of port forwarding and how it works. - -The port forwarding section in the Tunnels panel shows a list of all -the port forwardings that PuTTY will try to set up when it connects -to the server. By default no port forwardings are set up, so this -list is empty. - -To add a port forwarding: - -\b Set one of the \q{Local} or \q{Remote} radio buttons, depending -on whether you want to \I{local port forwarding}forward a local port -to a remote destination (\q{Local}) or \I{remote port forwarding}forward -a remote port to a local destination (\q{Remote}). Alternatively, -select \q{Dynamic} if you want PuTTY to \I{dynamic port forwarding}provide -a local SOCKS 4/4A/5 proxy on a local port (note that this proxy only -supports TCP connections; the SSH protocol does not support forwarding -\i{UDP}). - -\b Enter a source \i{port number} into the \q{Source port} box. For -local forwardings, PuTTY will listen on this port of your PC. For -remote forwardings, your SSH server will listen on this port of the -remote machine. Note that most servers will not allow you to listen -on \I{privileged port}port numbers less than 1024. - -\b If you have selected \q{Local} or \q{Remote} (this step is not -needed with \q{Dynamic}), enter a hostname and port number separated -by a colon, in the \q{Destination} box. Connections received on the -source port will be directed to this destination. For example, to -connect to a POP-3 server, you might enter -\c{popserver.example.com:110}. (If you need to enter a literal -\i{IPv6 address}, enclose it in square brackets, for instance -\cq{[::1]:2200}.) - -\b Click the \q{Add} button. Your forwarding details should appear -in the list box. - -To remove a port forwarding, simply select its details in the list -box, and click the \q{Remove} button. - -In the \q{Source port} box, you can also optionally enter an \I{listen -address}IP address to listen on, by specifying (for instance) -\c{127.0.0.5:79}. -See \k{using-port-forwarding} for more information on how this -works and its restrictions. - -In place of port numbers, you can enter \i{service names}, if they are -known to the local system. For instance, in the \q{Destination} box, -you could enter \c{popserver.example.com:pop3}. - -You can \I{port forwarding, changing mid-session}modify the currently -active set of port forwardings in mid-session using \q{Change -Settings} (see \k{using-changesettings}). If you delete a local or -dynamic port forwarding in mid-session, PuTTY will stop listening for -connections on that port, so it can be re-used by another program. If -you delete a remote port forwarding, note that: - -\b The SSH-1 protocol contains no mechanism for asking the server to -stop listening on a remote port. - -\b The SSH-2 protocol does contain such a mechanism, but not all SSH -servers support it. (In particular, \i{OpenSSH} does not support it in -any version earlier than 3.9.) - -If you ask to delete a remote port forwarding and PuTTY cannot make -the server actually stop listening on the port, it will instead just -start refusing incoming connections on that port. Therefore, -although the port cannot be reused by another program, you can at -least be reasonably sure that server-side programs can no longer -access the service at your end of the port forwarding. - -If you delete a forwarding, any existing connections established using -that forwarding remain open. Similarly, changes to global settings -such as \q{Local ports accept connections from other hosts} only take -effect on new forwardings. - -If the connection you are forwarding over SSH is itself a second SSH -connection made by another copy of PuTTY, you might find the -\q{logical host name} configuration option useful to warn PuTTY of -which host key it should be expecting. See \k{config-loghost} for -details of this. - -\S{config-ssh-portfwd-localhost} Controlling the visibility of -forwarded ports - -The source port for a forwarded connection usually does not accept -connections from any machine except the \I{localhost}SSH client or -server machine itself (for local and remote forwardings respectively). -There are controls in the Tunnels panel to change this: - -\b The \q{Local ports accept connections from other hosts} option -allows you to set up local-to-remote port forwardings in such a way -that machines other than your client PC can connect to the forwarded -port. (This also applies to dynamic SOCKS forwarding.) - -\b The \q{Remote ports do the same} option does the same thing for -remote-to-local port forwardings (so that machines other than the -SSH server machine can connect to the forwarded port.) Note that -this feature is only available in the SSH-2 protocol, and not all -SSH-2 servers support it (\i{OpenSSH} 3.0 does not, for example). - -\S{config-ssh-portfwd-address-family} Selecting \i{Internet protocol -version} for forwarded ports - -This switch allows you to select a specific Internet protocol (\i{IPv4} -or \i{IPv6}) for the local end of a forwarded port. By default, it is -set on \q{Auto}, which means that: - -\b for a local-to-remote port forwarding, PuTTY will listen for -incoming connections in both IPv4 and (if available) IPv6 - -\b for a remote-to-local port forwarding, PuTTY will choose a -sensible protocol for the outgoing connection. - -This overrides the general Internet protocol version preference -on the Connection panel (see \k{config-address-family}). - -Note that some operating systems may listen for incoming connections -in IPv4 even if you specifically asked for IPv6, because their IPv4 -and IPv6 protocol stacks are linked together. Apparently \i{Linux} does -this, and Windows does not. So if you're running PuTTY on Windows -and you tick \q{IPv6} for a local or dynamic port forwarding, it -will \e{only} be usable by connecting to it using IPv6; whereas if -you do the same on Linux, you can also use it with IPv4. However, -ticking \q{Auto} should always give you a port which you can connect -to using either protocol. - -\H{config-ssh-bugs} \I{SSH server bugs}The Bugs and More Bugs panels - -Not all SSH servers work properly. Various existing servers have -bugs in them, which can make it impossible for a client to talk to -them unless it knows about the bug and works around it. - -Since most servers announce their software version number at the -beginning of the SSH connection, PuTTY will attempt to detect which -bugs it can expect to see in the server and automatically enable -workarounds. However, sometimes it will make mistakes; if the server -has been deliberately configured to conceal its version number, or -if the server is a version which PuTTY's bug database does not know -about, then PuTTY will not know what bugs to expect. - -The Bugs and More Bugs panels (there are two because we have so many -bug compatibility modes) allow you to manually configure the bugs -PuTTY expects to see in the server. Each bug can be configured in -three states: - -\b \q{Off}: PuTTY will assume the server does not have the bug. - -\b \q{On}: PuTTY will assume the server \e{does} have the bug. - -\b \q{Auto}: PuTTY will use the server's version number announcement -to try to guess whether or not the server has the bug. (This option is -not available for bugs that \e{cannot} be detected from the server -version, e.g. because they must be acted on before the server version -is known.) - -(The PuTTY project has a defined policy about when we're prepared to -add auto-detection for a bug workaround. See \k{feedback-workarounds}.) - -\S{config-ssh-bug-ignore2} \q{Chokes on SSH-2 \i{ignore message}s} - -An ignore message (SSH_MSG_IGNORE) is a message in the SSH protocol -which can be sent from the client to the server, or from the server -to the client, at any time. Either side is required to ignore the -message whenever it receives it. PuTTY uses ignore messages in SSH-2 -to confuse the encrypted data stream and make it harder to -cryptanalyse. It also uses ignore messages for connection -\i{keepalives} (see \k{config-keepalive}). - -If it believes the server to have this bug, PuTTY will stop using -ignore messages. If this bug is enabled when talking to a correct -server, the session will succeed, but keepalives will not work and -the session might be less cryptographically secure than it could be. - -\S{config-ssh-bug-rekey} \q{Handles SSH-2 key re-exchange badly} - -Some SSH servers cannot cope with \i{repeat key exchange} at -all, and will ignore attempts by the client to start one. Since -PuTTY pauses the session while performing a repeat key exchange, the -effect of this would be to cause the session to hang after an hour -(unless you have your rekey timeout set differently; see -\k{config-ssh-kex-rekey} for more about rekeys). -Other, very old, SSH servers handle repeat key exchange even more -badly, and disconnect upon receiving a repeat key exchange request. - -If this bug is detected, PuTTY will never initiate a repeat key -exchange. If this bug is enabled when talking to a correct server, -the session should still function, but may be less secure than you -would expect. - -This is an SSH-2-specific bug. - -\S{config-ssh-bug-winadj} \q{Chokes on PuTTY's SSH-2 \cq{winadj} requests} - -PuTTY sometimes sends a special request to SSH servers in the middle -of channel data, with the name \cw{winadj@putty.projects.tartarus.org} -(see \k{sshnames-channel}). The purpose of this request is to measure -the round-trip time to the server, which PuTTY uses to tune its flow -control. The server does not actually have to \e{understand} the -message; it is expected to send back a \cw{SSH_MSG_CHANNEL_FAILURE} -message indicating that it didn't understand it. (All PuTTY needs for -its timing calculations is \e{some} kind of response.) - -It has been known for some SSH servers to get confused by this message -in one way or another \dash because it has a long name, or because -they can't cope with unrecognised request names even to the extent of -sending back the correct failure response, or because they handle it -sensibly but fill up the server's log file with pointless spam, or -whatever. PuTTY therefore supports this bug-compatibility flag: if it -believes the server has this bug, it will never send its -\cq{winadj@putty.projects.tartarus.org} request, and will make do -without its timing data. - -\S{config-ssh-bug-chanreq} \q{Replies to requests on closed channels} - -The SSH protocol as published in RFC 4254 has an ambiguity which -arises if one side of a connection tries to close a channel, while the -other side simultaneously sends a request within the channel and asks -for a reply. RFC 4254 leaves it unclear whether the closing side -should reply to the channel request after having announced its -intention to close the channel. - -Discussion on the \cw{ietf-ssh} mailing list in April 2014 formed a -clear consensus that the right answer is no. However, because of the -ambiguity in the specification, some SSH servers have implemented the -other policy; for example, -\W{https://bugzilla.mindrot.org/show_bug.cgi?id=1818}{OpenSSH used to} -until it was fixed. - -Because PuTTY sends channel requests with the \q{want reply} flag -throughout channels' lifetime (see \k{config-ssh-bug-winadj}), it's -possible that when connecting to such a server it might receive a -reply to a request after it thinks the channel has entirely closed, -and terminate with an error along the lines of \q{Received -\cw{SSH2_MSG_CHANNEL_FAILURE} for nonexistent channel 256}. - -\S{config-ssh-bug-maxpkt2} \q{Ignores SSH-2 \i{maximum packet size}} - -When an SSH-2 channel is set up, each end announces the maximum size -of data packet that it is willing to receive for that channel. Some -servers ignore PuTTY's announcement and send packets larger than PuTTY -is willing to accept, causing it to report \q{Incoming packet was -garbled on decryption}. - -If this bug is detected, PuTTY never allows the channel's -\i{flow-control window} to grow large enough to allow the server to -send an over-sized packet. If this bug is enabled when talking to a -correct server, the session will work correctly, but download -performance will be less than it could be. - -\S{config-ssh-bug-dropstart} \q{Discards data sent before its greeting} - -Just occasionally, an SSH connection can be established over some -channel that will accidentally discard outgoing data very early in the -connection. - -This is not typically seen as a bug in an actual SSH server, but it -can sometimes occur in situations involving a complicated proxy -process. An example is -\W{https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958}{Debian -bug #991958}, in which a connection going over the console of a User -Mode Linux kernel can lose outgoing data before the kernel has fully -booted. - -You can work around this problem by manually enabling this bug flag, -which will cause PuTTY to wait to send its initial SSH greeting until -after it sees the greeting from the server. - -Note that this bug flag can never be automatically detected, since -auto-detection relies on the version string in the server's greeting, -and PuTTY has to decide whether to expect this bug \e{before} it sees -the server's greeting. So this is a manual workaround only. - -\S{config-ssh-bug-filter-kexinit} \q{Chokes on PuTTY's full \cw{KEXINIT}} - -At the start of an SSH connection, the client and server exchange long -messages of type \cw{SSH_MSG_KEXINIT}, containing lists of all the -cryptographic algorithms they're prepared to use. This is used to -negotiate a set of algorithms that both ends can speak. - -Occasionally, a badly written server might have a length limit on the -list it's prepared to receive, and refuse to make a connection simply -because PuTTY is giving it too many choices. - -A workaround is to enable this flag, which will make PuTTY wait to -send \cw{KEXINIT} until after it receives the one from the server, and -then filter its own \cw{KEXINIT} to leave out any algorithm the server -doesn't also announce support for. This will generally make PuTTY's -\cw{KEXINIT} at most the size of the server's, and will otherwise make -no difference to the algorithm negotiation. - -This flag is a minor violation of the SSH protocol, because both sides -are supposed to send \cw{KEXINIT} proactively. It still works provided -\e{one} side sends its \cw{KEXINIT} without waiting, but if both -client and server waited for the other one to speak first, the -connection would deadlock. We don't know of any servers that do this, -but if there is one, then this flag will make PuTTY unable to speak to -them at all. - -\S{config-ssh-bug-rsa-sha2-cert-userauth} \q{Old RSA/SHA2 cert -algorithm naming} - -If PuTTY is trying to do SSH-2 user authentication using an RSA key, -and the server is using one of the newer SHA-2 based versions of the -SSH RSA protocol, and the user's key is also a certificate, then -earlier versions of OpenSSH (up to 7.7) disagree with later versions -about the right key algorithm string to send in the -\cw{SSH2_MSG_USERAUTH_REQUEST} packet. Modern versions send a string -that indicates both the SHA-2 nature and the certificate nature of the -key, such as \cq{rsa-sha2-512-cert-v01@openssh.com}. Earlier versions -would reject that, and insist on seeing -\cq{ssh-rsa-cert-v01@openssh.com} followed by a SHA-2 based signature. - -PuTTY should auto-detect the presence of this bug in earlier OpenSSH -and adjust to send the right string. - -\S{config-ssh-bug-sig} \q{Requires padding on SSH-2 \i{RSA} \i{signatures}} - -Versions below 3.3 of \i{OpenSSH} require SSH-2 RSA signatures to be -padded with zero bytes to the same length as the RSA key modulus. -The SSH-2 specification says that an unpadded signature MUST be -accepted, so this is a bug. A typical symptom of this problem is -that PuTTY mysteriously fails RSA authentication once in every few -hundred attempts, and falls back to passwords. - -If this bug is detected, PuTTY will pad its signatures in the way -OpenSSH expects. If this bug is enabled when talking to a correct -server, it is likely that no damage will be done, since correct -servers usually still accept padded signatures because they're used -to talking to OpenSSH. - -This is an SSH-2-specific bug. - -\S{config-ssh-bug-oldgex2} \q{Only supports pre-RFC4419 SSH-2 DH GEX} - -The SSH key exchange method that uses Diffie-Hellman group exchange -was redesigned after its original release, to use a slightly more -sophisticated setup message. Almost all SSH implementations switched -over to the new version. (PuTTY was one of the last.) A few old -servers still only support the old one. - -If this bug is detected, and the client and server negotiate -Diffie-Hellman group exchange, then PuTTY will send the old message -now known as \cw{SSH2_MSG_KEX_DH_GEX_REQUEST_OLD} in place of the new -\cw{SSH2_MSG_KEX_DH_GEX_REQUEST}. - -This is an SSH-2-specific bug. - -\S{config-ssh-bug-hmac2} \q{Miscomputes SSH-2 HMAC keys} - -Versions 2.3.0 and below of the SSH server software from -\cw{ssh.com} compute the keys for their \i{HMAC} \i{message authentication -code}s incorrectly. A typical symptom of this problem is that PuTTY -dies unexpectedly at the beginning of the session, saying -\q{Incorrect MAC received on packet}. - -If this bug is detected, PuTTY will compute its HMAC keys in the -same way as the buggy server, so that communication will still be -possible. If this bug is enabled when talking to a correct server, -communication will fail. - -This is an SSH-2-specific bug. - -\S{config-ssh-bug-pksessid2} \q{Misuses the \i{session ID} in SSH-2 PK auth} - -Versions below 2.3 of \i{OpenSSH} require SSH-2 \i{public-key authentication} -to be done slightly differently: the data to be signed by the client -contains the session ID formatted in a different way. If public-key -authentication mysteriously does not work but the Event Log (see -\k{using-eventlog}) thinks it has successfully sent a signature, it -might be worth enabling the workaround for this bug to see if it -helps. - -If this bug is detected, PuTTY will sign data in the way OpenSSH -expects. If this bug is enabled when talking to a correct server, -SSH-2 public-key authentication will fail. - -This is an SSH-2-specific bug. - -\S{config-ssh-bug-derivekey2} \q{Miscomputes SSH-2 \i{encryption} keys} - -Versions below 2.0.11 of the SSH server software from \i\cw{ssh.com} -compute the keys for the session encryption incorrectly. This -problem can cause various error messages, such as \q{Incoming packet -was garbled on decryption}, or possibly even \q{Out of memory}. - -If this bug is detected, PuTTY will compute its encryption keys in -the same way as the buggy server, so that communication will still -be possible. If this bug is enabled when talking to a correct -server, communication will fail. - -This is an SSH-2-specific bug. - -\S{config-ssh-bug-ignore1} \q{Chokes on SSH-1 \i{ignore message}s} - -An ignore message (SSH_MSG_IGNORE) is a message in the SSH protocol -which can be sent from the client to the server, or from the server -to the client, at any time. Either side is required to ignore the -message whenever it receives it. PuTTY uses ignore messages to -\I{password camouflage}hide the password packet in SSH-1, so that -a listener cannot tell the length of the user's password; it also -uses ignore messages for connection \i{keepalives} (see -\k{config-keepalive}). - -If this bug is detected, PuTTY will stop using ignore messages. This -means that keepalives will stop working, and PuTTY will have to fall -back to a secondary defence against SSH-1 password-length -eavesdropping. See \k{config-ssh-bug-plainpw1}. If this bug is -enabled when talking to a correct server, the session will succeed, -but keepalives will not work and the session might be more -vulnerable to eavesdroppers than it could be. - -\S{config-ssh-bug-plainpw1} \q{Refuses all SSH-1 \i{password camouflage}} - -When talking to an SSH-1 server which cannot deal with ignore -messages (see \k{config-ssh-bug-ignore1}), PuTTY will attempt to -disguise the length of the user's password by sending additional -padding \e{within} the password packet. This is technically a -violation of the SSH-1 specification, and so PuTTY will only do it -when it cannot use standards-compliant ignore messages as -camouflage. In this sense, for a server to refuse to accept a padded -password packet is not really a bug, but it does make life -inconvenient if the server can also not handle ignore messages. - -If this \q{bug} is detected, PuTTY will assume that neither ignore -messages nor padding are acceptable, and that it thus has no choice -but to send the user's password with no form of camouflage, so that -an eavesdropping user will be easily able to find out the exact length -of the password. If this bug is enabled when talking to a correct -server, the session will succeed, but will be more vulnerable to -eavesdroppers than it could be. - -This is an SSH-1-specific bug. SSH-2 is secure against this type of -attack. - -\S{config-ssh-bug-rsa1} \q{Chokes on SSH-1 \i{RSA} authentication} - -Some SSH-1 servers cannot deal with RSA authentication messages at -all. If \i{Pageant} is running and contains any SSH-1 keys, PuTTY will -normally automatically try RSA authentication before falling back to -passwords, so these servers will crash when they see the RSA attempt. - -If this bug is detected, PuTTY will go straight to password -authentication. If this bug is enabled when talking to a correct -server, the session will succeed, but of course RSA authentication -will be impossible. - -This is an SSH-1-specific bug. - -\H{config-psusan} The \q{Bare \cw{\i{ssh-connection}}} protocol - -In addition to SSH itself, PuTTY also supports a second protocol that -is derived from SSH. It's listed in the PuTTY GUI under the name -\q{Bare \cw{ssh-connection}}. - -This protocol consists of just the innermost of SSH-2's three layers: it -leaves out the cryptography layer providing network security, and it -leaves out the authentication layer where you provide a username and -prove you're allowed to log in as that user. - -It is therefore \s{completely unsuited to any network connection}. -Don't try to use it over a network! - -The purpose of this protocol is for various specialist circumstances -in which the \q{connection} is not over a real network, but is a pipe -or IPC channel between different processes running on the \e{same} -computer. In these contexts, the operating system will already have -guaranteed that each of the two communicating processes is owned by -the expected user (so that no authentication is necessary), and that -the communications channel cannot be tapped by a hostile user on the -same machine (so that no cryptography is necessary either). Examples -of possible uses involve communicating with a strongly separated -context such as the inside of a container, or a VM, or a different -network namespace. - -Explicit support for this protocol is new in PuTTY 0.75. As of -2021-04, the only known server for the bare \cw{ssh-connection} -protocol is the Unix program \cq{\i{psusan}} that is also part of the -PuTTY tool suite. - -(However, this protocol is also the same one used between instances of -PuTTY to implement connection sharing: see \k{config-ssh-sharing}. In -fact, in the Unix version of PuTTY, when a sharing upstream records -\q{Sharing this connection at [pathname]} in the Event Log, it's -possible to connect another instance of PuTTY directly to that Unix -socket, by entering its pathname in the host name box and selecting -\q{Bare \cw{ssh-connection}} as the protocol!) - -Many of the options under the SSH panel also affect this protocol, -although options to do with cryptography and authentication do not, -for obvious reasons. - -I repeat, \s{DON'T TRY TO USE THIS PROTOCOL FOR NETWORK CONNECTIONS!} -That's not what it's for, and it's not at all safe to do it. - -\H{config-serial} The Serial panel - -The \i{Serial} panel allows you to configure options that only apply -when PuTTY is connecting to a local \I{serial port}\i{serial line}. - -\S{config-serial-line} Selecting a serial line to connect to - -The \q{Serial line to connect to} box allows you to choose which -serial line you want PuTTY to talk to, if your computer has more -than one serial port. - -On Windows, the first serial line is called \i\cw{COM1}, and if there -is a second it is called \cw{COM2}, and so on. - -This configuration setting is also visible on the Session panel, -where it replaces the \q{Host Name} box (see \k{config-hostname}) if -the connection type is set to \q{Serial}. - -\S{config-serial-speed} Selecting the speed of your serial line - -The \q{Speed} box allows you to choose the speed (or \q{baud rate}) -at which to talk to the serial line. Typical values might be 9600, -19200, 38400 or 57600. Which one you need will depend on the device -at the other end of the serial cable; consult the manual for that -device if you are in doubt. - -This configuration setting is also visible on the Session panel, -where it replaces the \q{Port} box (see \k{config-hostname}) if the -connection type is set to \q{Serial}. - -\S{config-serial-databits} Selecting the number of data bits - -The \q{Data bits} box allows you to choose how many data bits are -transmitted in each byte sent or received through the serial line. -Typical values are 7 or 8. - -\S{config-serial-stopbits} Selecting the number of stop bits - -The \q{Stop bits} box allows you to choose how many stop bits are -used in the serial line protocol. Typical values are 1, 1.5 or 2. - -\S{config-serial-parity} Selecting the serial parity checking scheme - -The \q{Parity} box allows you to choose what type of parity checking -is used on the serial line. The settings are: - -\b \q{None}: no parity bit is sent at all. - -\b \q{Odd}: an extra parity bit is sent alongside each byte, and -arranged so that the total number of 1 bits is odd. - -\b \q{Even}: an extra parity bit is sent alongside each byte, and -arranged so that the total number of 1 bits is even. - -\b \q{Mark}: an extra parity bit is sent alongside each byte, and -always set to 1. - -\b \q{Space}: an extra parity bit is sent alongside each byte, and -always set to 0. - -\S{config-serial-flow} Selecting the serial flow control scheme - -The \q{Flow control} box allows you to choose what type of flow -control checking is used on the serial line. The settings are: - -\b \q{None}: no flow control is done. Data may be lost if either -side attempts to send faster than the serial line permits. - -\b \q{XON/XOFF}: flow control is done by sending XON and XOFF -characters within the data stream. - -\b \q{RTS/CTS}: flow control is done using the RTS and CTS wires on -the serial line. - -\b \q{DSR/DTR}: flow control is done using the DSR and DTR wires on -the serial line. - -\H{config-telnet} The \i{Telnet} panel - -The Telnet panel allows you to configure options that only apply to -Telnet sessions. - -\S{config-oldenviron} \q{Handling of OLD_ENVIRON ambiguity} - -The original Telnet mechanism for passing \i{environment variables} was -badly specified. At the time the standard (RFC 1408) was written, -BSD telnet implementations were already supporting the feature, and -the intention of the standard was to describe the behaviour the BSD -implementations were already using. - -Sadly there was a typing error in the standard when it was issued, -and two vital function codes were specified the wrong way round. BSD -implementations did not change, and the standard was not corrected. -Therefore, it's possible you might find either \i{BSD} or \i{RFC}-compliant -implementations out there. This switch allows you to choose which -one PuTTY claims to be. - -The problem was solved by issuing a second standard, defining a new -Telnet mechanism called \i\cw{NEW_ENVIRON}, which behaved exactly like -the original \i\cw{OLD_ENVIRON} but was not encumbered by existing -implementations. Most Telnet servers now support this, and it's -unambiguous. This feature should only be needed if you have trouble -passing environment variables to quite an old server. - -\S{config-ptelnet} Passive and active \i{Telnet negotiation} modes - -In a Telnet connection, there are two types of data passed between -the client and the server: actual text, and \e{negotiations} about -which Telnet extra features to use. - -PuTTY can use two different strategies for negotiation: - -\b In \I{active Telnet negotiation}\e{active} mode, PuTTY starts to send -negotiations as soon as the connection is opened. - -\b In \I{passive Telnet negotiation}\e{passive} mode, PuTTY will wait to -negotiate until it sees a negotiation from the server. - -The obvious disadvantage of passive mode is that if the server is -also operating in a passive mode, then negotiation will never begin -at all. For this reason PuTTY defaults to active mode. - -However, sometimes passive mode is required in order to successfully -get through certain types of firewall and \i{Telnet proxy} server. If -you have confusing trouble with a \i{firewall}, you could try enabling -passive mode to see if it helps. - -\S{config-telnetkey} \q{Keyboard sends \i{Telnet special commands}} - -If this box is checked, several key sequences will have their normal -actions modified: - -\b the Backspace key on the keyboard will send the \I{Erase Character, -Telnet special command}Telnet special backspace code; - -\b Control-C will send the Telnet special \I{Interrupt Process, Telnet -special command}Interrupt Process code; - -\b Control-Z will send the Telnet special \I{Suspend Process, Telnet -special command}Suspend Process code. - -You probably shouldn't enable this -unless you know what you're doing. - -\S{config-telnetnl} \q{Return key sends \i{Telnet New Line} instead of ^M} - -Unlike most other remote login protocols, the Telnet protocol has a -special \q{\i{new line}} code that is not the same as the usual line -endings of Control-M or Control-J. By default, PuTTY sends the -Telnet New Line code when you press Return, instead of sending -Control-M as it does in most other protocols. - -Most Unix-style Telnet servers don't mind whether they receive -Telnet New Line or Control-M; some servers do expect New Line, and -some servers prefer to see ^M. If you are seeing surprising -behaviour when you press Return in a Telnet session, you might try -turning this option off to see if it helps. - -\H{config-rlogin} The Rlogin panel - -The \i{Rlogin} panel allows you to configure options that only apply to -Rlogin sessions. - -\S{config-rlogin-localuser} \I{local username in Rlogin}\q{Local username} - -Rlogin allows an automated (password-free) form of login by means of -a file called \i\c{.rhosts} on the server. You put a line in your -\c{.rhosts} file saying something like \c{jbloggs@pc1.example.com}, -and then when you make an Rlogin connection the client transmits the -username of the user running the Rlogin client. The server checks -the username and hostname against \c{.rhosts}, and if they match it -\I{passwordless login}does not ask for a password. - -This only works because Unix systems contain a safeguard to stop a -user from pretending to be another user in an Rlogin connection. -Rlogin connections have to come from \I{privileged port}port numbers below -1024, and Unix systems prohibit this to unprivileged processes; so when the -server sees a connection from a low-numbered port, it assumes the -client end of the connection is held by a privileged (and therefore -trusted) process, so it believes the claim of who the user is. - -Windows does not have this restriction: \e{any} user can initiate an -outgoing connection from a low-numbered port. Hence, the Rlogin -\c{.rhosts} mechanism is completely useless for securely -distinguishing several different users on a Windows machine. If you -have a \c{.rhosts} entry pointing at a Windows PC, you should assume -that \e{anyone} using that PC can \i{spoof} your username in -an Rlogin connection and access your account on the server. - -The \q{Local username} control allows you to specify what user name -PuTTY should claim you have, in case it doesn't match your \i{Windows -user name} (or in case you didn't bother to set up a Windows user -name). - -\H{config-supdup} The \i{SUPDUP} panel - -The SUPDUP panel allows you to configure options that only apply -to SUPDUP sessions. See \k{using-supdup} for more about the SUPDUP -protocol. - -\S{supdup-location} \q{Location string} - -In SUPDUP, the client sends a piece of text of its choice to the -server giving the user's location. This is typically displayed in -lists of logged-in users. - -By default, PuTTY just defaults this to "The Internet". If you want -your location to show up as something more specific, you can configure -it here. - -\S{supdup-ascii} \q{Extended ASCII Character set} - -This declares what kind of character set extension your terminal -supports. If the server supports it, it will send text using that -character set. \q{None} means the standard 95 printable ASCII -characters. \q{ITS} means ASCII extended with printable characters in -the control character range. This character set is documented in the -SUPDUP protocol definition. \q{WAITS} is similar to \q{ITS} but uses -some alternative characters in the extended set: most prominently, it -will display arrows instead of \c{^} and \c{_}, and \c{\}} instead of -\c{~}. \q{ITS} extended ASCII is used by ITS and Lisp machines, -whilst \q{WAITS} is only used by the WAITS operating system from the -Stanford AI Laboratory. - -\S{supdup-more} \q{**MORE** processing} - -When **MORE** processing is enabled, the server causes output to pause -at the bottom of the screen, until a space is typed. - -\S{supdup-scroll} \q{Terminal scrolling} - -This controls whether the terminal will perform scrolling when the -cursor goes below the last line, or if the cursor will return to the -first line. - -\H{config-file} \ii{Storing configuration in a file} - -PuTTY does not currently support storing its configuration in a file -instead of the \i{Registry}. However, you can work around this with a -couple of \i{batch file}s. - -You will need a file called (say) \c{PUTTY.BAT} which imports the -contents of a file into the Registry, then runs PuTTY, exports the -contents of the Registry back into the file, and deletes the -Registry entries. This can all be done using the Regedit command -line options, so it's all automatic. Here is what you need in -\c{PUTTY.BAT}: - -\c @ECHO OFF -\c regedit /s putty.reg -\c regedit /s puttyrnd.reg -\c start /w putty.exe -\c regedit /ea new.reg HKEY_CURRENT_USER\Software\SimonTatham\PuTTY -\c copy new.reg putty.reg -\c del new.reg -\c regedit /s puttydel.reg - -This batch file needs two auxiliary files: \c{PUTTYRND.REG} which -sets up an initial safe location for the \c{PUTTY.RND} random seed -file, and \c{PUTTYDEL.REG} which destroys everything in the Registry -once it's been successfully saved back to the file. - -Here is \c{PUTTYDEL.REG}: - -\c REGEDIT4 -\c -\c [-HKEY_CURRENT_USER\Software\SimonTatham\PuTTY] - -Here is an example \c{PUTTYRND.REG} file: - -\c REGEDIT4 -\c -\c [HKEY_CURRENT_USER\Software\SimonTatham\PuTTY] -\c "RandSeedFile"="a:\\putty.rnd" - -You should replace \c{a:\\putty.rnd} with the location where you -want to store your random number data. If the aim is to carry around -PuTTY and its settings on one USB stick, you probably want to store it -on the USB stick. diff --git a/doc/errors.but b/doc/errors.but deleted file mode 100644 index ca1a53e03..000000000 --- a/doc/errors.but +++ /dev/null @@ -1,409 +0,0 @@ -\C{errors} Common \i{error messages} - -This chapter lists a number of common error messages which PuTTY and -its associated tools can produce, and explains what they mean in -more detail. - -We do not attempt to list \e{all} error messages here: there are -many which should never occur, and some which should be -self-explanatory. If you get an error message which is not listed in -this chapter and which you don't understand, report it to us as a -bug (see \k{feedback}) and we will add documentation for it. - -\H{errors-hostkey-absent} \q{The host key is not cached for this -server} - -This error message occurs when PuTTY connects to a new SSH server. -Every server identifies itself by means of a host key; once PuTTY -knows the host key for a server, it will be able to detect if a -malicious attacker redirects your connection to another machine. - -If you see this message, it means that PuTTY has not seen this host -key before, and has no way of knowing whether it is correct or not. -You should attempt to verify the host key by other means, such as -asking the machine's administrator. - -If you see this message and you know that your installation of PuTTY -\e{has} connected to the same server before, it may have been -recently upgraded to SSH protocol version 2. SSH protocols 1 and 2 -use separate host keys, so when you first use \i{SSH-2} with a server -you have only used SSH-1 with before, you will see this message -again. You should verify the correctness of the key as before. - -See \k{gs-hostkey} for more information on host keys. - -\H{errors-hostkey-wrong} \q{WARNING - POTENTIAL SECURITY BREACH!} - -This message, followed by \q{The server's host key does not match -the one PuTTY has cached for this server}, means that PuTTY has -connected to the SSH server before, knows what its host key -\e{should} be, but has found a different one. - -(If the message instead talks about a \q{certified host key}, see -instead \k{errors-cert-mismatch}.) - -This may mean that a malicious attacker has replaced your server -with a different one, or has redirected your network connection to -their own machine. On the other hand, it may simply mean that the -administrator of your server has accidentally changed the key while -upgrading the SSH software; this \e{shouldn't} happen but it is -unfortunately possible. - -You should contact your server's administrator and see whether they -expect the host key to have changed. If so, verify the new host key -in the same way as you would if it was new. - -See \k{gs-hostkey} for more information on host keys. - -\H{errors-cert-mismatch} \q{This server presented a certified host key -which was signed by a different certification authority ...} - -If you've configured PuTTY to trust at least one -\I{certificate}certification authority for signing host keys (see -\k{config-ssh-kex-cert}), then it will ask the SSH server to send it -any available certified host keys. If the server sends back a -certified key signed by a \e{different} certification authority, PuTTY -will present this variant of the host key prompt, preceded by -\q{WARNING - POTENTIAL SECURITY BREACH!} - -One reason why this can happen is a deliberate attack. Just like an -ordinary man-in-the-middle attack which substitutes a wrong host key, -a particularly ambitious attacker might substitute an entire wrong -certification authority, and hope that you connect anyway. - -But it's also possible in some situations that this error might arise -legitimately. For example, if your organisation's IT department has -just rolled out a new CA key which you haven't yet entered in PuTTY's -configuration, or if your CA configuration involves two overlapping -domains, or something similar. - -So, unfortunately, you'll have to work out what to do about it -yourself: make an exception for this specific case, or abandon this -connection and install a new CA key before trying again (if you're -really sure you trust the CA), or edit your configuration in some -other way, or just stop trying to use this server. - -If you're convinced that this particular server is legitimate even -though the CA is not one you trust, PuTTY will let you cache the -certified host key, treating it in the same way as an uncertified one. -Then that particular certificate will be accepted for future -connections to this specific server, even though other certificates -signed by the same CA will still be rejected. - -\H{errors-ssh-protocol} \q{SSH protocol version 2 required by our -configuration but remote only provides (old, insecure) SSH-1} - -By default, PuTTY only supports connecting to SSH servers that -implement \i{SSH protocol version 2}. If you see this message, the -server you're trying to connect to only supports the older SSH-1 -protocol. - -If the server genuinely only supports SSH-1, then you need to either -change the \q{SSH protocol version} setting (see \k{config-ssh-prot}), -or use the \c{-1} command-line option; in any case, you should not -treat the resulting connection as secure. - -You might start seeing this message with new versions of PuTTY (from -0.68 onwards) where you didn't before, because it used to be possible -to configure PuTTY to automatically fall back from SSH-2 to SSH-1. -This is no longer supported, to prevent the possibility of a downgrade -attack. - -\H{errors-cipher-warning} \q{The first cipher supported by the server is -... below the configured warning threshold} - -This occurs when the SSH server does not offer any ciphers which you -have configured PuTTY to consider strong enough. By default, PuTTY -puts up this warning only for \i{Blowfish}, \ii{single-DES}, and -\i{Arcfour} encryption. - -See \k{config-ssh-encryption} for more information on this message. - -(There are similar messages for other cryptographic primitives, such -as host key algorithms.) - -\H{errors-toomanyauth} \q{Remote side sent disconnect message type 2 -(protocol error): "Too many authentication failures for root"} - -This message is produced by an \i{OpenSSH} (or \i{Sun SSH}) server if it -receives more failed authentication attempts than it is willing to -tolerate. - -This can easily happen if you are using Pageant and have a -large number of keys loaded into it, since these servers count each -offer of a public key as an authentication attempt. This can be worked -around by specifying the key that's required for the authentication in -the PuTTY configuration (see \k{config-ssh-privkey}); PuTTY will ignore -any other keys Pageant may have, but will ask Pageant to do the -authentication, so that you don't have to type your passphrase. - -On the server, this can be worked around by disabling public-key -authentication or (for Sun SSH only) by increasing \c{MaxAuthTries} in -\c{sshd_config}. - -\H{errors-memory} \q{\ii{Out of memory}} - -This occurs when PuTTY tries to allocate more memory than the system -can give it. This \e{may} happen for genuine reasons: if the -computer really has run out of memory, or if you have configured an -extremely large number of lines of scrollback in your terminal. -PuTTY is not able to recover from running out of memory; it will -terminate immediately after giving this error. - -However, this error can also occur when memory is not running out at -all, because PuTTY receives data in the wrong format. In SSH-2 and -also in SFTP, the server sends the length of each message before the -message itself; so PuTTY will receive the length, try to allocate -space for the message, and then receive the rest of the message. If -the length PuTTY receives is garbage, it will try to allocate a -ridiculous amount of memory, and will terminate with an \q{Out of -memory} error. - -This can happen in SSH-2, if PuTTY and the server have not enabled -encryption in the same way (see \k{faq-outofmem} in the FAQ). - -This can also happen in PSCP or PSFTP, if your \i{login scripts} on the -server generate output: the client program will be expecting an SFTP -message starting with a length, and if it receives some text from -your login scripts instead it will try to interpret them as a -message length. See \k{faq-outofmem2} for details of this. - -\H{errors-internal} \q{\ii{Internal error}}, \q{\ii{Internal fault}}, -\q{\ii{Assertion failed}} - -Any error beginning with the word \q{Internal} should \e{never} -occur. If it does, there is a bug in PuTTY by definition; please see -\k{feedback} and report it to us. - -Similarly, any error message starting with \q{Assertion failed} is a -bug in PuTTY. Please report it to us, and include the exact text -from the error message box. - -\H{errors-cant-load-key} \q{Unable to use key file}, -\q{Couldn't load private key}, \q{Couldn't load this key} - -Various forms of this error are printed in the PuTTY window, or -written to the PuTTY Event Log (see \k{using-eventlog}) when trying -public-key authentication, or given by Pageant when trying to load a -private key. - -If you see one of these messages, it often indicates that you've tried -to load a key of an inappropriate type into PuTTY, Plink, PSCP, PSFTP, -or Pageant. - -You may have tried to load an SSH-2 key in a \q{foreign} -format (OpenSSH or \cw{ssh.com}) directly into one of the PuTTY tools, -in which case you need to import it into PuTTY's native format -(\c{*.PPK}) using PuTTYgen \dash see \k{puttygen-conversions}. - -Alternatively, you may have specified a key that's inappropriate for -the connection you're making. The SSH-2 and the old SSH-1 protocols -require different private key formats, and a SSH-1 key can't be used -for a SSH-2 connection (or vice versa). - -\H{errors-refused} \q{Server refused our key}, -\q{Server refused our public key}, \q{Key refused} - -Various forms of this error are printed in the PuTTY window, or -written to the PuTTY Event Log (see \k{using-eventlog}) when trying -public-key authentication. - -If you see one of these messages, it means that PuTTY has sent a -public key to the server and offered to authenticate with it, and -the server has refused to accept authentication. This usually means -that the server is not configured to accept this key to authenticate -this user. - -This is almost certainly not a problem with PuTTY. If you see this -type of message, the first thing you should do is check your -\e{server} configuration carefully. Common errors include having -the wrong permissions or ownership set on the public key or the -user's home directory on the server. Also, read the PuTTY Event Log; -the server may have sent diagnostic messages explaining exactly what -problem it had with your setup. - -\K{pubkey-gettingready} has some hints on server-side public key -setup. - -\H{errors-access-denied} \q{Access denied}, \q{Authentication refused} - -Various forms of this error are printed in the PuTTY window, or -written to the PuTTY Event Log (see \k{using-eventlog}) during -authentication. - -If you see one of these messages, it means that the server has refused -all the forms of authentication PuTTY has tried and it has no further -ideas. - -It may be worth checking the Event Log for diagnostic messages from -the server giving more detail. - -This error can be caused by buggy SSH-1 servers that fail to cope with -the various strategies we use for camouflaging passwords in transit. -Upgrade your server, or use the workarounds described in -\k{config-ssh-bug-ignore1} and possibly \k{config-ssh-bug-plainpw1}. - -\H{errors-no-auth} \q{No supported authentication methods available} - -This error indicates that PuTTY has run out of ways to authenticate -you to an SSH server. This may be because PuTTY has TIS or -keyboard-interactive authentication disabled, in which case see -\k{config-ssh-tis} and \k{config-ssh-ki}. - -\H{errors-crc} \q{Incorrect \i{MAC} received on packet} or -\q{Incorrect \i{CRC} received on packet} - -This error occurs when PuTTY decrypts an SSH packet and its checksum -is not correct. This probably means something has gone wrong in the -encryption or decryption process. It's difficult to tell from this -error message whether the problem is in the client, in the server, -or in between. - -In particular, if the network is corrupting data at the TCP level, it -may only be obvious with cryptographic protocols such as SSH, which -explicitly check the integrity of the transferred data and complain -loudly if the checks fail. Corruption of protocols without integrity -protection (such as HTTP) will manifest in more subtle failures (such -as misdisplayed text or images in a web browser) which may not be -noticed. - -Occasionally this has been caused by server bugs. An example is the -bug described at \k{config-ssh-bug-hmac2}, although you're very -unlikely to encounter that one these days. - -In this context MAC stands for \ii{Message Authentication Code}. It's a -cryptographic term, and it has nothing at all to do with Ethernet -MAC (Media Access Control) addresses, or with the Apple computer. - -\H{errors-garbled} \q{Incoming packet was garbled on decryption} - -This error occurs when PuTTY decrypts an SSH packet and the -decrypted data makes no sense. This probably means something has -gone wrong in the encryption or decryption process. It's difficult -to tell from this error message whether the problem is in the client, -in the server, or in between. - -If you get this error, one thing you could try would be to fiddle with -the setting of \q{Miscomputes SSH-2 encryption keys} (see -\k{config-ssh-bug-derivekey2}) or \q{Ignores SSH-2 maximum packet -size} (see \k{config-ssh-bug-maxpkt2}) on the Bugs panel. - -\H{errors-x11-proxy} \q{PuTTY X11 proxy: \e{various errors}} - -This family of errors are reported when PuTTY is doing X forwarding. -They are sent back to the X application running on the SSH server, -which will usually report the error to the user. - -When PuTTY enables X forwarding (see \k{using-x-forwarding}) it -creates a virtual X display running on the SSH server. This display -requires authentication to connect to it (this is how PuTTY prevents -other users on your server machine from connecting through the PuTTY -proxy to your real X display). PuTTY also sends the server the -details it needs to enable clients to connect, and the server should -put this mechanism in place automatically, so your X applications -should just work. - -A common reason why people see one of these messages is because they -used SSH to log in as one user (let's say \q{fred}), and then used -the Unix \c{su} command to become another user (typically \q{root}). -The original user, \q{fred}, has access to the X authentication data -provided by the SSH server, and can run X applications which are -forwarded over the SSH connection. However, the second user -(\q{root}) does not automatically have the authentication data -passed on to it, so attempting to run an X application as that user -often fails with this error. - -If this happens, \e{it is not a problem with PuTTY}. You need to -arrange for your X authentication data to be passed from the user -you logged in as to the user you used \c{su} to become. How you do -this depends on your particular system; in fact many modern versions -of \c{su} do it automatically. - -\H{errors-connaborted} \q{Network error: Software caused connection -abort} - -This is a generic error produced by the Windows network code when it -kills an established connection for some reason. For example, it might -happen if you pull the network cable out of the back of an -Ethernet-connected computer, or if Windows has any other similar -reason to believe the entire network has become unreachable. - -Windows also generates this error if it has given up on the machine -at the other end of the connection ever responding to it. If the -network between your client and server goes down and your client -then tries to send some data, Windows will make several attempts to -send the data and will then give up and kill the connection. In -particular, this can occur even if you didn't type anything, if you -are using SSH-2 and PuTTY attempts a key re-exchange. (See -\k{config-ssh-kex-rekey} for more about key re-exchange.) - -(It can also occur if you are using keepalives in your connection. -Other people have reported that keepalives \e{fix} this error for -them. See \k{config-keepalive} for a discussion of the pros and cons -of keepalives.) - -We are not aware of any reason why this error might occur that would -represent a bug in PuTTY. The problem is between you, your Windows -system, your network and the remote system. - -\H{errors-connreset} \q{Network error: Connection reset by peer} - -This error occurs when the machines at each end of a network -connection lose track of the state of the connection between them. -For example, you might see it if your SSH server crashes, and -manages to reboot fully before you next attempt to send data to it. - -However, the most common reason to see this message is if you are -connecting through a \i{firewall} or a \i{NAT router} which has timed the -connection out. See \k{faq-idleout} in the FAQ for more details. You -may be able to improve the situation by using keepalives; see -\k{config-keepalive} for details on this. - -Note that Windows can produce this error in some circumstances without -seeing a connection reset from the server, for instance if the -connection to the network is lost. - -\H{errors-connrefused} \q{Network error: Connection refused} - -This error means that the network connection PuTTY tried to make to -your server was rejected by the server. Usually this happens because -the server does not provide the service which PuTTY is trying to -access. - -Check that you are connecting with the correct protocol (SSH, Telnet, -etc), and check that the port number is correct. If that -fails, consult the administrator of your server. - -This error can also be caused by a firewall in between you and the -server, which rejects the connection and sends back the same type of -error packet as the real server would have sent. - -\H{errors-conntimedout} \q{Network error: Connection timed out} - -This error means that the network connection PuTTY tried to make to -your server received no response at all from the server. Usually -this happens because the server machine is completely isolated from -the network, or because it is turned off. - -Check that you have correctly entered the host name or IP address of -your server machine. If that fails, consult the administrator of -your server. - -\i{Unix} also generates this error when it tries to send data down a -connection and contact with the server has been completely lost -during a connection. (There is a delay of minutes before Unix gives -up on receiving a reply from the server.) This can occur if you type -things into PuTTY while the network is down, but it can also occur -if PuTTY decides of its own accord to send data: due to a repeat key -exchange in SSH-2 (see \k{config-ssh-kex-rekey}) or due to -keepalives (\k{config-keepalive}). - -\H{errors-cannotassignaddress} \q{Network error: Cannot assign requested -address} - -This means that the operating system rejected the parameters of the -network connection PuTTY tried to make, usually without actually -trying to connect to anything, because they were simply invalid. - -A common way to provoke this error is to accidentally try to connect -to port 0, which is not a valid port number. diff --git a/doc/faq.but b/doc/faq.but deleted file mode 100644 index 35db12e4e..000000000 --- a/doc/faq.but +++ /dev/null @@ -1,1650 +0,0 @@ -\A{faq} PuTTY \i{FAQ} - -This FAQ is published on the PuTTY web site, and also provided as an -appendix in the manual. - -\H{faq-intro} Introduction - -\S{faq-what}{Question} What is PuTTY? - -PuTTY is a client program for the SSH, Telnet, Rlogin, and SUPDUP -network protocols. - -These protocols are all used to run a remote session on a computer, -over a network. PuTTY implements the client end of that session: the -end at which the session is displayed, rather than the end at which -it runs. - -In really simple terms: you run PuTTY on a Windows machine, and tell -it to connect to (for example) a Unix machine. PuTTY opens a window. -Then, anything you type into that window is sent straight to the -Unix machine, and everything the Unix machine sends back is -displayed in the window. So you can work on the Unix machine as if -you were sitting at its console, while actually sitting somewhere -else. - -\H{faq-support} Features supported in PuTTY - -\I{supported features}In general, if you want to know if PuTTY supports -a particular feature, you should look for it on the -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}{PuTTY web site}. -In particular: - -\b try the -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{changes -page}, and see if you can find the feature on there. If a feature is -listed there, it's been implemented. If it's listed as a change made -\e{since} the latest version, it should be available in the -development snapshots, in which case testing will be very welcome. - -\b try the -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist -page}, and see if you can find the feature there. If it's on there, -and not in the \q{Recently fixed} section, it probably \e{hasn't} been -implemented. - -\S{faq-ssh2}{Question} Does PuTTY support SSH-2? - -Yes. SSH-2 support has been available in PuTTY since version 0.50 in -2000. - -Public key authentication (both RSA and DSA) in SSH-2 was new in -version 0.52 in 2002. - -\S{faq-ssh2-keyfmt}{Question} Does PuTTY support reading OpenSSH or -\cw{ssh.com} SSH-2 private key files? - -PuTTY doesn't support this natively (see -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/key-formats-natively.html}{the wishlist entry} -for reasons why not), but as of 0.53 -PuTTYgen can convert both OpenSSH and \cw{ssh.com} private key -files into PuTTY's format. - -\S{faq-ssh1}{Question} Does PuTTY support SSH-1? - -Yes. SSH-1 support has always been available in PuTTY. - -However, the SSH-1 protocol has many weaknesses and is no longer -considered secure; you should use SSH-2 instead if at all possible. - -As of 0.68, PuTTY will no longer fall back to SSH-1 if the server -doesn't appear to support SSH-2; you must explicitly ask for SSH-1. - -\S{faq-localecho}{Question} Does PuTTY support \i{local echo}? - -Yes. Version 0.52 has proper support for local echo. - -In version 0.51 and before, local echo could not be separated from -local line editing (where you type a line of text locally, and it is -not sent to the server until you press Return, so you have the -chance to edit it and correct mistakes \e{before} the server sees -it). New in version 0.52, local echo and local line editing are -separate options, and by default PuTTY will try to determine -automatically whether to enable them or not, based on which protocol -you have selected and also based on hints from the server. If you -have a problem with PuTTY's default choice, you can force each -option to be enabled or disabled as you choose. The controls are in -the Terminal panel, in the section marked \q{Line discipline -options}. - -\S{faq-savedsettings}{Question} Does PuTTY support storing settings, -so I don't have to change them every time? - -Yes, all of PuTTY's settings can be saved in named session profiles. -You can also change the default settings that are used for new sessions. -See \k{config-saving} in the documentation for how to do this. - -\S{faq-disksettings}{Question} Does PuTTY support storing its -settings in a disk file? - -Not at present, although \k{config-file} in the documentation gives -a method of achieving the same effect. - -\S{faq-fullscreen}{Question} Does PuTTY support full-screen mode, -like a DOS box? - -Yes; this was added in version 0.52, in 2002. - -\S{faq-password-remember}{Question} Does PuTTY have the ability to -\i{remember my password} so I don't have to type it every time? - -No, it doesn't. - -Remembering your password is a bad plan for obvious security -reasons: anyone who gains access to your machine while you're away -from your desk can find out the remembered password, and use it, -abuse it or change it. - -In addition, it's not even \e{possible} for PuTTY to automatically -send your password in a Telnet session, because Telnet doesn't give -the client software any indication of which part of the login -process is the password prompt. PuTTY would have to guess, by -looking for words like \q{password} in the session data; and if your -login program is written in something other than English, this won't -work. - -In SSH, remembering your password would be possible in theory, but -there doesn't seem to be much point since SSH supports public key -authentication, which is more flexible and more secure. See -\k{pubkey} in the documentation for a full discussion of public key -authentication. - -\S{faq-hostkeys}{Question} Is there an option to turn off the -\I{verifying the host key}annoying host key prompts? - -No, there isn't. And there won't be. Even if you write it yourself -and send us the patch, we won't accept it. - -Those annoying host key prompts are the \e{whole point} of SSH. -Without them, all the cryptographic technology SSH uses to secure -your session is doing nothing more than making an attacker's job -slightly harder; instead of sitting between you and the server with -a packet sniffer, the attacker must actually subvert a router and -start modifying the packets going back and forth. But that's not all -that much harder than just sniffing; and without host key checking, -it will go completely undetected by client or server. - -Host key checking is your guarantee that the encryption you put on -your data at the client end is the \e{same} encryption taken off the -data at the server end; it's your guarantee that it hasn't been -removed and replaced somewhere on the way. Host key checking makes -the attacker's job \e{astronomically} hard, compared to packet -sniffing, and even compared to subverting a router. Instead of -applying a little intelligence and keeping an eye on oss-security, the -attacker must now perform a brute-force attack against at least one -military-strength cipher. That insignificant host key prompt really -does make \e{that} much difference. - -If you're having a specific problem with host key checking - perhaps -you want an automated batch job to make use of PSCP or Plink, and the -interactive host key prompt is hanging the batch process - then the -right way to fix it is to add the correct host key to the Registry in -advance, or if the Registry is not available, to use the \cw{-hostkey} -command-line option. That way, you retain the \e{important} feature of -host key checking: the right key will be accepted and the wrong ones -will not. Adding an option to turn host key checking off completely is -the wrong solution and we will not do it. - -If you have host keys available in the common \i\c{known_hosts} format, -we have a script called -\W{https://git.tartarus.org/?p=simon/putty.git;a=blob;f=contrib/kh2reg.py;hb=HEAD}\c{kh2reg.py} -to convert them to a Windows .REG file, which can be installed ahead of -time by double-clicking or using \c{REGEDIT}. - -\S{faq-server}{Question} Will you write an SSH server for the PuTTY -suite, to go with the client? - -Not one that you'd want to use. - -While much of the protocol and networking code can be made common -between a client and server, to make a \e{useful} general-purpose -server requires all sorts of fiddly new code like interacting with OS -authentication databases and the like. - -A special-purpose SSH server (called \i{Uppity}) can now be built from -the PuTTY source code, and indeed it is not usable as a -general-purpose server; it exists mainly as a test harness. - -If someone else wants to use this as a basis for writing a -general-purpose SSH server, they'd be perfectly welcome to of course; -but we don't have time, and we don't have motivation. The code is -available if anyone else wants to try it. - -\S{faq-pscp-ascii}{Question} Can PSCP or PSFTP transfer files in -\i{ASCII} mode? - -Unfortunately not. - -This was a limitation of the file transfer protocols as originally -specified: the SCP and SFTP protocols had no notion of transferring -a file in anything other than binary mode. (This is still true of SCP.) - -The current draft protocol spec of SFTP proposes a means of -implementing ASCII transfer. At some point PSCP/PSFTP may implement -this proposal. - -\H{faq-ports} Ports to other operating systems - -The eventual goal is for PuTTY to be a multi-platform program, able -to run on at least Windows, Mac OS and Unix. - -PuTTY has been gaining a generalised porting layer, drawing a clear -line between platform-dependent and platform-independent code. The -general intention was for this porting layer to evolve naturally as -part of the process of doing the first port; a Unix port has now been -released and the plan seems to be working so far. - -\S{faq-ports-general}{Question} What ports of PuTTY exist? - -Currently, release versions of PuTTY tools only run on Windows -systems and Unix. - -As of 0.68, the supplied PuTTY executables run on versions of Windows -from XP onwards, up to and including Windows 11; and we know of no -reason why PuTTY should not continue to work on future versions of -Windows. We provide 32-bit and 64-bit Windows executables for the -common x86 processor family; see \k{faq-32bit-64bit} for discussion -of the compatibility issues around that. The 32-bit executables -require a \i{Pentium 4} or newer processor. We also provide -executables for Windows on Arm processors. - -(We used to also provide executables for Windows for the Alpha -processor, but stopped after 0.58 due to lack of interest.) - -In the development code, a partial port to Mac OS exists (see -\k{faq-mac-port}). - -Currently PuTTY does \e{not} run on Windows CE (see \k{faq-wince}). - -We do not have release-quality ports for any other systems at the -present time. If anyone told you we had an Android port, or an iOS -port, or any other port of PuTTY, they were mistaken. We don't. - -There are some third-party ports to various platforms, mentioned -on the -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website}. - -\S{faq-unix}{Question} \I{Unix version}Is there a port to Unix? - -There are Unix ports of most of the traditional PuTTY tools, and also -one entirely new application. - -If you look at the source release, you should find a \c{unix} -subdirectory. You need \c{cmake} to build it; see the file \c{README} -in the source distribution. This should build you: - -\b Unix ports of PuTTY, Plink, PSCP, and PSFTP, which work pretty much -the same as their Windows counterparts; - -\b Command-line versions of PuTTYgen and Pageant, whose user interface -is quite different to the Windows GUI versions; - -\b \i\c{pterm} - an \cw{xterm}-type program which supports the same -terminal emulation as PuTTY. - -If you don't have \i{Gtk}, you should still be able to build the -command-line tools. - -\S{faq-unix-why}{Question} What's the point of the Unix port? Unix -has OpenSSH. - -All sorts of little things. \c{pterm} is directly useful to anyone -who prefers PuTTY's terminal emulation to \c{xterm}'s, which at -least some people do. Unix Plink has apparently found a niche among -people who find the complexity of OpenSSL makes OpenSSH hard to -install (and who don't mind Plink not having as many features). Some -users want to generate a large number of SSH keys on Unix and then -copy them all into PuTTY, and the Unix PuTTYgen should allow them to -automate that conversion process. - -There were development advantages as well; porting PuTTY to Unix was -a valuable path-finding effort for other future ports, and also -allowed us to use the excellent Linux tool -\W{http://valgrind.kde.org/}{Valgrind} to help with debugging, which -has already improved PuTTY's stability on \e{all} platforms. - -However, if you're a Unix user and you can see no reason to switch -from OpenSSH to PuTTY/Plink, then you're probably right. We don't -expect our Unix port to be the right thing for everybody. - -\S{faq-wince}{Question} Will there be a port to Windows CE or PocketPC? - -We once did some work on such a port, but it only reached an early -stage, and certainly not a useful one. It's no longer being actively -worked on. - -\S{faq-win31}{Question} Is there a port to \i{Windows 3.1}? - -PuTTY is a 32-bit application from the ground up, so it won't run on -Windows 3.1 as a native 16-bit program; and it would be \e{very} -hard to port it to do so, because of Windows 3.1's vile memory -allocation mechanisms. - -However, it is possible in theory to compile the existing PuTTY -source in such a way that it will run under \i{Win32s} (an extension to -Windows 3.1 to let you run 32-bit programs). In order to do this -you'll need the right kind of C compiler - modern versions of Visual -C at least have stopped being backwards compatible to Win32s. Also, -the last time we tried this it didn't work very well. - -\S{faq-mac-port}{Question} Will there be a port to the \I{Mac OS}Mac? - -We hope so! - -We attempted one around 2005, written as a native Cocoa application, -but it turned out to be very slow to redraw its window for some reason -we never got to the bottom of. - -In 2015, after porting the GTK front end to work with GTK 3, we began -another attempt based on making small changes to the GTK code and -building it against the OS X Quartz version of GTK 3. This doesn't -seem to have the window redrawing problem any more, so it's already -got further than the last effort, but it is still substantially -unfinished. - -If any OS X and/or GTK programming experts are keen to have a finished -version of this, we urge them to help out with some of the remaining -problems! See the TODO list in \c{unix/main-gtk-application.c} in the -source code. - -\S{faq-epoc}{Question} Will there be a port to EPOC? - -I hope so, but given that ports aren't really progressing very fast -even on systems the developers \e{do} already know how to program -for, it might be a long time before any of us get round to learning -a new system and doing the port for that. - -However, some of the work has been done by other people; see the -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/links.html}{Links page of our website} -for various third-party ports. - -\S{faq-iphone}{Question} Will there be a port to the iPhone? - -We have no plans to write such a port ourselves; none of us has an -iPhone, and developing and publishing applications for it looks -awkward and expensive. - -However, there is a third-party SSH client for the iPhone and -iPod\_Touch called \W{http://www.instantcocoa.com/products/pTerm/}{pTerm}, -which is apparently based on PuTTY. (This is nothing to do with our -similarly-named \c{pterm}, which is a standalone terminal emulator for -Unix systems; see \k{faq-unix}.) - -\H{faq-embedding} Embedding PuTTY in other programs - -\S{faq-dll}{Question} Is the SSH or Telnet code available as a DLL? - -No, it isn't. It would take a reasonable amount of rewriting for -this to be possible, and since the PuTTY project itself doesn't -believe in DLLs (they make installation more error-prone) none of us -has taken the time to do it. - -Most of the code cleanup work would be a good thing to happen in -general, so if anyone feels like helping, we wouldn't say no. - -See also -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/dll-frontend.html}{the wishlist entry}. - -\S{faq-vb}{Question} Is the SSH or Telnet code available as a Visual -Basic component? - -No, it isn't. None of the PuTTY team uses Visual Basic, and none of -us has any particular need to make SSH connections from a Visual -Basic application. In addition, all the preliminary work to turn it -into a DLL would be necessary first; and furthermore, we don't even -know how to write VB components. - -If someone offers to do some of this work for us, we might consider -it, but unless that happens I can't see VB integration being -anywhere other than the very bottom of our priority list. - -\S{faq-ipc}{Question} How can I use PuTTY to make an SSH connection -from within another program? - -Probably your best bet is to use Plink, the command-line connection -tool. If you can start Plink as a second Windows process, and -arrange for your primary process to be able to send data to the -Plink process, and receive data from it, through pipes, then you -should be able to make SSH connections from your program. - -This is what CVS for Windows does, for example. - -\H{faq-details} Details of PuTTY's operation - -\S{faq-term}{Question} What \i{terminal type} does PuTTY use? - -For most purposes, PuTTY can be considered to be an \cw{xterm} -terminal. - -PuTTY also supports some terminal \i{control sequences} not supported by -the real \cw{xterm}: notably the Linux console sequences that -reconfigure the colour palette, and the title bar control sequences -used by \i\cw{DECterm} (which are different from the \cw{xterm} ones; -PuTTY supports both). - -By default, PuTTY announces its terminal type to the server as -\c{xterm}. If you have a problem with this, you can reconfigure it -to say something else; \c{vt220} might help if you have trouble. - -\S{faq-settings}{Question} Where does PuTTY store its data? - -On Windows, PuTTY stores most of its data (saved sessions, SSH host -keys) in the \i{Registry}. The precise location is - -\c HKEY_CURRENT_USER\Software\SimonTatham\PuTTY - -and within that area, saved sessions are stored under \c{Sessions} -while host keys are stored under \c{SshHostKeys}. - -PuTTY also requires a random number seed file, to improve the -unpredictability of randomly chosen data needed as part of the SSH -cryptography. This is stored by default in a file called \i\c{PUTTY.RND}; -this is stored by default in the \q{Application Data} directory, -or failing that, one of a number of fallback locations. If you -want to change the location of the random number seed file, you can -put your chosen pathname in the Registry, at - -\c HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\RandSeedFile - -You can ask PuTTY to delete all this data; see \k{faq-cleanup}. - -On Unix, PuTTY stores all of this data in a directory \cw{~/.putty} -by default. - -\S{faq-trust-sigils} Why do small \i{PuTTY icon}s appear next to the login -prompts? - -As of PuTTY 0.71, some lines of text in the terminal window are marked -with a small copy of the PuTTY icon (as far as pixels allow). - -This is to show trustworthiness. When the PuTTY icon appears next to a -line of text, it indicates that that line of text was generated by -PuTTY itself, and not generated by the server and sent to PuTTY. - -Text that comes from the server does not have this icon, and we've -arranged that the server should not be able to fake it. (There's no -control sequence the server can send which will make PuTTY draw its -own icon, and if the server tries to move the cursor back up to a line -that \e{already} has an icon and overwrite the text, the icon will -disappear.) - -This lets you tell the difference between (for example) a legitimate -prompt in which PuTTY itself asks you for your private key passphrase, -and a fake prompt in which the server tries to send the identical text -to trick you into telling \e{it} your private key passphrase. - -\S{faq-plink-pause} Why has Plink started saying \q{Press Return to -begin session}? - -As of PuTTY 0.71, if you use Plink for an interactive SSH session, -then after the login phase has finished, it will present a final -interactive prompt saying \q{Access granted. Press Return to begin -session}. - -This is another defence against servers trying to mimic the real -authentication prompts after the session has started. When you pass -through that prompt, you know that everything after it is generated by -the server and not by Plink itself, so any request for your private -key passphrase should be treated with suspicion. - -In Plink, we can't use the defence described in \k{faq-trust-sigils}: -Plink is running \e{in} the terminal, so anything it can write into -the terminal, the server could write in the same way after the session -starts. And we can't just print a separator line without a pause, -because then the server could simply move the cursor back up to it and -overwrite it (probably with a brief flicker, but you might easily miss -that). The only robust defence anyone has come up with involves this -pause. - -If you trust your server not to be abusive, you can turn this off. It -will also not appear in various other circumstances where Plink can be -confident it isn't necessary. See \k{plink-option-antispoof} for -details. - -\H{faq-howto} HOWTO questions - -\S{faq-login}{Question} What login name / password should I use? - -This is not a question you should be asking \e{us}. - -PuTTY is a communications tool, for making connections to other -computers. We maintain the tool; we \e{don't} administer any computers -that you're likely to be able to use, in the same way that the people -who make web browsers aren't responsible for most of the content you can -view in them. \#{FIXME: less technical analogy?} We cannot help with -questions of this sort. - -If you know the name of the computer you want to connect to, but don't -know what login name or password to use, you should talk to whoever -administers that computer. If you don't know who that is, see the next -question for some possible ways to find out. - -\# FIXME: some people ask us to provide them with a login name -apparently as random members of the public rather than in the -belief that we run a server belonging to an organisation they already -have some relationship with. Not sure what to say to such people. - -\S{faq-commands}{Question} \I{commands on the server}What commands -can I type into my PuTTY terminal window? - -Again, this is not a question you should be asking \e{us}. You need -to read the manuals, or ask the administrator, of \e{the computer -you have connected to}. - -PuTTY does not process the commands you type into it. It's only a -communications tool. It makes a connection to another computer; it -passes the commands you type to that other computer; and it passes -the other computer's responses back to you. Therefore, the precise -range of commands you can use will not depend on PuTTY, but on what -kind of computer you have connected to and what software is running -on it. The PuTTY team cannot help you with that. - -(Think of PuTTY as being a bit like a telephone. If you phone -somebody up and you don't know what language to speak to make them -understand you, it isn't \e{the telephone company}'s job to find -that out for you. We just provide the means for you to get in touch; -making yourself understood is somebody else's problem.) - -If you are unsure of where to start looking for the administrator of -your server, a good place to start might be to remember how you -found out the host name in the PuTTY configuration. If you were -given that host name by e-mail, for example, you could try asking -the person who sent you that e-mail. If your company's IT department -provided you with ready-made PuTTY saved sessions, then that IT -department can probably also tell you something about what commands -you can type during those sessions. But the PuTTY maintainer team -does not administer any server you are likely to be connecting to, -and cannot help you with questions of this type. - -\S{faq-startmax}{Question} How can I make PuTTY start up \i{maximise}d? - -Create a Windows shortcut to start PuTTY from, and set it as \q{Run -Maximized}. - -\S{faq-startsess}{Question} How can I create a \i{Windows shortcut} to -start a particular saved session directly? - -To run a PuTTY session saved under the name \q{\cw{mysession}}, -create a Windows shortcut that invokes PuTTY with a command line -like - -\c \path\name\to\putty.exe -load "mysession" - -(Note: prior to 0.53, the syntax was \c{@session}. This is now -deprecated and may be removed at some point.) - -\S{faq-startssh}{Question} How can I start an SSH session straight -from the command line? - -Use the command line \c{putty -ssh host.name}. Alternatively, create -a saved session that specifies the SSH protocol, and start the saved -session as shown in \k{faq-startsess}. - -\S{faq-cutpaste}{Question} How do I \i{copy and paste} between PuTTY and -other Windows applications? - -Copy and paste works similarly to the X Window System. You use the -left mouse button to select text in the PuTTY window. The act of -selection \e{automatically} copies the text to the clipboard: there -is no need to press Ctrl-Ins or Ctrl-C or anything else. In fact, -pressing Ctrl-C will send a Ctrl-C character to the other end of -your connection (just like it does the rest of the time), which may -have unpleasant effects. The \e{only} thing you need to do, to copy -text to the clipboard, is to select it. - -To paste the clipboard contents into a PuTTY window, by default you -click the right mouse button. If you have a three-button mouse and -are used to X applications, you can configure pasting to be done by -the middle button instead, but this is not the default because most -Windows users don't have a middle button at all. - -You can also paste by pressing Shift-Ins. - -\S{faq-options}{Question} How do I use all PuTTY's features (public -keys, proxying, cipher selection, etc.) in PSCP, PSFTP and Plink? - -Most major features (e.g., public keys, port forwarding) are available -through command line options. See \k{using-general-opts}. - -Not all features are accessible from the command line yet, although -we'd like to fix this. In the meantime, you can use most of -PuTTY's features if you create a PuTTY saved session, and then use -the name of the saved session on the command line in place of a -hostname. This works for PSCP, PSFTP and Plink (but don't expect -port forwarding in the file transfer applications!). - -\S{faq-pscp}{Question} How do I use PSCP.EXE? When I double-click it -gives me a command prompt window which then closes instantly. - -PSCP is a command-line application, not a GUI application. If you -run it without arguments, it will simply print a help message and -terminate. - -To use PSCP properly, run it from a Command Prompt window. See -\k{pscp} in the documentation for more details. - -\S{faq-pscp-spaces}{Question} \I{spaces in filenames}How do I use -PSCP to copy a file whose name has spaces in? - -If PSCP is using the newer SFTP protocol (which is usual with most -modern servers), this is straightforward; all filenames with spaces -in are specified using a single pair of quotes in the obvious way: - -\c pscp "local file" user@host: -\c pscp user@host:"remote file" . - -However, if PSCP is using the older SCP protocol for some reason, -things are more confusing. If you're specifying a file at the local -end, you just use one set of quotes as you would normally do: - -\c pscp "local filename with spaces" user@host: -\c pscp user@host:myfile "local filename with spaces" - -But if the filename you're specifying is on the \e{remote} side, you -have to use backslashes and two sets of quotes: - -\c pscp user@host:"\"remote filename with spaces\"" local_filename -\c pscp local_filename user@host:"\"remote filename with spaces\"" - -Worse still, in a remote-to-local copy you have to specify the local -file name explicitly, otherwise PSCP will complain that they don't -match (unless you specified the \c{-unsafe} option). The following -command will give an error message: - -\c c:\>pscp user@host:"\"oo er\"" . -\c warning: remote host tried to write to a file called 'oo er' -\c when we requested a file called '"oo er"'. - -Instead, you need to specify the local file name in full: - -\c c:\>pscp user@host:"\"oo er\"" "oo er" - -\S{faq-32bit-64bit}{Question} Should I run the 32-bit or the -64-bit version? - -If you're not sure, the \I{32-bit Windows}32-bit version is generally -the safe option. It will run perfectly well on all processors and on -all versions of Windows that PuTTY supports. PuTTY doesn't require to -run as a 64-bit application to work well, and having a 32-bit PuTTY on -a 64-bit system isn't likely to cause you any trouble. - -The 64-bit version (first released in 0.68) will only run if you have -a 64-bit processor \e{and} a \I{64-bit Windows}64-bit edition of -Windows (both of these things are likely to be true of any recent -Windows PC). It will run somewhat faster (in particular, the -cryptography will be faster, especially during link setup), but it -will consume slightly more memory. - -If you need to use an external \i{DLL} for GSSAPI authentication, that -DLL may only be available in a 32-bit or 64-bit form, and that will -dictate the version of PuTTY you need to use. (You will probably know -if you're doing this; see \k{config-ssh-auth-gssapi-libraries} in the -documentation.) - -\H{faq-trouble} Troubleshooting - -\S{faq-pscp-protocol}{Question} Why do I see \q{Fatal: Protocol -error: Expected control record} in PSCP? - -This happens because PSCP was expecting to see data from the server -that was part of the PSCP protocol exchange, and instead it saw data -that it couldn't make any sense of at all. - -This almost always happens because the \i{startup scripts} in your -account on the server machine are generating output. This is -impossible for PSCP, or any other SCP client, to work around. You -should never use startup files (\c{.bashrc}, \c{.cshrc} and so on) -which generate output in non-interactive sessions. - -This is not actually a PuTTY problem. If PSCP fails in this way, -then all other SCP clients are likely to fail in exactly the same -way. The problem is at the server end. - -\S{faq-colours}{Question} I clicked on a colour in the \ii{Colours} -panel, and the colour didn't change in my terminal. - -That isn't how you're supposed to use the Colours panel. - -During the course of a session, PuTTY potentially uses \e{all} the -colours listed in the Colours panel. It's not a question of using -only one of them and you choosing which one; PuTTY will use them -\e{all}. The purpose of the Colours panel is to let you adjust the -appearance of all the colours. So to change the colour of the -cursor, for example, you would select \q{Cursor Colour}, press the -\q{Modify} button, and select a new colour from the dialog box that -appeared. Similarly, if you want your session to appear in green, -you should select \q{Default Foreground} and press \q{Modify}. -Clicking on \q{ANSI Green} won't turn your session green; it will -only allow you to adjust the \e{shade} of green used when PuTTY is -instructed by the server to display green text. - -\S{faq-outofmem}{Question} After trying to establish an SSH-2 -connection, PuTTY says \q{\ii{Out of memory}} and dies. - -If this happens just while the connection is starting up, this often -indicates that for some reason the client and server have failed to -establish a session encryption key. Somehow, they have performed -calculations that should have given each of them the same key, but -have ended up with different keys; so data encrypted by one and -decrypted by the other looks like random garbage. - -This causes an \q{out of memory} error because the first encrypted -data PuTTY expects to see is the length of an SSH message. Normally -this will be something well under 100 bytes. If the decryption has -failed, PuTTY will see a completely random length in the region of -two \e{gigabytes}, and will try to allocate enough memory to store -this non-existent message. This will immediately lead to it thinking -it doesn't have enough memory, and panicking. - -If this happens to you, it is quite likely to still be a PuTTY bug -and you should report it (although it might be a bug in your SSH -server instead); but it doesn't necessarily mean you've actually run -out of memory. - -\S{faq-outofmem2}{Question} When attempting a file transfer, either -PSCP or PSFTP says \q{\ii{Out of memory}} and dies. - -This is almost always caused by your \i{login scripts} on the server -generating output. PSCP or PSFTP will receive that output when they -were expecting to see the start of a file transfer protocol, and -they will attempt to interpret the output as file-transfer protocol. -This will usually lead to an \q{out of memory} error for much the -same reasons as given in \k{faq-outofmem}. - -This is a setup problem in your account on your server, \e{not} a -PSCP/PSFTP bug. Your login scripts should \e{never} generate output -during non-interactive sessions; secure file transfer is not the -only form of remote access that will break if they do. - -On Unix, a simple fix is to ensure that all the parts of your login -script that might generate output are in \c{.profile} (if you use a -Bourne shell derivative) or \c{.login} (if you use a C shell). -Putting them in more general files such as \c{.bashrc} or \c{.cshrc} -is liable to lead to problems. - -\S{faq-psftp-slow}{Question} PSFTP transfers files much slower than PSCP. - -The throughput of PSFTP 0.54 should be much better than 0.53b and -prior; we've added code to the SFTP backend to queue several blocks -of data rather than waiting for an acknowledgement for each. (The -SCP backend did not suffer from this performance issue because SCP -is a much simpler protocol.) - -\S{faq-bce}{Question} When I run full-colour applications, I see -areas of black space where colour ought to be, or vice versa. - -You almost certainly need to change the \q{Use \i{background colour} to -erase screen} setting in the Terminal panel. If there is too much -black space (the commoner situation), you should enable it, while if -there is too much colour, you should disable it. (See \k{config-erase}.) - -In old versions of PuTTY, this was disabled by default, and would not -take effect until you reset the terminal (see \k{faq-resetterm}). -Since 0.54, it is enabled by default, and changes take effect -immediately. - -\S{faq-resetterm}{Question} When I change some terminal settings, -nothing happens. - -Some of the terminal options (notably \ii{Auto Wrap} and -background-colour screen erase) actually represent the \e{default} -setting, rather than the currently active setting. The server can -send sequences that modify these options in mid-session, but when -the terminal is reset (by server action, or by you choosing \q{Reset -Terminal} from the System menu) the defaults are restored. - -In versions 0.53b and prior, if you change one of these options in -the middle of a session, you will find that the change does not -immediately take effect. It will only take effect once you reset -the terminal. - -In version 0.54, the behaviour has changed - changes to these -settings take effect immediately. - -\S{faq-idleout}{Question} My PuTTY sessions unexpectedly close after -they are \I{idle connections}idle for a while. - -Some types of \i{firewall}, and almost any router doing Network Address -Translation (\i{NAT}, also known as IP masquerading), will forget about -a connection through them if the connection does nothing for too -long. This will cause the connection to be rudely cut off when -contact is resumed. - -You can try to combat this by telling PuTTY to send \e{keepalives}: -packets of data which have no effect on the actual session, but -which reassure the router or firewall that the network connection is -still active and worth remembering about. - -Keepalives don't solve everything, unfortunately; although they -cause greater robustness against this sort of router, they can also -cause a \e{loss} of robustness against network dropouts. See -\k{config-keepalive} in the documentation for more discussion of -this. - -\S{faq-timeout}{Question} PuTTY's network connections time out too -quickly when \I{breaks in connectivity}network connectivity is -temporarily lost. - -This is a Windows problem, not a PuTTY problem. The timeout value -can't be set on per application or per session basis. To increase -the TCP timeout globally, you need to tinker with the Registry. - -On Windows 95, 98 or ME, the registry key you need to create or -change is - -\c HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\ -\c MSTCP\MaxDataRetries - -(it must be of type DWORD in Win95, or String in Win98/ME). -(See MS Knowledge Base article -\W{http://support.microsoft.com/default.aspx?scid=kb;en-us;158474}{158474} -for more information.) - -On Windows NT, 2000, or XP, the registry key to create or change is - -\c HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\ -\c Parameters\TcpMaxDataRetransmissions - -and it must be of type DWORD. -(See MS Knowledge Base articles -\W{http://support.microsoft.com/default.aspx?scid=kb;en-us;120642}{120642} -and -\W{http://support.microsoft.com/default.aspx?scid=kb;en-us;314053}{314053} -for more information.) - -Set the key's value to something like 10. This will cause Windows to -try harder to keep connections alive instead of abandoning them. - -\S{faq-puttyputty}{Question} When I \cw{cat} a binary file, I get -\q{PuTTYPuTTYPuTTY} on my command line. - -Don't do that, then. - -This is designed behaviour; when PuTTY receives the character -Control-E from the remote server, it interprets it as a request to -identify itself, and so it sends back the string \q{\cw{PuTTY}} as -if that string had been entered at the keyboard. Control-E should -only be sent by programs that are prepared to deal with the -response. Writing a binary file to your terminal is likely to output -many Control-E characters, and cause this behaviour. Don't do it. -It's a bad plan. - -To mitigate the effects, you could configure the answerback string -to be empty (see \k{config-answerback}); but writing binary files to -your terminal is likely to cause various other unpleasant behaviour, -so this is only a small remedy. - -\S{faq-wintitle}{Question} When I \cw{cat} a binary file, my \i{window -title} changes to a nonsense string. - -Don't do that, then. - -It is designed behaviour that PuTTY should have the ability to -adjust the window title on instructions from the server. Normally -the control sequence that does this should only be sent -deliberately, by programs that know what they are doing and intend -to put meaningful text in the window title. Writing a binary file to -your terminal runs the risk of sending the same control sequence by -accident, and cause unexpected changes in the window title. Don't do -it. - -\S{faq-password-fails}{Question} My \i{keyboard} stops working once -PuTTY displays the \i{password prompt}. - -No, it doesn't. PuTTY just doesn't display the password you type, so -that someone looking at your screen can't see what it is. - -Unlike the Windows login prompts, PuTTY doesn't display the password -as a row of asterisks either. This is so that someone looking at -your screen can't even tell how \e{long} your password is, which -might be valuable information. - -\S{faq-keyboard}{Question} One or more \I{keyboard}\i{function keys} -don't do what I expected in a server-side application. - -If you've already tried all the relevant options in the PuTTY -Keyboard panel, you may need to mail the PuTTY maintainers and ask. - -It is \e{not} usually helpful just to tell us which application, -which server operating system, and which key isn't working; in order -to replicate the problem we would need to have a copy of every -operating system, and every application, that anyone has ever -complained about. - -PuTTY responds to function key presses by sending a sequence of -control characters to the server. If a function key isn't doing what -you expect, it's likely that the character sequence your application -is expecting to receive is not the same as the one PuTTY is sending. -Therefore what we really need to know is \e{what} sequence the -application is expecting. - -The simplest way to investigate this is to find some other terminal -environment, in which that function key \e{does} work; and then -investigate what sequence the function key is sending in that -situation. One reasonably easy way to do this on a \i{Unix} system is to -type the command \i\c{cat}, and then press the function key. This is -likely to produce output of the form \c{^[[11~}. You can also do -this in PuTTY, to find out what sequence the function key is -producing in that. Then you can mail the PuTTY maintainers and tell -us \q{I wanted the F1 key to send \c{^[[11~}, but instead it's -sending \c{^[OP}, can this be done?}, or something similar. - -You should still read the -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/feedback.html}{Feedback -page} on the PuTTY website (also provided as \k{feedback} in the -manual), and follow the guidelines contained in that. - -\S{faq-ssh2key-ssh1conn}{Question} Why do I see \q{Couldn't load -private key from ...}? Why can PuTTYgen load my key but not PuTTY? - -It's likely that you've generated an SSH protocol 2 key with PuTTYgen, -but you're trying to use it in an SSH-1 connection. SSH-1 and SSH-2 keys -have different formats, and (at least in 0.52) PuTTY's reporting of a -key in the wrong format isn't optimal. - -To connect using SSH-2 to a server that supports both versions, you -need to change the configuration from the default (see \k{faq-ssh2}). - -\S{faq-rh8-utf8}{Question} When I'm connected to a \i{Red Hat Linux} 8.0 -system, some characters don't display properly. - -A common complaint is that hyphens in man pages show up as a-acute. - -With release 8.0, Red Hat appear to have made \i{UTF-8} the default -character set. There appears to be no way for terminal emulators such -as PuTTY to know this (as far as we know, the appropriate escape -sequence to switch into UTF-8 mode isn't sent). - -A fix is to configure sessions to RH8 systems to use UTF-8 -translation - see \k{config-charset} in the documentation. (Note that -if you use \q{Change Settings}, changes may not take place immediately -- see \k{faq-resetterm}.) - -If you really want to change the character set used by the server, the -right place is \c{/etc/sysconfig/i18n}, but this shouldn't be -necessary. - -\S{faq-screen}{Question} Since I upgraded to PuTTY 0.54, the -scrollback has stopped working when I run \c{screen}. - -PuTTY's terminal emulator has always had the policy that when the -\q{\i{alternate screen}} is in use, nothing is added to the scrollback. -This is because the usual sorts of programs which use the alternate -screen are things like text editors, which tend to scroll back and -forth in the same document a lot; so (a) they would fill up the -scrollback with a large amount of unhelpfully disordered text, and -(b) they contain their \e{own} method for the user to scroll back to -the bit they were interested in. We have generally found this policy -to do the Right Thing in almost all situations. - -Unfortunately, \c{screen} is one exception: it uses the alternate -screen, but it's still usually helpful to have PuTTY's scrollback -continue working. The simplest solution is to go to the Features -control panel and tick \q{Disable switching to alternate terminal -screen}. (See \k{config-features-altscreen} for more details.) -Alternatively, you can tell \c{screen} itself not to use the -alternate screen: the -\W{http://www4.informatik.uni-erlangen.de/~jnweiger/screen-faq.html}{\c{screen} -FAQ} suggests adding the line \cq{termcapinfo xterm ti@:te@} to your -\cw{.screenrc} file. - -The reason why this only started to be a problem in 0.54 is because -\c{screen} typically uses an unusual control sequence to switch to -the alternate screen, and previous versions of PuTTY did not support -this sequence. - -\S{faq-alternate-localhost}{Question} Since I upgraded \i{Windows XP} -to Service Pack 2, I can't use addresses like \cw{127.0.0.2}. - -Some people who ask PuTTY to listen on \i{localhost} addresses other -than \cw{127.0.0.1} to forward services such as \i{SMB} and \i{Windows -Terminal Services} have found that doing so no longer works since -they upgraded to WinXP SP2. - -This is apparently an issue with SP2 that is acknowledged by Microsoft -in MS Knowledge Base article -\W{http://support.microsoft.com/default.aspx?scid=kb;en-us;884020}{884020}. -The article links to a fix you can download. - -(\e{However}, we've been told that SP2 \e{also} fixes the bug that -means you need to use non-\cw{127.0.0.1} addresses to forward -Terminal Services in the first place.) - -\S{faq-missing-slash}{Question} PSFTP commands seem to be missing a -directory separator (slash). - -Some people have reported the following incorrect behaviour with -PSFTP: - -\c psftp> pwd -\e iii -\c Remote directory is /dir1/dir2 -\c psftp> get filename.ext -\e iiiiiiiiiiiiiiii -\c /dir1/dir2filename.ext: no such file or directory - -This is not a bug in PSFTP. There is a known bug in some versions of -portable \i{OpenSSH} -(\W{http://bugzilla.mindrot.org/show_bug.cgi?id=697}{bug 697}) that -causes these symptoms; it appears to have been introduced around -3.7.x. It manifests only on certain platforms (AIX is what has been -reported to us). - -There is a patch for OpenSSH attached to that bug; it's also fixed in -recent versions of portable OpenSSH (from around 3.8). - -\S{faq-connaborted}{Question} Do you want to hear about \q{Software -caused connection abort}? - -In the documentation for PuTTY 0.53 and 0.53b, we mentioned that we'd -like to hear about any occurrences of this error. Since the release -of PuTTY 0.54, however, we've been convinced that this error doesn't -indicate that PuTTY's doing anything wrong, and we don't need to hear -about further occurrences. See \k{errors-connaborted} for our current -documentation of this error. - -\S{faq-rekey}{Question} My SSH-2 session \I{locking up, SSH-2 -sessions}locks up for a few seconds every so often. - -Recent versions of PuTTY automatically initiate \i{repeat key -exchange} once per hour, to improve session security. If your client -or server machine is slow, you may experience this as a delay of -anything up to thirty seconds or so. - -These \I{delays, in SSH-2 sessions}delays are inconvenient, but they -are there for your protection. If they really cause you a problem, -you can choose to turn off periodic rekeying using the \q{Kex} -configuration panel (see \k{config-ssh-kex}), but be aware that you -will be sacrificing security for this. (Falling back to SSH-1 would -also remove the delays, but would lose a \e{lot} more security -still. We do not recommend it.) - -\S{faq-xpwontrun}{Question} PuTTY fails to start up. Windows claims that -\q{the application configuration is incorrect}. - -This is caused by a bug in certain versions of \i{Windows XP} which -is triggered by PuTTY 0.58. This was fixed in 0.59. The -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/xp-wont-run}{\q{xp-wont-run}} -entry in PuTTY's wishlist has more details. - -\S{faq-system32}{Question} When I put 32-bit PuTTY in -\cw{C:\\WINDOWS\\\i{SYSTEM32}} on my \i{64-bit Windows} system, -\i{\q{Duplicate Session}} doesn't work. - -The short answer is not to put the PuTTY executables in that location. - -On 64-bit systems, \cw{C:\\WINDOWS\\SYSTEM32} is intended to contain -only 64-bit binaries; Windows' 32-bit binaries live in -\cw{C:\\WINDOWS\\SYSWOW64}. When a 32-bit PuTTY executable runs -on a 64-bit system, it cannot by default see the \q{real} -\cw{C:\\WINDOWS\\SYSTEM32} at all, because the -\W{http://msdn.microsoft.com/en-us/library/aa384187(v=vs.85).aspx}{File -System Redirector} arranges that the running program sees the -appropriate kind of binaries in \cw{SYSTEM32}. Thus, operations in -the PuTTY suite that involve it accessing its own executables, such as -\i{\q{New Session}} and \q{Duplicate Session}, will not work. - -\S{faq-iutf8}{Question} After I upgraded PuTTY to 0.68, I can no longer -connect to my embedded device or appliance. - -If your SSH server has started unexpectedly closing SSH connections -after you enter your password, and it worked before 0.68, you may have -a buggy server that objects to certain SSH protocol extensions. - -The SSH protocol recently gained a new \q{terminal mode}, \cw{IUTF8}, -which PuTTY sends by default; see \k{config-ttymodes}. This is the -first new terminal mode since the SSH-2 protocol was defined. While -servers are supposed to ignore modes they don't know about, some buggy -servers will unceremoniously close the connection if they see anything -they don't recognise. SSH servers in embedded devices, network -appliances, and the like seem to disproportionately have this bug. - -If you think you have such a server, from 0.69 onwards you can disable -sending of the \cw{IUTF8} mode: on the SSH / TTY panel, select -\cw{IUTF8} on the list, select \q{Nothing}, and press \q{Set}. (It's -not possible to disable sending this mode in 0.68.) - -\S{faq-privkey-control-moved}{Question} Since 0.78, I can't find where -to configure my SSH private key. - -In PuTTY 0.78, the \q{\ii{Private key} file for authentication} control, -where you specify a \c{.\i{PPK}} file for SSH public key authentication, -moved to a new \q{Credentials} panel in the configuration dialog. You can -find this by opening the \q{SSH} category in the tree view on the left, -then opening the \q{Auth} subcategory under that, then clicking on -\q{Credentials}. On this page you'll find the \q{Browse...} button you -need to select a \c{.PPK} file for authentication, as described in -\k{config-ssh-privkey}. - -(This control had previously been on the \q{Auth} panel since public -key authentication was first released in 2002, so many online how-to -guides still describe it there. The configuration controls were -reorganised to make room for features added in 0.78, such as OpenSSH -certificates.) - -\H{faq-secure} Security questions - -\S{faq-publicpc}{Question} Is it safe for me to download PuTTY and -use it on a public PC? - -It depends on whether you trust that PC. If you don't trust the -public PC, don't use PuTTY on it, and don't use any other software -you plan to type passwords into either. It might be watching your -keystrokes, or it might tamper with the PuTTY binary you download. -There is \e{no} program safe enough that you can run it on an -actively malicious PC and get away with typing passwords into it. - -If you do trust the PC, then it's probably OK to use PuTTY on it -(but if you don't trust the network, then the PuTTY download might -be tampered with, so it would be better to carry PuTTY with you on a -USB stick). - -\S{faq-cleanup}{Question} What does PuTTY leave on a system? How can -I \i{clean up} after it? - -PuTTY will leave some Registry entries, and a random seed file, on -the PC (see \k{faq-settings}). Windows 7 and up also remember some -information about recently launched sessions for the \q{jump list} -feature. - -If you are using PuTTY on a public PC, or somebody else's PC, you -might want to clean this information up when you leave. You can do -that automatically, by running the command \c{putty -cleanup}. See -\k{using-cleanup} in the documentation for more detail. (Note that -this only removes settings for the currently logged-in user on -\i{multi-user systems}.) - -If PuTTY was installed from the installer package, it will also -appear in \q{Add/Remove Programs}. Current versions of the installer -do not offer to remove the above-mentioned items, so if you want them -removed you should run \c{putty -cleanup} before uninstalling. - -\S{faq-dsa}{Question} How come PuTTY now supports \i{DSA}, when the -website used to say how insecure it was? - -DSA has a major weakness \e{if badly implemented}: it relies on a -random number generator to far too great an extent. If the random -number generator produces a number an attacker can predict, the DSA -private key is exposed - meaning that the attacker can log in as you -on all systems that accept that key. - -The PuTTY policy changed because the developers were informed of -ways to implement DSA which do not suffer nearly as badly from this -weakness, and indeed which don't need to rely on random numbers at -all. For this reason we now believe PuTTY's DSA implementation is -probably OK. - -The recently added elliptic-curve signature methods are also DSA-style -algorithms, so they have this same weakness in principle. Our ECDSA -implementation uses the same defence as DSA, while our Ed25519 -implementation uses the similar system (but different in details) that -the Ed25519 spec mandates. - -\S{faq-virtuallock}{Question} Couldn't Pageant use -\cw{VirtualLock()} to stop private keys being written to disk? - -Unfortunately not. The \cw{VirtualLock()} function in the Windows -API doesn't do a proper job: it may prevent small pieces of a -process's memory from being paged to disk while the process is -running, but it doesn't stop the process's memory as a whole from -being swapped completely out to disk when the process is long-term -inactive. And Pageant spends most of its time inactive. - -\S{faq-windowsstore}{Question} Is the version of PuTTY in the -\i{Microsoft Store} legit? - -The free-of-charge \q{PuTTY} application at -\W{https://apps.microsoft.com/store/detail/putty/XPFNZKSKLBP7RJ}{this link} -is published and maintained by us. The copy there is the latest -release, usually updated within a few days of us publishing it on our -own website. - -There have been other copies of PuTTY on the store, some looking quite -similar, and some charging money. Those were uploaded by other people, -and we can't guarantee anything about them. - -The first version we published to the Microsoft Store was 0.76 (some -time after its initial release on our website). - -\H{faq-admin} Administrative questions - -\S{faq-putty-org}{Question} Is \cw{putty.org} your website? - -No, it isn't. \cw{putty.org} is run by an opportunist who uses it to -advertise their own commercial SSH implementation to people looking -for our free one. We don't own that site, we can't control it, and we -don't advise anyone to use it in preference to our own site. - -The real PuTTY web site, run by the PuTTY team, has always been at -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/}. - -\S{faq-the}{Question} Why do the download links point to -\cw{the.earth.li} and not chiark? Has your website been hacked? - -We haven't been hacked: links to \cw{the.earth.li} are legit. The -files for released versions of PuTTY are hosted on a different server -from the web pages, for bandwidth reasons. - -The download site \cw{the.earth.li} is hosted by -\W{https://www.mythic-beasts.com/}{Mythic Beasts}, and we're very -grateful to them! - -\S{faq-domain}{Question} Would you like me to register you a nicer -domain name? - -No, thank you. Even if you can find one (most of them seem to have -been registered already, by people who didn't ask whether we -actually wanted it before they applied), we're happy with the PuTTY -web site being exactly where it is. It's not hard to find (just type -\q{putty} into \W{http://www.google.com/}{google.com} and we're the -first link returned), and we don't believe the administrative hassle -of moving the site would be worth the benefit. - -In addition, if we \e{did} want a custom domain name, we would want -to run it ourselves, so we knew for certain that it would continue -to point where we wanted it, and wouldn't suddenly change or do -strange things. Having it registered for us by a third party who we -don't even know is not the best way to achieve this. - -\S{faq-webhosting}{Question} Would you like free web hosting for the -PuTTY web site? - -We already have some, thanks. - -\S{faq-link}{Question} Would you link to my web site from the PuTTY -web site? - -Only if the content of your web page is of definite direct interest -to PuTTY users. If your content is unrelated, or only tangentially -related, to PuTTY, then the link would simply be advertising for -you. - -One very nice effect of the Google ranking mechanism is that by and -large, the most popular web sites get the highest rankings. This -means that when an ordinary person does a search, the top item in -the search is very likely to be a high-quality site or the site they -actually wanted, rather than the site which paid the most money for -its ranking. - -The PuTTY web site is held in high esteem by Google, for precisely -this reason: lots of people have linked to it simply because they -like PuTTY, without us ever having to ask anyone to link to us. We -feel that it would be an abuse of this esteem to use it to boost the -ranking of random advertisers' web sites. If you want your web site -to have a high Google ranking, we'd prefer that you achieve this the -way we did - by being good enough at what you do that people will -link to you simply because they like you. - -In particular, we aren't interested in trading links for money (see -above), and we \e{certainly} aren't interested in trading links for -other links (since we have no advertising on our web site, our -Google ranking is not even directly worth anything to us). If we -don't want to link to you for free, then we probably won't want to -link to you at all. - -If you have software based on PuTTY, or specifically designed to -interoperate with PuTTY, or in some other way of genuine interest to -PuTTY users, then we will probably be happy to add a link to you on -our Links page. And if you're running a particularly valuable mirror -of the PuTTY web site, we might be interested in linking to you from -our Mirrors page. - -\S{faq-sourceforge}{Question} Why don't you move PuTTY to -SourceForge? - -Partly, because we don't want to move the web site location (see -\k{faq-domain}). - -Also, security reasons. PuTTY is a security product, and as such it -is particularly important to guard the code and the web site against -unauthorised modifications which might introduce subtle security -flaws. Therefore, we prefer that the Git repository, web site and -FTP site remain where they are, under the direct control of system -administrators we know and trust personally, rather than being run -by a large organisation full of people we've never met and which is -known to have had breakins in the past. - -No offence to SourceForge; I think they do a wonderful job. But -they're not ideal for everyone, and in particular they're not ideal -for us. - -\S{faq-mailinglist1}{Question} Why can't I subscribe to the -putty-bugs mailing list? - -Because you're not a member of the PuTTY core development team. The -putty-bugs mailing list is not a general newsgroup-like discussion -forum; it's a contact address for the core developers, and an -\e{internal} mailing list for us to discuss things among ourselves. -If we opened it up for everybody to subscribe to, it would turn into -something more like a newsgroup and we would be completely -overwhelmed by the volume of traffic. It's hard enough to keep up -with the list as it is. - -\S{faq-mailinglist2}{Question} If putty-bugs isn't a -general-subscription mailing list, what is? - -There isn't one, that we know of. - -If someone else wants to set up a mailing list or other forum for -PuTTY users to help each other with common problems, that would be -fine with us, though the PuTTY team would almost certainly not have the -time to read it. - -\S{faq-donations}{Question} How can I donate to PuTTY development? - -Please, \e{please} don't feel you have to. PuTTY is completely free -software, and not shareware. We think it's very important that -\e{everybody} who wants to use PuTTY should be able to, whether they -have any money or not; so the last thing we would want is for a -PuTTY user to feel guilty because they haven't paid us any money. If -you want to keep your money, please do keep it. We wouldn't dream of -asking for any. - -Having said all that, if you still really \e{want} to give us money, -we won't argue :-) The easiest way for us to accept donations is if -you send money to \cw{} using PayPal -(\W{http://www.paypal.com/}\cw{www.paypal.com}). If you don't like -PayPal, talk to us; we can probably arrange some alternative means. - -Small donations (tens of dollars or tens of euros) will probably be -spent on beer or curry, which helps motivate our volunteer team to -continue doing this for the world. Larger donations will be spent on -something that actually helps development, if we can find anything -(perhaps new hardware, or a new version of Windows), but if we can't -find anything then we'll just distribute the money among the -developers. If you want to be sure your donation is going towards -something worthwhile, ask us first. If you don't like these terms, -feel perfectly free not to donate. We don't mind. - -\S{faq-permission}{Question} Can I have permission to put PuTTY on a -cover disk / distribute it with other software / etc? - -Yes. For most things, you need not bother asking us explicitly for -permission; our licence already grants you permission. - -See \k{feedback-permission} for more details. - -\S{faq-indemnity}{Question} Can you sign an agreement indemnifying -us against security problems in PuTTY? - -No! - -A vendor of physical security products (e.g. locks) might plausibly -be willing to accept financial liability for a product that failed -to perform as advertised and resulted in damage (e.g. valuables -being stolen). The reason they can afford to do this is because they -sell a \e{lot} of units, and only a small proportion of them will -fail; so they can meet their financial liability out of the income -from all the rest of their sales, and still have enough left over to -make a profit. Financial liability is intrinsically linked to -selling your product for money. - -There are two reasons why PuTTY is not analogous to a physical lock -in this context. One is that software products don't exhibit random -variation: \e{if} PuTTY has a security hole (which does happen, -although we do our utmost to prevent it and to respond quickly when -it does), every copy of PuTTY will have the same hole, so it's -likely to affect all the users at the same time. So even if our -users were all paying us to use PuTTY, we wouldn't be able to -\e{simultaneously} pay every affected user compensation in excess of -the amount they had paid us in the first place. It just wouldn't -work. - -The second, much more important, reason is that PuTTY users -\e{don't} pay us. The PuTTY team does not have an income; it's a -volunteer effort composed of people spending their spare time to try -to write useful software. We aren't even a company or any kind of -legally recognised organisation. We're just a bunch of people who -happen to do some stuff in our spare time. - -Therefore, to ask us to assume financial liability is to ask us to -assume a risk of having to pay it out of our own \e{personal} -pockets: out of the same budget from which we buy food and clothes -and pay our rent. That's more than we're willing to give. We're -already giving a lot of our spare \e{time} to developing software -for free; if we had to pay our own \e{money} to do it as well, we'd -start to wonder why we were bothering. - -Free software fundamentally does not work on the basis of financial -guarantees. Your guarantee of the software functioning correctly is -simply that you have the source code and can check it before you use -it. If you want to be sure there aren't any security holes, do a -security audit of the PuTTY code, or hire a security engineer if you -don't have the necessary skills yourself: instead of trying to -ensure you can get compensation in the event of a disaster, try to -ensure there isn't a disaster in the first place. - -If you \e{really} want financial security, see if you can find a -security engineer who will take financial responsibility for the -correctness of their review. (This might be less likely to suffer -from the everything-failing-at-once problem mentioned above, because -such an engineer would probably be reviewing a lot of \e{different} -products which would tend to fail independently.) Failing that, see -if you can persuade an insurance company to insure you against -security incidents, and if the insurer demands it as a condition -then get our code reviewed by a security engineer they're happy -with. - -\S{faq-permission-form}{Question} Can you sign this form granting us -permission to use/distribute PuTTY? - -If your form contains any clause along the lines of \q{the -undersigned represents and warrants}, we're not going to sign it. -This is particularly true if it asks us to warrant that PuTTY is -secure; see \k{faq-indemnity} for more discussion of this. But it -doesn't really matter what we're supposed to be warranting: even if -it's something we already believe is true, such as that we don't -infringe any third-party copyright, we will not sign a document -accepting any legal or financial liability. This is simply because -the PuTTY development project has no income out of which to satisfy -that liability, or pay legal costs, should it become necessary. We -cannot afford to be sued. We are assuring you that \e{we have done -our best}; if that isn't good enough for you, tough. - -The existing PuTTY licence document already gives you permission to -use or distribute PuTTY in pretty much any way which does not -involve pretending you wrote it or suing us if it goes wrong. We -think that really ought to be enough for anybody. - -See also \k{faq-permission-general} for another reason why we don't -want to do this sort of thing. - -\S{faq-permission-future}{Question} Can you write us a formal notice -of permission to use PuTTY? - -We could, in principle, but it isn't clear what use it would be. If -you think there's a serious chance of one of the PuTTY copyright -holders suing you (which we don't!), you would presumably want a -signed notice from \e{all} of them; and we couldn't provide that -even if we wanted to, because many of the copyright holders are -people who contributed some code in the past and with whom we -subsequently lost contact. Therefore the best we would be able to do -\e{even in theory} would be to have the core development team sign -the document, which wouldn't guarantee you that some other copyright -holder might not sue. - -See also \k{faq-permission-general} for another reason why we don't -want to do this sort of thing. - -\S{faq-permission-general}{Question} Can you sign \e{anything} for -us? - -Not unless there's an incredibly good reason. - -We are generally unwilling to set a precedent that involves us -having to enter into individual agreements with PuTTY users. We -estimate that we have literally \e{millions} of users, and we -absolutely would not have time to go round signing specific -agreements with every one of them. So if you want us to sign -something specific for you, you might usefully stop to consider -whether there's anything special that distinguishes you from 999,999 -other users, and therefore any reason we should be willing to sign -something for you without it setting such a precedent. - -If your company policy requires you to have an individual agreement -with the supplier of any software you use, then your company policy -is simply not well suited to using popular free software, and we -urge you to consider this as a flaw in your policy. - -\S{faq-permission-assurance}{Question} If you won't sign anything, -can you give us some sort of assurance that you won't make PuTTY -closed-source in future? - -Yes and no. - -If what you want is an assurance that some \e{current version} of -PuTTY which you've already downloaded will remain free, then you -already have that assurance: it's called the PuTTY Licence. It -grants you permission to use, distribute and copy the software to -which it applies; once we've granted that permission (which we -have), we can't just revoke it. - -On the other hand, if you want an assurance that \e{future} versions -of PuTTY won't be closed-source, that's more difficult. We could in -principle sign a document stating that we would never release a -closed-source PuTTY, but that wouldn't assure you that we \e{would} -keep releasing \e{open}-source PuTTYs: we would still have the -option of ceasing to develop PuTTY at all, which would surely be -even worse for you than making it closed-source! (And we almost -certainly wouldn't \e{want} to sign a document guaranteeing that we -would actually continue to do development work on PuTTY; we -certainly wouldn't sign it for free. Documents like that are called -contracts of employment, and are generally not signed except in -return for a sizeable salary.) - -If we \e{were} to stop developing PuTTY, or to decide to make all -future releases closed-source, then you would still be free to copy -the last open release in accordance with the current licence, and in -particular you could start your own fork of the project from that -release. If this happened, I confidently predict that \e{somebody} -would do that, and that some kind of a free PuTTY would continue to -be developed. There's already precedent for that sort of thing -happening in free software. We can't guarantee that somebody -\e{other than you} would do it, of course; you might have to do it -yourself. But we can assure you that there would be nothing -\e{preventing} anyone from continuing free development if we -stopped. - -(Finally, we can also confidently predict that if we made PuTTY -closed-source and someone made an open-source fork, most people -would switch to the latter. Therefore, it would be pretty stupid of -us to try it.) - -\S{faq-export-cert}{Question} Can you provide us with export control -information / FIPS certification for PuTTY? - -Some people have asked us for an Export Control Classification Number -(ECCN) for PuTTY. We don't know whether we have one, and as a team of -free software developers based in the UK we don't have the time, -money, or effort to deal with US bureaucracy to investigate any -further. We believe that PuTTY falls under 5D002 on the US Commerce -Control List, but that shouldn't be taken as definitive. If you need -to know more you should seek professional legal advice. The same -applies to any other country's legal requirements and restrictions. - -Similarly, some people have asked us for FIPS certification of the -PuTTY tools. Unless someone else is prepared to do the necessary work -and pay any costs, we can't provide this. - -\S{faq-vendor}{Question} As one of our existing software vendors, can -you just fill in this questionnaire for us? - -We periodically receive requests like this, from organisations which -have apparently sent out a form letter to everyone listed in their big -spreadsheet of \q{software vendors} requiring them all to answer some -long list of questions about supported OS versions, paid support -arrangements, compliance with assorted local regulations we haven't -heard of, contact phone numbers, and other such administrivia. Many of -the questions are obviously meaningless when applied to PuTTY (we -don't provide any paid support in the first place!), most of the rest -could have been answered with only a very quick look at our website, -and some we are actively unwilling to answer (we are private -individuals, why would we want to give out our home phone numbers to -large corporations?). - -We don't make a habit of responding in full to these questionnaires, -because \e{we are not a software vendor}. - -A software \e{vendor} is a company to which you are paying lots of -money in return for some software. They know who you are, and they -know you're paying them money; so they have an incentive to fill in -your forms and questionnaires, to research any local regulations you -cite if they don't already know about them, and generally to provide -every scrap of information you might possibly need in the most -convenient manner for you, because they want to keep being paid. - -But we are a team of free software developers, and that means your -relationship with us is nothing like that at all. If you once -downloaded our software from our website, that's great and we hope you -found it useful, but it doesn't mean we have the least idea who you -are, or any incentive to do lots of unpaid work to support our -\q{relationship} with you. - -It's not that we are unwilling to \e{provide information}. We put as -much of it as we can on our website for your convenience, and if you -actually need to know some fact about PuTTY which you haven't been -able to find on the website (and which is not obviously inapplicable -to free software in the first place) then please do ask us, and we'll -try to answer as best we can. But we put up the website and this FAQ -precisely so that we \e{don't} have to keep answering the same -questions over and over again, so we aren't prepared to fill in -completely generic form-letter questionnaires for people who haven't -done their best to find the answers here first. - -If you work for an organisation which you think might be at risk of -making this mistake, we urge you to reorganise your list of software -suppliers so that it clearly distinguishes paid vendors who know about -you from free software developers who don't have any idea who you are. -Then, only send out these mass mailings to the former. - -\S{faq-checksums}{Question} The \c{sha1sums} / \c{sha256sums} / etc -files on your download page don't match the binaries. - -People report this every so often, and usually the reason turns out to -be that they've matched up the wrong checksums file with the wrong -binaries. - -The PuTTY download page contains more than one version of the -software. There's a \e{latest release} version; there are the -\e{development snapshots}; and when we're in the run-up to making a -release, there are also \e{pre-release} builds of the upcoming new -version. Each one has its own collection of binaries, and its own -collection of checksums files to go with them. - -So if you've downloaded the release version of the actual program, you -need the release version of the checksums too, otherwise you will see -a mismatch. Similarly, the development snapshot binaries go with the -development snapshot checksums, and so on. (We've colour-coded the -download page in an effort to reduce this confusion a bit.) - -Another thing to watch out for: as of 0.71, executables like -\c{putty.exe} come in two flavours for each platform: the standalone -versions on the website, each of which contains embedded help, and the -versions installed by the installer, which use a separate help file -also in the installer. We provide checksums for both; the latter are -indicated with \cq{(installer version)} after the filename. - -If you have double-checked all that, and you still think there's a real -mismatch, then please send us a report carefully quoting everything -relevant: - -\b the exact URL you got your binary from - -\b the checksum of the binary after you downloaded - -\b the exact URL you got your checksums file from - -\b the checksum that file says the binary should have. - -\H{faq-misc} Miscellaneous questions - -\S{faq-openssh}{Question} Is PuTTY a port of \i{OpenSSH}, or based on -OpenSSH or OpenSSL? - -No, it isn't. PuTTY is almost completely composed of code written -from scratch for PuTTY. The only code we share with OpenSSH is the -detector for SSH-1 CRC compensation attacks, written by CORE SDI -S.A; we share no code at all with OpenSSL. - -\S{faq-sillyputty}{Question} Where can I buy silly putty? - -You're looking at the wrong web site; the only PuTTY we know about -here is the name of a computer program. - -If you want the kind of putty you can buy as an executive toy, the -PuTTY team can personally recommend Thinking Putty, which you can -buy from Crazy Aaron's Putty World, at -\W{http://www.puttyworld.com}\cw{www.puttyworld.com}. - -\S{faq-meaning}{Question} What does \q{PuTTY} mean? - -It's the name of a popular SSH and Telnet client. Any other meaning -is in the eye of the beholder. It's been rumoured that \q{PuTTY} -is the antonym of \q{\cw{getty}}, or that it's the stuff that makes your -Windows useful, or that it's a kind of plutonium Teletype. We -couldn't possibly comment on such allegations. - -\S{faq-pronounce}{Question} How do I pronounce \q{PuTTY}? - -Exactly like the English word \q{putty}, which we pronounce -/\u02C8{'}p\u028C{V}ti/. diff --git a/doc/feedback.but b/doc/feedback.but deleted file mode 100644 index c459e49dd..000000000 --- a/doc/feedback.but +++ /dev/null @@ -1,468 +0,0 @@ -\A{feedback} \ii{Feedback} and \i{bug reporting} - -This is a guide to providing feedback to the PuTTY development team. -It is provided as both a web page on the PuTTY site, and an appendix -in the PuTTY manual. - -\K{feedback-general} gives some general guidelines for sending any -kind of e-mail to the development team. Following sections give more -specific guidelines for particular types of e-mail, such as bug -reports and feature requests. - -\H{feedback-general} General guidelines - -The PuTTY development team gets a \e{lot} of mail. If you can -possibly solve your own problem by reading the manual, reading the -FAQ, reading the web site, asking a fellow user, or some other -means, then it would make our lives much easier. - -We get so much e-mail that we literally do not have time to answer -it all. We regret this, but there's nothing we can do about it. So -if you can \e{possibly} avoid sending mail to the PuTTY team, we -recommend you do so. In particular, support requests -(\k{feedback-support}) are probably better sent to some public -forum, or passed to a local expert if possible. - -The PuTTY contact email address is a private \i{mailing list} containing -four or five core developers. Don't be put off by it being a mailing -list: if you need to send confidential data as part of a bug report, -you can trust the people on the list to respect that confidence. -Also, the archives aren't publicly available, so you shouldn't be -letting yourself in for any spam by sending us mail. - -Please use a meaningful subject line on your message. We get a lot of -mail, and it's hard to find the message we're looking for if they all -have subject lines like \q{PuTTY bug}. - -\S{feedback-largefiles} Sending large attachments - -Since the PuTTY contact address is a mailing list, e-mails larger -than 40Kb will be held for inspection by the list administrator, and -will not be allowed through unless they really appear to be worth -their large size. - -If you are considering sending any kind of large data file to the -PuTTY team, it's almost always a bad idea, or at the very least it -would be better to ask us first whether we actually need the file. -Alternatively, you could put the file on a web site and just send us -the URL; that way, we don't have to download it unless we decide we -actually need it, and only one of us needs to download it instead of -it being automatically copied to all the developers. - -(If the file contains confidential information, then you could encrypt -it with our Secure Contact Key; see \k{pgpkeys-pubkey} for details. -Please \e{only} use this for information that \e{needs} to be -confidential.) - -Some people like to send mail in MS Word format. Please \e{don't} -send us bug reports, or any other mail, as a Word document. Word -documents are roughly fifty times larger than writing the same -report in plain text. In addition, most of the PuTTY team read their -e-mail on Unix machines, so copying the file to a Windows box to run -Word is very inconvenient. Not only that, but several of us don't -even \e{have} a copy of Word! - -Some people like to send us screen shots when demonstrating a -problem. Please don't do this without checking with us first - we -almost never actually need the information in the screen shot. -Sending a screen shot of an error box is almost certainly -unnecessary when you could just tell us in plain text what the error -was. (On some versions of Windows, pressing Ctrl-C when the error -box is displayed will copy the text of the message to the clipboard.) -Sending a full-screen shot is \e{occasionally} useful, but it's -probably still wise to check whether we need it before sending it. - -If you \e{must} mail a screen shot, don't send it as a \cw{.BMP} -file. \cw{BMP}s have no compression and they are \e{much} larger -than other image formats such as PNG, TIFF and GIF. Convert the file -to a properly compressed image format before sending it. - -Please don't mail us executables, at all. Our mail server blocks all -incoming e-mail containing executables, as a defence against the -vast numbers of e-mail viruses we receive every day. If you mail us -an executable, it will just bounce. - -If you have made a tiny modification to the PuTTY code, please send -us a \e{patch} to the source code if possible, rather than sending -us a huge \cw{.ZIP} file containing the complete sources plus your -modification. If you've only changed 10 lines, we'd prefer to -receive a mail that's 30 lines long than one containing multiple -megabytes of data we already have. - -\# \S{feedback-other-fora} Other places to ask for help - -\H{feedback-bugs} Reporting bugs - -If you think you have found a bug in PuTTY, your first steps should -be: - -\b Check the -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist -page} on the PuTTY website, and see if we already know about the -problem. If we do, it is almost certainly not necessary to mail us -about it, unless you think you have extra information that might be -helpful to us in fixing it. (Of course, if we actually \e{need} -specific extra information about a particular bug, the Wishlist page -will say so.) - -\b Check the -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change -Log} on the PuTTY website, and see if we have already fixed the bug -in the \i{development snapshots}. - -\b Check the -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/faq.html}{FAQ} -on the PuTTY website (also provided as \k{faq} in the manual), and -see if it answers your question. The FAQ lists the most common -things which people think are bugs, but which aren't bugs. - -\b Download the latest development snapshot and see if the problem -still happens with that. This really is worth doing. As a general -rule we aren't very interested in bugs that appear in the release -version but not in the development version, because that usually -means they are bugs we have \e{already fixed}. On the other hand, if -you can find a bug in the development version that doesn't appear in -the release, that's likely to be a new bug we've introduced since -the release and we're definitely interested in it. - -If none of those options solved your problem, and you still need to -report a bug to us, it is useful if you include some general -information: - -\b Tell us what \i{version of PuTTY} you are running. To find this out, -use the \q{About PuTTY} option from the System menu. Please \e{do -not} just tell us \q{I'm running the latest version}; e-mail can be -delayed and it may not be obvious which version was the latest at -the time you sent the message. - -\b PuTTY is a multi-platform application; tell us what version of what -OS you are running PuTTY on. (If you're running on Unix, or Windows -for Arm, tell us, or we'll assume you're running on Windows for -Intel as this is overwhelmingly the case.) - -\b Tell us what protocol you are connecting with: SSH, Telnet, -Rlogin, SUPDUP, or Raw mode, or a serial connection. - -\b Tell us what kind of server you are connecting to; what OS, and -if possible what SSH server (if you're using SSH). You can get some -of this information from the PuTTY Event Log (see \k{using-eventlog} -in the manual). - -\b Send us the contents of the PuTTY Event Log, unless you -have a specific reason not to (for example, if it contains -confidential information that you think we should be able to solve -your problem without needing to know). - -\b Try to give us as much information as you can to help us -see the problem for ourselves. If possible, give us a step-by-step -sequence of \e{precise} instructions for reproducing the fault. - -\b Don't just tell us that PuTTY \q{does the wrong thing}; tell us -exactly and precisely what it did, and also tell us exactly and -precisely what you think it should have done instead. Some people -tell us PuTTY does the wrong thing, and it turns out that it was -doing the right thing and their expectations were wrong. Help to -avoid this problem by telling us exactly what you think it should -have done, and exactly what it did do. - -\b If you think you can, you're welcome to try to fix the problem -yourself. A \i{patch} to the code which fixes a bug is an excellent -addition to a bug report. However, a patch is never a \e{substitute} -for a good bug report; if your patch is wrong or inappropriate, and -you haven't supplied us with full information about the actual bug, -then we won't be able to find a better solution. - -\b -\W{https://www.chiark.greenend.org.uk/~sgtatham/bugs.html}\cw{https://www.chiark.greenend.org.uk/~sgtatham/bugs.html} -is an article on how to report bugs effectively in general. If your -bug report is \e{particularly} unclear, we may ask you to go away, -read this article, and then report the bug again. - -It is reasonable to report bugs in PuTTY's documentation, if you -think the documentation is unclear or unhelpful. But we do need to -be given exact details of \e{what} you think the documentation has -failed to tell you, or \e{how} you think it could be made clearer. -If your problem is simply that you don't \e{understand} the -documentation, we suggest asking around and seeing if someone -will explain what you need to know. \e{Then}, if you think the -documentation could usefully have told you that, send us a bug -report and explain how you think we should change it. - -\H{feedback-vulns} Reporting security vulnerabilities - -If you've found a security vulnerability in PuTTY, you might well want -to notify us using an encrypted communications channel, to avoid -disclosing information about the vulnerability before a fixed release -is available. - -For this purpose, we provide a GPG key suitable for encryption: the -Secure Contact Key. See \k{pgpkeys-pubkey} for details of this. - -(Of course, vulnerabilities are also bugs, so please do include as -much information as possible about them, the same way you would with -any other bug report.) - -\H{feedback-features} Requesting extra features - -If you want to request a new feature in PuTTY, the very first things -you should do are: - -\b Check the -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/}{Wishlist -page} on the PuTTY website, and see if your feature is already on -the list. If it is, it probably won't achieve very much to repeat -the request. (But see \k{feedback-feature-priority} if you want to -persuade us to give your particular feature higher priority.) - -\b Check the Wishlist and -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/changes.html}{Change -Log} on the PuTTY website, and see if we have already added your -feature in the development snapshots. If it isn't clear, download -the latest development snapshot and see if the feature is present. -If it is, then it will also be in the next release and there is no -need to mail us at all. - -If you can't find your feature in either the development snapshots -\e{or} the Wishlist, then you probably do need to submit a feature -request. Since the PuTTY authors are very busy, it helps if you try -to do some of the work for us: - -\b Do as much of the design as you can. Think about \q{corner -cases}; think about how your feature interacts with other existing -features. Think about the user interface; if you can't come up with -a simple and intuitive interface to your feature, you shouldn't be -surprised if we can't either. Always imagine whether it's possible -for there to be more than one, or less than one, of something you'd -assumed there would be one of. (For example, if you were to want -PuTTY to put an icon in the System tray rather than the Taskbar, you -should think about what happens if there's more than one PuTTY -active; how would the user tell which was which?) - -\b If you can program, it may be worth offering to write the feature -yourself and send us a patch. However, it is likely to be helpful -if you confer with us first; there may be design issues you haven't -thought of, or we may be about to make big changes to the code which -your patch would clash with, or something. If you check with the -maintainers first, there is a better chance of your code actually -being usable. Also, read the design principles listed in \k{udp}: if -you do not conform to them, we will probably not be able to accept -your patch. - -\H{feedback-feature-priority} Requesting features that have already -been requested - -If a feature is already listed on the Wishlist, then it usually -means we would like to add it to PuTTY at some point. However, this -may not be in the near future. If there's a feature on the Wishlist -which you would like to see in the \e{near} future, there are -several things you can do to try to increase its priority level: - -\b Mail us and vote for it. (Be sure to mention that you've seen it -on the Wishlist, or we might think you haven't even \e{read} the -Wishlist). This probably won't have very \e{much} effect; if a huge -number of people vote for something then it may make a difference, -but one or two extra votes for a particular feature are unlikely to -change our priority list immediately. Offering a new and compelling -justification might help. Also, don't expect a reply. - -\b Offer us money if we do the work sooner rather than later. This -sometimes works, but not always. The PuTTY team all have full-time -jobs and we're doing all of this work in our free time; we may -sometimes be willing to give up some more of our free time in -exchange for some money, but if you try to bribe us for a \e{big} -feature it's entirely possible that we simply won't have the time to -spare - whether you pay us or not. (Also, we don't accept bribes to -add \e{bad} features to the Wishlist, because our desire to provide -high-quality software to the users comes first.) - -\b Offer to help us write the code. This is probably the \e{only} -way to get a feature implemented quickly, if it's a big one that we -don't have time to do ourselves. - -\H{feedback-workarounds} Workarounds for SSH server bugs - -It's normal for SSH implementations to automatically enable -workarounds for each other's bugs, using the software version strings -that are exchanged at the start of the connection. Typically an SSH -client will have a list of server version strings that it believes to -have particular bugs, and auto-enable the appropriate set of -workarounds when it sees one of those strings. (And servers will have -a similar list of workarounds for \e{client} software they believe to -be buggy.) - -If you've found a bug in an SSH server, and you'd like us to add an -auto-detected workaround for it, our policy is that \s{the server -implementor should fix it first}. - -If the server implementor has fixed it in the latest version, and can -give us a complete description of the version strings that go with the -bug, then we're happy to use those version strings as a trigger to -automatically enable our workaround (assuming one is possible). We -\e{won't} accept requests to auto-enable workarounds for an open-ended -set of version strings, such as \q{any version of FooServer, including -future ones not yet released}. - -The aim of this policy is to encourage implementors to gradually -converge on the actual standardised SSH protocol. If we enable people -to continue violating the spec, by installing open-ended workarounds -in PuTTY for bugs they're never going to fix, then we're contributing -to an ecosystem in which everyone carries on having bugs and everyone -else carries on having to work around them. - -An exception: if an SSH server is no longer maintained \e{at all} -(e.g. the company that produced it has gone out of business), and -every version of it that was ever released has a bug, then that's one -situation in which we may be prepared to add a workaround rule that -matches all versions of that software. (The aim is to stop -implementors from continuing to release software with the bug \dash -and if they're not releasing it \e{at all} any more, then that's -already done!) - -We do recognise that sometimes it will be difficult to get the server -maintainer to fix a bug, or even to answer support requests at all. Or -it might take them a very long time to get round to doing anything -about it. We're not completely unwilling to compromise: we're prepared -to add \e{manually enabled} workarounds to PuTTY even for bugs that an -implementation hasn't fixed yet. We just won't \e{automatically} -enable the workaround unless the server maintainer has also done their -part. - -\H{feedback-support} \ii{Support requests} - -If you're trying to make PuTTY do something for you and it isn't -working, but you're not sure whether it's a bug or not, then -\e{please} consider looking for help somewhere else. This is one of -the most common types of mail the PuTTY team receives, and we simply -don't have time to answer all the questions. Questions of this type -include: - -\b If you want to do something with PuTTY but have no idea where to -start, and reading the manual hasn't helped, try posting to a -public forum and see if someone can explain it to you. - -\b If you have tried to do something with PuTTY but it hasn't -worked, and you aren't sure whether it's a bug in PuTTY or a bug in -your SSH server or simply that you're not doing it right, then try -posting to some public forum and see if someone can solve your -problem. Or try doing the same thing with a different SSH client -and see if it works with that. Please do not report it as a PuTTY -bug unless you are really sure it \e{is} a bug in PuTTY. - -\b If someone else installed PuTTY for you, or you're using PuTTY on -someone else's computer, try asking them for help first. They're more -likely to understand how they installed it and what they expected you -to use it for than we are. - -\b If you have successfully made a connection to your server and now -need to know what to type at the server's command prompt, or other -details of how to use the server-end software, talk to your server's -system administrator. This is not the PuTTY team's problem. PuTTY is -only a communications tool, like a telephone; if you can't speak the -same language as the person at the other end of the phone, it isn't -the telephone company's job to teach it to you. - -If you absolutely cannot get a support question answered any other -way, you can try mailing it to us, but we can't guarantee to have -time to answer it. - -\H{feedback-webadmin} Web server administration - -If the PuTTY \i{web site} is down (Connection Timed Out), please don't -bother mailing us to tell us about it. Most of us read our e-mail on -the same machines that host the web site, so if those machines are -down then we will notice \e{before} we read our e-mail. So there's -no point telling us our servers are down. - -Of course, if the web site has some other error (Connection Refused, -404 Not Found, 403 Forbidden, or something else) then we might -\e{not} have noticed and it might still be worth telling us about it. - -If you want to report a problem with our web site, check that you're -looking at our \e{real} web site and not a mirror. The real web site -is at -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\c{https://www.chiark.greenend.org.uk/~sgtatham/putty/}; -if that's not where you're reading this, then don't report the -problem to us until you've checked that it's really a problem with -the main site. If it's only a problem with the mirror, you should -try to contact the administrator of that mirror site first, and only -contact us if that doesn't solve the problem (in case we need to -remove the mirror from our list). - -\H{feedback-permission} Asking permission for things - -PuTTY is distributed under the MIT Licence (see \k{licence} for -details). This means you can do almost \e{anything} you like with -our software, our source code, and our documentation. The only -things you aren't allowed to do are to remove our copyright notices -or the licence text itself, or to hold us legally responsible if -something goes wrong. - -So if you want permission to include PuTTY on a magazine cover disk, -or as part of a collection of useful software on a CD or a web site, -then \e{permission is already granted}. You don't have to mail us -and ask. Just go ahead and do it. We don't mind. - -(If you want to distribute PuTTY alongside your own application for -use with that application, or if you want to distribute PuTTY within -your own organisation, then we recommend, but do not insist, that -you offer your own first-line technical support, to answer questions -about the interaction of PuTTY with your environment. If your users -mail us directly, we won't be able to tell them anything useful about -your specific setup.) - -If you want to use parts of the PuTTY source code in another -program, then it might be worth mailing us to talk about technical -details, but if all you want is to ask permission then you don't -need to bother. You already have permission. - -If you just want to link to our web site, just go ahead. (It's not -clear that we \e{could} stop you doing this, even if we wanted to!) - -\H{feedback-mirrors} Mirroring the PuTTY web site - -\# the next two paragraphs also on the Mirrors page itself, with -\# minor context changes - -If you want to set up a mirror of the PuTTY website, go ahead and -set one up. Please don't bother asking us for permission before -setting up a mirror. You already have permission. - -If the mirror is in a country where we don't already have plenty of -mirrors, we may be willing to add it to the list on our -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html}{mirrors -page}. Read the guidelines on that page, make sure your mirror -works, and email us the information listed at the bottom of the -page. - -Note that we do not \e{promise} to list your mirror: we get a lot of -mirror notifications and yours may not happen to find its way to the -top of the list. - -Also note that we link to all our mirror sites using the -\c{rel="nofollow"} attribute. Running a PuTTY mirror is not intended -to be a cheap way to gain search rankings. - -If you have technical questions about the process of mirroring, then -you might want to mail us before setting up the mirror (see also the -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/mirrors.html#guidelines}{guidelines on the Mirrors page}); -but if you just want to ask for permission, you don't need to. You -already have permission. - -\H{feedback-compliments} Praise and compliments - -One of the most rewarding things about maintaining free software is -getting e-mails that just say \q{thanks}. We are always happy to -receive e-mails of this type. - -Regrettably we don't have time to answer them all in person. If you -mail us a compliment and don't receive a reply, \e{please} don't -think we've ignored you. We did receive it and we were happy about -it; we just didn't have time to tell you so personally. - -To everyone who's ever sent us praise and compliments, in the past -and the future: \e{you're welcome}! - -\H{feedback-address} E-mail address - -The actual address to mail is -\cw{<\W{mailto:putty@projects.tartarus.org}{putty@projects.tartarus.org}>}. diff --git a/doc/gs.but b/doc/gs.but deleted file mode 100644 index 8b915dbfc..000000000 --- a/doc/gs.but +++ /dev/null @@ -1,187 +0,0 @@ -\C{gs} Getting started with PuTTY - -This chapter gives a quick guide to the simplest types of -interactive login session using PuTTY. - -\H{gs-insecure} \ii{Starting a session} - -When you start PuTTY, you will see a \i{dialog box}. This dialog box -allows you to control everything PuTTY can do. See \k{config} for -details of all the things you can control. - -You don't usually need to change most of the configuration options. -To start the simplest kind of session, all you need to do is to -enter a few basic parameters. - -In the \q{Host Name} box, enter the Internet \i{host name} of the server -you want to connect to. You should have been told this by the -provider of your login account. - -Now select a login \i{protocol} to use, from the \q{Connection type} -controls. For a login session, you should select \i{SSH}, \i{Telnet}, -\i{Rlogin}, or \i{SUPDUP}. See \k{which-one} for a description of the -differences between these protocols, and advice on which one to -use. The \I{raw protocol}\e{Raw} protocol is not used for interactive -login sessions; you would usually use this for debugging other Internet -services (see \k{using-rawprot}). The \e{Serial} option is used for -connecting to a local serial line, and works somewhat differently: -see \k{using-serial} for more information on this. -\#{FIXME: describe bare ssh-connection} - -When you change the selected protocol, the number in the \q{Port} -box will change. This is normal: it happens because the various -login services are usually provided on different network ports by -the server machine. Most servers will use the standard port numbers, -so you will not need to change the port setting. If your server -provides login services on a non-standard port, your system -administrator should have told you which one. (For example, many -\i{MUDs} run Telnet service on a port other than 23.) - -Once you have filled in the \q{Host Name}, \q{Connection type}, and -possibly \q{Port} settings, you are ready to connect. Press the -\q{Open} button at the bottom of the dialog box, and PuTTY will -begin trying to connect you to the server. - -\H{gs-hostkey} \ii{Verifying the host key} (SSH only) - -If you are not using the \i{SSH} protocol, you can skip this -section. - -If you are using SSH to connect to a server for the first time, you -will probably see a message looking something like this: - -\c The host key is not cached for this server: -\c ssh.example.com (port 22) -\c You have no guarantee that the server is the computer you think it is. -\c The server's ssh-ed25519 key fingerprint is: -\c ssh-ed25519 255 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w -\c If you trust this host, press "Accept" to add the key to PuTTY's -\c cache and carry on connecting. -\c If you want to carry on connecting just once, without adding the key -\c to the cache, press "Connect Once". -\c If you do not trust this host, press "Cancel" to abandon the connection. - -This is a feature of the SSH protocol. It is designed to protect you -against a network attack known as \i\e{spoofing}: secretly -redirecting your connection to a different computer, so that you -send your password to the wrong machine. Using this technique, an -attacker would be able to learn the password that guards your login -account, and could then log in as if they were you and use the -account for their own purposes. - -To prevent this attack, each server has a unique identifying code, -called a \e{host key}. These keys are created in a way that prevents -one server from forging another server's key. So if you connect to a -server and it sends you a different host key from the one you were -expecting, PuTTY can warn you that the server may have been switched -and that a spoofing attack might be in progress. - -PuTTY \I{host key cache}records the host key for each server you -connect to, in the Windows \i{Registry}. Every time you connect to a -server, it checks that the host key presented by the server is the -same host key as it was the last time you connected. If it is not, -you will see a stronger warning, and you will have the chance to -abandon your connection before you type any private information (such -as a password) into it. (See \k{errors-hostkey-wrong} for what that -looks like.) - -However, when you connect to a server you have not connected to -before, PuTTY has no way of telling whether the host key is the -right one or not. So it gives the warning shown above, and asks you -whether you want to \I{trusting host keys}trust this host key or -not. - -Whether or not to trust the host key is your choice. If you are -connecting within a company network, you might feel that all the -network users are on the same side and spoofing attacks are -unlikely, so you might choose to trust the key without checking it. -If you are connecting across a hostile network (such as the -Internet), you should check with your system administrator, perhaps -by telephone or in person. (When verifying the fingerprint, be careful -with letters and numbers that can be confused with each other: -\c{0}/\c{O}, \c{1}/\c{I}/\c{l}, and so on.) - -Many servers have more than one host key. If the system administrator -sends you more than one \I{host key fingerprint}fingerprint, you should -make sure the one PuTTY shows you is on the list, but it doesn't matter -which one it is. - -If you don't have any fingerprints that look like the example -(\I{SHA256 fingerprint}\c{SHA256:} followed by a long string of -characters), but instead have pairs of characters separated by colons -like \c{a4:db:96:a7:...}, try pressing the \q{More info...} button and -see if you have a fingerprint matching the \q{\i{MD5 fingerprint}} -there. This is an older and less secure way to summarise the same -underlying host key; it's possible for an attacker to create their -own host key with the same fingerprint; so you should avoid relying on -this fingerprint format unless you have no choice. The -\q{More info...} dialog box also shows the full host public key, in -case that is easier to compare than a fingerprint. - -See \k{config-ssh-hostkey} for advanced options for managing host keys. - -\# FIXME: this is all very fine but of course in practice the world -doesn't work that way. Ask the team if they have any good ideas for -changes to this section! - -\H{gs-login} \ii{Logging in} - -After you have connected, and perhaps verified the server's host -key, you will be asked to log in, probably using a \i{username} and -a \i{password}. Your system administrator should have provided you -with these. (If, instead, your system administrator has asked you to -provide, or provided you with, a \q{public key} or \q{key file}, see -\k{pubkey}.) - -PuTTY will display a text window (the \q{\i{terminal window}} \dash it -will have a black background unless you've changed the defaults), and -prompt you to type your username and password into that window. (These -prompts will include the \i{PuTTY icon}, to distinguish them from any -text sent by the server in the same window.) - -Enter the username and the password, and the server should grant you -access and begin your session. If you have -\I{mistyping a password}mistyped your password, most servers will give -you several chances to get it right. - -While you are typing your password, you will not usually see the -cursor moving in the window, but PuTTY \e{is} registering what you -type, and will send it when you press Return. (It works this way to -avoid revealing the length of your password to anyone watching your -screen.) - -If you are using SSH, be careful not to type your username wrongly, -because you will not have a chance to correct it after you press -Return; many SSH servers do not permit you to make two login attempts -using \i{different usernames}. If you type your username wrongly, you -must close PuTTY and start again. - -If your password is refused but you are sure you have typed it -correctly, check that Caps Lock is not enabled. Many login servers, -particularly Unix computers, treat upper case and lower case as -different when checking your password; so if Caps Lock is on, your -password will probably be refused. - -\H{gs-session} After logging in - -After you log in to the server, what happens next is up to the -server! Most servers will print some sort of login message and then -present a \i{prompt}, at which you can type -\I{commands on the server}commands which the -server will carry out. Some servers will offer you on-line help; -others might not. If you are in doubt about what to do next, consult -your system administrator. - -\H{gs-logout} \ii{Logging out} - -When you have finished your session, you should log out by typing -the server's own logout command. This might vary between servers; if -in doubt, try \c{logout} or \c{exit}, or consult a manual or your -system administrator. When the server processes your logout command, -the PuTTY window should close itself automatically. - -You \e{can} close a PuTTY session using the \i{Close button} in the -window border, but this might confuse the server - a bit like -hanging up a telephone unexpectedly in the middle of a conversation. -We recommend you do not do this unless the server has stopped -responding to you and you cannot close the window any other way. diff --git a/doc/index.but b/doc/index.but deleted file mode 100644 index 86c6f931c..000000000 --- a/doc/index.but +++ /dev/null @@ -1,971 +0,0 @@ -\IM{Unix version} Unix version of PuTTY tools -\IM{Unix version} Linux version of PuTTY tools - -\IM{Unix} Unix -\IM{Unix} Linux - -\IM{Command Prompt}{command prompt window}{MS-DOS Prompt}{console window} Command Prompt -\IM{Command Prompt}{command prompt window}{MS-DOS Prompt}{console window} MS-DOS Prompt -\IM{Command Prompt}{command prompt window}{MS-DOS Prompt}{console window} console window - -\IM{spoof}{spoofed}{spoofing} spoofing - -\IM{verifying the host key} verifying the host key -\IM{verifying the host key} host key, verifying - -\IM{trusting host keys} trusting host keys -\IM{trusting host keys} host keys, trusting - -\IM{host key fingerprint} fingerprint, of SSH host key -\IM{host key fingerprint} host key fingerprint (SSH) -\IM{host key fingerprint} SSH host key fingerprint - -\IM{MD5 fingerprint} MD5 fingerprint, of SSH host key -\IM{MD5 fingerprint} fingerprint, MD5, of SSH host key - -\IM{SHA256 fingerprint} SHA-256 fingerprint, of SSH host key -\IM{SHA256 fingerprint} fingerprint, SHA-256, of SSH host key - -\IM{manually configuring host keys} manually configuring host keys -\IM{manually configuring host keys} overriding host keys -\IM{manually configuring host keys} host keys, manually configuring - -\IM{starting a session} starting a session -\IM{starting a session} session, starting - -\IM{commands on the server}{remote command} commands on the server -\IM{commands on the server}{remote command} remote commands -\IM{commands on the server}{remote command} server, commands on - -\IM{mistyping a password} mistyping a password -\IM{mistyping a password} password, mistyping - -\IM{different usernames}{changes of username} different user names -\IM{different usernames}{changes of username} changing user names -\IM{different usernames}{changes of username} user names, different -\IM{different usernames}{changes of username} login names, different -\IM{different usernames}{changes of username} account names, different - -\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} differences between -SSH, Telnet, Rlogin, and SUPDUP -\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} protocols, -differences between -\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} SSH, differences -from other protocols -\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} Telnet, differences -from other protocols -\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} Rlogin, differences -from other protocols -\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} SUPDUP, differences -from other protocols -\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} selecting a protocol -\IM{differences between SSH, Telnet, Rlogin, and SUPDUP} choosing a protocol - -\IM{MUD}{MUDs} MUDs - -\IM{talker}{talker systems} talker systems - -\IM{security hazard}{security risk} security hazard - -\IM{SSH-2}{SSH protocol version 2} SSH-2 - -\IM{terminal window}{PuTTY window} terminal window -\IM{terminal window}{PuTTY window} PuTTY terminal window -\IM{terminal window}{PuTTY window} window, terminal - -\IM{copy and paste} copy and paste -\IM{copy and paste} cut and paste -\IM{copy and paste} paste, copy and - -\IM{three-button mouse} three-button mouse -\IM{three-button mouse} mouse, three-button - -\IM{left mouse button}{left button} left mouse button -\IM{middle mouse button}{middle button}{middle-clicking} middle mouse button -\IM{right mouse button}{right button} right mouse button - -\IM{selecting words}{word-by-word selection} selecting whole words -\IM{selecting words}{word-by-word selection} words, selecting - -\IM{selecting lines} selecting whole lines -\IM{selecting lines} lines, selecting - -\IM{rectangular selection} rectangular selection -\IM{rectangular selection} selection, rectangular - -\IM{adjusting a selection} adjusting a selection -\IM{adjusting a selection} extending a selection -\IM{adjusting a selection} selection, adjusting - -\IM{right mouse button, with Ctrl} right mouse button, with Ctrl -\IM{right mouse button, with Ctrl} Ctrl, with right mouse button - -\IM{selections} selections, multiple -\IM{selections} clipboards, multiple - -\IM{PRIMARY} \c{PRIMARY} selection -\IM{PRIMARY} selection, \c{PRIMARY} - -\IM{CLIPBOARD selection} \c{CLIPBOARD} selection -\IM{CLIPBOARD selection} selection, \c{CLIPBOARD} - -\IM{SECONDARY} \c{SECONDARY} selection -\IM{SECONDARY} selection, \c{SECONDARY} - -\IM{system menu} system menu -\IM{system menu} menu, system -\IM{system menu} window menu - -\IM{context menu} context menu -\IM{context menu} menu, context -\IM{context menu} right mouse button menu - -\IM{Event Log} Event Log -\IM{Event Log} PuTTY Event Log -\IM{Event Log} Log, Event - -\IM{Telnet special commands} Telnet special commands -\IM{Telnet special commands} special commands, in Telnet - -\IM{SSH special commands} SSH special commands -\IM{SSH special commands} special commands, in SSH - -\IM{Repeat key exchange, SSH special command} Repeat key exchange, SSH special command -\IM{Repeat key exchange, SSH special command} key exchange, forcing repeat -\IM{Repeat key exchange, SSH special command} SSH key exchange, forcing repeat - -\IM{accented characters} accented characters -\IM{accented characters} characters, accented - -\IM{line-drawing characters} line-drawing characters -\IM{line-drawing characters} box-drawing characters -\IM{line-drawing characters} characters, line-drawing -\IM{line-drawing characters} ANSI graphics - -\IM{port forwarding}{port forwardings} port forwarding in SSH -\IM{port forwarding}{port forwardings} SSH port forwarding -\IM{port forwarding}{port forwardings} forwarding ports in SSH -\IM{port forwarding}{port forwardings} tunnelling using SSH -\IM{port forwarding}{port forwardings} SSH tunnelling - -\IM{port forwarding, changing mid-session} port forwarding in SSH, changing mid-session -\IM{port forwarding, changing mid-session} SSH port forwarding, changing mid-session -\IM{port forwarding, changing mid-session} forwarding ports in SSH, changing mid-session -\IM{port forwarding, changing mid-session} tunnelling using SSH, changing mid-session -\IM{port forwarding, changing mid-session} SSH tunnelling, changing mid-session - -\IM{local port forwarding} local-to-remote port forwarding -\IM{remote port forwarding} remote-to-local port forwarding - -\IM{dynamic port forwarding} dynamic port forwarding -\IM{dynamic port forwarding} SOCKS port forwarding - -\IM{debugging Internet protocols} debugging Internet protocols -\IM{debugging Internet protocols} Internet protocols, debugging -\IM{debugging Internet protocols} protocols, debugging - -\IM{Internet protocol version} Internet Protocol version -\IM{Internet protocol version} version, of Internet Protocol - -\IM{raw TCP connections} raw TCP connections -\IM{raw TCP connections} TCP connections, raw - -\IM{command-line arguments} command-line arguments -\IM{command-line arguments} arguments, command-line -\IM{command-line arguments} options, command-line -\IM{command-line arguments} switches, command-line - -\IM{Windows shortcut} Windows shortcut -\IM{Windows shortcut} shortcut, Windows - -\IM{telnet URLs} Telnet URLs -\IM{telnet URLs} URLs, Telnet - -\IM{saved sessions, loading from command line} saved sessions, -loading from command line -\IM{saved sessions, loading from command line} loading saved -sessions from command line -\IM{saved sessions, loading from command line} command line, loading -saved sessions from - -\IM{putty @sessionname} \c{putty @sessionname} -\IM{putty @sessionname} \c{@sessionname} command-line argument - -\IM{protocol selection} protocol selection -\IM{protocol selection} selecting a protocol -\IM{protocol selection} choosing a protocol - -\IM{ssh-connection} bare \cw{ssh-connection} protocol -\IM{ssh-connection} \cw{ssh-connection} protocol, bare - -\IM{psusan} \cq{psusan} program - -\IM{login name}{username} login name -\IM{login name}{username} user name -\IM{login name}{username} account name - -\IM{reading commands from a file} reading commands from a file -\IM{reading commands from a file} commands, reading from a file - -\IM{agent forwarding} agent forwarding -\IM{agent forwarding} authentication agent forwarding -\IM{agent forwarding} SSH agent forwarding -\IM{agent forwarding} forwarding, SSH agent - -\IM{X11 forwarding}{forwarding of X11} X11 forwarding -\IM{X11 forwarding}{forwarding of X11} SSH X11 forwarding -\IM{X11 forwarding}{forwarding of X11} forwarding, of X11 - -\IM{X11 authentication} X11 authentication -\IM{X11 authentication} authentication, X11 - -\IM{pseudo-terminal allocation} pseudo-terminal allocation -\IM{pseudo-terminal allocation} pty allocation -\IM{pseudo-terminal allocation} allocation, of pseudo-terminal - -\IM{ERASE special character} \cw{ERASE}, special character -\IM{ERASE special character} \cw{VERASE}, special character -\IM{QUIT special character} \cw{QUIT}, special character -\IM{QUIT special character} \cw{VQUIT}, special character - -\IM{-telnet} \c{-telnet} command-line option -\IM{-raw} \c{-raw} command-line option -\IM{-rlogin} \c{-rlogin} command-line option -\IM{-supdup} \c{-supdup} command-line option -\IM{-ssh} \c{-ssh} command-line option -\IM{-ssh-connection} \c{-ssh-connection} command-line option -\IM{-serial} \c{-serial} command-line option -\IM{-cleanup} \c{-cleanup} command-line option -\IM{-load} \c{-load} command-line option -\IM{-v} \c{-v} command-line option -\IM{-l} \c{-l} command-line option -\IM{-L-upper} \c{-L} command-line option -\IM{-R-upper} \c{-R} command-line option -\IM{-D-upper} \c{-D} command-line option -\IM{-m} \c{-m} command-line option -\IM{-P-upper} \c{-P} command-line option -\IM{-pw} \c{-pw} command-line option -\IM{-pwfile} \c{-pwfile} command-line option -\IM{-A-upper} \c{-A} command-line option -\IM{-a} \c{-a} command-line option -\IM{-X-upper} \c{-X} command-line option -\IM{-x} \c{-x} command-line option -\IM{-T-upper} \c{-T} command-line option -\IM{-t} \c{-t} command-line option -\IM{-C-upper} \c{-C} command-line option -\IM{-N-upper} \c{-N} command-line option -\IM{-1} \c{-1} command-line option -\IM{-2} \c{-2} command-line option -\IM{-i} \c{-i} command-line option -\IM{-pgpfp} \c{-pgpfp} command-line option -\IM{-sercfg} \c{-sercfg} command-line option - -\IM{removing registry entries} removing registry entries -\IM{removing registry entries} registry entries, removing - -\IM{random seed file} random seed file -\IM{random seed file} \c{putty.rnd} (random seed file) - -\IM{putty.rnd} \c{putty.rnd} (random seed file) - -\IM{suppressing remote shell} remote shell, suppressing -\IM{suppressing remote shell} shell, remote, suppressing - -\IM{SSH protocol version} SSH protocol version -\IM{SSH protocol version} protocol version, SSH -\IM{SSH protocol version} version, of SSH protocol - -\IM{PPK} \cw{PPK} file -\IM{PPK} private key file, PuTTY - -\IM{Argon2} Argon2 passphrase hashing function - -\IM{passphrase hashing} passphrase hashing, for private key files -\IM{passphrase hashing} password hashing, for private key files - -\IM{PGP key fingerprint} PGP key fingerprint -\IM{PGP key fingerprint} fingerprint, of PGP key - -\IM{verifying new versions} verifying new versions of PuTTY -\IM{verifying new versions} new version, verifying -\IM{verifying new versions} upgraded version, verifying - -\IM{connection}{network connection} network connection -\IM{connection}{network connection} connection, network - -\IM{host name}{hostname} host name -\IM{host name}{hostname} DNS name -\IM{host name}{hostname} server name - -\IM{IP address}{Internet address} IP address -\IM{IP address}{Internet address} address, IP - -\IM{localhost} \c{localhost} - -\IM{loopback IP address}{loopback address} loopback IP address -\IM{loopback IP address}{loopback address} IP address, loopback - -\IM{listen address} listen address -\IM{listen address} bind address - -\IM{DNS} DNS -\IM{DNS} Domain Name System - -\IM{name resolution} name resolution -\IM{name resolution} DNS resolution -\IM{name resolution} host name resolution -\IM{name resolution} server name resolution - -\IM{loading and storing saved sessions} sessions, loading and storing -\IM{loading and storing saved sessions} settings, loading and storing -\IM{loading and storing saved sessions} saving settings -\IM{loading and storing saved sessions} storing settings -\IM{loading and storing saved sessions} loading settings - -\IM{Default Settings} Default Settings -\IM{Default Settings} settings, default - -\IM{Registry} Registry (Windows) -\IM{Registry} Windows Registry - -\IM{inactive window} inactive window -\IM{inactive window} window, inactive -\IM{inactive window} terminal window, inactive - -\IM{SSH packet log} SSH packet log -\IM{SSH packet log} packet log, SSH - -\IM{auto wrap mode}{auto wrap} auto wrap mode -\IM{auto wrap mode}{auto wrap} wrapping, automatic -\IM{auto wrap mode}{auto wrap} line wrapping, automatic - -\IM{control sequence}{control codes} control sequences -\IM{control sequence}{control codes} terminal control sequences -\IM{control sequence}{control codes} escape sequences - -\IM{cursor coordinates} cursor coordinates -\IM{cursor coordinates} coordinates, cursor - -\IM{CR} CR (Carriage Return) -\IM{CR} Carriage Return - -\IM{LF} LF (Line Feed) -\IM{LF} Line Feed - -\IM{clear screen} clear screen -\IM{clear screen} erase screen -\IM{clear screen} screen, clearing - -\IM{blinking text} blinking text -\IM{blinking text} flashing text - -\IM{answerback} answerback string - -\IM{local echo} local echo -\IM{local echo} echo, local - -\IM{remote echo} remote echo -\IM{remote echo} echo, remote - -\IM{local line editing} local line editing -\IM{local line editing} line editing, local - -\IM{remote-controlled printing} ANSI printing -\IM{remote-controlled printing} remote-controlled printing -\IM{remote-controlled printing} printing, remote-controlled -\IM{remote-controlled printing} passthrough printing - -\IM{Control-H} Control-H -\IM{Control-H} Ctrl-H -\IM{Control-?} Control-? -\IM{Control-?} Ctrl-? - -\IM{Home and End keys} Home key -\IM{Home and End keys} End key - -\IM{keypad} keypad, numeric -\IM{keypad} numeric keypad - -\IM{Application Cursor Keys} Application Cursor Keys -\IM{Application Cursor Keys} cursor keys, \q{Application} mode - -\IM{Application Keypad} Application Keypad -\IM{Application Keypad} keypad, \q{Application} mode -\IM{Application Keypad} numeric keypad, \q{Application} mode - -\IM{Num Lock}{NumLock} Num Lock - -\IM{NetHack keypad mode} NetHack keypad mode -\IM{NetHack keypad mode} keypad, NetHack mode - -\IM{compose key} Compose key -\IM{compose key} DEC Compose key - -\IM{terminal bell} terminal bell -\IM{terminal bell} bell, terminal -\IM{terminal bell} beep, terminal -\IM{terminal bell} feep - -\IM{Windows Default Beep} Windows Default Beep sound -\IM{Windows Default Beep} Default Beep sound, Windows - -\IM{terminal bell, disabling} terminal bell, disabling -\IM{terminal bell, disabling} bell, disabling - -\IM{visual bell} visual bell -\IM{visual bell} bell, visual - -\IM{PC speaker} PC speaker -\IM{PC speaker} beep, with PC speaker - -\IM{sound file} sound file -\IM{sound file} \cw{WAV} file - -\IM{bell overload} bell overload mode -\IM{bell overload} terminal bell overload mode - -\IM{mouse reporting} mouse reporting -\IM{mouse reporting} \c{xterm} mouse reporting - -\IM{links} \c{links} (web browser) - -\IM{mc} \c{mc} -\IM{mc} Midnight Commander - -\IM{terminal resizing}{window resizing} terminal resizing -\IM{terminal resizing}{window resizing} window resizing -\IM{terminal resizing}{window resizing} resizing, terminal - -\IM{destructive backspace} destructive backspace -\IM{destructive backspace} non-destructive backspace -\IM{destructive backspace} backspace, destructive - -\IM{Arabic text shaping} Arabic text shaping -\IM{Arabic text shaping} shaping, of Arabic text - -\IM{Unicode} Unicode -\IM{Unicode} ISO-10646 (Unicode) - -\IM{ASCII} ASCII -\IM{ASCII} US-ASCII - -\IM{bidirectional text} bidirectional text -\IM{bidirectional text} right-to-left text - -\IM{display becomes corrupted} display corruption -\IM{display becomes corrupted} corruption, of display - -\IM{rows} rows, in terminal window -\IM{columns} columns, in terminal window - -\IM{window size} window size -\IM{window size} size, of window - -\IM{font size} font size -\IM{font size} size, of font - -\IM{full screen}{full-screen} full-screen mode - -\IM{cursor blinks} blinking cursor -\IM{cursor blinks} flashing cursor -\IM{cursor blinks} cursor, blinking - -\IM{font} font -\IM{font} typeface - -\IM{minimise} minimise window -\IM{minimise} window, minimising - -\IM{maximise} maximise window -\IM{maximise} window, maximising - -\IM{closing window}{close window} closing window -\IM{closing window}{close window} window, closing - -\IM{Dragon NaturallySpeaking} Dragon NaturallySpeaking -\IM{Dragon NaturallySpeaking} NaturallySpeaking - -\IM{AltGr} \q{AltGr} key -\IM{Alt} \q{Alt} key - -\IM{CJK} CJK -\IM{CJK} Chinese -\IM{CJK} Japanese -\IM{CJK} Korean - -\IM{East Asian Ambiguous characters} East Asian Ambiguous characters -\IM{East Asian Ambiguous characters} CJK ambiguous characters - -\IM{character width} character width -\IM{character width} single-width character -\IM{character width} double-width character - -\IM{Rich Text Format} Rich Text Format -\IM{Rich Text Format} RTF - -\IM{bold}{bold text} bold text - -\IM{colour}{colours} colour - -\IM{8-bit colour} 8-bit colour -\IM{8-bit colour} colour, 8-bit - -\IM{system colours} system colours -\IM{system colours} colours, system - -\IM{ANSI colours} ANSI colours -\IM{ANSI colours} colours, ANSI - -\IM{cursor colour} cursor colour -\IM{cursor colour} colour, of cursor - -\IM{default background} background colour, default -\IM{default background} colour, background, default - -\IM{default foreground} foreground colour, default -\IM{default foreground} colour, foreground, default - -\IM{bold black} bold black -\IM{bold black} black, bold -\IM{bold black} bright black - -\IM{TERM} \cw{TERM} environment variable - -\IM{logical palettes} logical palettes -\IM{logical palettes} palettes, logical - -\IM{breaks in connectivity} connectivity, breaks in -\IM{breaks in connectivity} intermittent connectivity - -\IM{idle connections} idle connections -\IM{idle connections} timeout, of connections -\IM{idle connections} connections, idle - -\IM{interactive connections}{interactive session} interactive connections -\IM{interactive connections}{interactive session} connections, interactive - -\IM{keepalives} keepalives, application - -\IM{Nagle's algorithm} Nagle's algorithm -\IM{Nagle's algorithm} \cw{TCP_NODELAY} - -\IM{TCP keepalives} TCP keepalives -\IM{TCP keepalives} keepalives, TCP -\IM{TCP keepalives} \cw{SO_KEEPALIVE} - -\IM{half-open connections} half-open connections -\IM{half-open connections} connections, half-open - -\IM{auto-login username} user name, for auto-login -\IM{auto-login username} login name, for auto-login -\IM{auto-login username} account name, for auto-login - -\IM{terminal emulation}{terminal-type} terminal emulation -\IM{terminal emulation}{terminal-type} emulation, terminal - -\IM{terminal speed} terminal speed -\IM{terminal speed} speed, terminal -\IM{terminal speed} baud rate, of terminal - -\IM{environment variables} environment variables -\IM{environment variables} variables, environment - -\IM{proxy} proxy server -\IM{proxy} server, proxy - -\IM{HTTP proxy} HTTP proxy -\IM{HTTP proxy} proxy, HTTP -\IM{HTTP proxy} server, HTTP -\IM{HTTP proxy} \cw{CONNECT} proxy (HTTP) - -\IM{SOCKS server} SOCKS proxy -\IM{SOCKS server} server, SOCKS -\IM{SOCKS server} proxy, SOCKS - -\IM{Telnet proxy} Telnet proxy -\IM{Telnet proxy} TCP proxy -\IM{Telnet proxy} ad-hoc proxy -\IM{Telnet proxy} proxy, Telnet - -\IM{Local proxy} local proxy -\IM{Local proxy} proxy command -\IM{Local proxy} command, proxy - -\IM{proxy DNS} proxy DNS -\IM{proxy DNS} DNS, with proxy -\IM{proxy DNS} name resolution, with proxy -\IM{proxy DNS} host name resolution, with proxy -\IM{proxy DNS} server name resolution, with proxy - -\IM{proxy username} proxy user name -\IM{proxy username} user name, for proxy -\IM{proxy username} login name, for proxy -\IM{proxy username} account name, for proxy - -\IM{proxy password} proxy password -\IM{proxy password} password, for proxy - -\IM{proxy authentication} proxy authentication -\IM{proxy authentication} authentication, to proxy - -\IM{HTTP Basic} HTTP Basic authentication -\IM{HTTP Basic} \q{basic} authentication (HTTP) - -\IM{HTTP Digest} HTTP Digest authentication -\IM{HTTP Digest} \q{digest} authentication (HTTP) - -\IM{plaintext password} plain text password -\IM{plaintext password} password, plain text - -\IM{Telnet negotiation} Telnet option negotiation -\IM{Telnet negotiation} option negotiation, Telnet -\IM{Telnet negotiation} negotiation, of Telnet options - -\IM{firewall}{firewalls} firewalls - -\IM{NAT router}{NAT} NAT routers -\IM{NAT router}{NAT} routers, NAT -\IM{NAT router}{NAT} Network Address Translation -\IM{NAT router}{NAT} IP masquerading - -\IM{Telnet New Line} Telnet New Line -\IM{Telnet New Line} new line, in Telnet - -\IM{.rhosts} \c{.rhosts} file -\IM{.rhosts} \q{rhosts} file - -\IM{passwordless login} passwordless login -\IM{passwordless login} login, passwordless - -\IM{Windows user name} local user name, in Windows -\IM{Windows user name} user name, local, in Windows -\IM{Windows user name} login name, local, in Windows -\IM{Windows user name} account name, local, in Windows - -\IM{local username in Rlogin} local user name, in Rlogin -\IM{local username in Rlogin} user name, local, in Rlogin -\IM{local username in Rlogin} login name, local, in Rlogin -\IM{local username in Rlogin} account name, local, in Rlogin - -\IM{privileged port} privileged port -\IM{privileged port} low-numbered port -\IM{privileged port} port, privileged - -\IM{remote shell} shell, remote -\IM{remote shell} remote shell - -\IM{encryption}{encrypted}{encrypt} encryption - -\IM{encryption algorithm} encryption algorithm -\IM{encryption algorithm} cipher algorithm -\IM{encryption algorithm} symmetric-key algorithm -\IM{encryption algorithm} algorithm, encryption - -\IM{AES} AES -\IM{AES} Advanced Encryption Standard -\IM{AES} Rijndael - -\IM{Arcfour} Arcfour -\IM{Arcfour} RC4 - -\IM{triple-DES} triple-DES - -\IM{single-DES} single-DES -\IM{single-DES} DES - -\IM{key exchange} key exchange -\IM{key exchange} kex - -\IM{shared secret} shared secret -\IM{shared secret} secret, shared - -\IM{key exchange algorithm} key exchange algorithm -\IM{key exchange algorithm} algorithm, key exchange - -\IM{Diffie-Hellman key exchange} Diffie-Hellman key exchange -\IM{Diffie-Hellman key exchange} key exchange, Diffie-Hellman - -\IM{group exchange} Diffie-Hellman group exchange -\IM{group exchange} group exchange, Diffie-Hellman - -\IM{ECDH} \q{ECDH} (elliptic-curve Diffie-Hellman) -\IM{ECDH} elliptic-curve Diffie-Hellman key exchange -\IM{ECDH} key exchange, elliptic-curve Diffie-Hellman -\IM{ECDH} Diffie-Hellman key exchange, with elliptic curves - -\IM{Streamlined NTRU Prime} Streamlined NTRU Prime -\IM{Streamlined NTRU Prime} NTRU Prime - -\IM{quantum attacks} quantum attacks, resistance to - -\IM{repeat key exchange} repeat key exchange -\IM{repeat key exchange} key exchange, repeat - -\IM{challenge/response authentication} challenge/response authentication -\IM{challenge/response authentication} authentication, challenge/response - -\IM{security token} security token -\IM{security token} token, security - -\IM{one-time passwords} one-time passwords -\IM{one-time passwords} password, one-time - -\IM{keyboard-interactive authentication} keyboard-interactive authentication -\IM{keyboard-interactive authentication} authentication, keyboard-interactive - -\IM{password expiry} password expiry -\IM{password expiry} expiry, of passwords - -\IM{public key authentication}{public-key authentication} public key authentication -\IM{public key authentication}{public-key authentication} RSA authentication -\IM{public key authentication}{public-key authentication} DSA authentication -\IM{public key authentication}{public-key authentication} authentication, public key - -\IM{MIT-MAGIC-COOKIE-1} \cw{MIT-MAGIC-COOKIE-1} -\IM{MIT-MAGIC-COOKIE-1} magic cookie -\IM{MIT-MAGIC-COOKIE-1} cookie, magic - -\IM{SSH server bugs} SSH server bugs -\IM{SSH server bugs} bugs, in SSH servers - -\IM{ignore message} SSH \q{ignore} messages -\IM{ignore message} \q{ignore} messages, in SSH - -\IM{message authentication code}{MAC} message authentication code (MAC) -\IM{message authentication code}{MAC} MAC (message authentication code) - -\IM{signatures} signature -\IM{signatures} digital signature - -\IM{storing configuration in a file} storing settings in a file -\IM{storing configuration in a file} saving settings in a file -\IM{storing configuration in a file} loading settings from a file - -\IM{transferring files} transferring files -\IM{transferring files} files, transferring - -\IM{receiving files}{download a file} receiving files -\IM{receiving files}{download a file} files, receiving -\IM{receiving files}{download a file} downloading files - -\IM{sending files}{upload a file} sending files -\IM{sending files}{upload a file} files, sending -\IM{sending files}{upload a file} uploading files - -\IM{listing files} listing files -\IM{listing files} files, listing - -\IM{wildcard}{wildcards} wildcards -\IM{wildcard}{wildcards} glob (wildcard) - -\IM{PATH} \c{PATH} environment variable - -\IM{SFTP} SFTP -\IM{SFTP} SSH file transfer protocol - -\IM{-unsafe} \c{-unsafe} PSCP command-line option -\IM{-ls-PSCP} \c{-ls} PSCP command-line option -\IM{-p-PSCP} \c{-p} PSCP command-line option -\IM{-q-PSCP} \c{-q} PSCP command-line option -\IM{-r-PSCP} \c{-r} PSCP command-line option -\IM{-batch-PSCP} \c{-batch} PSCP command-line option -\IM{-sftp} \c{-sftp} PSCP command-line option -\IM{-scp} \c{-scp} PSCP command-line option - -\IM{return value} return value -\IM{return value} exit value - -\IM{-b-PSFTP} \c{-b} PSFTP command-line option -\IM{-bc-PSFTP} \c{-bc} PSFTP command-line option -\IM{-be-PSFTP} \c{-be} PSFTP command-line option -\IM{-batch-PSFTP} \c{-batch} PSFTP command-line option - -\IM{spaces in filenames} spaces in filenames -\IM{spaces in filenames} filenames containing spaces - -\IM{working directory} working directory -\IM{working directory} current working directory - -\IM{resuming file transfers} resuming file transfers -\IM{resuming file transfers} files, resuming transfer of - -\IM{changing permissions on files} changing permissions on files -\IM{changing permissions on files} permissions on files, changing -\IM{changing permissions on files} files, changing permissions on -\IM{changing permissions on files} modes of files, changing -\IM{changing permissions on files} access to files, changing - -\IM{deleting files} deleting files -\IM{deleting files} files, deleting -\IM{deleting files} removing files - -\IM{create a directory} creating directories -\IM{create a directory} directories, creating - -\IM{remove a directory} removing directories -\IM{remove a directory} directories, removing -\IM{remove a directory} deleting directories - -\IM{rename remote files} renaming files -\IM{rename remote files} files, renaming and moving -\IM{rename remote files} moving files - -\IM{local Windows command} local Windows command -\IM{local Windows command} Windows command - -\IM{PLINK_PROTOCOL} \c{PLINK_PROTOCOL} environment variable - -\IM{-batch-plink} \c{-batch} Plink command-line option -\IM{-s-plink} \c{-s} Plink command-line option -\IM{-shareexists-plink} \c{-shareexists} Plink command-line option - -\IM{subsystem} subsystem, SSH -\IM{subsystem} SSH subsystem - -\IM{batch file}{batch files} batch files - -\IM{CVS_RSH} \c{CVS_RSH} environment variable - -\IM{DSA} DSA -\IM{DSA} Digital Signature Standard - -\IM{ECDSA} ECDSA -\IM{ECDSA} elliptic-curve DSA - -\IM{NIST} NIST-standardised elliptic curves -\IM{NIST} elliptic curves, NIST-standardised - -\IM{EdDSA} EdDSA -\IM{EdDSA} Edwards-curve DSA - -\IM{public-key algorithm} public-key algorithm -\IM{public-key algorithm} asymmetric key algorithm -\IM{public-key algorithm} algorithm, public-key - -\IM{generating keys} generating key pairs -\IM{generating keys} creating key pairs -\IM{generating keys} key pairs, generating -\IM{generating keys} public keys, generating -\IM{generating keys} private keys, generating - -\IM{probable primes} probable primes -\IM{probable primes} primes, probable - -\IM{proven primes} proven primes -\IM{proven primes} primes, proven - -\IM{authorized_keys file}{authorized_keys} \cw{authorized_keys} file - -\IM{key fingerprint} fingerprint, of SSH authentication key -\IM{key fingerprint} public key fingerprint (SSH) -\IM{key fingerprint} SSH public key fingerprint - -\IM{SSH-2 public key format} SSH-2 public key file format -\IM{SSH-2 public key format} public key file, SSH-2 - -\IM{OpenSSH private key format} OpenSSH private key file format -\IM{OpenSSH private key format} private key file, OpenSSH - -\IM{ssh.com private key format} \cw{ssh.com} private key file format -\IM{ssh.com private key format} private key file, \cw{ssh.com} - -\IM{PEM-style} PEM-style OpenSSH private key format -\IM{PEM-style} OpenSSH private key format, PEM-style - -\IM{importing keys} importing private keys -\IM{importing keys} loading private keys - -\IM{export private keys} exporting private keys -\IM{export private keys} saving private keys - -\IM{.ssh} \c{.ssh} directory - -\IM{.ssh2} \c{.ssh2} directory - -\IM{authentication agent} authentication agent -\IM{authentication agent} agent, authentication - -\IM{-c-pageant} \c{-c} Pageant command-line option -\IM{--keylist} \c{--keylist} Pageant command-line option -\IM{--openssh-config} \c{--openssh-config} Pageant command-line option - -\IM{Windows OpenSSH} Windows OpenSSH -\IM{Windows OpenSSH} OpenSSH, on Windows - -\IM{FAQ} FAQ -\IM{FAQ} Frequently Asked Questions - -\IM{supported features} supported features -\IM{supported features} features, supported - -\IM{remember my password} storing passwords -\IM{remember my password} password, storing - -\IM{login scripts}{startup scripts} login scripts -\IM{login scripts}{startup scripts} startup scripts - -\IM{Red Hat Linux} Red Hat Linux -\IM{Red Hat Linux} Linux, Red Hat - -\IM{SMB} SMB -\IM{SMB} Windows file sharing - -\IM{clean up} clean up after PuTTY -\IM{clean up} uninstalling - -\IM{version of PuTTY} version, of PuTTY - -\IM{GPG signatures} PGP signatures, of PuTTY binaries -\IM{GPG signatures} GPG signatures, of PuTTY binaries -\IM{GPG signatures} signatures, of PuTTY binaries - -\IM{logical host name} logical host name -\IM{logical host name} host name, logical - -\IM{host key cache}{host key management} host key management -\IM{host key cache}{host key management} cache, of SSH host keys - -\IM{web browsers} web browser - -\IM{GSSAPI credential delegation} GSSAPI credential delegation -\IM{GSSAPI credential delegation} credential delegation, GSSAPI -\IM{GSSAPI credential delegation} delegation, of GSSAPI credentials - -\IM{cascading credentials} cascading credentials -\IM{cascading credentials} credentials, cascading - -\IM{SYSTEM32} \cw{SYSTEM32} directory, on Windows - -\IM{32-bit Windows} 32-bit Windows -\IM{32-bit Windows} Windows, 32-bit -\IM{32-bit Windows} x86 (32-bit processor architecture) -\IM{64-bit Windows} 64-bit Windows -\IM{64-bit Windows} Windows, 64-bit - -\IM{Windows process ACL} Windows process ACL -\IM{Windows process ACL} process ACL (Windows) -\IM{Windows process ACL} ACL, process (Windows) - -\IM{proxy logging} proxy logging -\IM{proxy logging} logging, proxy -\IM{proxy logging} diagnostic, proxy -\IM{proxy logging} standard error, proxy - -\IM{PuTTY icon} PuTTY icon -\IM{PuTTY icon} icon, PuTTY's -\IM{PuTTY icon} logo, PuTTY's - -\IM{system tray} system tray, Windows -\IM{system tray} notification area, Windows (aka system tray) -\IM{system tray} taskbar notification area, Windows (aka system tray) - -\IM{shifted arrow keys} arrow keys, shifted -\IM{shifted arrow keys} shifted arrow keys - -\IM{certificate}{certificates} certificates, SSH -\IM{certificate}{certificates} SSH certificates -\IM{certificate}{certificates} OpenSSH certificates -\IM{certificate}{certificates} CA (certification authority) - -\IM{Microsoft Store} Microsoft Store -\IM{Microsoft Store} Windows Store diff --git a/doc/index.but.terabox.uploading.cfg b/doc/index.but.terabox.uploading.cfg deleted file mode 100644 index 2ca377268ba70b65e9845ab2727a13af9174f21e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2692 zcmXZeOIHF(3-BhQNdvB7eYDTG; zYUw52rMFa0N2#0kX_o#{Jw34R(yQw}wbFI^PNOv8*-Dq`CVldL^!}5^X`W6}k9Egm zld2+q(iYA~x?mmf8?#-e4&<8oNy?|Qv`hC5BxRcE+vChGBXvs%<`O>U*kE#{luE1a6-eL&oKpM z*mag1ls4pcI^{Q`u&x7J3?<`Zn1?)WaTFlDsn1r;2j&jEJ@a~n)rEH9xuoXM{c7%9 zwa(OU1-HPzEB-E3%rEgaF>K_z;k$=eGV{adO@AM#sf+uduF85se^X`8Wb>c`Q#nm| zUSe24Ym3ci^@;wx;%lTn#CN|%>dxV8T=!Jo$mK#*JyQ}roAFse?#XqfW>Z{qD&JM4 zow2rHOvS!ty>9S(bM^G~j^|HSXd`<;2z??kVggXrL3O36|pp} zh-Yytimv3+hdiV4-E8pfGbPViWR3lc|H5;CWyRwHe??Zu?o-}(?0vd_!MB?!`86tGozr{G^UrgSy=*p5L79%I zc2iz$3N}<$VLh9Df)l%C3}cSBz_QEwwZ+qeRa4z-N}?Jso`>T0v~~Tqu@tD@s7*_j zFCs@gK3H;Wby#!SZs5<%p12C;VnrSCEx|apW?QOsX4DdKhv$OkD*q;?ZyLVTr)Km+ z{WVob|F5eNZ4RO&d6ha{Yw7<#B5Ehnj_%8O;;?LpTMx zc3ITDK2{*b&WLKqNwC$C7JUaal;ye;JJjnfuc&79_n7U8&W8GwF)b+Sm>J-B&w96m zc*!rSm1kYb>4fr$Ec?{P8Pnh~#?{YMM_1wm>WV5rjp$g3ISS{pI?#z8CD*VDu6mYd z8-CPtEz@?knzr6LSjyOLvkZz@PN=-0Gj{ANzSswSDF3aOI30&_7`q>I?>ft&N6}P` zANAvt=={<4$2;$>RimhqVi5 zk+FBp9ja;U-Ea9W;Y>Y0=-6gz7qDiSL*Y(Uf93uu;=ixYZN{7vTZVC~)>UdQ;Q#1Y ziRD&A9b)t=R@Rj7pF1z3Uoml}^=NpK*{{(fk2nLjJo4GwALVf_?Y%u4jq%4Gj=dXu zs%V|XzG~x~Vd%rEX1O;p$Cs?AM(=QkvW+t^=k36Hdw06U7b4d2FVvlEBKj)pQAwSK zJo+km3o)X%w0Cu=MNJ@&y-m!Nc~!+lrD6xhSNIJ3j;=c$iJHa#h6PLPt#=3o*0r4W zvhLGUlVjb=Dad0WZk?%(eY47T?57HLu%6S?k<&GtzFLp?SLJlaGN5P#uO*9fox73U zxpi8>azs8?wL8}*Wgo;^@xg4ea#x{Ir5Ivu#B^eaR|F@JePZ!f9(933T@&1iQeFe z?`fTiZ)+RO%08)B^HPaWH^hHZAZ@v{O+M|#7=Z%Y#d+t5)~u1&_9sf f6vijt_z#dbo@c8n_C|EF$5x>s&YZl6e_a0pH^aHM diff --git a/doc/intro.but b/doc/intro.but deleted file mode 100644 index 7c66b6561..000000000 --- a/doc/intro.but +++ /dev/null @@ -1,88 +0,0 @@ -\C{intro} Introduction to PuTTY - -PuTTY is a free SSH, Telnet, Rlogin, and SUPDUP client for Windows -systems. - -\H{you-what} What are SSH, Telnet, Rlogin, and SUPDUP? - -If you already know what SSH, Telnet, Rlogin, and SUPDUP are, you can -safely skip on to the next section. - -SSH, Telnet, Rlogin, and SUPDUP are four ways of doing the same thing: -logging in to a multi-user computer from another computer, over a -network. - -Multi-user operating systems, typically of the Unix family (such as -Linux, MacOS, and the BSD family), usually present a \i{command-line -interface} to the user, much like the \q{\i{Command Prompt}} or -\q{\i{MS-DOS Prompt}} in Windows. The system prints a prompt, and you -type commands which the system will obey. - -Using this type of interface, there is no need for you to be sitting -at the same machine you are typing commands to. The commands, and -responses, can be sent over a network, so you can sit at one -computer and give commands to another one, or even to more than one. - -SSH, Telnet, Rlogin, and SUPDUP are \i\e{network protocols} that allow -you to do this. On the computer you sit at, you run a \i\e{client}, -which makes a network connection to the other computer (the -\i\e{server}). The network connection carries your keystrokes and -commands from the client to the server, and carries the server's -responses back to you. - -These protocols can also be used for other types of keyboard-based -interactive session. In particular, there are a lot of bulletin -boards, \i{talker systems} and \i{MUDs} (Multi-User Dungeons) which support -access using Telnet. There are even a few that support SSH. - -You might want to use SSH, Telnet, Rlogin, or SUPDUP if: - -\b you have an account on a Unix system (or some other multi-user OS -such as VMS or ITS) which you want to be able to access from somewhere -else - -\b your Internet Service Provider provides you with a login account -on a \i{web server}. (This might also be known as a \i\e{shell account}. -A \e{shell} is the program that runs on the server and interprets -your commands for you.) - -\b you want to use a \i{bulletin board system}, talker or MUD which can -be accessed using Telnet. - -You probably do \e{not} want to use SSH, Telnet, Rlogin, or SUPDUP if: - -\b you only use Windows. Windows computers have their own -ways of networking between themselves, and unless you are doing -something fairly unusual, you will not need to use any of these -remote login protocols. - -\H{which-one} How do SSH, Telnet, Rlogin, and SUPDUP differ? - -This list summarises some of the \i{differences between SSH, Telnet, -Rlogin, and SUPDUP}. - -\b SSH (which stands for \q{\i{secure shell}}) is a recently designed, -high-security protocol. It uses strong cryptography to protect your -connection against eavesdropping, hijacking and other attacks. Telnet, -Rlogin, and SUPDUP are all older protocols offering minimal security. - -\b SSH and Rlogin both allow you to \I{passwordless login}log in to the -server without having to type a password. (Rlogin's method of doing this is -insecure, and can allow an attacker to access your account on the -server. SSH's method is much more secure, and typically breaking the -security requires the attacker to have gained access to your actual -client machine.) - -\b SSH allows you to connect to the server and automatically send a -command, so that the server will run that command and then -disconnect. So you can use it in automated processing. - -The Internet is a hostile environment and security is everybody's -responsibility. If you are connecting across the open Internet, then -we recommend you use SSH. If the server you want to connect to -doesn't support SSH, it might be worth trying to persuade the -administrator to install it. - -If your client and server are both behind the same (good) firewall, -it is more likely to be safe to use Telnet, Rlogin, or SUPDUP, but we -still recommend you use SSH. diff --git a/doc/intro.but.terabox.uploading.cfg b/doc/intro.but.terabox.uploading.cfg deleted file mode 100644 index b1d296f37e9d06b4f07b682d71e0ca897989fafc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1954 zcmW;NOIIRG5Cq^_`&U@Vn_+Pm($azgGa@3eLmlOzFuajpf4}Ner`xW~$cV_SrhN;A za1&m`bNC3=P!12_5*FbnG{TGcDg3pDp%os(A-si8$X4iur?7z@*&oAuScZD&h@K#} z;g^bI*x~sOUC}<=yI3#WW4@994A2kcX`EJPb`0(PtW_R3oO%Q4{-Kq#h>t5>*APFI@I;{>Ut4_ISGD zxAea#{&OY#46w{8PM~KhkLvH$twN52Cz+E>bUDJ4synC{Q{L-mrrR!#8IGxaiZ!ww zt$}IZLC!dAaK3{)c%GY95q3bQ>3S2{%lDzKLJd!zc9pv`R?*{yi=vsdxVV=a>7_xZ2z||69V(T*^;X2(4C5z-es*dr%qO@j znXS58+0+R{H%(Jon@CR0R;&;6>`cF?)Iv$08%#r(BQJmS_YbaMdT~k(%u%(a!;KC< zI9;2_!Sw(?Iab;fuLR_b}m&)WVs#|tWz z?9Ja(xzl0Enwmz*+y|K7p!b+}nU^-_Rh$)XQKGKQX*-)pqO))PTy*^Hx`Mpshp$!jVZ(_ zPbtQ8zcH(K4wsnndjHDKYcJNp(lqTvWzl-|E7+<#WA0>~ymQpzbHs5?J~Ec5Z#~7| zy=!e6d0m%PmqjYn=OQ0eFHl2~n%cej zp5b07?5O{70$1ierTL+Df@i0Lqo=y;wG$ZY@R>sN63_YG)cZ8rSfj=>j)^ya4jt#+ zac$)j^>N|_XZ~)j?F*E~I*I39JLH^mp2qo+Llw{?Pe=4q|!#=dF_ z@k=Ma;e-e8hOYr&H?s@RnE>U ss?M;^T|ZoxvPqBB#BN`*s*(G^wc)DbOlB}0O7Uwla!;{V_5WY^AE$3YvH$=8 diff --git a/doc/man-pageant.but b/doc/man-pageant.but deleted file mode 100644 index d202f1668..000000000 --- a/doc/man-pageant.but +++ /dev/null @@ -1,431 +0,0 @@ -\cfg{man-identity}{pageant}{1}{2015-05-19}{PuTTY tool suite}{PuTTY tool suite} - -\H{pageant-manpage} Man page for Pageant - -\S{pageant-manpage-name} NAME - -\cw{pageant} - PuTTY SSH authentication agent - -\S{pageant-manpage-synopsis} SYNOPSIS - -\c pageant ( -X | -T | --permanent | --debug ) [ [ --encrypted ] key-file... ] -\e bbbbbbb bb bb bbbbbbbbbbb bbbbbbb bbbbbbbbbbb iiiiiiii -\c pageant [ [ --encrypted ] key-file... ] --exec command [ args... ] -\e bbbbbbb bbbbbbbbb iiiiiiii bbbbbb iiiiiii iiii -\c pageant -a [ --encrypted ] key-file... -\e bbbbbbb bb bbbbbbbbbbb iiiiiiii -\c pageant ( -d | -r | --public | --public-openssh ) key-identifier... -\e bbbbbbb bb bb bbbbbbbb bbbbbbbbbbbbbbbb iiiiiiiiiiiiii -\c pageant ( -D | -R ) -\e bbbbbbb bb bb -\c pageant -l [ --fptype format ] -\e bbbbbbb bb bbbbbbbb iiiiii -\c pageant --askpass prompt -\e bbbbbbb bbbbbbbbb iiiiii - -\S{pageant-manpage-description} DESCRIPTION - -\c{pageant} is both an SSH authentication agent, and also a tool for -communicating with an already-running agent. - -When running as an SSH agent, it listens on a Unix-domain socket for -connections from client processes running under your user id. Clients -can load SSH private keys into the agent, or request signatures on a -given message from a key already in the agent. This permits one-touch -authentication by SSH client programs, if Pageant is holding a key -that the server they are connecting to will accept. - -\c{pageant} can also act as a client program itself, communicating -with an already-running agent to add or remove keys, list the keys, or -extract their public half. - -The agent protocol used by \c{pageant} is compatible with the PuTTY -tools and also with other implementations such as OpenSSH's SSH client -and \cw{ssh-agent}(\e{1}). Some \c{pageant} features are implemented with -protocol extensions, so will only work if \c{pageant} is on both ends. - -To run \c{pageant} as an agent, you must provide an option to tell it -what its \e{lifetime} should be. Typically you would probably want -Pageant to last for the duration of a login session, in which case you -should use either \cw{-X} or \cw{-T}, depending on whether your login -session is GUI or purely terminal-based respectively. For example, in -your X session startup script you might write - -\c eval $(pageant -X) -\e bbbbbbbbbbbbbbbbbb - -which will cause Pageant to start running, monitor the X server to -notice when your session terminates (and then it will terminate too), -and print on standard output some shell commands to set environment -variables that client processes will need to find the running agent. - -In a terminal-based login, you could do almost exactly the same thing -but with \cw{-T}: - -\c eval $(pageant -T) -\e bbbbbbbbbbbbbbbbbb - -This will cause Pageant to tie its lifetime to that of your -controlling terminal: when you log out, and the terminal device ceases -to be associated with your session, Pageant will notice that it has no -controlling terminal any more, and will terminate automatically. - -In either of these modes, you can also add one or more private keys as -extra command-line arguments, e.g. - -\c eval $(pageant -T ~/.ssh/key.ppk) -\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - -in which case Pageant will immediately prompt for the keys' passphrases -(if any) and start the agent with those keys already loaded in -cleartext form. Passphrase prompts will use the controlling terminal if -one is available, or failing that the GUI if one of those is available. -(The prompt method can be overridden with the \cw{--gui-prompt} or -\cw{--tty-prompt} options.) If neither is available, no passphrase -prompting can be done. - -Alternatively, you can start an agent with keys stored in encrypted -form: - -\c eval $(pageant -T --encrypted ~/.ssh/key.ppk) -\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - -In this case, Pageant will not prompt for a passphrase at startup; -instead, it will prompt the first time a client tries to use the key. -(Pageant will need access to a GUI so that it can pop up a passphrase -prompt when required, unless it's running in \cw{--debug} mode.) - -To use Pageant to talk to an existing agent, you can add new keys -using \cw{-a}, list the current set of keys' fingerprints and comments -with \cw{-l}, extract the full public half of any key using -\cw{--public} or \cw{--public-openssh}, delete a specific key or -all keys using \cw{-d} or \cw{-D} respectively, or request -re-encryption of a specific key or all keys using \cw{-r} or \cw{-R} -respectively. - -\S{pageant-manpage-lifetime} LIFETIME - -The following options are called \e{lifetime modes}. They all request -Pageant to operate in agent mode; each one specifies a different -method for Pageant to start up and know when to shut down. - -\dt \cw{-X} - -\dd Pageant will open a connection to your X display, and when that -connection is lost, it will terminate. This gives it the same lifetime -as your GUI login session, so in this mode it is suitable for running -from a startup script such as \cw{.xsession}. The actual agent will be -a subprocess; the main Pageant process will terminate immediately, -after printing environment-variable setting commands on standard -output which should be installed in any process wanting to communicate -with the agent. - -\lcont{ - -The usual approach would be to run - -\c eval $(pageant -X) -\e bbbbbbbbbbbbbbbbbb - -in an X session startup script. However, other possibilities exist, -such as directing the standard output of \cq{pageant -X} to a file -which is then sourced by any new shell. - -} - -\dt \cw{-T} - -\dd Pageant will tie its lifetime to that of the login session running -on its controlling terminal, by noticing when it ceases to have a -controlling terminal (which will automatically happen as a side effect -of the session leader process terminating). Like \cw{-X}, Pageant will -print environment-variable commands on standard output. - -\dt \cw{--exec} \e{command} - -\dd Pageant will run the provided command as a subprocess, preloaded -with the appropriate environment variables to access the agent it -starts up. When the subprocess terminates, Pageant will terminate as -well. - -\lcont{ - -All arguments on Pageant's command line after \cw{--exec} will be -treated as part of the command to run, even if they look like other -valid Pageant options or key files. - -} - -\dt \cw{--permanent} - -\dd Pageant will fork off a subprocess to be the agent, and print -environment-variable commands on standard output, like \cw{-X} and -\cw{-T}. However, in this case, it will make no effort to limit its -lifetime in any way; it will simply run permanently, unless manually -killed. The environment variable \cw{SSH_AGENT_PID}, set by the -commands printed by Pageant, permits the agent process to be found for -this purpose. - -\lcont{ - -This option is not recommended, because any method of manually killing -the agent carries the risk of the session terminating unexpectedly -before it manages to happen. - -} - -\dt \cw{--debug} - -\dd Pageant will run in the foreground, without forking. It will print -its environment variable setup commands on standard output, and then it -will log all agent activity to standard output as well; any passphrase -prompts will need to be answered on standard input. This is useful -for debugging what Pageant itself is doing, or what another process is -doing to it. - -\S{pageant-manpage-client} CLIENT OPTIONS - -The following options tell Pageant to operate in client mode, -contacting an existing agent via environment variables that it should -already have set. - -\dt \cw{-a} \e{key-files} - -\dd Load the specified private key file(s) and add them to the -already-running agent. Unless \cw{--encrypted} is also specified, -\c{pageant} will decrypt them if necessary by prompting for their -passphrases (with the same choice of user interfaces as in agent -mode). - -\lcont{ -The private key files must be in PuTTY's \cw{.ppk} file format. -} - -\dt \cw{-l} - -\dd List the keys currently in the running agent. Each key's -fingerprint and comment string will be shown. (Use the \cw{-E} -option to change the fingerprint format.) - -\lcont{ -Keys that will require a passphrase on their next use are listed as -\q{encrypted}. Keys that can be returned to this state with \cw{-r} -are listed as \q{re-encryptable}. -} - -\dt \cw{--public} \e{key-identifiers} - -\dd Print the public half of each specified key, in the RFC 4716 -standard format (multiple lines, starting with \cq{---- BEGIN SSH2 -PUBLIC KEY ----}). - -\lcont{ - -Each \e{key-identifier} can be any of the following: - -\b The name of a file containing the key, either the whole key (again -in \cw{.ppk} format) or just its public half. - -\b The key's comment string, as shown by \cw{pageant -l}. - -\b Enough of one of the key's fingerprint formats to be unique among -keys currently loaded into the agent. - -If Pageant can uniquely identify one key by interpreting the -\e{key-identifier} in any of these ways, it will assume that key was -the one you meant. If it cannot, you will have to specify more detail. - -If you find that your desired \e{key-identifier} string can be validly -interpreted as more than one of the above \e{kinds} of identification, -you can disambiguate by prefixing it as follows: - -\dt \cq{file:} - -\dd to indicate that it is a filename - -\dt \cq{comment:} - -\dd to indicate that it is a comment string - -\dt \cq{fp:} - -\dd to indicate that it is a fingerprint; any fingerprint format will -be matched - -\dt \cq{sha256:} or \cq{md5:} - -\dd to indicate that it is a fingerprint of a specific format - -\dt \cq{sha256-cert:} or \cq{md5-cert:} - -\dd to indicate that it is a fingerprint of a specific format, and -specifically matches the fingerprint of the public key \e{including} a -certificate if any - -} - -\dt \cw{--public-openssh} \e{key-identifiers}, \cw{-L} \e{key-identifiers} - -\dd Print the public half of each specified key, in the one-line -format used by OpenSSH, suitable for putting in -\cw{.ssh/authorized_keys} files. - -\dt \cw{-d} \e{key-identifiers} - -\dd Delete each specified key from the agent's memory, so that the -agent will no longer serve it to clients unless it is loaded in again -using \cw{pageant -a}. - -\dt \cw{-D} - -\dd Delete all keys from the agent's memory, leaving it completely -empty. - -\dt \cw{-r} \e{key-identifiers} - -\dd \q{Re-encrypt} each specified key in the agent's memory - -that is, forget any cleartext version, so that the user will be -prompted for a passphrase again next time the key is used. -(For this to be possible, the key must previously have been added -with the \cw{--encrypted} option.) - -\lcont{ -(Holding encrypted keys is a Pageant extension, so this option and -\cw{-R} are unlikely to work with other agents.) -} - -\dt \cw{-R} - -\dd \q{Re-encrypt} all possible keys in the agent's memory. -(This may leave some keys in cleartext, if they were not previously -added with the \cw{--encrypted} option.) - -\dt \cw{--test-sign} \e{key-identifier} - -\dt \cw{--test-sign-with-flags=}\e{flags} \e{key-identifier} - -\dd Sign arbitrary data with the given key. This mode is only likely -to be useful when testing \c{pageant} itself. - -\lcont{ - -The data to sign is taken from standard input, signed by the agent -with the key identified by \e{key-identifier}, and the resulting -signature emitted on standard output (as a binary blob in the format -defined by the SSH specifications). - -\e{flags} is a number representing a combination of flag bits defined -by the SSH agent protocol. - -} - -\S{pageant-manpage-askpass} SSH-ASKPASS REPLACEMENT - -\dt \cw{--askpass} \e{prompt} - -\dd With this option, \c{pageant} acts as an \cw{ssh-askpass}(\e{1}) -replacement, rather than performing any SSH agent functionality. This -may be useful if you prefer Pageant's GUI prompt style, which -minimises information leakage about your passphrase length in its -visual feedback, compared to other \cw{ssh-askpass}(\e{1}) implementations. - -\lcont{ - -\c{pageant --askpass} implements the standard \cw{ssh-askpass}(\e{1}) -interface: it can be passed a prompt to display (as a single argument) -and, if successful, prints the passphrase on standard output and -returns a zero exit status. Typically you would use the environment -variable \cw{SSH_ASKPASS} to tell other programs to use \c{pageant} in -this way. - -} - -\S{pageant-manpage-options} OPTIONS - -\dt \cw{-v} - -\dd Verbose mode. When Pageant runs in agent mode, this option causes -it to log all agent activity to its standard error. For example, you -might run - -\lcont{ - -\c eval $(pageant -X -v 2>~/.pageant.log) -\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - -and expect a list of all signatures requested by agent clients to -build up in that log file. - -The log information is the same as that produced by the \cw{--debug} -lifetime option, but \cw{--debug} sends it to standard output (since -that is the main point of debugging mode) whereas \cw{-v} in all other -lifetime modes sends the same log data to standard error (being a -by-product of the program's main purpose). Using \cw{-v} in -\cw{--debug} mode has no effect: the log still goes to standard -output. - -} - -\dt \cw{-s}, \cw{-c} - -\dd Force Pageant to output its environment setup commands in the -style of POSIX / Bourne shells (\cw{-s}) or C shells (\cw{-c}) -respectively. If neither option is given, Pageant will guess based on -whether the environment variable \cw{SHELL} has a value ending in -\cq{csh}. - -\dt \cw{--symlink} \e{fixed-path} - -\dd When operating in agent mode, as well as creating a uniquely named -listening socket, \c{pageant} will also create (or update) a symbolic -link at \e{fixed-path} pointing to that socket. - -\lcont{ -This allows access to an agent instance by setting the -\c{SSH_AUTH_SOCK} environment variable to \e{fixed-path}, rather than -having to use the value invented by \c{pageant} when it starts. It's -mainly expected to be useful for debugging. -} - -\dt \cw{--encrypted}, \cw{--no-decrypt} - -\dd When adding keys to the agent (at startup or later), keep them -in encrypted form until the first attempt to use them; the user will -be prompted for a passphrase then. Once decrypted, a key that was -added in this way can be \q{re-encrypted} with the \cw{-r} or \cw{-R} -client options. - -\lcont{ -The \cw{--encrypted} option makes no difference for key files which -do not have a passphrase. - -(Storing keys in encrypted form is a Pageant extension; other agent -implementations are unlikely to support it.) -} - -\dt \cw{-E} \e{fingerprint-type}, \cw{--fptype} \e{fingerprint-type} - -\dd Specify the fingerprint format to print. Only applicable when -listing fingerprints with \cw{-l}. The available formats are -\cw{sha256} (the default) and \cw{md5}. - -\dt \cw{--gui-prompt}, \cw{--tty-prompt} - -\dd Force Pageant to prompt for key passphrases with a particular -method (GUI or terminal) rather than trying to guess the most -appropriate method as described above. (These options are relevant -whenever a key file is specified to \c{pageant} that needs -immediate decryption, and in \c{--askpass} mode.) - -\dt \cw{--help} - -\dd Print a brief summary of command-line options and terminate. - -\dt \cw{--version}, \cw{-V} - -\dd Print the version of Pageant. - -\dt \cw{--} - -\dd Cause all subsequent arguments to be treated as key file names, -even if they look like options. diff --git a/doc/man-plink.but b/doc/man-plink.but deleted file mode 100644 index 2a3b36c7c..000000000 --- a/doc/man-plink.but +++ /dev/null @@ -1,323 +0,0 @@ -\cfg{man-identity}{plink}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} - -\H{plink-manpage} Man page for Plink - -\S{plink-manpage-name} NAME - -\cw{plink} \- PuTTY link, command line network connection tool - -\S{plink-manpage-synopsis} SYNOPSIS - -\c plink [options] [user@]host [command] -\e bbbbb iiiiiii iiiib iiii iiiiiii - -\S{plink-manpage-description} DESCRIPTION - -\cw{plink} is a network connection tool supporting several protocols. - -\S{plink-manpage-options} OPTIONS - -The command-line options supported by \cw{plink} are: - -\dt \cw{-V} - -\dd Show version information and exit. - -\dt \cw{-pgpfp} - -\dd Display the fingerprints of the PuTTY PGP Master Keys and exit, -to aid in verifying new files released by the PuTTY team. - -\dt \cw{-v} - -\dd Show verbose messages. - -\dt \cw{-load} \e{session} - -\dd Load settings from saved session. - -\dt \cw{-ssh} - -\dd Force use of SSH protocol (default). - -\dt \cw{-telnet} - -\dd Force use of Telnet protocol. - -\dt \cw{-rlogin} - -\dd Force use of rlogin protocol. - -\dt \cw{-raw} - -\dd Force raw mode. - -\dt \cw{-serial} - -\dd Force serial mode. - -\dt \cw{-ssh-connection} - -\dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is -only likely to be useful when connecting to a \cw{psusan}(\e{1}) -server, most likely with an absolute path to a Unix-domain socket in -place of \e{host}. - -\dt \cw{\-proxycmd} \e{command} - -\dd Instead of making a TCP connection, use \e{command} as a proxy; -network traffic will be redirected to the standard input and output -of \e{command}. \e{command} must be a single word, so is likely to -need quoting by the shell. - -\lcont{ -The special strings \cw{%host} and \cw{%port} in \e{command} will be -replaced by the hostname and port number you want to connect to; to get -a literal \c{%} sign, enter \c{%%}. - -Backslash escapes are also supported, such as sequences like \c{\\n} -being replaced by a literal newline; to get a literal backslash, -enter \c{\\\\}. (Further escaping may be required by the shell.) - -(See the main PuTTY manual for full details of the supported \cw{%}- -and backslash-delimited tokens, although most of them are probably not -very useful in this context.) -} - -\dt \cw{-P} \e{port} - -\dd Connect to port \e{port}. - -\dt \cw{-l} \e{user} - -\dd Set remote username to \e{user}. - -\dt \cw{-m} \e{path} - -\dd Read remote command(s) from local file \e{path}. - -\dt \cw{-batch} - -\dd Disable interactive prompts. - -\dt \cw{-sanitise-stderr} - -\dt \cw{-sanitise-stdout} - -\dt \cw{-no-sanitise-stderr} - -\dt \cw{-no-sanitise-stdout} - -\dd By default, Plink can choose to filter control characters if that -seems appropriate, to prevent remote processes sending confusing escape -sequences. These options override Plink's default behaviour to enable -or disabling such filtering on the standard error and standard output -channels. - -\dt \cw{-pwfile} \e{filename} - -\dd Open the specified file, and use the first line of text read from -it as the remote password. - -\dt \cw{-pw} \e{password} - -\dd Set remote password to \e{password}. \e{CAUTION:} this will likely -make the password visible to other users of the local machine (via -commands such as \q{\c{ps}} or \q{\c{w}}). Use \cw{-pwfile} instead. - -\dt \cw{\-L} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport} - -\dd Set up a local port forwarding: listen on \e{srcport} (or -\e{srcaddr}:\e{srcport} if specified), and forward any connections -over the SSH connection to the destination address -\e{desthost}:\e{destport}. Only works in SSH. - -\dt \cw{\-R} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport} - -\dd Set up a remote port forwarding: ask the SSH server to listen on -\e{srcport} (or \e{srcaddr}:\e{srcport} if specified), and to -forward any connections back over the SSH connection where the -client will pass them on to the destination address -\e{desthost}:\e{destport}. Only works in SSH. - -\dt \cw{\-D} [\e{srcaddr}:]\e{srcport} - -\dd Set up dynamic port forwarding. The client listens on -\e{srcport} (or \e{srcaddr}:\e{srcport} if specified), and -implements a SOCKS server. So you can point SOCKS-aware applications -at this port and they will automatically use the SSH connection to -tunnel all their connections. Only works in SSH. - -\dt \cw{-X} - -\dd Enable X11 forwarding. - -\dt \cw{-x} - -\dd Disable X11 forwarding (default). - -\dt \cw{-A} - -\dd Enable agent forwarding. - -\dt \cw{-a} - -\dd Disable agent forwarding (default). - -\dt \cw{-t} - -\dd Enable pty allocation (default if a command is NOT specified). - -\dt \cw{-T} - -\dd Disable pty allocation (default if a command is specified). - -\dt \cw{-1} - -\dd Force use of SSH protocol version 1. - -\dt \cw{-2} - -\dd Force use of SSH protocol version 2. - -\dt \cw{-4}, \cw{-6} - -\dd Force use of IPv4 or IPv6 for network connections. - -\dt \cw{-C} - -\dd Enable SSH compression. - -\dt \cw{-i} \e{keyfile} - -\dd Private key file for user authentication. For SSH-2 keys, this key -file must be in PuTTY's PPK format, not OpenSSH's format or anyone -else's. - -\lcont{ If you are using an authentication agent, you can also specify -a \e{public} key here (in RFC 4716 or OpenSSH format), to identify -which of the agent's keys to use. } - -\dt \cw{\-noagent} - -\dd Don't try to use an authentication agent for local authentication. -(This doesn't affect agent forwarding.) - -\dt \cw{\-agent} - -\dd Allow use of an authentication agent. (This option is only necessary -to override a setting in a saved session.) - -\dt \cw{\-no\-trivial\-auth} - -\dd Disconnect from any SSH server which accepts authentication without -ever having asked for any kind of password or signature or token. (You -might want to enable this for a server you always expect to challenge -you, for instance to ensure you don't accidentally type your key file's -passphrase into a compromised server spoofing Plink's passphrase -prompt.) - -\dt \cw{\-noshare} - -\dd Don't test and try to share an existing connection, always make -a new connection. - -\dt \cw{\-share} - -\dd Test and try to share an existing connection. - -\dt \cw{\-hostkey} \e{key} - -\dd Specify an acceptable host public key. This option may be specified -multiple times; each key can be either a fingerprint (\cw{SHA256:AbCdE...}, -\cw{99:aa:bb:...}, etc) or a base64-encoded blob in OpenSSH's one-line -format. - -\lcont{ Specifying this option overrides automated host key -management; \e{only} the key(s) specified on the command-line will be -accepted (unless a saved session also overrides host keys, in which -case those will be added to), and the host key cache will not be -written. } - -\dt \cw{-s} - -\dd Remote command is SSH subsystem (SSH-2 only). - -\dt \cw{-N} - -\dd Don't start a remote command or shell at all (SSH-2 only). - -\dt \cw{\-nc} \e{host}:\e{port} - -\dd Make a remote network connection from the server instead of -starting a shell or command. - -\dt \cw{\-sercfg} \e{configuration-string} - -\dd Specify the configuration parameters for the serial port, in -\cw{-serial} mode. \e{configuration-string} should be a -comma-separated list of configuration parameters as follows: - -\lcont{ - -\b Any single digit from 5 to 9 sets the number of data bits. - -\b \cq{1}, \cq{1.5} or \cq{2} sets the number of stop bits. - -\b Any other numeric string is interpreted as a baud rate. - -\b A single lower-case letter specifies the parity: \cq{n} for none, -\cq{o} for odd, \cq{e} for even, \cq{m} for mark and \cq{s} for space. - -\b A single upper-case letter specifies the flow control: \cq{N} for -none, \cq{X} for XON/XOFF, \cq{R} for RTS/CTS and \cq{D} for -DSR/DTR. - -} - -\dt \cw{\-sshlog} \e{logfile} - -\dt \cw{\-sshrawlog} \e{logfile} - -\dd For SSH connections, these options make \cw{plink} log protocol -details to a file. (Some of these may be sensitive, although by default -an effort is made to suppress obvious passwords.) - -\lcont{ -\cw{\-sshlog} logs decoded SSH packets and other events (those that -\cw{\-v} would print). \cw{\-sshrawlog} additionally logs the raw -encrypted packet data. -} - -\dt \cw{\-logoverwrite} - -\dd If Plink is configured to write to a log file that already exists, -discard the existing file. - -\dt \cw{\-logappend} - -\dd If Plink is configured to write to a log file that already exists, -append new log data to the existing file. - -\dt \cw{\-shareexists} - -\dd Instead of making a new connection, test for the presence of an -existing connection that can be shared. The desired session can be -specified in any of the usual ways. - -\lcont{ -Returns immediately with a zero exit status if a suitable \q{upstream} -exists, nonzero otherwise. -} - -\S{plink-manpage-more-information} MORE INFORMATION - -For more information on plink, it's probably best to go and look at -the manual on the PuTTY web page: - -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} - -\S{plink-manpage-bugs} BUGS - -This man page isn't terribly complete. See the above web link for -better documentation. diff --git a/doc/man-pscp.but b/doc/man-pscp.but deleted file mode 100644 index 544d3a405..000000000 --- a/doc/man-pscp.but +++ /dev/null @@ -1,226 +0,0 @@ -\cfg{man-identity}{pscp}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} - -\H{pscp-manpage} Man page for PSCP - -\S{pscp-manpage-name} NAME - -\cw{pscp} \- command-line SCP (secure copy) / SFTP client - -\S{pscp-manpage-synopsis} SYNOPSIS - -\c pscp [options] [user@]host:source target -\e bbbb iiiiiii iiiib iiiibiiiiii iiiiii -\c pscp [options] source [source...] [user@]host:target -\e bbbb iiiiiii iiiiii iiiiii iiiib iiiibiiiiii -\c pscp [options] -ls [user@]host:filespec -\e bbbb iiiiiii bbb iiiib iiiibiiiiiiii - -\S{pscp-manpage-description} DESCRIPTION - -\cw{pscp} is a command-line client for the SSH-based SCP (secure -copy) and SFTP (secure file transfer protocol) protocols. - -\S{pscp-manpage-options} OPTIONS - -The command-line options supported by \e{pscp} are: - -\dt \cw{-V} - -\dd Show version information and exit. - -\dt \cw{-pgpfp} - -\dd Display the fingerprints of the PuTTY PGP Master Keys and exit, -to aid in verifying new files released by the PuTTY team. - -\dt \cw{-ls} - -\dd Remote directory listing. - -\dt \cw{-p} - -\dd Preserve file attributes. - -\dt \cw{-q} - -\dd Quiet, don't show statistics. - -\dt \cw{-r} - -\dd Copy directories recursively. - -\dt \cw{-unsafe} - -\dd Allow server-side wildcards (DANGEROUS). - -\dt \cw{-v} - -\dd Show verbose messages. - -\dt \cw{-load} \e{session} - -\dd Load settings from saved session. - -\dt \cw{-P} \e{port} - -\dd Connect to port \e{port}. - -\dt \cw{\-proxycmd} \e{command} - -\dd Instead of making a TCP connection, use \e{command} as a proxy; -network traffic will be redirected to the standard input and output -of \e{command}. \e{command} must be a single word, so is likely to -need quoting by the shell. - -\lcont{ -The special strings \cw{%host} and \cw{%port} in \e{command} will be -replaced by the hostname and port number you want to connect to; to get -a literal \c{%} sign, enter \c{%%}. - -Backslash escapes are also supported, such as sequences like \c{\\n} -being replaced by a literal newline; to get a literal backslash, -enter \c{\\\\}. (Further escaping may be required by the shell.) - -(See the main PuTTY manual for full details of the supported \cw{%}- -and backslash-delimited tokens, although most of them are probably not -very useful in this context.) -} - -\dt \cw{-l} \e{user} - -\dd Set remote username to \e{user}. - -\dt \cw{-batch} - -\dd Disable interactive prompts. - -\dt \cw{-no-sanitise-stderr} - -\dd By default, PSCP will filter control characters from the standard error -channel from the server, to prevent remote processes sending confusing -escape sequences. This option forces the standard error channel to not be -filtered. - -\dt \cw{-pwfile} \e{filename} - -\dd Open the specified file, and use the first line of text read from -it as the remote password. - -\dt \cw{-pw} \e{password} - -\dd Set remote password to \e{password}. \e{CAUTION:} this will likely -make the password visible to other users of the local machine (via -commands such as \q{\c{ps}} or \q{\c{w}}). Use \cw{-pwfile} instead. - -\dt \cw{-1} - -\dd Force use of SSH protocol version 1. - -\dt \cw{-2} - -\dd Force use of SSH protocol version 2. - -\dt \cw{-ssh-connection} - -\dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is -only likely to be useful when connecting to a \cw{psusan}(\e{1}) -server, most likely with an absolute path to a Unix-domain socket in -place of \e{host}. - -\dt \cw{-ssh} - -\dd Force use of the SSH protocol. (This is usually not needed; it's -only likely to be useful if you need to override some other -configuration of the \q{bare \cw{ssh-connection}} protocol.) - -\dt \cw{-4}, \cw{-6} - -\dd Force use of IPv4 or IPv6 for network connections. - -\dt \cw{-C} - -\dd Enable SSH compression. - -\dt \cw{-i} \e{keyfile} - -\dd Private key file for user authentication. For SSH-2 keys, this key -file must be in PuTTY's PPK format, not OpenSSH's format or anyone -else's. - -\lcont{ If you are using an authentication agent, you can also specify -a \e{public} key here (in RFC 4716 or OpenSSH format), to identify -which of the agent's keys to use. } - -\dt \cw{\-noagent} - -\dd Don't try to use an authentication agent. - -\dt \cw{\-agent} - -\dd Allow use of an authentication agent. (This option is only necessary -to override a setting in a saved session.) - -\dt \cw{\-no\-trivial\-auth} - -\dd Disconnect from any SSH server which accepts authentication without -ever having asked for any kind of password or signature or token. (You -might want to enable this for a server you always expect to challenge -you, for instance to ensure you don't accidentally type your key file's -passphrase into a compromised server spoofing PSCP's passphrase prompt.) - -\dt \cw{\-hostkey} \e{key} - -\dd Specify an acceptable host public key. This option may be specified -multiple times; each key can be either a fingerprint (\cw{SHA256:AbCdE...}, -\cw{99:aa:bb:...}, etc) or a base64-encoded blob in OpenSSH's one-line -format. - -\lcont{ Specifying this option overrides automated host key -management; \e{only} the key(s) specified on the command-line will be -accepted (unless a saved session also overrides host keys, in which -case those will be added to), and the host key cache will not be -written. } - -\dt \cw{-scp} - -\dd Force use of SCP protocol. - -\dt \cw{-sftp} - -\dd Force use of SFTP protocol. - -\dt \cw{\-sshlog} \e{logfile} - -\dt \cw{\-sshrawlog} \e{logfile} - -\dd These options make \cw{pscp} log protocol details to a file. -(Some of these may be sensitive, although by default an effort is made -to suppress obvious passwords.) - -\lcont{ -\cw{\-sshlog} logs decoded SSH packets and other events (those that -\cw{\-v} would print). \cw{\-sshrawlog} additionally logs the raw -encrypted packet data. -} - -\dt \cw{\-logoverwrite} - -\dd If PSCP is configured to write to a log file that already exists, -discard the existing file. - -\dt \cw{\-logappend} - -\dd If PSCP is configured to write to a log file that already exists, -append new log data to the existing file. - -\S{pscp-manpage-more-information} MORE INFORMATION - -For more information on \cw{pscp} it's probably best to go and look at -the manual on the PuTTY web page: - -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} - -\S{pscp-manpage-bugs} BUGS - -This man page isn't terribly complete. See the above web link for -better documentation. diff --git a/doc/man-psftp.but b/doc/man-psftp.but deleted file mode 100644 index e0b486020..000000000 --- a/doc/man-psftp.but +++ /dev/null @@ -1,212 +0,0 @@ -\cfg{man-identity}{psftp}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} - -\H{psftp-manpage} Man page for PSFTP - -\S{psftp-manpage-name} NAME - -\cw{psftp} \- interactive SFTP (secure file transfer protocol) client - -\S{psftp-manpage-synopsis} SYNOPSIS - -\c psftp [options] [user@]host -\e bbbbb iiiiiii iiiib iiii - -\S{psftp-manpage-description} DESCRIPTION - -\cw{psftp} is an interactive text-based client for the SSH-based SFTP -(secure file transfer) protocol. - -\S{psftp-manpage-options} OPTIONS - -The command-line options supported by \cw{psftp} are: - -\dt \cw{-V} - -\dd Show version information and exit. - -\dt \cw{-pgpfp} - -\dd Display the fingerprints of the PuTTY PGP Master Keys and exit, -to aid in verifying new files released by the PuTTY team. - -\dt \cw{-b} \e{batchfile} - -\dd Use specified batchfile. - -\dt \cw{-bc} - -\dd Output batchfile commands. - -\dt \cw{-be} - -\dd Don't stop batchfile processing on errors. - -\dt \cw{-v} - -\dd Show verbose messages. - -\dt \cw{-load} \e{session} - -\dd Load settings from saved session. - -\dt \cw{-P} \e{port} - -\dd Connect to port \e{port}. - -\dt \cw{\-proxycmd} \e{command} - -\dd Instead of making a TCP connection, use \e{command} as a proxy; -network traffic will be redirected to the standard input and output -of \e{command}. \e{command} must be a single word, so is likely to -need quoting by the shell. - -\lcont{ -The special strings \cw{%host} and \cw{%port} in \e{command} will be -replaced by the hostname and port number you want to connect to; to get -a literal \c{%} sign, enter \c{%%}. - -Backslash escapes are also supported, such as sequences like \c{\\n} -being replaced by a literal newline; to get a literal backslash, -enter \c{\\\\}. (Further escaping may be required by the shell.) - -(See the main PuTTY manual for full details of the supported \cw{%}- -and backslash-delimited tokens, although most of them are probably not -very useful in this context.) -} - -\dt \cw{-l} \e{user} - -\dd Set remote username to \e{user}. - -\dt \cw{-batch} - -\dd Disable interactive prompts. - -\dt \cw{-no-sanitise-stderr} - -\dd By default, PSFTP will filter control characters from the standard error -channel from the server, to prevent remote processes sending confusing -escape sequences. This option forces the standard error channel to not be -filtered. - -\dt \cw{-pwfile} \e{filename} - -\dd Open the specified file, and use the first line of text read from -it as the remote password. - -\dt \cw{-pw} \e{password} - -\dd Set remote password to \e{password}. \e{CAUTION:} this will likely -make the password visible to other users of the local machine (via -commands such as \q{\c{ps}} or \q{\c{w}}). Use \cw{-pwfile} instead. - -\dt \cw{-1} - -\dd Force use of SSH protocol version 1. - -\dt \cw{-2} - -\dd Force use of SSH protocol version 2. - -\dt \cw{-ssh-connection} - -\dd Force use of the \q{bare \cw{ssh-connection}} protocol. This is -only likely to be useful when connecting to a \cw{psusan}(\e{1}) -server, most likely with an absolute path to a Unix-domain socket in -place of \e{host}. - -\dt \cw{-ssh} - -\dd Force use of the SSH protocol. (This is usually not needed; it's -only likely to be useful if you need to override some other -configuration of the \q{bare \cw{ssh-connection}} protocol.) - -\dt \cw{-4}, \cw{-6} - -\dd Force use of IPv4 or IPv6 for network connections. - -\dt \cw{-C} - -\dd Enable SSH compression. - -\dt \cw{-i} \e{keyfile} - -\dd Private key file for user authentication. For SSH-2 keys, this key -file must be in PuTTY's PPK format, not OpenSSH's format or anyone -else's. - -\lcont{ If you are using an authentication agent, you can also specify -a \e{public} key here (in RFC 4716 or OpenSSH format), to identify -which of the agent's keys to use. } - -\dt \cw{\-noagent} - -\dd Don't try to use an authentication agent. - -\dt \cw{\-agent} - -\dd Allow use of an authentication agent. (This option is only necessary -to override a setting in a saved session.) - -\dt \cw{\-no\-trivial\-auth} - -\dd Disconnect from any SSH server which accepts authentication without -ever having asked for any kind of password or signature or token. (You -might want to enable this for a server you always expect to challenge -you, for instance to ensure you don't accidentally type your key file's -passphrase into a compromised server spoofing PSFTP's passphrase -prompt.) - -\dt \cw{\-hostkey} \e{key} - -\dd Specify an acceptable host public key. This option may be specified -multiple times; each key can be either a fingerprint (\cw{SHA256:AbCdE...}, -\cw{99:aa:bb:...}, etc) or a base64-encoded blob in OpenSSH's one-line -format. - -\lcont{ Specifying this option overrides automated host key -management; \e{only} the key(s) specified on the command-line will be -accepted (unless a saved session also overrides host keys, in which -case those will be added to), and the host key cache will not be -written. } - -\dt \cw{\-sshlog} \e{logfile} - -\dt \cw{\-sshrawlog} \e{logfile} - -\dd These options make \cw{psftp} log protocol details to a file. -(Some of these may be sensitive, although by default an effort is made -to suppress obvious passwords.) - -\lcont{ -\cw{\-sshlog} logs decoded SSH packets and other events (those that -\cw{\-v} would print). \cw{\-sshrawlog} additionally logs the raw -encrypted packet data. -} - -\dt \cw{\-logoverwrite} - -\dd If PSFTP is configured to write to a log file that already exists, -discard the existing file. - -\dt \cw{\-logappend} - -\dd If PSFTP is configured to write to a log file that already exists, -append new log data to the existing file. - -\S{psftp-manpage-commands} COMMANDS - -For a list of commands available inside \cw{psftp}, type \cw{help} -at the \cw{psftp>} prompt. - -\S{psftp-manpage-more-information} MORE INFORMATION - -For more information on \cw{psftp} it's probably best to go and look at -the manual on the PuTTY web page: - -\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} - -\S{psftp-manpage-bugs} BUGS - -This man page isn't terribly complete. See the above web link for -better documentation. diff --git a/doc/man-psocks.but b/doc/man-psocks.but deleted file mode 100644 index eb075a6e0..000000000 --- a/doc/man-psocks.but +++ /dev/null @@ -1,92 +0,0 @@ -\cfg{man-identity}{psocks}{1}{2021-04-08}{PuTTY tool suite}{PuTTY tool suite} - -\H{psocks-manpage} Man page for \cw{psocks} - -\S{psocks-manpage-name} NAME - -\cw{psocks} \- simple SOCKS proxy server - -\S{psocks-manpage-synopsis} SYNOPSIS - -\c psocks [ -d ] [ -f | -p pipe-cmd ] [ -g ] [ port-number ] -\e bbbbbb bb bb bb iiiiiiii bb iiiiiiiiiii - -\S{psocks-manpage-description} DESCRIPTION - -\cw{psocks} is a simple SOCKS4/5 proxy server. It supports proxying -IPv4 and IPv6 connections. It does not support requiring -authentication of its clients. - -\cw{psocks} can be used together with an SSH client such as -\cw{putty}(\e{1}) to implement a reverse dynamic SSH tunnel. It can -also be used for network protocol debugging, as it can record all the -traffic passing through it in various ways. - -By default, \cw{psocks} listens to connections from localhost only, -on TCP port 1080. A different \e{port-number} can optionally be -supplied, and with \cw{-g} it will listen to connections from any -host. - -\cw{psocks} will emit log messages about connections it receives on -standard error. With \cw{-d}, it will log the contents of those -connections too. - -\S{psocks-manpage-options} OPTIONS - -The command-line options supported by \cw{psocks} are: - -\dt \cw{-g} - -\dd Accept connections from anywhere. By default, \cw{psocks} only -accepts connections on the loopback interface. - -\dt \cw{--exec} \e{command} - -\dd \cw{psocks} will run the provided command as a subprocess. When -the subprocess terminates, \cw{psocks} will terminate as well. - -\lcont{ - -All arguments on the \cw{psocks} command line after \cw{--exec} will be -treated as part of the command to run, even if they look like other -valid \cw{psocks} options. - -} - -\dt \cw{-d} - -\dd Log all traffic to standard error, in a more or less human-readable -form (in addition to messages about connections being opened and -closed, which are always logged). - -\dt \cw{-f} - -\dd Record all traffic to files. For every incoming connection, two -files are created, \cw{sockout.NNNN} and \cw{sockin.NNNN}, where -\e{NNNN} is a decimal index starting at 0 identifying the proxied -connection. These record, respectively, traffic from the SOCKS client, -and from the server it connected to through the proxy. - -\dt \cw{-p} \e{pipe-cmd} - -\dd Pipe all traffic to a command. For every incoming connection, -\e{pipe-cmd} is invoked twice: - -\lcont{ -\c pipe-cmd out N -\e iiiiiiii bbb i -\c pipe-cmd in N -\e iiiiiiii bb i - -Each command will run for the direction of a proxied connection, and -have the connection's traffic piped into it, similar to \cw{-f}. -} - -\S{psocks-manpage-examples} EXAMPLES - -In combination with the \cw{plink}(\e{1}) SSH client, to set up a -reverse dynamic SSH tunnel, in which the remote listening port 1080 on -remote host \cw{myhost} acts as a SOCKS server giving access to your -local network: - -\c psocks 12345 --exec plink -R 1080:localhost:12345 user@myhost diff --git a/doc/man-psusan.but b/doc/man-psusan.but deleted file mode 100644 index 64d3a0303..000000000 --- a/doc/man-psusan.but +++ /dev/null @@ -1,429 +0,0 @@ -\cfg{man-identity}{psusan}{1}{2020-12-13}{PuTTY tool suite}{PuTTY tool suite} - -\H{psusan-manpage} Man page for \cw{psusan} - -\S{psusan-manpage-name} NAME - -\cw{psusan} \- pseudo-SSH for untappable, separately authenticated networks - -\S{psusan-manpage-synopsis} SYNOPSIS - -\c psusan [ options ] -\e bbbbbb iiiiiii - -\S{psusan-manpage-description} DESCRIPTION - -\cw{psusan} is a server program that behaves like the innermost -\q{connection} layer of an SSH session, without the two outer security -layers of encryption and authentication. It provides all the -post-authentication features of an SSH connection: - -\b choosing whether to run an interactive terminal session or a single -specified command - -\b multiple terminal sessions at once (or a mixture of those and -specified commands) - -\b SFTP file transfer - -\b all the standard SSH port-forwarding options - -\b X11 forwarding - -\b SSH agent forwarding - -The catch is that, because it lacks the outer layers of SSH, you have -to run it over some kind of data channel that is already authenticated -as the right user, and that is already protected to your satisfaction -against eavesdropping and session hijacking. A good rule of thumb is -that any channel that you were prepared to run a \e{bare} shell -session over, you can run \cw{psusan} over instead, which adds all the -above conveniences without changing the security properties. - -The protocol that \cw{psusan} speaks is also spoken by PuTTY, Plink, -PSCP, and PSFTP, if you select the protocol type \q{Bare ssh-connection} -or the command-line option \cw{-ssh-connection} and specify the -absolute path to the appropriate Unix-domain socket in place of -a hostname. - -\S{psusan-manpage-examples} EXAMPLES - -The idea of a secure, pre-authenticated data channel seems strange to -people thinking about \e{network} connections. But there are lots of -examples within the context of a single Unix system, and that's where -\cw{psusan} is typically useful. - -\S2{psusan-manpage-examples-docker} Docker - -A good example is the console or standard I/O channel leading into a -container or virtualisation system. Docker is a familiar example. If -you want to start a Docker container and run a shell directly within -it, you might say something like - -\c docker run -i -t some:image -\e iiiiiiiiii - -which will allow you to run a single shell session inside the -container, in the same terminal you started Docker from. - -Suppose that you'd prefer to run \e{multiple} shell sessions in the -same container at once (perhaps so that one of them can use debugging -tools to poke at what another is doing). And perhaps inside that -container you're going to run a program that you don't trust with full -access to your network, but are prepared to let it make one or two -specific network connections of the kind you could set up with an SSH -port forwarding. - -In that case, you could remove the \cw{-t} option from that Docker -command line (which means \q{allocate a terminal device}), and tell it -to run \cw{psusan} inside the container: - -\c docker run -i some:image /some/path/to/psusan -\e iiiiiiiiii iiiiiiiiiiii - -(Of course, you'll need to ensure that \cw{psusan} is installed -somewhere inside the container image.) - -If you do that from a shell command line, you'll see a banner line -looking something like this: - -\c SSHCONNECTION@putty.projects.tartarus.org-2.0-PSUSAN_Release_0.75 - -which isn't particularly helpful except that it tells you that -\cw{psusan} has started up successfully. - -To talk to this server \e{usefully}, you can set up a PuTTY saved -session as follows: - -\b Set the protocol to \q{Bare ssh-connection} (the \cw{psusan} -protocol). - -\b Write \e{something} in the hostname box. It will appear in PuTTY's -window title (if you run GUI PuTTY), so you might want to write -something that will remind you what kind of window it is. If you have -no opinion, something generic like \cq{dummy} will do. - -\b In the \q{Proxy} configuration panel, set the proxy type to -\q{Local}, and enter the above \cq{docker run} command in the -\q{Telnet command, or local proxy command} edit box. - -\b In the \q{SSH} configuration panel, you will very likely want to -turn on connection sharing. (See below.) - -This arranges that when PuTTY starts up, it will run the Docker -command as shown above in place of making a network connection, and -talk to that command using the \cw{psusan} SSH-like protocol. - -The effect is that you will still get a shell session in the context -of a Docker container. But this time, it's got all the SSH amenities. -If you also turn on connection sharing in the \q{SSH} configuration -panel, then the \q{Duplicate Session} option will get you a second -shell in the \e{same} Docker container (instead of a primary shell in -a separate instance). You can transfer files in and out of the -container while it's running using PSCP or PSFTP; you can forward -network ports, X11 programs, and/or an SSH agent to the container. - -Of course, another way to do all of this would be to run the \e{full} -SSH protocol over the same channel. This involves more setup: you have -to invent an SSH host key for the container, accept it in the client, -and deal with it being left behind in your client's host key cache -when the container is discarded. And you have to set up some login -details in the container: either configure a password, and type it in -the client, or copy in the public half of some SSH key you already -had. And all this inconvenience is \e{unnecessary}, because these are -all precautions you need to take when the connection between two -systems is going over a hostile network. In this case, it's only going -over a kernel IPC channel that's guaranteed to go to the right place, -so those safety precautions are redundant, and they only add -awkwardness. - -\S2{psusan-manpage-examples-uml} User-mode Linux - -User-mode Linux is another container type you can talk to in the same -way. Here's a small worked example. - -The \e{easiest} way to run UML is to use its \cq{hostfs} file system -type to give the guest kernel access to the same virtual filesystem as -you have on the host. For example, a command line like this gets you a -shell prompt inside a UML instance sharing your existing filesystem: - -\c linux mem=512M rootfstype=hostfs rootflags=/ rw init=/bin/bash - -If you run this at a command line (assuming you have a UML kernel -available on your path under the name \cq{linux}), then you should see -a lot of kernel startup messages, followed by a shell prompt along the -lines of - -\c root@(none):/# - -To convert this into a \cw{psusan}-based UML session, we need to -adjust the command line so that instead of running \cw{bash} it runs -\cw{psusan}. But running \cw{psusan} directly isn't quite enough, -because \cw{psusan} will depend on a small amount of setup, such as -having \cw{/proc} mounted. So instead, we set the init process to a -shell script which will do the necessary setup and \e{then} invoke -\cw{psusan}. - -Also, running \cw{psusan} directly over the UML console device is a -bad idea, because then the \cw{psusan} binary protocol will be mixed -with textual console messages. So a better plan is to redirect UML's -console to the standard error of the \cw{linux} process, and map its -standard input and output to a serial port. So the replacement UML -command line might look something like this: - -\c linux mem=512M rootfstype=hostfs rootflags=/ rw \ -\c con=fd:2,fd:2 ssl0=fd:0,fd:1 init=/some/path/to/uml-psusan.sh -\e iiiiiiiiiiiiiiiiiiiiiiiiiii - -And the setup script \cw{uml-psusan.sh} might look like this: - -\c #!/bin/bash -\c # Set up vital pseudo-filesystems -\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii -\c mount -t proc none /proc -\c mount -t devpts none /dev/pts -\c # Redirect I/O to the serial port, but stderr to the console -\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii -\c exec 0<>/dev/ttyS0 1>&0 2>/dev/console -\c # Set the serial port into raw mode, to run a binary protocol -\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii -\c stty raw -echo -\c # Choose what shell you want to run inside psusan -\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii -\c export SHELL=/bin/bash -\c # Set up a default path -\e iiiiiiiiiiiiiiiiiiiiiii -\c export PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin -\c # And now run psusan over the serial port -\e iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii -\c exec /home/simon/src/putty/misc/psusan - -Now set up a PuTTY saved session as in the Docker example above. -Basically you'll want to use the above \cw{linux} command as the local -proxy command. However, it's worth wrapping it in \cw{setsid}(\e{1}), -because when UML terminates, it kills its entire process group. So -it's better that PuTTY should not be part of that group, and should -have the opportunity to shut down cleanly by itself. So probably you -end up setting the proxy command to be something more like: - -\c setsid linux mem=512M rootfstype=hostfs rootflags=/ rw \ -\c con=fd:2,fd:2 ssl0=fd:0,fd:1 init=/some/path/to/uml-psusan.sh -\e iiiiiiiiiiiiiiiiiiiiiiiiiii - -You may also find that you have to enable the bug workaround that -indicates that the server \q{Discards data sent before its greeting}, -because otherwise PuTTY's outgoing protocol greeting can be -accidentally lost during UML startup. (See -\W{https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958}{Debian -bug #991958}.) - -Once you've done that, you'll have a PuTTY session that starts up a -clean UML instance when you run it, and (if you enabled connection -sharing) further instances of the same session will connect to the -same instance again. - -\S2{psusan-manpage-examples-wsl} Windows Subsystem for Linux - -On Windows, the default way to use WSL is to run the \cw{wsl} program, -or one of its aliases, in a Windows console, either by launching it -from an existing command prompt, or by using a shortcut that opens it -in a fresh console. This gives you a Linux terminal environment, but -in a Windows console window. - -If you'd prefer to interact with the same environment using PuTTY as -the terminal (for example, if you prefer PuTTY's mouse shortcuts for -copy and paste), you can set it up by installing \cw{psusan} in the -Linux environment, and then setting up a PuTTY saved session that -talks to it. A nice way to do this is to use the name of the WSL -distribution as the \q{host name}: - -\b set the local proxy command to \cq{wsl -d %host -/usr/local/bin/psusan} (or wherever you installed \cw{psusan} in the -Linux system) - -\b enter the name of a particular WSL distribution in the host name -box. (For example, if you installed WSL Debian in the standard way -from the Windows store, this will just be \q{Debian}.) - -\b set the protocol to \q{Bare ssh-connection}, as usual. - -Like all the other examples here, this also permits you to forward -ports in and out of the WSL environment (e.g. expose a WSL2 network -service through the hypervisor's internal NAT), forward Pageant into -it, and so on. - -\S2{psusan-manpage-examples-cygwin} Cygwin - -Another Unix-like environment on Windows is Cygwin. That comes with -its own GUI terminal application, \cw{mintty} (as it happens, a -derivative of PuTTY); but if you'd prefer to use PuTTY itself to talk -to your Cygwin terminal sessions, \cw{psusan} can help. - -To do this, you'll first need to build the Unix PuTTY tools inside -Cygwin (via the usual \cw{cmake} method). Then, copy the resulting -\cw{psusan.exe} into Cygwin's \cw{/bin} directory. (It has to be -in that directory for non-Cygwin programs to run it; otherwise it -won't be able to find the Cygwin DLL at startup.) - -Then set up your PuTTY saved session like this: - -\b set the local proxy command to run \cw{psusan.exe} via its real -Windows path. You might also want to add the \cw{--sessiondir} option -so that shell sessions start up in your Cygwin home directory. For -example, you might use the command \cq{c:\\cygwin64\\bin\\psusan.exe ---sessiondir /home/simon} (changing the pathname and username to match -your setup). - -\b enter anything you like in the host name box; \cq{Cygwin} is -probably a good choice - -\b set the protocol to \q{Bare ssh-connection}, as usual. - -Port forwarding is probably not particularly useful in this case, -since Cygwin shares the same network port space as the host machine. -But turning on agent forwarding is useful, because then the Cygwin -command-line SSH client can talk to Pageant without any further -configuration. - -\S2{psusan-manpage-examples-schroot} \cw{schroot} - -Another example of a container-like environment is the alternative -filesystem layout set up by \cw{schroot}(\e{1}). - -\cw{schroot} is another program that defaults to running an -interactive shell session in the terminal you launched it from. But -again, you can get a \cw{psusan} connection into the \cw{schroot} -environment by setting up a PuTTY saved session whose local proxy -command is along the lines of - -\c schroot -c chroot-name /some/path/to/psusan -\e iiiiiiiiiii iiiiiiiiiiii - -Depending on how much of the chroot environment is copied from your -main one, you might find this makes it easier to (for example) run X11 -programs inside the chroot that open windows on your main X display, -or transfer files in and out of the chroot. - -\S2{psusan-manpage-examples-namespace} Between network namespaces - -If you've set up multiple network namespaces on a Linux system, with -different TCP/IP configurations, then \cw{psusan} can be a convenient -unprivileged-user gateway between them, if you run it as a non-root -user in the non-default one of your namespaces, listening for -connections on a Unix-domain socket. - -If you do that, then it gives you convenient control over which of -your outgoing network connections use which TCP/IP configuration: you -can use PuTTY to run a shell session in the context of the other -namespace if you want to run commands like \cw{ping}, or you can set -up individual port forwardings or even a SOCKS server so that -processes running in one namespace can send their network connections -via the other one. - -For this application, it's probably most convenient to use the -\cw{--listen} option in \cw{psusan}, which makes it run as a server -and listen for connections on a Unix-domain socket. Then you can enter -that socket name in PuTTY's host name configuration field (and also -still select the \q{Bare ssh-connection} protocol option), to connect -to that socket as if it were an SSH client. - -Provided the Unix-domain socket is inside a directory that only the -right user has access to, this will ensure that authentication is done -implicitly by the Linux kernel. - -\S2{psusan-manpage-examples-userv} Between user ids, via GNU userv - -If you use multiple user ids on the same machine, say for purposes of -privilege separation (running some less-trusted program with limited -abilities to access all your stuff), then you probably have a -\q{default} or most privileged account where you run your main login -session, and sometimes need to run a shell in another account. - -\cw{psusan} can be used as an access channel between the accounts, -using GNU \cw{userv}(\e{1}) as the transport. In the account you want -to access, write a \cw{userv} configuration stanza along the lines of - -\c if (glob service psusan & glob calling-user my-main-account-name) -\e iiiiiiiiiiiiiiiiiiii -\c reset -\c execute /some/path/to/psusan -\e iiiiiiiiiiii -\c fi - -This gives your main account the right to run the command - -\c userv my-sub-account-name psusan -\e iiiiiiiiiiiiiiiiiii - -and you can configure that command name as a PuTTY local proxy -command, in the same way as most of the previous examples. - -Of course, there are plenty of ways already to access one local -account from another, such as \cw{sudo}. One advantage of doing it -this way is that you don't need the system administrator to intervene -when you want to change the access controls (e.g. change which of your -accounts have access to another): as long as you have \e{some} means -of getting into each account in the first place, and \cw{userv} is -installed, you can make further configuration changes without having -to bother root about it. - -Another advantage is that it might make file transfer between the -accounts easier. If you're the kind of person who keeps your home -directories private, then it's awkward to copy a file from one of your -accounts to another just by using the \cw{cp} command, because there's -nowhere convenient that you can leave it in one account where the -other one can read it. But with \cw{psusan} over \cw{userv}, you don't -need any shared piece of filesystem: you can \cw{scp} files back and -forth without any difficulty. - -\S{psusan-manpage-options} OPTIONS - -The command-line options supported by \cw{psusan} are: - -\dt \cw{--listen} \e{unix-socket-name} - -\dd Run \cw{psusan} in listening mode. \e{unix-socket-name} is the -pathname of a Unix-domain socket to listen on. You should ensure that -this pathname is inside a directory whose read and exec permissions -are restricted to only the user(s) you want to be able to access the -environment that \cw{psusan} is running in. - -\lcont{ - -The listening socket has to be a Unix-domain socket. \cw{psusan} does -not provide an option to run over TCP/IP, because the unauthenticated -nature of the protocol would make it inherently insecure. - -} - -\dt \cw{--listen-once} - -\dd In listening mode, this option causes \cw{psusan} to listen for -only one connection, and exit immediately after that connection -terminates. - -\dt \cw{--sessiondir} \e{pathname} - -\dd This option sets the directory that shell sessions and -subprocesses will start in. By default it is \cw{psusan}'s own working -directory, but in some situations it's easier to change it with a -command-line option than by wrapping \cw{psusan} in a script that -changes directory before starting it. - -\dt \cw{-v}, \cw{--verbose} - -\dd This option causes \cw{psusan} to print verbose log messages on -its standard error. This is probably most useful in listening mode. - -\dt \cw{\-sshlog} \e{logfile} - -\dt \cw{\-sshrawlog} \e{logfile} - -\dd These options cause \cw{psusan} to log protocol details to a file, -similarly to the logging options in PuTTY and Plink. - -\lcont{ -\cw{\-sshlog} logs decoded SSH packets and other events (those that -\cw{\-v} would print). \cw{\-sshrawlog} additionally logs the raw wire -data, including the outer packet format and the initial greetings. -} diff --git a/doc/man-pterm.but b/doc/man-pterm.but deleted file mode 100644 index d3d1d96a4..000000000 --- a/doc/man-pterm.but +++ /dev/null @@ -1,678 +0,0 @@ -\cfg{man-identity}{pterm}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} - -\H{pterm-manpage} Man page for pterm - -\S{pterm-manpage-name} NAME - -pterm \- yet another X terminal emulator - -\S{pterm-manpage-synopsis} SYNOPSIS - -\c pterm [ options ] -\e bbbbb iiiiiii - -\S{pterm-manpage-description} DESCRIPTION - -\cw{pterm} is a terminal emulator for X. It is based on a port of -the terminal emulation engine in the Windows SSH client PuTTY. - -\S{pterm-manpage-options} OPTIONS - -The command-line options supported by \cw{pterm} are: - -\dt \cw{\-e} \e{command} [ \e{arguments} ] - -\dd Specify a command to be executed in the new terminal. Everything on -the command line after this option will be passed straight to the -\cw{execvp} system call; so if you need the command to redirect its -input or output, you will have to use \cw{sh}: - -\lcont{ - -\c pterm -e sh -c 'mycommand < inputfile' - -} - -\dt \cw{\-\-display} \e{display\-name} - -\dd Specify the X display on which to open \cw{pterm}. (Note this -option has a double minus sign, even though none of the others do. -This is because this option is supplied automatically by GTK. -Sorry.) - -\dt \cw{\-name} \e{name} - -\dd Specify the name under which \cw{pterm} looks up X resources. -Normally it will look them up as (for example) \cw{pterm.Font}. If -you specify \q{\cw{\-name xyz}}, it will look them up as -\cw{xyz.Font} instead. This allows you to set up several different -sets of defaults and choose between them. - -\dt \cw{\-fn} \e{font-name} - -\dd Specify the font to use for normal text displayed in the terminal. -For example, \cw{\-fn\_fixed}, \cw{\-fn\_"Monospace\_12"}. - -\dt \cw{\-fb} \e{font-name} - -\dd Specify the font to use for bold text displayed in the terminal. If -the \cw{BoldAsColour} resource is set to 1 (the default), bold text -will be displayed in different colours instead of a different font, -so this option will be ignored. If \cw{BoldAsColour} is set to 0 or 2 -and you do not specify a bold font, \cw{pterm} will overprint the -normal font to make it look bolder. - -\dt \cw{\-fw} \e{font-name} - -\dd Specify the font to use for double-width characters (typically -Chinese, Japanese and Korean text) displayed in the terminal. - -\dt \cw{\-fwb} \e{font-name} - -\dd Specify the font to use for bold double-width characters -(typically Chinese, Japanese and Korean text). Like \cw{-fb}, this -will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2. - -\dt \cw{\-geometry} \e{geometry} - -\dd Specify the size of the terminal, in rows and columns of text. See -\cw{X}(\e{7}) for more information on the syntax of geometry -specifications. - -\dt \cw{\-sl} \e{lines} - -\dd Specify the number of lines of scrollback to save off the top of the -terminal. - -\dt \cw{\-fg} \e{colour} - -\dd Specify the foreground colour to use for normal text. - -\dt \cw{\-bg} \e{colour} - -\dd Specify the background colour to use for normal text. - -\dt \cw{\-bfg} \e{colour} - -\dd Specify the foreground colour to use for bold text, if the -\cw{BoldAsColour} resource is set to 1 (the default) or 2. - -\dt \cw{\-bbg} \e{colour} - -\dd Specify the foreground colour to use for bold reverse-video text, if -the \cw{BoldAsColour} resource is set to 1 (the default) or 2. (This -colour is best thought of as the bold version of the background -colour; so it only appears when text is displayed \e{in} the -background colour.) - -\dt \cw{\-cfg} \e{colour} - -\dd Specify the foreground colour to use for text covered by the cursor. - -\dt \cw{\-cbg} \e{colour} - -\dd Specify the background colour to use for text covered by the cursor. -In other words, this is the main colour of the cursor. - -\dt \cw{\-title} \e{title} - -\dd Specify the initial title of the terminal window. (This can be -changed under control of the server.) - -\dt \cw{\-ut\-} or \cw{+ut} - -\dd Tells \cw{pterm} not to record your login in the \cw{utmp}, -\cw{wtmp} and \cw{lastlog} system log files; so you will not show -up on \cw{finger} or \cw{who} listings, for example. - -\dt \cw{\-ut} - -\dd Tells \cw{pterm} to record your login in \cw{utmp}, \cw{wtmp} and -\cw{lastlog}: this is the opposite of \cw{\-ut\-}. This is the -default option: you will probably only need to specify it explicitly -if you have changed the default using the \cw{StampUtmp} resource. - -\dt \cw{\-ls\-} or \cw{+ls} - -\dd Tells \cw{pterm} not to execute your shell as a login shell. - -\dt \cw{\-ls} - -\dd Tells \cw{pterm} to execute your shell as a login shell: this is -the opposite of \cw{\-ls\-}. This is the default option: you will -probably only need to specify it explicitly if you have changed the -default using the \cw{LoginShell} resource. - -\dt \cw{\-sb\-} or \cw{+sb} - -\dd Tells \cw{pterm} not to display a scroll bar. - -\dt \cw{\-sb} - -\dd Tells \cw{pterm} to display a scroll bar: this is the opposite of -\cw{\-sb\-}. This is the default option: you will probably only need -to specify it explicitly if you have changed the default using the -\cw{ScrollBar} resource. - -\dt \cw{\-log} \e{logfile}, \cw{\-sessionlog} \e{logfile} - -\dd This option makes \cw{pterm} log all the terminal output to a file -as well as displaying it in the terminal. - -\dt \cw{\-cs} \e{charset} - -\dd This option specifies the character set in which \cw{pterm} should -assume the session is operating. This character set will be used to -interpret all the data received from the session, and all input you -type or paste into \cw{pterm} will be converted into this character -set before being sent to the session. - -\lcont{ Any character set name which is valid in a MIME header (and -supported by \cw{pterm}) should be valid here (examples are -\q{\cw{ISO-8859-1}}, \q{\cw{windows-1252}} or \q{\cw{UTF-8}}). Also, -any character encoding which is valid in an X logical font -description should be valid (\q{\cw{ibm-cp437}}, for example). - -\cw{pterm}'s default behaviour is to use the same character encoding -as its primary font. If you supply a Unicode (\cw{iso10646-1}) font, -it will default to the UTF-8 character set. - -Character set names are case-insensitive. -} - -\dt \cw{\-nethack} - -\dd Tells \cw{pterm} to enable NetHack keypad mode, in which the -numeric keypad generates the NetHack \c{hjklyubn} direction keys. -This enables you to play NetHack with the numeric keypad without -having to use the NetHack \c{number_pad} option (which requires you -to press \q{\cw{n}} before any repeat count). So you can move with -the numeric keypad, and enter repeat counts with the normal number -keys. - -\dt \cw{\-xrm} \e{resource-string} - -\dd This option specifies an X resource string. Useful for setting -resources which do not have their own command-line options. For -example: - -\lcont{ - -\c pterm -xrm 'ScrollbarOnLeft: 1' - -} - -\dt \cw{\-help}, \cw{\-\-help} - -\dd Display a message summarizing the available options. - -\dt \cw{\-pgpfp} - -\dd Display the fingerprints of the PuTTY PGP Master Keys, to aid -in verifying new files released by the PuTTY team. - -\S{pterm-manpage-x-resources} X RESOURCES - -\cw{pterm} can be more completely configured by means of X -resources. All of these resources are of the form \cw{pterm.FOO} for -some \cw{FOO}; you can make \cw{pterm} look them up under another -name, such as \cw{xyz.FOO}, by specifying the command-line option -\q{\cw{\-name xyz}}. - -\dt \cw{pterm.CloseOnExit} - -\dd This option should be set to 0, 1 or 2; the default is 2. It -controls what \cw{pterm} does when the process running inside it -terminates. When set to 2 (the default), \cw{pterm} will close its -window as soon as the process inside it terminates. When set to 0, -\cw{pterm} will print the process's exit status, and the window -will remain present until a key is pressed (allowing you to inspect -the scrollback, and copy and paste text out of it). - -\lcont{ - -When this setting is set to 1, \cw{pterm} will close -immediately if the process exits cleanly (with an exit status of -zero), but the window will stay around if the process exits with a -non-zero code or on a signal. This enables you to see what went -wrong if the process suffers an error, but not to have to bother -closing the window in normal circumstances. - -} - -\dt \cw{pterm.WarnOnClose} - -\dd This option should be set to either 0 or 1; the default is 1. -When set to 1, \cw{pterm} will ask for confirmation before closing -its window when you press the close button. - -\dt \cw{pterm.TerminalType} - -\dd This controls the value set in the \cw{TERM} environment -variable inside the new terminal. The default is \q{\cw{xterm}}. - -\dt \cw{pterm.BackspaceIsDelete} - -\dd This option should be set to either 0 or 1; the default is 1. -When set to 0, the ordinary Backspace key generates the Backspace -character (\cw{^H}); when set to 1, it generates the Delete -character (\cw{^?}). Whichever one you set, the terminal device -inside \cw{pterm} will be set up to expect it. - -\dt \cw{pterm.RXVTHomeEnd} - -\dd This option should be set to either 0 or 1; the default is 0. When -it is set to 1, the Home and End keys generate the control sequences -they would generate in the \cw{rxvt} terminal emulator, instead of -the more usual ones generated by other emulators. - -\dt \cw{pterm.LinuxFunctionKeys} - -\dd This option can be set to any number between 0 and 5 inclusive; -the default is 0. The modes vary the control sequences sent by the -function keys; for more complete documentation, it is probably -simplest to try each option in \q{\cw{pterm \-e cat}}, and press the -keys to see what they generate. - -\dt \cw{pterm.NoApplicationKeys} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, it stops the server from ever switching the numeric keypad -into application mode (where the keys send function-key-like -sequences instead of numbers or arrow keys). You probably only need -this if some application is making a nuisance of itself. - -\dt \cw{pterm.NoApplicationCursors} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, it stops the server from ever switching the cursor keys -into application mode (where the keys send slightly different -sequences). You probably only need this if some application is -making a nuisance of itself. - -\dt \cw{pterm.NoMouseReporting} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, it stops the server from ever enabling mouse reporting -mode (where mouse clicks are sent to the application instead of -controlling cut and paste). - -\dt \cw{pterm.NoRemoteResize} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, it stops the server from being able to remotely control -the size of the \cw{pterm} window. - -\dt \cw{pterm.NoAltScreen} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, it stops the server from using the \q{alternate screen} -terminal feature, which lets full-screen applications leave the -screen exactly the way they found it. - -\dt \cw{pterm.NoRemoteWinTitle} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, it stops the server from remotely controlling the title of -the \cw{pterm} window. - -\dt \cw{pterm.NoRemoteQTitle} - -\dd This option should be set to either 0 or 1; the default is 1. When -set to 1, it stops the server from remotely requesting the title of -the \cw{pterm} window. - -\lcont{ -This feature is a \e{POTENTIAL SECURITY HAZARD}. If a malicious -application can write data to your terminal (for example, if you -merely \cw{cat} a file owned by someone else on the server -machine), it can change your window title (unless you have disabled -this using the \cw{NoRemoteWinTitle} resource) and then use this -service to have the new window title sent back to the server as if -typed at the keyboard. This allows an attacker to fake keypresses -and potentially cause your server-side applications to do things you -didn't want. Therefore this feature is disabled by default, and we -recommend you do not turn it on unless you \e{really} know what -you are doing. -} - -\dt \cw{pterm.NoDBackspace} - -\dd This option should be set to either 0 or 1; the default is 0. -When set to 1, it disables the normal action of the Delete (\cw{^?}) -character when sent from the server to the terminal, which is to -move the cursor left by one space and erase the character now under -it. - -\dt \cw{pterm.ApplicationCursorKeys} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, the default initial state of the cursor keys are -application mode (where the keys send function-key-like sequences -instead of numbers or arrow keys). When set to 0, the default state -is the normal one. - -\dt \cw{pterm.ApplicationKeypad} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, the default initial state of the numeric keypad is -application mode (where the keys send function-key-like sequences -instead of numbers or arrow keys). When set to 0, the default state -is the normal one. - -\dt \cw{pterm.NetHackKeypad} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, the numeric keypad operates in NetHack mode. This is -equivalent to the \cw{\-nethack} command-line option. - -\dt \cw{pterm.Answerback} - -\dd This option controls the string which the terminal sends in -response to receiving the \cw{^E} character (\q{tell me about -yourself}). By default this string is \q{\cw{PuTTY}}. - -\dt \cw{pterm.HideMousePtr} - -\dd This option should be set to either 0 or 1; the default is 0. When -it is set to 1, the mouse pointer will disappear if it is over the -\cw{pterm} window and you press a key. It will reappear as soon as -you move it. - -\dt \cw{pterm.WindowBorder} - -\dd This option controls the number of pixels of space between the text -in the \cw{pterm} window and the window frame. The default is 1. -You can increase this value, but decreasing it to 0 is not -recommended because it can cause the window manager's size hints to -work incorrectly. - -\dt \cw{pterm.CurType} - -\dd This option should be set to either 0, 1 or 2; the default is 0. -When set to 0, the text cursor displayed in the window is a -rectangular block. When set to 1, the cursor is an underline; when -set to 2, it is a vertical line. - -\dt \cw{pterm.BlinkCur} - -\dd This option should be set to either 0 or 1; the default is 0. When -it is set to 1, the text cursor will blink when the window is active. - -\dt \cw{pterm.Beep} - -\dd This option should be set to either 0 or 2 (yes, 2); the default -is 0. When it is set to 2, \cw{pterm} will respond to a bell -character (\cw{^G}) by flashing the window instead of beeping. - -\dt \cw{pterm.BellOverload} - -\dd This option should be set to either 0 or 1; the default is 0. When -it is set to 1, \cw{pterm} will watch out for large numbers of -bells arriving in a short time and will temporarily disable the bell -until they stop. The idea is that if you \cw{cat} a binary file, -the frantic beeping will mostly be silenced by this feature and will -not drive you crazy. - -\lcont{ -The bell overload mode is activated by receiving N bells in time T; -after a further time S without any bells, overload mode will turn -itself off again. - -Bell overload mode is always deactivated by any keypress in the -terminal. This means it can respond to large unexpected streams of -data, but does not interfere with ordinary command-line activities -that generate beeps (such as filename completion). -} - -\dt \cw{pterm.BellOverloadN} - -\dd This option counts the number of bell characters which will activate -bell overload if they are received within a length of time T. The -default is 5. - -\dt \cw{pterm.BellOverloadT} - -\dd This option specifies the time period in which receiving N or more -bells will activate bell overload mode. It is measured in -microseconds, so (for example) set it to 1000000 for one second. The -default is 2000000 (two seconds). - -\dt \cw{pterm.BellOverloadS} - -\dd This option specifies the time period of silence required to turn -off bell overload mode. It is measured in microseconds, so (for -example) set it to 1000000 for one second. The default is 5000000 -(five seconds of silence). - -\dt \cw{pterm.ScrollbackLines} - -\dd This option specifies how many lines of scrollback to save above the -visible terminal screen. The default is 200. This resource is -equivalent to the \cw{\-sl} command-line option. - -\dt \cw{pterm.DECOriginMode} - -\dd This option should be set to either 0 or 1; the default is 0. It -specifies the default state of DEC Origin Mode. (If you don't know -what that means, you probably don't need to mess with it.) - -\dt \cw{pterm.AutoWrapMode} - -\dd This option should be set to either 0 or 1; the default is 1. It -specifies the default state of auto wrap mode. When set to 1, very -long lines will wrap over to the next line on the terminal; when set -to 0, long lines will be squashed against the right-hand edge of the -screen. - -\dt \cw{pterm.LFImpliesCR} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, the terminal will return the cursor to the left side of -the screen when it receives a line feed character. - -\dt \cw{pterm.WinTitle} - -\dd This resource is the same as the \cw{\-T} command-line option: -it controls the initial title of the window. The default is -\q{\cw{pterm}}. - -\dt \cw{pterm.TermWidth} - -\dd This resource is the same as the width part of the \cw{\-geometry} -command-line option: it controls the number of columns of text in -the window. The default is 80. - -\dt \cw{pterm.TermHeight} - -\dd This resource is the same as the width part of the \cw{\-geometry} -command-line option: it controls the number of columns of text in -the window. The defaults is 24. - -\dt \cw{pterm.Font} - -\dd This resource is the same as the \cw{\-fn} command-line option: it -controls the font used to display normal text. The default is -\q{\cw{fixed}}. - -\dt \cw{pterm.BoldFont} - -\dd This resource is the same as the \cw{\-fb} command-line option: it -controls the font used to display bold text when \cw{BoldAsColour} -is set to 0 or 2. The default is unset (the font will be bolded by -printing it twice at a one-pixel offset). - -\dt \cw{pterm.WideFont} - -\dd This resource is the same as the \cw{\-fw} command-line option: it -controls the font used to display double-width characters. The -default is unset (double-width characters cannot be displayed). - -\dt \cw{pterm.WideBoldFont} - -\dd This resource is the same as the \cw{\-fwb} command-line option: it -controls the font used to display double-width characters in bold, -when \cw{BoldAsColour} is set to 0 or 2. The default is unset -(double-width characters are displayed in bold by printing them -twice at a one-pixel offset). - -\dt \cw{pterm.ShadowBoldOffset} - -\dd This resource can be set to an integer; the default is \-1. It -specifies the offset at which text is overprinted when using -\q{shadow bold} mode. The default (1) means that the text will be -printed in the normal place, and also one character to the right; -this seems to work well for most X bitmap fonts, which have a blank -line of pixels down the right-hand side. For some fonts, you may -need to set this to \-1, so that the text is overprinted one pixel -to the left; for really large fonts, you may want to set it higher -than 1 (in one direction or the other). - -\dt \cw{pterm.BoldAsColour} - -\dd This option should be set to either 0, 1, or 2; the default is 1. -It specifies how bold text should be displayed. When set to 1, bold -text is shown by displaying it in a brighter colour; when set to 0, -bold text is shown by displaying it in a heavier font; when set to 2, -both effects happen at once (a heavy font \e{and} a brighter colour). - -\dt \cw{pterm.Colour0}, \cw{pterm.Colour1}, ..., \cw{pterm.Colour21} - -\dd These options control the various colours used to display text -in the \cw{pterm} window. Each one should be specified as a triple -of decimal numbers giving red, green and blue values: so that black -is \q{\cw{0,0,0}}, white is \q{\cw{255,255,255}}, red is -\q{\cw{255,0,0}} and so on. - -\lcont{ - -Colours 0 and 1 specify the foreground colour and its bold -equivalent (the \cw{\-fg} and \cw{\-bfg} command-line options). -Colours 2 and 3 specify the background colour and its bold -equivalent (the \cw{\-bg} and \cw{\-bbg} command-line options). -Colours 4 and 5 specify the text and block colours used for the -cursor (the \cw{\-cfg} and \cw{\-cbg} command-line options). Each -even number from 6 to 20 inclusive specifies the colour to be used -for one of the ANSI primary colour specifications (black, red, -green, yellow, blue, magenta, cyan, white, in that order); the odd -numbers from 7 to 21 inclusive specify the bold version of each -colour, in the same order. The defaults are: - -\c pterm.Colour0: 187,187,187 -\c pterm.Colour1: 255,255,255 -\c pterm.Colour2: 0,0,0 -\c pterm.Colour3: 85,85,85 -\c pterm.Colour4: 0,0,0 -\c pterm.Colour5: 0,255,0 -\c pterm.Colour6: 0,0,0 -\c pterm.Colour7: 85,85,85 -\c pterm.Colour8: 187,0,0 -\c pterm.Colour9: 255,85,85 -\c pterm.Colour10: 0,187,0 -\c pterm.Colour11: 85,255,85 -\c pterm.Colour12: 187,187,0 -\c pterm.Colour13: 255,255,85 -\c pterm.Colour14: 0,0,187 -\c pterm.Colour15: 85,85,255 -\c pterm.Colour16: 187,0,187 -\c pterm.Colour17: 255,85,255 -\c pterm.Colour18: 0,187,187 -\c pterm.Colour19: 85,255,255 -\c pterm.Colour20: 187,187,187 -\c pterm.Colour21: 255,255,255 - -} - -\dt \cw{pterm.RectSelect} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 0, dragging the mouse over several lines selects to the end -of each line and from the beginning of the next; when set to 1, -dragging the mouse over several lines selects a rectangular region. -In each case, holding down Alt while dragging gives the other -behaviour. - -\dt \cw{pterm.MouseOverride} - -\dd This option should be set to either 0 or 1; the default is 1. When -set to 1, if the application requests mouse tracking (so that mouse -clicks are sent to it instead of doing selection), holding down -Shift will revert the mouse to normal selection. When set to 0, -mouse tracking completely disables selection. - -\dt \cw{pterm.Printer} - -\dd This option is unset by default. If you set it, then -server-controlled printing is enabled: the server can send control -sequences to request data to be sent to a printer. That data will be -piped into the command you specify here; so you might want to set it -to \q{\cw{lpr}}, for example, or \q{\cw{lpr \-Pmyprinter}}. - -\dt \cw{pterm.ScrollBar} - -\dd This option should be set to either 0 or 1; the default is 1. When -set to 0, the scrollbar is hidden (although Shift-PageUp and -Shift-PageDown still work). This is the same as the \cw{\-sb} -command-line option. - -\dt \cw{pterm.ScrollbarOnLeft} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, the scrollbar will be displayed on the left of the -terminal instead of on the right. - -\dt \cw{pterm.ScrollOnKey} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, any keypress causes the position of the scrollback to be -reset to the very bottom. - -\dt \cw{pterm.ScrollOnDisp} - -\dd This option should be set to either 0 or 1; the default is 1. When -set to 1, any activity in the display causes the position of the -scrollback to be reset to the very bottom. - -\dt \cw{pterm.LineCodePage} - -\dd This option specifies the character set to be used for the session. -This is the same as the \cw{\-cs} command-line option. - -\dt \cw{pterm.NoRemoteCharset} - -\dd This option disables the terminal's ability to change its character -set when it receives escape sequences telling it to. You might need -to do this to interoperate with programs which incorrectly change -the character set to something they think is sensible. - -\dt \cw{pterm.BCE} - -\dd This option should be set to either 0 or 1; the default is 1. When -set to 1, the various control sequences that erase parts of the -terminal display will erase in whatever the current background -colour is; when set to 0, they will erase in black always. - -\dt \cw{pterm.BlinkText} - -\dd This option should be set to either 0 or 1; the default is 0. When -set to 1, text specified as blinking by the server will actually -blink on and off; when set to 0, \cw{pterm} will use the less -distracting approach of making the text's background colour bold. - -\dt \cw{pterm.StampUtmp} - -\dd This option should be set to either 0 or 1; the default is 1. When -set to 1, \cw{pterm} will log the login in the various system log -files. This resource is equivalent to the \cw{\-ut} command-line -option. - -\dt \cw{pterm.LoginShell} - -\dd This option should be set to either 0 or 1; the default is 1. When -set to 1, \cw{pterm} will execute your shell as a login shell. This -resource is equivalent to the \cw{\-ls} command-line option. - -\S{pterm-manpage-bugs} BUGS - -Most of the X resources have silly names. (Historical reasons from -PuTTY, mostly.) diff --git a/doc/man-putty.but b/doc/man-putty.but deleted file mode 100644 index a85b4505f..000000000 --- a/doc/man-putty.but +++ /dev/null @@ -1,349 +0,0 @@ -\cfg{man-identity}{putty}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} - -\H{putty-manpage} Man page for PuTTY - -\S{putty-manpage-name} NAME - -\cw{putty} - GUI SSH, Telnet, Rlogin, and SUPDUP client for X - -\S{putty-manpage-synopsis} SYNOPSIS - -\c putty [ options ] [ host ] -\e bbbbb iiiiiii iiii - -\S{putty-manpage-description} DESCRIPTION - -\cw{putty} is a graphical SSH, Telnet, Rlogin, and SUPDUP client for -X. It is a direct port of the Windows SSH client of the same name. - -\S{putty-manpage-options} OPTIONS - -The command-line options supported by \cw{putty} are: - -\dt \cw{\-\-display} \e{display\-name} - -\dd Specify the X display on which to open \cw{putty}. (Note this -option has a double minus sign, even though none of the others do. -This is because this option is supplied automatically by GTK. -Sorry.) - -\dt \cw{\-fn} \e{font-name} - -\dd Specify the font to use for normal text displayed in the terminal. -For example, \cw{\-fn\_fixed}, \cw{\-fn\_"Monospace\_12"}. - -\dt \cw{\-fb} \e{font-name} - -\dd Specify the font to use for bold text displayed in the terminal. -If the \cw{BoldAsColour} resource is set to 1 (the default), bold -text will be displayed in different colours instead of a different -font, so this option will be ignored. If \cw{BoldAsColour} is set to -0 or 2 and you do not specify a bold font, \cw{putty} will overprint the -normal font to make it look bolder. - -\dt \cw{\-fw} \e{font-name} - -\dd Specify the font to use for double-width characters (typically -Chinese, Japanese and Korean text) displayed in the terminal. - -\dt \cw{\-fwb} \e{font-name} - -\dd Specify the font to use for bold double-width characters -(typically Chinese, Japanese and Korean text). Like \cw{-fb}, this -will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2. - -\dt \cw{\-geometry} \e{geometry} - -\dd Specify the size of the terminal, in rows and columns of text. -See \cw{X}(\e{7}) for more information on the syntax of geometry -specifications. - -\dt \cw{\-sl} \e{lines} - -\dd Specify the number of lines of scrollback to save off the top of the -terminal. - -\dt \cw{\-fg} \e{colour} - -\dd Specify the foreground colour to use for normal text. - -\dt \cw{\-bg} \e{colour} - -\dd Specify the background colour to use for normal text. - -\dt \cw{\-bfg} \e{colour} - -\dd Specify the foreground colour to use for bold text, if the -\cw{BoldAsColour} resource is set to 1 (the default) or 2. - -\dt \cw{\-bbg} \e{colour} - -\dd Specify the foreground colour to use for bold reverse-video -text, if the \cw{BoldAsColour} resource is set to 1 (the default) or 2. -(This colour is best thought of as the bold version of the -background colour; so it only appears when text is displayed \e{in} -the background colour.) - -\dt \cw{\-cfg} \e{colour} - -\dd Specify the foreground colour to use for text covered by the cursor. - -\dt \cw{\-cbg} \e{colour} - -\dd Specify the background colour to use for text covered by the cursor. -In other words, this is the main colour of the cursor. - -\dt \cw{\-title} \e{title} - -\dd Specify the initial title of the terminal window. (This can be -changed under control of the server.) - -\dt \cw{\-sb\-} or \cw{+sb} - -\dd Tells \cw{putty} not to display a scroll bar. - -\dt \cw{\-sb} - -\dd Tells \cw{putty} to display a scroll bar: this is the opposite of -\cw{\-sb\-}. This is the default option: you will probably only need -to specify it explicitly if you have changed the default using the -\cw{ScrollBar} resource. - -\dt \cw{\-log} \e{logfile}, \cw{\-sessionlog} \e{logfile} - -\dd This option makes \cw{putty} log all the terminal output to a file -as well as displaying it in the terminal. - -\dt \cw{\-sshlog} \e{logfile} - -\dt \cw{\-sshrawlog} \e{logfile} - -\dd For SSH connections, these options make \cw{putty} log protocol -details to a file. (Some of these may be sensitive, although by default -an effort is made to suppress obvious passwords.) - -\lcont{ -\cw{\-sshlog} logs decoded SSH packets and other events (those that -\cw{\-v} would print). \cw{\-sshrawlog} additionally logs the raw -encrypted packet data. -} - -\dt \cw{\-logoverwrite} - -\dd If \cw{putty} is configured to write to a log file that already exists, -discard the existing file. - -\dt \cw{\-logappend} - -\dd If \cw{putty} is configured to write to a log file that already exists, -append new log data to the existing file. - -\dt \cw{\-cs} \e{charset} - -\dd This option specifies the character set in which \cw{putty} -should assume the session is operating. This character set will be -used to interpret all the data received from the session, and all -input you type or paste into \cw{putty} will be converted into -this character set before being sent to the session. - -\lcont{ Any character set name which is valid in a MIME header (and -supported by \cw{putty}) should be valid here (examples are -\q{\cw{ISO-8859-1}}, \q{\cw{windows-1252}} or \q{\cw{UTF-8}}). Also, -any character encoding which is valid in an X logical font -description should be valid (\q{\cw{ibm-cp437}}, for example). - -\cw{putty}'s default behaviour is to use the same character -encoding as its primary font. If you supply a Unicode -(\cw{iso10646-1}) font, it will default to the UTF-8 character set. - -Character set names are case-insensitive. -} - -\dt \cw{\-nethack} - -\dd Tells \cw{putty} to enable NetHack keypad mode, in which the -numeric keypad generates the NetHack \c{hjklyubn} direction keys. -This enables you to play NetHack with the numeric keypad without -having to use the NetHack \c{number_pad} option (which requires you -to press \q{\cw{n}} before any repeat count). So you can move with -the numeric keypad, and enter repeat counts with the normal number -keys. - -\dt \cw{\-help}, \cw{\-\-help} - -\dd Display a message summarizing the available options. - -\dt \cw{\-pgpfp} - -\dd Display the fingerprints of the PuTTY PGP Master Keys, to aid -in verifying new files released by the PuTTY team. - -\dt \cw{\-load} \e{session} - -\dd Load a saved session by name. This allows you to run a saved session -straight from the command line without having to go through the -configuration box first. - -\dt \cw{\-ssh}, \cw{\-telnet}, \cw{\-rlogin}, \cw{\-supdup}, \cw{\-raw}, -\cw{-ssh-connection}, \cw{\-serial} - -\dd Select the protocol \cw{putty} will use to make the connection. - -\dt \cw{\-proxycmd} \e{command} - -\dd Instead of making a TCP connection, use \e{command} as a proxy; -network traffic will be redirected to the standard input and output -of \e{command}. \e{command} must be a single word, so is likely to -need quoting by the shell. - -\lcont{ -The special strings \cw{%host} and \cw{%port} in \e{command} will be -replaced by the hostname and port number you want to connect to; to get -a literal \c{%} sign, enter \c{%%}. - -Backslash escapes are also supported, such as sequences like \c{\\n} -being replaced by a literal newline; to get a literal backslash, -enter \c{\\\\}. (Further escaping may be required by the shell.) - -(See the main PuTTY manual for full details of the supported \cw{%}- -and backslash-delimited tokens, although most of them are probably not -very useful in this context.) -} - -\dt \cw{\-l} \e{username} - -\dd Specify the username to use when logging in to the server. - -\dt \cw{\-L} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport} - -\dd Set up a local port forwarding: listen on \e{srcport} (or -\e{srcaddr}:\e{srcport} if specified), and forward any connections -over the SSH connection to the destination address -\e{desthost}:\e{destport}. Only works in SSH. - -\dt \cw{\-R} \cw{[}\e{srcaddr}\cw{:]}\e{srcport}\cw{:}\e{desthost}\cw{:}\e{destport} - -\dd Set up a remote port forwarding: ask the SSH server to listen on -\e{srcport} (or \e{srcaddr}:\e{srcport} if specified), and to -forward any connections back over the SSH connection where the -client will pass them on to the destination address -\e{desthost}:\e{destport}. Only works in SSH. - -\dt \cw{\-D} [\e{srcaddr}:]\e{srcport} - -\dd Set up dynamic port forwarding. The client listens on -\e{srcport} (or \e{srcaddr}:\e{srcport} if specified), and -implements a SOCKS server. So you can point SOCKS-aware applications -at this port and they will automatically use the SSH connection to -tunnel all their connections. Only works in SSH. - -\dt \cw{\-P} \e{port} - -\dd Specify the port to connect to the server on. - -\dt \cw{\-A}, \cw{\-a} - -\dd Enable (\cw{\-A}) or disable (\cw{\-a}) SSH agent forwarding. -Currently this only works with OpenSSH and SSH-1. - -\dt \cw{\-X}, \cw{\-x} - -\dd Enable (\cw{\-X}) or disable (\cw{\-x}) X11 forwarding. - -\dt \cw{\-T}, \cw{\-t} - -\dd Enable (\cw{\-t}) or disable (\cw{\-T}) the allocation of a -pseudo-terminal at the server end. - -\dt \cw{\-C} - -\dd Enable zlib-style compression on the connection. - -\dt \cw{\-1}, \cw{\-2} - -\dd Select SSH protocol version 1 or 2. - -\dt \cw{-4}, \cw{-6} - -\dd Force use of IPv4 or IPv6 for network connections. - -\dt \cw{\-i} \e{keyfile} - -\dd Private key file for user authentication. For SSH-2 keys, this key -file must be in PuTTY's PPK format, not OpenSSH's format or anyone -else's. - -\lcont{ If you are using an authentication agent, you can also specify -a \e{public} key here (in RFC 4716 or OpenSSH format), to identify -which of the agent's keys to use. } - -\dt \cw{\-noagent} - -\dd Don't try to use an authentication agent for local authentication. -(This doesn't affect agent forwarding.) - -\dt \cw{\-agent} - -\dd Allow use of an authentication agent. (This option is only necessary -to override a setting in a saved session.) - -\dt \cw{\-no\-trivial\-auth} - -\dd Disconnect from any SSH server which accepts authentication without -ever having asked for any kind of password or signature or token. (You -might want to enable this for a server you always expect to challenge -you, for instance to ensure you don't accidentally type your key file's -passphrase into a compromised server spoofing PuTTY's passphrase -prompt.) - -\dt \cw{\-hostkey} \e{key} - -\dd Specify an acceptable host public key. This option may be specified -multiple times; each key can be either a fingerprint (\cw{SHA256:AbCdE...}, -\cw{99:aa:bb:...}, etc) or a base64-encoded blob in OpenSSH's one-line -format. - -\lcont{ Specifying this option overrides automated host key -management; \e{only} the key(s) specified on the command-line will be -accepted (unless a saved session also overrides host keys, in which -case those will be added to), and the host key cache will not be -written. } - -\dt \cw{\-sercfg} \e{configuration-string} - -\dd Specify the configuration parameters for the serial port, in -\cw{-serial} mode. \e{configuration-string} should be a -comma-separated list of configuration parameters as follows: - -\lcont{ - -\b Any single digit from 5 to 9 sets the number of data bits. - -\b \cq{1}, \cq{1.5} or \cq{2} sets the number of stop bits. - -\b Any other numeric string is interpreted as a baud rate. - -\b A single lower-case letter specifies the parity: \cq{n} for none, -\cq{o} for odd, \cq{e} for even, \cq{m} for mark and \cq{s} for space. - -\b A single upper-case letter specifies the flow control: \cq{N} for -none, \cq{X} for XON/XOFF, \cq{R} for RTS/CTS and \cq{D} for -DSR/DTR. - -} - -\S{putty-manpage-saved-sessions} SAVED SESSIONS - -Saved sessions are stored in a \cw{.putty/sessions} subdirectory in -your home directory. - -\S{putty-manpage-more-information} MORE INFORMATION - -For more information on PuTTY, it's probably best to go and look at -the manual on the web page: - -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} - -\S{putty-manpage-bugs} BUGS - -This man page isn't terribly complete. diff --git a/doc/man-puttygen.but b/doc/man-puttygen.but deleted file mode 100644 index e6a2c9901..000000000 --- a/doc/man-puttygen.but +++ /dev/null @@ -1,411 +0,0 @@ -\cfg{man-identity}{puttygen}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} - -\H{puttygen-manpage} Man page for PuTTYgen - -\S{puttygen-manpage-name} NAME - -\cw{puttygen} - public-key generator for the PuTTY tools - -\S{puttygen-manpage-synopsis} SYNOPSIS - -\c puttygen ( keyfile | -t keytype [ -b bits ] [ --primes method ] [ -q ] ) -\e bbbbbbbb iiiiiii bb iiiiiii bb iiii bbbbbbbb iiiiii bb -\c [ -C new-comment ] [ -P ] [ --reencrypt ] -\e bb iiiiiiiiiii bb bbbbbbbbbbb -\c [ --certificate cert-file | --remove-certificate ] -\e bbbbbbbbbbbbb iiiiiiiii bbbbbbbbbbbbbbbbbbbb -\c [ -O output-type | -l | -L | -p | --dump | --cert-info ] -\e bb iiiiiiiiiii bb bb bb bbbbbb bbbbbbbbbbb -\c [ --ppk-param key=value,... | -E fptype ] -\e bbbbbbbbbbb iiibiiiiib bb iiiiii -\c [ -o output-file ] -\e bb iiiiiiiiiii - -\S{puttygen-manpage-description} DESCRIPTION - -\c{puttygen} is a tool to generate and manipulate SSH public and -private key pairs. It is part of the PuTTY suite, although it can -also interoperate with the key formats used by some other SSH clients. - -When you run \c{puttygen}, it does three things. Firstly, it either -loads an existing key file (if you specified \e{keyfile}), or -generates a new key (if you specified \e{keytype}). Then, it -optionally makes modifications to the key (such as changing the comment -and/or the passphrase); finally, it outputs the key, or some -information about the key, to a file. - -All three of these phases are controlled by the options described in -the following section. - -\S{puttygen-manpage-options} OPTIONS - -In the first phase, \c{puttygen} either loads or generates a key. -Note that generating a key requires random data, which can cause -\c{puttygen} to pause, possibly for some time if your system does -not have much randomness available. - -The options to control this phase are: - -\dt \e{keyfile} - -\dd Specify a key file to be loaded. (Use \cq{-} to read a key -file from standard input.) - -\lcont{ - -Usually this will be a private key, which can be in the (de facto -standard) SSH-1 key format, or in PuTTY's SSH-2 key format, or in -either of the SSH-2 private key formats used by OpenSSH and -ssh.com's implementation. - -You can also specify a file containing only a \e{public} key here. -The operations you can do are limited to outputting another public -key format (possibly removing an attached certificate first), or a -fingerprint. Public keys can be in RFC 4716 or OpenSSH format, or -the standard SSH-1 format. - -} - -\dt \cw{\-t} \e{keytype} - -\dd Specify a type of key to generate. The acceptable values here are -\c{rsa}, \c{dsa}, \c{ecdsa}, \c{eddsa}, \c{ed25519}, and \c{ed448} -(to generate SSH-2 keys), and \c{rsa1} (to generate SSH-1 keys). - -\dt \cw{\-b} \e{bits} - -\dd Specify the size of the key to generate, in bits. Default for -\c{rsa} and \c{dsa} keys is 2048. - -\dt \cw{\-\-primes} \e{method} - -\dd Method for generating prime numbers. The acceptable values here -are \c{probable} (the default), \c{proven}, and \c{proven-even}; -the later methods are slower. (Various synonyms for these method -names are also accepted.) - -\lcont{ - -The \q{probable primes} method sounds unsafe, but it's the most -commonly used prime-generation strategy. There is in theory a -possibility that it might accidentally generate a number that isn't -prime, but the software does enough checking to make that probability -vanishingly small (less than 1 in 2^80, or 1 in 10^24). So, in -practice, nobody worries about it very much. - -The other methods cause PuTTYgen to use numbers that it is \e{sure} -are prime, because it generates the output number together with a -proof of its primality. This takes more effort, but it eliminates that -theoretical risk in the probabilistic method. - -You might choose to switch from probable to proven primes if you have -a local security standard that demands it, or if you don't trust the -probabilistic argument for the safety of the usual method. - -} - -\dt \cw{\-\-strong-rsa} - -\dd When generating an RSA key, make sure the prime factors of the key -modulus are \q{strong primes}. A strong prime is a prime number chosen -to have a particular structure that makes certain factoring algorithms -more difficult to apply, so some security standards recommend their -use. However, the most modern factoring algorithms are unaffected, so -this option is probably not worth turning on \e{unless} you have a -local standard that recommends it. - -\dt \cw{\-q} - -\dd Suppress the progress display when generating a new key. - -\dt \cw{\-\-old\-passphrase} \e{file} - -\dd Specify a file name; the first line will be read from this file -(removing any trailing newline) and used as the old passphrase. -\s{CAUTION:} If the passphrase is important, the file should be stored -on a temporary filesystem or else securely erased after use. - -\dt \cw{\-\-random\-device} \e{device} - -\dd Specify device to read entropy from. By default, \c{puttygen} -uses \c{/dev/urandom}, falling back to \c{/dev/random} if it has to. - -In the second phase, \c{puttygen} optionally alters properties of -the key it has loaded or generated. The options to control this are: - -\dt \cw{\-C} \e{new\-comment} - -\dd Specify a comment string to describe the key. This comment string -will be used by PuTTY to identify the key to you (when asking you to -enter the passphrase, for example, so that you know which passphrase -to type). - -\dt \cw{\-P} - -\dd Indicate that you want to change the key's passphrase. This is -automatic when you are generating a new key, but not when you are -modifying an existing key. - -\dt \cw{\-\-certificate} \e{certificate-file} - -\dd Adds an OpenSSH-style certificate to the public half of the key, -so that the output file contains a certified public key with the same -private key. If the input file already contained a certificate, it -will be replaced with the new one. (Use \cq{-} to read a certificate -from standard input.) - -\dt \cw{\-\-remove\-certificate} - -\dd Removes any certificate that was part of the key, to recover the -uncertified version of the underlying key. - -\dt \cw{\-\-reencrypt} - -\dd For an existing private key saved with a passphrase, refresh the -encryption without changing the passphrase. - -\lcont{ -This is most likely to be useful with the \cw{\-\-ppk-param} option, -to change some aspect of the key file's format or encryption. -} - -\dt \cw{\-\-ppk-param} \e{key}\cw{=}\e{value}\cw{,}... - -\dd When saving a PPK file (the default \cw{private} output type for SSH-2 -keys), adjust details of the on-disk format. - -\lcont{ - -Aspects to change are specified as a series of \e{key}\cw{=}\e{value} pairs -separated by commas. The \e{key}s are: - -\dt \cw{version} - -\dd The PPK format version. Possible values are \cw{3} (the default) -and \cw{2} (which is less resistant to brute-force decryption, but -which you might need if your key needs to be used by old versions of -PuTTY tools, or other PPK consumers). - -\lcont{ -The following \e{key}s only affect PPK version 3 files. -} - -\dt \cw{kdf} - -\dd The variant of the Argon2 key derivation function to use. Options -are \cw{argon2id} (default, and recommended), \cw{argon2i}, and -\cw{argon2d}. - -\lcont{ -You might change this if you consider your exposure to side-channel -attacks to be different to the norm. -} - -\dt \cw{memory} - -\dd The amount of memory needed to decrypt the key, in Kbyte. Default -is 8192 (i.e., 8 Mbyte). - -\dt \cw{time} - -\dd Approximate time, on this machine, required to attempt decrypting -the key, in milliseconds. Default is 100 (ms). - -\dt \cw{passes} - -\dd Alternative to \cw{time}: explicitly specify the number of hash -passes required to attempt decrypting the key. - -\dt \cw{parallelism} - -\dd Number of parallelisable threads that can be used to decrypt the -key. Default is 1 (force decryption to run single-threaded). - -} - -In the third phase, \c{puttygen} saves the key or information -about it. The options to control this are: - -\dt \cw{\-O} \e{output\-type} - -\dd Specify the type of output you want \c{puttygen} to produce. -Acceptable options are: - -\lcont{ - -\dt \cw{private} - -\dd Save the private key in a format usable by PuTTY. This will either -be the standard SSH-1 key format, or PuTTY's own SSH-2 key format -(\q{PPK}). This is the default. - -\dt \cw{public} - -\dd Save the public key only. For SSH-1 keys, the standard public key -format will be used (\q{\cw{1024 37 5698745}...}). For SSH-2 keys, the -public key will be output in the format specified by RFC 4716, -which is a multi-line text file beginning with the line -\q{\cw{---- BEGIN SSH2 PUBLIC KEY ----}}. - -\dt \cw{public-openssh} - -\dd Save the public key only, in a format usable by OpenSSH. For SSH-1 -keys, this output format behaves identically to \c{public}. For -SSH-2 keys, the public key will be output in the OpenSSH format, -which is a single line (\q{\cw{ssh-rsa AAAAB3NzaC1yc2}...}). - -\dt \cw{fingerprint} - -\dd Print a fingerprint of the public key. The \cw{-E} option lets you -specify which fingerprinting algorithm to use. All algorithms are -believed compatible with OpenSSH. - -\dt \cw{private-openssh} - -\dd Save an SSH-2 private key in OpenSSH's format, using the oldest -format available to maximise backward compatibility. This option is not -permitted for SSH-1 keys. - -\dt \cw{private-openssh-new} - -\dd As \c{private-openssh}, except that it forces the use of OpenSSH's -newer format even for RSA, DSA, and ECDSA keys. - -\dt \cw{private-sshcom} - -\dd Save an SSH-2 private key in ssh.com's format. This option is not -permitted for SSH-1 keys. - -\dt \cw{cert-info} - -\dd Save a textual dump of information about the certificate on the -key, if any: whether it's a host or a user certificate, what host(s) -or user(s) it's certified to be, its validity period, ID and serial -number, and the fingerprint of the signing CA. - -\dt \cw{text} - -\dd Save a textual dump of the numeric components comprising the key -(both the public and private parts, if present). Useful for debugging, -or for using PuTTYgen as a key generator for applications other than -SSH. - -\lcont{ -The output consists of a series of \cw{name=value} lines, where each -\c{value} is either a C-like string literal in double quotes, a -hexadecimal number starting with \cw{0x...}, or a binary blob -encoded with base64, denoted by \cw{b64("...")}. -} - -If no output type is specified, the default is \c{private}. - -} - -\dt \cw{\-o} \e{output\-file} - -\dd Specify the file where \c{puttygen} should write its output. If -this option is not specified, \c{puttygen} will assume you want to -overwrite the original file if the input and output file types are -the same (changing a comment or passphrase), and will assume you -want to output to stdout if you are asking for a public key, -fingerprint, or one of the textual dump types. Otherwise, the -\c{\-o} option is required. - -\dt \cw{\-l} - -\dd Synonym for \q{\cw{-O fingerprint}}. - -\dt \cw{\-L} - -\dd Synonym for \q{\cw{-O public-openssh}}. - -\dt \cw{\-p} - -\dd Synonym for \q{\cw{-O public}}. - -\dt \cw{\-\-cert\-info} - -\dd Synonym for \q{\cw{-O cert-info}}. - -\dt \cw{\-\-dump} - -\dd Synonym for \q{\cw{-O text}}. - -\dt \cw{-E} \e{fptype} - -\dd Specify the algorithm to use if generating a fingerprint. The -available algorithms are are \cw{sha256} (the default) and \cw{md5}. - -\lcont{ - -By default, when showing the fingerprint of a public key that includes -a certificate, \c{puttygen} will not include the certificate, so that -the fingerprint shown will be the same as the underlying public key. -If you want the fingerprint including the certificate (for example, so -as to tell two certified keys apart), you can specify \cw{sha256-cert} -or \cw{md5-cert} as the fingerprint type. - -} - -\dt \cw{\-\-new\-passphrase} \e{file} - -\dd Specify a file name; the first line will be read from this file -(removing any trailing newline) and used as the new passphrase. If the -file is empty then the saved key will be unencrypted. \s{CAUTION:} If -the passphrase is important, the file should be stored on a temporary -filesystem or else securely erased after use. - -The following options do not run PuTTYgen as normal, but print -informational messages and then quit: - -\dt \cw{\-h}, \cw{\-\-help} - -\dd Display a message summarizing the available options. - -\dt \cw{\-V}, \cw{\-\-version} - -\dd Display the version of PuTTYgen. - -\dt \cw{\-\-pgpfp} - -\dd Display the fingerprints of the PuTTY PGP Master Keys, to aid -in verifying new files released by the PuTTY team. - -\S{puttygen-manpage-examples} EXAMPLES - -To generate an SSH-2 RSA key pair and save it in PuTTY's own format -(you will be prompted for the passphrase): - -\c puttygen -t rsa -C "my home key" -o mykey.ppk - -To generate a larger (4096-bit) key: - -\c puttygen -t rsa -b 4096 -C "my home key" -o mykey.ppk - -To change the passphrase on a key (you will be prompted for the old -and new passphrases): - -\c puttygen -P mykey.ppk - -To change the comment on a key: - -\c puttygen -C "new comment" mykey.ppk - -To convert a key into OpenSSH's private key format: - -\c puttygen mykey.ppk -O private-openssh -o my-openssh-key - -To convert a key \e{from} another format (\c{puttygen} will -automatically detect the input key type): - -\c puttygen my-ssh.com-key -o mykey.ppk - -To display the SHA-256 fingerprint of a key (some key types require a -passphrase to extract even this much information): - -\c puttygen -l mykey.ppk - -To add the OpenSSH-format public half of a key to your authorised -keys file: - -\c puttygen -L mykey.ppk >> $HOME/.ssh/authorized_keys diff --git a/doc/man-puttytel.but b/doc/man-puttytel.but deleted file mode 100644 index 075eeea73..000000000 --- a/doc/man-puttytel.but +++ /dev/null @@ -1,215 +0,0 @@ -\cfg{man-identity}{puttytel}{1}{2004-03-24}{PuTTY tool suite}{PuTTY tool suite} - -\H{puttytel-manpage} Man page for PuTTYtel - -\S{puttytel-manpage-name} NAME - -\cw{puttytel} \- GUI Telnet, Rlogin, and SUPDUP client for X - -\S{puttytel-manpage-synopsis} SYNOPSIS - -\c puttytel [ options ] [ host ] -\e bbbbbbbb iiiiiii iiii - -\S{puttytel-manpage-description} DESCRIPTION - -\cw{puttytel} is a graphical Telnet, Rlogin, and SUPDUP client for X. It -is a direct port of the Windows Telnet, Rlogin, and SUPDUP client of the -same name, and a cut-down cryptography-free version of PuTTY. - -\S{puttytel-manpage-options} OPTIONS - -The command-line options supported by \cw{puttytel} are: - -\dt \cw{\-\-display} \e{display\-name} - -\dd Specify the X display on which to open \cw{puttytel}. (Note this -option has a double minus sign, even though none of the others do. -This is because this option is supplied automatically by GTK. -Sorry.) - -\dt \cw{\-fn} \e{font-name} - -\dd Specify the font to use for normal text displayed in the terminal. -For example, \cw{\-fn\_fixed}, \cw{\-fn\_"Monospace\_12"}. - -\dt \cw{\-fb} \e{font-name} - -\dd Specify the font to use for bold text displayed in the terminal. If -the \cw{BoldAsColour} resource is set to 1 (the default), bold text -will be displayed in different colours instead of a different font, -so this option will be ignored. If \cw{BoldAsColour} is set to 0 or 2 -and you do not specify a bold font, \cw{puttytel} will overprint the -normal font to make it look bolder. - -\dt \cw{\-fw} \e{font-name} - -\dd Specify the font to use for double-width characters (typically -Chinese, Japanese and Korean text) displayed in the terminal. - -\dt \cw{\-fwb} \e{font-name} - -\dd Specify the font to use for bold double-width characters -(typically Chinese, Japanese and Korean text). Like \cw{-fb}, this -will be ignored unless the \cw{BoldAsColour} resource is set to 0 or 2. - -\dt \cw{\-geometry} \e{geometry} - -\dd Specify the size of the terminal, in rows and columns of text. See -\cw{X}(\e{7}) for more information on the syntax of geometry -specifications. - -\dt \cw{\-sl} \e{lines} - -\dd Specify the number of lines of scrollback to save off the top of the -terminal. - -\dt \cw{\-fg} \e{colour} - -\dd Specify the foreground colour to use for normal text. - -\dt \cw{\-bg} \e{colour} - -\dd Specify the background colour to use for normal text. - -\dt \cw{\-bfg} \e{colour} - -\dd Specify the foreground colour to use for bold text, if the -\cw{BoldAsColour} resource is set to 1 (the default) or 2. - -\dt \cw{\-bbg} \e{colour} - -\dd Specify the foreground colour to use for bold reverse-video text, if -the \cw{BoldAsColour} resource is set to 1 (the default) or 2. (This -colour is best thought of as the bold version of the background -colour; so it only appears when text is displayed \e{in} the -background colour.) - -\dt \cw{\-cfg} \e{colour} - -\dd Specify the foreground colour to use for text covered by the cursor. - -\dt \cw{\-cbg} \e{colour} - -\dd Specify the background colour to use for text covered by the cursor. -In other words, this is the main colour of the cursor. - -\dt \cw{\-title} \e{title} - -\dd Specify the initial title of the terminal window. (This can be -changed under control of the server.) - -\dt \cw{\-sb\-} or \cw{+sb} - -\dd Tells \cw{puttytel} not to display a scroll bar. - -\dt \cw{\-sb} - -\dd Tells \cw{puttytel} to display a scroll bar: this is the opposite of -\cw{\-sb\-}. This is the default option: you will probably only need -to specify it explicitly if you have changed the default using the -\cw{ScrollBar} resource. - -\dt \cw{\-log} \e{logfile}, \cw{\-sessionlog} \e{logfile} - -\dd This option makes \cw{puttytel} log all the terminal output to a file -as well as displaying it in the terminal. - -\dt \cw{\-cs} \e{charset} - -\dd This option specifies the character set in which \cw{puttytel} -should assume the session is operating. This character set will be -used to interpret all the data received from the session, and all -input you type or paste into \cw{puttytel} will be converted into -this character set before being sent to the session. - -\lcont{ Any character set name which is valid in a MIME header (and -supported by \cw{puttytel}) should be valid here (examples are -\q{\cw{ISO-8859-1}}, \q{\cw{windows-1252}} or \q{\cw{UTF-8}}). Also, -any character encoding which is valid in an X logical font -description should be valid (\q{\cw{ibm-cp437}}, for example). - -\cw{puttytel}'s default behaviour is to use the same character -encoding as its primary font. If you supply a Unicode -(\cw{iso10646-1}) font, it will default to the UTF-8 character set. - -Character set names are case-insensitive. -} - -\dt \cw{\-nethack} - -\dd Tells \cw{puttytel} to enable NetHack keypad mode, in which the -numeric keypad generates the NetHack \c{hjklyubn} direction keys. -This enables you to play NetHack with the numeric keypad without -having to use the NetHack \c{number_pad} option (which requires you -to press \q{\cw{n}} before any repeat count). So you can move with -the numeric keypad, and enter repeat counts with the normal number -keys. - -\dt \cw{\-help}, \cw{\-\-help} - -\dd Display a message summarizing the available options. - -\dt \cw{\-pgpfp} - -\dd Display the fingerprints of the PuTTY PGP Master Keys, to aid -in verifying new files released by the PuTTY team. - -\dt \cw{\-load} \e{session} - -\dd Load a saved session by name. This allows you to run a saved session -straight from the command line without having to go through the -configuration box first. - -\dt \cw{\-telnet}, \cw{\-rlogin}, \cw{\-supdup}, \cw{\-raw} - -\dd Select the protocol \cw{puttytel} will use to make the connection. - -\dt \cw{\-proxycmd} \e{command} - -\dd Instead of making a TCP connection, use \e{command} as a proxy; -network traffic will be redirected to the standard input and output -of \e{command}. \e{command} must be a single word, so is likely to -need quoting by the shell. - -\lcont{ -The special strings \cw{%host} and \cw{%port} in \e{command} will be -replaced by the hostname and port number you want to connect to; to get -a literal \c{%} sign, enter \c{%%}. - -Backslash escapes are also supported, such as sequences like \c{\\n} -being replaced by a literal newline; to get a literal backslash, -enter \c{\\\\}. (Further escaping may be required by the shell.) - -(See the main PuTTY manual for full details of the supported \cw{%}- -and backslash-delimited tokens, although most of them are probably not -very useful in this context.) -} - -\dt \cw{\-l} \e{username} - -\dd Specify the username to use when logging in to the server. - -\dt \cw{\-P} \e{port} - -\dd Specify the port to connect to the server on. - -\dt \cw{-4}, \cw{-6} - -\dd Force use of IPv4 or IPv6 for network connections. - -\S{puttytel-manpage-saved-sessions} SAVED SESSIONS - -Saved sessions are stored in a \cw{.putty/sessions} subdirectory in -your home directory. - -\S{puttytel-manpage-more-information} MORE INFORMATION - -For more information on PuTTY and PuTTYtel, it's probably best to go -and look at the manual on the web page: - -\W{https://www.chiark.greenend.org.uk/~sgtatham/putty/}\cw{https://www.chiark.greenend.org.uk/~sgtatham/putty/} - -\S{puttytel-manpage-bugs} BUGS - -This man page isn't terribly complete. diff --git a/doc/mancfg.but b/doc/mancfg.but deleted file mode 100644 index 04f7fc44c..000000000 --- a/doc/mancfg.but +++ /dev/null @@ -1,3 +0,0 @@ -\cfg{man-mindepth}{2} - -\C{not-shown} Chapter title which is not shown diff --git a/doc/manpages.but b/doc/manpages.but deleted file mode 100644 index bb75ec8c6..000000000 --- a/doc/manpages.but +++ /dev/null @@ -1,3 +0,0 @@ -\A{man-pages} Man pages for Unix PuTTY - -This appendix contains all the man pages for Unix PuTTY. diff --git a/doc/pageant.but b/doc/pageant.but deleted file mode 100644 index de6d4cb8f..000000000 --- a/doc/pageant.but +++ /dev/null @@ -1,444 +0,0 @@ -\C{pageant} Using \i{Pageant} for authentication - -Pageant is an SSH \i{authentication agent}. It holds your \i{private key}s -in memory, already decoded, so that you can use them often -\I{passwordless login}without needing to type a \i{passphrase}. - -\H{pageant-start} Getting started with Pageant - -Before you run Pageant, you need to have a private key in \c{*.\i{PPK}} -format. See \k{pubkey} to find out how to generate and use one. - -When you run Pageant, it will put an icon of a computer wearing a -hat into the \ii{System tray}. It will then sit and do nothing, until you -load a private key into it. (You may need to use Windows' -\q{Show hidden icons} arrow to see the Pageant icon.) - -If you click the Pageant icon with the right mouse button, you will -see a menu. Select \q{View Keys} from this menu. The Pageant main -window will appear. (You can also bring this window up by -double-clicking on the Pageant icon.) - -The Pageant window contains a list box. This shows the private keys -Pageant is holding. When you start Pageant, it has no keys, so the -list box will be empty. After you add one or more keys, they will -show up in the list box. - -To add a key to Pageant, press the \q{Add Key} button. Pageant will -bring up a file dialog, labelled \q{Select Private Key File}. Find -your private key file in this dialog, and press \q{Open}. - -Pageant will now load the private key. If the key is protected by a -passphrase, Pageant will ask you to type the passphrase. When the -key has been loaded, it will appear in the list in the Pageant -window. - -Now start PuTTY and open an SSH session to a site that accepts your -key. PuTTY will notice that Pageant is running, retrieve the key -automatically from Pageant, and use it to authenticate. You can now -open as many PuTTY sessions as you like without having to type your -passphrase again. - -(PuTTY can be configured not to try to use Pageant, but it will try -by default. See \k{config-ssh-tryagent} and -\k{using-cmdline-agentauth} for more information.) - -When you want to shut down Pageant, click the right button on the -Pageant icon in the System tray, and select \q{Exit} from the menu. -Closing the Pageant main window does \e{not} shut down Pageant. - -If you want Pageant to stay running but forget all the keys it has -acquired, select \q{Remove All Keys} from the System tray menu. - -\H{pageant-mainwin} The Pageant main window - -The Pageant main window appears when you left-click on the Pageant -system tray icon, or alternatively right-click and select \q{View -Keys} from the menu. You can use it to keep track of what keys are -currently loaded into Pageant, and to add new ones or remove the -existing keys. - -\S{pageant-mainwin-keylist} The key list box - -The large list box in the Pageant main window lists the private keys -that are currently loaded into Pageant. The list might look -something like this: - -\c Ed25519 SHA256:TddlQk20DVs4LRcAsIfDN9pInKpY06D+h4kSHwWAj4w -\c RSA 2048 SHA256:8DFtyHm3kQihgy52nzX96qMcEVOq7/yJmmwQQhBWYFg - -For each key, the list box will tell you: - -\b The type of the key. Currently, this can be -\q{RSA} (an RSA key for use with the SSH-2 protocol), -\q{DSA} (a DSA key for use with the SSH-2 protocol), -\q{\i{NIST}} (an ECDSA key for use with the SSH-2 protocol), -\q{Ed25519} (an Ed25519 key for use with the SSH-2 protocol), -\q{Ed448} (an Ed448 key for use with the SSH-2 protocol), -or \q{SSH-1} (an RSA key for use with the old SSH-1 protocol). -(If the key has an associated certificate, this is shown here with a -\q{cert} suffix.) - -\b The size (in bits) of the key, for key types that come in different -sizes. (For ECDSA \q{NIST} keys, this is indicated as \q{p256} or -\q{p384} or \q{p521}.) - -\b The \I{key fingerprint}fingerprint for the public key. This should be -the same fingerprint given by PuTTYgen, and (hopefully) also the same -fingerprint shown by remote utilities such as \i\c{ssh-keygen} when -applied to your \c{authorized_keys} file. - -\lcont{ -For SSH-2 keys, by default this is shown in the \q{SHA256} format. You -can change to the older \q{MD5} format (which looks like \c{aa:bb:cc:...}) -with the \q{Fingerprint type} drop-down, but bear in mind that this -format is less secure and should be avoided for comparison purposes -where possible. - -If some of the keys loaded into Pageant have certificates attached, -then Pageant will default to showing the fingerprint of the underlying -key. This way, a certified and uncertified version of the same key -will have the same fingerprint, so you can see that they match. You -can instead use the \q{Fingerprint type} drop-down to ask for a -different fingerprint to be shown for certified keys, which includes -the certificate as part of the fingerprinted data. That way you can -tell two certificates apart. -} - -\b The comment attached to the key. - -\b The state of deferred decryption, if enabled for this key. -See \k{pageant-deferred-decryption}. - -\S{pageant-mainwin-addkey} The \q{Add Key} button - -To add a key to Pageant by reading it out of a local disk file, -press the \q{Add Key} button in the Pageant main window, or -alternatively right-click on the Pageant icon in the system tray and -select \q{Add Key} from there. - -Pageant will bring up a file dialog, labelled \q{Select Private Key -File}. Find your private key file in this dialog, and press -\q{Open}. If you want to add more than one key at once, you can -select multiple files using Shift-click (to select several adjacent -files) or Ctrl-click (to select non-adjacent files). - -Pageant will now load the private key(s). If a key is protected by a -passphrase, Pageant will ask you to type the passphrase. - -(This is not the only way to add a private key to Pageant. You can -also add one from a remote system by using agent forwarding; see -\k{pageant-forward} for details.) - -\S{pageant-mainwin-remkey} The \q{Remove Key} button - -If you need to remove a key from Pageant, select that key in the -list box, and press the \q{Remove Key} button. Pageant will remove -the key from its memory. - -You can apply this to keys you added using the \q{Add Key} button, -or to keys you added remotely using agent forwarding (see -\k{pageant-forward}); it makes no difference. - -\H{pageant-cmdline} The Pageant command line - -Pageant can be made to do things automatically when it starts up, by -\I{command-line arguments}specifying instructions on its command line. -If you're starting Pageant from the Windows GUI, you can arrange this -by editing the properties of the \i{Windows shortcut} that it was -started from. - -If Pageant is already running, invoking it again with the options -below causes actions to be performed with the existing instance, not a -new one. - -\S{pageant-cmdline-loadkey} Making Pageant automatically load keys -on startup - -Pageant can automatically load one or more private keys when it -starts up, if you provide them on the Pageant command line. Your -command line might then look like: - -\c C:\PuTTY\pageant.exe d:\main.ppk d:\secondary.ppk - -If the keys are stored encrypted, Pageant will request the -passphrases on startup. - -If Pageant is already running, this syntax loads keys into the -existing Pageant. - -You can specify the \cq{--encrypted} option to defer decryption of -these keys; see \k{pageant-deferred-decryption}. - -\S{pageant-cmdline-command} Making Pageant run another program - -You can arrange for Pageant to start another program once it has -initialised itself and loaded any keys specified on its command -line. This program (perhaps a PuTTY, or a WinCVS making use of -Plink, or whatever) will then be able to use the keys Pageant has -loaded. - -You do this by specifying the \I{-c-pageant}\c{-c} option followed -by the command, like this: - -\c C:\PuTTY\pageant.exe d:\main.ppk -c C:\PuTTY\putty.exe - -\S{pageant-cmdline-openssh} Integrating with \i{Windows OpenSSH} - -Windows's own port of OpenSSH uses the same mechanism as Pageant to -talk to its SSH agent (Windows named pipes). This means that Windows -OpenSSH can talk directly to Pageant, if it knows where to find -Pageant's named pipe. - -When Pageant starts up, it can optionally write out a file containing -an OpenSSH configuration directive that tells the Windows \c{ssh.exe} -where to find Pageant. If you include this file from your Windows SSH -configuration, then \c{ssh.exe} should automatically use Pageant as -its agent, so that you can keep your keys in one place and have both -SSH clients able to use them. - -The option is \i\c{--openssh-config}, and you follow it with a filename. - -To refer to this file from your main OpenSSH configuration, you can -use the \cq{Include} directive. For example, you might run Pageant -like this (with your own username substituted, of course): - -\c pageant --openssh-config C:\Users\Simon\.ssh\pageant.conf - -and then add a directive like this to your main \cq{.ssh\\config} file -(assuming that lives in the same directory that you just put -\cw{pageant.conf}): - -\c Include pageant.conf - -\s{Note}: this technique only works with \e{Windows's} port of -OpenSSH, which lives at \cw{C:\\Windows\\System32\\OpenSSH\\ssh.exe} -if you have it installed. (If not, it can be installed as a Windows -optional feature, e.g., via Settings > Apps & features > Optional -features > Add a feature > OpenSSH Client.) - -There are other versions of OpenSSH for Windows, notably the one that -comes with Windows \cw{git}. Those will likely not work with the same -configuration, because they tend to depend on Unix emulation layers -like MinGW or MSys, so they won't speak Windows native pathname syntax -or understand named pipes. The above instructions will only work with -Windows's own version of OpenSSH. - -So, if you want to use Windows \cw{git} with an SSH key held in -Pageant, you'll have to set the environment variable \cw{GIT_SSH}, to -point at a different program. You could point it at -\cw{c:\\Windows\\System32\\OpenSSH\\ssh.exe} once you've done this -setup \dash but it's just as easy to point it at Plink! - -\S{pageant-cmdline-unix} Unix-domain sockets: integrating with WSL 1 - -Pageant can listen on the WinSock implementation of \q{Unix-domain -sockets}. These interoperate with the Unix-domain sockets found in the -original Windows Subsystem for Linux (now known as WSL 1). So if you -ask Pageant to listen on one of these, then your WSL 1 processes can -talk directly to Pageant. - -To configure this, run Pageant with the option \c{--unix}, followed -with a pathname. Then, in WSL 1, set the environment variable -\cw{SSH_AUTH_SOCK} to point at the WSL translation of that pathname. - -For example, you might run - -\c pageant --unix C:\Users\Simon\.ssh\agent.sock - -and in WSL 1, set the environment variable - -\c SSH_AUTH_SOCK=/mnt/c/Users/Simon/.ssh/agent.sock - -Alternatively, you can add a line to your \cw{.ssh/config} file inside -WSL that says - -\c IdentityAgent /mnt/c/Users/Simon/.ssh/agent.sock - -although doing it like that may mean that \cw{ssh-add} commands won't -find the agent, even though \cw{ssh} itself will. - -\s{Security note}: Unix-domain sockets are protected against access by -other users by the file protections on their containing directory. So -if your Windows machine is multiuser, make sure you create the socket -inside a directory that other users can't access at all. (In fact, -that's a good idea on general principles.) - -\s{Compatibility note}: WSL 2 processes cannot talk to Pageant by this -mechanism, because WSL 2's Unix-domain sockets are managed by a -separate Linux kernel, and not by the same kernel that WinSock talks -to. - -\S{pageant-cmdline-keylist} Starting with the key list visible - -Start Pageant with the \i\c{--keylist} option to show the main window -as soon as it starts up. - -\S{pageant-cmdline-restrict-acl} Restricting the \i{Windows process ACL} - -Pageant supports the same \i\c{-restrict-acl} option as the other -PuTTY utilities to lock down the Pageant process's access control; -see \k{using-cmdline-restrict-acl} for why you might want to do this. - -By default, if Pageant is started with \c{-restrict-acl}, it won't -pass this to any PuTTY sessions started from its System Tray submenu. -Use \c{-restrict-putty-acl} to change this. (Again, see -\k{using-cmdline-restrict-acl} for details.) - -\H{pageant-forward} Using \i{agent forwarding} - -Agent forwarding is a mechanism that allows applications on your SSH -server machine to talk to the agent on your client machine. - -Note that at present, whether agent forwarding in SSH-2 is available -depends on your server. Pageant's protocol is compatible with the -\i{OpenSSH} server, but the \i\cw{ssh.com} server uses a different -agent protocol, which PuTTY does not yet support. - -To enable agent forwarding, first start Pageant. Then set up a PuTTY -SSH session in which \q{Allow agent forwarding} is enabled (see -\k{config-ssh-agentfwd}). Open the session as normal. (Alternatively, -you can use the \c{-A} command line option; see -\k{using-cmdline-agent} for details.) - -If this has worked, your applications on the server should now have -access to a Unix domain socket which the SSH server will forward -back to PuTTY, and PuTTY will forward on to the agent. To check that -this has actually happened, you can try this command on Unix server -machines: - -\c unixbox:~$ echo $SSH_AUTH_SOCK -\c /tmp/ssh-XXNP18Jz/agent.28794 -\c unixbox:~$ - -If the result line comes up blank, agent forwarding has not been -enabled at all. - -Now if you run \c{ssh} on the server and use it to connect through -to another server that accepts one of the keys in Pageant, you -should be able to log in without a password: - -\c unixbox:~$ ssh -v otherunixbox -\c [...] -\c debug: next auth method to try is publickey -\c debug: userauth_pubkey_agent: trying agent key my-putty-key -\c debug: ssh-userauth2 successful: method publickey -\c [...] - -If you enable agent forwarding on \e{that} SSH connection as well -(see the manual for your server-side SSH client to find out how to -do this), your authentication keys will still be available on the -next machine you connect to - two SSH connections away from where -they're actually stored. - -In addition, if you have a private key on one of the SSH servers, -you can send it all the way back to Pageant using the local -\i\c{ssh-add} command: - -\c unixbox:~$ ssh-add ~/.ssh/id_rsa -\c Need passphrase for /home/fred/.ssh/id_rsa -\c Enter passphrase for /home/fred/.ssh/id_rsa: -\c Identity added: /home/fred/.ssh/id_rsa (/home/simon/.ssh/id_rsa) -\c unixbox:~$ - -and then it's available to every machine that has agent forwarding -available (not just the ones downstream of the place you added it). - -\H{pageant-deferred-decryption} Loading keys without decrypting them - -You can add keys to Pageant \e{without} decrypting them. The key -file will be held in Pageant's memory still encrypted, and when a -client program first tries to use the key, Pageant will display a -dialog box prompting for the passphrase so that the key can be -decrypted. - -This works the same way whether the key is used by an instance of -PuTTY running locally, or a remote client connecting to Pageant -through agent forwarding. - -To add a key to Pageant in this encrypted form, press the \q{Add Key -(encrypted)} button in the Pageant main window, or alternatively -right-click on the Pageant icon in the system tray and select \q{Add -Key (encrypted)} from there. Pageant will bring up a file dialog, in -just the same way as it would for the plain \q{Add Key} button. But it -won't ask for a passphrase. Instead, the key will be listed in the -main window with \q{(encrypted)} after it. - -To start Pageant up in the first place with encrypted keys loaded into -it, you can use the \cq{--encrypted} option on the command line. For -example: - -\c C:\PuTTY\pageant.exe --encrypted d:\main.ppk - -After a key has been decrypted for the first use, it remains -decrypted, so that it can be used again. The main window will list -the key with \q{(\i{re-encryptable})} after it. You can revert it -to the previous state, where a passphrase is required, using the -\q{\i{Re-encrypt}} button in the Pageant main window. - -You can also \q{re-encrypt} all keys that were added encrypted by -choosing \q{Re-encrypt All Keys} from the System tray menu. -(Note that this does \e{not} discard cleartext keys that were not -previously added encrypted!) - -\s{CAUTION}: When Pageant displays a prompt to decrypt an -already-loaded key, it cannot give keyboard focus to the prompt dialog -box. As far as I know this is a deliberate defensive measure by -Windows, against malicious software. So make sure you click in the -prompt window before typing your passphrase, or else the passphrase -might be sent to somewhere you didn't want to trust with it! - -\H{pageant-security} Security considerations - -\I{security risk}Using Pageant for public-key authentication gives you the -convenience of being able to open multiple SSH sessions without -having to type a passphrase every time, but also gives you the -security benefit of never storing a decrypted private key on disk. -Many people feel this is a good compromise between security and -convenience. - -It \e{is} a compromise, however. Holding your decrypted private keys -in Pageant is better than storing them in easy-to-find disk files, -but still less secure than not storing them anywhere at all. This is -for two reasons: - -\b Windows unfortunately provides no way to protect pieces of memory -from being written to the system \i{swap file}. So if Pageant is holding -your private keys for a long period of time, it's possible that -decrypted private key data may be written to the system swap file, -and an attacker who gained access to your hard disk later on might -be able to recover that data. (However, if you stored an unencrypted -key in a disk file they would \e{certainly} be able to recover it.) - -\b Although, like most modern operating systems, Windows prevents -programs from accidentally accessing one another's memory space, it -does allow programs to access one another's memory space -deliberately, for special purposes such as debugging. This means -that if you allow a virus, trojan, or other malicious program on to -your Windows system while Pageant is running, it could access the -memory of the Pageant process, extract your decrypted authentication -keys, and send them back to its master. - -Similarly, use of agent \e{forwarding} is a security improvement on -other methods of one-touch authentication, but not perfect. Holding -your keys in Pageant on your Windows box has a security advantage -over holding them on the remote server machine itself (either in an -agent or just unencrypted on disk), because if the server machine -ever sees your unencrypted private key then the sysadmin or anyone -who cracks the machine can steal the keys and pretend to be you for -as long as they want. - -However, the sysadmin of the server machine can always pretend to be -you \e{on that machine}. So if you forward your agent to a server -machine, then the sysadmin of that machine can access the forwarded -agent connection and request signatures from any of your private keys, -and can therefore log in to other machines as you. They can only do -this to a limited extent - when the agent forwarding disappears they -lose the ability - but using Pageant doesn't actually \e{prevent} the -sysadmin (or hackers) on the server from doing this. - -Therefore, if you don't trust the sysadmin of a server machine, you -should \e{never} use agent forwarding to that machine. (Of course -you also shouldn't store private keys on that machine, type -passphrases into it, or log into other machines from it in any way -at all; Pageant is hardly unique in this respect.) diff --git a/doc/pgpkeys.but b/doc/pgpkeys.but deleted file mode 100644 index 9e25e2081..000000000 --- a/doc/pgpkeys.but +++ /dev/null @@ -1,282 +0,0 @@ -\A{pgpkeys} PuTTY download keys and signatures - -\I{verifying new versions}We create \i{GPG signatures} for all the PuTTY -files distributed from our web site, so that users can be confident -that the files have not been tampered with. Here we identify -our public keys, and explain our signature policy so you can have an -accurate idea of what each signature guarantees. -This description is provided as both a web page on the PuTTY site, and -an appendix in the PuTTY manual. - -As of release 0.58, all of the PuTTY executables contain fingerprint -material (usually accessed via the \i\c{-pgpfp} command-line -option), such that if you have an executable you trust, you can use -it to establish a trust path, for instance to a newer version -downloaded from the Internet. - -As of release 0.67, the Windows executables and installer also contain -built-in signatures that are automatically verified by Windows' own -mechanism (\q{\i{Authenticode}}). The keys used for that are different, -and are not covered here. - -(Note that none of the keys, signatures, etc mentioned here have -anything to do with keys used with SSH - they are purely for verifying -the origin of files distributed by the PuTTY team.) - -\H{pgpkeys-pubkey} Public keys - -We maintain multiple keys, stored with different levels of security -due to being used in different ways. See \k{pgpkeys-security} below -for details. - -The keys we provide are: - -\dt Snapshot Key - -\dd Used to sign routine development builds of PuTTY: nightly -snapshots, pre-releases, and sometimes also custom diagnostic builds -we send to particular users. - -\dt Release Key - -\dd Used to sign manually released versions of PuTTY. - -\dt Secure Contact Key - -\dd An encryption-capable key suitable for people to send confidential -messages to the PuTTY team, e.g. reports of vulnerabilities. - -\dt Master Key - -\dd Used to tie all the above keys into the GPG web of trust. The -Master Key signs all the other keys, and other GPG users have signed -it in turn. - -The current issue of those keys are available for download from the -PuTTY website, and are also available on PGP keyservers using the key -IDs listed below. - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2023.asc}{\s{Master Key} (2023)} - -\dd RSA, 4096-bit. Key ID: \cw{B15D9EFC216B06A1}. Fingerprint: -\cw{28D4\_7C46\_55E7\_65A6\_D827\_AC66\_B15D\_9EFC\_216B\_06A1} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2023.asc}{\s{Release Key} (2023)} - -\dd RSA, 3072-bit. Key ID: \cw{1993D21BCAD1AA77}. Fingerprint: -\cw{F412\_BA3A\_A30F\_DC0E\_77B4\_E387\_1993\_D21B\_CAD1\_AA77} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2023.asc}{\s{Snapshot Key} (2023)} - -\dd RSA, 3072-bit. Key ID: \cw{10625E553F53FAAD}. Fingerprint: -\cw{74CC\_6DD9\_ABA7\_31D4\_C5A0\_C2D0\_1062\_5E55\_3F53\_FAAD} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2023.asc}{\s{Secure Contact Key} (2023)} - -\dd RSA, 3072-bit. Key ID: \cw{1559F6A8929F5EFC}. Fingerprint: -\cw{01F5\_A2B1\_1388\_D64B\_707F\_897F\_1559\_F6A8\_929F\_5EFC} - -\H{pgpkeys-security} Security details - -The various keys have various different security levels. This -section explains what those security levels are, and how far you can -expect to trust each key. - -\S{pgpkeys-snapshot} The Development Snapshots key - -The Development Snapshots private key is stored \e{without a -passphrase}. This is necessary, because the snapshots are generated -every night without human intervention, so nobody would be able to -type a passphrase. - -The snapshots are built and signed on a team member's home computers, -before being uploaded to the web server from which you download them. - -Therefore, a signature from the Development Snapshots key \e{DOES} -protect you against: - -\b People tampering with the PuTTY binaries between the PuTTY web site -and you. - -\b The maintainers of our web server attempting to abuse their root -privilege to tamper with the binaries. - -But it \e{DOES NOT} protect you against: - -\b People tampering with the binaries before they are uploaded to our -download servers. - -\b People tampering with the build machines so that the next set of -binaries they build will be malicious in some way. - -\b People stealing the unencrypted private key from the build machine -it lives on. - -Of course, we take all reasonable precautions to guard the build -machines. But when you see a signature, you should always be certain -of precisely what it guarantees and precisely what it does not. - -\S{pgpkeys-release} The Releases key - -The Releases key is more secure: because it is only used at release -time, to sign each release by hand, we can store it encrypted. - -The Releases private key is kept encrypted on the developers' own -local machines. So an attacker wanting to steal it would have to also -steal the passphrase. - -\S{pgpkeys-contact} The Secure Contact Key - -The Secure Contact Key is stored with a similar level of security to -the Release Key: it is stored with a passphrase, and no automated -script has access to it. - -\S{pgpkeys-master} The Master Keys - -The Master Key signs almost nothing. Its purpose is to bind the other -keys together and certify that they are all owned by the same people -and part of the same integrated setup. The only signatures produced by -the Master Key, \e{ever}, should be the signatures on the other keys. - -The Master Key is especially long, and its private key and passphrase -are stored with special care. - -We have collected some third-party signatures on the Master Key, in -order to increase the chances that you can find a suitable trust path -to them. - -We have uploaded our various keys to public keyservers, so that -even if you don't know any of the people who have signed our -keys, you can still be reasonably confident that an attacker would -find it hard to substitute fake keys on all the public keyservers at -once. - -\H{pgpkeys-rollover} Key rollover - -Our current keys were generated in July 2023. - -Each new Master Key is signed with the old one, to show that it really -is owned by the same people and not substituted by an attacker. - -Each new Master Key also signs the previous Release Keys, in case -you're trying to verify the signatures on a release prior to the -rollover and can find a chain of trust to those keys from any of the -people who have signed our new Master Key. - -Each release is signed with the Release Key that was current at the -time of release. We don't go back and re-sign old releases with newly -generated keys. - -The details of all previous keys are given here. - -\s{Keys generated in the 2021 rollover} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2021.asc}{\s{Master Key} (2021)} - -\dd RSA, 3072-bit. Key ID: \cw{DD4355EAAC1119DE}. Fingerprint: -\cw{A872\_D42F\_1660\_890F\_0E05\_223E\_DD43\_55EA\_AC11\_19DE} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2021.asc}{\s{Release Key} (2021)} - -\dd RSA, 3072-bit. Key ID: \cw{E4F83EA2AA4915EC}. Fingerprint: -\cw{2CF6\_134B\_D3F7\_7A65\_88EB\_D668\_E4F8\_3EA2\_AA49\_15EC} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2021.asc}{\s{Snapshot Key} (2021)} - -\dd RSA, 3072-bit. Key ID: \cw{B43979F89F446CFD}. Fingerprint: -\cw{1FD3\_BCAC\_E532\_FBE0\_6A8C\_09E2\_B439\_79F8\_9F44\_6CFD} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2021.asc}{\s{Secure Contact Key} (2021)} - -\dd RSA, 3072-bit. Key ID: \cw{012C59D4211BD62A}. Fingerprint: -\cw{E30F\_1354\_2A04\_BE0E\_56F0\_5801\_012C\_59D4\_211B\_D62A} - -\s{Keys generated in the 2018 rollover} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2018.asc}{\s{Master Key} (2018)} - -\dd RSA, 4096-bit. Key ID: \cw{76BC7FE4EBFD2D9E}. Fingerprint: -\cw{24E1\_B1C5\_75EA\_3C9F\_F752\_\_A922\_76BC\_7FE4\_EBFD\_2D9E} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2018.asc}{\s{Release Key} (2018)} - -\dd RSA, 3072-bit. Key ID: \cw{6289A25F4AE8DA82}. Fingerprint: -\cw{E273\_94AC\_A3F9\_D904\_9522\_\_E054\_6289\_A25F\_4AE8\_DA82} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2018.asc}{\s{Snapshot Key} (2018)} - -\dd RSA, 3072-bit. Key ID: \cw{38BA7229B7588FD1}. Fingerprint: -\cw{C92B\_52E9\_9AB6\_1DDA\_33DB\_\_2B7A\_38BA\_7229\_B758\_8FD1} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2018.asc}{\s{Secure Contact Key} (2018)} - -\dd RSA, 3072-bit. Key ID: \cw{657D487977F95C98}. Fingerprint: -\cw{A680\_0082\_2998\_6E46\_22CA\_\_0E43\_657D\_4879\_77F9\_5C98} - -\s{Key generated in 2016} (when we first introduced the Secure Contact Key) - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/contact-2016.asc}{\s{Secure Contact Key} (2016)} - -\dd RSA, 2048-bit. Main key ID: \cw{2048R/8A0AF00B} (long version: -\cw{2048R/C4FCAAD08A0AF00B}). Encryption subkey ID: -\cw{2048R/50C2CF5C} (long version: \cw{2048R/9EB39CC150C2CF5C}). -Fingerprint: -\cw{8A26\_250E\_763F\_E359\_75F3\_\_118F\_C4FC\_AAD0\_8A0A\_F00B} - -\s{Keys generated in the 2015 rollover} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-2015.asc}{\s{Master Key} (2015)} - -\dd RSA, 4096-bit. Key ID: \cw{4096R/04676F7C} (long version: -\cw{4096R/AB585DC604676F7C}). Fingerprint: -\cw{440D\_E3B5\_B7A1\_CA85\_B3CC\_\_1718\_AB58\_5DC6\_0467\_6F7C} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-2015.asc}{\s{Release Key} (2015)} - -\dd RSA, 2048-bit. Key ID: \cw{2048R/B43434E4} (long version: -\cw{2048R/9DFE2648B43434E4}). Fingerprint: -\cw{0054\_DDAA\_8ADA\_15D2\_768A\_\_6DE7\_9DFE\_2648\_B434\_34E4} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-2015.asc}{\s{Snapshot Key} (2015)} - -\dd RSA, 2048-bit. Key ID: \cw{2048R/D15F7E8A} (long version: -\cw{2048R/EEF20295D15F7E8A}). Fingerprint: -\cw{0A3B\_0048\_FE49\_9B67\_A234\_\_FEB6\_EEF2\_0295\_D15F\_7E8A} - -\s{Original keys generated in 2000} (two sets, RSA and DSA) - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-rsa.asc}{\s{Master Key} (original RSA)} - -\dd RSA, 1024-bit. Key ID: \cw{1024R/1E34AC41} (long version: -\cw{1024R/9D5877BF1E34AC41}). Fingerprint: -\cw{8F\_15\_97\_DA\_25\_30\_AB\_0D\_\_88\_D1\_92\_54\_11\_CF\_0C\_4C} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/master-dsa.asc}{\s{Master Key} (original DSA)} - -\dd DSA, 1024-bit. Key ID: \cw{1024D/6A93B34E} (long version: -\cw{1024D/4F5E6DF56A93B34E}). Fingerprint: -\cw{313C\_3E76\_4B74\_C2C5\_F2AE\_\_83A8\_4F5E\_6DF5\_6A93\_B34E} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-rsa.asc}{\s{Release Key} (original RSA)} - -\dd RSA, 1024-bit. Key ID: \cw{1024R/B41CAE29} (long version: -\cw{1024R/EF39CCC0B41CAE29}). Fingerprint: -\cw{AE\_65\_D3\_F7\_85\_D3\_18\_E0\_\_3B\_0C\_9B\_02\_FF\_3A\_81\_FE} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/release-dsa.asc}{\s{Release Key} (original DSA)} - -\dd DSA, 1024-bit. Key ID: \cw{1024D/08B0A90B} (long version: -\cw{1024D/FECD6F3F08B0A90B}). Fingerprint: -\cw{00B1\_1009\_38E6\_9800\_6518\_\_F0AB\_FECD\_6F3F\_08B0\_A90B} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-rsa.asc}{\s{Snapshot Key} (original RSA)} - -\dd RSA, 1024-bit. Key ID: \cw{1024R/32B903A9} (long version: -\cw{1024R/FAAED21532B903A9}). Fingerprint: -\cw{86\_8B\_1F\_79\_9C\_F4\_7F\_BD\_\_8B\_1B\_D7\_8E\_C6\_4E\_4C\_03} - -\dt \W{https://www.chiark.greenend.org.uk/~sgtatham/putty/keys/snapshot-dsa.asc}{\s{Snapshot Key} (original DSA)} - -\dd DSA, 1024-bit. Key ID: \cw{1024D/7D3E4A00} (long version: -\cw{1024D/165E56F77D3E4A00}). Fingerprint: -\cw{63DD\_8EF8\_32F5\_D777\_9FF0\_\_2947\_165E\_56F7\_7D3E\_4A00} diff --git a/doc/plink.but b/doc/plink.but deleted file mode 100644 index 96d78c10b..000000000 --- a/doc/plink.but +++ /dev/null @@ -1,444 +0,0 @@ -\C{plink} Using the command-line connection tool \i{Plink} - -\i{Plink} is a command-line connection tool similar to UNIX \c{ssh}. -It is mostly used for \i{automated operations}, such as making CVS -access a repository on a remote server. - -Plink is probably not what you want if you want to run an -\i{interactive session} in a console window. - -\H{plink-starting} Starting Plink - -Plink is a command line application. This means that you cannot just -double-click on its icon to run it and instead you have to bring up -a \i{console window}. In Windows 95, 98, and ME, this is called an -\q{MS-DOS Prompt}, and in Windows NT, 2000, and XP, it is called a -\q{Command Prompt}. It should be available from the Programs section -of your Start Menu. - -In order to use Plink, the file \c{plink.exe} will need either to be -on your \i{\c{PATH}} or in your current directory. To add the -directory containing Plink to your \c{PATH} environment variable, -type into the console window: - -\c set PATH=C:\path\to\putty\directory;%PATH% - -This will only work for the lifetime of that particular console -window. To set your \c{PATH} more permanently on Windows NT, 2000, -and XP, use the Environment tab of the System Control Panel. On -Windows 95, 98, and ME, you will need to edit your \i\c{AUTOEXEC.BAT} -to include a \c{set} command like the one above. - -\H{plink-usage} Using Plink - -This section describes the basics of how to use Plink for -interactive logins and for automated processes. - -Once you've got a console window to type into, you can just type -\c{plink} on its own to bring up a usage message. This tells you the -version of Plink you're using, and gives you a brief summary of how to -use Plink: - -\c C:\>plink -\c Plink: command-line connection utility -\c Release 0.80 -\c Usage: plink [options] [user@]host [command] -\c ("host" can also be a PuTTY saved session name) -\c Options: -\c -V print version information and exit -\c -pgpfp print PGP key fingerprints and exit -\c -v show verbose messages -\c -load sessname Load settings from saved session -\c -ssh -telnet -rlogin -raw -serial -\c force use of a particular protocol -\c -ssh-connection -\c force use of the bare ssh-connection protocol -\c -P port connect to specified port -\c -l user connect with specified username -\c -batch disable all interactive prompts -\c -proxycmd command -\c use 'command' as local proxy -\c -sercfg configuration-string (e.g. 19200,8,n,1,X) -\c Specify the serial configuration (serial only) -\c The following options only apply to SSH connections: -\c -pwfile file login with password read from specified file -\c -D [listen-IP:]listen-port -\c Dynamic SOCKS-based port forwarding -\c -L [listen-IP:]listen-port:host:port -\c Forward local port to remote address -\c -R [listen-IP:]listen-port:host:port -\c Forward remote port to local address -\c -X -x enable / disable X11 forwarding -\c -A -a enable / disable agent forwarding -\c -t -T enable / disable pty allocation -\c -1 -2 force use of particular SSH protocol version -\c -4 -6 force use of IPv4 or IPv6 -\c -C enable compression -\c -i key private key file for user authentication -\c -noagent disable use of Pageant -\c -agent enable use of Pageant -\c -no-trivial-auth -\c disconnect if SSH authentication succeeds trivially -\c -noshare disable use of connection sharing -\c -share enable use of connection sharing -\c -hostkey keyid -\c manually specify a host key (may be repeated) -\c -sanitise-stderr, -sanitise-stdout, -no-sanitise-stderr, -no-sanitise-stdout -\c do/don't strip control chars from standard output/error -\c -no-antispoof omit anti-spoofing prompt after authentication -\c -m file read remote command(s) from file -\c -s remote command is an SSH subsystem (SSH-2 only) -\c -N don't start a shell/command (SSH-2 only) -\c -nc host:port -\c open tunnel in place of session (SSH-2 only) -\c -sshlog file -\c -sshrawlog file -\c log protocol details to a file -\c -logoverwrite -\c -logappend -\c control what happens when a log file already exists -\c -shareexists -\c test whether a connection-sharing upstream exists - -Once this works, you are ready to use Plink. - -\S{plink-usage-interactive} Using Plink for interactive logins - -To make a simple interactive connection to a remote server, just -type \c{plink} and then the host name: - -\c C:\>plink login.example.com -\c -\c Debian GNU/Linux 2.2 flunky.example.com -\c flunky login: - -You should then be able to log in as normal and run a session. The -output sent by the server will be written straight to your command -prompt window, which will most likely not interpret terminal \i{control -codes} in the way the server expects it to. So if you run any -full-screen applications, for example, you can expect to see strange -characters appearing in your window. Interactive connections like -this are not the main point of Plink. - -In order to connect with a different protocol, you can give the -command line options \c{-ssh}, \c{-ssh-connection}, \c{-telnet}, -\c{-rlogin}, or \c{-raw}. To make an SSH connection, for example: - -\c C:\>plink -ssh login.example.com -\c login as: - -If you have already set up a PuTTY saved session, then instead of -supplying a host name, you can give the saved session name. This -allows you to use public-key authentication, specify a user name, -and use most of the other features of PuTTY: - -\c C:\>plink my-ssh-session -\c Sent username "fred" -\c Authenticating with public key "fred@winbox" -\c Last login: Thu Dec 6 19:25:33 2001 from :0.0 -\c fred@flunky:~$ - -(You can also use the \c{-load} command-line option to load a saved -session; see \k{using-cmdline-load}. If you use \c{-load}, the saved -session exists, and it specifies a hostname, you cannot also specify a -\c{host} or \c{user@host} argument - it will be treated as part of the -remote command.) - -\S{plink-usage-batch} Using Plink for automated connections - -More typically Plink is used with the SSH protocol, to enable you to -talk directly to a program running on the server. To do this you -have to ensure Plink is \e{using} the SSH protocol. You can do this -in several ways: - -\b Use the \c{-ssh} option as described in -\k{plink-usage-interactive}. - -\b Set up a PuTTY saved session that describes the server you are -connecting to, and that also specifies the protocol as SSH. - -\b Set the Windows environment variable \i\c{PLINK_PROTOCOL} to the -word \c{ssh}. - -Usually Plink is not invoked directly by a user, but run -automatically by another process. Therefore you typically do not -want Plink to prompt you for a user name or a password. - -Next, you are likely to need to avoid the various interactive -prompts Plink can produce. You might be prompted to verify the host -key of the server you're connecting to, to enter a user name, or to -enter a password. - -To avoid being prompted for the server host key when using Plink for -an automated connection, you can first make a \e{manual} -connection (using either of PuTTY or Plink) to the same server, -verify the host key (see \k{gs-hostkey} for more information), and -select \q{Accept} to add the host key to the Registry. After that, -Plink commands connecting to that server should not give a host key -prompt unless the host key changes. Alternatively, you can specify -the appropriate host key(s) on Plink's command line every time you -use it; see \k{using-cmdline-hostkey}. - -To avoid being prompted for a user name, you can: - -\b Use the \c{-l} option to specify a user name on the command line. -For example, \c{plink login.example.com -l fred}. - -\b Set up a PuTTY saved session that describes the server you are -connecting to, and that also specifies the username to log in as -(see \k{config-username}). - -To avoid being prompted for a password, you should almost certainly -set up \i{public-key authentication}. (See \k{pubkey} for a general -introduction to public-key authentication.) Again, you can do this -in two ways: - -\b Set up a PuTTY saved session that describes the server you are -connecting to, and that also specifies a private key file (see -\k{config-ssh-privkey}). For this to work without prompting, your -private key will need to have no passphrase. - -\b Store the private key in Pageant. See \k{pageant} for further -information. - -Once you have done all this, you should be able to run a remote -command on the SSH server machine and have it execute automatically -with no prompting: - -\c C:\>plink login.example.com -l fred echo hello, world -\c hello, world -\c -\c C:\> - -Or, if you have set up a saved session with all the connection -details: - -\c C:\>plink mysession echo hello, world -\c hello, world -\c -\c C:\> - -Then you can set up other programs to run this Plink command and -talk to it as if it were a process on the server machine. - -\S{plink-options} Plink command line options - -Plink accepts all the general command line options supported by the -PuTTY tools. See \k{using-general-opts} for a description of these -options. - -Plink also supports some of its own options. The following sections -describe Plink's specific command-line options. - -\S2{plink-option-batch} \I{-batch-plink}\c{-batch}: disable all -interactive prompts - -If you use the \c{-batch} option, Plink will never give an -interactive prompt while establishing the connection. If the -server's host key is invalid, for example (see \k{gs-hostkey}), then -the connection will simply be abandoned instead of asking you what -to do next. - -This may help Plink's behaviour when it is used in automated -scripts: using \c{-batch}, if something goes wrong at connection -time, the batch job will fail rather than hang. - -\S2{plink-option-s} \I{-s-plink}\c{-s}: remote command is SSH subsystem - -If you specify the \c{-s} option, Plink passes the specified command -as the name of an SSH \q{\i{subsystem}} rather than an ordinary command -line. - -(This option is only meaningful with the SSH-2 protocol.) - -\S2{plink-option-share} \I{-share-plink}\c{-share}: -Test and try to share an existing connection. - -This option tries to detect if an existing connection can be shared -(See \k{config-ssh-sharing} for more information about SSH connection -sharing.) and reuses that connection. - -A Plink invocation of the form: - -\c plink -share -\e iiiiiiiii - -will test whether there is currently a viable \q{upstream} for the -session in question, which can be specified using any syntax you'd -normally use with Plink to make an actual connection (a host/port -number, a bare saved session name, \c{-load}, etc). If no \q{upstream} -viable session is found and \c{-share} is specified, this connection -will be become the \q{upstream} connection for subsequent connection -sharing tries. - -(This option is only meaningful with the SSH-2 protocol.) - -\S2{plink-option-shareexists} \I{-shareexists-plink}\c{-shareexists}: -test for connection-sharing upstream - -This option does not make a new connection; instead it allows testing -for the presence of an existing connection that can be shared. -(See \k{config-ssh-sharing} for more information about SSH connection -sharing.) - -A Plink invocation of the form: - -\c plink -shareexists -\e iiiiiiiii - -will test whether there is currently a viable \q{upstream} for the -session in question, which can be specified using any syntax you'd -normally use with Plink to make an actual connection (a host/port -number, a bare saved session name, \c{-load}, etc). It returns a -zero exit status if a usable \q{upstream} exists, nonzero otherwise. - -(This option is only meaningful with the SSH-2 protocol.) - -\S2{plink-option-sanitise} \I{-sanitise-stderr}\I{-sanitise-stdout}\I{-no-sanitise-stderr}\I{-no-sanitise-stdout}\c{-sanitise-}\e{stream}: control output sanitisation - -In some situations, Plink applies a sanitisation pass to the output -received from the server, to strip out control characters such as -backspace and the escape character. - -The idea of this is to prevent remote processes from sending confusing -escape sequences through the standard error channel when Plink is -being used as a transport for something like \cw{git} or CVS. If the -server actually wants to send an error message, it will probably be -plain text; if the server abuses that channel to try to write over -unexpected parts of your terminal display, Plink will try to stop it. - -By default, this only happens for output channels which are sent to a -Windows console device, or a Unix terminal device. (Any output stream -going somewhere else is likely to be needed by an 8-bit protocol and -must not be tampered with at all.) It also stops happening if you tell -Plink to allocate a remote pseudo-terminal (see \k{using-cmdline-pty} -and \k{config-ssh-pty}), on the basis that in that situation you often -\e{want} escape sequences from the server to go to your terminal. - -But in case Plink guesses wrong about whether you want this -sanitisation, you can override it in either direction, using one of -these options: - -\dt \c{-sanitise-stderr} - -\dd Sanitise server data written to Plink's standard error channel, -regardless of terminals and consoles and remote ptys. - -\dt \c{-no-sanitise-stderr} - -\dd Do not sanitise server data written to Plink's standard error -channel. - -\dt \c{-sanitise-stdout} - -\dd Sanitise server data written to Plink's standard output channel. - -\dt \c{-no-sanitise-stdout} - -\dd Do not sanitise server data written to Plink's standard output -channel. - -\S2{plink-option-antispoof} \i{-no-antispoof}: turn off authentication spoofing protection prompt - -In SSH, some possible server authentication methods require user input -(for example, password authentication, or entering a private key -passphrase), and others do not (e.g. a private key held in Pageant). - -If you use Plink to run an interactive login session, and if Plink -authenticates without needing any user interaction, and if the server -is malicious or compromised, it could try to trick you into giving it -authentication data that should not go to the server (such as your -private key passphrase), by sending what \e{looks} like one of Plink's -local prompts, as if Plink had not already authenticated. - -To protect against this, Plink's default policy is to finish the -authentication phase with a final trivial prompt looking like this: - -\c Access granted. Press Return to begin session. - -so that if you saw anything that looked like an authentication prompt -\e{after} that line, you would know it was not from Plink. - -That extra interactive step is inconvenient. So Plink will turn it off -in as many situations as it can: - -\b If Plink's standard input is not pointing at a console or terminal -device \dash for example, if you're using Plink as a transport for -some automated application like version control \dash then you -\e{can't} type passphrases into the server anyway. In that situation, -Plink won't try to protect you from the server trying to fool you into -doing so. - -\b If Plink is in batch mode (see \k{plink-usage-batch}), then it -\e{never} does any interactive authentication. So anything looking -like an interactive authentication prompt is automatically suspect, -and so Plink omits the anti-spoofing prompt. - -But if you still find the protective prompt inconvenient, and you -trust the server not to try a trick like this, you can turn it off -using the \cq{-no-antispoof} option. - -\H{plink-batch} Using Plink in \i{batch files} and \i{scripts} - -Once you have set up Plink to be able to log in to a remote server -without any interactive prompting (see \k{plink-usage-batch}), you -can use it for lots of scripting and batch purposes. For example, to -start a backup on a remote machine, you might use a command like: - -\c plink root@myserver /etc/backups/do-backup.sh - -Or perhaps you want to fetch all system log lines relating to a -particular web area: - -\c plink mysession grep /~fred/ /var/log/httpd/access.log > fredlog - -Any non-interactive command you could usefully run on the server -command line, you can run in a batch file using Plink in this way. - -\H{plink-cvs} Using Plink with \i{CVS} - -To use Plink with CVS, you need to set the environment variable -\i\c{CVS_RSH} to point to Plink: - -\c set CVS_RSH=\path\to\plink.exe - -You also need to arrange to be able to connect to a remote host -without any interactive prompts, as described in -\k{plink-usage-batch}. - -You should then be able to run CVS as follows: - -\c cvs -d :ext:user@sessionname:/path/to/repository co module - -If you specified a username in your saved session, you don't even -need to specify the \q{user} part of this, and you can just say: - -\c cvs -d :ext:sessionname:/path/to/repository co module - -\H{plink-wincvs} Using Plink with \i{WinCVS} - -Plink can also be used with WinCVS. Firstly, arrange for Plink to be -able to connect to a remote host non-interactively, as described in -\k{plink-usage-batch}. - -Then, in WinCVS, bring up the \q{Preferences} dialogue box from the -\e{Admin} menu, and switch to the \q{Ports} tab. Tick the box there -labelled \q{Check for an alternate \cw{rsh} name} and in the text -entry field to the right enter the full path to \c{plink.exe}. -Select \q{OK} on the \q{Preferences} dialogue box. - -Next, select \q{Command Line} from the WinCVS \q{Admin} menu, and type -a CVS command as in \k{plink-cvs}, for example: - -\c cvs -d :ext:user@hostname:/path/to/repository co module - -or (if you're using a saved session): - -\c cvs -d :ext:user@sessionname:/path/to/repository co module - -Select the folder you want to check out to with the \q{Change Folder} -button, and click \q{OK} to check out your module. Once you've got -modules checked out, WinCVS will happily invoke plink from the GUI for -CVS operations. - -\# \H{plink-whatelse} Using Plink with... ? diff --git a/doc/pscp.but b/doc/pscp.but deleted file mode 100644 index 2d7f022f7..000000000 --- a/doc/pscp.but +++ /dev/null @@ -1,340 +0,0 @@ -\#FIXME: Need examples - -\C{pscp} Using \i{PSCP} to transfer files securely - -\i{PSCP}, the PuTTY Secure Copy client, is a tool for \i{transferring files} -securely between computers using an SSH connection. - -If you have an SSH-2 server, you might prefer PSFTP (see \k{psftp}) -for interactive use. PSFTP does not in general work with SSH-1 -servers, however. - -\H{pscp-starting} Starting PSCP - -PSCP is a command line application. This means that you cannot just -double-click on its icon to run it and instead you have to bring up a -\i{console window}. With Windows 95, 98, and ME, this is called an -\q{MS-DOS Prompt} and with Windows NT, 2000, and XP, it is called a -\q{Command Prompt}. It should be available from the Programs section -of your \i{Start Menu}. - -To start PSCP it will need either to be on your \i{\c{PATH}} or in your -current directory. To add the directory containing PSCP to your -\c{PATH} environment variable, type into the console window: - -\c set PATH=C:\path\to\putty\directory;%PATH% - -This will only work for the lifetime of that particular console -window. To set your \c{PATH} more permanently on Windows NT, 2000, -and XP, use the Environment tab of the System Control Panel. On -Windows 95, 98, and ME, you will need to edit your \i\c{AUTOEXEC.BAT} -to include a \c{set} command like the one above. - -\H{pscp-usage} PSCP Usage - -Once you've got a console window to type into, you can just type -\c{pscp} on its own to bring up a usage message. This tells you the -version of PSCP you're using, and gives you a brief summary of how to -use PSCP: - -\c C:\>pscp -\c PuTTY Secure Copy client -\c Release 0.80 -\c Usage: pscp [options] [user@]host:source target -\c pscp [options] source [source...] [user@]host:target -\c pscp [options] -ls [user@]host:filespec -\c Options: -\c -V print version information and exit -\c -pgpfp print PGP key fingerprints and exit -\c -p preserve file attributes -\c -q quiet, don't show statistics -\c -r copy directories recursively -\c -v show verbose messages -\c -load sessname Load settings from saved session -\c -P port connect to specified port -\c -l user connect with specified username -\c -pwfile file login with password read from specified file -\c -1 -2 force use of particular SSH protocol version -\c -ssh -ssh-connection -\c force use of particular SSH protocol variant -\c -4 -6 force use of IPv4 or IPv6 -\c -C enable compression -\c -i key private key file for user authentication -\c -noagent disable use of Pageant -\c -agent enable use of Pageant -\c -no-trivial-auth -\c disconnect if SSH authentication succeeds trivially -\c -hostkey keyid -\c manually specify a host key (may be repeated) -\c -batch disable all interactive prompts -\c -no-sanitise-stderr don't strip control chars from standard error -\c -proxycmd command -\c use 'command' as local proxy -\c -unsafe allow server-side wildcards (DANGEROUS) -\c -sftp force use of SFTP protocol -\c -scp force use of SCP protocol -\c -sshlog file -\c -sshrawlog file -\c log protocol details to a file -\c -logoverwrite -\c -logappend -\c control what happens when a log file already exists - -(PSCP's interface is much like the Unix \c{scp} command, if you're -familiar with that.) - -\S{pscp-usage-basics} The basics - -To \I{receiving files}receive (a) file(s) from a remote server: - -\c pscp [options] [user@]host:source target - -So to copy the file \c{/etc/hosts} from the server \c{example.com} as -user \c{fred} to the file \c{c:\\temp\\example-hosts.txt}, you would type: - -\c pscp fred@example.com:/etc/hosts c:\temp\example-hosts.txt - -To \I{sending files}send (a) file(s) to a remote server: - -\c pscp [options] source [source...] [user@]host:target - -So to copy the local file \c{c:\\documents\\foo.txt} to the server -\c{example.com} as user \c{fred} to the file \c{/tmp/foo} you would -type: - -\c pscp c:\documents\foo.txt fred@example.com:/tmp/foo - -You can use \i{wildcards} to transfer multiple files in either -direction, like this: - -\c pscp c:\documents\*.doc fred@example.com:docfiles -\c pscp fred@example.com:source/*.c c:\source - -However, in the second case (using a wildcard for multiple remote -files) you may see a warning saying something like \q{warning: -remote host tried to write to a file called \cq{terminal.c} when we -requested a file called \cq{*.c}. If this is a wildcard, consider -upgrading to SSH-2 or using the \cq{-unsafe} option. Renaming of -this file has been disallowed}. - -This is due to a \I{security risk}fundamental insecurity in the old-style -\i{SCP protocol}: the client sends the wildcard string (\c{*.c}) to the -server, and the server sends back a sequence of file names that -match the wildcard pattern. However, there is nothing to stop the -server sending back a \e{different} pattern and writing over one of -your other files: if you request \c{*.c}, the server might send back -the file name \c{AUTOEXEC.BAT} and install a virus for you. Since -the wildcard matching rules are decided by the server, the client -cannot reliably verify that the filenames sent back match the -pattern. - -PSCP will attempt to use the newer \i{SFTP} protocol (part of SSH-2) -where possible, which does not suffer from this security flaw. If -you are talking to an SSH-2 server which supports SFTP, you will -never see this warning. (You can force use of the SFTP protocol, -if available, with \c{-sftp} - see \k{pscp-usage-options-backend}.) - -If you really need to use a server-side wildcard with an SSH-1 -server, you can use the \i\c{-unsafe} command line option with PSCP: - -\c pscp -unsafe fred@example.com:source/*.c c:\source - -This will suppress the warning message and the file transfer will -happen. However, you should be aware that by using this option you -are giving the server the ability to write to \e{any} file in the -target directory, so you should only use this option if you trust -the server administrator not to be malicious (and not to let the -server machine be cracked by malicious people). Alternatively, do -any such download in a newly created empty directory. (Even in -\q{unsafe} mode, PSCP will still protect you against the server -trying to get out of that directory using pathnames including -\cq{..}.) - -\S2{pscp-usage-basics-user} \c{user} - -The \i{login name} on the remote server. If this is omitted, and \c{host} -is a PuTTY saved session, PSCP will use any username specified by that -saved session. Otherwise, PSCP will attempt to use the local Windows -username. - -\S2{pscp-usage-basics-host} \I{hostname}\c{host} - -The name of the remote server, or the name of an existing PuTTY saved -session. In the latter case, the session's settings for hostname, port -number, cipher type and username will be used. - -\S2{pscp-usage-basics-source} \c{source} - -One or more source files. \ii{Wildcards} are allowed. The syntax of -wildcards depends on the system to which they apply, so if you are -copying \e{from} a Windows system \e{to} a UNIX system, you should use -Windows wildcard syntax (e.g. \c{*.*}), but if you are copying \e{from} -a UNIX system \e{to} a Windows system, you would use the wildcard -syntax allowed by your UNIX shell (e.g. \c{*}). - -If the source is a remote server and you do not specify a full -pathname (in UNIX, a pathname beginning with a \c{/} (slash) -character), what you specify as a source will be interpreted relative -to your \i{home directory} on the remote server. - -\S2{pscp-usage-basics-target} \c{target} - -The filename or directory to put the file(s). When copying from a -remote server to a local host, you may wish simply to place the -file(s) in the current directory. To do this, you should specify a -target of \c{.}. For example: - -\c pscp fred@example.com:/home/tom/.emacs . - -...would copy \c{/home/tom/.emacs} on the remote server to the current -directory. - -As with the \c{source} parameter, if the target is on a remote server -and is not a full path name, it is interpreted relative to your home -directory on the remote server. - -\S{pscp-usage-options} Options - -PSCP accepts all the general command line options supported by the -PuTTY tools, except the ones which make no sense in a file transfer -utility. See \k{using-general-opts} for a description of these -options. (The ones not supported by PSCP are clearly marked.) - -PSCP also supports some of its own options. The following sections -describe PSCP's specific command-line options. - -\S2{pscp-usage-options-ls}\I{-ls-PSCP}\c{-ls} \I{listing files}list remote files - -If the \c{-ls} option is given, no files are transferred; instead, -remote files are listed. Only a hostname specification and -optional remote file specification need be given. For example: - -\c pscp -ls fred@example.com:dir1 - -The SCP protocol does not contain within itself a means of listing -files. If SCP is in use, this option therefore assumes that the -server responds appropriately to the command \c{ls\_-la}; -this may not work with all servers. - -If SFTP is in use, this option should work with all servers. - -\S2{pscp-usage-options-p}\I{-p-PSCP}\c{-p} \i{preserve file attributes} - -By default, files copied with PSCP are \i{timestamp}ed with the date and -time they were copied. The \c{-p} option preserves the original -timestamp on copied files. - -\S2{pscp-usage-options-q}\I{-q-PSCP}\c{-q} quiet, don't show \i{statistics} - -By default, PSCP displays a meter displaying the progress of the -current transfer: - -\c mibs.tar | 168 kB | 84.0 kB/s | ETA: 00:00:13 | 13% - -The fields in this display are (from left to right), filename, size -(in kilobytes) of file transferred so far, estimate of how fast the -file is being transferred (in kilobytes per second), estimated time -that the transfer will be complete, and percentage of the file so far -transferred. The \c{-q} option to PSCP suppresses the printing of -these statistics. - -\S2{pscp-usage-options-r}\I{-r-PSCP}\c{-r} copies directories \i{recursive}ly - -By default, PSCP will only copy files. Any directories you specify to -copy will be skipped, as will their contents. The \c{-r} option tells -PSCP to descend into any directories you specify, and to copy them and -their contents. This allows you to use PSCP to transfer whole -directory structures between machines. - -\S2{pscp-usage-options-batch}\I{-batch-PSCP}\c{-batch} avoid interactive prompts - -If you use the \c{-batch} option, PSCP will never give an -interactive prompt while establishing the connection. If the -server's host key is invalid, for example (see \k{gs-hostkey}), then -the connection will simply be abandoned instead of asking you what -to do next. - -This may help PSCP's behaviour when it is used in automated -scripts: using \c{-batch}, if something goes wrong at connection -time, the batch job will fail rather than hang. - -\S2{pscp-usage-options-backend}\i\c{-sftp}, \i\c{-scp} force use of -particular file transfer protocol - -As mentioned in \k{pscp-usage-basics}, there are two different file -transfer protocols in use with SSH. Despite its name, PSCP (like many -other ostensible \cw{scp} clients) can use either of these protocols. - -The older \i{SCP protocol} does not have a written specification and -leaves a lot of detail to the server platform. \ii{Wildcards} are expanded -on the server. The simple design means that any wildcard specification -supported by the server platform (such as brace expansion) can be -used, but also leads to interoperability issues such as with filename -quoting (for instance, where filenames contain spaces), and also the -security issue described in \k{pscp-usage-basics}. - -The newer \i{SFTP} protocol, which is usually associated with SSH-2 -servers, is specified in a more platform independent way, and leaves -issues such as wildcard syntax up to the client. (PuTTY's SFTP -wildcard syntax is described in \k{psftp-wildcards}.) This makes it -more consistent across platforms, more suitable for scripting and -automation, and avoids security issues with wildcard matching. - -Normally PSCP will attempt to use the SFTP protocol, and only fall -back to the SCP protocol if SFTP is not available on the server. - -The \c{-scp} option forces PSCP to use the SCP protocol or quit. - -The \c{-sftp} option forces PSCP to use the SFTP protocol or quit. -When this option is specified, PSCP looks harder for an SFTP server, -which may allow use of SFTP with SSH-1 depending on server setup. - -\S2{pscp-option-sanitise} \I{-sanitise-stderr}\I{-no-sanitise-stderr}\c{-no-sanitise-stderr}: control error message sanitisation - -The \c{-no-sanitise-stderr} option will cause PSCP to pass through the -server's standard-error stream literally, without stripping control -characters from it first. This might be useful if the server were -sending coloured error messages, but it also gives the server the -ability to have unexpected effects on your terminal display. For more -discussion, see \k{plink-option-sanitise}. - -\S{pscp-retval} \ii{Return value} - -PSCP returns an \i\cw{ERRORLEVEL} of zero (success) only if the files -were correctly transferred. You can test for this in a \i{batch file}, -using code such as this: - -\c pscp file*.* user@hostname: -\c if errorlevel 1 echo There was an error - -\S{pscp-pubkey} Using \i{public key authentication} with PSCP - -Like PuTTY, PSCP can authenticate using a public key instead of a -password. There are three ways you can do this. - -Firstly, PSCP can use PuTTY saved sessions in place of hostnames -(see \k{pscp-usage-basics-host}). So you would do this: - -\b Run PuTTY, and create a PuTTY saved session (see -\k{config-saving}) which specifies your private key file (see -\k{config-ssh-privkey}). You will probably also want to specify a -username to log in as (see \k{config-username}). - -\b In PSCP, you can now use the name of the session instead of a -hostname: type \c{pscp sessionname:file localfile}, where -\c{sessionname} is replaced by the name of your saved session. - -Secondly, you can supply the name of a private key file on the command -line, with the \c{-i} option. See \k{using-cmdline-identity} for more -information. - -Thirdly, PSCP will attempt to authenticate using Pageant if Pageant -is running (see \k{pageant}). So you would do this: - -\b Ensure Pageant is running, and has your private key stored in it. - -\b Specify a user and host name to PSCP as normal. PSCP will -automatically detect Pageant and try to use the keys within it. - -For more general information on public-key authentication, see -\k{pubkey}. diff --git a/doc/psftp.but b/doc/psftp.but deleted file mode 100644 index cc4bb2597..000000000 --- a/doc/psftp.but +++ /dev/null @@ -1,600 +0,0 @@ -\C{psftp} Using \i{PSFTP} to transfer files securely - -\i{PSFTP}, the PuTTY SFTP client, is a tool for \i{transferring files} -securely between computers using an SSH connection. - -PSFTP differs from PSCP in the following ways: - -\b PSCP should work on virtually every SSH server. PSFTP uses the -new \i{SFTP} protocol, which is a feature of SSH-2 only. (PSCP will also -use this protocol if it can, but there is an SSH-1 equivalent it can -fall back to if it cannot.) - -\b PSFTP allows you to run an interactive file transfer session, -much like the Windows \i\c{ftp} program. You can list the contents of -directories, browse around the file system, issue multiple \c{get} -and \c{put} commands, and eventually log out. By contrast, PSCP is -designed to do a single file transfer operation and immediately -terminate. - -\H{psftp-starting} Starting PSFTP - -The usual way to start PSFTP is from a command prompt, much like -PSCP. To do this, it will need either to be on your \i{\c{PATH}} or -in your current directory. To add the directory containing PSFTP to -your \c{PATH} environment variable, type into the console window: - -\c set PATH=C:\path\to\putty\directory;%PATH% - -Unlike PSCP, however, PSFTP has no complex command-line syntax; you -just specify a host name and perhaps a user name: - -\c psftp server.example.com - -or perhaps - -\c psftp fred@server.example.com - -Alternatively, if you just type \c{psftp} on its own (or -double-click the PSFTP icon in the Windows GUI), you will see the -PSFTP prompt, and a message telling you PSFTP has not connected to -any server: - -\c C:\>psftp -\c psftp: no hostname specified; use "open host.name" to connect -\c psftp> - -At this point you can type \c{open server.example.com} or \c{open -fred@server.example.com} to start a session. - -PSFTP accepts all the general command line options supported by the -PuTTY tools, except the ones which make no sense in a file transfer -utility. See \k{using-general-opts} for a description of these -options. (The ones not supported by PSFTP are clearly marked.) - -PSFTP also supports some of its own options. The following sections -describe PSFTP's specific command-line options. - -\S{psftp-option-b} \I{-b-PSFTP}\c{-b}: specify a file containing batch commands - -In normal operation, PSFTP is an interactive program which displays -a command line and accepts commands from the keyboard. - -If you need to do automated tasks with PSFTP, you would probably -prefer to \I{batch scripts in PSFTP}specify a set of commands in -advance and have them executed automatically. The \c{-b} option -allows you to do this. You use it with a file name containing batch -commands. For example, you might create a file called \c{myscript.scr} -containing lines like this: - -\c cd /home/ftp/users/jeff -\c del jam-old.tar.gz -\c ren jam.tar.gz jam-old.tar.gz -\c put jam.tar.gz -\c chmod a+r jam.tar.gz - -and then you could run the script by typing - -\c psftp user@hostname -b myscript.scr - -When you run a batch script in this way, PSFTP will abort the script -if any command fails to complete successfully. To change this -behaviour, you can add the \c{-be} option (\k{psftp-option-be}). - -PSFTP will terminate after it finishes executing the batch script. - -\S{psftp-option-bc} \I{-bc-PSFTP}\c{-bc}: display batch commands as they are run - -The \c{-bc} option alters what PSFTP displays while processing a -batch script specified with \c{-b}. With the \c{-bc} option, PSFTP -will display prompts and commands just as if the commands had been -typed at the keyboard. So instead of seeing this: - -\c C:\>psftp fred@hostname -b batchfile -\c Sent username "fred" -\c Remote working directory is /home/fred -\c Listing directory /home/fred/lib -\c drwxrwsr-x 4 fred fred 1024 Sep 6 10:42 . -\c drwxr-sr-x 25 fred fred 2048 Dec 14 09:36 .. -\c drwxrwsr-x 3 fred fred 1024 Apr 17 2000 jed -\c lrwxrwxrwx 1 fred fred 24 Apr 17 2000 timber -\c drwxrwsr-x 2 fred fred 1024 Mar 13 2000 trn - -you might see this: - -\c C:\>psftp fred@hostname -bc -b batchfile -\c Sent username "fred" -\c Remote working directory is /home/fred -\c psftp> dir lib -\c Listing directory /home/fred/lib -\c drwxrwsr-x 4 fred fred 1024 Sep 6 10:42 . -\c drwxr-sr-x 25 fred fred 2048 Dec 14 09:36 .. -\c drwxrwsr-x 3 fred fred 1024 Apr 17 2000 jed -\c lrwxrwxrwx 1 fred fred 24 Apr 17 2000 timber -\c drwxrwsr-x 2 fred fred 1024 Mar 13 2000 trn -\c psftp> quit - -\S{psftp-option-be} \I{-be-PSFTP}\c{-be}: continue batch processing on errors - -When running a batch file, this additional option causes PSFTP to -continue processing even if a command fails to complete successfully. - -You might want this to happen if you wanted to delete a file and -didn't care if it was already not present, for example. - -\S{psftp-usage-options-batch} \I{-batch-PSFTP}\c{-batch}: avoid -interactive prompts - -If you use the \c{-batch} option, PSFTP will never give an -interactive prompt while establishing the connection. If the -server's host key is invalid, for example (see \k{gs-hostkey}), then -the connection will simply be abandoned instead of asking you what -to do next. - -This may help PSFTP's behaviour when it is used in automated -scripts: using \c{-batch}, if something goes wrong at connection -time, the batch job will fail rather than hang. - -\S2{psftp-option-sanitise} \I{-sanitise-stderr}\I{-no-sanitise-stderr}\c{-no-sanitise-stderr}: control error message sanitisation - -The \c{-no-sanitise-stderr} option will cause PSFTP to pass through the -server's standard-error stream literally, without stripping control -characters from it first. This might be useful if the server were -sending coloured error messages, but it also gives the server the -ability to have unexpected effects on your terminal display. For more -discussion, see \k{plink-option-sanitise}. - -\H{psftp-commands} Running PSFTP - -Once you have started your PSFTP session, you will see a \c{psftp>} -prompt. You can now type commands to perform file-transfer -functions. This section lists all the available commands. - -Any line starting with a \cw{#} will be treated as a \i{comment} -and ignored. - -\S{psftp-quoting} \I{quoting, in PSFTP}General quoting rules for PSFTP commands - -Most PSFTP commands are considered by the PSFTP command interpreter -as a sequence of words, separated by spaces. For example, the -command \c{ren oldfilename newfilename} splits up into three words: -\c{ren} (the command name), \c{oldfilename} (the name of the file to -be renamed), and \c{newfilename} (the new name to give the file). - -Sometimes you will need to specify \I{spaces in filenames}file names -that \e{contain} spaces. In order to do this, you can surround -the file name with double quotes. This works equally well for -local file names and remote file names: - -\c psftp> get "spacey file name.txt" "save it under this name.txt" - -The double quotes themselves will not appear as part of the file -names; they are removed by PSFTP and their only effect is to stop -the spaces inside them from acting as word separators. - -If you need to \e{use} a double quote (on some types of remote -system, such as Unix, you are allowed to use double quotes in file -names), you can do this by doubling it. This works both inside and -outside double quotes. For example, this command - -\c psftp> ren ""this"" "a file with ""quotes"" in it" - -will take a file whose current name is \c{"this"} (with a double -quote character at the beginning and the end) and rename it to a -file whose name is \c{a file with "quotes" in it}. - -(The one exception to the PSFTP quoting rules is the \c{!} command, -which passes its command line straight to Windows without splitting -it up into words at all. See \k{psftp-cmd-pling}.) - -\S{psftp-wildcards} Wildcards in PSFTP - -Several commands in PSFTP support \q{\i{wildcards}} to select multiple -files. - -For \e{local} file specifications (such as the first argument to -\c{put}), wildcard rules for the local operating system are used. For -instance, PSFTP running on Windows might require the use of \c{*.*} -where PSFTP on Unix would need \c{*}. - -For \e{remote} file specifications (such as the first argument to -\c{get}), PSFTP uses a standard wildcard syntax (similar to \i{POSIX} -wildcards): - -\b \c{*} matches any sequence of characters (including a zero-length -sequence). - -\b \c{?} matches exactly one character. - -\b \c{[abc]} matches exactly one character which can be \cw{a}, -\cw{b}, or \cw{c}. - -\lcont{ - -\c{[a-z]} matches any character in the range \cw{a} to \cw{z}. - -\c{[^abc]} matches a single character that is \e{not} \cw{a}, \cw{b}, -or \cw{c}. - -Special cases: \c{[-a]} matches a literal hyphen (\cw{-}) or \cw{a}; -\c{[^-a]} matches all other characters. \c{[a^]} matches a literal -caret (\cw{^}) or \cw{a}. - -} - -\b \c{\\} (backslash) before any of the above characters (or itself) -removes that character's special meaning. - -A leading period (\cw{.}) on a filename is not treated specially, -unlike in some Unix contexts; \c{get *} will fetch all files, whether -or not they start with a leading period. - -\S{psftp-cmd-open} The \c{open} command: start a session - -If you started PSFTP by double-clicking in the GUI, or just by -typing \c{psftp} at the command line, you will need to open a -connection to an SFTP server before you can issue any other -commands (except \c{help} and \c{quit}). - -To create a connection, type \c{open host.name}, or if you need to -specify a user name as well you can type \c{open user@host.name}. -You can optionally specify a port as well: -\c{open user@host.name 22}. - -Once you have issued this command, you will not be able to issue it -again, \e{even} if the command fails (for example, if you mistype -the host name or the connection times out). So if the connection is -not opened successfully, PSFTP will terminate immediately. - -\S{psftp-cmd-quit} The \c{quit} command: end your session - -When you have finished your session, type the command \c{quit} to -close the connection, terminate PSFTP and return to the command line -(or just close the PSFTP console window if you started it from the -GUI). - -You can also use the \c{bye} and \c{exit} commands, which have -exactly the same effect. - -\S{psftp-cmd-close} The \c{close} command: close your connection - -If you just want to close the network connection but keep PSFTP -running, you can use the \c{close} command. You can then use the -\c{open} command to open a new connection. - -\S{psftp-cmd-help} The \c{help} command: get quick online help - -If you type \c{help}, PSFTP will give a short list of the available -commands. - -If you type \c{help} with a command name - for example, \c{help get} -- then PSFTP will give a short piece of help on that particular -command. - -\S{psftp-cmd-cd} The \c{cd} and \c{pwd} commands: changing the -remote \i{working directory} - -PSFTP maintains a notion of your \q{working directory} on the -server. This is the default directory that other commands will -operate on. For example, if you type \c{get filename.dat} then PSFTP -will look for \c{filename.dat} in your remote working directory on -the server. - -To change your remote working directory, use the \c{cd} command. If -you don't provide an argument, \c{cd} will return you to your home -directory on the server (more precisely, the remote directory you were -in at the start of the connection). - -To display your current remote working directory, type \c{pwd}. - -\S{psftp-cmd-lcd} The \c{lcd} and \c{lpwd} commands: changing the -local \i{working directory} - -As well as having a working directory on the remote server, PSFTP -also has a working directory on your local machine (just like any -other Windows process). This is the default local directory that -other commands will operate on. For example, if you type \c{get -filename.dat} then PSFTP will save the resulting file as -\c{filename.dat} in your local working directory. - -To change your local working directory, use the \c{lcd} command. To -display your current local working directory, type \c{lpwd}. - -\S{psftp-cmd-get} The \c{get} command: fetch a file from the server - -To \i{download a file} from the server and store it on your local PC, -you use the \c{get} command. - -In its simplest form, you just use this with a file name: - -\c get myfile.dat - -If you want to store the file locally under a different name, -specify the local file name after the remote one: - -\c get myfile.dat newname.dat - -This will fetch the file on the server called \c{myfile.dat}, but -will save it to your local machine under the name \c{newname.dat}. - -To fetch an entire directory \i{recursive}ly, you can use the \c{-r} -option: - -\c get -r mydir -\c get -r mydir newname - -(If you want to fetch a file whose name starts with a hyphen, you -may have to use the \c{--} special argument, which stops \c{get} -from interpreting anything as a switch after it. For example, -\cq{get -- -silly-name-}.) - -\S{psftp-cmd-put} The \c{put} command: send a file to the server - -To \i{upload a file} to the server from your local PC, you use the -\c{put} command. - -In its simplest form, you just use this with a file name: - -\c put myfile.dat - -If you want to store the file remotely under a different name, -specify the remote file name after the local one: - -\c put myfile.dat newname.dat - -This will send the local file called \c{myfile.dat}, but will store -it on the server under the name \c{newname.dat}. - -To send an entire directory \i{recursive}ly, you can use the \c{-r} -option: - -\c put -r mydir -\c put -r mydir newname - -(If you want to send a file whose name starts with a hyphen, you may -have to use the \c{--} special argument, which stops \c{put} from -interpreting anything as a switch after it. For example, \cq{put -- --silly-name-}.) - -\S{psftp-cmd-mgetput} The \c{mget} and \c{mput} commands: fetch or -send multiple files - -\c{mget} works almost exactly like \c{get}, except that it allows -you to specify more than one file to fetch at once. You can do this -in two ways: - -\b by giving two or more explicit file names (\cq{mget file1.txt -file2.txt}) - -\b by using a wildcard (\cq{mget *.txt}). - -Every argument to \c{mget} is treated as the name of a file to fetch -(unlike \c{get}, which will interpret at most one argument like -that, and a second argument will be treated as an alternative name -under which to store the retrieved file), or a \i{wildcard} expression -matching more than one file. - -The \c{-r} and \c{--} options from \c{get} are also available with -\c{mget}. - -\c{mput} is similar to \c{put}, with the same differences. - -\S{psftp-cmd-regetput} The \c{reget} and \c{reput} commands: -\i{resuming file transfers} - -If a file transfer fails half way through, and you end up with half -the file stored on your disk, you can resume the file transfer using -the \c{reget} and \c{reput} commands. These work exactly like the -\c{get} and \c{put} commands, but they check for the presence of the -half-written destination file and start transferring from where the -last attempt left off. - -The syntax of \c{reget} and \c{reput} is exactly the same as the -syntax of \c{get} and \c{put}: - -\c reget myfile.dat -\c reget myfile.dat newname.dat -\c reget -r mydir - -These commands are intended mainly for resuming interrupted transfers. -They assume that the remote file or directory structure has not -changed in any way; if there have been changes, you may end up with -corrupted files. In particular, the \c{-r} option will not pick up -changes to files or directories already transferred in full. - -\S{psftp-cmd-dir} The \c{dir} command: \I{listing files}list remote files - -To list the files in your remote working directory, just type -\c{dir}. - -You can also list the contents of a different directory by typing -\c{dir} followed by the directory name: - -\c dir /home/fred -\c dir sources - -And you can list a subset of the contents of a directory by -providing a wildcard: - -\c dir /home/fred/*.txt -\c dir sources/*.c - -The \c{ls} command works exactly the same way as \c{dir}. - -\S{psftp-cmd-chmod} The \c{chmod} command: change permissions on -remote files - -\I{changing permissions on files}PSFTP -allows you to modify the file permissions on files and -directories on the server. You do this using the \c{chmod} command, -which works very much like the Unix \c{chmod} command. - -The basic syntax is \c{chmod modes file}, where \c{modes} represents -a modification to the file permissions, and \c{file} is the filename -to modify. You can specify multiple files or wildcards. For example: - -\c chmod go-rwx,u+w privatefile -\c chmod a+r public* -\c chmod 640 groupfile1 groupfile2 - -The \c{modes} parameter can be a set of octal digits in the Unix -style. (If you don't know what this means, you probably don't want -to be using it!) Alternatively, it can be a list of permission -modifications, separated by commas. Each modification consists of: - -\b The people affected by the modification. This can be \c{u} (the -owning user), \c{g} (members of the owning group), or \c{o} -(everybody else - \q{others}), or some combination of those. It can -also be \c{a} (\q{all}) to affect everybody at once. - -\b A \c{+} or \c{-} sign, indicating whether permissions are to be -added or removed. - -\b The actual permissions being added or removed. These can be -\I{read permission}\c{r} (permission to read the file), -\I{write permission}\c{w} (permission to write to the file), and -\I{execute permission}\c{x} (permission to execute the file, or in -the case of a directory, permission to access files within the -directory). - -So the above examples would do: - -\b The first example: \c{go-rwx} removes read, write and execute -permissions for members of the owning group and everybody else (so -the only permissions left are the ones for the file owner). \c{u+w} -adds write permission for the file owner. - -\b The second example: \c{a+r} adds read permission for everybody to -all files and directories starting with \q{public}. - -In addition to all this, there are a few extra special cases for -\i{Unix} systems. On non-Unix systems these are unlikely to be useful: - -\b You can specify \c{u+s} and \c{u-s} to add or remove the Unix -\i{set-user-ID bit}. This is typically only useful for special purposes; -refer to your Unix documentation if you're not sure about it. - -\b You can specify \c{g+s} and \c{g-s} to add or remove the Unix -\i{set-group-ID bit}. On a file, this works similarly to the set-user-ID -bit (see your Unix documentation again); on a directory it ensures -that files created in the directory are accessible by members of the -group that owns the directory. - -\b You can specify \c{+t} and \c{-t} to add or remove the Unix -\q{\i{sticky bit}}. When applied to a directory, this means that the -owner of a file in that directory can delete the file (whereas -normally only the owner of the \e{directory} would be allowed to). - -\S{psftp-cmd-del} The \c{del} command: delete remote files - -To \I{deleting files}delete a file on the server, type \c{del} and -then the filename or filenames: - -\c del oldfile.dat -\c del file1.txt file2.txt -\c del *.o - -Files will be deleted without further prompting, even if multiple files -are specified. - -\c{del} will only delete files. You cannot use it to delete -directories; use \c{rmdir} for that. - -The \c{rm} command works exactly the same way as \c{del}. - -\S{psftp-cmd-mkdir} The \c{mkdir} command: create remote directories - -To \i{create a directory} on the server, type \c{mkdir} and then the -directory name: - -\c mkdir newstuff - -You can specify multiple directories to create at once: - -\c mkdir dir1 dir2 dir3 - -\S{psftp-cmd-rmdir} The \c{rmdir} command: remove remote directories - -To \i{remove a directory} on the server, type \c{rmdir} and then the -directory name or names: - -\c rmdir oldstuff -\c rmdir *.old ancient - -Directories will be deleted without further prompting, even if -multiple directories are specified. - -Most SFTP servers will probably refuse to remove a directory if the -directory has anything in it, so you will need to delete the -contents first. - -\S{psftp-cmd-mv} The \c{mv} command: move and \i{rename remote files} - -To rename a single file on the server, type \c{mv}, then the current -file name, and then the new file name: - -\c mv oldfile newname - -You can also move the file into a different directory and change the -name: - -\c mv oldfile dir/newname - -To move one or more files into an existing subdirectory, specify the -files (using wildcards if desired), and then the destination -directory: - -\c mv file dir -\c mv file1 dir1/file2 dir2 -\c mv *.c *.h .. - -The \c{rename} and \c{ren} commands work exactly the same way as -\c{mv}. - -\S{psftp-cmd-pling} The \c{!} command: run a \i{local Windows command} - -You can run local Windows commands using the \c{!} command. This is -the only PSFTP command that is not subject to the command quoting -rules given in \k{psftp-quoting}. If any command line begins with -the \c{!} character, then the rest of the line will be passed -straight to Windows without further translation. - -For example, if you want to move an existing copy of a file out of -the way before downloading an updated version, you might type: - -\c psftp> !ren myfile.dat myfile.bak -\c psftp> get myfile.dat - -using the Windows \c{ren} command to rename files on your local PC. - -\H{psftp-pubkey} Using \i{public key authentication} with PSFTP - -Like PuTTY, PSFTP can authenticate using a public key instead of a -password. There are three ways you can do this. - -Firstly, PSFTP can use PuTTY saved sessions in place of hostnames. -So you might do this: - -\b Run PuTTY, and create a PuTTY saved session (see -\k{config-saving}) which specifies your private key file (see -\k{config-ssh-privkey}). You will probably also want to specify a -username to log in as (see \k{config-username}). - -\b In PSFTP, you can now use the name of the session instead of a -hostname: type \c{psftp sessionname}, where \c{sessionname} is -replaced by the name of your saved session. - -Secondly, you can supply the name of a private key file on the command -line, with the \c{-i} option. See \k{using-cmdline-identity} for more -information. - -Thirdly, PSFTP will attempt to authenticate using Pageant if Pageant -is running (see \k{pageant}). So you would do this: - -\b Ensure Pageant is running, and has your private key stored in it. - -\b Specify a user and host name to PSFTP as normal. PSFTP will -automatically detect Pageant and try to use the keys within it. - -For more general information on public-key authentication, see -\k{pubkey}. diff --git a/doc/pubkey.but b/doc/pubkey.but deleted file mode 100644 index c421cedb2..000000000 --- a/doc/pubkey.but +++ /dev/null @@ -1,662 +0,0 @@ -\C{pubkey} Using public keys for SSH authentication - -\H{pubkey-intro} \ii{Public key authentication} - an introduction - -Public key authentication is an alternative means of identifying -yourself to a login server, instead of typing a password. It is more -secure and more flexible, but more difficult to set up. - -In conventional password authentication, you prove you are who you -claim to be by proving that you know the correct password. The only -way to prove you know the password is to tell the server what you -think the password is. This means that if the server has been -hacked, or \i\e{spoofed} (see \k{gs-hostkey}), an attacker can learn -your password. - -Public key authentication solves this problem. You generate a \i\e{key -pair}, consisting of a \i{public key} (which everybody is allowed to -know) and a \i{private key} (which you keep secret and do not give to -anybody). The private key is able to generate \i\e{signatures}. -A signature created using your private key cannot be forged by -anybody who does not have that key; but anybody who has your public -key can verify that a particular signature is genuine. - -So you generate a key pair on your own computer, and you copy the -public key to the server. Then, when the server asks you to prove -who you are, PuTTY can generate a signature using your private key. -The server can verify that signature (since it has your public key) -and allow you to log in. Now if the server is hacked or spoofed, the -attacker does not gain your private key or password; they only gain -one signature. And signatures cannot be re-used, so they have gained -nothing. - -There is a problem with this: if your private key is stored -unprotected on your own computer, then anybody who gains access to -\e{that} will be able to generate signatures as if they were you. So -they will be able to log in to your server under your account. For -this reason, your private key is usually \i\e{encrypted} when it is -stored on your local machine, using a \i{passphrase} of your choice. In -order to generate a signature, PuTTY must decrypt the key, so you -have to type your passphrase. - -This can make public-key authentication less convenient than -password authentication: every time you log in to the server, -instead of typing a short password, you have to type a longer -passphrase. One solution to this is to use an \i\e{authentication -agent}, a separate program which holds decrypted private keys and -generates signatures on request. PuTTY's authentication agent is -called \i{Pageant}. When you begin a Windows session, you start Pageant -and load your private key into it (typing your passphrase once). For -the rest of your session, you can start PuTTY any number of times -and Pageant will automatically generate signatures without you -having to do anything. When you close your Windows session, Pageant -shuts down, without ever having stored your decrypted private key on -disk. Many people feel this is a good compromise between security -and convenience. See \k{pageant} for further details. - -There is more than one \i{public-key algorithm} available. The most -common are \i{RSA} and \i{ECDSA}, but others exist, notably \i{DSA} -(otherwise known as \i{DSS}), the USA's federal Digital Signature Standard. -The key types supported by PuTTY are described in \k{puttygen-keytype}. - -\H{pubkey-puttygen} Using \i{PuTTYgen}, the PuTTY key generator - -PuTTYgen is a key generator. It \I{generating keys}generates pairs of -public and private keys to be used with PuTTY, PSCP, PSFTP, and Plink, -as well as the PuTTY authentication agent, Pageant (see \k{pageant}). -PuTTYgen generates RSA, DSA, ECDSA, and EdDSA keys. - -When you run PuTTYgen you will see a window where you have two main -choices: \q{Generate}, to generate a new public/private key pair, or -\q{Load} to load in an existing private key. - -\S{puttygen-generating} Generating a new key - -This is a general outline of the procedure for generating a new key -pair. The following sections describe the process in more detail. - -\b First, you need to select which type of key you want to generate, -and also select the strength of the key. This is described in more -detail in \k{puttygen-keytype} and -\k{puttygen-strength}. - -\b Then press the \q{Generate} button, to actually generate the key. -\K{puttygen-generate} describes this step. - -\b Once you have generated the key, select a comment field -(\k{puttygen-comment}) and a passphrase (\k{puttygen-passphrase}). - -\b Now you're ready to save the private key to disk; press the -\q{Save private key} button. (See \k{puttygen-savepriv}). - -Your key pair is now ready for use. You may also want to copy the -public key to your server, either by copying it out of the \q{Public -key for pasting into OpenSSH authorized_keys file} box (see -\k{puttygen-pastekey}), or by using the \q{Save public key} button -(\k{puttygen-savepub}). However, you don't need to do this -immediately; if you want, you can load the private key back into -PuTTYgen later (see \k{puttygen-load}) and the public key will be -available for copying and pasting again. - -\K{pubkey-gettingready} describes the typical process of configuring -PuTTY to attempt public-key authentication, and configuring your SSH -server to accept it. - -\S{puttygen-keytype} Selecting the type of key - -Before generating a key pair using PuTTYgen, you need to select -which type of key you need. - -The current version of the SSH protocol, SSH-2, supports several -different key types, although specific servers may not support all of -them. PuTTYgen can generate: - -\b An \i{RSA} key for use with the SSH-2 protocol. - -\b A \i{DSA} key for use with the SSH-2 protocol. - -\b An \i{ECDSA} (\i{elliptic curve} DSA) key for use with the -SSH-2 protocol. - -\b An \i{EdDSA} key (Edwards-curve DSA, another elliptic curve -algorithm) for use with the SSH-2 protocol. - -PuTTYgen can also generate an RSA key suitable for use with the old -SSH-1 protocol (which only supports RSA); for this, you need to select -the \q{SSH-1 (RSA)} option. Since the SSH-1 protocol is no longer -considered secure, it's rare to need this option. - -\S{puttygen-strength} Selecting the size (strength) of the key - -The \q{Number of bits} input box allows you to choose the strength -of the key PuTTYgen will generate. - -\b For RSA and DSA, 2048 bits should currently be sufficient for most -purposes. (Smaller keys of these types are no longer considered -secure, and PuTTYgen will warn if you try to generate them.) - -\b For ECDSA, only 256, 384, and 521 bits are supported, corresponding -to \i{NIST}-standardised elliptic curves. (Elliptic-curve keys do not -need as many bits as RSA keys for equivalent security, so these numbers -are smaller than the RSA recommendations.) - -\b For EdDSA, the only valid sizes are 255 bits (these keys are also -known as \q{\i{Ed25519}} and are commonly used) and 448 bits -(\q{\i{Ed448}}, which is much less common at the time of writing). -(256 is also accepted for backward compatibility, but the effect is -the same as 255.) - -\S{puttygen-primes} Selecting the \i{prime generation method} - -(This is entirely optional. Unless you know better, it's entirely -sensible to skip this and use the default settings.) - -On the \q{Key} menu, you can also optionally change the method for -generating the prime numbers used in the generated key. This is used -for RSA and DSA keys only. (The other key types don't require -generating prime numbers at all.) - -The prime-generation method does not affect compatibility: a key -generated with any of these methods will still work with all the same -SSH servers. - -The available methods are: - -\b Use \i{probable primes} (fast) - -\b Use \i{proven primes} (slower) - -\b Use proven primes with even distribution (slowest) - -The \q{probable primes} method sounds unsafe, but it's the most -commonly used prime-generation strategy. There is in theory a -possibility that it might accidentally generate a number that isn't -prime, but the software does enough checking to make that probability -vanishingly small (less than 1 in 2^80, or 1 in 10^24). So, in -practice, nobody worries about it very much. - -The other methods cause PuTTYgen to use numbers that it is \e{sure} -are prime, because it generates the output number together with a -proof of its primality. This takes more effort, but it eliminates that -theoretical risk in the probabilistic method. - -There in one way in which PuTTYgen's \q{proven primes} method is not -strictly better than its \q{probable primes} method. If you use -PuTTYgen to generate an RSA key on a computer that is potentially -susceptible to timing- or cache-based \i{side-channel attacks}, such -as a shared computer, the \q{probable primes} method is designed to -resist such attacks, whereas the \q{proven primes} methods are not. -(This is only a concern for RSA keys; for other key types, primes -are either not secret or not involved.) - -You might choose to switch from probable to proven primes if you have -a local security standard that demands it, or if you don't trust the -probabilistic argument for the safety of the usual method. - -For RSA keys, there's also an option on the \q{Key} menu to use -\i{\q{strong} primes} as the prime factors of the public key. A \q{strong} -prime is a prime number chosen to have a particular structure that -makes certain factoring algorithms more difficult to apply, so some -security standards recommend their use. However, the most modern -factoring algorithms are unaffected, so this option is probably not -worth turning on \e{unless} you have a local standard that recommends -it. - -\S{puttygen-generate} The \q{Generate} button - -Once you have chosen the type of key you want, and the strength of -the key, press the \q{Generate} button and PuTTYgen will begin the -process of actually generating the key. - -First, a progress bar will appear and PuTTYgen will ask you to move -the mouse around to generate randomness. Wave the mouse in circles -over the blank area in the PuTTYgen window, and the progress bar -will gradually fill up as PuTTYgen collects enough randomness. You -don't need to wave the mouse in particularly imaginative patterns -(although it can't hurt); PuTTYgen will collect enough randomness -just from the fine detail of \e{exactly} how far the mouse has moved -each time Windows samples its position. - -When the progress bar reaches the end, PuTTYgen will begin creating -the key. The progress bar will reset to the start, and gradually -move up again to track the progress of the key generation. It will -not move evenly, and may occasionally slow down to a stop; this is -unfortunately unavoidable, because key generation is a random -process and it is impossible to reliably predict how long it will -take. - -When the key generation is complete, a new set of controls will -appear in the window to indicate this. - -\S{puttygen-fingerprint} The \q{\ii{Key fingerprint}} box - -The \q{Key fingerprint} box shows you a fingerprint value for the -generated key. This is derived cryptographically from the \e{public} -key value, so it doesn't need to be kept secret; it is supposed to -be more manageable for human beings than the public key itself. - -The fingerprint value is intended to be cryptographically secure, in -the sense that it is computationally infeasible for someone to -invent a second key with the same fingerprint, or to find a key with -a particular fingerprint. So some utilities, such as the Pageant key -list box (see \k{pageant-mainwin-keylist}) and the Unix \c{ssh-add} -utility, will list key fingerprints rather than the whole public key. - -By default, PuTTYgen will display SSH-2 key fingerprints in the -\q{SHA256} format. If you need to see the fingerprint in the older -\q{MD5} format (which looks like \c{aa:bb:cc:...}), you can choose -\q{Show fingerprint as MD5} from the \q{Key} menu, but bear in mind -that this is less cryptographically secure; it may be feasible for -an attacker to create a key with the same fingerprint as yours. - -\S{puttygen-comment} Setting a comment for your key - -If you have more than one key and use them for different purposes, -you don't need to memorise the key fingerprints in order to tell -them apart. PuTTYgen allows you to enter a \e{comment} for your key, -which will be displayed whenever PuTTY or Pageant asks you for the -passphrase. - -The default comment format, if you don't specify one, contains the -key type and the date of generation, such as \c{rsa-key-20011212}. -Another commonly used approach is to use your name and the name of -the computer the key will be used on, such as \c{simon@simons-pc}. - -To alter the key comment, just type your comment text into the -\q{Key comment} box before saving the private key. If you want to -change the comment later, you can load the private key back into -PuTTYgen, change the comment, and save it again. - -\S{puttygen-passphrase} Setting a \i{passphrase} for your key - -The \q{Key passphrase} and \q{Confirm passphrase} boxes allow you to -choose a passphrase for your key. The passphrase will be used to -\i{encrypt} the key on disk, so you will not be able to use the key -without first entering the passphrase. - -When you save the key, PuTTYgen will check that the \q{Key passphrase} -and \q{Confirm passphrase} boxes both contain exactly the same -passphrase, and will refuse to save the key otherwise. - -If you leave the passphrase fields blank, the key will be saved -unencrypted. You should \e{not} do this without good reason; if you -do, your private key file on disk will be all an attacker needs to -gain access to any machine configured to accept that key. If you -want to be able to \I{passwordless login}log in without having to -type a passphrase every time, you should consider using Pageant -(\k{pageant}) so that your decrypted key is only held in memory -rather than on disk. - -Under special circumstances you may genuinely \e{need} to use a key -with no passphrase; for example, if you need to run an automated -batch script that needs to make an SSH connection, you can't be -there to type the passphrase. In this case we recommend you generate -a special key for each specific batch script (or whatever) that -needs one, and on the server side you should arrange that each key -is \e{restricted} so that it can only be used for that specific -purpose. The documentation for your SSH server should explain how to -do this (it will probably vary between servers). - -Choosing a good passphrase is difficult. Just as you shouldn't use a -dictionary word as a password because it's easy for an attacker to -run through a whole dictionary, you should not use a song lyric, -quotation or other well-known sentence as a passphrase. \i{DiceWare} -(\W{http://www.diceware.com/}\cw{www.diceware.com}) recommends using -at least five words each generated randomly by rolling five dice, -which gives over 2^64 possible passphrases and is probably not a bad -scheme. If you want your passphrase to make grammatical sense, this -cuts down the possibilities a lot and you should use a longer one as -a result. - -\e{Do not forget your passphrase}. There is no way to recover it. - -\S{puttygen-cert} Adding a \i{certificate} to your key - -In some environments, user authentication keys can be signed in turn -by a \q{certifying authority} (\q{CA} for short), and user accounts on -an SSH server can be configured to automatically trust any key that's -certified by the right signature. - -This can be a convenient setup if you have a very large number of -servers. When you change your key pair, you might otherwise have to -edit the \cw{authorized_keys} file on every server individually, to -make them all accept the new key. But if instead you configure all -those servers \e{once} to accept keys signed as yours by a CA, then -when you change your public key, all you have to do is to get the new -key certified by the same CA as before, and then all your servers will -automatically accept it without needing individual reconfiguration. - -To get your key signed by a CA, you'll probably send the CA the new -\e{public} key (not the private half), and get back a modified version -of the public key with the certificate included. - -If you want to incorporate the certificate into your PPK file for -convenience, you can use the \q{Add certificate to key} menu option in -PuTTYgen's \q{Key} menu. This will give you a single file containing -your private key and the certificate, which is everything you need to -authenticate to a server prepared to accept that certificate. - -To remove the certificate again and restore the uncertified PPK file, -there's also a \q{Remove certificate from key} option. - -(However, you don't \e{have} to incorporate the certificate into your -PPK file. You can equally well use it separately, via the -\q{Certificate to use with the private key} option in PuTTY itself. -See \k{config-ssh-cert}. It's up to you which you find more -convenient.) - -When the currently loaded key in PuTTYgen contains a certificate, the -large \q{Public key for pasting} edit box (see \k{puttygen-pastekey}) -is replaced by a button that brings up an information box telling you -about the certificate, such as who it certifies your key as belonging -to, when it expires (if ever), and the fingerprint of the CA key that -signed it in turn. - -\S{puttygen-savepriv} Saving your private key to a disk file - -Once you have generated a key, set a comment field and set a -passphrase, you are ready to save your private key to disk. - -Press the \q{Save private key} button. PuTTYgen will put up a dialog -box asking you where to save the file. Select a directory, type in a -file name, and press \q{Save}. - -This file is in PuTTY's native format (\c{*.\i{PPK}}); it is the one you -will need to tell PuTTY to use for authentication (see -\k{config-ssh-privkey}) or tell Pageant to load (see -\k{pageant-mainwin-addkey}). - -(You can optionally change some details of the PPK format for your saved -key files; see \k{puttygen-save-params}. But the defaults should be -fine for most purposes.) - -\S{puttygen-savepub} Saving your public key to a disk file - -RFC 4716 specifies a \I{SSH-2 public key format}standard format for -storing SSH-2 public keys on disk. Some SSH servers (such as -\i\cw{ssh.com}'s) require a public key in this format in order to accept -authentication with the corresponding private key. (Others, such as -OpenSSH, use a different format; see \k{puttygen-pastekey}.) - -To save your public key in the SSH-2 standard format, press the -\q{Save public key} button in PuTTYgen. PuTTYgen will put up a -dialog box asking you where to save the file. Select a directory, -type in a file name, and press \q{Save}. - -You will then probably want to copy the public key file to your SSH -server machine. See \k{pubkey-gettingready} for general instructions -on configuring public-key authentication once you have generated a -key. - -If you use this option with an SSH-1 key, the file PuTTYgen saves -will contain exactly the same text that appears in the \q{Public key -for pasting} box. This is the only existing standard for SSH-1 -public keys. - -\S{puttygen-pastekey} \q{Public key for pasting into OpenSSH -\i{authorized_keys file}} - -The \i{OpenSSH} server, among others, requires your public key to be -given to it in a one-line format before it will accept authentication -with your private key. (SSH-1 servers also used this method.) - -The \q{Public key for pasting into OpenSSH authorized_keys file} gives the -public-key data in the correct one-line format. Typically you will -want to select the entire contents of the box using the mouse, press -Ctrl+C to copy it to the clipboard, and then paste the data into a -PuTTY session which is already connected to the server. - -See \k{pubkey-gettingready} for general instructions on configuring -public-key authentication once you have generated a key. - -\S{puttygen-save-params} Parameters for saving key files - -Selecting \q{Parameters for saving key files...} from the \q{Key} menu -lets you adjust some aspects of PPK-format private key files stored on -disk. None of these options affect compatibility with SSH servers. - -In most cases, it's entirely sensible to leave all of these at their -default settings. - -\S2{puttygen-save-ppk-version} PPK file version - -This defaults to version 3, which is fine for most uses. - -You might need to select PPK version 2 if you need your private key -file to be loadable in older versions of PuTTY (0.74 and older), or in -other tools which do not yet support the version 3 format (which was -introduced in 2021). - -The version 2 format is less resistant to brute-force decryption, and -doesn't support any of the following options to control that. - -\S2{puttygen-save-passphrase-hashing} Options affecting \i{passphrase hashing} - -All of the following options only affect keys saved with passphrases. -They control how much work is required to decrypt the key (which -happens every time you type its passphrase). This allows you to trade -off the cost of legitimate use of the key against the resistance of -the encrypted key to password-guessing attacks. - -These options only affect PPK version 3. - -\dt Key derivation function - -\dd The variant of the \i{Argon2} key derivation function to use. -You might change this if you consider your exposure to \i{side-channel -attacks} to be different to the norm. - -\dt Memory to use for passphrase hash - -\dd The amount of memory needed to decrypt the key, in Kbyte. - -\dt Time to use for passphrase hash - -\dd Controls how much time is required to attempt decrypting the key. -You can either specify an approximate time in milliseconds (on this -machine), or explicitly specify a number of hash passes (which is what -the time is turned into during encryption). - -\dt Parallelism for passphrase hash - -\dd Number of parallelisable threads that can be used to decrypt the -key. The default, 1, forces the process to run single-threaded, even -on machines with multiple cores. - -\S{puttygen-load} Reloading a private key - -PuTTYgen allows you to load an existing private key file into -memory. If you do this, you can then change the passphrase and -comment before saving it again; you can also make extra copies of -the public key. - -To load an existing key, press the \q{Load} button. PuTTYgen will -put up a dialog box where you can browse around the file system and -find your key file. Once you select the file, PuTTYgen will ask you -for a passphrase (if necessary) and will then display the key -details in the same way as if it had just generated the key. - -If you use the Load command to load a foreign key format, it will -work, but you will see a message box warning you that the key you -have loaded is not a PuTTY native key. See \k{puttygen-conversions} -for information about importing foreign key formats. - -\S{puttygen-conversions} Dealing with private keys in other formats - -SSH-2 private keys have no standard format. \I{OpenSSH private -key format}OpenSSH and \I{ssh.com private key format}\cw{ssh.com} have -different formats, and PuTTY's is different again. -So a key generated with one client cannot immediately be used with -another. - -Using the \I{importing keys}\q{Import} command from the \q{Conversions} -menu, PuTTYgen can load SSH-2 private keys in OpenSSH's format and -\cw{ssh.com}'s format. Once you have loaded one of these key types, you -can then save it back out as a PuTTY-format key (\c{*.\i{PPK}}) so that -you can use it with the PuTTY suite. The passphrase will be unchanged by this -process (unless you deliberately change it). You may want to change -the key comment before you save the key, since some OpenSSH key -formats contained no space for a comment, and \cw{ssh.com}'s default -comment format is long and verbose. - -PuTTYgen can also \i{export private keys} in OpenSSH format and in -\cw{ssh.com} format. To do so, select one of the \q{Export} options -from the \q{Conversions} menu. Exporting a key works exactly like -saving it (see \k{puttygen-savepriv}) - you need to have typed your -passphrase in beforehand, and you will be warned if you are about to -save a key without a passphrase. - -For OpenSSH there are two options. Modern OpenSSH actually has two -formats it uses for storing private keys: an older (\q{\i{PEM-style}}) -format, and a newer \q{native} format with better resistance to -passphrase guessing and support for comments. \q{Export OpenSSH key} -will automatically choose the oldest format supported for the key -type, for maximum backward compatibility with older versions of -OpenSSH; for newer key types like Ed25519, it will use the newer -format as that is the only legal option. If you have some specific -reason for wanting to use OpenSSH's newer format even for RSA, DSA, -or ECDSA keys \dash for instance, you know your file will only be -used by OpenSSH 6.5 or newer (released in 2014), and want the extra -security \dash you can choose \q{Export OpenSSH key (force new file -format)}. - -Most clients for the older SSH-1 protocol use a standard format for -storing private keys on disk. PuTTY uses this format as well; so if -you have generated an SSH-1 private key using OpenSSH or -\cw{ssh.com}'s client, you can use it with PuTTY, and vice versa. -Hence, the export options are not available if you have generated an -SSH-1 key. - -\S{puttygen-cli} PuTTYgen command-line configuration - -PuTTYgen supports a set of command-line options to configure many of -the same settings you can select in the GUI. This allows you to start -it up with your own preferences ready-selected, which might be useful -if you generate a lot of keys. (For example, you could make a Windows -shortcut that runs PuTTYgen with some command line options, or a batch -file or Powershell script that you could distribute to a whole -organisation containing your local standards.) - -The options supported on the command line are: - -\dt \cw{\-t} \e{keytype} - -\dd Type of key to generate. You can select \c{rsa}, \c{dsa}, -\c{ecdsa}, \c{eddsa}, \c{ed25519}, \c{ed448}, or \c{rsa1}. -See \k{puttygen-keytype}. - -\dt \cw{\-b} \e{bits} - -\dd Size of the key to generate, in bits. See \k{puttygen-strength}. - -\dt \cw{\-\-primes} \e{method} - -\dd Method for generating prime numbers. You can select \c{probable}, -\c{proven}, and \c{proven-even}. See \k{puttygen-primes}. - -\dt \cw{\-\-strong-rsa} - -\dd When generating an RSA key, make sure the prime factors of the key -modulus are \q{strong primes}. See \k{puttygen-primes}. - -\dt \cw{\-\-ppk-param} \e{key}\cw{=}\e{value}\cw{,}... - -\dd Allows setting all the same details of the PPK save file format -described in \k{puttygen-save-params}. - -\lcont{ - -Aspects to change are specified as a series of \e{key}\cw{=}\e{value} pairs -separated by commas. The \e{key}s are: - -\dt \cw{version} - -\dd The PPK format version: either \cw{3} or \cw{2}. - -\dt \cw{kdf} - -\dd The variant of Argon2 to use: \cw{argon2id}, \cw{argon2i}, and -\cw{argon2d}. - -\dt \cw{memory} - -\dd The amount of memory needed to decrypt the key, in Kbyte. - -\dt \cw{time} - -\dd Specifies how much time is required to attempt decrypting the key, -in milliseconds. - -\dt \cw{passes} - -\dd Alternative to \cw{time}: specifies the number of hash passes -required to attempt decrypting the key. - -\dt \cw{parallelism} - -\dd Number of parallelisable threads that can be used to decrypt the -key. - -} - -\dt \cw{\-E} \e{fptype} - -\dd Algorithm to use when displaying key fingerprints. You can -select \c{sha256} or \c{md5}. See \k{puttygen-fingerprint}. - -\H{pubkey-gettingready} Getting ready for public key authentication - -Connect to your SSH server using PuTTY with the SSH protocol. When the -connection succeeds you will be prompted for your user name and -password to login. Once logged in, you must configure the server to -accept your public key for authentication: - -\b If your server is \i{OpenSSH}, you should change into the -\i\c{.ssh} directory under your home directory, and open the file -\i\c{authorized_keys} with your favourite editor. (You may have to -create this file, if this is the first key you have put in it.) Then -switch to the PuTTYgen window, select all of the text in the \q{Public -key for pasting into OpenSSH authorized_keys file} box (see -\k{puttygen-pastekey}), and copy it to the clipboard (\c{Ctrl+C}). -Then, switch back to the PuTTY window and insert the data into the -open file, making sure it ends up all on one line. Save the file. - -\lcont{ -(In very old versions of OpenSSH, SSH-2 keys had to be put in a -separate file called \c{authorized_keys2}. In all current versions, -the same \c{authorized_keys} file is used for both SSH-1 and SSH-2 keys.) -} - -\b If your server is \i\cw{ssh.com}'s product and is using SSH-2, you -need to save a \e{public} key file from PuTTYgen (see -\k{puttygen-savepub}), and copy that into the \i\c{.ssh2} directory on -the server. Then you should go into that \c{.ssh2} directory, and edit -(or create) a file called \c{authorization}. In this file you should -put a line like \c{Key mykey.pub}, with \c{mykey.pub} replaced by the -name of your key file. - -\b For other SSH server software, you should refer to the manual for -that server. - -You may also need to ensure that your home directory, your \c{.ssh} -directory, and any other files involved (such as -\c{authorized_keys}, \c{authorized_keys2} or \c{authorization}) are -not group-writable or world-writable; servers will typically ignore -the keys unless this is done. You can typically do this by using a -command such as - -\c chmod go-w $HOME $HOME/.ssh $HOME/.ssh/authorized_keys - -Your server should now be configured to accept authentication using -your private key. Now you need to configure PuTTY to \e{attempt} -authentication using your private key. You can do this in any of -three ways: - -\b Select the private key in PuTTY's configuration. See -\k{config-ssh-privkey} for details. - -\b Specify the key file on the command line with the \c{-i} option. -See \k{using-cmdline-identity} for details. - -\b Load the private key into Pageant (see \k{pageant}). In this case -PuTTY will automatically try to use it for authentication if it can. diff --git a/doc/pubkeyfmt.but b/doc/pubkeyfmt.but deleted file mode 100644 index 836ed5278..000000000 --- a/doc/pubkeyfmt.but +++ /dev/null @@ -1,408 +0,0 @@ -\A{ppk} PPK file format - -This appendix documents the file format used by PuTTY to store private -keys. - -In this appendix, binary data structures are described using data type -representations such as \cq{uint32}, \cq{string} and \cq{mpint} as -used in the SSH protocol standards themselves. These are defined -authoritatively by -\W{https://www.rfc-editor.org/rfc/rfc4251#section-5}{RFC 4251 section 5}, -\q{Data Type Representations Used in the SSH Protocols}. - -\H{ppk-overview} Overview - -A PPK file stores a private key, and the corresponding public key. -Both are contained in the same file. - -The file format can be completely unencrypted, or it can encrypt the -private key. The \e{public} key is stored in cleartext in both cases. -(This enables PuTTY to send the public key to an SSH server to see -whether it will accept it, and not bother prompting for the passphrase -unless the server says yes.) - -When the key file is encrypted, the encryption key is derived from a -passphrase. An encrypted PPK file is also tamper-proofed using a MAC -(authentication code), also derived from the same passphrase. The MAC -protects the encrypted private key data, but it also covers the -cleartext parts of the file. So you can't edit the public half of the -key without invalidating the MAC and causing the key file as a whole -to become useless. - -This MAC protects the key file against active cryptographic attacks in -which the public half of a key pair is modified in a controlled way -that allows an attacker to deduce information about the private half -from the resulting corrupted signatures. Any attempt to do that to a -PPK file should be reliably caught by the MAC failing to validate. - -(Such an attack would only be useful if the key file was stored in a -location where the attacker could modify it without also having full -access to the process that you type passphrases into. But that's not -impossible; for example, if your home directory was on a network file -server, then the file server's administrator could access the key file -but not processes on the client machine.) - -The MAC also covers the \e{comment} on the key. This stops an attacker -from swapping keys with each other and editing the comments to -disguise the fact. As a consequence, PuTTYgen cannot edit the comment -on a key unless you decrypt the key with your passphrase first. - -(The circumstances in which \e{that} attack would be useful are even -more restricted. One example might be that the different keys trigger -specific actions on the server you're connecting to and one of those -actions is more useful to the attacker than the other. But once you -have a MAC at all, it's no extra effort to make it cover as much as -possible, and usually sensible.) - -\H{ppk-outer} Outer layer - -The outer layer of a PPK file is text-based. The PuTTY tools will -always use LF line termination when writing PPK files, but will -tolerate CR+LF and CR-only on input. - -The first few lines identify it as a PPK, and give some initial data -about what's stored in it and how. They look like this: - -\c PuTTY-User-Key-File-version: algorithm-name -\e bbbbbbb bbbbbbbbbbbbbb -\c Encryption: encryption-type -\e bbbbbbbbbbbbbbb -\c Comment: key-comment-string -\e bbbbbbbbbbbbbbbbbb - -\s{version} is a decimal number giving the version number of the file -format itself. The current file format version is 3. - -\s{algorithm-name} is the SSH protocol identifier for the public key -algorithm that this key is used for (such as \cq{ssh-dss} or -\cq{ecdsa-sha2-nistp384}). - -\s{encryption-type} indicates whether this key is stored encrypted, -and if so, by what method. Currently the only supported encryption -types are \cq{aes256-cbc} and \cq{none}. - -\s{key-comment-string} is a free text field giving the comment. This -can contain any byte values other than 13 and 10 (CR and LF). - -The next part of the file gives the public key. This is stored -unencrypted but base64-encoded -(\W{https://www.rfc-editor.org/rfc/rfc4648}{RFC 4648}), and is preceded -by a header line saying how many lines of base64 data are shown, -looking like this: - -\c Public-Lines: number-of-lines -\e bbbbbbbbbbbbbbb -\c that many lines of base64 data -\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - -The base64-encoded data in this blob is formatted in exactly the same -way as an SSH public key sent over the wire in the SSH protocol -itself. That is also the same format as the base64 data stored in -OpenSSH's \c{authorized_keys} file, except that in a PPK file the -base64 data is split across multiple lines. But if you remove the -newlines from the middle of this section, the resulting base64 blob is -in the right format to go in an \c{authorized_keys} line. - -If the key is encrypted (i.e. \s{encryption-type} is not \cq{none}), -then the next thing that appears is a sequence of lines specifying how -the keys for encrypting the file are to be derived from the -passphrase: - -\c Key-Derivation: argon2-flavour -\e bbbbbbbbbbbbbb -\c Argon2-Memory: decimal-integer -\e bbbbbbbbbbbbbbb -\c Argon2-Passes: decimal-integer -\e bbbbbbbbbbbbbbb -\c Argon2-Parallelism: decimal-integer -\e bbbbbbbbbbbbbbb -\c Argon2-Salt: hex-string -\e bbbbbbbbbb - -\s{argon2-flavour} is one of the identifiers \cq{Argon2d}, -\cq{Argon2i} or \cq{Argon2id}, all describing variants of the Argon2 -password-hashing function. - -The three integer values are used as parameters for Argon2, which -allows you to configure the amount of memory used (in Kbyte), the number -of passes of the algorithm to run (to tune its running time), and the -degree of parallelism required by the hash function. The salt is -decoded into a sequence of binary bytes and used as an additional -input to Argon2. (It is chosen randomly when the key file is written, -so that a guessing attack can't be mounted in parallel against -multiple key files.) - -The next part of the file gives the private key. This is -base64-encoded in the same way: - -\c Private-Lines: number-of-lines -\e bbbbbbbbbbbbbbb -\c that many lines of base64 data -\e bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - -The binary data represented in this base64 blob may be encrypted, -depending on the \e{encryption-type} field in the key file header -shown above: - -\b If \s{encryption-type} is \cq{none}, then this data is stored in -plain text. - -\b If \s{encryption-type} is \cq{aes256-cbc}, then this data is -encrypted using AES, with a 256-bit key length, in the CBC cipher -mode. The key and initialisation vector are derived from the -passphrase: see \k{ppk-keys}. - -\lcont{ - -In order to encrypt the private key data with AES, it must be a -multiple of 16 bytes (the AES cipher block length). This is achieved -by appending random padding to the data before encrypting it. When -decoding it after decryption, the random data can be ignored: the -internal structure of the data is enough to tell you when you've -reached the end of the meaningful part. - -} - -Unlike public keys, the binary encoding of private keys is not -specified at all in the SSH standards. See \k{ppk-privkeys} for -details of the private key format for each key type supported by -PuTTY. - -The final thing in the key file is the MAC: - -\c Private-MAC: hex-mac-data -\e bbbbbbbbbbbb - -\s{hex-mac-data} is a hexadecimal-encoded value, 64 digits long (i.e. -32 bytes), generated using the HMAC-SHA-256 algorithm with the -following binary data as input: - -\b \cw{string}: the \s{algorithm-name} header field. - -\b \cw{string}: the \s{encryption-type} header field. - -\b \cw{string}: the \s{key-comment-string} header field. - -\b \cw{string}: the binary public key data, as decoded from the base64 -lines after the \cq{Public-Lines} header. - -\b \cw{string}: the plaintext of the binary private key data, as -decoded from the base64 lines after the \cq{Private-Lines} header. If -that data was stored encrypted, then the decrypted version of it is -used in this MAC preimage, \e{including} the random padding mentioned -above. - -The MAC key is derived from the passphrase: see \k{ppk-keys}. - -\H{ppk-privkeys} Private key encodings - -This section describes the private key format for each key type -supported by PuTTY. - -Because the PPK format also contains the public key (and both public -and private key are protected by the same MAC to ensure they can't be -made inconsistent), there is no need for the private key section of -the file to repeat data from the public section. So some of these -formats are very short. - -In all cases, a decoding application can begin reading from the start -of the decrypted private key data, and know when it has read all that -it needs. This allows random padding after the meaningful data to be -safely ignored. - -\S{ppk-privkey-rsa} RSA - -RSA keys are stored using an \s{algorithm-name} of \cq{ssh-rsa}. (Keys -stored like this are also used by the updated RSA signature schemes -that use hashes other than SHA-1.) - -The public key data has already provided the key modulus and the -public encoding exponent. The private data stores: - -\b \cw{mpint}: the private decoding exponent of the key. - -\b \cw{mpint}: one prime factor \e{p} of the key. - -\b \cw{mpint}: the other prime factor \e{q} of the key. (RSA keys -stored in this format are expected to have exactly two prime factors.) - -\b \cw{mpint}: the multiplicative inverse of \e{q} modulo \e{p}. - -\S{ppk-privkey-dsa} DSA - -DSA keys are stored using an \s{algorithm-name} of \cq{ssh-dss}. - -The public key data has already provided the key parameters (the large -prime \e{p}, the small prime \e{q} and the group generator \e{g}), and -the public key \e{y}. The private key stores: - -\b \cw{mpint}: the private key \e{x}, which is the discrete logarithm -of \e{y} in the group generated by \e{g} mod \e{p}. - -\S{ppk-privkey-ecdsa} NIST elliptic-curve keys - -\i{NIST} elliptic-curve keys are stored using one of the following -\s{algorithm-name} values, each corresponding to a different elliptic -curve and key size: - -\b \cq{ecdsa-sha2-nistp256} - -\b \cq{ecdsa-sha2-nistp384} - -\b \cq{ecdsa-sha2-nistp521} - -The public key data has already provided the public elliptic curve -point. The private key stores: - -\b \cw{mpint}: the private exponent, which is the discrete log of the -public point. - -\S{ppk-privkey-eddsa} EdDSA elliptic-curve keys (Ed25519 and Ed448) - -EdDSA elliptic-curve keys are stored using one of the following -\s{algorithm-name} values, each corresponding to a different elliptic -curve and key size: - -\b \cq{ssh-ed25519} - -\b \cq{ssh-ed448} - -The public key data has already provided the public elliptic curve -point. The private key stores: - -\b \cw{mpint}: the private exponent, which is the discrete log of the -public point. - -\H{ppk-keys} Key derivation - -When a key file is encrypted, there are three pieces of key material -that need to be computed from the passphrase: - -\b the key for the symmetric cipher used to encrypt the private key - -\b the initialisation vector for that cipher encryption - -\b the key for the MAC. - -If \s{encryption-type} is \cq{aes256-cbc}, then the symmetric cipher -key is 32 bytes long, and the initialisation vector is 16 bytes (one -cipher block). The length of the MAC key is also chosen to be 32 -bytes. - -If \s{encryption-type} is \cq{none}, then all three of these pieces of -data have zero length. (The MAC is still generated and checked in the -key file format, but it has a zero-length key.) - -If the amount of key material required is not zero, then the -passphrase is fed to the Argon2 key derivation function, in whichever -mode is described in the \cq{Key-Derivation} header in the key file, -with parameters derived from the various -\q{\cw{Argon2-}\e{Parameter}\cw{:}} headers. - -(If the key is unencrypted, then all those headers are omitted, and -Argon2 is not run at all.) - -Argon2 takes two extra string inputs in addition to the passphrase and -the salt: a secret key, and some \q{associated data}. In PPK's use of -Argon2, these are both set to the empty string. - -The \q{tag length} parameter to Argon2 (i.e. the amount of data it is -asked to output) is set to the sum of the lengths of all of the data -items required, i.e. (cipher key length + IV length + MAC key length). -The output data is interpreted as the concatenation of the cipher key, -the IV and the MAC key, in that order. - -So, for \cq{aes256-cbc}, the tag length will be 32+16+32\_=\_80 bytes; -of the 80 bytes of output data, the first 32 bytes are used as the -256-bit AES key, the next 16 as the CBC IV, and the final 32 bytes as -the HMAC-SHA-256 key. - -\H{ppk-old} Older versions of the PPK format - -\S{ppk-v2} Version 2 - -PPK version 2 was used by PuTTY 0.52 to 0.74 inclusive. - -In PPK version 2, the MAC algorithm used was HMAC-SHA-1 (so the -\cw{Private-MAC} line contained only 40 hex digits). - -The \cq{Key-Derivation:} header and all the -\q{\cw{Argon2-}\e{Parameter}\cw{:}} headers were absent. Instead of -using Argon2, the key material for encrypting the private blob was -derived from the passphrase in a totally different way, as follows. - -The cipher key for \cq{aes256-cbc} was constructed by generating two -SHA-1 hashes, concatenating them, and taking the first 32 bytes of the -result. (So you'd get all 20 bytes of the first hash output, and the -first 12 of the second). Each hash preimage was as follows: - -\b \cw{uint32}: a sequence number. This is 0 in the first hash, and 1 -in the second. (The idea was to extend this mechanism to further -hashes by continuing to increment the sequence number, if future -changes required even longer keys.) - -\b the passphrase, without any prefix length field. - -In PPK v2, the CBC initialisation vector was all zeroes. - -The MAC key was 20 bytes long, and was a single SHA-1 hash of the -following data: - -\b the fixed string \cq{putty-private-key-file-mac-key}, without any -prefix length field. - -\b the passphrase, without any prefix length field. (If the key is -stored unencrypted, the passphrase was taken to be the empty string -for these purposes.) - -\S{ppk-v1} Version 1 - -PPK version 1 was a badly designed format, only used during initial -development, and not recommended for production use. - -PPK version 1 was never used by a released version of PuTTY. It was -only emitted by some early development snapshots between version 0.51 -(which did not support SSH-2 public keys at all) and 0.52 (which -already used version 2 of this file format). I \e{hope} there are no -PPK v1 files in use anywhere. But just in case, the old badly designed -format is documented here anyway. - -In PPK version 1, the input to the MAC does not include any of the -header fields or the public key. It is simply the private key data -(still in plaintext and including random padding), all by itself -(without a wrapping \cw{string}). - -PPK version 1 keys must therefore be rigorously validated after -loading, to ensure that the public and private parts of the key were -consistent with each other. - -PPK version 1 only supported the RSA and DSA key types. For RSA, this -validation can be done using only the provided data (since the private -key blob contains enough information to reconstruct the public values -anyway). But for DSA, that isn't quite enough. - -Hence, PPK version 1 DSA keys extended the private data so that -immediately after \e{x} was stored an extra value: - -\b \cw{string}: a SHA-1 hash of the public key data, whose preimage -consists of - -\lcont{ - -\b \cw{string}: the large prime \e{p} - -\b \cw{string}: the small prime \e{q} - -\b \cw{string}: the group generator \e{g} - -} - -The idea was that checking this hash would verify that the key -parameters had not been tampered with, and then the loading -application could directly verify that -\e{g}\cw{^}\e{x}\cw{\_=\_}\e{y}. - -In an \e{unencrypted} version 1 key file, the MAC is replaced by a -plain SHA-1 hash of the private key data. This is indicated by the -\cq{Private-MAC:} header being replaced with \cq{Private-Hash:} -instead. diff --git a/doc/site.but b/doc/site.but deleted file mode 100644 index a74f4dfeb..000000000 --- a/doc/site.but +++ /dev/null @@ -1,4 +0,0 @@ -\# Additional configuration for the version of the PuTTY docs -\# actually published as HTML on the website. - -\cfg{xhtml-head-end}{} diff --git a/doc/sshnames.but b/doc/sshnames.but deleted file mode 100644 index a00ac6787..000000000 --- a/doc/sshnames.but +++ /dev/null @@ -1,130 +0,0 @@ -\A{sshnames} SSH-2 names specified for PuTTY - -There are various parts of the SSH-2 protocol where things are specified -using a textual name. Names ending in \cw{@putty.projects.tartarus.org} -are reserved for allocation by the PuTTY team. Allocated names are -documented here. - -\H{sshnames-channel} Connection protocol channel request names - -These names can be sent in a \cw{SSH_MSG_CHANNEL_REQUEST} message. - -\dt \cw{simple@putty.projects.tartarus.org} - -\dd This is sent by a client to announce that it will not have more than -one channel open at a time in the current connection (that one being -the one the request is sent on). The intention is that the server, -knowing this, can set the window on that one channel to something very -large, and leave flow control to TCP. There is no message-specific data. - -\dt \cw{winadj@putty.projects.tartarus.org} - -\dd PuTTY sends this request along with some -\cw{SSH_MSG_CHANNEL_WINDOW_ADJUST} messages as part of its window-size -tuning. It can be sent on any type of channel. There is no -message-specific data. Servers MUST treat it as an unrecognised request -and respond with \cw{SSH_MSG_CHANNEL_FAILURE}. - -\lcont{ -(Some SSH servers get confused by this message, so there is a -bug-compatibility mode for disabling it. See \k{config-ssh-bug-winadj}.) -} - -\H{sshnames-kex} Key exchange method names - -\dt \cw{rsa-sha1-draft-00@putty.projects.tartarus.org} - -\dt \cw{rsa-sha256-draft-00@putty.projects.tartarus.org} - -\dt \cw{rsa1024-sha1-draft-01@putty.projects.tartarus.org} - -\dt \cw{rsa1024-sha256-draft-01@putty.projects.tartarus.org} - -\dt \cw{rsa2048-sha256-draft-01@putty.projects.tartarus.org} - -\dt \cw{rsa1024-sha1-draft-02@putty.projects.tartarus.org} - -\dt \cw{rsa2048-sha512-draft-02@putty.projects.tartarus.org} - -\dt \cw{rsa1024-sha1-draft-03@putty.projects.tartarus.org} - -\dt \cw{rsa2048-sha256-draft-03@putty.projects.tartarus.org} - -\dt \cw{rsa1024-sha1-draft-04@putty.projects.tartarus.org} - -\dt \cw{rsa2048-sha256-draft-04@putty.projects.tartarus.org} - -\dd These appeared in various drafts of what eventually became RFC\_4432. -They have been superseded by \cw{rsa1024-sha1} and \cw{rsa2048-sha256}. - -\H{sshnames-encrypt} Encryption algorithm names - -\dt \cw{arcfour128-draft-00@putty.projects.tartarus.org} - -\dt \cw{arcfour256-draft-00@putty.projects.tartarus.org} - -\dd These were used in drafts of what eventually became RFC\_4345. -They have been superseded by \cw{arcfour128} and \cw{arcfour256}. - -\H{sshnames-agent} Agent extension request names - -The SSH agent protocol, which is only specified in an Internet-Draft -at the time of writing -(\W{https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent}\cw{draft-miller-ssh-agent}), -defines an extension mechanism. These names can be sent in an -\cw{SSH_AGENTC_EXTENSION} message. - -\dt \cw{add-ppk@putty.projects.tartarus.org} - -\dd The payload is a single SSH-2 \cw{string} containing a keypair in -the PPK format defined in \k{ppk}. Compared to the standard -\cw{SSH_AGENTC_ADD_IDENTITY}, this extension allows adding keys in -encrypted form, with the agent requesting a decryption passphrase from -the user on demand, and able to revert the key to encrypted form. - -\dt \cw{reencrypt@putty.projects.tartarus.org} - -\dd The payload is a single SSH-2 \cw{string} specifying a public key -blob, as in \cw{SSH_AGENTC_REMOVE_IDENTITY}. Requests that the agent -forget any cleartext form of a specific key. - -\lcont{ -Returns \cw{SSH_AGENT_SUCCESS} if the agent ended up holding the key -only in encrypted form (even if it was already encrypted); returns -\cw{SSH_AGENT_EXTENSION_FAILURE} if not (if it wasn't held by the -agent at all, or only in cleartext form). -} - -\dt \cw{reencrypt-all@putty.projects.tartarus.org} - -\dd No payload. Requests that the agent forget the cleartext form of -any keys for which it holds an encrypted form. - -\lcont{ -If the agent holds any keys with an encrypted form (or no keys at all), -returns \cw{SSH_AGENT_SUCCESS} to indicate that no such keys are now -held in cleartext form, followed by a \cw{uint32} specifying how many keys -remain in cleartext form (because the agent didn't hold an encrypted -form for them). If the agent holds nothing but keys in cleartext form, -returns \cw{SSH_AGENT_EXTENSION_FAILURE}. -} - -\dt \cw{list-extended@putty.projects.tartarus.org} - -\dd No payload. Returns \cw{SSH_AGENT_SUCCESS} followed by a list of -identities similar to \cw{SSH_AGENT_IDENTITIES_ANSWER}, except that -each key has an extra SSH-2 \cw{string} at the end. Currently that -\cw{string} contains a single \cw{uint32} flags word, with the -following bits defined: - -\lcont{ -\dt Bit 0 - -\dd If set, key is held with an encrypted form (so that the -\c{reencrypt} extension can do something useful with it). - -\dt Bit 1 - -\dd If set, key's cleartext form is not currently held (so the -user will have to supply a passphrase before the key can be used). -} diff --git a/doc/udp.but b/doc/udp.but deleted file mode 100644 index 12b3392b1..000000000 --- a/doc/udp.but +++ /dev/null @@ -1,764 +0,0 @@ -\# This file is so named for tradition's sake: it contains what we -\# always used to refer to, before they were written down, as -\# PuTTY's `unwritten design principles'. It has nothing to do with -\# the User Datagram Protocol. - -\A{udp} PuTTY hacking guide - -This appendix lists a selection of the design principles applying to -the PuTTY source code. If you are planning to send code -contributions, you should read this first. - -\H{udp-portability} Cross-OS portability - -Despite Windows being its main area of fame, PuTTY is no longer a -Windows-only application suite. It has a working Unix port; a Mac -port is in progress; more ports may or may not happen at a later -date. - -Therefore, embedding Windows-specific code in core modules such as -\cw{ssh.c} is not acceptable. We went to great lengths to \e{remove} -all the Windows-specific stuff from our core modules, and to shift -it out into Windows-specific modules. Adding large amounts of -Windows-specific stuff in parts of the code that should be portable -is almost guaranteed to make us reject a contribution. - -The PuTTY source base is divided into platform-specific modules and -platform-generic modules. The Unix-specific modules are all in the -\c{unix} subdirectory; the Windows-specific modules are in the -\c{windows} subdirectory. - -All the modules in the main source directory and other -subdirectories - notably \e{all} of the code for the various back -ends - are platform-generic. We want to keep them that way. - -This also means you should stick to the C semantics guaranteed by the -C standard: try not to make assumptions about the precise size of -basic types such as \c{int} and \c{long int}; don't use pointer casts -to do endianness-dependent operations, and so on. - -(Even \e{within} a platform front end you should still be careful of -some of these portability issues. The Windows front end compiles on -both 32- and 64-bit x86 and also Arm.) - -Our current choice of C standards version is \e{mostly} C99. With a -couple of exceptions, you can assume that C99 features are available -(in particular \cw{}, \cw{} and \c{inline}), but -you shouldn't use things that are new in C11 (such as \cw{} -or \cw{_Generic}). - -The exceptions to that rule are due to the need for Visual Studio -compatibility: - -\b Don't use variable-length arrays. Visual Studio doesn't support -them even now that it's adopted the rest of C99. We use \cw{-Wvla} -when building with gcc and clang, to make it easier to avoid -accidentally breaking that rule. - -\b For historical reasons, we still build with one older VS version -which lacks \cw{}. So that file is included centrally in -\c{defs.h}, and has a set of workaround definitions for the -\cw{PRIx64}-type macros we use. If you need to use another one of -those macros, you need to add a workaround definition in \c{defs.h}, -and don't casually re-include \cw{} anywhere else in the -source file. - -Here are a few portability assumptions that we \e{do} currently allow -(because we'd already have to thoroughly vet the existing code if they -ever needed to change, and it doesn't seem worth doing that unless we -really have to): - -\b You can assume \c{int} is \e{at least} 32 bits wide. (We've never -tried to port PuTTY to a platform with 16-bit \cw{int}, and it doesn't -look likely to be necessary in future.) - -\b Similarly, you can assume \c{char} is exactly 8 bits. (Exceptions -to that are even less likely to be relevant to us than short -\cw{int}.) - -\b You can assume that using \c{memset} to write zero bytes over a -whole structure will have the effect of setting all its pointer fields -to \cw{NULL}. (The standard itself guarantees this for \e{integer} -fields, but not for pointers.) - -\b You can assume that \c{time_t} has POSIX semantics, i.e. that it -represents an integer number of non-leap seconds since 1970-01-01 -00:00:00 UTC. (Times in this format are used in X authorisation, but -we could work around that by carefully distinguishing local \c{time_t} -from time values used in the wire protocol; but these semantics of -\c{time_t} are also baked into the shared library API used by the -GSSAPI authentication code, which would be much harder to change.) - -\b You can assume that the execution character encoding is a superset -of the printable characters of ASCII. (In particular, it's fine to do -arithmetic on a \c{char} value representing a Latin alphabetic -character, without bothering to allow for EBCDIC or other -non-consecutive encodings of the alphabet.) - -On the other hand, here are some particular things \e{not} to assume: - -\b Don't assume anything about the \e{signedness} of \c{char}. In -particular, you \e{must} cast \c{char} values to \c{unsigned char} -before passing them to any \cw{} function (because those -expect a non-negative character value, or \cw{EOF}). If you need a -particular signedness, explicitly specify \c{signed char} or -\c{unsigned char}, or use C99 \cw{int8_t} or \cw{uint8_t}. - -\b From past experience with MacOS, we're still a bit nervous about -\cw{'\\n'} and \cw{'\\r'} potentially having unusual meanings on a -given platform. So it's fine to say \c{\\n} in a string you're passing -to \c{printf}, but in any context where those characters appear in a -standardised wire protocol or a binary file format, they should be -spelled \cw{'\\012'} and \cw{'\\015'} respectively. - -\H{udp-multi-backend} Multiple backends treated equally - -PuTTY is not an SSH client with some other stuff tacked on the side. -PuTTY is a generic, multiple-backend, remote VT-terminal client -which happens to support one backend which is larger, more popular -and more useful than the rest. Any extra feature which can possibly -be general across all backends should be so: localising features -unnecessarily into the SSH back end is a design error. (For example, -we had several code submissions for proxy support which worked by -hacking \cw{ssh.c}. Clearly this is completely wrong: the -\cw{network.h} abstraction is the place to put it, so that it will -apply to all back ends equally, and indeed we eventually put it -there after another contributor sent a better patch.) - -The rest of PuTTY should try to avoid knowing anything about -specific back ends if at all possible. To support a feature which is -only available in one network protocol, for example, the back end -interface should be extended in a general manner such that \e{any} -back end which is able to provide that feature can do so. If it so -happens that only one back end actually does, that's just the way it -is, but it shouldn't be relied upon by any code. - -\H{udp-globals} Multiple sessions per process on some platforms - -Some ports of PuTTY - notably the in-progress Mac port - are -constrained by the operating system to run as a single process -potentially managing multiple sessions. - -Therefore, the platform-independent parts of PuTTY never use global -variables to store per-session data. The global variables that do -exist are tolerated because they are not specific to a particular -login session. The random number state in \cw{sshrand.c}, the timer -list in \cw{timing.c} and the queue of top-level callbacks in -\cw{callback.c} serve all sessions equally. But most data is specific -to a particular network session, and is therefore stored in -dynamically allocated data structures, and pointers to these -structures are passed around between functions. - -Platform-specific code can reverse this decision if it likes. The -Windows code, for historical reasons, stores most of its data as -global variables. That's OK, because \e{on Windows} we know there is -only one session per PuTTY process, so it's safe to do that. But -changes to the platform-independent code should avoid introducing -global variables, unless they are genuinely cross-session. - -\H{udp-pure-c} C, not C++ - -PuTTY is written entirely in C, not in C++. - -We have made \e{some} effort to make it easy to compile our code -using a C++ compiler: notably, our \c{snew}, \c{snewn} and -\c{sresize} macros explicitly cast the return values of \cw{malloc} -and \cw{realloc} to the target type. (This has type checking -advantages even in C: it means you never accidentally allocate the -wrong size piece of memory for the pointer type you're assigning it -to. C++ friendliness is really a side benefit.) - -We want PuTTY to continue being pure C, at least in the -platform-independent parts and the currently existing ports. Patches -which switch the Makefiles to compile it as C++ and start using -classes will not be accepted. - -The one exception: a port to a new platform may use languages other -than C if they are necessary to code on that platform. If your -favourite PDA has a GUI with a C++ API, then there's no way you can -do a port of PuTTY without using C++, so go ahead and use it. But -keep the C++ restricted to that platform's subdirectory; if your -changes force the Unix or Windows ports to be compiled as C++, they -will be unacceptable to us. - -\H{udp-security} Security-conscious coding - -PuTTY is a network application and a security application. Assume -your code will end up being fed deliberately malicious data by -attackers, and try to code in a way that makes it unlikely to be a -security risk. - -In particular, try not to use fixed-size buffers for variable-size -data such as strings received from the network (or even the user). -We provide functions such as \cw{dupcat} and \cw{dupprintf}, which -dynamically allocate buffers of the right size for the string they -construct. Use these wherever possible. - -\H{udp-multi-compiler} Independence of specific compiler - -Windows PuTTY can currently be compiled with any of three Windows -compilers: MS Visual C, the Cygwin / \cw{mingw32} GNU tools, and -\cw{clang} (in MS compatibility mode). - -This is a really useful property of PuTTY, because it means people -who want to contribute to the coding don't depend on having a -specific compiler; so they don't have to fork out money for MSVC if -they don't already have it, but on the other hand if they \e{do} -have it they also don't have to spend effort installing \cw{gcc} -alongside it. They can use whichever compiler they happen to have -available, or install whichever is cheapest and easiest if they -don't have one. - -Therefore, we don't want PuTTY to start depending on which compiler -you're using. Using GNU extensions to the C language, for example, -would ruin this useful property (not that anyone's ever tried it!); -and more realistically, depending on an MS-specific library function -supplied by the MSVC C library (\cw{_snprintf}, for example) is a -mistake, because that function won't be available under the other -compilers. Any function supplied in an official Windows DLL as part -of the Windows API is fine, and anything defined in the C library -standard is also fine, because those should be available -irrespective of compilation environment. But things in between, -available as non-standard library and language extensions in only -one compiler, are disallowed. - -(\cw{_snprintf} in particular should be unnecessary, since we -provide \cw{dupprintf}; see \k{udp-security}.) - -Compiler independence should apply on all platforms, of course, not -just on Windows. - -\H{udp-small} Small code size - -PuTTY is tiny, compared to many other Windows applications. And it's -easy to install: it depends on no DLLs, no other applications, no -service packs or system upgrades. It's just one executable. You -install that executable wherever you want to, and run it. - -We want to keep both these properties - the small size, and the ease -of installation - if at all possible. So code contributions that -depend critically on external DLLs, or that add a huge amount to the -code size for a feature which is only useful to a small minority of -users, are likely to be thrown out immediately. - -We do vaguely intend to introduce a DLL plugin interface for PuTTY, -whereby seriously large extra features can be implemented in plugin -modules. The important thing, though, is that those DLLs will be -\e{optional}; if PuTTY can't find them on startup, it should run -perfectly happily and just won't provide those particular features. -A full installation of PuTTY might one day contain ten or twenty -little DLL plugins, which would cut down a little on the ease of -installation - but if you really needed ease of installation you -\e{could} still just install the one PuTTY binary, or just the DLLs -you really needed, and it would still work fine. - -Depending on \e{external} DLLs is something we'd like to avoid if at -all possible (though for some purposes, such as complex SSH -authentication mechanisms, it may be unavoidable). If it can't be -avoided, the important thing is to follow the same principle of -graceful degradation: if a DLL can't be found, then PuTTY should run -happily and just not supply the feature that depended on it. - -\H{udp-single-threaded} Single-threaded code - -PuTTY and its supporting tools, or at least the vast majority of -them, run in only one OS thread. - -This means that if you're devising some piece of internal mechanism, -there's no need to use locks to make sure it doesn't get called by -two threads at once. The only way code can be called re-entrantly is -by recursion. - -That said, most of Windows PuTTY's network handling is triggered off -Windows messages requested by \cw{WSAAsyncSelect()}, so if you call -\cw{MessageBox()} deep within some network event handling code you -should be aware that you might be re-entered if a network event -comes in and is passed on to our window procedure by the -\cw{MessageBox()} message loop. - -Also, the front ends can use multiple threads if they like. For -example, the Windows front-end code spawns subthreads to deal with -bidirectional blocking I/O on non-network streams such as Windows -pipes. However, it keeps tight control of its auxiliary threads, and -uses them only for that one purpose, as a form of \cw{select()}. -Pretty much all the code outside \cw{windows/handle-io.c} is \e{only} -ever called from the one primary thread; the others just loop round -blocking on file handles, and signal the main thread (via Windows -event objects) when some real work needs doing. This is not considered -a portability hazard because that code is already Windows-specific and -needs rewriting on other platforms. - -One important consequence of this: PuTTY has only one thread in -which to do everything. That \q{everything} may include managing -more than one login session (\k{udp-globals}), managing multiple -data channels within an SSH session, responding to GUI events even -when nothing is happening on the network, and responding to network -requests from the server (such as repeat key exchange) even when the -program is dealing with complex user interaction such as the -re-configuration dialog box. This means that \e{almost none} of the -PuTTY code can safely block. - -\H{udp-keystrokes} Keystrokes sent to the server wherever possible - -In almost all cases, PuTTY sends keystrokes to the server. Even -weird keystrokes that you think should be hot keys controlling -PuTTY. Even Alt-F4 or Alt-Space, for example. If a keystroke has a -well-defined escape sequence that it could usefully be sending to -the server, then it should do so, or at the very least it should be -configurably able to do so. - -To unconditionally turn a key combination into a hot key to control -PuTTY is almost always a design error. If a hot key is really truly -required, then try to find a key combination for it which \e{isn't} -already used in existing PuTTYs (either it sends nothing to the -server, or it sends the same thing as some other combination). Even -then, be prepared for the possibility that one day that key -combination might end up being needed to send something to the -server - so make sure that there's an alternative way to invoke -whatever PuTTY feature it controls. - -\H{udp-640x480} 640\u00D7{x}480 friendliness in configuration panels - -There's a reason we have lots of tiny configuration panels instead -of a few huge ones, and that reason is that not everyone has a -1600\u00D7{x}1200 desktop. 640\u00D7{x}480 is still a viable -resolution for running Windows (and indeed it's still the default if -you start up in safe mode), so it's still a resolution we care -about. - -Accordingly, the PuTTY configuration box, and the PuTTYgen control -window, are deliberately kept just small enough to fit comfortably -on a 640\u00D7{x}480 display. If you're adding controls to either of -these boxes and you find yourself wanting to increase the size of -the whole box, \e{don't}. Split it into more panels instead. - -\H{udp-ssh-coroutines} Coroutines in protocol code - -Large parts of the code in modules implementing wire protocols -(mainly SSH) are structured using a set of macros that implement -(something close to) Donald Knuth's \q{coroutines} concept in C. - -Essentially, the purpose of these macros are to arrange that a -function can call \cw{crReturn()} to return to its caller, and the -next time it is called control will resume from just after that -\cw{crReturn} statement. - -This means that any local (automatic) variables declared in such a -function will be corrupted every time you call \cw{crReturn}. If you -need a variable to persist for longer than that, you \e{must} make it -a field in some appropriate structure containing the persistent state -of the coroutine \dash typically the main state structure for a -protocol layer. - -See -\W{https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html}\c{https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html} -for a more in-depth discussion of what these macros are for and how -they work. - -Another caveat: most of these coroutines are not \e{guaranteed} to run -to completion, because the SSH connection (or whatever) that they're -part of might be interrupted at any time by an unexpected network -event or user action. So whenever a coroutine-managed variable refers -to a resource that needs releasing, you should also ensure that the -cleanup function for its containing state structure can reliably -release it even if the coroutine is aborted at an arbitrary point. - -For example, if an SSH packet protocol layer has to have a field that -sometimes points to a piece of allocated memory, then you should -ensure that when you free that memory you reset the pointer field to -\cw{NULL}. Then, no matter when the protocol layer's cleanup function -is called, it can reliably free the memory if there is any, and not -crash if there isn't. - -\H{udp-traits} Explicit vtable structures to implement traits - -A lot of PuTTY's code is written in a style that looks structurally -rather like an object-oriented language, in spite of PuTTY being a -pure C program. - -For example, there's a single data type called \cw{ssh_hash}, which is -an abstraction of a secure hash function, and a bunch of functions -called things like \cw{ssh_hash_}\e{foo} that do things with those -data types. But in fact, PuTTY supports many different hash functions, -and each one has to provide its own implementation of those functions. - -In C++ terms, this is rather like having a single abstract base class, -and multiple concrete subclasses of it, each of which fills in all the -pure virtual methods in a way that's compatible with the data fields -of the subclass. The implementation is more or less the same, as well: -in C, we do explicitly in the source code what the C++ compiler will -be doing behind the scenes at compile time. - -But perhaps a closer analogy in functional terms is the Rust concept -of a \q{trait}, or the Java idea of an \q{interface}. C++ supports a -multi-level hierarchy of inheritance, whereas PuTTY's system \dash -like traits or interfaces \dash has only two levels, one describing a -generic object of a type (e.g. a hash function) and another describing -a specific implementation of that type (e.g. SHA-256). - -The PuTTY code base has a standard idiom for doing this in C, as -follows. - -Firstly, we define two \cw{struct} types for our trait. One of them -describes a particular \e{kind} of implementation of that trait, and -it's full of (mostly) function pointers. The other describes a -specific \e{instance} of an implementation of that trait, and it will -contain a pointer to a \cw{const} instance of the first type. For -example: - -\c typedef struct MyAbstraction MyAbstraction; -\c typedef struct MyAbstractionVtable MyAbstractionVtable; -\c -\c struct MyAbstractionVtable { -\c MyAbstraction *(*new)(const MyAbstractionVtable *vt); -\c void (*free)(MyAbstraction *); -\c void (*modify)(MyAbstraction *, unsigned some_parameter); -\c unsigned (*query)(MyAbstraction *, unsigned some_parameter); -\c }; -\c -\c struct MyAbstraction { -\c const MyAbstractionVtable *vt; -\c }; - -Here, we imagine that \cw{MyAbstraction} might be some kind of object -that contains mutable state. The associated vtable structure shows -what operations you can perform on a \cw{MyAbstraction}: you can -create one (dynamically allocated), free one you already have, or call -the example methods \q{modify} (to change the state of the object in -some way) and \q{query} (to return some value derived from the -object's current state). - -(In most cases, the vtable structure has a name ending in \cq{vtable}. -But for historical reasons a lot of the crypto primitives that use -this scheme \dash ciphers, hash functions, public key methods and so -on \dash instead have names ending in \cq{alg}, on the basis that the -primitives they implement are often referred to as \q{encryption -algorithms}, \q{hash algorithms} and so forth.) - -Now, to define a concrete instance of this trait, you'd define a -\cw{struct} that contains a \cw{MyAbstraction} field, plus any other -data it might need: - -\c struct MyImplementation { -\c unsigned internal_data[16]; -\c SomeOtherType *dynamic_subthing; -\c -\c MyAbstraction myabs; -\c }; - -Next, you'd implement all the necessary methods for that -implementation of the trait, in this kind of style: - -\c static MyAbstraction *myimpl_new(const MyAbstractionVtable *vt) -\c { -\c MyImplementation *impl = snew(MyImplementation); -\c memset(impl, 0, sizeof(*impl)); -\c impl->dynamic_subthing = allocate_some_other_type(); -\c impl->myabs.vt = vt; -\c return &impl->myabs; -\c } -\c -\c static void myimpl_free(MyAbstraction *myabs) -\c { -\c MyImplementation *impl = container_of(myabs, MyImplementation, myabs); -\c free_other_type(impl->dynamic_subthing); -\c sfree(impl); -\c } -\c -\c static void myimpl_modify(MyAbstraction *myabs, unsigned param) -\c { -\c MyImplementation *impl = container_of(myabs, MyImplementation, myabs); -\c impl->internal_data[param] += do_something_with(impl->dynamic_subthing); -\c } -\c -\c static unsigned myimpl_query(MyAbstraction *myabs, unsigned param) -\c { -\c MyImplementation *impl = container_of(myabs, MyImplementation, myabs); -\c return impl->internal_data[param]; -\c } - -Having defined those methods, now we can define a \cw{const} instance -of the vtable structure containing pointers to them: - -\c const MyAbstractionVtable MyImplementation_vt = { -\c .new = myimpl_new, -\c .free = myimpl_free, -\c .modify = myimpl_modify, -\c .query = myimpl_query, -\c }; - -\e{In principle}, this is all you need. Client code can construct a -new instance of a particular implementation of \cw{MyAbstraction} by -digging out the \cw{new} method from the vtable and calling it (with -the vtable itself as a parameter), which returns a \cw{MyAbstraction -*} pointer that identifies a newly created instance, in which the -\cw{vt} field will contain a pointer to the same vtable structure you -passed in. And once you have an instance object, say \cw{MyAbstraction -*myabs}, you can dig out one of the other method pointers from the -vtable it points to, and call that, passing the object itself as a -parameter. - -But in fact, we don't do that, because it looks pretty ugly at all the -call sites. Instead, what we generally do in this code base is to -write a set of \cw{static inline} wrapper functions in the same header -file that defined the \cw{MyAbstraction} structure types, like this: - -\c static inline MyAbstraction *myabs_new(const MyAbstractionVtable *vt) -\c { return vt->new(vt); } -\c static inline void myabs_free(MyAbstraction *myabs) -\c { myabs->vt->free(myabs); } -\c static inline void myimpl_modify(MyAbstraction *myabs, unsigned param) -\c { myabs->vt->modify(myabs, param); } -\c static inline unsigned myimpl_query(MyAbstraction *myabs, unsigned param) -\c { return myabs->vt->query(myabs, param); } - -And now call sites can use those reasonably clean-looking wrapper -functions, and shouldn't ever have to directly refer to the \cw{vt} -field inside any \cw{myabs} object they're holding. For example, you -might write something like this: - -\c MyAbstraction *myabs = myabs_new(&MyImplementation_vtable); -\c myabs_update(myabs, 10); -\c unsigned output = myabs_query(myabs, 2); -\c myabs_free(myabs); - -and then all this code can use a different implementation of the same -abstraction by just changing which vtable pointer it passed in in the -first line. - -Some things to note about this system: - -\b The implementation instance type (here \cq{MyImplementation} -contains the abstraction type (\cq{MyAbstraction}) as one of its -fields. But that field is not necessarily at the start of the -structure. So you can't just \e{cast} pointers back and forth between -the two types. Instead: - -\lcont{ - -\b You \q{up-cast} from implementation to abstraction by taking the -address of the \cw{MyAbstraction} field. You can see the example -\cw{new} method above doing this, returning \cw{&impl->myabs}. All -\cw{new} methods do this on return. - -\b Going in the other direction, each method that was passed a generic -\cw{MyAbstraction *myabs} parameter has to recover a pointer to the -specific implementation type \cw{MyImplementation *impl}. The idiom -for doing that is to use the \cq{container_of} macro, also seen in the -Linux kernel code. Generally, \cw{container_of(p, Type, field)} says: -\q{I'm confident that the pointer value \cq{p} is pointing to the -field called \cq{field} within a larger \cw{struct} of type \cw{Type}. -Please return me the pointer to the containing structure.} So in this -case, we take the \cq{myabs} pointer passed to the function, and -\q{down-cast} it into a pointer to the larger and more specific -structure type \cw{MyImplementation}, by adjusting the pointer value -based on the offset within that structure of the field called -\cq{myabs}. - -This system is flexible enough to permit \q{multiple inheritance}, or -rather, multiple \e{implementation}: having one object type implement -more than one trait. For example, the \cw{ProxySocket} type implements -both the \cw{Socket} trait and the \cw{Plug} trait that connects to it, -because it has to act as an adapter between another instance of each -of those types. - -It's also perfectly possible to have the same object implement the -\e{same} trait in two different ways. At the time of writing this I -can't think of any case where we actually do this, but a theoretical -example might be if you needed to support a trait like \cw{Comparable} -in two ways that sorted by different criteria. There would be no -difficulty doing this in the PuTTY system: simply have your -implementation \cw{struct} contain two (or more) fields of the same -abstraction type. The fields will have different names, which makes it -easy to explicitly specify which one you're returning a pointer to -during up-casting, or which one you're down-casting from using -\cw{container_of}. And then both sets of implementation methods can -recover a pointer to the same containing structure. - -} - -\b Unlike in C++, all objects in PuTTY that use this system are -dynamically allocated. The \q{constructor} functions (whether they're -virtualised across the whole abstraction or specific to each -implementation) always allocate memory and return a pointer to it. The -\q{free} method (our analogue of a destructor) always expects the -input pointer to be dynamically allocated, and frees it. As a result, -client code doesn't need to know how large the implementing object -type is, because it will never need to allocate it (on the stack or -anywhere else). - -\b Unlike in C++, the abstraction's \q{vtable} structure does not only -hold methods that you can call on an instance object. It can also -hold several other kinds of thing: - -\lcont{ - -\b Methods that you can call \e{without} an instance object, given -only the vtable structure identifying a particular implementation of -the trait. You might think of these as \q{static methods}, as in C++, -except that they're \e{virtual} \dash the same code can call the -static method of a different \q{class} given a different vtable -pointer. So they're more like \q{virtual static methods}, which is a -concept C++ doesn't have. An example is the \cw{pubkey_bits} method in -\cw{ssh_keyalg}. - -\b The most important case of a \q{virtual static method} is the -\cw{new} method that allocates and returns a new object. You can think -of it as a \q{virtual constructor} \dash another concept C++ doesn't -have. (However, not all types need one of these: see below.) - -\b The vtable can also contain constant data relevant to the class as -a whole \dash \q{virtual constant data}. For example, a cryptographic -hash function will contain an integer field giving the length of the -output hash, and most crypto primitives will contain a string field -giving the identifier used in the SSH protocol that describes that -primitive. - -The effect of all of this is that you can make other pieces of code -able to use any instance of one of these types, by passing it an -actual vtable as a parameter. For example, the \cw{hash_simple} -function takes an \cw{ssh_hashalg} vtable pointer specifying any hash -algorithm you like, and internally, it creates an object of that type, -uses it, and frees it. In C++, you'd probably do this using a -template, which would mean you had multiple specialisations of -\cw{hash_simple} \dash and then it would be much more difficult to -decide \e{at run time} which one you needed to use. Here, -\cw{hash_simple} is still just one function, and you can decide as -late as you like which vtable to pass to it. - -} - -\b The abstract \e{instance} structure can also contain publicly -visible data fields (this time, usually treated as mutable) which are -common to all implementations of the trait. For example, -\cw{BinaryPacketProtocol} has lots of these. - -\b Not all abstractions of this kind want virtual constructors. It -depends on how different the implementations are. - -\lcont{ - -With a crypto primitive like a hash algorithm, the constructor call -looks the same for every implementing type, so it makes sense to have -a standardised virtual constructor in the vtable and a -\cw{ssh_hash_new} wrapper function which can make an instance of -whatever vtable you pass it. And then you make all the vtable objects -themselves globally visible throughout the source code, so that any -module can call (for example) \cw{ssh_hash_new(&ssh_sha256)}. - -But with other kinds of object, the constructor for each implementing -type has to take a different set of parameters. For example, -implementations of \cw{Socket} are not generally interchangeable at -construction time, because constructing different kinds of socket -require totally different kinds of address parameter. In that -situation, it makes more sense to keep the vtable structure itself -private to the implementing source file, and instead, publish an -ordinary constructing function that allocates and returns an instance -of that particular subtype, taking whatever parameters are appropriate -to that subtype. - -} - -\b If you do have virtual constructors, you can choose whether they -take a vtable pointer as a parameter (as shown above), or an -\e{existing} instance object. In the latter case, they can refer to -the object itself as well as the vtable. For example, you could have a -trait come with a virtual constructor called \q{clone}, meaning -\q{Make a copy of this object, no matter which implementation it is.} - -\b Sometimes, a single vtable structure type can be shared between two -completely different object types, and contain all the methods for -both. For example, \cw{ssh_compression_alg} contains methods to -create, use and free \cw{ssh_compressor} and \cw{ssh_decompressor} -objects, which are not interchangeable \dash but putting their methods -in the same vtable means that it's easy to create a matching pair of -objects that are compatible with each other. - -\b Passing the vtable itself as an argument to the \cw{new} method is -not compulsory: if a given \cw{new} implementation is only used by a -single vtable, then that function can simply hard-code the vtable -pointer that it writes into the object it constructs. But passing the -vtable is more flexible, because it allows a single constructor -function to be shared between multiple slightly different object -types. For example, SHA-384 and SHA-512 share the same \cw{new} method -and the same implementation data type, because they're very nearly the -same hash algorithm \dash but a couple of the other methods in their -vtables are different, because the \q{reset} function has to set up -the initial algorithm state differently, and the \q{digest} method has -to write out a different amount of data. - -\lcont{ - -One practical advantage of having the \cw{myabs_}\e{foo} family of -inline wrapper functions in the header file is that if you change your -mind later about whether the vtable needs to be passed to \cw{new}, -you only have to update the \cw{myabs_new} wrapper, and then the -existing call sites won't need changing. - -} - -\b Another piece of \q{stunt object orientation} made possible by this -scheme is that you can write two vtables that both use the same -structure layout for the implementation object, and have an object -\e{transform from one to the other} part way through its lifetime, by -overwriting its own vtable pointer field. For example, the -\cw{sesschan} type that handles the server side of an SSH terminal -session will sometimes transform in mid-lifetime into an SCP or SFTP -file-transfer channel in this way, at the point where the client sends -an \cq{exec} or \cq{subsystem} request that indicates that that's what -it wants to do with the channel. - -\lcont{ - -This concept would be difficult to arrange in C++. In Rust, it -wouldn't even \e{make sense}, because in Rust, objects implementing a -trait don't even contain a vtable pointer at all \dash instead, the -\q{trait object} type (identifying a specific instance of some -implementation of a given trait) consists of a pair of pointers, one -to the object itself and one to the vtable. In that model, the only -way you could make an existing object turn into a different trait -would be to know where all the pointers to it were stored elsewhere in -the program, and persuade all their owners to rewrite them. - -} - -\b Another stunt you can do is to have a vtable that doesn't have a -corresponding implementation structure at all, because the only -methods implemented in it are the constructors, and they always end up -returning an implementation of some other vtable. For example, some of -PuTTY's crypto primitives have a hardware-accelerated version and a -pure software version, and decide at run time which one to use (based -on whether the CPU they're running on supports the necessary -acceleration instructions). So, for example, there are vtables for -\cw{ssh_sha256_sw} and \cw{ssh_sha256_hw}, each of which has its own -data layout and its own implementations of all the methods; and then -there's a top-level vtable \cw{ssh_sha256}, which only provides the -\q{new} method, and implements it by calling the \q{new} method on one -or other of the subtypes depending on what it finds out about the -machine it's running on. That top-level selector vtable is nearly -always the one used by client code. (Except for the test suite, which -has to instantiate both of the subtypes in order to make sure they -both pass the tests.) - -\lcont{ - -As a result, the top-level selector vtable \cw{ssh_sha256} doesn't -need to implement any method that takes an \cw{ssh_cipher *} -parameter, because no \cw{ssh_cipher} object is ever constructed whose -\cw{vt} field points to \cw{&ssh_sha256}: they all point to one of the -other two full implementation vtables. - -} - -\H{udp-perfection} Do as we say, not as we do - -The current PuTTY code probably does not conform strictly to \e{all} -of the principles listed above. There may be the occasional -SSH-specific piece of code in what should be a backend-independent -module, or the occasional dependence on a non-standard X library -function under Unix. - -This should not be taken as a licence to go ahead and violate the -rules. Where we violate them ourselves, we're not happy about it, -and we would welcome patches that fix any existing problems. Please -try to help us make our code better, not worse! diff --git a/doc/using.but b/doc/using.but deleted file mode 100644 index 5865ac956..000000000 --- a/doc/using.but +++ /dev/null @@ -1,1186 +0,0 @@ -\C{using} Using PuTTY - -This chapter provides a general introduction to some more advanced -features of PuTTY. For extreme detail and reference purposes, -\k{config} is likely to contain more information. - -\H{using-session} During your session - -A lot of PuTTY's complexity and features are in the configuration -panel. Once you have worked your way through that and started -a session, things should be reasonably simple after that. -Nevertheless, there are a few more useful features available. - -\S{using-selection} Copying and pasting text - -\I{copy and paste}Often in a PuTTY session you will find text on -your terminal screen which you want to type in again. Like most -other terminal emulators, PuTTY allows you to copy and paste the -text rather than having to type it again. Also, copy and paste uses -the \I{Windows clipboard}Windows \i{clipboard}, so that you can -paste (for example) URLs into a web browser, or paste from a word -processor or spreadsheet into your terminal session. - -By default, PuTTY's copy and paste works entirely with the \i{mouse}. -(This will be familiar to people who have used \i\c{xterm} on Unix.) -In order to copy text to the clipboard, you just click the \i{left -mouse button} in the \i{terminal window}, and drag to -\I{selecting text}select text. When you let go of the button, the text -is \e{automatically} copied to the clipboard. You do not need to press -\i{Ctrl-C} or \i{Ctrl-Ins}; in fact, if you do press Ctrl-C, PuTTY will -send a Ctrl-C character down your session to the server where it will -probably cause a process to be interrupted. - -Pasting into PuTTY is done using the right button (or the middle mouse -button, if you have a \i{three-button mouse} and have set it up; see -\k{config-mouse}). (Pressing \i{Shift-Ins}, or selecting \q{Paste} -from the \I{right mouse button, with Ctrl}Ctrl+right-click -\i{context menu}, have the same effect.) When -you click the \i{right mouse button}, PuTTY will read whatever is in -the Windows clipboard and paste it into your session. By default, this -behaves \e{exactly} as if the clipboard contents had been typed at the -keyboard; therefore, be careful of pasting formatted text into an -editor that does automatic \i{indenting}, as you may find that the spaces -pasted from the clipboard plus the spaces added by the editor add up -to too many spaces and ruin the formatting. (Some remote applications -can ask PuTTY to identify text that is being pasted, to avoid this -sort of problem; but if your application does not, there is nothing -PuTTY can do to avoid this.) - -If you \i{double-click} the left mouse button, PuTTY will -\I{selecting words}select a whole word. If you double-click, hold -down the second click, and drag the mouse, PuTTY will select a -sequence of whole words. (You can adjust precisely what PuTTY -considers to be part of a word; see \k{config-charclasses}.) -If you \e{triple}-click, or \i{triple-click} and drag, then -PuTTY will \I{selecting lines}select a whole line or sequence of lines. - -If you want to select a \I{rectangular selection}rectangular region -instead of selecting to the end of each line, you can do this by -holding down Alt when you make your selection. You can also -configure rectangular selection to be the default, and then holding -down Alt gives the normal behaviour instead: see -\k{config-rectselect} for details. - -(In some Unix environments, Alt+drag is intercepted by the window -manager. Shift+Alt+drag should work for rectangular selection as -well, so you could try that instead.) - -If you have a \i{middle mouse button}, then you can use it to -\I{adjusting a selection}adjust an existing selection if you -selected something slightly wrong. (If you have configured the -middle mouse button to paste, then the right mouse button does this -instead.) Click the button on the screen, and you can pick up the -nearest end of the selection and drag it to somewhere else. - -If you are running PuTTY itself on Unix (not just using it to connect -to a Unix system from Windows), by default you will likely have to use -similar mouse actions in other applications to paste the text you -copied from PuTTY, and to copy text for pasting into PuTTY; actions -like \i{Ctrl-C} and Ctrl-V will likely not behave as you expect. -\K{config-clipboards} explains why this is, and how you can change the -behaviour. (On Windows there is only a single selection shared with other -applications, so this confusion does not arise.) - -It's possible for the server to ask to \I{mouse reporting}handle mouse -clicks in the PuTTY window itself. If this happens, the \i{mouse pointer} -will turn into an arrow, and using the mouse to copy and paste will only -work if you hold down Shift. See \k{config-features-mouse} and -\k{config-mouseshift} for details of this feature and how to configure -it. - -You can customise much of this behaviour, for instance to enable copy -and paste from the keyboard; see \k{config-selection}. - -\S{using-scrollback} \I{scrollback}Scrolling the screen back - -PuTTY keeps track of text that has scrolled up off the top of the -terminal. So if something appears on the screen that you want to -read, but it scrolls too fast and it's gone by the time you try to -look for it, you can use the \i{scrollbar} on the right side of the -window to look back up the session \i{history} and find it again. - -As well as using the scrollbar, you can also page the scrollback up -and down by pressing \i{Shift-PgUp} and \i{Shift-PgDn}. You can -scroll a line at a time using \i{Ctrl-PgUp} and \i{Ctrl-PgDn}, or -to the top/bottom of the scrollback with \i{Ctrl-Shift-PgUp} and -\i{Ctrl-Shift-PgDn}. These are still available if you configure the -scrollbar to be invisible. - -By default the last 2000 lines scrolled off the top are -preserved for you to look at. You can increase (or decrease) this -value using the configuration box; see \k{config-scrollback}. - -\S{using-sysmenu} The \ii{System menu} - -If you click the left mouse button on the icon in the top left -corner of PuTTY's terminal window, or click the right mouse button -on the title bar, you will see the standard Windows system menu -containing items like Minimise, Move, Size and Close. - -PuTTY's system menu contains extra program features in addition to -the Windows standard options. These extra menu commands are -described below. - -(These options are also available in a \i{context menu} brought up -by holding Ctrl and clicking with the right mouse button anywhere -in the \i{PuTTY window}.) - -\S2{using-eventlog} The PuTTY \i{Event Log} - -If you choose \q{Event Log} from the system menu, a small window -will pop up in which PuTTY logs significant events during the -connection. Most of the events in the log will probably take place -during session startup, but a few can occur at any point in the -session, and one or two occur right at the end. - -You can use the mouse to select one or more lines of the Event Log, -and hit the Copy button to copy them to the \i{clipboard}. If you -are reporting a bug, it's often useful to paste the contents of the -Event Log into your bug report. - -(The Event Log is not the same as the facility to create a log file -of your session; that's described in \k{using-logging}.) - -\S2{using-specials} \ii{Special commands} - -Depending on the protocol used for the current session, there may be -a submenu of \q{special commands}. These are protocol-specific -tokens, such as a \q{break} signal, that can be sent down a -connection in addition to normal data. Their precise effect is usually -up to the server. Currently only Telnet, SSH, and serial connections -have special commands. - -The \q{break} signal can also be invoked from the keyboard with -\i{Ctrl-Break}. - -In an SSH connection, the following \I{SSH special commands}special -commands are available: - -\b \I{IGNORE message, SSH special command}\I{No-op, in SSH}\ii{IGNORE message} - -\lcont{ -Should have no effect. -} - -\b \I{Repeat key exchange, SSH special command}Repeat key exchange - -\lcont{ -Only available in SSH-2. Forces a \i{repeat key exchange} immediately (and -resets associated timers and counters). For more information about -repeat key exchanges, see \k{config-ssh-kex-rekey}. -} - -\b \I{host key cache}Cache new host key type - -\lcont{ -Only available in SSH-2. This submenu appears only if the server has -host keys of a type that PuTTY doesn't already have cached, and so -won't consider. Selecting a key here will allow PuTTY to use that key -now and in future: PuTTY will do a fresh key-exchange with the selected -key, and immediately add that key to its permanent cache (relying on -the host key used at the start of the connection to cross-certify the -new key). That key will be used for the rest of the current session; -it may not actually be used for future sessions, depending on your -preferences (see \k{config-ssh-hostkey-order}). - -Normally, PuTTY will carry on using a host key it already knows, even -if the server offers key formats that PuTTY would otherwise prefer, -to avoid host key prompts. As a result, if you've been using a server -for some years, you may still be using an older key than a new user -would use, due to server upgrades in the meantime. The SSH protocol -unfortunately does not have organised facilities for host key migration -and rollover, but this allows you to \I{host keys, upgrading}manually -upgrade. -} - -\b \I{Break, SSH special command}Break - -\lcont{ -Only available in SSH-2, and only during a session. Optional -extension; may not be supported by server. PuTTY requests the server's -default break length. -} - -\b \I{Signal, SSH special command}Signals (SIGINT, SIGTERM etc) - -\lcont{ -Only available in SSH-2, and only during a session. Sends various -POSIX signals. Not honoured by all servers. -} - -The following \I{Telnet special commands}special commands are -available in Telnet: - -\b \I{Are You There, Telnet special command}Are You There - -\b \I{Break, Telnet special command}Break - -\b \I{Synch, Telnet special command}Synch - -\b \I{Erase Character, Telnet special command}Erase Character - -\lcont{ -PuTTY can also be configured to send this when the Backspace key is -pressed; see \k{config-telnetkey}. -} - -\b \I{Erase Line, Telnet special command}Erase Line - -\b \I{Go Ahead, Telnet special command}Go Ahead - -\b \I{No Operation, Telnet special command}No Operation - -\lcont{ -Should have no effect. -} - -\b \I{Abort Process, Telnet special command}Abort Process - -\b \I{Abort Output, Telnet special command}Abort Output - -\b \I{Interrupt Process, Telnet special command}Interrupt Process - -\lcont{ -PuTTY can also be configured to send this when Ctrl-C is typed; see -\k{config-telnetkey}. -} - -\b \I{Suspend Process, Telnet special command}Suspend Process - -\lcont{ -PuTTY can also be configured to send this when Ctrl-Z is typed; see -\k{config-telnetkey}. -} - -\b \I{End Of Record, Telnet special command}End Of Record - -\b \I{End Of File, Telnet special command}End Of File - -With a serial connection, the only available special command is -\I{Break, serial special command}\q{Break}. - -\S2{using-newsession} Starting new sessions - -PuTTY's system menu provides some shortcut ways to start new -sessions: - -\b Selecting \i{\q{New Session}} will start a completely new -instance of PuTTY, and bring up the configuration box as normal. - -\b Selecting \i{\q{Duplicate Session}} will start a session in a -new window with precisely the same options as your current one - -connecting to the same host using the same protocol, with all the -same terminal settings and everything. - -\b In an inactive window, selecting \i{\q{Restart Session}} will -do the same as \q{Duplicate Session}, but in the current window. - -\b The \i{\q{Saved Sessions} submenu} gives you quick access to any -sets of stored session details you have previously saved. See -\k{config-saving} for details of how to create saved sessions. - -\S2{using-changesettings} \I{settings, changing}Changing your -session settings - -If you select \i{\q{Change Settings}} from the system menu, PuTTY will -display a cut-down version of its initial configuration box. This -allows you to adjust most properties of your current session. You -can change the terminal size, the font, the actions of various -keypresses, the colours, and so on. - -Some of the options that are available in the main configuration box -are not shown in the cut-down Change Settings box. These are usually -options which don't make sense to change in the middle of a session -(for example, you can't switch from SSH to Telnet in mid-session). - -You can save the current settings to a saved session for future use -from this dialog box. See \k{config-saving} for more on saved -sessions. - -\S2{using-copyall} \i{Copy All to Clipboard} - -This system menu option provides a convenient way to copy the whole -contents of the terminal screen (up to the last nonempty line) and -scrollback to the \i{clipboard} in one go. - -\S2{reset-terminal} \I{scrollback, clearing}Clearing and -\I{terminal, resetting}resetting the terminal - -The \i{\q{Clear Scrollback}} option on the system menu tells PuTTY -to discard all the lines of text that have been kept after they -scrolled off the top of the screen. This might be useful, for -example, if you displayed sensitive information and wanted to make -sure nobody could look over your shoulder and see it. (Note that -this only prevents a casual user from using the scrollbar to view -the information; the text is not guaranteed not to still be in -PuTTY's memory.) - -The \i{\q{Reset Terminal}} option causes a full reset of the -\i{terminal emulation}. A VT-series terminal is a complex piece of -software and can easily get into a state where all the text printed -becomes unreadable. (This can happen, for example, if you -accidentally output a binary file to your terminal.) If this -happens, selecting Reset Terminal should sort it out. - -\S2{using-fullscreen} \ii{Full screen} mode - -If you find the title bar on a maximised window to be ugly or -distracting, you can select Full Screen mode to maximise PuTTY -\q{even more}. When you select this, PuTTY will expand to fill the -whole screen and its borders, title bar and scrollbar will -disappear. (You can configure the scrollbar not to disappear in -full-screen mode if you want to keep it; see \k{config-scrollback}.) - -When you are in full-screen mode, you can still access the \i{system -menu} if you click the left mouse button in the \e{extreme} top left -corner of the screen. - -\H{using-logging} Creating a \i{log file} of your \I{session -log}session - -For some purposes you may find you want to log everything that -appears on your screen. You can do this using the \q{Logging} -panel in the configuration box. - -To begin a session log, select \q{Change Settings} from the system -menu and go to the Logging panel. Enter a log file name, and select -a logging mode. (You can log all session output including the -terminal \i{control sequence}s, or you can just log the printable text. -It depends what you want the log for.) Click \q{Apply} and your log -will be started. Later on, you can go back to the Logging panel and -select \q{Logging turned off completely} to stop logging; then PuTTY -will close the log file and you can safely read it. - -See \k{config-logging} for more details and options. - -\H{using-translation} Altering your \i{character set} configuration - -If you find that special characters (\i{accented characters}, for -example, or \i{line-drawing characters}) are not being displayed -correctly in your PuTTY session, it may be that PuTTY is interpreting -the characters sent by the server according to the wrong \e{character -set}. There are a lot of different character sets available, and no -good way for PuTTY to know which to use, so it's entirely possible -for this to happen. - -If you click \q{Change Settings} and look at the \q{Translation} -panel, you should see a large number of character sets which you can -select, and other related options. Now all you need is to find out -which of them you want! (See \k{config-translation} for more -information.) - -\H{using-x-forwarding} Using \i{X11 forwarding} in SSH - -The SSH protocol has the ability to securely forward X Window System -\i{graphical applications} over your encrypted SSH connection, so that -you can run an application on the SSH server machine and have it put -its windows up on your local machine without sending any X network -traffic in the clear. - -In order to use this feature, you will need an X display server for -your Windows machine, such as Cygwin/X, X-Win32, or Exceed. This will probably -install itself as display number 0 on your local machine; if it -doesn't, the manual for the \i{X server} should tell you what it -does do. - -You should then tick the \q{Enable X11 forwarding} box in the -X11 panel (see \k{config-ssh-x11}) before starting your SSH -session. The \i{\q{X display location}} box is blank by default, which -means that PuTTY will try to use a sensible default such as \c{:0}, -which is the usual display location where your X server will be -installed. If that needs changing, then change it. - -Now you should be able to log in to the SSH server as normal. To -check that X forwarding has been successfully negotiated during -connection startup, you can check the PuTTY Event Log (see -\k{using-eventlog}). It should say something like this: - -\c 2001-12-05 17:22:01 Requesting X11 forwarding -\c 2001-12-05 17:22:02 X11 forwarding enabled - -If the remote system is Unix or Unix-like, you should also be able -to see that the \i{\c{DISPLAY} environment variable} has been set to -point at display 10 or above on the SSH server machine itself: - -\c fred@unixbox:~$ echo $DISPLAY -\c unixbox:10.0 - -If this works, you should then be able to run X applications in the -remote session and have them display their windows on your PC. - -For more options relating to X11 forwarding, see \k{config-ssh-x11}. - -\H{using-port-forwarding} Using \i{port forwarding} in SSH - -The SSH protocol has the ability to forward arbitrary \I{network -connection}network (TCP) connections over your encrypted SSH -connection, to avoid the network traffic being sent in clear. For -example, you could use this to connect from your home computer to a -\i{POP-3} server on a remote machine without your POP-3 password being -visible to network sniffers. - -In order to use port forwarding to \I{local port forwarding}connect -from your local machine to a port on a remote server, you need to: - -\b Choose a \i{port number} on your local machine where PuTTY should -listen for incoming connections. There are likely to be plenty of -unused port numbers above 3000. (You can also use a local loopback -address here; see below for more details.) - -\b Now, before you start your SSH connection, go to the Tunnels -panel (see \k{config-ssh-portfwd}). Make sure the \q{Local} radio -button is set. Enter the local port number into the \q{Source port} -box. Enter the destination host name and port number into the -\q{Destination} box, separated by a colon (for example, -\c{popserver.example.com:110} to connect to a POP-3 server). - -\b Now click the \q{Add} button. The details of your port forwarding -should appear in the list box. - -Now start your session and log in. (Port forwarding will not be -enabled until after you have logged in; otherwise it would be easy -to perform completely anonymous network attacks, and gain access to -anyone's virtual private network.) To check that PuTTY has set up -the port forwarding correctly, you can look at the PuTTY Event Log -(see \k{using-eventlog}). It should say something like this: - -\c 2001-12-05 17:22:10 Local port 3110 forwarding to -\c popserver.example.com:110 - -Now if you connect to the source port number on your local PC, you -should find that it answers you exactly as if it were the service -running on the destination machine. So in this example, you could -then configure an e-mail client to use \c{localhost:3110} as a POP-3 -server instead of \c{popserver.example.com:110}. (Of course, the -forwarding will stop happening when your PuTTY session closes down.) - -You can also forward ports in the other direction: arrange for a -particular port number on the \e{server} machine to be \I{remote -port forwarding}forwarded back to your PC as a connection to a -service on your PC or near it. -To do this, just select the \q{Remote} radio button instead of the -\q{Local} one. The \q{Source port} box will now specify a port -number on the \e{server} (note that most servers will not allow you -to use \I{privileged port}port numbers under 1024 for this purpose). - -An alternative way to forward local connections to remote hosts is -to use \I{dynamic port forwarding}dynamic SOCKS proxying. In this -mode, PuTTY acts as a SOCKS server, which SOCKS-aware programs can -connect to and open forwarded connections to the destination of their -choice, so this can be an alternative to long lists of static -forwardings. To use this mode, you will need to select the \q{Dynamic} -radio button instead of \q{Local}, and then you should not enter -anything into the \q{Destination} box (it will be ignored). PuTTY will -then listen for SOCKS connections on the port you have specified. -Most \i{web browsers} can be configured to connect to this SOCKS proxy -service; also, you can forward other PuTTY connections through it by -setting up the Proxy control panel (see \k{config-proxy} for details). - -The source port for a forwarded connection usually does not accept -connections from any machine except the \I{localhost}SSH client or -server machine itself (for local and remote forwardings respectively). -There are controls in the Tunnels panel to change this: - -\b The \q{Local ports accept connections from other hosts} option -allows you to set up local-to-remote port forwardings (including -dynamic port forwardings) in such a way that machines other than -your client PC can connect to the forwarded port. - -\b The \q{Remote ports do the same} option does the same thing for -remote-to-local port forwardings (so that machines other than the -SSH server machine can connect to the forwarded port.) Note that -this feature is only available in the SSH-2 protocol, and not all -SSH-2 servers honour it (in \i{OpenSSH}, for example, it's usually -disabled by default). - -You can also specify an \i{IP address} to \I{listen address}listen -on. Typically a Windows machine can be asked to listen on any single -IP address in the \cw{127.*.*.*} range, and all of these are -\i{loopback address}es available only to the local machine. So if -you forward (for example) \c{127.0.0.5:79} to a remote machine's -\i\cw{finger} port, then you should be able to run commands such as -\c{finger fred@127.0.0.5}. -This can be useful if the program connecting to the forwarded port -doesn't allow you to change the port number it uses. This feature is -available for local-to-remote forwarded ports; SSH-1 is unable to -support it for remote-to-local ports, while SSH-2 can support it in -theory but servers will not necessarily cooperate. - -(Note that if you're using Windows XP Service Pack 2, you may need -to obtain a fix from Microsoft in order to use addresses like -\cw{127.0.0.5} - see \k{faq-alternate-localhost}.) - -For more options relating to port forwarding, see -\k{config-ssh-portfwd}. - -If the connection you are forwarding over SSH is itself a second SSH -connection made by another copy of PuTTY, you might find the -\q{logical host name} configuration option useful to warn PuTTY of -which host key it should be expecting. See \k{config-loghost} for -details of this. - -\H{using-serial} Connecting to a local serial line - -PuTTY can connect directly to a local serial line as an alternative -to making a network connection. In this mode, text typed into the -PuTTY window will be sent straight out of your computer's serial -port, and data received through that port will be displayed in the -PuTTY window. You might use this mode, for example, if your serial -port is connected to another computer which has a serial connection. - -To make a connection of this type, simply select \q{Serial} from the -\q{Connection type} radio buttons on the \q{Session} configuration -panel (see \k{config-hostname}). The \q{Host Name} and \q{Port} -boxes will transform into \q{Serial line} and \q{Speed}, allowing -you to specify which serial line to use (if your computer has more -than one) and what speed (baud rate) to use when transferring data. -For further configuration options (data bits, stop bits, parity, -flow control), you can use the \q{Serial} configuration panel (see -\k{config-serial}). - -After you start up PuTTY in serial mode, you might find that you -have to make the first move, by sending some data out of the serial -line in order to notify the device at the other end that someone is -there for it to talk to. This probably depends on the device. If you -start up a PuTTY serial session and nothing appears in the window, -try pressing Return a few times and see if that helps. - -A serial line provides no well defined means for one end of the -connection to notify the other that the connection is finished. -Therefore, PuTTY in serial mode will remain connected until you -close the window using the close button. - -\H{using-rawprot} Making \i{raw TCP connections} - -A lot of \I{debugging Internet protocols}Internet protocols are -composed of commands and responses in plain text. For example, -\i{SMTP} (the protocol used to transfer e-mail), \i{NNTP} (the -protocol used to transfer Usenet news), and \i{HTTP} (the protocol -used to serve Web pages) all consist of commands in readable plain -text. - -Sometimes it can be useful to connect directly to one of these -services and speak the protocol \q{by hand}, by typing protocol -commands and watching the responses. On Unix machines, you can do -this using the system's \c{telnet} command to connect to the right -port number. For example, \c{telnet mailserver.example.com 25} might -enable you to talk directly to the SMTP service running on a mail -server. - -Although the Unix \c{telnet} program provides this functionality, -the protocol being used is not really Telnet. Really there is no -actual protocol at all; the bytes sent down the connection are -exactly the ones you type, and the bytes shown on the screen are -exactly the ones sent by the server. Unix \c{telnet} will attempt to -detect or guess whether the service it is talking to is a real -Telnet service or not; PuTTY prefers to be told for certain. - -In order to make a debugging connection to a service of this type, -you simply select the fourth protocol name, \I{\q{Raw} -protocol}\q{Raw}, from the \q{Protocol} buttons in the \q{Session} -configuration panel. (See \k{config-hostname}.) You can then enter a -host name and a port number, and make the connection. - -\H{using-telnet} Connecting using the \i{Telnet} protocol - -PuTTY can use the Telnet protocol to connect to a server. - -Telnet was perhaps the most popular remote login protocol before SSH -was introduced. It was general enough to be used by multiple server -operating systems (Unix and VMS in particular), and supported many -optional protocol extensions providing extra support for particular -server features. - -Unlike SSH, Telnet runs over an unsecured network connection, so it is -a very bad idea to use it over the hostile Internet (though it is -still used to some extent as of 2020). - -\H{using-rlogin} Connecting using the \i{Rlogin} protocol - -PuTTY can use the Rlogin protocol to connect to a server. - -Rlogin was similar to Telnet in concept, but more focused on -connections between Unix machines. It supported a feature for -passwordless login, based on use of \q{privileged ports} (ports with -numbers below 1024, which Unix traditionally does not allow users -other than \cw{root} to allocate). Ultimately, based on the server -trusting that the client's IP address was owned by the Unix machine it -claimed to be, and that that machine would guard its privileged ports -appropriately. - -Like Telnet, Rlogin runs over an unsecured network connection. - -\H{using-supdup} Connecting using the \i{SUPDUP} protocol - -PuTTY can use the SUPDUP protocol to connect to a server. - -SUPDUP is a login protocol used mainly by PDP-10 and Lisp machines -during the period 1975-1990. Like Telnet and Rlogin, it is unsecured, -so modern systems almost never support it. - -To make a connection of this type, select \q{SUPDUP} from the -\q{Connection type} radio buttons on the \q{Session} panel (see -\k{config-hostname}). For further configuration options (character -set, more processing, scrolling), you can use the \q{SUPDUP} -configuration panel (see \k{config-supdup}). - -In SUPDUP, terminal emulation is more integrated with the network -protocol than in other protocols such as SSH. The SUPDUP protocol can -thus only be used with PuTTY proper, not with the command-line tool -Plink. - -The SUPDUP protocol does not support changing the terminal dimensions, -so this capability is disabled during a SUPDUP session. - -SUPDUP provides no well defined means for one end of the connection to -notify the other that the connection is finished. Therefore, PuTTY in -SUPDUP mode will remain connected until you close the window using the -close button. - -\H{using-cmdline} The PuTTY command line - -PuTTY can be made to do various things without user intervention by -supplying \i{command-line arguments} (e.g., from a \i{command prompt -window}, or a \i{Windows shortcut}). - -\S{using-cmdline-session} Starting a session from the command line - -\I\c{-ssh}\I\c{-ssh-connection}\I\c{-telnet}\I\c{-rlogin}\I\c{-supdup}\I\c{-raw}\I\c{-serial}These -options allow you to bypass the configuration window and launch -straight into a session. - -To start a connection to a server called \c{host}: - -\c putty.exe [-ssh | -ssh-connection | -telnet | -rlogin | -supdup | -raw] [user@]host - -If this syntax is used, settings are taken from the \i{Default Settings} -(see \k{config-saving}); \c{user} overrides these settings if -supplied. Also, you can specify a protocol, which will override the -default protocol (see \k{using-cmdline-protocol}). - -For telnet sessions, the following alternative syntax is supported -(this makes PuTTY suitable for use as a URL handler for \i{telnet -URLs} in \i{web browsers}): - -\c putty.exe telnet://host[:port]/ - -To start a connection to a serial port, e.g. COM1: - -\c putty.exe -serial com1 - -In order to start an existing saved session called \c{sessionname}, -use the \c{-load} option (described in \k{using-cmdline-load}). - -\c putty.exe -load "session name" - -\S{using-cleanup} \i\c{-cleanup} - -If invoked with the \c{-cleanup} option, rather than running as -normal, PuTTY will remove its \I{removing registry entries}registry -entries and \i{random seed file} from the local machine (after -confirming with the user). It will also attempt to remove information -about recently launched sessions stored in the \q{jump list} on -Windows 7 and up. - -Note that on \i{multi-user systems}, \c{-cleanup} only removes -registry entries and files associated with the currently logged-in -user. - -\S{using-general-opts} Standard command-line options - -PuTTY and its associated tools support a range of command-line -options, most of which are consistent across all the tools. This -section lists the available options in all tools. Options which are -specific to a particular tool are covered in the chapter about that -tool. - -\S2{using-cmdline-load} \i\c{-load}: load a saved session - -\I{saved sessions, loading from command line}The \c{-load} option -causes PuTTY to load configuration details out of a saved session. -If these details include a host name, then this option is all you -need to make PuTTY start a session. - -You need double quotes around the session name if it contains spaces. - -If you want to create a \i{Windows shortcut} to start a PuTTY saved -session, this is the option you should use: your shortcut should -call something like - -\c d:\path\to\putty.exe -load "my session" - -(Note that PuTTY itself supports an alternative form of this option, -for backwards compatibility. If you execute \i\c{putty @sessionname} -it will have the same effect as \c{putty -load "sessionname"}. With -the \c{@} form, no double quotes are required, and the \c{@} sign -must be the very first thing on the command line. This form of the -option is deprecated.) - -\S2{using-cmdline-protocol} Selecting a protocol: \c{-ssh}, -\c{-ssh-connection}, \c{-telnet}, \c{-rlogin}, \c{-supdup}, -\c{-raw}, \c{-serial} - -To choose which protocol you want to connect with, you can use one -of these options: - -\b \i\c{-ssh} selects the SSH protocol. - -\b \i\c{-ssh-connection} selects the bare ssh-connection protocol. -(This is only useful in specialised circumstances; see \k{config-psusan} -for more information.) - -\b \i\c{-telnet} selects the Telnet protocol. - -\b \i\c{-rlogin} selects the Rlogin protocol. - -\b \i\c{-supdup} selects the SUPDUP protocol. - -\b \i\c{-raw} selects the raw protocol. - -\b \i\c{-serial} selects a serial connection. - -Most of these options are not available in the file transfer tools -PSCP and PSFTP (which only work with the SSH protocol and the bare -ssh-connection protocol). - -These options are equivalent to the \i{protocol selection} buttons -in the Session panel of the PuTTY configuration box (see -\k{config-hostname}). - -\S2{using-cmdline-v} \i\c{-v}: increase verbosity - -\I{verbose mode}Most of the PuTTY tools can be made to tell you more -about what they are doing by supplying the \c{-v} option. If you are -having trouble when making a connection, or you're simply curious, -you can turn this switch on and hope to find out more about what is -happening. - -\S2{using-cmdline-l} \i\c{-l}: specify a \i{login name} - -You can specify the user name to log in as on the remote server -using the \c{-l} option. For example, \c{plink login.example.com -l -fred}. - -These options are equivalent to the username selection box in the -Connection panel of the PuTTY configuration box (see -\k{config-username}). - -\S2{using-cmdline-portfwd} \I{-L-upper}\c{-L}, \I{-R-upper}\c{-R} -and \I{-D-upper}\c{-D}: set up \i{port forwardings} - -As well as setting up port forwardings in the PuTTY configuration -(see \k{config-ssh-portfwd}), you can also set up forwardings on the -command line. The command-line options work just like the ones in -Unix \c{ssh} programs. - -To \I{local port forwarding}forward a local port (say 5110) to a -remote destination (say \cw{popserver.example.com} port 110), you -can write something like one of these: - -\c putty -L 5110:popserver.example.com:110 -load mysession -\c plink mysession -L 5110:popserver.example.com:110 - -To forward a \I{remote port forwarding}remote port to a local -destination, just use the \c{-R} option instead of \c{-L}: - -\c putty -R 5023:mytelnetserver.myhouse.org:23 -load mysession -\c plink mysession -R 5023:mytelnetserver.myhouse.org:23 - -To \I{listen address}specify an IP address for the listening end of the -tunnel, prepend it to the argument: - -\c plink -L 127.0.0.5:23:localhost:23 myhost - -To set up \I{dynamic port forwarding}SOCKS-based dynamic port -forwarding on a local port, use the \c{-D} option. For this one you -only have to pass the port number: - -\c putty -D 4096 -load mysession - -For general information on port forwarding, see -\k{using-port-forwarding}. - -These options are not available in the file transfer tools PSCP and -PSFTP. - -\S2{using-cmdline-m} \i\c{-m}: \I{reading commands from a file}read -a remote command or script from a file - -The \i\c{-m} option performs a similar function to the \q{\ii{Remote -command}} box in the SSH panel of the PuTTY configuration box (see -\k{config-command}). However, the \c{-m} option expects to be given -a local file name, and it will read a command from that file. - -With some servers (particularly Unix systems), you can even put -multiple lines in this file and execute more than one command in -sequence, or a whole shell script; but this is arguably an abuse, and -cannot be expected to work on all servers. In particular, it is known -\e{not} to work with certain \q{embedded} servers, such as \i{Cisco} -routers. - -This option is not available in the file transfer tools PSCP and -PSFTP. - -\S2{using-cmdline-p} \I{-P-upper}\c{-P}: specify a \i{port number} - -The \c{-P} option is used to specify the port number to connect to. If -you have a Telnet server running on port 9696 of a machine instead of -port 23, for example: - -\c putty -telnet -P 9696 host.name -\c plink -telnet -P 9696 host.name - -(Note that this option is more useful in Plink than in PuTTY, -because in PuTTY you can write \c{putty -telnet host.name 9696} in -any case.) - -This option is equivalent to the port number control in the Session -panel of the PuTTY configuration box (see \k{config-hostname}). - -\S2{using-cmdline-pw} \i\c{-pwfile} and \i\c{-pw}: specify a \i{password} - -A simple way to automate a remote login is to supply your password -on the command line. - -The \c{-pwfile} option takes a file name as an argument. The first -line of text in that file will be used as your password. - -The \c{-pw} option takes the password itself as an argument. This is -\s{NOT SECURE} if anybody else uses the same computer, because the -whole command line (including the password) is likely to show up if -another user lists the running processes. \c{-pw} is retained for -backwards compatibility only; you should use \c{-pwfile} instead. - -Note that these options only work when you are using the SSH protocol. -Due to fundamental limitations of Telnet, Rlogin, and SUPDUP, these -protocols do not support automated password authentication. - -\S2{using-cmdline-agentauth} \i\c{-agent} and \i\c{-noagent}: -control use of Pageant for authentication - -The \c{-agent} option turns on SSH authentication using Pageant, and -\c{-noagent} turns it off. These options are only meaningful if you -are using SSH. - -See \k{pageant} for general information on \i{Pageant}. - -These options are equivalent to the agent authentication checkbox in -the Auth panel of the PuTTY configuration box (see -\k{config-ssh-tryagent}). - -\S2{using-cmdline-agent} \I{-A-upper}\c{-A} and \i\c{-a}: control \i{agent -forwarding} - -The \c{-A} option turns on SSH agent forwarding, and \c{-a} turns it -off. These options are only meaningful if you are using SSH. - -See \k{pageant} for general information on \i{Pageant}, and -\k{pageant-forward} for information on agent forwarding. Note that -there is a security risk involved with enabling this option; see -\k{pageant-security} for details. - -These options are equivalent to the agent forwarding checkbox in the -Auth panel of the PuTTY configuration box (see \k{config-ssh-agentfwd}). - -These options are not available in the file transfer tools PSCP and -PSFTP. - -\S2{using-cmdline-x11} \I{-X-upper}\c{-X} and \i\c{-x}: control \i{X11 -forwarding} - -The \c{-X} option turns on X11 forwarding in SSH, and \c{-x} turns -it off. These options are only meaningful if you are using SSH. - -For information on X11 forwarding, see \k{using-x-forwarding}. - -These options are equivalent to the X11 forwarding checkbox in the -X11 panel of the PuTTY configuration box (see \k{config-ssh-x11}). - -These options are not available in the file transfer tools PSCP and -PSFTP. - -\S2{using-cmdline-pty} \i\c{-t} and \I{-T-upper}\c{-T}: control -\i{pseudo-terminal allocation} - -The \c{-t} option ensures PuTTY attempts to allocate a -pseudo-terminal at the server, and \c{-T} stops it from allocating -one. These options are only meaningful if you are using SSH. - -These options are equivalent to the \q{Don't allocate a -pseudo-terminal} checkbox in the SSH panel of the PuTTY -configuration box (see \k{config-ssh-pty}). - -These options are not available in the file transfer tools PSCP and -PSFTP. - -\S2{using-cmdline-noshell} \I{-N-upper}\c{-N}: suppress starting a -\I{suppressing remote shell}shell or command - -The \c{-N} option prevents PuTTY from attempting to start a shell or -command on the remote server. You might want to use this option if -you are only using the SSH connection for port forwarding, and your -user account on the server does not have the ability to run a shell. - -This feature is only available in SSH protocol version 2 (since the -version 1 protocol assumes you will always want to run a shell). - -This option is equivalent to the \q{Don't start a shell or command -at all} checkbox in the SSH panel of the PuTTY configuration box -(see \k{config-ssh-noshell}). - -This option is not available in the file transfer tools PSCP and -PSFTP. - -\S2{using-cmdline-ncmode} \I{-nc}\c{-nc}: make a \i{remote network -connection} in place of a remote shell or command - -The \c{-nc} option prevents Plink (or PuTTY) from attempting to -start a shell or command on the remote server. Instead, it will -instruct the remote server to open a network connection to a host -name and port number specified by you, and treat that network -connection as if it were the main session. - -You specify a host and port as an argument to the \c{-nc} option, -with a colon separating the host name from the port number, like -this: - -\c plink host1.example.com -nc host2.example.com:1234 - -This can be useful if you're trying to make a connection to a target -host which you can only reach by SSH forwarding through a proxy host. -One way to do this would be to have an existing SSH connection to the -proxy host, with a port forwarding, but if you prefer to have the -connection started on demand as needed, then this approach can also -work. - -However, this does depend on the program \e{using} the proxy being -able to run a subprocess in place of making a network connection. -PuTTY itself can do this using the \q{Local} proxy type, but there's a -built-in more flexible way using the \q{SSH} proxy type. (See -\k{config-proxy-type} for a description of both.) So this feature is -probably most useful with another client program as the end user. - -This feature is only available in SSH protocol version 2 (since the -version 1 protocol assumes you will always want to run a shell). It -is not available in the file transfer tools PSCP and PSFTP. It is -available in PuTTY itself, although it is unlikely to be very useful -in any tool other than Plink. Also, \c{-nc} uses the same server -functionality as port forwarding, so it will not work if your server -administrator has disabled port forwarding. - -(The option is named \c{-nc} after the Unix program -\W{http://www.vulnwatch.org/netcat/}\c{nc}, short for \q{netcat}. -The command \cq{plink host1 -nc host2:port} is very similar in -functionality to \cq{plink host1 nc host2 port}, which invokes -\c{nc} on the server and tells it to connect to the specified -destination. However, Plink's built-in \c{-nc} option does not -depend on the \c{nc} program being installed on the server.) - -\S2{using-cmdline-compress} \I{-C-upper}\c{-C}: enable \i{compression} - -The \c{-C} option enables compression of the data sent across the -network. This option is only meaningful if you are using SSH. - -This option is equivalent to the \q{Enable compression} checkbox in -the SSH panel of the PuTTY configuration box (see -\k{config-ssh-comp}). - -\S2{using-cmdline-sshprot} \i\c{-1} and \i\c{-2}: specify an \i{SSH -protocol version} - -The \c{-1} and \c{-2} options force PuTTY to use version \I{SSH-1}1 -or version \I{SSH-2}2 of the SSH protocol. These options are only -meaningful if you are using SSH. - -These options are equivalent to selecting the SSH protocol version in -the SSH panel of the PuTTY configuration box (see \k{config-ssh-prot}). - -\S2{using-cmdline-ipversion} \i\c{-4} and \i\c{-6}: specify an -\i{Internet protocol version} - -The \c{-4} and \c{-6} options force PuTTY to use the older Internet -protocol \i{IPv4} or the newer \i{IPv6} for most outgoing -connections. - -These options are equivalent to selecting your preferred Internet -protocol version as \q{IPv4} or \q{IPv6} in the Connection panel of -the PuTTY configuration box (see \k{config-address-family}). - -\S2{using-cmdline-identity} \i\c{-i}: specify an SSH \i{private key} - -The \c{-i} option allows you to specify the name of a private key -file in \c{*.\i{PPK}} format which PuTTY will use to authenticate with the -server. This option is only meaningful if you are using SSH. - -If you are using Pageant, you can also specify a \e{public} key file -(in RFC 4716 or OpenSSH format) to identify a specific key file to use. -(This won't work if you're not running Pageant, of course.) - -For general information on \i{public-key authentication}, see -\k{pubkey}. - -This option is equivalent to the \q{Private key file for -authentication} box in the Auth panel of the PuTTY configuration box -(see \k{config-ssh-privkey}). - -\S2{using-cmdline-cert} \i\c{-cert}: specify an SSH \i{certificate} - -The \c{-cert} option allows you to specify the name of a certificate -file containing a signed version of your public key. If you specify -this option, PuTTY will present that certificate in place of the plain -public key, whenever it tries to authenticate with a key that matches. -(This applies whether the key is stored in Pageant or loaded directly -from a file by PuTTY.) - -This option is equivalent to the \q{Certificate to use with the -private key} box in the Auth panel of the PuTTY configuration box (see -\k{config-ssh-cert}). - -\S2{using-cmdline-no-trivial-auth} \i\c{-no-trivial-auth}: disconnect -if SSH authentication succeeds trivially - -This option causes PuTTY to abandon an SSH session if the server -accepts authentication without ever having asked for any kind of -password or signature or token. - -See \k{config-ssh-notrivialauth} for why you might want this. - -\S2{using-cmdline-loghost} \i\c{-loghost}: specify a \i{logical host -name} - -This option overrides PuTTY's normal SSH \I{host key cache}host key -caching policy by telling it the name of the host you expect your -connection to end up at (in cases where this differs from the location -PuTTY thinks it's connecting to). It can be a plain host name, or a -host name followed by a colon and a port number. See -\k{config-loghost} for more detail on this. - -\S2{using-cmdline-hostkey} \i\c{-hostkey}: \I{manually configuring -host keys}manually specify an expected host key - -This option overrides PuTTY's normal SSH \I{host key cache}host key -caching policy by telling it exactly what host key to expect, which -can be useful if the normal automatic host key store in the Registry -is unavailable. The argument to this option should be either a host key -fingerprint, or an SSH-2 public key blob. See -\k{config-ssh-kex-manual-hostkeys} for more information. - -You can specify this option more than once if you want to configure -more than one key to be accepted. - -\S2{using-cmdline-pgpfp} \i\c{-pgpfp}: display \i{PGP key fingerprint}s - -This option causes the PuTTY tools not to run as normal, but instead -to display the fingerprints of the PuTTY PGP Master Keys, in order to -aid with \i{verifying new versions}. See \k{pgpkeys} for more information. - -\S2{using-cmdline-sercfg} \i\c{-sercfg}: specify serial port -\i{configuration} - -This option specifies the configuration parameters for the serial -port (baud rate, stop bits etc). Its argument is interpreted as a -comma-separated list of configuration options, which can be as -follows: - -\b Any single digit from 5 to 9 sets the number of data bits. - -\b \cq{1}, \cq{1.5} or \cq{2} sets the number of stop bits. - -\b Any other numeric string is interpreted as a baud rate. - -\b A single lower-case letter specifies the parity: \cq{n} for none, -\cq{o} for odd, \cq{e} for even, \cq{m} for mark and \cq{s} for space. - -\b A single upper-case letter specifies the flow control: \cq{N} for -none, \cq{X} for XON/XOFF, \cq{R} for RTS/CTS and \cq{D} for -DSR/DTR. - -For example, \cq{-sercfg 19200,8,n,1,N} denotes a baud rate of -19200, 8 data bits, no parity, 1 stop bit and no flow control. - -\S2{using-cmdline-sshlog} \i\c{-sessionlog}, \i\c{-sshlog}, -\i\c{-sshrawlog}: enable session logging - -These options cause the PuTTY network tools to write out a \i{log -file}. Each of them expects a file name as an argument, e.g. -\cq{-sshlog putty.log} causes an SSH packet log to be written to a -file called \cq{putty.log}. The three different options select -different logging modes, all available from the GUI too: - -\b \c{-sessionlog} selects \q{All session output} logging mode. - -\b \c{-sshlog} selects \q{SSH packets} logging mode. - -\b \c{-sshrawlog} selects \q{SSH packets and raw data} logging mode. - -For more information on logging configuration, see \k{config-logging}. - -\S2{using-cmdline-logfileexists} \i\c{-logoverwrite}, \i\c{-logappend}: -control behaviour with existing log file - -If logging has been enabled (in the saved configuration, or by another -command-line option), and the specified log file already exists, these -options tell the PuTTY network tools what to do so that they don't -have to ask the user. See \k{config-logfileexists} for details. - -\S2{using-cmdline-proxycmd} \i\c{-proxycmd}: specify a local proxy -command - -This option enables PuTTY's mode for running a \I{Local proxy}command -on the local machine and using it as a proxy for the network -connection. It expects a shell command string as an argument. - -See \k{config-proxy-type} for more information on this, and on other -proxy settings. In particular, note that since the special sequences -described there are understood in the argument string, literal -backslashes must be doubled (if you want \c{\\} in your command, you -must put \c{\\\\} on the command line). - -\S2{using-cmdline-restrict-acl} \i\c{-restrict-acl}: restrict the -\i{Windows process ACL} - -This option (on Windows only) causes PuTTY (or another PuTTY tool) to -try to lock down the operating system's access control on its own -process. If this succeeds, it should present an extra obstacle to -malware that has managed to run under the same user id as the PuTTY -process, by preventing it from attaching to PuTTY using the same -interfaces debuggers use and either reading sensitive information out -of its memory or hijacking its network session. - -This option is not enabled by default, because this form of -interaction between Windows programs has many legitimate uses, -including accessibility software such as screen readers. Also, it -cannot provide full security against this class of attack in any case, -because PuTTY can only lock down its own ACL \e{after} it has started -up, and malware could still get in if it attacks the process between -startup and lockdown. So it trades away noticeable convenience, and -delivers less real security than you might want. However, if you do -want to make that tradeoff anyway, the option is available. - -A PuTTY process started with \c{-restrict-acl} will pass that on to -any processes started with Duplicate Session, New Session etc. -(However, if you're invoking PuTTY tools explicitly, for instance as a -proxy command, you'll need to arrange to pass them the -\c{-restrict-acl} option yourself, if that's what you want.) - -If Pageant is started with the \c{-restrict-acl} option, and you use -it to launch a PuTTY session from its \ii{System Tray} submenu, then -Pageant will \e{not} default to starting the PuTTY subprocess with a -restricted ACL. This is because PuTTY is more likely to suffer reduced -functionality as a result of restricted ACLs (e.g. screen reader -software will have a greater need to interact with it), whereas -Pageant stores the more critical information (hence benefits more from -the extra protection), so it's reasonable to want to run Pageant but -not PuTTY with the ACL restrictions. You can force Pageant to start -subsidiary PuTTY processes with a restricted ACL if you also pass the -\i\c{-restrict-putty-acl} option. - -\S2{using-cmdline-host-ca} \i{\c{-host-ca}}: launch the -\I{certificate}host CA configuration - -If you start PuTTY with the \c{-host-ca} option, it will not launch a -session at all. Instead, it will just display the configuration dialog -box for host certification authorities, as described in -\k{config-ssh-kex-cert}. When you dismiss that dialog box, PuTTY will -terminate. diff --git a/doc/vids.but b/doc/vids.but deleted file mode 100644 index 1d685a421..000000000 --- a/doc/vids.but +++ /dev/null @@ -1,4 +0,0 @@ -\# Fallback versionid for use when the build system hasn't provided a -better one. - -\versionid no version information available diff --git a/errsock.c b/errsock.c deleted file mode 100644 index 6f26629d2..000000000 --- a/errsock.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * A dummy Socket implementation which just holds an error message. - */ - -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" - -typedef struct { - char *error; - Plug *plug; - - Socket sock; -} ErrorSocket; - -static Plug *sk_error_plug(Socket *s, Plug *p) -{ - ErrorSocket *es = container_of(s, ErrorSocket, sock); - Plug *ret = es->plug; - if (p) - es->plug = p; - return ret; -} - -static void sk_error_close(Socket *s) -{ - ErrorSocket *es = container_of(s, ErrorSocket, sock); - - sfree(es->error); - sfree(es); -} - -static const char *sk_error_socket_error(Socket *s) -{ - ErrorSocket *es = container_of(s, ErrorSocket, sock); - return es->error; -} - -static SocketPeerInfo *sk_error_peer_info(Socket *s) -{ - return NULL; -} - -static const SocketVtable ErrorSocket_sockvt = { - .plug = sk_error_plug, - .close = sk_error_close, - .socket_error = sk_error_socket_error, - .peer_info = sk_error_peer_info, - /* other methods are NULL */ -}; - -Socket *new_error_socket_consume_string(Plug *plug, char *errmsg) -{ - ErrorSocket *es = snew(ErrorSocket); - es->sock.vt = &ErrorSocket_sockvt; - es->plug = plug; - es->error = errmsg; - return &es->sock; -} - -Socket *new_error_socket_fmt(Plug *plug, const char *fmt, ...) -{ - va_list ap; - char *msg; - - va_start(ap, fmt); - msg = dupvprintf(fmt, ap); - va_end(ap); - - return new_error_socket_consume_string(plug, msg); -} diff --git a/icons/cicon.pl b/icons/cicon.pl deleted file mode 100644 index dd635ce13..000000000 --- a/icons/cicon.pl +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/perl - -# Given a list of input PNGs, create a C source file containing a -# const array of XPMs, named by a given C identifier. - -$id = shift @ARGV; -$k = 0; -@xpms = (); -foreach $f (@ARGV) { - # XPM format is generated directly by ImageMagick, so that's easy - # enough. We just have to adjust the declaration line so that it - # has the right name, linkage and storage class. - @lines = (); - open XPM, "convert $f xpm:- |"; - push @lines, $_ while ; - close XPM; - die "XPM from $f in unexpected format\n" unless $lines[1] =~ /^static.*\{$/; - $lines[1] = "static const char *const ${id}_$k"."[] = {\n"; - $k++; - push @xpms, @lines, "\n"; -} - -# Now output. -foreach $line (@xpms) { print $line; } -print "const char *const *const ${id}[] = {\n"; -for ($i = 0; $i < $k; $i++) { print " ${id}_$i,\n"; } -print "};\n"; -print "const int n_${id} = $k;\n"; diff --git a/icons/icon.pl b/icons/icon.pl deleted file mode 100644 index 4275660f1..000000000 --- a/icons/icon.pl +++ /dev/null @@ -1,270 +0,0 @@ -#!/usr/bin/perl - -# Take a collection of input image files and convert them into a -# multi-resolution Windows .ICO icon file. -# -# The input images can be treated as having four different colour -# depths: -# -# - 24-bit true colour -# - 8-bit with custom palette -# - 4-bit using the Windows 16-colour palette (see comment below -# for details) -# - 1-bit using black and white only. -# -# The images can be supplied in any input format acceptable to -# ImageMagick, but their actual colour usage must already be -# appropriate for the specified mode; this script will not do any -# substantive conversion. So if an image intended to be used in 4- -# or 1-bit mode contains any colour not in the appropriate fixed -# palette, that's a fatal error; if an image to be used in 8-bit -# mode contains more than 256 distinct colours, that's also a fatal -# error. -# -# Command-line syntax is: -# -# icon.pl -depth imagefile [imagefile...] [-depth imagefile [imagefile...]] -# -# where `-depth' is one of `-24', `-8', `-4' or `-1', and tells the -# script how to treat all the image files given after that option -# until the next depth option. For example, you might execute -# -# icon.pl -24 48x48x24.png 32x32x24.png -8 32x32x8.png -1 monochrome.png -# -# to build an icon file containing two differently sized 24-bit -# images, one 8-bit image and one black and white image. -# -# Windows .ICO files support a 1-bit alpha channel on all these -# image types. That is, any pixel can be either opaque or fully -# transparent, but not partially transparent. The alpha channel is -# separate from the main image data, meaning that `transparent' is -# not required to take up a palette entry. (So an 8-bit image can -# have 256 distinct _opaque_ colours, plus transparent pixels as -# well.) If the input images have alpha channels, they will be used -# to determine which pixels of the icon are transparent, by simple -# quantisation half way up (e.g. in a PNG image with an 8-bit alpha -# channel, alpha values of 00-7F will be mapped to transparent -# pixels, and 80-FF will become opaque). - -# The Windows 16-colour palette consists of: -# - the eight corners of the colour cube (000000, 0000FF, 00FF00, -# 00FFFF, FF0000, FF00FF, FFFF00, FFFFFF) -# - dim versions of the seven non-black corners, at 128/255 of the -# brightness (000080, 008000, 008080, 800000, 800080, 808000, -# 808080) -# - light grey at 192/255 of full brightness (C0C0C0). -%win16pal = ( - "\x00\x00\x00\x00" => 0, - "\x00\x00\x80\x00" => 1, - "\x00\x80\x00\x00" => 2, - "\x00\x80\x80\x00" => 3, - "\x80\x00\x00\x00" => 4, - "\x80\x00\x80\x00" => 5, - "\x80\x80\x00\x00" => 6, - "\xC0\xC0\xC0\x00" => 7, - "\x80\x80\x80\x00" => 8, - "\x00\x00\xFF\x00" => 9, - "\x00\xFF\x00\x00" => 10, - "\x00\xFF\xFF\x00" => 11, - "\xFF\x00\x00\x00" => 12, - "\xFF\x00\xFF\x00" => 13, - "\xFF\xFF\x00\x00" => 14, - "\xFF\xFF\xFF\x00" => 15, -); -@win16pal = sort { $win16pal{$a} <=> $win16pal{$b} } keys %win16pal; - -# The black and white palette consists of black (000000) and white -# (FFFFFF), obviously. -%win2pal = ( - "\x00\x00\x00\x00" => 0, - "\xFF\xFF\xFF\x00" => 1, -); -@win2pal = sort { $win16pal{$a} <=> $win2pal{$b} } keys %win2pal; - -@hdr = (); -@dat = (); - -$depth = undef; -foreach $_ (@ARGV) { - if (/^-(24|8|4|1)$/) { - $depth = $1; - } elsif (defined $depth) { - &readicon($_, $depth); - } else { - $usage = 1; - } -} -if ($usage || length @hdr == 0) { - print "usage: icon.pl ( -24 | -8 | -4 | -1 ) image [image...]\n"; - print " [ ( -24 | -8 | -4 | -1 ) image [image...] ...]\n"; - exit 0; -} - -# Now write out the output icon file. -print pack "vvv", 0, 1, scalar @hdr; # file-level header -$filepos = 6 + 16 * scalar @hdr; -for ($i = 0; $i < scalar @hdr; $i++) { - print $hdr[$i]; - print pack "V", $filepos; - $filepos += length($dat[$i]); -} -for ($i = 0; $i < scalar @hdr; $i++) { - print $dat[$i]; -} - -sub readicon { - my $filename = shift @_; - my $depth = shift @_; - my $pix; - my $i; - my %pal; - - # Determine the icon's width and height. - my $w = `identify -format %w $filename`; - my $h = `identify -format %h $filename`; - - # Read the file in as RGBA data. We flip vertically at this - # point, to avoid having to do it ourselves (.BMP and hence - # .ICO are bottom-up). - my $data = []; - open IDATA, "convert -flip -depth 8 $filename rgba:- |"; - push @$data, $rgb while (read IDATA,$rgb,4,0) == 4; - close IDATA; - # Check we have the right amount of data. - $xl = $w * $h; - $al = scalar @$data; - die "wrong amount of image data ($al, expected $xl) from $filename\n" - unless $al == $xl; - - # Build the alpha channel now, so we can exclude transparent - # pixels from the palette analysis. We replace transparent - # pixels with undef in the data array. - # - # We quantise the alpha channel half way up, so that alpha of - # 0x80 or more is taken to be fully opaque and 0x7F or less is - # fully transparent. Nasty, but the best we can do without - # dithering (and don't even suggest we do that!). - my $x; - my $y; - my $alpha = ""; - - for ($y = 0; $y < $h; $y++) { - my $currbyte = 0, $currbits = 0; - for ($x = 0; $x < (($w+31)|31)-31; $x++) { - $pix = ($x < $w ? $data->[$y*$w+$x] : "\x00\x00\x00\xFF"); - my @rgba = unpack "CCCC", $pix; - $currbyte <<= 1; - $currbits++; - if ($rgba[3] < 0x80) { - if ($x < $w) { - $data->[$y*$w+$x] = undef; - } - $currbyte |= 1; # MS has the alpha channel inverted :-) - } else { - # Might as well flip RGBA into BGR0 while we're here. - if ($x < $w) { - $data->[$y*$w+$x] = pack "CCCC", - $rgba[2], $rgba[1], $rgba[0], 0; - } - } - if ($currbits >= 8) { - $alpha .= pack "C", $currbyte; - $currbits -= 8; - } - } - } - - # For an 8-bit image, check we have at most 256 distinct - # colours, and build the palette. - %pal = (); - if ($depth == 8) { - my $palindex = 0; - foreach $pix (@$data) { - next unless defined $pix; - $pal{$pix} = $palindex++ unless defined $pal{$pix}; - } - die "too many colours in 8-bit image $filename\n" unless $palindex <= 256; - } elsif ($depth == 4) { - %pal = %win16pal; - } elsif ($depth == 1) { - %pal = %win2pal; - } - - my $raster = ""; - if ($depth < 24) { - # For a non-24-bit image, flatten the image into one palette - # index per pixel. - $pad = 32 / $depth; # number of pixels to pad scanline to 4-byte align - $pmask = $pad-1; - for ($y = 0; $y < $h; $y++) { - my $currbyte = 0, $currbits = 0; - for ($x = 0; $x < (($w+$pmask)|$pmask)-$pmask; $x++) { - $currbyte <<= $depth; - $currbits += $depth; - if ($x < $w && defined ($pix = $data->[$y*$w+$x])) { - if (!defined $pal{$pix}) { - $pixhex = sprintf "%02x%02x%02x", unpack "CCC", $pix; - die "illegal colour value $pixhex at pixel ($x,$y) in $filename\n"; - } - $currbyte |= $pal{$pix}; - } - if ($currbits >= 8) { - $raster .= pack "C", $currbyte; - $currbits -= 8; - } - } - } - } else { - # For a 24-bit image, reverse the order of the R,G,B values - # and stick a padding zero on the end. - # - # (In this loop we don't need to bother padding the - # scanline out to a multiple of four bytes, because every - # pixel takes four whole bytes anyway.) - for ($i = 0; $i < scalar @$data; $i++) { - if (defined $data->[$i]) { - $raster .= $data->[$i]; - } else { - $raster .= "\x00\x00\x00\x00"; - } - } - $depth = 32; # and adjust this - } - - # Prepare the icon data. First the header... - my $data = pack "VVVvvVVVVVV", - 40, # size of bitmap info header - $w, # icon width - $h*2, # icon height (x2 to indicate the subsequent alpha channel) - 1, # 1 plane (common to all MS image formats) - $depth, # bits per pixel - 0, # no compression - length $raster, # image size - 0, 0, 0, 0; # resolution, colours used, colours important (ignored) - # ... then the palette ... - if ($depth <= 8) { - my $ncols = (1 << $depth); - my $palette = "\x00\x00\x00\x00" x $ncols; - foreach $i (keys %pal) { - substr($palette, $pal{$i}*4, 4) = $i; - } - $data .= $palette; - } - # ... the raster data we already had ready ... - $data .= $raster; - # ... and the alpha channel we already had as well. - $data .= $alpha; - - # Prepare the header which will represent this image in the - # icon file. - my $header = pack "CCCCvvV", - $w, $h, # width and height (this time the real height) - 1 << $depth, # number of colours, if less than 256 - 0, # reserved - 1, # planes - $depth, # bits per pixel - length $data; # size of real icon data - - push @hdr, $header; - push @dat, $data; -} diff --git a/icons/macicon.py b/icons/macicon.py deleted file mode 100644 index 3b9ff75a1..000000000 --- a/icons/macicon.py +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env python3 - -# Generate Mac OS X .icns files, or at least the simple subformats -# that don't involve JPEG encoding and the like. -# -# Sources: https://en.wikipedia.org/wiki/Apple_Icon_Image_format and -# some details implicitly documented by the source code of 'libicns'. - -import sys -import struct -import subprocess - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -# The file format has a typical IFF-style (type, length, data) chunk -# structure, with one outer chunk containing subchunks for various -# different icon sizes and formats. -def make_chunk(chunkid, data): - assert len(chunkid) == 4 - return chunkid + struct.pack(">I", len(data) + 8) + data - -# Monochrome icons: a single chunk containing a 1 bpp image followed -# by a 1 bpp transparency mask. Both uncompressed, unless you count -# packing the bits into bytes. -def make_mono_icon(size, rgba): - assert len(rgba) == size * size - - # We assume our input image was monochrome, so that the R,G,B - # channels are all the same; we want the image and then the mask, - # so we take the R channel followed by the alpha channel. However, - # we have to flip the former, because in the output format the - # image has 0=white and 1=black, while the mask has 0=transparent - # and 1=opaque. - pixels = [rgba[index][chan] ^ flip for (chan, flip) in [(0,0xFF),(3,0)] - for index in range(len(rgba))] - - # Encode in 1-bit big-endian format. - data = b'' - for i in range(0, len(pixels), 8): - byte = 0 - for j in range(8): - if pixels[i+j] >= 0x80: - byte |= 0x80 >> j - data += bytes(byte) - - # This size-32 chunk id is an anomaly in what would otherwise be a - # consistent system of using {s,l,h,t} for {16,32,48,128}-pixel - # icon sizes. - chunkid = { 16: b"ics#", 32: b"ICN#", 48: b"ich#" }[size] - return make_chunk(chunkid, data) - -# Mask for full-colour icons: a chunk containing an 8 bpp alpha -# bitmap, uncompressed. The RGB data appears in a separate chunk. -def make_colour_mask(size, rgba): - assert len(rgba) == size * size - - data = bytes(map(lambda pix: pix[3], rgba)) - - chunkid = { 16: b"s8mk", 32: b"l8mk", 48: b"h8mk", 128: b"t8mk" }[size] - return make_chunk(chunkid, data) - -# Helper routine for deciding when to start and stop run-length -# encoding. -def runof3(string, position): - return (position < len(string) and - string[position:position+3] == string[position] * 3) - -# RGB data for full-colour icons: a chunk containing 8 bpp red, green -# and blue images, each run-length encoded (see comment inside the -# function), and then concatenated. -def make_colour_icon(size, rgba): - assert len(rgba) == size * size - - data = b"" - - # Mysterious extra zero header word appearing only in the size-128 - # icon chunk. libicns doesn't know what it's for, and neither do - # I. - if size == 128: - data += b"\0\0\0\0" - - # Handle R,G,B channels in sequence. (Ignore the alpha channel; it - # goes into the separate mask chunk constructed above.) - for chan in range(3): - pixels = bytes([rgba[index][chan] for index in range(len(rgba))]) - - # Run-length encode each channel using the following format: - # * byte 0x80-0xFF followed by one literal byte means repeat - # that byte 3-130 times - # * byte 0x00-0x7F followed by n+1 literal bytes means emit - # those bytes once each. - pos = 0 - while pos < len(pixels): - start = pos - if runof3(pixels, start): - pos += 3 - pixval = pixels[start] - while (pos - start < 130 and - pos < len(pixels) and - pixels[pos] == pixval): - pos += 1 - data += bytes(0x80 + pos-start - 3) + pixval - else: - while (pos - start < 128 and - pos < len(pixels) and - not runof3(pixels, pos)): - pos += 1 - data += bytes(0x00 + pos-start - 1) + pixels[start:pos] - - chunkid = { 16: b"is32", 32: b"il32", 48: b"ih32", 128: b"it32" }[size] - return make_chunk(chunkid, data) - -# Load an image file from disk and turn it into a simple list of -# 4-tuples giving 8-bit R,G,B,A values for each pixel. -# -# To avoid adding any build dependency on ImageMagick or Python -# imaging libraries, none of which comes as standard on OS X, I insist -# here that the file is in RGBA .pam format (as mkicon.py will have -# generated it). -def load_rgba(filename): - with open(filename, "rb") as f: - assert f.readline() == b"P7\n" - for line in iter(f.readline, ''): - words = line.decode("ASCII").rstrip("\n").split() - if words[0] == "WIDTH": - width = int(words[1]) - elif words[0] == "HEIGHT": - height = int(words[1]) - elif words[0] == "DEPTH": - assert int(words[1]) == 4 - elif words[0] == "TUPLTYPE": - assert words[1] == "RGB_ALPHA" - elif words[0] == "ENDHDR": - break - - assert width == height - data = f.read() - assert len(data) == width*height*4 - rgba = [list(data[i:i+4]) for i in range(0, len(data), 4)] - return width, rgba - -data = b"" - -# Trivial argument format: each argument is a filename prefixed with -# "mono:", "colour:" or "output:". The first two indicate image files -# to use as part of the icon, and the last gives the output file name. -# Icon subformat chunks are written out in the order of the arguments. -for arg in sys.argv[1:]: - kind, filename = arg.split(":", 2) - if kind == "output": - outfile = filename - else: - size, rgba = load_rgba(filename) - if kind == "mono": - data += make_mono_icon(size, rgba) - elif kind == "colour": - data += make_colour_icon(size, rgba) + make_colour_mask(size, rgba) - else: - assert False, "bad argument '%s'" % arg - -data = make_chunk(b"icns", data) - -with open(outfile, "wb") as f: - f.write(data) diff --git a/icons/mkicon.py b/icons/mkicon.py deleted file mode 100644 index c50082dbe..000000000 --- a/icons/mkicon.py +++ /dev/null @@ -1,1107 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import division - -import sys -import decimal -import math - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -# Python code which draws the PuTTY icon components at a range of -# sizes. - -# TODO -# ---- -# -# - use of alpha blending -# + try for variable-transparency borders -# -# - can we integrate the Mac icons into all this? Do we want to? - -# Python 3 prefers round-to-even. Emulate Python 2's behaviour instead. -def round(number): - return float( - decimal.Decimal(number).to_integral(rounding=decimal.ROUND_HALF_UP)) - -def pixel(x, y, colour, canvas): - canvas[(int(x),int(y))] = colour - -def overlay(src, x, y, dst): - x = int(x) - y = int(y) - for (sx, sy), colour in src.items(): - dst[sx+x, sy+y] = blend(colour, dst.get((sx+x, sy+y), cT)) - -def finalise(canvas): - for k in canvas.keys(): - canvas[k] = finalisepix(canvas[k]) - -def bbox(canvas): - minx, miny, maxx, maxy = None, None, None, None - for (x, y) in canvas.keys(): - if minx == None: - minx, miny, maxx, maxy = x, y, x+1, y+1 - else: - minx = min(minx, x) - miny = min(miny, y) - maxx = max(maxx, x+1) - maxy = max(maxy, y+1) - return (minx, miny, maxx, maxy) - -def topy(canvas): - miny = {} - for (x, y) in canvas.keys(): - miny[x] = min(miny.get(x, y), y) - return miny - -def render(canvas, minx, miny, maxx, maxy): - w = maxx - minx - h = maxy - miny - ret = [] - for y in range(h): - ret.append([outpix(cT)] * w) - for (x, y), colour in canvas.items(): - if x >= minx and x < maxx and y >= miny and y < maxy: - ret[y-miny][x-minx] = outpix(colour) - return ret - -# Code to actually draw pieces of icon. These don't generally worry -# about positioning within a canvas; they just draw at a standard -# location, return some useful coordinates, and leave composition -# to other pieces of code. - -sqrthash = {} -def memoisedsqrt(x): - if x not in sqrthash: - sqrthash[x] = math.sqrt(x) - return sqrthash[x] - -BR, TR, BL, TL = list(range(4)) # enumeration of quadrants for border() - -def border(canvas, thickness, squarecorners, out={}): - # I haven't yet worked out exactly how to do borders in a - # properly alpha-blended fashion. - # - # When you have two shades of dark available (half-dark H and - # full-dark F), the right sequence of circular border sections - # around a pixel x starts off with these two layouts: - # - # H F - # HxH FxF - # H F - # - # Where it goes after that I'm not entirely sure, but I'm - # absolutely sure those are the right places to start. However, - # every automated algorithm I've tried has always started off - # with the two layouts - # - # H HHH - # HxH HxH - # H HHH - # - # which looks much worse. This is true whether you do - # pixel-centre sampling (define an inner circle and an outer - # circle with radii differing by 1, set any pixel whose centre - # is inside the inner circle to F, any pixel whose centre is - # outside the outer one to nothing, interpolate between the two - # and round sensibly), _or_ whether you plot a notional circle - # of a given radius and measure the actual _proportion_ of each - # pixel square taken up by it. - # - # It's not clear what I should be doing to prevent this. One - # option is to attempt error-diffusion: Ian Jackson proved on - # paper that if you round each pixel's ideal value to the - # nearest of the available output values, then measure the - # error at each pixel, propagate that error outwards into the - # original values of the surrounding pixels, and re-round - # everything, you do get the correct second stage. However, I - # haven't tried it at a proper range of radii. - # - # Another option is that the automated mechanisms described - # above would be entirely adequate if it weren't for the fact - # that the human visual centres are adapted to detect - # horizontal and vertical lines in particular, so the only - # place you have to behave a bit differently is at the ends of - # the top and bottom row of pixels in the circle, and the top - # and bottom of the extreme columns. - # - # For the moment, what I have below is a very simple mechanism - # which always uses only one alpha level for any given border - # thickness, and which seems to work well enough for Windows - # 16-colour icons. Everything else will have to wait. - - thickness = memoisedsqrt(thickness) - - if thickness < 0.9: - darkness = 0.5 - else: - darkness = 1 - if thickness < 1: thickness = 1 - thickness = round(thickness - 0.5) + 0.3 - - out["borderthickness"] = thickness - - dmax = int(round(thickness)) - if dmax < thickness: dmax = dmax + 1 - - cquadrant = [[0] * (dmax+1) for x in range(dmax+1)] - squadrant = [[0] * (dmax+1) for x in range(dmax+1)] - - for x in range(dmax+1): - for y in range(dmax+1): - if max(x, y) < thickness: - squadrant[x][y] = darkness - if memoisedsqrt(x*x+y*y) < thickness: - cquadrant[x][y] = darkness - - bvalues = {} - for (x, y), colour in canvas.items(): - for dx in range(-dmax, dmax+1): - for dy in range(-dmax, dmax+1): - quadrant = 2 * (dx < 0) + (dy < 0) - if (x, y, quadrant) in squarecorners: - bval = squadrant[abs(dx)][abs(dy)] - else: - bval = cquadrant[abs(dx)][abs(dy)] - if bvalues.get((x+dx,y+dy),0) < bval: - bvalues[(x+dx,y+dy)] = bval - - for (x, y), value in bvalues.items(): - if (x,y) not in canvas: - canvas[(x,y)] = dark(value) - -def sysbox(size, out={}): - canvas = {} - - # The system box of the computer. - - height = int(round(3.6*size)) - width = int(round(16.51*size)) - depth = int(round(2*size)) - highlight = int(round(1*size)) - bothighlight = int(round(1*size)) - - out["sysboxheight"] = height - - floppystart = int(round(19*size)) # measured in half-pixels - floppyend = int(round(29*size)) # measured in half-pixels - floppybottom = height - bothighlight - floppyrheight = 0.7 * size - floppyheight = int(round(floppyrheight)) - if floppyheight < 1: - floppyheight = 1 - floppytop = floppybottom - floppyheight - - # The front panel is rectangular. - for x in range(width): - for y in range(height): - grey = 3 - if x < highlight or y < highlight: - grey = grey + 1 - if x >= width-highlight or y >= height-bothighlight: - grey = grey - 1 - if y < highlight and x >= width-highlight: - v = (highlight-1-y) - (x-(width-highlight)) - if v < 0: - grey = grey - 1 - elif v > 0: - grey = grey + 1 - if y >= floppytop and y < floppybottom and \ - 2*x+2 > floppystart and 2*x < floppyend: - if 2*x >= floppystart and 2*x+2 <= floppyend and \ - floppyrheight >= 0.7: - grey = 0 - else: - grey = 2 - pixel(x, y, greypix(grey/4.0), canvas) - - # The side panel is a parallelogram. - for x in range(depth): - for y in range(height): - pixel(x+width, y-(x+1), greypix(0.5), canvas) - - # The top panel is another parallelogram. - for x in range(width-1): - for y in range(depth): - grey = 3 - if x >= width-1 - highlight: - grey = grey + 1 - pixel(x+(y+1), -(y+1), greypix(grey/4.0), canvas) - - # And draw a border. - border(canvas, size, [], out) - - return canvas - -def monitor(size): - canvas = {} - - # The computer's monitor. - - height = int(round(9.55*size)) - width = int(round(11.49*size)) - surround = int(round(1*size)) - botsurround = int(round(2*size)) - sheight = height - surround - botsurround - swidth = width - 2*surround - depth = int(round(2*size)) - highlight = int(round(math.sqrt(size))) - shadow = int(round(0.55*size)) - - # The front panel is rectangular. - for x in range(width): - for y in range(height): - if x >= surround and y >= surround and \ - x < surround+swidth and y < surround+sheight: - # Screen. - sx = (float(x-surround) - swidth//3) / swidth - sy = (float(y-surround) - sheight//3) / sheight - shighlight = 1.0 - (sx*sx+sy*sy)*0.27 - pix = bluepix(shighlight) - if x < surround+shadow or y < surround+shadow: - pix = blend(cD, pix) # sharp-edged shadow on top and left - else: - # Complicated double bevel on the screen surround. - - # First, the outer bevel. We compute the distance - # from this pixel to each edge of the front - # rectangle. - list = [ - (x, +1), - (y, +1), - (width-1-x, -1), - (height-1-y, -1) - ] - # Now sort the list to find the distance to the - # _nearest_ edge, or the two joint nearest. - list.sort() - # If there's one nearest edge, that determines our - # bevel colour. If there are two joint nearest, our - # bevel colour is their shared one if they agree, - # and neutral otherwise. - outerbevel = 0 - if list[0][0] < list[1][0] or list[0][1] == list[1][1]: - if list[0][0] < highlight: - outerbevel = list[0][1] - - # Now, the inner bevel. We compute the distance - # from this pixel to each edge of the screen - # itself. - list = [ - (surround-1-x, -1), - (surround-1-y, -1), - (x-(surround+swidth), +1), - (y-(surround+sheight), +1) - ] - # Now we sort to find the _maximum_ distance, which - # conveniently ignores any less than zero. - list.sort() - # And now the strategy is pretty much the same as - # above, only we're working from the opposite end - # of the list. - innerbevel = 0 - if list[-1][0] > list[-2][0] or list[-1][1] == list[-2][1]: - if list[-1][0] >= 0 and list[-1][0] < highlight: - innerbevel = list[-1][1] - - # Now we know the adjustment we want to make to the - # pixel's overall grey shade due to the outer - # bevel, and due to the inner one. We break a tie - # in favour of a light outer bevel, but otherwise - # add. - grey = 3 - if outerbevel > 0 or outerbevel == innerbevel: - innerbevel = 0 - grey = grey + outerbevel + innerbevel - - pix = greypix(grey / 4.0) - - pixel(x, y, pix, canvas) - - # The side panel is a parallelogram. - for x in range(depth): - for y in range(height): - pixel(x+width, y-x, greypix(0.5), canvas) - - # The top panel is another parallelogram. - for x in range(width): - for y in range(depth-1): - pixel(x+(y+1), -(y+1), greypix(0.75), canvas) - - # And draw a border. - border(canvas, size, [(0,int(height-1),BL)]) - - return canvas - -def computer(size): - # Monitor plus sysbox. - out = {} - m = monitor(size) - s = sysbox(size, out) - x = int(round((2+size/(size+1))*size)) - y = int(out["sysboxheight"] + out["borderthickness"]) - mb = bbox(m) - sb = bbox(s) - xoff = sb[0] - mb[0] + x - yoff = sb[3] - mb[3] - y - overlay(m, xoff, yoff, s) - return s - -def lightning(size): - canvas = {} - - # The lightning bolt motif. - - # We always want this to be an even number of pixels in height, - # and an odd number in width. - width = round(7*size) * 2 - 1 - height = round(8*size) * 2 - - # The outer edge of each side of the bolt goes to this point. - outery = round(8.4*size) - outerx = round(11*size) - - # And the inner edge goes to this point. - innery = height - 1 - outery - innerx = round(7*size) - - for y in range(int(height)): - list = [] - if y <= outery: - list.append(width-1-int(outerx * float(y) / outery + 0.3)) - if y <= innery: - list.append(width-1-int(innerx * float(y) / innery + 0.3)) - y0 = height-1-y - if y0 <= outery: - list.append(int(outerx * float(y0) / outery + 0.3)) - if y0 <= innery: - list.append(int(innerx * float(y0) / innery + 0.3)) - list.sort() - for x in range(int(list[0]), int(list[-1]+1)): - pixel(x, y, cY, canvas) - - # And draw a border. - border(canvas, size, [(int(width-1),0,TR), (0,int(height-1),BL)]) - - return canvas - -def document(size): - canvas = {} - - # The document used in the PSCP/PSFTP icon. - - width = round(13*size) - height = round(16*size) - - lineht = round(1*size) - if lineht < 1: lineht = 1 - linespc = round(0.7*size) - if linespc < 1: linespc = 1 - nlines = int((height-linespc)/(lineht+linespc)) - height = nlines*(lineht+linespc)+linespc # round this so it fits better - - # Start by drawing a big white rectangle. - for y in range(int(height)): - for x in range(int(width)): - pixel(x, y, cW, canvas) - - # Now draw lines of text. - for line in range(nlines): - # Decide where this line of text begins. - if line == 0: - start = round(4*size) - elif line < 5*nlines//7: - start = round((line - (nlines//7)) * size) - else: - start = round(1*size) - if start < round(1*size): - start = round(1*size) - # Decide where it ends. - endpoints = [10, 8, 11, 6, 5, 7, 5] - ey = line * 6.0 / (nlines-1) - eyf = math.floor(ey) - eyc = math.ceil(ey) - exf = endpoints[int(eyf)] - exc = endpoints[int(eyc)] - if eyf == eyc: - end = exf - else: - end = exf * (eyc-ey) + exc * (ey-eyf) - end = round(end * size) - - liney = height - (lineht+linespc) * (line+1) - for x in range(int(start), int(end)): - for y in range(int(lineht)): - pixel(x, y+liney, cK, canvas) - - # And draw a border. - border(canvas, size, \ - [(0,0,TL),(int(width-1),0,TR),(0,int(height-1),BL), \ - (int(width-1),int(height-1),BR)]) - - return canvas - -def hat(size): - canvas = {} - - # The secret-agent hat in the Pageant icon. - - topa = [6]*9+[5,3,1,0,0,1,2,2,1,1,1,9,9,10,10,11,11,12,12] - topa = [round(x*size) for x in topa] - botl = round(topa[0]+2.4*math.sqrt(size)) - botr = round(topa[-1]+2.4*math.sqrt(size)) - width = round(len(topa)*size) - - # Line equations for the top and bottom of the hat brim, in the - # form y=mx+c. c, of course, needs scaling by size, but m is - # independent of size. - brimm = 1.0 / 3.75 - brimtopc = round(4*size/3) - brimbotc = round(10*size/3) - - for x in range(int(width)): - xs = float(x) * (len(topa)-1) / (width-1) - xf = math.floor(xs) - xc = math.ceil(xs) - topf = topa[int(xf)] - topc = topa[int(xc)] - if xf == xc: - top = topf - else: - top = topf * (xc-xs) + topc * (xs-xf) - top = math.floor(top) - bot = round(botl + (botr-botl) * x/(width-1)) - - for y in range(int(top), int(bot)): - pixel(x, y, cK, canvas) - - # Now draw the brim. - for x in range(int(width)): - brimtop = brimtopc + brimm * x - brimbot = brimbotc + brimm * x - for y in range(int(math.floor(brimtop)), int(math.ceil(brimbot))): - tophere = max(min(brimtop - y, 1), 0) - bothere = max(min(brimbot - y, 1), 0) - grey = bothere - tophere - # Only draw brim pixels over pixels which are (a) part - # of the main hat, and (b) not right on its edge. - if (x,y) in canvas and \ - (x,y-1) in canvas and \ - (x,y+1) in canvas and \ - (x-1,y) in canvas and \ - (x+1,y) in canvas: - pixel(x, y, greypix(grey), canvas) - - return canvas - -def key(size): - canvas = {} - - # The key in the PuTTYgen icon. - - keyheadw = round(9.5*size) - keyheadh = round(12*size) - keyholed = round(4*size) - keyholeoff = round(2*size) - # Ensure keyheadh and keyshafth have the same parity. - keyshafth = round((2*size - (int(keyheadh)&1)) / 2) * 2 + (int(keyheadh)&1) - keyshaftw = round(18.5*size) - keyhead = [round(x*size) for x in [12,11,8,10,9,8,11,12]] - - squarepix = [] - - # Ellipse for the key head, minus an off-centre circular hole. - for y in range(int(keyheadh)): - dy = (y-(keyheadh-1)/2.0) / (keyheadh/2.0) - dyh = (y-(keyheadh-1)/2.0) / (keyholed/2.0) - for x in range(int(keyheadw)): - dx = (x-(keyheadw-1)/2.0) / (keyheadw/2.0) - dxh = (x-(keyheadw-1)/2.0-keyholeoff) / (keyholed/2.0) - if dy*dy+dx*dx <= 1 and dyh*dyh+dxh*dxh > 1: - pixel(x + keyshaftw, y, cy, canvas) - - # Rectangle for the key shaft, extended at the bottom for the - # key head detail. - for x in range(int(keyshaftw)): - top = round((keyheadh - keyshafth) / 2) - bot = round((keyheadh + keyshafth) / 2) - xs = float(x) * (len(keyhead)-1) / round((len(keyhead)-1)*size) - xf = math.floor(xs) - xc = math.ceil(xs) - in_head = 0 - if xc < len(keyhead): - in_head = 1 - yf = keyhead[int(xf)] - yc = keyhead[int(xc)] - if xf == xc: - bot = yf - else: - bot = yf * (xc-xs) + yc * (xs-xf) - for y in range(int(top),int(bot)): - pixel(x, y, cy, canvas) - if in_head: - last = (x, y) - if x == 0: - squarepix.append((x, int(top), TL)) - if x == 0: - squarepix.append(last + (BL,)) - if last != None and not in_head: - squarepix.append(last + (BR,)) - last = None - - # And draw a border. - border(canvas, size, squarepix) - - return canvas - -def linedist(x1,y1, x2,y2, x,y): - # Compute the distance from the point x,y to the line segment - # joining x1,y1 to x2,y2. Returns the distance vector, measured - # with x,y at the origin. - - vectors = [] - - # Special case: if x1,y1 and x2,y2 are the same point, we - # don't attempt to extrapolate it into a line at all. - if x1 != x2 or y1 != y2: - # First, find the nearest point to x,y on the infinite - # projection of the line segment. So we construct a vector - # n perpendicular to that segment... - nx = y2-y1 - ny = x1-x2 - # ... compute the dot product of (x1,y1)-(x,y) with that - # vector... - nd = (x1-x)*nx + (y1-y)*ny - # ... multiply by the vector we first thought of... - ndx = nd * nx - ndy = nd * ny - # ... and divide twice by the length of n. - ndx = ndx / (nx*nx+ny*ny) - ndy = ndy / (nx*nx+ny*ny) - # That gives us a displacement vector from x,y to the - # nearest point. See if it's within the range of the line - # segment. - cx = x + ndx - cy = y + ndy - if cx >= min(x1,x2) and cx <= max(x1,x2) and \ - cy >= min(y1,y2) and cy <= max(y1,y2): - vectors.append((ndx,ndy)) - - # Now we have up to three candidate result vectors: (ndx,ndy) - # as computed just above, and the two vectors to the ends of - # the line segment, (x1-x,y1-y) and (x2-x,y2-y). Pick the - # shortest. - vectors = vectors + [(x1-x,y1-y), (x2-x,y2-y)] - bestlen, best = None, None - for v in vectors: - vlen = v[0]*v[0]+v[1]*v[1] - if bestlen == None or bestlen > vlen: - bestlen = vlen - best = v - return best - -def spanner(size): - canvas = {} - - # The spanner in the config box icon. - - headcentre = 0.5 + round(4*size) - headradius = headcentre + 0.1 - headhighlight = round(1.5*size) - holecentre = 0.5 + round(3*size) - holeradius = round(2*size) - holehighlight = round(1.5*size) - shaftend = 0.5 + round(25*size) - shaftwidth = round(2*size) - shafthighlight = round(1.5*size) - cmax = shaftend + shaftwidth - - # Define three line segments, such that the shortest distance - # vectors from any point to each of these segments determines - # everything we need to know about where it is on the spanner - # shape. - segments = [ - ((0,0), (holecentre, holecentre)), - ((headcentre, headcentre), (headcentre, headcentre)), - ((headcentre+headradius/math.sqrt(2), headcentre+headradius/math.sqrt(2)), - (cmax, cmax)) - ] - - for y in range(int(cmax)): - for x in range(int(cmax)): - vectors = [linedist(a,b,c,d,x,y) for ((a,b),(c,d)) in segments] - dists = [memoisedsqrt(vx*vx+vy*vy) for (vx,vy) in vectors] - - # If the distance to the hole line is less than - # holeradius, we're not part of the spanner. - if dists[0] < holeradius: - continue - # If the distance to the head `line' is less than - # headradius, we are part of the spanner; likewise if - # the distance to the shaft line is less than - # shaftwidth _and_ the resulting shaft point isn't - # beyond the shaft end. - if dists[1] > headradius and \ - (dists[2] > shaftwidth or x+vectors[2][0] >= shaftend): - continue - - # We're part of the spanner. Now compute the highlight - # on this pixel. We do this by computing a `slope - # vector', which points from this pixel in the - # direction of its nearest edge. We store an array of - # slope vectors, in polar coordinates. - angles = [math.atan2(vy,vx) for (vx,vy) in vectors] - slopes = [] - if dists[0] < holeradius + holehighlight: - slopes.append(((dists[0]-holeradius)/holehighlight,angles[0])) - if dists[1]/headradius < dists[2]/shaftwidth: - if dists[1] > headradius - headhighlight and dists[1] < headradius: - slopes.append(((headradius-dists[1])/headhighlight,math.pi+angles[1])) - else: - if dists[2] > shaftwidth - shafthighlight and dists[2] < shaftwidth: - slopes.append(((shaftwidth-dists[2])/shafthighlight,math.pi+angles[2])) - # Now we find the smallest distance in that array, if - # any, and that gives us a notional position on a - # sphere which we can use to compute the final - # highlight level. - bestdist = None - bestangle = 0 - for dist, angle in slopes: - if bestdist == None or bestdist > dist: - bestdist = dist - bestangle = angle - if bestdist == None: - bestdist = 1.0 - sx = (1.0-bestdist) * math.cos(bestangle) - sy = (1.0-bestdist) * math.sin(bestangle) - sz = math.sqrt(1.0 - sx*sx - sy*sy) - shade = sx-sy+sz / math.sqrt(3) # can range from -1 to +1 - shade = 1.0 - (1-shade)/3 - - pixel(x, y, yellowpix(shade), canvas) - - # And draw a border. - border(canvas, size, []) - - return canvas - -def box(size, back): - canvas = {} - - # The back side of the cardboard box in the installer icon. - - boxwidth = round(15 * size) - boxheight = round(12 * size) - boxdepth = round(4 * size) - boxfrontflapheight = round(5 * size) - boxrightflapheight = round(3 * size) - - # Three shades of basically acceptable brown, all achieved by - # halftoning between two of the Windows-16 colours. I'm quite - # pleased that was feasible at all! - dark = halftone(cr, cK) - med = halftone(cr, cy) - light = halftone(cr, cY) - # We define our halftoning parity in such a way that the black - # pixels along the RHS of the visible part of the box back - # match up with the one-pixel black outline around the - # right-hand side of the box. In other words, we want the pixel - # at (-1, boxwidth-1) to be black, and hence the one at (0, - # boxwidth) too. - parityadjust = int(boxwidth) % 2 - - # The entire back of the box. - if back: - for x in range(int(boxwidth + boxdepth)): - ytop = max(-x-1, -boxdepth-1) - ybot = min(boxheight, boxheight+boxwidth-1-x) - for y in range(int(ytop), int(ybot)): - pixel(x, y, dark[(x+y+parityadjust) % 2], canvas) - - # Even when drawing the back of the box, we still draw the - # whole shape, because that means we get the right overall size - # (the flaps make the box front larger than the box back) and - # it'll all be overwritten anyway. - - # The front face of the box. - for x in range(int(boxwidth)): - for y in range(int(boxheight)): - pixel(x, y, med[(x+y+parityadjust) % 2], canvas) - # The right face of the box. - for x in range(int(boxwidth), int(boxwidth+boxdepth)): - ybot = boxheight + boxwidth-x - ytop = ybot - boxheight - for y in range(int(ytop), int(ybot)): - pixel(x, y, dark[(x+y+parityadjust) % 2], canvas) - # The front flap of the box. - for y in range(int(boxfrontflapheight)): - xadj = int(round(-0.5*y)) - for x in range(int(xadj), int(xadj+boxwidth)): - pixel(x, y, light[(x+y+parityadjust) % 2], canvas) - # The right flap of the box. - for x in range(int(boxwidth), int(boxwidth + boxdepth + boxrightflapheight + 1)): - ytop = max(boxwidth - 1 - x, x - boxwidth - 2*boxdepth - 1) - ybot = min(x - boxwidth - 1, boxwidth + 2*boxrightflapheight - 1 - x) - for y in range(int(ytop), int(ybot+1)): - pixel(x, y, med[(x+y+parityadjust) % 2], canvas) - - # And draw a border. - border(canvas, size, [(0, int(boxheight)-1, BL)]) - - return canvas - -def boxback(size): - return box(size, 1) -def boxfront(size): - return box(size, 0) - -# Functions to draw entire icons by composing the above components. - -def xybolt(c1, c2, size, boltoffx=0, boltoffy=0, aux={}): - # Two unspecified objects and a lightning bolt. - - canvas = {} - w = h = round(32 * size) - - bolt = lightning(size) - - # Position c2 against the top right of the icon. - bb = bbox(c2) - assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h - overlay(c2, w-bb[2], 0-bb[1], canvas) - aux["c2pos"] = (w-bb[2], 0-bb[1]) - # Position c1 against the bottom left of the icon. - bb = bbox(c1) - assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h - overlay(c1, 0-bb[0], h-bb[3], canvas) - aux["c1pos"] = (0-bb[0], h-bb[3]) - # Place the lightning bolt artistically off-centre. (The - # rationale for this positioning is that it's centred on the - # midpoint between the centres of the two monitors in the PuTTY - # icon proper, but it's not really feasible to _base_ the - # calculation here on that.) - bb = bbox(bolt) - assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h - overlay(bolt, (w-bb[0]-bb[2])/2 + round(boltoffx*size), \ - (h-bb[1]-bb[3])/2 + round((boltoffy-2)*size), canvas) - - return canvas - -def putty_icon(size): - return xybolt(computer(size), computer(size), size) - -def puttycfg_icon(size): - w = h = round(32 * size) - s = spanner(size) - canvas = putty_icon(size) - # Centre the spanner. - bb = bbox(s) - overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas) - return canvas - -def puttygen_icon(size): - return xybolt(computer(size), key(size), size, boltoffx=2) - -def pscp_icon(size): - return xybolt(document(size), computer(size), size) - -def puttyins_icon(size): - aret = {} - # The box back goes behind the lightning bolt. - canvas = xybolt(boxback(size), computer(size), size, boltoffx=-2, boltoffy=+1, aux=aret) - # But the box front goes over the top, so that the lightning - # bolt appears to come _out_ of the box. Here it's useful to - # know the exact coordinates where xybolt placed the box back, - # so we can overlay the box front exactly on top of it. - c1x, c1y = aret["c1pos"] - overlay(boxfront(size), c1x, c1y, canvas) - return canvas - -def pterm_icon(size): - # Just a really big computer. - - canvas = {} - w = h = round(32 * size) - - c = computer(size * 1.4) - - # Centre c in the return canvas. - bb = bbox(c) - assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h - overlay(c, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas) - - return canvas - -def ptermcfg_icon(size): - w = h = round(32 * size) - s = spanner(size) - canvas = pterm_icon(size) - # Centre the spanner. - bb = bbox(s) - overlay(s, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas) - return canvas - -def pageant_icon(size): - # A biggish computer, in a hat. - - canvas = {} - w = h = round(32 * size) - - c = computer(size * 1.2) - ht = hat(size) - - cbb = bbox(c) - hbb = bbox(ht) - - # Determine the relative y-coordinates of the computer and hat. - # We just centre the one on the other. - xrel = (cbb[0]+cbb[2]-hbb[0]-hbb[2])//2 - - # Determine the relative y-coordinates of the computer and hat. - # We do this by sitting the hat as low down on the computer as - # possible without any computer showing over the top. To do - # this we first have to find the minimum x coordinate at each - # y-coordinate of both components. - cty = topy(c) - hty = topy(ht) - yrelmin = None - for cx in cty.keys(): - hx = cx - xrel - assert hx in hty - yrel = cty[cx] - hty[hx] - if yrelmin == None: - yrelmin = yrel - else: - yrelmin = min(yrelmin, yrel) - - # Overlay the hat on the computer. - overlay(ht, xrel, yrelmin, c) - - # And centre the result in the main icon canvas. - bb = bbox(c) - assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h - overlay(c, (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2, canvas) - - return canvas - -# Test and output functions. - -import os -import sys - -def testrun(func, fname): - canvases = [] - for size in [0.5, 0.6, 1.0, 1.2, 1.5, 4.0]: - canvases.append(func(size)) - wid = 0 - ht = 0 - for canvas in canvases: - minx, miny, maxx, maxy = bbox(canvas) - wid = max(wid, maxx-minx+4) - ht = ht + maxy-miny+4 - block = [] - for canvas in canvases: - minx, miny, maxx, maxy = bbox(canvas) - block.extend(render(canvas, minx-2, miny-2, minx-2+wid, maxy+2)) - with open(fname, "wb") as f: - f.write((("P7\nWIDTH %d\nHEIGHT %d\nDEPTH 3\nMAXVAL 255\n" + - "TUPLTYPE RGB\nENDHDR\n") % (wid, ht)).encode('ASCII')) - assert len(block) == ht - for line in block: - assert len(line) == wid - for r, g, b, a in line: - # Composite on to orange. - r = int(round((r * a + 255 * (255-a)) / 255.0)) - g = int(round((g * a + 128 * (255-a)) / 255.0)) - b = int(round((b * a + 0 * (255-a)) / 255.0)) - f.write(bytes(bytearray([r, g, b]))) - -def drawicon(func, width, fname, orangebackground = 0): - canvas = func(width / 32.0) - finalise(canvas) - minx, miny, maxx, maxy = bbox(canvas) - assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width - - block = render(canvas, 0, 0, width, width) - with open(fname, "wb") as f: - f.write((("P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\n" + - "TUPLTYPE RGB_ALPHA\nENDHDR\n") % - (width, width)).encode('ASCII')) - assert len(block) == width - for line in block: - assert len(line) == width - for r, g, b, a in line: - if orangebackground: - # Composite on to orange. - r = int(round((r * a + 255 * (255-a)) / 255.0)) - g = int(round((g * a + 128 * (255-a)) / 255.0)) - b = int(round((b * a + 0 * (255-a)) / 255.0)) - a = 255 - f.write(bytes(bytearray([r, g, b, a]))) - -args = sys.argv[1:] - -orangebackground = test = 0 -colours = 1 # 0=mono, 1=16col, 2=truecol -doingargs = 1 - -realargs = [] -for arg in args: - if doingargs and arg[0] == "-": - if arg == "-t": - test = 1 - elif arg == "-it": - orangebackground = 1 - elif arg == "-2": - colours = 0 - elif arg == "-T": - colours = 2 - elif arg == "--": - doingargs = 0 - else: - sys.stderr.write("unrecognised option '%s'\n" % arg) - sys.exit(1) - else: - realargs.append(arg) - -if colours == 0: - # Monochrome. - cK=cr=cg=cb=cm=cc=cP=cw=cR=cG=cB=cM=cC=cD = 0 - cY=cy=cW = 1 - cT = -1 - def greypix(value): - return [cK,cW][int(round(value))] - def yellowpix(value): - return [cK,cW][int(round(value))] - def bluepix(value): - return cK - def dark(value): - return [cT,cK][int(round(value))] - def blend(col1, col2): - if col1 == cT: - return col2 - else: - return col1 - pixvals = [ - (0x00, 0x00, 0x00, 0xFF), # cK - (0xFF, 0xFF, 0xFF, 0xFF), # cW - (0x00, 0x00, 0x00, 0x00), # cT - ] - def outpix(colour): - return pixvals[colour] - def finalisepix(colour): - return colour - def halftone(col1, col2): - return (col1, col2) -elif colours == 1: - # Windows 16-colour palette. - cK,cr,cg,cy,cb,cm,cc,cP,cw,cR,cG,cY,cB,cM,cC,cW = list(range(16)) - cT = -1 - cD = -2 # special translucent half-darkening value used internally - def greypix(value): - return [cK,cw,cw,cP,cW][int(round(4*value))] - def yellowpix(value): - return [cK,cy,cY][int(round(2*value))] - def bluepix(value): - return [cK,cb,cB][int(round(2*value))] - def dark(value): - return [cT,cD,cK][int(round(2*value))] - def blend(col1, col2): - if col1 == cT: - return col2 - elif col1 == cD: - return [cK,cK,cK,cK,cK,cK,cK,cw,cK,cr,cg,cy,cb,cm,cc,cw,cD,cD][col2] - else: - return col1 - pixvals = [ - (0x00, 0x00, 0x00, 0xFF), # cK - (0x80, 0x00, 0x00, 0xFF), # cr - (0x00, 0x80, 0x00, 0xFF), # cg - (0x80, 0x80, 0x00, 0xFF), # cy - (0x00, 0x00, 0x80, 0xFF), # cb - (0x80, 0x00, 0x80, 0xFF), # cm - (0x00, 0x80, 0x80, 0xFF), # cc - (0xC0, 0xC0, 0xC0, 0xFF), # cP - (0x80, 0x80, 0x80, 0xFF), # cw - (0xFF, 0x00, 0x00, 0xFF), # cR - (0x00, 0xFF, 0x00, 0xFF), # cG - (0xFF, 0xFF, 0x00, 0xFF), # cY - (0x00, 0x00, 0xFF, 0xFF), # cB - (0xFF, 0x00, 0xFF, 0xFF), # cM - (0x00, 0xFF, 0xFF, 0xFF), # cC - (0xFF, 0xFF, 0xFF, 0xFF), # cW - (0x00, 0x00, 0x00, 0x80), # cD - (0x00, 0x00, 0x00, 0x00), # cT - ] - def outpix(colour): - return pixvals[colour] - def finalisepix(colour): - # cD is used internally, but can't be output. Convert to cK. - if colour == cD: - return cK - return colour - def halftone(col1, col2): - return (col1, col2) -else: - # True colour. - cK = (0x00, 0x00, 0x00, 0xFF) - cr = (0x80, 0x00, 0x00, 0xFF) - cg = (0x00, 0x80, 0x00, 0xFF) - cy = (0x80, 0x80, 0x00, 0xFF) - cb = (0x00, 0x00, 0x80, 0xFF) - cm = (0x80, 0x00, 0x80, 0xFF) - cc = (0x00, 0x80, 0x80, 0xFF) - cP = (0xC0, 0xC0, 0xC0, 0xFF) - cw = (0x80, 0x80, 0x80, 0xFF) - cR = (0xFF, 0x00, 0x00, 0xFF) - cG = (0x00, 0xFF, 0x00, 0xFF) - cY = (0xFF, 0xFF, 0x00, 0xFF) - cB = (0x00, 0x00, 0xFF, 0xFF) - cM = (0xFF, 0x00, 0xFF, 0xFF) - cC = (0x00, 0xFF, 0xFF, 0xFF) - cW = (0xFF, 0xFF, 0xFF, 0xFF) - cD = (0x00, 0x00, 0x00, 0x80) - cT = (0x00, 0x00, 0x00, 0x00) - def greypix(value): - value = max(min(value, 1), 0) - return (int(round(0xFF*value)),) * 3 + (0xFF,) - def yellowpix(value): - value = max(min(value, 1), 0) - return (int(round(0xFF*value)),) * 2 + (0, 0xFF) - def bluepix(value): - value = max(min(value, 1), 0) - return (0, 0, int(round(0xFF*value)), 0xFF) - def dark(value): - value = max(min(value, 1), 0) - return (0, 0, 0, int(round(0xFF*value))) - def blend(col1, col2): - r1,g1,b1,a1 = col1 - r2,g2,b2,a2 = col2 - r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0)) - g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0)) - b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0)) - a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0)) - return r, g, b, a - def outpix(colour): - return colour - if colours == 2: - # True colour with no alpha blending: we still have to - # finalise half-dark pixels to black. - def finalisepix(colour): - if colour[3] > 0: - return colour[:3] + (0xFF,) - return colour - else: - def finalisepix(colour): - return colour - def halftone(col1, col2): - r1,g1,b1,a1 = col1 - r2,g2,b2,a2 = col2 - colret = (int(r1+r2)//2, int(g1+g2)//2, int(b1+b2)//2, int(a1+a2)//2) - return (colret, colret) - -if test: - testrun(eval(realargs[0]), realargs[1]) -else: - drawicon(eval(realargs[0]), int(realargs[1]), realargs[2], orangebackground) diff --git a/icons/mksvg.py b/icons/mksvg.py deleted file mode 100644 index f29ff25b8..000000000 --- a/icons/mksvg.py +++ /dev/null @@ -1,938 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import itertools -import math -import os -import sys -from fractions import Fraction - -import xml.etree.cElementTree as ET - -# Python code which draws the PuTTY icon components in SVG. - -def makegroup(*objects): - if len(objects) == 1: - return objects[0] - g = ET.Element("g") - for obj in objects: - g.append(obj) - return g - -class Container: - "Empty class for keeping things in." - pass - -class SVGthing(object): - def __init__(self): - self.fillc = "none" - self.strokec = "none" - self.strokewidth = 0 - self.strokebehind = False - self.clipobj = None - self.props = Container() - def fmt_colour(self, rgb): - return "#{0:02x}{1:02x}{2:02x}".format(*rgb) - def fill(self, colour): - self.fillc = self.fmt_colour(colour) - def stroke(self, colour, width=1, behind=False): - self.strokec = self.fmt_colour(colour) - self.strokewidth = width - self.strokebehind = behind - def clip(self, obj): - self.clipobj = obj - def styles(self, elt, styles): - elt.attrib["style"] = ";".join("{}:{}".format(k,v) - for k,v in sorted(styles.items())) - def add_clip_paths(self, container, idents, X, Y): - if self.clipobj: - self.clipobj.identifier = next(idents) - clipelt = self.clipobj.render_thing(X, Y) - clippath = ET.Element("clipPath") - clippath.attrib["id"] = self.clipobj.identifier - clippath.append(clipelt) - container.append(clippath) - return True - return False - def render(self, X, Y, with_styles=True): - elt = self.render_thing(X, Y) - if self.clipobj: - elt.attrib["clip-path"] = "url(#{})".format( - self.clipobj.identifier) - estyles = {"fill": self.fillc} - sstyles = {"stroke": self.strokec} - if self.strokewidth: - sstyles["stroke-width"] = "{:g}".format(self.strokewidth) - sstyles["stroke-linecap"] = "round" - sstyles["stroke-linejoin"] = "round" - if not self.strokebehind: - estyles.update(sstyles) - if with_styles: - self.styles(elt, estyles) - if not self.strokebehind: - return elt - selt = self.render_thing(X, Y) - if with_styles: - self.styles(selt, sstyles) - return makegroup(selt, elt) - def bbox(self): - it = self.bb_iter() - xmin, ymin = xmax, ymax = next(it) - for x, y in it: - xmin = min(x, xmin) - xmax = max(x, xmax) - ymin = min(y, ymin) - ymax = max(y, ymax) - r = self.strokewidth / 2.0 - xmin -= r - ymin -= r - xmax += r - ymax += r - if self.clipobj: - x0, y0, x1, y1 = self.clipobj.bbox() - xmin = max(x0, xmin) - xmax = min(x1, xmax) - ymin = max(y0, ymin) - ymax = min(y1, ymax) - return xmin, ymin, xmax, ymax - -class SVGpath(SVGthing): - def __init__(self, pointlists, closed=True): - super().__init__() - self.pointlists = pointlists - self.closed = closed - def bb_iter(self): - for points in self.pointlists: - for x,y,on in points: - yield x,y - def render_thing(self, X, Y): - pathcmds = [] - - for points in self.pointlists: - while not points[-1][2]: - points = points[1:] + [points[0]] - - piter = iter(points) - - if self.closed: - xp, yp, _ = points[-1] - pathcmds.extend(["M", X+xp, Y-yp]) - else: - xp, yp, on = next(piter) - assert on, "Open paths must start with an on-curve point" - pathcmds.extend(["M", X+xp, Y-yp]) - - for x, y, on in piter: - if isinstance(on, type(())): - assert on[0] == "arc" - _, rx, ry, rotation, large, sweep = on - pathcmds.extend(["a", - rx, ry, rotation, - 1 if large else 0, - 1 if sweep else 0, - x-xp, -(y-yp)]) - elif not on: - x0, y0 = x, y - x1, y1, on = next(piter) - assert not on - x, y, on = next(piter) - assert on - pathcmds.extend(["c", x0-xp, -(y0-yp), - ",", x1-xp, -(y1-yp), - ",", x-xp, -(y-yp)]) - elif x == xp: - pathcmds.extend(["v", -(y-yp)]) - elif x == xp: - pathcmds.extend(["h", x-xp]) - else: - pathcmds.extend(["l", x-xp, -(y-yp)]) - - xp, yp = x, y - - if self.closed: - pathcmds.append("z") - - path = ET.Element("path") - path.attrib["d"] = " ".join(str(cmd) for cmd in pathcmds) - return path - -class SVGrect(SVGthing): - def __init__(self, x0, y0, x1, y1): - super().__init__() - self.points = x0, y0, x1, y1 - def bb_iter(self): - x0, y0, x1, y1 = self.points - return iter([(x0,y0), (x1,y1)]) - def render_thing(self, X, Y): - x0, y0, x1, y1 = self.points - rect = ET.Element("rect") - rect.attrib["x"] = "{:g}".format(min(X+x0,X+x1)) - rect.attrib["y"] = "{:g}".format(min(Y-y0,Y-y1)) - rect.attrib["width"] = "{:g}".format(abs(x0-x1)) - rect.attrib["height"] = "{:g}".format(abs(y0-y1)) - return rect - -class SVGpoly(SVGthing): - def __init__(self, points): - super().__init__() - self.points = points - def bb_iter(self): - return iter(self.points) - def render_thing(self, X, Y): - poly = ET.Element("polygon") - poly.attrib["points"] = " ".join("{:g},{:g}".format(X+x,Y-y) - for x,y in self.points) - return poly - -class SVGgroup(object): - def __init__(self, objects, translations=[]): - translations = translations + ( - [(0,0)] * (len(objects)-len(translations))) - self.contents = list(zip(objects, translations)) - self.props = Container() - def render(self, X, Y): - return makegroup(*[obj.render(X+x, Y-y) - for obj, (x,y) in self.contents]) - def add_clip_paths(self, container, idents, X, Y): - toret = False - for obj, (x,y) in self.contents: - if obj.add_clip_paths(container, idents, X+x, Y-y): - toret = True - return toret - def bbox(self): - it = ((x,y) + obj.bbox() for obj, (x,y) in self.contents) - x, y, xmin, ymin, xmax, ymax = next(it) - xmin = x+xmin - ymin = y+ymin - xmax = x+xmax - ymax = y+ymax - for x, y, x0, y0, x1, y1 in it: - xmin = min(x+x0, xmin) - xmax = max(x+x1, xmax) - ymin = min(y+y0, ymin) - ymax = max(y+y1, ymax) - return (xmin, ymin, xmax, ymax) - -class SVGtranslate(object): - def __init__(self, obj, translation): - self.obj = obj - self.tx, self.ty = translation - def render(self, X, Y): - return self.obj.render(X+self.tx, Y+self.ty) - def add_clip_paths(self, container, idents, X, Y): - return self.obj.add_clip_paths(container, idents, X+self.tx, Y-self.ty) - def bbox(self): - xmin, ymin, xmax, ymax = self.obj.bbox() - return xmin+self.tx, ymin+self.ty, xmax+self.tx, ymax+self.ty - -# Code to actually draw pieces of icon. These don't generally worry -# about positioning within a rectangle; they just draw at a standard -# location, return some useful coordinates, and leave composition -# to other pieces of code. - -def sysbox(size): - # The system box of the computer. - - height = 3.6*size - width = 16.51*size - depth = 2*size - highlight = 1*size - - floppystart = 19*size # measured in half-pixels - floppyend = 29*size # measured in half-pixels - floppybottom = highlight - floppyrheight = 0.7 * size - floppyheight = floppyrheight - if floppyheight < 1: - floppyheight = 1 - floppytop = floppybottom + floppyheight - - background_coords = [ - (0,0), (width,0), (width+depth,depth), - (width+depth,height+depth), (depth,height+depth), (0,height)] - background = SVGpoly(background_coords) - background.fill(greypix(0.75)) - - hl_dark = SVGpoly([ - (highlight,0), (highlight,highlight), (width-highlight,highlight), - (width-highlight,height-highlight), (width+depth,height+depth), - (width+depth,depth), (width,0)]) - hl_dark.fill(greypix(0.5)) - - hl_light = SVGpoly([ - (0,highlight), (highlight,highlight), (highlight,height-highlight), - (width-highlight,height-highlight), (width+depth,height+depth), - (width+depth-highlight,height+depth), (width-highlight,height), - (0,height)]) - hl_light.fill(cW) - - floppy = SVGrect(floppystart/2.0, floppybottom, - floppyend/2.0, floppytop) - floppy.fill(cK) - - outline = SVGpoly(background_coords) - outline.stroke(cK, width=0.5) - - toret = SVGgroup([background, hl_dark, hl_light, floppy, outline]) - toret.props.sysboxheight = height - toret.props.borderthickness = 1 # FIXME - return toret - -def monitor(size): - # The computer's monitor. - - height = 9.5*size - width = 11.5*size - surround = 1*size - botsurround = 2*size - sheight = height - surround - botsurround - swidth = width - 2*surround - depth = 2*size - highlight = surround/2 - shadow = 0.5*size - - background_coords = [ - (0,0), (width,0), (width+depth,depth), - (width+depth,height+depth), (depth,height+depth), (0,height)] - background = SVGpoly(background_coords) - background.fill(greypix(0.75)) - - hl0_dark = SVGpoly([ - (0,0), (highlight,highlight), (width-highlight,highlight), - (width-highlight,height-highlight), (width+depth,height+depth), - (width+depth,depth), (width,0)]) - hl0_dark.fill(greypix(0.5)) - - hl0_light = SVGpoly([ - (0,0), (highlight,highlight), (highlight,height-highlight), - (width-highlight,height-highlight), (width,height), (0,height)]) - hl0_light.fill(greypix(1)) - - hl1_dark = SVGpoly([ - (surround-highlight,botsurround-highlight), (surround,botsurround), - (surround,height-surround), (width-surround,height-surround), - (width-surround+highlight,height-surround+highlight), - (surround-highlight,height-surround+highlight)]) - hl1_dark.fill(greypix(0.5)) - - hl1_light = SVGpoly([ - (surround-highlight,botsurround-highlight), (surround,botsurround), - (width-surround,botsurround), (width-surround,height-surround), - (width-surround+highlight,height-surround+highlight), - (width-surround+highlight,botsurround-highlight)]) - hl1_light.fill(greypix(1)) - - screen = SVGrect(surround, botsurround, width-surround, height-surround) - screen.fill(bluepix(1)) - - screenshadow = SVGpoly([ - (surround,botsurround), (surround+shadow,botsurround), - (surround+shadow,height-surround-shadow), - (width-surround,height-surround-shadow), - (width-surround,height-surround), (surround,height-surround)]) - screenshadow.fill(bluepix(0.5)) - - outline = SVGpoly(background_coords) - outline.stroke(cK, width=0.5) - - toret = SVGgroup([background, hl0_dark, hl0_light, hl1_dark, hl1_light, - screen, screenshadow, outline]) - # Give the centre of the screen (for lightning-bolt positioning purposes) - # as the centre of the _light_ area of the screen, not counting the - # shadow on the top and left. I think that looks very slightly nicer. - sbb = (surround+shadow, botsurround, width-surround, height-surround-shadow) - toret.props.screencentre = ((sbb[0]+sbb[2])/2, (sbb[1]+sbb[3])/2) - return toret - -def computer(size): - # Monitor plus sysbox. - m = monitor(size) - s = sysbox(size) - x = (2+size/(size+1))*size - y = int(s.props.sysboxheight + s.props.borderthickness) - mb = m.bbox() - sb = s.bbox() - xoff = mb[0] - sb[0] + x - yoff = mb[1] - sb[1] + y - toret = SVGgroup([s, m], [(0,0), (xoff,yoff)]) - toret.props.screencentre = (m.props.screencentre[0]+xoff, - m.props.screencentre[1]+yoff) - return toret - -def lightning(size): - # The lightning bolt motif. - - # Compute the right size of a lightning bolt to exactly connect - # the centres of the two screens in the main PuTTY icon. We'll use - # that size of bolt for all the other icons too, for consistency. - iconw = iconh = 32 * size - cbb = computer(size).bbox() - assert cbb[2]-cbb[0] <= iconw and cbb[3]-cbb[1] <= iconh - width, height = iconw-(cbb[2]-cbb[0]), iconh-(cbb[3]-cbb[1]) - - degree = math.pi/180 - - centrethickness = 2*size # top-to-bottom thickness of centre bar - innerangle = 46 * degree # slope of the inner slanting line - outerangle = 39 * degree # slope of the outer one - - innery = (height - centrethickness) / 2 - outery = (height + centrethickness) / 2 - innerx = innery / math.tan(innerangle) - outerx = outery / math.tan(outerangle) - - points = [(innerx, innery), (0,0), (outerx, outery)] - points.extend([(width-x, height-y) for x,y in points]) - - # Fill and stroke the lightning bolt. - # - # Most of the filled-and-stroked objects in these icons are filled - # first, and then stroked with width 0.5, so that the edge of the - # filled area runs down the centre line of the stroke. Put another - # way, half the stroke covers what would have been the filled - # area, and the other half covers the background. This seems like - # the normal way to fill-and-stroke a shape of a given size, and - # SVG makes it easy by allowing us to specify the polygon just - # once with both 'fill' and 'stroke' CSS properties. - # - # But if we did that in this case, then the tips of the lightning - # bolt wouldn't have lightning-colour anywhere near them, because - # the two edges are so close together in angle that the point - # where the strokes would first _not_ overlap would be miles away - # from the logical endpoint. - # - # So, for this one case, we stroke the polygon first at double the - # width, and then fill it on top of that, requiring two copies of - # it in the SVG (though my construction class here hides that - # detail). The effect is that we still get a stroke of visible - # width 0.5, but it's entirely outside the filled area of the - # polygon, so the tips of the yellow interior of the lightning - # bolt are exactly at the logical endpoints. - poly = SVGpoly(points) - poly.fill(cY) - poly.stroke(cK, width=1, behind=True) - poly.props.end1 = (0,0) - poly.props.end2 = (width,height) - return poly - -def document(size): - # The document used in the PSCP/PSFTP icon. - - width = 13*size - height = 16*size - - lineht = 0.875*size - linespc = 1.125*size - nlines = int((height-linespc)/(lineht+linespc)) - height = nlines*(lineht+linespc)+linespc # round this so it fits better - - paper = SVGrect(0, 0, width, height) - paper.fill(cW) - paper.stroke(cK, width=0.5) - - objs = [paper] - - # Now draw lines of text. - for line in range(nlines): - # Decide where this line of text begins. - if line == 0: - start = 4*size - elif line < 5*nlines/7: - start = (line * 4/5) * size - else: - start = 1*size - # Decide where it ends. - endpoints = [10, 8, 11, 6, 5, 7, 5] - ey = line * 6.0 / (nlines-1) - eyf = math.floor(ey) - eyc = math.ceil(ey) - exf = endpoints[int(eyf)] - exc = endpoints[int(eyc)] - if eyf == eyc: - end = exf - else: - end = exf * (eyc-ey) + exc * (ey-eyf) - end = end * size - - liney = (lineht+linespc) * (line+1) - line = SVGrect(start, liney-lineht, end, liney) - line.fill(cK) - objs.append(line) - - return SVGgroup(objs) - -def hat(size): - # The secret-agent hat in the Pageant icon. - - leftend = (0, -6*size) - rightend = (28*size, -12*size) - dx = rightend[0]-leftend[0] - dy = rightend[1]-leftend[1] - tcentre = (leftend[0] + 0.5*dx - 0.3*dy, leftend[1] + 0.5*dy + 0.3*dx) - - hatpoints = [leftend + (True,), - (7.5*size, -6*size, True), - (12*size, 0, True), - (14*size, 3*size, False), - (tcentre[0] - 0.1*dx, tcentre[1] - 0.1*dy, False), - tcentre + (True,)] - for x, y, on in list(reversed(hatpoints))[1:]: - vx, vy = x-tcentre[0], y-tcentre[1] - coeff = float(vx*dx + vy*dy) / float(dx*dx + dy*dy) - rx, ry = x - 2*coeff*dx, y - 2*coeff*dy - hatpoints.append((rx, ry, on)) - - mainhat = SVGpath([hatpoints]) - mainhat.fill(cK) - - band = SVGpoly([ - (leftend[0] - 0.1*dy, leftend[1] + 0.1*dx), - (rightend[0] - 0.1*dy, rightend[1] + 0.1*dx), - (rightend[0] - 0.15*dy, rightend[1] + 0.15*dx), - (leftend[0] - 0.15*dy, leftend[1] + 0.15*dx)]) - band.fill(cW) - band.clip(SVGpath([hatpoints])) - - outline = SVGpath([hatpoints]) - outline.stroke(cK, width=1) - - return SVGgroup([mainhat, band, outline]) - -def key(size): - # The key in the PuTTYgen icon. - - keyheadw = 9.5*size - keyheadh = 12*size - keyholed = 4*size - keyholeoff = 2*size - # Ensure keyheadh and keyshafth have the same parity. - keyshafth = (2*size - (int(keyheadh)&1)) / 2 * 2 + (int(keyheadh)&1) - keyshaftw = 18.5*size - keyheaddetail = [x*size for x in [12,11,8,10,9,8,11,12]] - - squarepix = [] - - keyheadcx = keyshaftw + keyheadw / 2.0 - keyheadcy = keyheadh / 2.0 - keyshafttop = keyheadcy + keyshafth / 2.0 - keyshaftbot = keyheadcy - keyshafth / 2.0 - - keyhead = [(0, keyshafttop, True), (keyshaftw, keyshafttop, True), - (keyshaftw, keyshaftbot, - ("arc", keyheadw/2.0, keyheadh/2.0, 0, True, True)), - (len(keyheaddetail)*size, keyshaftbot, True)] - for i, h in reversed(list(enumerate(keyheaddetail))): - keyhead.append(((i+1)*size, keyheadh-h, True)) - keyhead.append(((i)*size, keyheadh-h, True)) - - keyholecx = keyheadcx + keyholeoff - keyholecy = keyheadcy - keyholer = keyholed / 2.0 - - keyhole = [(keyholecx + keyholer, keyholecy, - ("arc", keyholer, keyholer, 0, False, False)), - (keyholecx - keyholer, keyholecy, - ("arc", keyholer, keyholer, 0, False, False))] - - outline = SVGpath([keyhead, keyhole]) - outline.fill(cy) - outline.stroke(cK, width=0.5) - return outline - -def linedist(x1,y1, x2,y2, x,y): - # Compute the distance from the point x,y to the line segment - # joining x1,y1 to x2,y2. Returns the distance vector, measured - # with x,y at the origin. - - vectors = [] - - # Special case: if x1,y1 and x2,y2 are the same point, we - # don't attempt to extrapolate it into a line at all. - if x1 != x2 or y1 != y2: - # First, find the nearest point to x,y on the infinite - # projection of the line segment. So we construct a vector - # n perpendicular to that segment... - nx = y2-y1 - ny = x1-x2 - # ... compute the dot product of (x1,y1)-(x,y) with that - # vector... - nd = (x1-x)*nx + (y1-y)*ny - # ... multiply by the vector we first thought of... - ndx = nd * nx - ndy = nd * ny - # ... and divide twice by the length of n. - ndx = ndx / (nx*nx+ny*ny) - ndy = ndy / (nx*nx+ny*ny) - # That gives us a displacement vector from x,y to the - # nearest point. See if it's within the range of the line - # segment. - cx = x + ndx - cy = y + ndy - if cx >= min(x1,x2) and cx <= max(x1,x2) and \ - cy >= min(y1,y2) and cy <= max(y1,y2): - vectors.append((ndx,ndy)) - - # Now we have up to three candidate result vectors: (ndx,ndy) - # as computed just above, and the two vectors to the ends of - # the line segment, (x1-x,y1-y) and (x2-x,y2-y). Pick the - # shortest. - vectors = vectors + [(x1-x,y1-y), (x2-x,y2-y)] - bestlen, best = None, None - for v in vectors: - vlen = v[0]*v[0]+v[1]*v[1] - if bestlen == None or bestlen > vlen: - bestlen = vlen - best = v - return best - -def spanner(size): - # The spanner in the config box icon. - - # Coordinate definitions. - headcentre = 0.5 + 4*size - headradius = headcentre + 0.1 - headhighlight = 1.5*size - holecentre = 0.5 + 3*size - holeradius = 2*size - holehighlight = 1.5*size - shaftend = 0.5 + 25*size - shaftwidth = 2*size - shafthighlight = 1.5*size - cmax = shaftend + shaftwidth - - # The spanner head is a circle centred at headcentre*(1,1) with - # radius headradius, minus a circle at holecentre*(1,1) with - # radius holeradius, and also minus every translate of that circle - # by a negative real multiple of (1,1). - # - # The spanner handle is a diagonally oriented rectangle, of width - # shaftwidth, with the centre of the far end at shaftend*(1,1), - # and the near end terminating somewhere inside the spanner head - # (doesn't really matter exactly where). - # - # Hence, in SVG we can represent the shape using a path of - # straight lines and circular arcs. But first we need to calculate - # the points where the straight lines meet the spanner head circle. - headpt = lambda a, on=True: (headcentre+headradius*math.cos(a), - -headcentre+headradius*math.sin(a), on) - holept = lambda a, on=True: (holecentre+holeradius*math.cos(a), - -holecentre+holeradius*math.sin(a), on) - - # Now we can specify the path. - spannercoords = [[ - holept(math.pi*5/4), - holept(math.pi*1/4, ("arc", holeradius,holeradius,0, False, False)), - headpt(math.pi*3/4 - math.asin(holeradius/headradius)), - headpt(math.pi*7/4 + math.asin(shaftwidth/headradius), - ("arc", headradius,headradius,0, False, True)), - (shaftend+math.sqrt(0.5)*shaftwidth, - -shaftend+math.sqrt(0.5)*shaftwidth, True), - (shaftend-math.sqrt(0.5)*shaftwidth, - -shaftend-math.sqrt(0.5)*shaftwidth, True), - headpt(math.pi*7/4 - math.asin(shaftwidth/headradius)), - headpt(math.pi*3/4 + math.asin(holeradius/headradius), - ("arc", headradius,headradius,0, False, True)), - ]] - - base = SVGpath(spannercoords) - base.fill(cY) - - shadowthickness = 2*size - sx, sy, _ = holept(math.pi*5/4) - sx += math.sqrt(0.5) * shadowthickness/2 - sy += math.sqrt(0.5) * shadowthickness/2 - sr = holeradius - shadowthickness/2 - - shadow = SVGpath([ - [(sx, sy, sr), - holept(math.pi*1/4, ("arc", sr, sr, 0, False, False)), - headpt(math.pi*3/4 - math.asin(holeradius/headradius))], - [(shaftend-math.sqrt(0.5)*shaftwidth, - -shaftend-math.sqrt(0.5)*shaftwidth, True), - headpt(math.pi*7/4 - math.asin(shaftwidth/headradius)), - headpt(math.pi*3/4 + math.asin(holeradius/headradius), - ("arc", headradius,headradius,0, False, True))], - ], closed=False) - shadow.clip(SVGpath(spannercoords)) - shadow.stroke(cy, width=shadowthickness) - - outline = SVGpath(spannercoords) - outline.stroke(cK, width=0.5) - - return SVGgroup([base, shadow, outline]) - -def box(size, wantback): - # The back side of the cardboard box in the installer icon. - - boxwidth = 15 * size - boxheight = 12 * size - boxdepth = 4 * size - boxfrontflapheight = 5 * size - boxrightflapheight = 3 * size - - # Three shades of basically acceptable brown, all achieved by - # halftoning between two of the Windows-16 colours. I'm quite - # pleased that was feasible at all! - dark = halftone(cr, cK) - med = halftone(cr, cy) - light = halftone(cr, cY) - # We define our halftoning parity in such a way that the black - # pixels along the RHS of the visible part of the box back - # match up with the one-pixel black outline around the - # right-hand side of the box. In other words, we want the pixel - # at (-1, boxwidth-1) to be black, and hence the one at (0, - # boxwidth) too. - parityadjust = int(boxwidth) % 2 - - # The back of the box. - if wantback: - back = SVGpoly([ - (0,0), (boxwidth,0), (boxwidth+boxdepth,boxdepth), - (boxwidth+boxdepth,boxheight+boxdepth), - (boxdepth,boxheight+boxdepth), (0,boxheight)]) - back.fill(dark) - back.stroke(cK, width=0.5) - return back - - # The front face of the box. - front = SVGrect(0, 0, boxwidth, boxheight) - front.fill(med) - front.stroke(cK, width=0.5) - # The right face of the box. - right = SVGpoly([ - (boxwidth,0), (boxwidth+boxdepth,boxdepth), - (boxwidth+boxdepth,boxheight+boxdepth), (boxwidth,boxheight)]) - right.fill(dark) - right.stroke(cK, width=0.5) - frontflap = SVGpoly([ - (0,boxheight), (boxwidth,boxheight), - (boxwidth-boxfrontflapheight/2, boxheight-boxfrontflapheight), - (-boxfrontflapheight/2, boxheight-boxfrontflapheight)]) - frontflap.stroke(cK, width=0.5) - frontflap.fill(light) - rightflap = SVGpoly([ - (boxwidth,boxheight), (boxwidth+boxdepth,boxheight+boxdepth), - (boxwidth+boxdepth+boxrightflapheight, - boxheight+boxdepth-boxrightflapheight), - (boxwidth+boxrightflapheight,boxheight-boxrightflapheight)]) - rightflap.stroke(cK, width=0.5) - rightflap.fill(med) - - return SVGgroup([front, right, frontflap, rightflap]) - -def boxback(size): - return box(size, 1) -def boxfront(size): - return box(size, 0) - -# Functions to draw entire icons by composing the above components. - -def xybolt(c1, c2, size, boltoffx=0, boltoffy=0, c1bb=None, c2bb=None): - # Two unspecified objects and a lightning bolt. - - w = h = 32 * size - - bolt = lightning(size) - - objs = [c2, c1, bolt] - origins = [None] * 3 - - # Position c2 against the top right of the icon. - bb = c2bb if c2bb is not None else c2.bbox() - assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h - origins[0] = w-bb[2], h-bb[3] - # Position c1 against the bottom left of the icon. - bb = c1bb if c1bb is not None else c1.bbox() - assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h - origins[1] = 0-bb[0], 0-bb[1] - - # Place the lightning bolt so that it ends precisely at the centre - # of the monitor, in whichever of the two sub-pictures has one. - # (In the case of the PuTTY icon proper, in which _both_ - # sub-pictures are computers, it should line up correctly for both.) - origin1 = origin2 = None - if hasattr(c1.props, "screencentre"): - origin1 = ( - c1.props.screencentre[0] + origins[1][0] - bolt.props.end1[0], - c1.props.screencentre[1] + origins[1][1] - bolt.props.end1[1]) - if hasattr(c2.props, "screencentre"): - origin2 = ( - c2.props.screencentre[0] + origins[0][0] - bolt.props.end2[0], - c2.props.screencentre[1] + origins[0][1] - bolt.props.end2[1]) - if origin1 is not None and origin2 is not None: - assert math.hypot(origin1[0]-origin2[0],origin1[1]-origin2[1]<1e-5), ( - "Lightning bolt didn't line up! Off by {}*size".format( - ((origin1[0]-origin2[0])/size, - (origin1[1]-origin2[1])/size))) - origins[2] = origin1 if origin1 is not None else origin2 - assert origins[2] is not None, "Need at least one computer to line up bolt" - - toret = SVGgroup(objs, origins) - toret.props.c1pos = origins[1] - toret.props.c2pos = origins[0] - return toret - -def putty_icon(size): - return xybolt(computer(size), computer(size), size) - -def puttycfg_icon(size): - w = h = 32 * size - s = spanner(size) - b = putty_icon(size) - bb = s.bbox() - return SVGgroup([b, s], [(0,0), ((w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2)]) - -def puttygen_icon(size): - k = key(size) - # Manually move the key around, by pretending to xybolt that its - # bounding box is offset from where it really is. - kbb = SVGtranslate(k,(2*size,5*size)).bbox() - return xybolt(computer(size), k, size, boltoffx=2, c2bb=kbb) - -def pscp_icon(size): - return xybolt(document(size), computer(size), size) - -def puttyins_icon(size): - boxfront = box(size, False) - boxback = box(size, True) - # The box back goes behind the lightning bolt. - most = xybolt(boxback, computer(size), size, c1bb=boxfront.bbox(), - boltoffx=-2, boltoffy=+1) - # But the box front goes over the top, so that the lightning - # bolt appears to come _out_ of the box. Here it's useful to - # know the exact coordinates where xybolt placed the box back, - # so we can overlay the box front exactly on top of it. - c1x, c1y = most.props.c1pos - return SVGgroup([most, boxfront], [(0,0), most.props.c1pos]) - -def pterm_icon(size): - # Just a really big computer. - - w = h = 32 * size - - c = computer(size * 1.4) - - # Centre c in the output rectangle. - bb = c.bbox() - assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h - - return SVGgroup([c], [((w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2)]) - -def ptermcfg_icon(size): - w = h = 32 * size - s = spanner(size) - b = pterm_icon(size) - bb = s.bbox() - return SVGgroup([b, s], [(0,0), ((w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2)]) - -def pageant_icon(size): - # A biggish computer, in a hat. - - w = h = 32 * size - - c = computer(size * 1.2) - ht = hat(size) - - cbb = c.bbox() - hbb = ht.bbox() - - # Determine the relative coordinates of the computer and hat. We - # do this by first centring one on the other, then adjusting by - # hand. - xrel = (cbb[0]+cbb[2]-hbb[0]-hbb[2])/2 + 2*size - yrel = (cbb[1]+cbb[3]-hbb[1]-hbb[3])/2 + 12*size - - both = SVGgroup([c, ht], [(0,0), (xrel,yrel)]) - - # Mostly-centre the result in the output rectangle. We want - # everything to fit in frame, but we also want to make it look as - # if the computer is more x-centred than the hat. - - # Coordinates that would centre the whole group. - bb = both.bbox() - assert bb[2]-bb[0] <= w and bb[3]-bb[1] <= h - grx, gry = (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2 - - # Coords that would centre just the computer. - bb = c.bbox() - crx, cry = (w-bb[0]-bb[2])/2, (h-bb[1]-bb[3])/2 - - # Use gry unchanged, but linear-combine grx with crx. - return SVGgroup([both], [(grx+0.6*(crx-grx), gry)]) - -# Test and output functions. - -cK = (0x00, 0x00, 0x00, 0xFF) -cr = (0x80, 0x00, 0x00, 0xFF) -cg = (0x00, 0x80, 0x00, 0xFF) -cy = (0x80, 0x80, 0x00, 0xFF) -cb = (0x00, 0x00, 0x80, 0xFF) -cm = (0x80, 0x00, 0x80, 0xFF) -cc = (0x00, 0x80, 0x80, 0xFF) -cP = (0xC0, 0xC0, 0xC0, 0xFF) -cw = (0x80, 0x80, 0x80, 0xFF) -cR = (0xFF, 0x00, 0x00, 0xFF) -cG = (0x00, 0xFF, 0x00, 0xFF) -cY = (0xFF, 0xFF, 0x00, 0xFF) -cB = (0x00, 0x00, 0xFF, 0xFF) -cM = (0xFF, 0x00, 0xFF, 0xFF) -cC = (0x00, 0xFF, 0xFF, 0xFF) -cW = (0xFF, 0xFF, 0xFF, 0xFF) -cD = (0x00, 0x00, 0x00, 0x80) -cT = (0x00, 0x00, 0x00, 0x00) -def greypix(value): - value = max(min(value, 1), 0) - return (int(round(0xFF*value)),) * 3 + (0xFF,) -def yellowpix(value): - value = max(min(value, 1), 0) - return (int(round(0xFF*value)),) * 2 + (0, 0xFF) -def bluepix(value): - value = max(min(value, 1), 0) - return (0, 0, int(round(0xFF*value)), 0xFF) -def dark(value): - value = max(min(value, 1), 0) - return (0, 0, 0, int(round(0xFF*value))) -def blend(col1, col2): - r1,g1,b1,a1 = col1 - r2,g2,b2,a2 = col2 - r = int(round((r1*a1 + r2*(0xFF-a1)) / 255.0)) - g = int(round((g1*a1 + g2*(0xFF-a1)) / 255.0)) - b = int(round((b1*a1 + b2*(0xFF-a1)) / 255.0)) - a = int(round((255*a1 + a2*(0xFF-a1)) / 255.0)) - return r, g, b, a -def halftone(col1, col2): - r1,g1,b1,a1 = col1 - r2,g2,b2,a2 = col2 - return ((r1+r2)//2, (g1+g2)//2, (b1+b2)//2, (a1+a2)//2) - -def drawicon(func, width, fname): - icon = func(width / 32.0) - minx, miny, maxx, maxy = icon.bbox() - #assert minx >= 0 and miny >= 0 and maxx <= width and maxy <= width - - svgroot = ET.Element("svg") - svgroot.attrib["xmlns"] = "http://www.w3.org/2000/svg" - svgroot.attrib["viewBox"] = "0 0 {w:d} {w:d}".format(w=width) - - defs = ET.Element("defs") - idents = ("iconid{:d}".format(n) for n in itertools.count()) - if icon.add_clip_paths(defs, idents, 0, width): - svgroot.append(defs) - - svgroot.append(icon.render(0,width)) - - ET.ElementTree(svgroot).write(fname) - -def main(): - parser = argparse.ArgumentParser(description='Generate PuTTY SVG icons.') - parser.add_argument("icon", help="Which icon to generate.") - parser.add_argument("-s", "--size", type=int, default=48, - help="Notional pixel size to base the SVG on.") - parser.add_argument("-o", "--output", required=True, - help="Output file name.") - args = parser.parse_args() - - drawicon(eval(args.icon), args.size, args.output) - -if __name__ == '__main__': - main() diff --git a/import.c b/import.c deleted file mode 100644 index 918de50ed..000000000 --- a/import.c +++ /dev/null @@ -1,2322 +0,0 @@ -/* - * 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 "mpint.h" -#include "misc.h" - -static bool openssh_pem_encrypted(BinarySource *src); -static bool openssh_new_encrypted(BinarySource *src); -static ssh2_userkey *openssh_pem_read( - BinarySource *src, const char *passphrase, const char **errmsg_p); -static ssh2_userkey *openssh_new_read( - BinarySource *src, const char *passphrase, const char **errmsg_p); -static bool openssh_auto_write( - const Filename *file, ssh2_userkey *key, const char *passphrase); -static bool openssh_pem_write( - const Filename *file, ssh2_userkey *key, const char *passphrase); -static bool openssh_new_write( - const Filename *file, ssh2_userkey *key, const char *passphrase); - -static bool sshcom_encrypted(BinarySource *src, char **comment); -static ssh2_userkey *sshcom_read( - BinarySource *src, const char *passphrase, const char **errmsg_p); -static bool sshcom_write( - const Filename *file, ssh2_userkey *key, const char *passphrase); - -/* - * Given a key type, determine whether we know how to import it. - */ -bool import_possible(int type) -{ - if (type == SSH_KEYTYPE_OPENSSH_PEM) - return true; - if (type == SSH_KEYTYPE_OPENSSH_NEW) - return true; - if (type == SSH_KEYTYPE_SSHCOM) - return true; - return false; -} - -/* - * 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; -} - -static inline char *bsgetline(BinarySource *src) -{ - ptrlen line = get_chomped_line(src); - if (get_err(src)) - return NULL; - return mkstr(line); -} - -/* - * Determine whether a foreign key is encrypted. - */ -bool import_encrypted_s(const Filename *filename, BinarySource *src, - int type, char **comment) -{ - if (type == SSH_KEYTYPE_OPENSSH_PEM) { - /* OpenSSH PEM format doesn't contain a key comment at all */ - *comment = dupstr(filename_to_str(filename)); - return openssh_pem_encrypted(src); - } else if (type == SSH_KEYTYPE_OPENSSH_NEW) { - /* OpenSSH new format does, but it's inside the encrypted - * section for some reason */ - *comment = dupstr(filename_to_str(filename)); - return openssh_new_encrypted(src); - } else if (type == SSH_KEYTYPE_SSHCOM) { - return sshcom_encrypted(src, comment); - } - return false; -} - -bool import_encrypted(const Filename *filename, int type, char **comment) -{ - LoadedFile *lf = lf_load_keyfile(filename, NULL); - if (!lf) - return false; /* couldn't even open the file */ - - bool toret = import_encrypted_s(filename, BinarySource_UPCAST(lf), - type, comment); - lf_free(lf); - return toret; -} - -/* - * Import an SSH-1 key. - */ -int import_ssh1_s(BinarySource *src, int type, - RSAKey *key, char *passphrase, const char **errmsg_p) -{ - return 0; -} - -int import_ssh1(const Filename *filename, int type, - RSAKey *key, char *passphrase, const char **errmsg_p) -{ - LoadedFile *lf = lf_load_keyfile(filename, errmsg_p); - if (!lf) - return false; - - int toret = import_ssh1_s(BinarySource_UPCAST(lf), - type, key, passphrase, errmsg_p); - lf_free(lf); - return toret; -} - -/* - * Import an SSH-2 key. - */ -ssh2_userkey *import_ssh2_s(BinarySource *src, int type, - char *passphrase, const char **errmsg_p) -{ - if (type == SSH_KEYTYPE_OPENSSH_PEM) - return openssh_pem_read(src, passphrase, errmsg_p); - else if (type == SSH_KEYTYPE_OPENSSH_NEW) - return openssh_new_read(src, passphrase, errmsg_p); - if (type == SSH_KEYTYPE_SSHCOM) - return sshcom_read(src, passphrase, errmsg_p); - return NULL; -} - -ssh2_userkey *import_ssh2(const Filename *filename, int type, - char *passphrase, const char **errmsg_p) -{ - LoadedFile *lf = lf_load_keyfile(filename, errmsg_p); - if (!lf) - return false; - - ssh2_userkey *toret = import_ssh2_s(BinarySource_UPCAST(lf), - type, passphrase, errmsg_p); - lf_free(lf); - return toret; -} - -/* - * Export an SSH-1 key. - */ -bool export_ssh1(const Filename *filename, int type, RSAKey *key, - char *passphrase) -{ - return false; -} - -/* - * Export an SSH-2 key. - */ -bool export_ssh2(const Filename *filename, int type, - ssh2_userkey *key, char *passphrase) -{ - if (type == SSH_KEYTYPE_OPENSSH_AUTO) - return openssh_auto_write(filename, key, passphrase); - if (type == SSH_KEYTYPE_OPENSSH_NEW) - return openssh_new_write(filename, key, passphrase); - if (type == SSH_KEYTYPE_SSHCOM) - return sshcom_write(filename, key, passphrase); - return false; -} - -/* ---------------------------------------------------------------------- - * 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) - -/* - * 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 void BinarySink_put_ber_id_len(BinarySink *bs, - int id, int length, int flags) -{ - if (id <= 30) { - /* - * Identifier is one byte. - */ - put_byte(bs, 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. - */ - put_byte(bs, 0x1F | flags); - for (n = 1; (id >> (7*n)) > 0; n++) - continue; /* count the bytes */ - while (n--) - put_byte(bs, (n ? 0x80 : 0) | ((id >> (7*n)) & 0x7F)); - } - - if (length < 128) { - /* - * Length is one byte. - */ - put_byte(bs, 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 */ - put_byte(bs, 0x80 | n); - while (n--) - put_byte(bs, (length >> (8*n)) & 0xFF); - } -} - -#define put_ber_id_len(bs, id, len, flags) \ - BinarySink_put_ber_id_len(BinarySink_UPCAST(bs), id, len, flags) - -typedef struct ber_item { - int id; - int flags; - ptrlen data; -} ber_item; - -static ber_item BinarySource_get_ber(BinarySource *src) -{ - ber_item toret; - unsigned char leadbyte, lenbyte; - size_t length; - - leadbyte = get_byte(src); - toret.flags = (leadbyte & 0xE0); - if ((leadbyte & 0x1F) == 0x1F) { - unsigned char idbyte; - - toret.id = 0; - do { - idbyte = get_byte(src); - toret.id = (toret.id << 7) | (idbyte & 0x7F); - } while (idbyte & 0x80); - } else { - toret.id = leadbyte & 0x1F; - } - - lenbyte = get_byte(src); - if (lenbyte & 0x80) { - int nbytes = lenbyte & 0x7F; - length = 0; - while (nbytes-- > 0) - length = (length << 8) | get_byte(src); - } else { - length = lenbyte; - } - - toret.data = get_data(src, length); - return toret; -} - -#define get_ber(bs) BinarySource_get_ber(BinarySource_UPCAST(bs)) - -/* ---------------------------------------------------------------------- - * Code to read and write OpenSSH private keys, in the old-style PEM - * format. - */ - -typedef enum { - OP_DSA, OP_RSA, OP_ECDSA -} openssh_pem_keytype; -typedef enum { - OP_E_3DES, OP_E_AES -} openssh_pem_enc; - -struct openssh_pem_key { - openssh_pem_keytype keytype; - bool encrypted; - openssh_pem_enc encryption; - char iv[32]; - strbuf *keyblob; -}; - -static void BinarySink_put_mp_ssh2_from_string(BinarySink *bs, ptrlen str) -{ - const unsigned char *bytes = (const unsigned char *)str.ptr; - size_t nbytes = str.len; - while (nbytes > 0 && bytes[0] == 0) { - nbytes--; - bytes++; - } - if (nbytes > 0 && bytes[0] & 0x80) { - put_uint32(bs, nbytes + 1); - put_byte(bs, 0); - } else { - put_uint32(bs, nbytes); - } - put_data(bs, bytes, nbytes); -} -#define put_mp_ssh2_from_string(bs, str) \ - BinarySink_put_mp_ssh2_from_string(BinarySink_UPCAST(bs), str) - -static struct openssh_pem_key *load_openssh_pem_key(BinarySource *src, - const char **errmsg_p) -{ - struct openssh_pem_key *ret; - char *line = NULL; - const char *errmsg; - char *p; - bool headers_done; - char base64_bit[4]; - int base64_chars = 0; - - ret = snew(struct openssh_pem_key); - ret->keyblob = strbuf_new_nm(); - - if (!(line = bsgetline(src))) { - errmsg = "unexpected end of file"; - goto error; - } - if (!strstartswith(line, "-----BEGIN ") || - !strendswith(line, "PRIVATE KEY-----")) { - errmsg = "file does not begin with OpenSSH key header"; - goto error; - } - /* - * Parse the BEGIN line. For old-format keys, this tells us the - * type of the key; for new-format keys, all it tells us is the - * format, and we'll find out the key type once we parse the - * base64. - */ - if (!strcmp(line, "-----BEGIN RSA PRIVATE KEY-----")) { - ret->keytype = OP_RSA; - } else if (!strcmp(line, "-----BEGIN DSA PRIVATE KEY-----")) { - ret->keytype = OP_DSA; - } else if (!strcmp(line, "-----BEGIN EC PRIVATE KEY-----")) { - ret->keytype = OP_ECDSA; - } else if (!strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) { - errmsg = "this is a new-style OpenSSH key"; - goto error; - } else { - errmsg = "unrecognised key type"; - goto error; - } - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - - ret->encrypted = false; - memset(ret->iv, 0, sizeof(ret->iv)); - - headers_done = false; - while (1) { - if (!(line = bsgetline(src))) { - errmsg = "unexpected end of file"; - goto error; - } - 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 = true; - } else if (!strcmp(line, "DEK-Info")) { - int i, ivlen; - - if (!strncmp(p, "DES-EDE3-CBC,", 13)) { - ret->encryption = OP_E_3DES; - ivlen = 8; - } else if (!strncmp(p, "AES-128-CBC,", 12)) { - ret->encryption = OP_E_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++) { - unsigned j; - 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 = true; - - 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; - } - - put_data(ret->keyblob, out, len); - - smemclr(out, sizeof(out)); - } - - p++; - } - } - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - } - - if (!ret->keyblob || ret->keyblob->len == 0) { - 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) - strbuf_free(ret->keyblob); - smemclr(ret, sizeof(*ret)); - sfree(ret); - } - if (errmsg_p) *errmsg_p = errmsg; - return NULL; -} - -static bool openssh_pem_encrypted(BinarySource *src) -{ - struct openssh_pem_key *key = load_openssh_pem_key(src, NULL); - bool ret; - - if (!key) - return false; - ret = key->encrypted; - strbuf_free(key->keyblob); - smemclr(key, sizeof(*key)); - sfree(key); - return ret; -} - -static void openssh_pem_derivekey( - ptrlen passphrase, const void *iv, uint8_t *keybuf) -{ - /* - * Derive the encryption key for a PEM key file from the - * 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.) - */ - ssh_hash *h; - - h = ssh_hash_new(&ssh_md5); - put_datapl(h, passphrase); - put_data(h, iv, 8); - ssh_hash_digest(h, keybuf); - - ssh_hash_reset(h); - put_data(h, keybuf, 16); - put_datapl(h, passphrase); - put_data(h, iv, 8); - ssh_hash_final(h, keybuf + 16); -} - -static ssh2_userkey *openssh_pem_read( - BinarySource *filesrc, const char *passphrase, const char **errmsg_p) -{ - struct openssh_pem_key *key = load_openssh_pem_key(filesrc, errmsg_p); - ssh2_userkey *retkey; - const ssh_keyalg *alg; - BinarySource src[1]; - int i, num_integers; - ssh2_userkey *retval = NULL; - const char *errmsg; - strbuf *blob = strbuf_new_nm(); - int privptr = 0, publen; - - if (!key) { - strbuf_free(blob); - return NULL; - } - - if (key->encrypted) { - unsigned char keybuf[32]; - openssh_pem_derivekey(ptrlen_from_asciz(passphrase), key->iv, keybuf); - - /* - * Decrypt the key blob. - */ - if (key->encryption == OP_E_3DES) - des3_decrypt_pubkey_ossh(keybuf, key->iv, - key->keyblob->u, key->keyblob->len); - else { - ssh_cipher *cipher = ssh_cipher_new(&ssh_aes128_cbc); - ssh_cipher_setkey(cipher, keybuf); - ssh_cipher_setiv(cipher, key->iv); - ssh_cipher_decrypt(cipher, key->keyblob->u, key->keyblob->len); - ssh_cipher_free(cipher); - } - - 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. - * - * - In ECDSA the format is totally different: we see the - * SEQUENCE, but beneath is an INTEGER 1, OCTET STRING priv - * EXPLICIT [0] OID curve, EXPLICIT [1] BIT STRING pubPoint - */ - - BinarySource_BARE_INIT(src, key->keyblob->u, key->keyblob->len); - - { - /* Expect the SEQUENCE header. Take its absence as a failure to - * decrypt, if the key was encrypted. */ - ber_item seq = get_ber(src); - if (get_err(src) || seq.id != 16) { - errmsg = "ASN.1 decoding failure"; - retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; - goto error; - } - - /* Reinitialise our BinarySource to parse just the inside of that - * SEQUENCE. */ - BinarySource_BARE_INIT_PL(src, seq.data); - } - - /* Expect a load of INTEGERs. */ - if (key->keytype == OP_RSA) - num_integers = 9; - else if (key->keytype == OP_DSA) - num_integers = 6; - else - num_integers = 0; /* placate compiler warnings */ - - - if (key->keytype == OP_ECDSA) { - /* And now for something completely different */ - ber_item integer, privkey, sub0, sub1, oid, pubkey; - const ssh_keyalg *alg; - const struct ec_curve *curve; - - /* Parse the outer layer of things inside the containing SEQUENCE */ - integer = get_ber(src); - privkey = get_ber(src); - sub0 = get_ber(src); - sub1 = get_ber(src); - - /* Now look inside sub0 for the curve OID */ - BinarySource_BARE_INIT_PL(src, sub0.data); - oid = get_ber(src); - - /* And inside sub1 for the public-key BIT STRING */ - BinarySource_BARE_INIT_PL(src, sub1.data); - pubkey = get_ber(src); - - if (get_err(src) || - integer.id != 2 || - integer.data.len != 1 || - ((const unsigned char *)integer.data.ptr)[0] != 1 || - privkey.id != 4 || - sub0.id != 0 || - sub1.id != 1 || - oid.id != 6 || - pubkey.id != 3) { - - errmsg = "ASN.1 decoding failure"; - retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; - goto error; - } - - alg = ec_alg_by_oid(oid.data.len, oid.data.ptr, &curve); - if (!alg) { - errmsg = "Unsupported ECDSA curve."; - retval = NULL; - goto error; - } - if (pubkey.data.len != ((((curve->fieldBits + 7) / 8) * 2) + 2)) { - errmsg = "ASN.1 decoding failure"; - retval = key->encrypted ? SSH2_WRONG_PASSPHRASE : NULL; - goto error; - } - /* Skip 0x00 before point */ - pubkey.data.ptr = (const char *)pubkey.data.ptr + 1; - pubkey.data.len -= 1; - - /* Construct the key */ - retkey = snew(ssh2_userkey); - - put_stringz(blob, alg->ssh_id); - put_stringz(blob, curve->name); - put_stringpl(blob, pubkey.data); - publen = blob->len; - put_mp_ssh2_from_string(blob, privkey.data); - - retkey->key = ssh_key_new_priv( - alg, make_ptrlen(blob->u, publen), - make_ptrlen(blob->u + publen, blob->len - publen)); - - if (!retkey->key) { - sfree(retkey); - errmsg = "unable to create key data structure"; - goto error; - } - - } else if (key->keytype == OP_RSA || key->keytype == OP_DSA) { - - put_stringz(blob, key->keytype == OP_DSA ? "ssh-dss" : "ssh-rsa"); - - ptrlen rsa_modulus = PTRLEN_LITERAL(""); - - for (i = 0; i < num_integers; i++) { - ber_item integer = get_ber(src); - - if (get_err(src) || integer.id != 2) { - 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 (integer.data.len != 1 || - ((const unsigned char *)integer.data.ptr)[0] != 0) { - errmsg = "version number mismatch"; - goto error; - } - } else if (key->keytype == OP_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. */ - rsa_modulus = integer.data; - } else if (i != 6 && i != 7) { - put_mp_ssh2_from_string(blob, integer.data); - if (i == 2) { - put_mp_ssh2_from_string(blob, rsa_modulus); - privptr = blob->len; - } - } - } else if (key->keytype == OP_DSA) { - /* - * Integers 1-4 go into the public blob; integer 5 goes - * into the private blob. - */ - put_mp_ssh2_from_string(blob, integer.data); - if (i == 4) - privptr = blob->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(ssh2_userkey); - alg = (key->keytype == OP_RSA ? &ssh_rsa : &ssh_dsa); - retkey->key = ssh_key_new_priv( - alg, make_ptrlen(blob->u, privptr), - make_ptrlen(blob->u+privptr, blob->len-privptr)); - - if (!retkey->key) { - sfree(retkey); - errmsg = "unable to create key data structure"; - goto error; - } - - } else { - unreachable("Bad key type from load_openssh_pem_key"); - errmsg = "Bad key type from load_openssh_pem_key"; - goto error; - } - - /* - * The old key format doesn't include a comment in the private - * key file. - */ - retkey->comment = dupstr("imported-openssh-key"); - - errmsg = NULL; /* no error */ - retval = retkey; - - error: - strbuf_free(blob); - strbuf_free(key->keyblob); - smemclr(key, sizeof(*key)); - sfree(key); - if (errmsg_p) *errmsg_p = errmsg; - return retval; -} - -static bool openssh_pem_write( - const Filename *filename, ssh2_userkey *ukey, const char *passphrase) -{ - strbuf *pubblob, *privblob, *outblob; - unsigned char *spareblob; - int sparelen = 0; - ptrlen numbers[9]; - int nnumbers, i; - const char *header, *footer; - char zero[1]; - unsigned char iv[8]; - bool ret = false; - FILE *fp; - BinarySource src[1]; - - /* OpenSSH's private key files never contain a certificate, so - * revert to the underlying base key if necessary */ - ssh_key *key = ssh_key_base_key(ukey->key); - - /* - * Fetch the key blobs. - */ - pubblob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(pubblob)); - privblob = strbuf_new_nm(); - ssh_key_private_blob(key, BinarySink_UPCAST(privblob)); - spareblob = NULL; - - outblob = strbuf_new_nm(); - - /* - * Encode the OpenSSH key blob, and also decide on the header - * line. - */ - if (ssh_key_alg(key) == &ssh_rsa || - ssh_key_alg(key) == &ssh_dsa) { - strbuf *seq; - - /* - * The RSA and DSA handlers share some code because the two - * key types have very similar ASN.1 representations, as a - * plain SEQUENCE of big integers. So we set up a list of - * bignums per key type and then construct the actual blob in - * common code after that. - */ - if (ssh_key_alg(key) == &ssh_rsa) { - ptrlen n, e, d, p, q, iqmp, dmp1, dmq1; - mp_int *bd, *bp, *bq, *bdmp1, *bdmq1; - - /* - * These blobs were generated from inside PuTTY, so we needn't - * treat them as untrusted. - */ - BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); - get_string(src); /* skip algorithm name */ - e = get_string(src); - n = get_string(src); - BinarySource_BARE_INIT(src, privblob->u, privblob->len); - d = get_string(src); - p = get_string(src); - q = get_string(src); - iqmp = get_string(src); - - assert(!get_err(src)); /* can't go wrong */ - - /* We also need d mod (p-1) and d mod (q-1). */ - bd = mp_from_bytes_be(d); - bp = mp_from_bytes_be(p); - bq = mp_from_bytes_be(q); - mp_sub_integer_into(bp, bp, 1); - mp_sub_integer_into(bq, bq, 1); - bdmp1 = mp_mod(bd, bp); - bdmq1 = mp_mod(bd, bq); - mp_free(bd); - mp_free(bp); - mp_free(bq); - - dmp1.len = (mp_get_nbits(bdmp1)+8)/8; - dmq1.len = (mp_get_nbits(bdmq1)+8)/8; - sparelen = dmp1.len + dmq1.len; - spareblob = snewn(sparelen, unsigned char); - dmp1.ptr = spareblob; - dmq1.ptr = spareblob + dmp1.len; - for (i = 0; i < dmp1.len; i++) - spareblob[i] = mp_get_byte(bdmp1, dmp1.len-1 - i); - for (i = 0; i < dmq1.len; i++) - spareblob[i+dmp1.len] = mp_get_byte(bdmq1, dmq1.len-1 - i); - mp_free(bdmp1); - mp_free(bdmq1); - - numbers[0] = make_ptrlen(zero, 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 { /* ssh-dss */ - ptrlen p, q, g, y, x; - - /* - * These blobs were generated from inside PuTTY, so we needn't - * treat them as untrusted. - */ - BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); - get_string(src); /* skip algorithm name */ - p = get_string(src); - q = get_string(src); - g = get_string(src); - y = get_string(src); - BinarySource_BARE_INIT(src, privblob->u, privblob->len); - x = get_string(src); - - assert(!get_err(src)); /* can't go wrong */ - - numbers[0].ptr = zero; numbers[0].len = 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"; - } - - seq = strbuf_new_nm(); - for (i = 0; i < nnumbers; i++) { - put_ber_id_len(seq, 2, numbers[i].len, 0); - put_datapl(seq, numbers[i]); - } - put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED); - put_data(outblob, seq->s, seq->len); - strbuf_free(seq); - } else if (ssh_key_alg(key) == &ssh_ecdsa_nistp256 || - ssh_key_alg(key) == &ssh_ecdsa_nistp384 || - ssh_key_alg(key) == &ssh_ecdsa_nistp521) { - const unsigned char *oid; - struct ecdsa_key *ec = container_of(key, struct ecdsa_key, sshk); - int oidlen; - int pointlen; - strbuf *seq, *sub; - - /* - * Structure of asn1: - * SEQUENCE - * INTEGER 1 - * OCTET STRING (private key) - * [0] - * OID (curve) - * [1] - * BIT STRING (0x00 public key point) - */ - oid = ec_alg_oid(ssh_key_alg(key), &oidlen); - pointlen = (ec->curve->fieldBits + 7) / 8 * 2; - - seq = strbuf_new_nm(); - - /* INTEGER 1 */ - put_ber_id_len(seq, 2, 1, 0); - put_byte(seq, 1); - - /* OCTET STRING private key */ - put_ber_id_len(seq, 4, privblob->len - 4, 0); - put_data(seq, privblob->s + 4, privblob->len - 4); - - /* Subsidiary OID */ - sub = strbuf_new(); - put_ber_id_len(sub, 6, oidlen, 0); - put_data(sub, oid, oidlen); - - /* Append the OID to the sequence */ - put_ber_id_len(seq, 0, sub->len, - ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); - put_data(seq, sub->s, sub->len); - strbuf_free(sub); - - /* Subsidiary BIT STRING */ - sub = strbuf_new(); - put_ber_id_len(sub, 3, 2 + pointlen, 0); - put_byte(sub, 0); - put_data(sub, pubblob->s+39, 1 + pointlen); - - /* Append the BIT STRING to the sequence */ - put_ber_id_len(seq, 1, sub->len, - ASN1_CLASS_CONTEXT_SPECIFIC | ASN1_CONSTRUCTED); - put_data(seq, sub->s, sub->len); - strbuf_free(sub); - - /* Write the full sequence with header to the output blob. */ - put_ber_id_len(outblob, 16, seq->len, ASN1_CONSTRUCTED); - put_data(outblob, seq->s, seq->len); - strbuf_free(seq); - - header = "-----BEGIN EC PRIVATE KEY-----\n"; - footer = "-----END EC PRIVATE KEY-----\n"; - } else { - unreachable("bad key alg in openssh_pem_write"); - } - - /* - * Encrypt the key. - * - * For the moment, we still encrypt our OpenSSH keys using - * old-style 3DES. - */ - if (passphrase) { - unsigned char keybuf[32]; - int origlen, outlen, pad; - - /* - * 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. - */ - origlen = outblob->len; - outlen = (origlen + 8) &~ 7; - pad = outlen - origlen; - put_padding(outblob, pad, pad); - - /* - * Invent an iv, and derive the encryption key. - */ - random_read(iv, 8); - - openssh_pem_derivekey(ptrlen_from_asciz(passphrase), iv, keybuf); - - /* - * Now encrypt the key blob. - */ - des3_encrypt_pubkey_ossh(keybuf, iv, - outblob->u, outlen); - - 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(fp, ptrlen_from_strbuf(outblob), 64); - fputs(footer, fp); - fclose(fp); - ret = true; - - error: - if (outblob) - strbuf_free(outblob); - if (spareblob) { - smemclr(spareblob, sparelen); - sfree(spareblob); - } - if (privblob) - strbuf_free(privblob); - if (pubblob) - strbuf_free(pubblob); - return ret; -} - -/* ---------------------------------------------------------------------- - * Code to read and write OpenSSH private keys in the new-style format. - */ - -typedef enum { - ON_E_NONE, ON_E_AES256CBC, ON_E_AES256CTR -} openssh_new_cipher; -typedef enum { - ON_K_NONE, ON_K_BCRYPT -} openssh_new_kdf; - -struct openssh_new_key { - openssh_new_cipher cipher; - openssh_new_kdf kdf; - union { - struct { - int rounds; - /* This points to a position within keyblob, not a - * separately allocated thing */ - ptrlen salt; - } bcrypt; - } kdfopts; - int nkeys, key_wanted; - /* This too points to a position within keyblob */ - ptrlen private; - - strbuf *keyblob; -}; - -static struct openssh_new_key *load_openssh_new_key(BinarySource *filesrc, - const char **errmsg_p) -{ - struct openssh_new_key *ret; - char *line = NULL; - const char *errmsg; - char *p; - char base64_bit[4]; - int base64_chars = 0; - BinarySource src[1]; - ptrlen str; - unsigned key_index; - - ret = snew(struct openssh_new_key); - ret->keyblob = strbuf_new_nm(); - - if (!(line = bsgetline(filesrc))) { - errmsg = "unexpected end of file"; - goto error; - } - if (0 != strcmp(line, "-----BEGIN OPENSSH PRIVATE KEY-----")) { - errmsg = "file does not begin with OpenSSH new-style key header"; - goto error; - } - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - - while (1) { - if (!(line = bsgetline(filesrc))) { - errmsg = "unexpected end of file"; - goto error; - } - if (0 == strcmp(line, "-----END OPENSSH PRIVATE KEY-----")) { - sfree(line); - line = NULL; - break; /* done */ - } - - 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; - } - - put_data(ret->keyblob, out, len); - - smemclr(out, sizeof(out)); - } - - p++; - } - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - } - - if (ret->keyblob->len == 0) { - errmsg = "key body not present"; - goto error; - } - - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(ret->keyblob)); - - if (strcmp(get_asciz(src), "openssh-key-v1") != 0) { - errmsg = "new-style OpenSSH magic number missing\n"; - goto error; - } - - /* Cipher name */ - str = get_string(src); - if (ptrlen_eq_string(str, "none")) { - ret->cipher = ON_E_NONE; - } else if (ptrlen_eq_string(str, "aes256-cbc")) { - ret->cipher = ON_E_AES256CBC; - } else if (ptrlen_eq_string(str, "aes256-ctr")) { - ret->cipher = ON_E_AES256CTR; - } else { - errmsg = get_err(src) ? "no cipher name found" : - "unrecognised cipher name\n"; - goto error; - } - - /* Key derivation function name */ - str = get_string(src); - if (ptrlen_eq_string(str, "none")) { - ret->kdf = ON_K_NONE; - } else if (ptrlen_eq_string(str, "bcrypt")) { - ret->kdf = ON_K_BCRYPT; - } else { - errmsg = get_err(src) ? "no kdf name found" : - "unrecognised kdf name\n"; - goto error; - } - - /* KDF extra options */ - str = get_string(src); - switch (ret->kdf) { - case ON_K_NONE: - if (str.len != 0) { - errmsg = "expected empty options string for 'none' kdf"; - goto error; - } - break; - case ON_K_BCRYPT: { - BinarySource opts[1]; - - BinarySource_BARE_INIT_PL(opts, str); - ret->kdfopts.bcrypt.salt = get_string(opts); - ret->kdfopts.bcrypt.rounds = get_uint32(opts); - - if (get_err(opts)) { - errmsg = "failed to parse bcrypt options string"; - goto error; - } - break; - } - } - - /* - * At this point we expect a uint32 saying how many keys are - * stored in this file. OpenSSH new-style key files can - * contain more than one. Currently we don't have any user - * interface to specify which one we're trying to extract, so - * we just bomb out with an error if more than one is found in - * the file. However, I've put in all the mechanism here to - * extract the nth one for a given n, in case we later connect - * up some UI to that mechanism. Just arrange that the - * 'key_wanted' field is set to a value in the range [0, - * nkeys) by some mechanism. - */ - ret->nkeys = toint(get_uint32(src)); - if (ret->nkeys != 1) { - errmsg = get_err(src) ? "no key count found" : - "multiple keys in new-style OpenSSH key file not supported\n"; - goto error; - } - ret->key_wanted = 0; - - /* Read and ignore a string per public key. */ - for (key_index = 0; key_index < ret->nkeys; key_index++) - str = get_string(src); - - /* - * Now we expect a string containing the encrypted part of the - * key file. - */ - ret->private = get_string(src); - if (get_err(src)) { - errmsg = "no private key container string found\n"; - goto error; - } - - /* - * And now we're done, until asked to actually decrypt. - */ - - 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) { - strbuf_free(ret->keyblob); - smemclr(ret, sizeof(*ret)); - sfree(ret); - } - if (errmsg_p) *errmsg_p = errmsg; - return NULL; -} - -static bool openssh_new_encrypted(BinarySource *src) -{ - struct openssh_new_key *key = load_openssh_new_key(src, NULL); - bool ret; - - if (!key) - return false; - ret = (key->cipher != ON_E_NONE); - strbuf_free(key->keyblob); - smemclr(key, sizeof(*key)); - sfree(key); - return ret; -} - -static ssh2_userkey *openssh_new_read( - BinarySource *filesrc, const char *passphrase, const char **errmsg_p) -{ - struct openssh_new_key *key = load_openssh_new_key(filesrc, errmsg_p); - ssh2_userkey *retkey = NULL; - ssh2_userkey *retval = NULL; - const char *errmsg; - unsigned checkint; - BinarySource src[1]; - int key_index; - const ssh_keyalg *alg = NULL; - - if (!key) - return NULL; - - if (key->cipher != ON_E_NONE) { - unsigned char keybuf[48]; - int keysize; - - /* - * Construct the decryption key, and decrypt the string. - */ - switch (key->cipher) { - case ON_E_NONE: - keysize = 0; - break; - case ON_E_AES256CBC: - case ON_E_AES256CTR: - keysize = 48; /* 32 byte key + 16 byte IV */ - break; - default: - unreachable("Bad cipher enumeration value"); - } - assert(keysize <= sizeof(keybuf)); - switch (key->kdf) { - case ON_K_NONE: - memset(keybuf, 0, keysize); - break; - case ON_K_BCRYPT: - openssh_bcrypt(ptrlen_from_asciz(passphrase), - key->kdfopts.bcrypt.salt, - key->kdfopts.bcrypt.rounds, - keybuf, keysize); - break; - default: - unreachable("Bad kdf enumeration value"); - } - switch (key->cipher) { - case ON_E_NONE: - break; - case ON_E_AES256CBC: - case ON_E_AES256CTR: - if (key->private.len % 16 != 0) { - errmsg = "private key container length is not a" - " multiple of AES block size\n"; - goto error; - } - { - ssh_cipher *cipher = ssh_cipher_new( - key->cipher == ON_E_AES256CBC ? - &ssh_aes256_cbc : &ssh_aes256_sdctr); - ssh_cipher_setkey(cipher, keybuf); - ssh_cipher_setiv(cipher, keybuf + 32); - /* Decrypt the private section in place, casting away - * the const from key->private being a ptrlen */ - ssh_cipher_decrypt(cipher, (char *)key->private.ptr, - key->private.len); - ssh_cipher_free(cipher); - } - break; - default: - unreachable("Bad cipher enumeration value"); - } - } - - /* - * Now parse the entire encrypted section, and extract the key - * identified by key_wanted. - */ - BinarySource_BARE_INIT_PL(src, key->private); - - checkint = get_uint32(src); - if (get_uint32(src) != checkint || get_err(src)) { - errmsg = "decryption check failed"; - goto error; - } - - retkey = snew(ssh2_userkey); - retkey->key = NULL; - retkey->comment = NULL; - - for (key_index = 0; key_index < key->nkeys; key_index++) { - ptrlen comment; - - /* - * Identify the key type. - */ - alg = find_pubkey_alg_len(get_string(src)); - if (!alg) { - errmsg = "private key type not recognised\n"; - goto error; - } - - /* - * Read the key. We have to do this even if it's not the one - * we want, because it's the only way to find out how much - * data to skip past to get to the next key in the file. - */ - retkey->key = ssh_key_new_priv_openssh(alg, src); - if (get_err(src)) { - errmsg = "unable to read entire private key"; - goto error; - } - if (!retkey->key) { - errmsg = "unable to create key data structure"; - goto error; - } - if (key_index != key->key_wanted) { - /* - * If this isn't the key we're looking for, throw it away. - */ - ssh_key_free(retkey->key); - retkey->key = NULL; - } - - /* - * Read the key comment. - */ - comment = get_string(src); - if (get_err(src)) { - errmsg = "unable to read key comment"; - goto error; - } - if (key_index == key->key_wanted) { - assert(retkey); - retkey->comment = mkstr(comment); - } - } - - if (!retkey->key) { - errmsg = "key index out of range"; - goto error; - } - - /* - * Now we expect nothing left but padding. - */ - { - unsigned char expected_pad_byte = 1; - while (get_avail(src) > 0) - if (get_byte(src) != expected_pad_byte++) { - errmsg = "padding at end of private string did not match"; - goto error; - } - } - - errmsg = NULL; /* no error */ - retval = retkey; - retkey = NULL; /* prevent the free */ - - error: - if (retkey) { - sfree(retkey->comment); - if (retkey->key) - ssh_key_free(retkey->key); - sfree(retkey); - } - strbuf_free(key->keyblob); - smemclr(key, sizeof(*key)); - sfree(key); - if (errmsg_p) *errmsg_p = errmsg; - return retval; -} - -static bool openssh_new_write( - const Filename *filename, ssh2_userkey *ukey, const char *passphrase) -{ - strbuf *pubblob, *privblob, *cblob; - int padvalue; - unsigned checkint; - bool ret = false; - unsigned char bcrypt_salt[16]; - const int bcrypt_rounds = 16; - FILE *fp; - - /* OpenSSH's private key files never contain a certificate, so - * revert to the underlying base key if necessary */ - ssh_key *key = ssh_key_base_key(ukey->key); - - /* - * Fetch the key blobs and find out the lengths of things. - */ - pubblob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(pubblob)); - privblob = strbuf_new_nm(); - ssh_key_openssh_blob(key, BinarySink_UPCAST(privblob)); - - /* - * Construct the cleartext version of the blob. - */ - cblob = strbuf_new_nm(); - - /* Magic number. */ - put_asciz(cblob, "openssh-key-v1"); - - /* Cipher and kdf names, and kdf options. */ - if (!passphrase) { - memset(bcrypt_salt, 0, sizeof(bcrypt_salt)); /* prevent warnings */ - put_stringz(cblob, "none"); - put_stringz(cblob, "none"); - put_stringz(cblob, ""); - } else { - strbuf *substr; - - random_read(bcrypt_salt, sizeof(bcrypt_salt)); - put_stringz(cblob, "aes256-ctr"); - put_stringz(cblob, "bcrypt"); - substr = strbuf_new_nm(); - put_string(substr, bcrypt_salt, sizeof(bcrypt_salt)); - put_uint32(substr, bcrypt_rounds); - put_stringsb(cblob, substr); - } - - /* Number of keys. */ - put_uint32(cblob, 1); - - /* Public blob. */ - put_string(cblob, pubblob->s, pubblob->len); - - /* Private section. */ - { - strbuf *cpblob = strbuf_new_nm(); - - /* checkint. */ - uint8_t checkint_buf[4]; - random_read(checkint_buf, 4); - checkint = GET_32BIT_MSB_FIRST(checkint_buf); - put_uint32(cpblob, checkint); - put_uint32(cpblob, checkint); - - /* Private key. The main private blob goes inline, with no string - * wrapper. */ - put_stringz(cpblob, ssh_key_ssh_id(key)); - put_data(cpblob, privblob->s, privblob->len); - - /* Comment. */ - put_stringz(cpblob, ukey->comment); - - /* Pad out the encrypted section. */ - padvalue = 1; - do { - put_byte(cpblob, padvalue++); - } while (cpblob->len & 15); - - if (passphrase) { - /* - * Encrypt the private section. We need 48 bytes of key - * material: 32 bytes AES key + 16 bytes iv. - */ - unsigned char keybuf[48]; - ssh_cipher *cipher; - - openssh_bcrypt(ptrlen_from_asciz(passphrase), - make_ptrlen(bcrypt_salt, sizeof(bcrypt_salt)), - bcrypt_rounds, keybuf, sizeof(keybuf)); - - cipher = ssh_cipher_new(&ssh_aes256_sdctr); - ssh_cipher_setkey(cipher, keybuf); - ssh_cipher_setiv(cipher, keybuf + 32); - ssh_cipher_encrypt(cipher, cpblob->u, cpblob->len); - ssh_cipher_free(cipher); - - smemclr(keybuf, sizeof(keybuf)); - } - - put_stringsb(cblob, cpblob); - } - - /* - * 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 OPENSSH PRIVATE KEY-----\n", fp); - base64_encode_fp(fp, ptrlen_from_strbuf(cblob), 64); - fputs("-----END OPENSSH PRIVATE KEY-----\n", fp); - fclose(fp); - ret = true; - - error: - if (cblob) - strbuf_free(cblob); - if (privblob) - strbuf_free(privblob); - if (pubblob) - strbuf_free(pubblob); - return ret; -} - -/* ---------------------------------------------------------------------- - * The switch function openssh_auto_write(), which chooses one of the - * concrete OpenSSH output formats based on the key type. - */ -static bool openssh_auto_write( - const Filename *filename, ssh2_userkey *key, const char *passphrase) -{ - /* - * The old OpenSSH format supports a fixed list of key types. We - * assume that anything not in that fixed list is newer, and hence - * will use the new format. - */ - const ssh_keyalg *alg = ssh_key_alg(ssh_key_base_key(key->key)); - if (alg == &ssh_dsa || - alg == &ssh_rsa || - alg == &ssh_ecdsa_nistp256 || - alg == &ssh_ecdsa_nistp384 || - alg == &ssh_ecdsa_nistp521) - return openssh_pem_write(filename, key, passphrase); - else - return openssh_new_write(filename, key, passphrase); -} - -/* ---------------------------------------------------------------------- - * 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 */ - strbuf *keyblob; -}; - -static struct sshcom_key *load_sshcom_key(BinarySource *src, - const char **errmsg_p) -{ - struct sshcom_key *ret; - char *line = NULL; - int hdrstart, len; - const char *errmsg; - char *p; - bool headers_done; - char base64_bit[4]; - int base64_chars = 0; - - ret = snew(struct sshcom_key); - ret->comment[0] = '\0'; - ret->keyblob = strbuf_new_nm(); - - if (!(line = bsgetline(src))) { - errmsg = "unexpected end of file"; - goto error; - } - 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 = false; - while (1) { - if (!(line = bsgetline(src))) { - errmsg = "unexpected end of file"; - goto error; - } - 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 = bsgetline(src); - if (!line2) { - errmsg = "unexpected end of file"; - goto error; - } - - 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; - 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 = true; - - 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; - } - - put_data(ret->keyblob, out, len); - } - - p++; - } - } - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - } - - if (ret->keyblob->len == 0) { - errmsg = "key body not present"; - goto error; - } - - if (errmsg_p) *errmsg_p = NULL; - return ret; - - error: - if (line) { - smemclr(line, strlen(line)); - sfree(line); - line = NULL; - } - if (ret) { - strbuf_free(ret->keyblob); - smemclr(ret, sizeof(*ret)); - sfree(ret); - } - if (errmsg_p) *errmsg_p = errmsg; - return NULL; -} - -static bool sshcom_encrypted(BinarySource *filesrc, char **comment) -{ - struct sshcom_key *key = load_sshcom_key(filesrc, NULL); - BinarySource src[1]; - ptrlen str; - bool answer = false; - - *comment = NULL; - if (!key) - goto done; - - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->keyblob)); - - if (get_uint32(src) != SSHCOM_MAGIC_NUMBER) - goto done; /* key is invalid */ - get_uint32(src); /* skip length field */ - get_string(src); /* skip key type */ - str = get_string(src); /* cipher type */ - if (get_err(src)) - goto done; /* key is invalid */ - if (!ptrlen_eq_string(str, "none")) - answer = true; - - done: - if (key) { - *comment = dupstr(key->comment); - strbuf_free(key->keyblob); - smemclr(key, sizeof(*key)); - sfree(key); - } else { - *comment = dupstr(""); - } - return answer; -} - -static void BinarySink_put_mp_sshcom_from_string(BinarySink *bs, ptrlen str) -{ - const unsigned char *bytes = (const unsigned char *)str.ptr; - size_t nbytes = str.len; - int bits = nbytes * 8 - 1; - - while (bits > 0) { - if (*bytes & (1 << (bits & 7))) - break; - if (!(bits-- & 7)) - bytes++, nbytes--; - } - - put_uint32(bs, bits+1); - put_data(bs, bytes, nbytes); -} - -#define put_mp_sshcom_from_string(bs, str) \ - BinarySink_put_mp_sshcom_from_string(BinarySink_UPCAST(bs), str) - -static ptrlen BinarySource_get_mp_sshcom_as_string(BinarySource *src) -{ - unsigned bits = get_uint32(src); - return get_data(src, (bits + 7) / 8); -} - -#define get_mp_sshcom_as_string(bs) \ - BinarySource_get_mp_sshcom_as_string(BinarySource_UPCAST(bs)) - -static void sshcom_derivekey(ptrlen passphrase, uint8_t *keybuf) -{ - /* - * Derive the encryption key for an ssh.com key file from the - * 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 - */ - ssh_hash *h; - - h = ssh_hash_new(&ssh_md5); - put_datapl(h, passphrase); - ssh_hash_digest_nondestructive(h, keybuf); - put_data(h, keybuf, 16); - ssh_hash_final(h, keybuf + 16); -} - -static ssh2_userkey *sshcom_read( - BinarySource *filesrc, const char *passphrase, const char **errmsg_p) -{ - struct sshcom_key *key = load_sshcom_key(filesrc, errmsg_p); - const char *errmsg; - BinarySource src[1]; - ptrlen str, ciphertext; - int publen; - const char prefix_rsa[] = "if-modn{sign{rsa"; - const char prefix_dsa[] = "dl-modp{sign{dsa"; - enum { RSA, DSA } type; - bool encrypted; - ssh2_userkey *ret = NULL, *retkey; - const ssh_keyalg *alg; - strbuf *blob = NULL; - - if (!key) - return NULL; - - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->keyblob)); - - if (get_uint32(src) != SSHCOM_MAGIC_NUMBER) { - errmsg = "key does not begin with magic number"; - goto error; - } - get_uint32(src); /* skip length field */ - - /* - * Determine the key type. - */ - str = get_string(src); - if (str.len > sizeof(prefix_rsa) - 1 && - !memcmp(str.ptr, prefix_rsa, sizeof(prefix_rsa) - 1)) { - type = RSA; - } else if (str.len > sizeof(prefix_dsa) - 1 && - !memcmp(str.ptr, prefix_dsa, sizeof(prefix_dsa) - 1)) { - type = DSA; - } else { - errmsg = "key is of unknown type"; - goto error; - } - - /* - * Determine the cipher type. - */ - str = get_string(src); - if (ptrlen_eq_string(str, "none")) - encrypted = false; - else if (ptrlen_eq_string(str, "3des-cbc")) - encrypted = true; - else { - errmsg = "key encryption is of unknown type"; - goto error; - } - - /* - * Get hold of the encrypted part of the key. - */ - ciphertext = get_string(src); - if (ciphertext.len == 0) { - errmsg = "no key data found"; - 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 - */ - unsigned char keybuf[32], iv[8]; - - if (ciphertext.len % 8 != 0) { - errmsg = "encrypted part of key is not a multiple of cipher block" - " size"; - goto error; - } - - sshcom_derivekey(ptrlen_from_asciz(passphrase), keybuf); - - /* - * Now decrypt the key blob in place (casting away const from - * ciphertext being a ptrlen). - */ - memset(iv, 0, sizeof(iv)); - des3_decrypt_pubkey_ossh(keybuf, iv, - (char *)ciphertext.ptr, ciphertext.len); - - 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; - } - - /* - * Expect the ciphertext to be formatted as a containing string, - * and reinitialise src to start parsing the inside of that string. - */ - BinarySource_BARE_INIT_PL(src, ciphertext); - str = get_string(src); - if (get_err(src)) { - errmsg = "containing string was ill-formed"; - goto error; - } - BinarySource_BARE_INIT_PL(src, str); - - /* - * 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 ssh_key_new_priv(). - */ - blob = strbuf_new_nm(); - if (type == RSA) { - ptrlen n, e, d, u, p, q; - - e = get_mp_sshcom_as_string(src); - d = get_mp_sshcom_as_string(src); - n = get_mp_sshcom_as_string(src); - u = get_mp_sshcom_as_string(src); - p = get_mp_sshcom_as_string(src); - q = get_mp_sshcom_as_string(src); - if (get_err(src)) { - errmsg = "key data did not contain six integers"; - goto error; - } - - alg = &ssh_rsa; - put_stringz(blob, "ssh-rsa"); - put_mp_ssh2_from_string(blob, e); - put_mp_ssh2_from_string(blob, n); - publen = blob->len; - put_mp_ssh2_from_string(blob, d); - put_mp_ssh2_from_string(blob, q); - put_mp_ssh2_from_string(blob, p); - put_mp_ssh2_from_string(blob, u); - } else { - ptrlen p, q, g, x, y; - - assert(type == DSA); /* the only other option from the if above */ - - if (get_uint32(src) != 0) { - errmsg = "predefined DSA parameters not supported"; - goto error; - } - p = get_mp_sshcom_as_string(src); - g = get_mp_sshcom_as_string(src); - q = get_mp_sshcom_as_string(src); - y = get_mp_sshcom_as_string(src); - x = get_mp_sshcom_as_string(src); - if (get_err(src)) { - errmsg = "key data did not contain five integers"; - goto error; - } - - alg = &ssh_dsa; - put_stringz(blob, "ssh-dss"); - put_mp_ssh2_from_string(blob, p); - put_mp_ssh2_from_string(blob, q); - put_mp_ssh2_from_string(blob, g); - put_mp_ssh2_from_string(blob, y); - publen = blob->len; - put_mp_ssh2_from_string(blob, x); - } - - retkey = snew(ssh2_userkey); - retkey->key = ssh_key_new_priv( - alg, make_ptrlen(blob->u, publen), - make_ptrlen(blob->u + publen, blob->len - publen)); - if (!retkey->key) { - 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) { - strbuf_free(blob); - } - strbuf_free(key->keyblob); - smemclr(key, sizeof(*key)); - sfree(key); - if (errmsg_p) *errmsg_p = errmsg; - return ret; -} - -static bool sshcom_write( - const Filename *filename, ssh2_userkey *key, const char *passphrase) -{ - strbuf *pubblob, *privblob, *outblob; - ptrlen numbers[6]; - int nnumbers, lenpos, i; - bool initial_zero; - BinarySource src[1]; - const char *type; - char *ciphertext; - int cipherlen; - bool ret = false; - FILE *fp; - - /* - * Fetch the key blobs. - */ - pubblob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); - privblob = strbuf_new_nm(); - ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob)); - outblob = NULL; - - /* - * Find the sequence of integers to be encoded into the OpenSSH - * key blob, and also decide on the header line. - */ - if (ssh_key_alg(key->key) == &ssh_rsa) { - ptrlen n, e, d, p, q, iqmp; - - /* - * These blobs were generated from inside PuTTY, so we needn't - * treat them as untrusted. - */ - BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); - get_string(src); /* skip algorithm name */ - e = get_string(src); - n = get_string(src); - BinarySource_BARE_INIT(src, privblob->u, privblob->len); - d = get_string(src); - p = get_string(src); - q = get_string(src); - iqmp = get_string(src); - - assert(!get_err(src)); /* 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 = false; - type = "if-modn{sign{rsa-pkcs1-sha1},encrypt{rsa-pkcs1v2-oaep}}"; - } else if (ssh_key_alg(key->key) == &ssh_dsa) { - ptrlen p, q, g, y, x; - - /* - * These blobs were generated from inside PuTTY, so we needn't - * treat them as untrusted. - */ - BinarySource_BARE_INIT(src, pubblob->u, pubblob->len); - get_string(src); /* skip algorithm name */ - p = get_string(src); - q = get_string(src); - g = get_string(src); - y = get_string(src); - BinarySource_BARE_INIT(src, privblob->u, privblob->len); - x = get_string(src); - - assert(!get_err(src)); /* can't go wrong */ - - numbers[0] = p; - numbers[1] = g; - numbers[2] = q; - numbers[3] = y; - numbers[4] = x; - - nnumbers = 5; - initial_zero = true; - type = "dl-modp{sign{dsa-nist-sha1},dh{plain}}"; - } else { - goto error; /* unsupported key type */ - } - - outblob = strbuf_new_nm(); - - /* - * Create the unencrypted key blob. - */ - put_uint32(outblob, SSHCOM_MAGIC_NUMBER); - put_uint32(outblob, 0); /* length field, fill in later */ - put_stringz(outblob, type); - put_stringz(outblob, passphrase ? "3des-cbc" : "none"); - lenpos = outblob->len; /* remember this position */ - put_uint32(outblob, 0); /* encrypted-blob size */ - put_uint32(outblob, 0); /* encrypted-payload size */ - if (initial_zero) - put_uint32(outblob, 0); - for (i = 0; i < nnumbers; i++) - put_mp_sshcom_from_string(outblob, numbers[i]); - /* Now wrap up the encrypted payload. */ - PUT_32BIT_MSB_FIRST(outblob->s + lenpos + 4, - outblob->len - (lenpos + 8)); - /* Pad encrypted blob to a multiple of cipher block size. */ - if (passphrase) { - int padding = -(outblob->len - (lenpos+4)) & 7; - uint8_t padding_buf[8]; - random_read(padding_buf, padding); - put_data(outblob, padding_buf, padding); - } - ciphertext = outblob->s + lenpos + 4; - cipherlen = outblob->len - (lenpos + 4); - assert(!passphrase || cipherlen % 8 == 0); - /* Wrap up the encrypted blob string. */ - PUT_32BIT_MSB_FIRST(outblob->s + lenpos, cipherlen); - /* And finally fill in the total length field. */ - PUT_32BIT_MSB_FIRST(outblob->s + 4, outblob->len); - - /* - * Encrypt the key. - */ - if (passphrase) { - unsigned char keybuf[32], iv[8]; - - sshcom_derivekey(ptrlen_from_asciz(passphrase), keybuf); - - /* - * Now decrypt the key blob. - */ - memset(iv, 0, sizeof(iv)); - des3_encrypt_pubkey_ossh(keybuf, iv, ciphertext, cipherlen); - - 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(fp, ptrlen_from_strbuf(outblob), 70); - fputs("---- END SSH2 ENCRYPTED PRIVATE KEY ----\n", fp); - fclose(fp); - ret = true; - - error: - if (outblob) - strbuf_free(outblob); - if (privblob) - strbuf_free(privblob); - if (pubblob) - strbuf_free(pubblob); - return ret; -} diff --git a/keygen/CMakeLists.txt b/keygen/CMakeLists.txt deleted file mode 100644 index 17eea2b15..000000000 --- a/keygen/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -add_sources_from_current_dir(keygen - dsa.c - ecdsa.c - millerrabin.c - mpunsafe.c - pockle.c - prime.c - primecandidate.c - rsa.c - smallprimes.c) diff --git a/keygen/dsa.c b/keygen/dsa.c deleted file mode 100644 index a7ea4f53b..000000000 --- a/keygen/dsa.c +++ /dev/null @@ -1,103 +0,0 @@ -/* - * DSA key generation. - */ - -#include "misc.h" -#include "ssh.h" -#include "sshkeygen.h" -#include "mpint.h" - -int dsa_generate(struct dsa_key *key, int bits, PrimeGenerationContext *pgc, - ProgressReceiver *prog) -{ - /* - * Progress-reporting setup. - * - * DSA generation involves three potentially long jobs: inventing - * the small prime q, the large prime p, and finding an order-q - * element of the multiplicative group of p. - * - * The latter is done by finding an element whose order is - * _divisible_ by q and raising it to the power of (p-1)/q. Every - * element whose order is not divisible by q is a qth power of q - * distinct elements whose order _is_ divisible by q, so the - * probability of not finding a suitable element on the first try - * is in the region of 1/q, i.e. at most 2^-159. - * - * (So the probability of success will end up indistinguishable - * from 1 in IEEE standard floating point! But what can you do.) - */ - ProgressPhase phase_q = primegen_add_progress_phase(pgc, prog, 160); - ProgressPhase phase_p = primegen_add_progress_phase(pgc, prog, bits); - double g_failure_probability = 1.0 - / (double)(1ULL << 53) - / (double)(1ULL << 53) - / (double)(1ULL << 53); - ProgressPhase phase_g = progress_add_probabilistic( - prog, estimate_modexp_cost(bits), 1.0 - g_failure_probability); - progress_ready(prog); - - PrimeCandidateSource *pcs; - - /* - * Generate q: a prime of length 160. - */ - progress_start_phase(prog, phase_q); - pcs = pcs_new(160); - mp_int *q = primegen_generate(pgc, pcs, prog); - progress_report_phase_complete(prog); - - /* - * Now generate p: a prime of length `bits', such that p-1 is - * divisible by q. - */ - progress_start_phase(prog, phase_p); - pcs = pcs_new(bits); - pcs_require_residue_1_mod_prime(pcs, q); - mp_int *p = primegen_generate(pgc, pcs, prog); - progress_report_phase_complete(prog); - - /* - * 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. - */ - progress_start_phase(prog, phase_g); - mp_int *power = mp_div(p, q); /* this is floor(p/q) == (p-1)/q */ - mp_int *h = mp_from_integer(2); - mp_int *g; - while (1) { - progress_report_attempt(prog); - g = mp_modpow(h, power, p); - if (mp_hs_integer(g, 2)) - break; /* got one */ - mp_free(g); - mp_add_integer_into(h, h, 1); - } - mp_free(h); - mp_free(power); - progress_report_phase_complete(prog); - - /* - * 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. - */ - mp_int *two = mp_from_integer(2); - mp_int *qm1 = mp_copy(q); - mp_sub_integer_into(qm1, qm1, 1); - mp_int *x = mp_random_in_range(two, qm1); - mp_free(two); - mp_free(qm1); - - key->sshk.vt = &ssh_dsa; - - key->p = p; - key->q = q; - key->g = g; - key->x = x; - key->y = mp_modpow(key->g, key->x, key->p); - - return 1; -} diff --git a/keygen/ecdsa.c b/keygen/ecdsa.c deleted file mode 100644 index 28a723b2d..000000000 --- a/keygen/ecdsa.c +++ /dev/null @@ -1,37 +0,0 @@ -/* - * EC key generation. - */ - -#include "ssh.h" -#include "sshkeygen.h" -#include "mpint.h" - -int ecdsa_generate(struct ecdsa_key *ek, int bits) -{ - if (!ec_nist_alg_and_curve_by_bits(bits, &ek->curve, &ek->sshk.vt)) - return 0; - - mp_int *one = mp_from_integer(1); - ek->privateKey = mp_random_in_range(one, ek->curve->w.G_order); - mp_free(one); - - ek->publicKey = ecdsa_public(ek->privateKey, ek->sshk.vt); - - return 1; -} - -int eddsa_generate(struct eddsa_key *ek, int bits) -{ - if (!ec_ed_alg_and_curve_by_bits(bits, &ek->curve, &ek->sshk.vt)) - return 0; - - /* EdDSA secret keys are just 32 bytes of hash preimage; the - * 64-byte SHA-512 hash of that key will be used when signing, - * but the form of the key stored on disk is the preimage - * only. */ - ek->privateKey = mp_random_bits(bits); - - ek->publicKey = eddsa_public(ek->privateKey, ek->sshk.vt); - - return 1; -} diff --git a/keygen/millerrabin.c b/keygen/millerrabin.c deleted file mode 100644 index 24ee61930..000000000 --- a/keygen/millerrabin.c +++ /dev/null @@ -1,288 +0,0 @@ -/* - * millerrabin.c: Miller-Rabin probabilistic primality testing, as - * declared in sshkeygen.h. - */ - -#include -#include "ssh.h" -#include "sshkeygen.h" -#include "mpint.h" -#include "mpunsafe.h" - -/* - * 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.) - */ - -struct MillerRabin { - MontyContext *mc; - - mp_int *pm1, *m_pm1; - mp_int *lowbit, *two; -}; - -MillerRabin *miller_rabin_new(mp_int *p) -{ - MillerRabin *mr = snew(MillerRabin); - - assert(mp_hs_integer(p, 2)); - assert(mp_get_bit(p, 0) == 1); - - mr->pm1 = mp_copy(p); - mp_sub_integer_into(mr->pm1, mr->pm1, 1); - - /* - * Standard bit-twiddling trick for isolating the lowest set bit - * of a number: x & (-x) - */ - mr->lowbit = mp_new(mp_max_bits(mr->pm1)); - mp_sub_into(mr->lowbit, mr->lowbit, mr->pm1); - mp_and_into(mr->lowbit, mr->lowbit, mr->pm1); - - mr->two = mp_from_integer(2); - - mr->mc = monty_new(p); - mr->m_pm1 = monty_import(mr->mc, mr->pm1); - - return mr; -} - -void miller_rabin_free(MillerRabin *mr) -{ - mp_free(mr->pm1); - mp_free(mr->m_pm1); - mp_free(mr->lowbit); - mp_free(mr->two); - monty_free(mr->mc); - smemclr(mr, sizeof(*mr)); - sfree(mr); -} - -/* - * The main internal function that implements a single M-R test. - * - * Expects the witness integer to be in Montgomery representation. - * (Since in live use witnesses are invented at random, this imposes - * no extra cost on the callers, and saves effort in here.) - */ -static struct mr_result miller_rabin_test_inner(MillerRabin *mr, mp_int *mw) -{ - mp_int *acc = mp_copy(monty_identity(mr->mc)); - mp_int *spare = mp_new(mp_max_bits(mr->pm1)); - size_t bit = mp_max_bits(mr->pm1); - - /* - * The obvious approach to Miller-Rabin would be to start by - * calling monty_pow to raise w to the power q, and then square it - * k times ourselves. But that introduces a timing leak that gives - * away the value of k, i.e., how many factors of 2 there are in - * p-1. - * - * Instead, we don't call monty_pow at all. We do a modular - * exponentiation ourselves to compute w^((p-1)/2), using the - * technique that works from the top bit of the exponent - * downwards. That is, in each iteration we compute - * w^floor(exponent/2^i) for i one less than the previous - * iteration, by squaring the value we previously had and then - * optionally multiplying in w if the next exponent bit is 1. - * - * At the end of that process, once i <= k, the division - * (exponent/2^i) yields an integer, so the values we're computing - * are not just w^(floor of that), but w^(exactly that). In other - * words, the last k intermediate values of this modexp are - * precisely the values M-R wants to check against +1 or -1. - * - * So we interleave those checks with the modexp loop itself, and - * to avoid a timing leak, we check _every_ intermediate result - * against (the Montgomery representations of) both +1 and -1. And - * then we do bitwise masking to arrange that only the sensible - * ones of those checks find their way into our final answer. - */ - - unsigned active = 0; - - struct mr_result result; - result.passed = result.potential_primitive_root = 0; - - while (bit-- > 1) { - /* - * In this iteration, we're computing w^(2e) or w^(2e+1), - * where we have w^e from the previous iteration. So we square - * the value we had already, and then optionally multiply in - * another copy of w depending on the next bit of the exponent. - */ - monty_mul_into(mr->mc, acc, acc, acc); - monty_mul_into(mr->mc, spare, acc, mw); - mp_select_into(acc, acc, spare, mp_get_bit(mr->pm1, bit)); - - /* - * mr->lowbit is a number with only one bit set, corresponding - * to the lowest set bit in p-1. So when that's the bit of the - * exponent we've just processed, we'll detect it by setting - * first_iter to true. That's our indication that we're now - * generating intermediate results useful to M-R, so we also - * set 'active', which stays set from then on. - */ - unsigned first_iter = mp_get_bit(mr->lowbit, bit); - active |= first_iter; - - /* - * Check the intermediate result against both +1 and -1. - */ - unsigned is_plus_1 = mp_cmp_eq(acc, monty_identity(mr->mc)); - unsigned is_minus_1 = mp_cmp_eq(acc, mr->m_pm1); - - /* - * M-R must report success iff either: the first of the useful - * intermediate results (which is w^q) is 1, or _any_ of them - * (from w^q all the way up to w^((p-1)/2)) is -1. - * - * So we want to pass the test if is_plus_1 is set on the - * first iteration, or if is_minus_1 is set on any iteration. - */ - result.passed |= (first_iter & is_plus_1); - result.passed |= (active & is_minus_1); - - /* - * In the final iteration, is_minus_1 is also used to set the - * 'potential primitive root' flag, because we haven't found - * any exponent smaller than p-1 for which w^(that) == 1. - */ - if (bit == 1) - result.potential_primitive_root = is_minus_1; - } - - mp_free(acc); - mp_free(spare); - - return result; -} - -/* - * Wrapper on miller_rabin_test_inner for the convenience of - * testcrypt. Expects the witness integer to be literal, so we - * monty_import it before running the real test. - */ -struct mr_result miller_rabin_test(MillerRabin *mr, mp_int *w) -{ - mp_int *mw = monty_import(mr->mc, w); - struct mr_result result = miller_rabin_test_inner(mr, mw); - mp_free(mw); - return result; -} - -bool miller_rabin_test_random(MillerRabin *mr) -{ - mp_int *mw = mp_random_in_range(mr->two, mr->pm1); - struct mr_result result = miller_rabin_test_inner(mr, mw); - mp_free(mw); - return result.passed; -} - -mp_int *miller_rabin_find_potential_primitive_root(MillerRabin *mr) -{ - while (true) { - mp_int *mw = mp_unsafe_shrink(mp_random_in_range(mr->two, mr->pm1)); - struct mr_result result = miller_rabin_test_inner(mr, mw); - - if (result.passed && result.potential_primitive_root) { - mp_int *pr = monty_export(mr->mc, mw); - mp_free(mw); - return pr; - } - - mp_free(mw); - - if (!result.passed) { - return NULL; - } - } -} - -unsigned miller_rabin_checks_needed(unsigned bits) -{ - /* Table 4.4 from Handbook of Applied Cryptography */ - return (bits >= 1300 ? 2 : bits >= 850 ? 3 : bits >= 650 ? 4 : - bits >= 550 ? 5 : bits >= 450 ? 6 : bits >= 400 ? 7 : - bits >= 350 ? 8 : bits >= 300 ? 9 : bits >= 250 ? 12 : - bits >= 200 ? 15 : bits >= 150 ? 18 : 27); -} - diff --git a/keygen/mpunsafe.c b/keygen/mpunsafe.c deleted file mode 100644 index 2cd7a37ae..000000000 --- a/keygen/mpunsafe.c +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include -#include - -#include "defs.h" -#include "misc.h" -#include "puttymem.h" - -#include "mpint.h" -#include "mpunsafe.h" -#include "crypto/mpint_i.h" - -/* - * This global symbol is also defined in ssh/kex2-client.c, to ensure - * that these unsafe non-constant-time mp_int functions can't end up - * accidentally linked in to any PuTTY tool that actually makes an SSH - * client connection. - * - * (Only _client_ connections, however. Uppity, being a test server - * only, is exempt.) - */ -const int deliberate_symbol_clash = 12345; - -static size_t mp_unsafe_words_needed(mp_int *x) -{ - size_t words = x->nw; - while (words > 1 && !x->w[words-1]) - words--; - return words; -} - -mp_int *mp_unsafe_shrink(mp_int *x) -{ - x->nw = mp_unsafe_words_needed(x); - /* This potentially leaves some allocated words between the new - * and old values of x->nw, which won't be wiped by mp_free now - * that x->nw doesn't mention that they exist. But we've just - * checked they're all zero, so we don't need to wipe them now - * either. */ - return x; -} - -mp_int *mp_unsafe_copy(mp_int *x) -{ - mp_int *copy = mp_make_sized(mp_unsafe_words_needed(x)); - mp_copy_into(copy, x); - return copy; -} diff --git a/keygen/mpunsafe.h b/keygen/mpunsafe.h deleted file mode 100644 index 072153723..000000000 --- a/keygen/mpunsafe.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * mpunsafe.h: functions that deal with mp_ints in ways that are *not* - * expected to be constant-time. Used during key generation, in which - * constant run time is a lost cause anyway. - * - * These functions are in a separate header, so that you can easily - * check that you're not calling them in the wrong context. They're - * also defined in a separate source file, which is only linked in to - * the key generation tools. Furthermore, that source file also - * defines a global symbol that intentionally conflicts with one - * defined in the SSH client code, so that any attempt to put these - * functions into the same binary as the live SSH client - * implementation will cause a link-time failure. They should only be - * linked into PuTTYgen and auxiliary test programs. - * - * Also, just in case those precautions aren't enough, all the unsafe - * functions have 'unsafe' in the name. - */ - -#ifndef PUTTY_MPINT_UNSAFE_H -#define PUTTY_MPINT_UNSAFE_H - -/* - * The most obvious unsafe thing you want to do with an mp_int is to - * get rid of leading zero words in its representation, so that its - * nominal size is as close as possible to its true size, and you - * don't waste any time processing it. - * - * mp_unsafe_shrink performs this operation in place, mutating the - * size field of the mp_int it's given. It returns the same pointer it - * was given. - * - * mp_unsafe_copy leaves the original mp_int alone and makes a new one - * with the minimal size. - */ -mp_int *mp_unsafe_shrink(mp_int *m); -mp_int *mp_unsafe_copy(mp_int *m); - -#endif /* PUTTY_MPINT_UNSAFE_H */ diff --git a/keygen/pockle.c b/keygen/pockle.c deleted file mode 100644 index 2a072f183..000000000 --- a/keygen/pockle.c +++ /dev/null @@ -1,450 +0,0 @@ -#include -#include "ssh.h" -#include "sshkeygen.h" -#include "mpint.h" -#include "mpunsafe.h" -#include "tree234.h" - -typedef struct PocklePrimeRecord PocklePrimeRecord; - -struct Pockle { - tree234 *tree; - - PocklePrimeRecord **list; - size_t nlist, listsize; -}; - -struct PocklePrimeRecord { - mp_int *prime; - PocklePrimeRecord **factors; - size_t nfactors; - mp_int *witness; - - size_t index; /* index in pockle->list */ -}; - -static int ppr_cmp(void *av, void *bv) -{ - PocklePrimeRecord *a = (PocklePrimeRecord *)av; - PocklePrimeRecord *b = (PocklePrimeRecord *)bv; - return mp_cmp_hs(a->prime, b->prime) - mp_cmp_hs(b->prime, a->prime); -} - -static int ppr_find(void *av, void *bv) -{ - mp_int *a = (mp_int *)av; - PocklePrimeRecord *b = (PocklePrimeRecord *)bv; - return mp_cmp_hs(a, b->prime) - mp_cmp_hs(b->prime, a); -} - -Pockle *pockle_new(void) -{ - Pockle *pockle = snew(Pockle); - pockle->tree = newtree234(ppr_cmp); - pockle->list = NULL; - pockle->nlist = pockle->listsize = 0; - return pockle; -} - -void pockle_free(Pockle *pockle) -{ - pockle_release(pockle, 0); - assert(count234(pockle->tree) == 0); - freetree234(pockle->tree); - sfree(pockle->list); - sfree(pockle); -} - -static PockleStatus pockle_insert(Pockle *pockle, mp_int *p, mp_int **factors, - size_t nfactors, mp_int *w) -{ - PocklePrimeRecord *pr = snew(PocklePrimeRecord); - pr->prime = mp_copy(p); - - PocklePrimeRecord *found = add234(pockle->tree, pr); - if (pr != found) { - /* it was already in there */ - mp_free(pr->prime); - sfree(pr); - return POCKLE_OK; - } - - if (w) { - pr->factors = snewn(nfactors, PocklePrimeRecord *); - for (size_t i = 0; i < nfactors; i++) { - pr->factors[i] = find234(pockle->tree, factors[i], ppr_find); - assert(pr->factors[i]); - } - pr->nfactors = nfactors; - pr->witness = mp_copy(w); - } else { - pr->factors = NULL; - pr->nfactors = 0; - pr->witness = NULL; - } - pr->index = pockle->nlist; - - sgrowarray(pockle->list, pockle->listsize, pockle->nlist); - pockle->list[pockle->nlist++] = pr; - return POCKLE_OK; -} - -size_t pockle_mark(Pockle *pockle) -{ - return pockle->nlist; -} - -void pockle_release(Pockle *pockle, size_t mark) -{ - while (pockle->nlist > mark) { - PocklePrimeRecord *pr = pockle->list[--pockle->nlist]; - del234(pockle->tree, pr); - mp_free(pr->prime); - if (pr->witness) - mp_free(pr->witness); - sfree(pr->factors); - sfree(pr); - } -} - -PockleStatus pockle_add_small_prime(Pockle *pockle, mp_int *p) -{ - if (mp_hs_integer(p, (1ULL << 32))) - return POCKLE_SMALL_PRIME_NOT_SMALL; - - uint32_t val = mp_get_integer(p); - - if (val < 2) - return POCKLE_PRIME_SMALLER_THAN_2; - - init_smallprimes(); - for (size_t i = 0; i < NSMALLPRIMES; i++) { - if (val == smallprimes[i]) - break; /* success */ - if (val % smallprimes[i] == 0) - return POCKLE_SMALL_PRIME_NOT_PRIME; - } - - return pockle_insert(pockle, p, NULL, 0, NULL); -} - -PockleStatus pockle_add_prime(Pockle *pockle, mp_int *p, - mp_int **factors, size_t nfactors, - mp_int *witness) -{ - MontyContext *mc = NULL; - mp_int *x = NULL, *f = NULL, *w = NULL; - PockleStatus status; - - /* - * We're going to try to verify that p is prime by using - * Pocklington's theorem. The idea is that we're given w such that - * w^{p-1} == 1 (mod p) (1) - * and for a collection of primes q | p-1, - * w^{(p-1)/q} - 1 is coprime to p. (2) - * - * Suppose r is a prime factor of p itself. Consider the - * multiplicative order of w mod r. By (1), r | w^{p-1}-1. But by - * (2), r does not divide w^{(p-1)/q}-1. So the order of w mod r - * is a factor of p-1, but not a factor of (p-1)/q. Hence, the - * largest power of q that divides p-1 must also divide ord w. - * - * Repeating this reasoning for all q, we find that the product of - * all the q (which we'll denote f) must divide ord w, which in - * turn divides r-1. So f | r-1 for any r | p. - * - * In particular, this means f < r. That is, all primes r | p are - * bigger than f. So if f > sqrt(p), then we've shown p is prime, - * because otherwise it would have to be the product of at least - * two factors bigger than its own square root. - * - * With an extra check, we can also show p to be prime even if - * we're only given enough factors to make f > cbrt(p). See below - * for that part, when we come to it. - */ - - /* - * Start by checking p > 1. It certainly can't be prime otherwise! - * (And since we're going to prove it prime by showing all its - * prime factors are large, we do also have to know it _has_ at - * least one prime factor for that to tell us anything.) - */ - if (!mp_hs_integer(p, 2)) - return POCKLE_PRIME_SMALLER_THAN_2; - - /* - * Check that all the factors we've been given really are primes - * (in the sense that we already had them in our index). Make the - * product f, and check it really does divide p-1. - */ - x = mp_copy(p); - mp_sub_integer_into(x, x, 1); - f = mp_from_integer(1); - for (size_t i = 0; i < nfactors; i++) { - mp_int *q = factors[i]; - - if (!find234(pockle->tree, q, ppr_find)) { - status = POCKLE_FACTOR_NOT_KNOWN_PRIME; - goto out; - } - - mp_int *quotient = mp_new(mp_max_bits(x)); - mp_int *residue = mp_new(mp_max_bits(q)); - mp_divmod_into(x, q, quotient, residue); - - unsigned exact = mp_eq_integer(residue, 0); - mp_free(residue); - - mp_free(x); - x = quotient; - - if (!exact) { - status = POCKLE_FACTOR_NOT_A_FACTOR; - goto out; - } - - mp_int *tmp = f; - f = mp_unsafe_shrink(mp_mul(tmp, q)); - mp_free(tmp); - } - - /* - * Check that f > cbrt(p). - */ - mp_int *f2 = mp_mul(f, f); - mp_int *f3 = mp_mul(f2, f); - bool too_big = mp_cmp_hs(p, f3); - mp_free(f3); - mp_free(f2); - if (too_big) { - status = POCKLE_PRODUCT_OF_FACTORS_TOO_SMALL; - goto out; - } - - /* - * Now do the extra check that allows us to get away with only - * having f > cbrt(p) instead of f > sqrt(p). - * - * If we can show that f | r-1 for any r | p, then we've ruled out - * p being a product of _more_ than two primes (because then it - * would be the product of at least three things bigger than its - * own cube root). But we still have to rule out it being a - * product of exactly two. - * - * Suppose for the sake of contradiction that p is the product of - * two prime factors. We know both of those factors would have to - * be congruent to 1 mod f. So we'd have to have - * - * p = (uf+1)(vf+1) = (uv)f^2 + (u+v)f + 1 (3) - * - * We can't have uv >= f, or else that expression would come to at - * least f^3, i.e. it would exceed p. So uv < f. Hence, u,v < f as - * well. - * - * Can we have u+v >= f? If we did, then we could write v >= f-u, - * and hence f > uv >= u(f-u). That can be rearranged to show that - * u^2 > (u-1)f; decrementing the LHS makes the inequality no - * longer necessarily strict, so we have u^2-1 >= (u-1)f, and - * dividing off u-1 gives u+1 >= f. But we know u < f, so the only - * way this could happen would be if u=f-1, which makes v=1. But - * _then_ (3) gives us p = (f-1)f^2 + f^2 + 1 = f^3+1. But that - * can't be true if f^3 > p. So we can't have u+v >= f either, by - * contradiction. - * - * After all that, what have we shown? We've shown that we can - * write p = (uv)f^2 + (u+v)f + 1, with both uv and u+v strictly - * less than f. In other words, if you write down p in base f, it - * has exactly three digits, and they are uv, u+v and 1. - * - * But that means we can _find_ u and v: we know p and f, so we - * can just extract those digits of p's base-f representation. - * Once we've done so, they give the sum and product of the - * potential u,v. And given the sum and product of two numbers, - * you can make a quadratic which has those numbers as roots. - * - * We don't actually have to _solve_ the quadratic: all we have to - * do is check if its discriminant is a perfect square. If not, - * we'll know that no integers u,v can match this description. - */ - { - /* We already have x = (p-1)/f. So we just need to write x in - * the form aF + b, and then we have a=uv and b=u+v. */ - mp_int *a = mp_new(mp_max_bits(x)); - mp_int *b = mp_new(mp_max_bits(f)); - mp_divmod_into(x, f, a, b); - assert(!mp_cmp_hs(a, f)); - assert(!mp_cmp_hs(b, f)); - - /* If a=0, then that means p < f^2, so we don't need to do - * this check at all: the straightforward Pocklington theorem - * is all we need. */ - if (!mp_eq_integer(a, 0)) { - unsigned perfect_square = 0; - - mp_int *bsq = mp_mul(b, b); - mp_lshift_fixed_into(a, a, 2); - - if (mp_cmp_hs(bsq, a)) { - /* b^2-4a is non-negative, so it might be a square. - * Check it. */ - mp_int *discriminant = mp_sub(bsq, a); - mp_int *remainder = mp_new(mp_max_bits(discriminant)); - mp_int *root = mp_nthroot(discriminant, 2, remainder); - perfect_square = mp_eq_integer(remainder, 0); - mp_free(discriminant); - mp_free(root); - mp_free(remainder); - } - - mp_free(bsq); - - if (perfect_square) { - mp_free(b); - mp_free(a); - status = POCKLE_DISCRIMINANT_IS_SQUARE; - goto out; - } - } - mp_free(b); - mp_free(a); - } - - /* - * Now we've done all the checks that are cheaper than a modpow, - * so we've ruled out as many things as possible before having to - * do any hard work. But there's nothing for it now: make a - * MontyContext. - */ - mc = monty_new(p); - w = monty_import(mc, witness); - - /* - * The initial Fermat check: is w^{p-1} itself congruent to 1 mod - * p? - */ - { - mp_int *pm1 = mp_copy(p); - mp_sub_integer_into(pm1, pm1, 1); - mp_int *power = monty_pow(mc, w, pm1); - unsigned fermat_pass = mp_cmp_eq(power, monty_identity(mc)); - mp_free(power); - mp_free(pm1); - - if (!fermat_pass) { - status = POCKLE_FERMAT_TEST_FAILED; - goto out; - } - } - - /* - * And now, for each factor q, is w^{(p-1)/q}-1 coprime to p? - */ - for (size_t i = 0; i < nfactors; i++) { - mp_int *q = factors[i]; - mp_int *exponent = mp_unsafe_shrink(mp_div(p, q)); - mp_int *power = monty_pow(mc, w, exponent); - mp_int *power_extracted = monty_export(mc, power); - mp_sub_integer_into(power_extracted, power_extracted, 1); - - unsigned coprime = mp_coprime(power_extracted, p); - if (!coprime) { - /* - * If w^{(p-1)/q}-1 is not coprime to p, the test has - * failed. But it makes a difference why. If the power of - * w turned out to be 1, so that we took gcd(1-1,p) = - * gcd(0,p) = p, that's like an inconclusive Fermat or M-R - * test: it might just mean you picked a witness integer - * that wasn't a primitive root. But if the power is any - * _other_ value mod p that is not coprime to p, it means - * we've detected that the number is *actually not prime*! - */ - if (mp_eq_integer(power_extracted, 0)) - status = POCKLE_WITNESS_POWER_IS_1; - else - status = POCKLE_WITNESS_POWER_NOT_COPRIME; - } - - mp_free(exponent); - mp_free(power); - mp_free(power_extracted); - - if (!coprime) - goto out; /* with the status we set up above */ - } - - /* - * Success! p is prime. Insert it into our tree234 of known - * primes, so that future calls to this function can cite it in - * evidence of larger numbers' primality. - */ - status = pockle_insert(pockle, p, factors, nfactors, witness); - - out: - if (x) - mp_free(x); - if (f) - mp_free(f); - if (w) - mp_free(w); - if (mc) - monty_free(mc); - return status; -} - -static void mp_write_decimal(strbuf *sb, mp_int *x) -{ - char *s = mp_get_decimal(x); - ptrlen pl = ptrlen_from_asciz(s); - put_datapl(sb, pl); - smemclr(s, pl.len); - sfree(s); -} - -strbuf *pockle_mpu(Pockle *pockle, mp_int *p) -{ - strbuf *sb = strbuf_new_nm(); - PocklePrimeRecord *pr = find234(pockle->tree, p, ppr_find); - assert(pr); - - bool *needed = snewn(pockle->nlist, bool); - memset(needed, 0, pockle->nlist * sizeof(bool)); - needed[pr->index] = true; - - put_fmt(sb, "[MPU - Primality Certificate]\nVersion 1.0\nBase 10\n\n" - "Proof for:\nN "); - mp_write_decimal(sb, p); - put_fmt(sb, "\n"); - - for (size_t index = pockle->nlist; index-- > 0 ;) { - if (!needed[index]) - continue; - pr = pockle->list[index]; - - if (mp_get_nbits(pr->prime) <= 64) { - put_fmt(sb, "\nType Small\nN "); - mp_write_decimal(sb, pr->prime); - put_fmt(sb, "\n"); - } else { - assert(pr->witness); - put_fmt(sb, "\nType BLS5\nN "); - mp_write_decimal(sb, pr->prime); - put_fmt(sb, "\n"); - for (size_t i = 0; i < pr->nfactors; i++) { - put_fmt(sb, "Q[%"SIZEu"] ", i+1); - mp_write_decimal(sb, pr->factors[i]->prime); - assert(pr->factors[i]->index < index); - needed[pr->factors[i]->index] = true; - put_fmt(sb, "\n"); - } - for (size_t i = 0; i < pr->nfactors + 1; i++) { - put_fmt(sb, "A[%"SIZEu"] ", i); - mp_write_decimal(sb, pr->witness); - put_fmt(sb, "\n"); - } - put_fmt(sb, "----\n"); - } - } - sfree(needed); - - return sb; -} diff --git a/keygen/prime.c b/keygen/prime.c deleted file mode 100644 index d9bdebbaf..000000000 --- a/keygen/prime.c +++ /dev/null @@ -1,762 +0,0 @@ -/* - * Prime generation. - */ - -#include -#include - -#include "ssh.h" -#include "mpint.h" -#include "mpunsafe.h" -#include "sshkeygen.h" - -/* ---------------------------------------------------------------------- - * Standard probabilistic prime-generation algorithm: - * - * - get a number from our PrimeCandidateSource which will at least - * avoid being divisible by any prime under 2^16 - * - * - 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. - */ - -static PrimeGenerationContext *probprime_new_context( - const PrimeGenerationPolicy *policy) -{ - PrimeGenerationContext *ctx = snew(PrimeGenerationContext); - ctx->vt = policy; - return ctx; -} - -static void probprime_free_context(PrimeGenerationContext *ctx) -{ - sfree(ctx); -} - -static ProgressPhase probprime_add_progress_phase( - const PrimeGenerationPolicy *policy, - ProgressReceiver *prog, unsigned bits) -{ - /* - * The density of primes near x is 1/(log x). When x is about 2^b, - * that's 1/(b log 2). - * - * But we're only doing the expensive part of the process (the M-R - * checks) for a number that passes the initial winnowing test of - * having no factor less than 2^16 (at least, unless the prime is - * so small that PrimeCandidateSource gives up on that winnowing). - * The density of _those_ numbers is about 1/19.76. So the odds of - * hitting a prime per expensive attempt are boosted by a factor - * of 19.76. - */ - const double log_2 = 0.693147180559945309417232121458; - double winnow_factor = (bits < 32 ? 1.0 : 19.76); - double prob = winnow_factor / (bits * log_2); - - /* - * Estimate the cost of prime generation as the cost of the M-R - * modexps. - */ - double cost = (miller_rabin_checks_needed(bits) * - estimate_modexp_cost(bits)); - return progress_add_probabilistic(prog, cost, prob); -} - -static mp_int *probprime_generate( - PrimeGenerationContext *ctx, - PrimeCandidateSource *pcs, ProgressReceiver *prog) -{ - pcs_ready(pcs); - - while (true) { - progress_report_attempt(prog); - - mp_int *p = pcs_generate(pcs); - if (!p) { - pcs_free(pcs); - return NULL; - } - - MillerRabin *mr = miller_rabin_new(p); - bool known_bad = false; - unsigned nchecks = miller_rabin_checks_needed(mp_get_nbits(p)); - for (unsigned check = 0; check < nchecks; check++) { - if (!miller_rabin_test_random(mr)) { - known_bad = true; - break; - } - } - miller_rabin_free(mr); - - if (!known_bad) { - /* - * We have a prime! - */ - pcs_free(pcs); - return p; - } - - mp_free(p); - } -} - -static strbuf *null_mpu_certificate(PrimeGenerationContext *ctx, mp_int *p) -{ - return NULL; -} - -const PrimeGenerationPolicy primegen_probabilistic = { - probprime_add_progress_phase, - probprime_new_context, - probprime_free_context, - probprime_generate, - null_mpu_certificate, -}; - -/* ---------------------------------------------------------------------- - * Alternative provable-prime algorithm, based on the following paper: - * - * [MAURER] Maurer, U.M. Fast generation of prime numbers and secure - * public-key cryptographic parameters. J. Cryptology 8, 123–155 - * (1995). https://doi.org/10.1007/BF00202269 - */ - -typedef enum SubprimePolicy { - SPP_FAST, - SPP_MAURER_SIMPLE, - SPP_MAURER_COMPLEX, -} SubprimePolicy; - -typedef struct ProvablePrimePolicyExtra { - SubprimePolicy spp; -} ProvablePrimePolicyExtra; - -typedef struct ProvablePrimeContext ProvablePrimeContext; -struct ProvablePrimeContext { - Pockle *pockle; - PrimeGenerationContext pgc; - const ProvablePrimePolicyExtra *extra; -}; - -static PrimeGenerationContext *provableprime_new_context( - const PrimeGenerationPolicy *policy) -{ - ProvablePrimeContext *ppc = snew(ProvablePrimeContext); - ppc->pgc.vt = policy; - ppc->pockle = pockle_new(); - ppc->extra = policy->extra; - return &ppc->pgc; -} - -static void provableprime_free_context(PrimeGenerationContext *ctx) -{ - ProvablePrimeContext *ppc = container_of(ctx, ProvablePrimeContext, pgc); - pockle_free(ppc->pockle); - sfree(ppc); -} - -static ProgressPhase provableprime_add_progress_phase( - const PrimeGenerationPolicy *policy, - ProgressReceiver *prog, unsigned bits) -{ - /* - * Estimating the cost of making a _provable_ prime is difficult - * because of all the recursions to smaller sizes. - * - * Once you have enough factors of p-1 to certify primality of p, - * the remaining work in provable prime generation is not very - * different from probabilistic: you generate a random candidate, - * test its primality probabilistically, and use the witness value - * generated as a byproduct of that test for the full Pocklington - * verification. The expensive part, as usual, is made of modpows. - * - * The Pocklington test needs at least two modpows (one for the - * Fermat check, and one per known factor of p-1). - * - * The prior M-R step needs an unknown number, because we iterate - * until we find a value whose order is divisible by the largest - * power of 2 that divides p-1, say 2^j. That excludes half the - * possible witness values (specifically, the quadratic residues), - * so we expect to need on average two M-R operations to find one. - * But that's only if the number _is_ prime - as usual, it's also - * possible that we hit a non-prime and have to try again. - * - * So, if we were only estimating the cost of that final step, it - * would look a lot like the probabilistic version: we'd have to - * estimate the expected total number of modexps by knowing - * something about the density of primes among our candidate - * integers, and then multiply that by estimate_modexp_cost(bits). - * But the problem is that we also have to _find_ a smaller prime, - * so we have to recurse. - * - * In the MAURER_SIMPLE version of the algorithm, you recurse to - * any one of a range of possible smaller sizes i, each with - * probability proportional to 1/i. So your expected time to - * generate an n-bit prime is given by a horrible recurrence of - * the form E_n = S_n + (sum E_i/i) / (sum 1/i), in which S_n is - * the expected cost of the final step once you have your smaller - * primes, and both sums are over ceil(n/2) <= i <= n-20. - * - * At this point I ran out of effort to actually do the maths - * rigorously, so instead I did the empirical experiment of - * generating that sequence in Python and plotting it on a graph. - * My Python code is here, in case I need it again: - -from math import log - -alpha = log(3)/log(2) + 1 # exponent for modexp using Karatsuba mult - -E = [1] * 16 # assume generating tiny primes is trivial - -for n in range(len(E), 4096): - - # Expected time for sub-generations, as a weighted mean of prior - # values of the same sequence. - lo = (n+1)//2 - hi = n-20 - if lo <= hi: - subrange = range(lo, hi+1) - num = sum(E[i]/i for i in subrange) - den = sum(1/i for i in subrange) - else: - num, den = 0, 1 - - # Constant term (cost of final step). - # Similar to probprime_add_progress_phase. - winnow_factor = 1 if n < 32 else 19.76 - prob = winnow_factor / (n * log(2)) - cost = 4 * n**alpha / prob - - E.append(cost + num / den) - -for i, p in enumerate(E): - try: - print(log(i), log(p)) - except ValueError: - continue - - * The output loop prints the logs of both i and E_i, so that when - * I plot the resulting data file in gnuplot I get a log-log - * diagram. That showed me some early noise and then a very - * straight-looking line; feeding the straight part of the graph - * to linear-regression analysis reported that it fits the line - * - * log E_n = -1.7901825337965498 + 3.6199197179662517 * log(n) - * => E_n = 0.16692969657466802 * n^3.6199197179662517 - * - * So my somewhat empirical estimate is that Maurer prime - * generation costs about 0.167 * bits^3.62, in the same arbitrary - * time units used by estimate_modexp_cost. - */ - - return progress_add_linear(prog, 0.167 * pow(bits, 3.62)); -} - -static mp_int *primegen_small(Pockle *pockle, PrimeCandidateSource *pcs) -{ - assert(pcs_get_bits(pcs) <= 32); - - pcs_ready(pcs); - - while (true) { - mp_int *p = pcs_generate(pcs); - if (!p) { - pcs_free(pcs); - return NULL; - } - if (pockle_add_small_prime(pockle, p) == POCKLE_OK) { - pcs_free(pcs); - return p; - } - mp_free(p); - } -} - -#ifdef DEBUG_PRIMEGEN -static void timestamp(FILE *fp) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - fprintf(fp, "%lu.%09lu: ", (unsigned long)ts.tv_sec, - (unsigned long)ts.tv_nsec); -} -static PRINTF_LIKE(1, 2) void debug_f(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - timestamp(stderr); - vfprintf(stderr, fmt, ap); - fputc('\n', stderr); - va_end(ap); -} -static void debug_f_mp(const char *fmt, mp_int *x, ...) -{ - va_list ap; - va_start(ap, x); - timestamp(stderr); - vfprintf(stderr, fmt, ap); - mp_dump(stderr, "", x, "\n"); - va_end(ap); -} -#else -#define debug_f(...) ((void)0) -#define debug_f_mp(...) ((void)0) -#endif - -static double uniform_random_double(void) -{ - unsigned char randbuf[8]; - random_read(randbuf, 8); - return GET_64BIT_MSB_FIRST(randbuf) * 0x1.0p-64; -} - -static mp_int *mp_ceil_div(mp_int *n, mp_int *d) -{ - mp_int *nplus = mp_add(n, d); - mp_sub_integer_into(nplus, nplus, 1); - mp_int *toret = mp_div(nplus, d); - mp_free(nplus); - return toret; -} - -static mp_int *provableprime_generate_inner( - ProvablePrimeContext *ppc, PrimeCandidateSource *pcs, - ProgressReceiver *prog, double progress_origin, double progress_scale) -{ - unsigned bits = pcs_get_bits(pcs); - assert(bits > 1); - - if (bits <= 32) { - debug_f("ppgi(%u) -> small", bits); - return primegen_small(ppc->pockle, pcs); - } - - unsigned min_bits_needed, max_bits_needed; - { - /* - * Find the product of all the prime factors we already know - * about. - */ - mp_int *size_got = mp_from_integer(1); - size_t nfactors; - mp_int **factors = pcs_get_known_prime_factors(pcs, &nfactors); - for (size_t i = 0; i < nfactors; i++) { - mp_int *to_free = size_got; - size_got = mp_unsafe_shrink(mp_mul(size_got, factors[i])); - mp_free(to_free); - } - - /* - * Find the largest cofactor we might be able to use, and the - * smallest one we can get away with. - */ - mp_int *upperbound = pcs_get_upper_bound(pcs); - mp_int *size_needed = mp_nthroot(upperbound, 3, NULL); - debug_f_mp("upperbound = ", upperbound); - { - mp_int *to_free = upperbound; - upperbound = mp_unsafe_shrink(mp_div(upperbound, size_got)); - mp_free(to_free); - } - debug_f_mp("size_needed = ", size_needed); - { - mp_int *to_free = size_needed; - size_needed = mp_unsafe_shrink(mp_ceil_div(size_needed, size_got)); - mp_free(to_free); - } - - max_bits_needed = pcs_get_bits_remaining(pcs); - - /* - * We need a prime that is greater than or equal to - * 'size_needed' in order for the product of all our known - * factors of p-1 to exceed the cube root of the largest value - * p might take. - * - * Since pcs_new wants a size specified in bits, we must count - * the bits in size_needed and then add 1. Otherwise we might - * get a value with the same bit count as size_needed but - * slightly smaller than it. - * - * An exception is if size_needed = 1. In that case the - * product of existing known factors is _already_ enough, so - * we don't need to generate an extra factor at all. - */ - if (mp_hs_integer(size_needed, 2)) { - min_bits_needed = mp_get_nbits(size_needed) + 1; - } else { - min_bits_needed = 0; - } - - mp_free(upperbound); - mp_free(size_needed); - mp_free(size_got); - } - - double progress = 0.0; - - if (min_bits_needed) { - debug_f("ppgi(%u) recursing, need [%u,%u] more bits", - bits, min_bits_needed, max_bits_needed); - - unsigned *sizes = NULL; - size_t nsizes = 0, sizesize = 0; - - unsigned real_min = max_bits_needed / 2; - unsigned real_max = (max_bits_needed >= 20 ? - max_bits_needed - 20 : 0); - if (real_min < min_bits_needed) - real_min = min_bits_needed; - if (real_max < real_min) - real_max = real_min; - debug_f("ppgi(%u) revised bits interval = [%u,%u]", - bits, real_min, real_max); - - switch (ppc->extra->spp) { - case SPP_FAST: - /* - * Always pick the smallest subsidiary prime we can get - * away with: just over n/3 bits. - * - * This is not a good mode for cryptographic prime - * generation, because it skews the distribution of primes - * greatly, and worse, it skews them in a direction that - * heads away from the properties crypto algorithms tend - * to like. - * - * (For both discrete-log systems and RSA, people have - * tended to recommend in the past that p-1 should have a - * _large_ factor if possible. There's some disagreement - * on which algorithms this is really necessary for, but - * certainly I've never seen anyone recommend arranging a - * _small_ factor on purpose.) - * - * I originally implemented this mode because it was - * convenient for debugging - it wastes as little time as - * possible on finding a sub-prime and lets you get to the - * interesting part! And I leave it in the code because it - * might still be useful for _something_. Because it's - * cryptographically questionable, it's not selectable in - * the UI of either version of PuTTYgen proper; but it can - * be accessed through testcrypt, and if for some reason a - * definite prime is needed for non-crypto purposes, it - * may still be the fastest way to put your hands on one. - */ - debug_f("ppgi(%u) fast mode, just ask for %u bits", - bits, min_bits_needed); - sgrowarray(sizes, sizesize, nsizes); - sizes[nsizes++] = min_bits_needed; - break; - case SPP_MAURER_SIMPLE: { - /* - * Select the size of the subsidiary prime at random from - * sqrt(outputprime) up to outputprime/2^20, in such a way - * that the probability distribution matches that of the - * largest prime factor of a random n-bit number. - * - * Per [MAURER] section 3.4, the cumulative distribution - * function of this relative size is 1+log2(x), for x in - * [1/2,1]. You can generate a value from the distribution - * given by a cdf by applying the inverse cdf to a uniform - * value in [0,1]. Simplifying that in this case, what we - * have to do is raise 2 to the power of a random real - * number between -1 and 0. (And that gives you the number - * of _bits_ in the sub-prime, as a factor of the desired - * output number of bits.) - * - * We also require that the subsidiary prime q is at least - * 20 bits smaller than the output one, to give us a - * fighting chance of there being _any_ prime we can find - * such that q | p-1. - * - * (But these rules have to be applied in an order that - * still leaves us _some_ interval of possible sizes we - * can pick!) - */ - maurer_simple: - debug_f("ppgi(%u) Maurer simple mode", bits); - - unsigned sub_bits; - do { - double uniform = uniform_random_double(); - sub_bits = real_max * pow(2.0, uniform - 1) + 0.5; - debug_f(" ... %.6f -> %u?", uniform, sub_bits); - } while (!(real_min <= sub_bits && sub_bits <= real_max)); - - debug_f("ppgi(%u) asking for %u bits", bits, sub_bits); - sgrowarray(sizes, sizesize, nsizes); - sizes[nsizes++] = sub_bits; - - break; - } - case SPP_MAURER_COMPLEX: { - /* - * In this mode, we may generate multiple factors of p-1 - * which between them add up to at least n/2 bits, in such - * a way that those are guaranteed to be the largest - * factors of p-1 and that they have the same probability - * distribution as the largest k factors would have in a - * random integer. The idea is that this more elaborate - * procedure gets as close as possible to the same - * probability distribution you'd get by selecting a - * completely random prime (if you feasibly could). - * - * Algorithm from Appendix 1 of [MAURER]: we generate - * random real numbers that sum to at most 1, by choosing - * each one uniformly from the range [0, 1 - sum of all - * the previous ones]. We maintain them in a list in - * decreasing order, and we stop as soon as we find an - * initial subsequence of the list s_1,...,s_r such that - * s_1 + ... + s_{r-1} + 2 s_r > 1. In particular, this - * guarantees that the sum of that initial subsequence is - * at least 1/2, so we end up with enough factors to - * satisfy Pocklington. - */ - - if (max_bits_needed / 2 + 1 > real_max) { - /* Early exit path in the case where this algorithm - * can't possibly generate a value in the range we - * need. In that situation, fall back to Maurer - * simple. */ - debug_f("ppgi(%u) skipping GenerateSizeList, " - "real_max too small", bits); - goto maurer_simple; /* sorry! */ - } - - double *s = NULL; - size_t ns, ssize = 0; - - while (true) { - debug_f("ppgi(%u) starting GenerateSizeList", bits); - ns = 0; - double range = 1.0; - while (true) { - /* Generate the next number */ - double u = uniform_random_double() * range; - range -= u; - debug_f(" u_%"SIZEu" = %g", ns, u); - - /* Insert it in the list */ - sgrowarray(s, ssize, ns); - size_t i; - for (i = ns; i > 0 && s[i-1] < u; i--) - s[i] = s[i-1]; - s[i] = u; - ns++; - debug_f(" inserting as s[%"SIZEu"]", i); - - /* Look for a suitable initial subsequence */ - double sum = 0; - for (i = 0; i < ns; i++) { - sum += s[i]; - if (sum + s[i] > 1.0) { - debug_f(" s[0..%"SIZEu"] works!", i); - - /* Truncate the sequence here, and stop - * generating random real numbers. */ - ns = i+1; - goto got_list; - } - } - } - - got_list:; - /* - * Now translate those real numbers into actual bit - * counts, and do a last-minute check to make sure - * their product is going to be in range. - * - * We have to check both the min and max sizes of the - * total. A b-bit number is in [2^{b-1},2^b). So the - * product of numbers of sizes b_1,...,b_k is at least - * 2^{\sum (b_i-1)}, and less than 2^{\sum b_i}. - */ - nsizes = 0; - - unsigned min_total = 0, max_total = 0; - - for (size_t i = 0; i < ns; i++) { - /* These sizes are measured in actual entropy, so - * add 1 bit each time to account for the - * zero-information leading 1 */ - unsigned this_size = max_bits_needed * s[i] + 1; - debug_f(" bits[%"SIZEu"] = %u", i, this_size); - sgrowarray(sizes, sizesize, nsizes); - sizes[nsizes++] = this_size; - - min_total += this_size - 1; - max_total += this_size; - } - - debug_f(" total bits = [%u,%u)", min_total, max_total); - if (min_total < real_min || max_total > real_max+1) { - debug_f(" total out of range, try again"); - } else { - debug_f(" success! %"SIZEu" sub-primes totalling [%u,%u) " - "bits", nsizes, min_total, max_total); - break; - } - } - - smemclr(s, ssize * sizeof(*s)); - sfree(s); - break; - } - default: - unreachable("bad subprime policy"); - } - - for (size_t i = 0; i < nsizes; i++) { - unsigned sub_bits = sizes[i]; - double progress_in_this_prime = (double)sub_bits / bits; - mp_int *q = provableprime_generate_inner( - ppc, pcs_new(sub_bits), - prog, progress_origin + progress_scale * progress, - progress_scale * progress_in_this_prime); - progress += progress_in_this_prime; - assert(q); - debug_f_mp("ppgi(%u) got factor ", q, bits); - pcs_require_residue_1_mod_prime(pcs, q); - mp_free(q); - } - - smemclr(sizes, sizesize * sizeof(*sizes)); - sfree(sizes); - } else { - debug_f("ppgi(%u) no need to recurse", bits); - } - - debug_f("ppgi(%u) ready, %u bits remaining", - bits, pcs_get_bits_remaining(pcs)); - pcs_ready(pcs); - - while (true) { - mp_int *p = pcs_generate(pcs); - if (!p) { - pcs_free(pcs); - return NULL; - } - - debug_f_mp("provable_step p=", p); - - MillerRabin *mr = miller_rabin_new(p); - debug_f("provable_step mr setup done"); - mp_int *witness = miller_rabin_find_potential_primitive_root(mr); - miller_rabin_free(mr); - - if (!witness) { - debug_f("provable_step mr failed"); - mp_free(p); - continue; - } - - size_t nfactors; - mp_int **factors = pcs_get_known_prime_factors(pcs, &nfactors); - PockleStatus st = pockle_add_prime( - ppc->pockle, p, factors, nfactors, witness); - - if (st != POCKLE_OK) { - debug_f("provable_step proof failed %d", (int)st); - - /* - * Check by assertion that the error status is not one of - * the ones we ought to have ruled out already by - * construction. If there's a bug in this code that means - * we can _never_ pass this test (e.g. picking products of - * factors that never quite reach cbrt(n)), we'd rather - * fail an assertion than loop forever. - */ - assert(st == POCKLE_DISCRIMINANT_IS_SQUARE || - st == POCKLE_WITNESS_POWER_IS_1 || - st == POCKLE_WITNESS_POWER_NOT_COPRIME); - - mp_free(p); - if (witness) - mp_free(witness); - continue; - } - - mp_free(witness); - pcs_free(pcs); - debug_f_mp("ppgi(%u) done, got ", p, bits); - progress_report(prog, progress_origin + progress_scale); - return p; - } -} - -static mp_int *provableprime_generate( - PrimeGenerationContext *ctx, - PrimeCandidateSource *pcs, ProgressReceiver *prog) -{ - ProvablePrimeContext *ppc = container_of(ctx, ProvablePrimeContext, pgc); - mp_int *p = provableprime_generate_inner(ppc, pcs, prog, 0.0, 1.0); - - return p; -} - -static inline strbuf *provableprime_mpu_certificate( - PrimeGenerationContext *ctx, mp_int *p) -{ - ProvablePrimeContext *ppc = container_of(ctx, ProvablePrimeContext, pgc); - return pockle_mpu(ppc->pockle, p); -} - -#define DECLARE_POLICY(name, policy) \ - static const struct ProvablePrimePolicyExtra \ - pppextra_##name = {policy}; \ - const PrimeGenerationPolicy name = { \ - provableprime_add_progress_phase, \ - provableprime_new_context, \ - provableprime_free_context, \ - provableprime_generate, \ - provableprime_mpu_certificate, \ - &pppextra_##name, \ - } - -DECLARE_POLICY(primegen_provable_fast, SPP_FAST); -DECLARE_POLICY(primegen_provable_maurer_simple, SPP_MAURER_SIMPLE); -DECLARE_POLICY(primegen_provable_maurer_complex, SPP_MAURER_COMPLEX); - -/* ---------------------------------------------------------------------- - * Reusable null implementation of the progress-reporting API. - */ - -static inline ProgressPhase null_progress_add(void) { - ProgressPhase ph = { .n = 0 }; - return ph; -} -ProgressPhase null_progress_add_linear( - ProgressReceiver *prog, double c) { return null_progress_add(); } -ProgressPhase null_progress_add_probabilistic( - ProgressReceiver *prog, double c, double p) { return null_progress_add(); } -void null_progress_ready(ProgressReceiver *prog) {} -void null_progress_start_phase(ProgressReceiver *prog, ProgressPhase phase) {} -void null_progress_report(ProgressReceiver *prog, double progress) {} -void null_progress_report_attempt(ProgressReceiver *prog) {} -void null_progress_report_phase_complete(ProgressReceiver *prog) {} -const ProgressReceiverVtable null_progress_vt = { - .add_linear = null_progress_add_linear, - .add_probabilistic = null_progress_add_probabilistic, - .ready = null_progress_ready, - .start_phase = null_progress_start_phase, - .report = null_progress_report, - .report_attempt = null_progress_report_attempt, - .report_phase_complete = null_progress_report_phase_complete, -}; - -/* ---------------------------------------------------------------------- - * Helper function for progress estimation. - */ - -double estimate_modexp_cost(unsigned bits) -{ - /* - * A modexp of n bits goes roughly like O(n^2.58), on the grounds - * that our modmul is O(n^1.58) (Karatsuba) and you need O(n) of - * them in a modexp. - */ - return pow(bits, 2.58); -} diff --git a/keygen/primecandidate.c b/keygen/primecandidate.c deleted file mode 100644 index fca2b2979..000000000 --- a/keygen/primecandidate.c +++ /dev/null @@ -1,447 +0,0 @@ -/* - * primecandidate.c: implementation of the PrimeCandidateSource - * abstraction declared in sshkeygen.h. - */ - -#include -#include "ssh.h" -#include "mpint.h" -#include "mpunsafe.h" -#include "sshkeygen.h" - -struct avoid { - unsigned mod, res; -}; - -struct PrimeCandidateSource { - unsigned bits; - bool ready, try_sophie_germain; - bool one_shot, thrown_away_my_shot; - - /* We'll start by making up a random number strictly less than this ... */ - mp_int *limit; - - /* ... then we'll multiply by 'factor', and add 'addend'. */ - mp_int *factor, *addend; - - /* Then we'll try to add a small multiple of 'factor' to it to - * avoid it being a multiple of any small prime. Also, for RSA, we - * may need to avoid it being _this_ multiple of _this_: */ - unsigned avoid_residue, avoid_modulus; - - /* Once we're actually running, this will be the complete list of - * (modulus, residue) pairs we want to avoid. */ - struct avoid *avoids; - size_t navoids, avoidsize; - - /* List of known primes that our number will be congruent to 1 modulo */ - mp_int **kps; - size_t nkps, kpsize; -}; - -PrimeCandidateSource *pcs_new_with_firstbits(unsigned bits, - unsigned first, unsigned nfirst) -{ - PrimeCandidateSource *s = snew(PrimeCandidateSource); - - assert(first >> (nfirst-1) == 1); - - s->bits = bits; - s->ready = false; - s->try_sophie_germain = false; - s->one_shot = false; - s->thrown_away_my_shot = false; - - s->kps = NULL; - s->nkps = s->kpsize = 0; - - s->avoids = NULL; - s->navoids = s->avoidsize = 0; - - /* Make the number that's the lower limit of our range */ - mp_int *firstmp = mp_from_integer(first); - mp_int *base = mp_lshift_fixed(firstmp, bits - nfirst); - mp_free(firstmp); - - /* Set the low bit of that, because all (nontrivial) primes are odd */ - mp_set_bit(base, 0, 1); - - /* That's our addend. Now initialise factor to 2, to ensure we - * only generate odd numbers */ - s->factor = mp_from_integer(2); - s->addend = base; - - /* And that means the limit of our random numbers must be one - * factor of two _less_ than the position of the low bit of - * 'first', because we'll be multiplying the random number by - * 2 immediately afterwards. */ - s->limit = mp_power_2(bits - nfirst - 1); - - /* avoid_modulus == 0 signals that there's no extra residue to avoid */ - s->avoid_residue = 1; - s->avoid_modulus = 0; - - return s; -} - -PrimeCandidateSource *pcs_new(unsigned bits) -{ - return pcs_new_with_firstbits(bits, 1, 1); -} - -void pcs_free(PrimeCandidateSource *s) -{ - mp_free(s->limit); - mp_free(s->factor); - mp_free(s->addend); - for (size_t i = 0; i < s->nkps; i++) - mp_free(s->kps[i]); - sfree(s->avoids); - sfree(s->kps); - sfree(s); -} - -void pcs_try_sophie_germain(PrimeCandidateSource *s) -{ - s->try_sophie_germain = true; -} - -void pcs_set_oneshot(PrimeCandidateSource *s) -{ - s->one_shot = true; -} - -static void pcs_require_residue_inner(PrimeCandidateSource *s, - mp_int *mod, mp_int *res) -{ - /* - * We already have a factor and addend. Ensure this one doesn't - * contradict it. - */ - mp_int *gcd = mp_gcd(mod, s->factor); - mp_int *test1 = mp_mod(s->addend, gcd); - mp_int *test2 = mp_mod(res, gcd); - assert(mp_cmp_eq(test1, test2)); - mp_free(test1); - mp_free(test2); - - /* - * Reduce our input factor and addend, which are constraints on - * the ultimate output number, so that they're constraints on the - * initial cofactor we're going to make up. - * - * If we're generating x and we want to ensure ax+b == r (mod m), - * how does that work? We've already checked that b == r modulo g - * = gcd(a,m), i.e. r-b is a multiple of g, and so are a and m. So - * let's write a=gA, m=gM, (r-b)=gR, and then we can start by - * dividing that off: - * - * ax == r-b (mod m ) - * => gAx == gR (mod gM) - * => Ax == R (mod M) - * - * Now the moduli A,M are coprime, which makes things easier. - * - * We're going to need to generate the x in this equation by - * generating a new smaller value y, multiplying it by M, and - * adding some constant K. So we have x = My + K, and we need to - * work out what K will satisfy the above equation. In other - * words, we need A(My+K) == R (mod M), and the AMy term vanishes, - * so we just need AK == R (mod M). So our congruence is solved by - * setting K to be R * A^{-1} mod M. - */ - mp_int *A = mp_div(s->factor, gcd); - mp_int *M = mp_div(mod, gcd); - mp_int *Rpre = mp_modsub(res, s->addend, mod); - mp_int *R = mp_div(Rpre, gcd); - mp_int *Ainv = mp_invert(A, M); - mp_int *K = mp_modmul(R, Ainv, M); - - mp_free(gcd); - mp_free(Rpre); - mp_free(Ainv); - mp_free(A); - mp_free(R); - - /* - * So we know we have to transform our existing (factor, addend) - * pair into (factor * M, addend * factor * K). Now we just need - * to work out what the limit should be on the random value we're - * generating. - * - * If we need My+K < old_limit, then y < (old_limit-K)/M. But the - * RHS is a fraction, so in integers, we need y < ceil of it. - */ - assert(!mp_cmp_hs(K, s->limit)); - mp_int *dividend = mp_add(s->limit, M); - mp_sub_integer_into(dividend, dividend, 1); - mp_sub_into(dividend, dividend, K); - mp_free(s->limit); - s->limit = mp_div(dividend, M); - mp_free(dividend); - - /* - * Now just update the real factor and addend, and we're done. - */ - - mp_int *addend_old = s->addend; - mp_int *tmp = mp_mul(s->factor, K); /* use the _old_ value of factor */ - s->addend = mp_add(s->addend, tmp); - mp_free(tmp); - mp_free(addend_old); - - mp_int *factor_old = s->factor; - s->factor = mp_mul(s->factor, M); - mp_free(factor_old); - - mp_free(M); - mp_free(K); - s->factor = mp_unsafe_shrink(s->factor); - s->addend = mp_unsafe_shrink(s->addend); - s->limit = mp_unsafe_shrink(s->limit); -} - -void pcs_require_residue(PrimeCandidateSource *s, - mp_int *mod, mp_int *res_orig) -{ - /* - * Reduce the input residue to its least non-negative value, in - * case it was given as a larger equivalent value. - */ - mp_int *res_reduced = mp_mod(res_orig, mod); - pcs_require_residue_inner(s, mod, res_reduced); - mp_free(res_reduced); -} - -void pcs_require_residue_1(PrimeCandidateSource *s, mp_int *mod) -{ - mp_int *res = mp_from_integer(1); - pcs_require_residue(s, mod, res); - mp_free(res); -} - -void pcs_require_residue_1_mod_prime(PrimeCandidateSource *s, mp_int *mod) -{ - pcs_require_residue_1(s, mod); - - sgrowarray(s->kps, s->kpsize, s->nkps); - s->kps[s->nkps++] = mp_copy(mod); -} - -void pcs_avoid_residue_small(PrimeCandidateSource *s, - unsigned mod, unsigned res) -{ - assert(!s->avoid_modulus); /* can't cope with more than one */ - s->avoid_modulus = mod; - s->avoid_residue = res % mod; /* reduce, just in case */ -} - -static int avoid_cmp(const void *av, const void *bv) -{ - const struct avoid *a = (const struct avoid *)av; - const struct avoid *b = (const struct avoid *)bv; - return a->mod < b->mod ? -1 : a->mod > b->mod ? +1 : 0; -} - -static uint64_t invert(uint64_t a, uint64_t m) -{ - int64_t v0 = a, i0 = 1; - int64_t v1 = m, i1 = 0; - while (v0) { - int64_t tmp, q = v1 / v0; - tmp = v0; v0 = v1 - q*v0; v1 = tmp; - tmp = i0; i0 = i1 - q*i0; i1 = tmp; - } - assert(v1 == 1 || v1 == -1); - return i1 * v1; -} - -void pcs_ready(PrimeCandidateSource *s) -{ - /* - * List all the small (modulus, residue) pairs we want to avoid. - */ - - init_smallprimes(); - -#define ADD_AVOID(newmod, newres) do { \ - sgrowarray(s->avoids, s->avoidsize, s->navoids); \ - s->avoids[s->navoids].mod = (newmod); \ - s->avoids[s->navoids].res = (newres); \ - s->navoids++; \ - } while (0) - - unsigned limit = (mp_hs_integer(s->addend, 65536) ? 65536 : - mp_get_integer(s->addend)); - - /* - * Don't be divisible by any small prime, or at least, any prime - * smaller than our output number might actually manage to be. (If - * asked to generate a really small prime, it would be - * embarrassing to rule out legitimate answers on the grounds that - * they were divisible by themselves.) - */ - for (size_t i = 0; i < NSMALLPRIMES && smallprimes[i] < limit; i++) - ADD_AVOID(smallprimes[i], 0); - - if (s->try_sophie_germain) { - /* - * If we're aiming to generate a Sophie Germain prime (i.e. p - * such that 2p+1 is also prime), then we also want to ensure - * 2p+1 is not congruent to 0 mod any small prime, because if - * it is, we'll waste a lot of time generating a p for which - * 2p+1 can't possibly work. So we have to avoid an extra - * residue mod each odd q. - * - * We can simplify: 2p+1 == 0 (mod q) - * => 2p == -1 (mod q) - * => p == -2^{-1} (mod q) - * - * There's no need to do Euclid's algorithm to compute those - * inverses, because for any odd q, the modular inverse of -2 - * mod q is just (q-1)/2. (Proof: multiplying it by -2 gives - * 1-q, which is congruent to 1 mod q.) - */ - for (size_t i = 0; i < NSMALLPRIMES && smallprimes[i] < limit; i++) - if (smallprimes[i] != 2) - ADD_AVOID(smallprimes[i], (smallprimes[i] - 1) / 2); - } - - /* - * Finally, if there's a particular modulus and residue we've been - * told to avoid, put it on the list. - */ - if (s->avoid_modulus) - ADD_AVOID(s->avoid_modulus, s->avoid_residue); - -#undef ADD_AVOID - - /* - * Sort our to-avoid list by modulus. Partly this is so that we'll - * check the smaller moduli first during the live runs, which lets - * us spot most failing cases earlier rather than later. Also, it - * brings equal moduli together, so that we can reuse the residue - * we computed from a previous one. - */ - qsort(s->avoids, s->navoids, sizeof(*s->avoids), avoid_cmp); - - /* - * Next, adjust each of these moduli to take account of our factor - * and addend. If we want factor*x+addend to avoid being congruent - * to 'res' modulo 'mod', then x itself must avoid being congruent - * to (res - addend) * factor^{-1}. - * - * If factor == 0 modulo mod, then the answer will have a fixed - * residue anyway, so we can discard it from our list to test. - */ - int64_t factor_m = 0, addend_m = 0, last_mod = 0; - - size_t out = 0; - for (size_t i = 0; i < s->navoids; i++) { - int64_t mod = s->avoids[i].mod, res = s->avoids[i].res; - if (mod != last_mod) { - last_mod = mod; - addend_m = mp_mod_known_integer(s->addend, mod); - factor_m = mp_mod_known_integer(s->factor, mod); - } - - if (factor_m == 0) { - assert(res != addend_m); - continue; - } - - res = (res - addend_m) * invert(factor_m, mod); - res %= mod; - if (res < 0) - res += mod; - - s->avoids[out].mod = mod; - s->avoids[out].res = res; - out++; - } - - s->navoids = out; - - s->ready = true; -} - -mp_int *pcs_generate(PrimeCandidateSource *s) -{ - assert(s->ready); - if (s->one_shot) { - if (s->thrown_away_my_shot) - return NULL; - s->thrown_away_my_shot = true; - } - - while (true) { - mp_int *x = mp_random_upto(s->limit); - - int64_t x_res = 0, last_mod = 0; - bool ok = true; - - for (size_t i = 0; i < s->navoids; i++) { - int64_t mod = s->avoids[i].mod, avoid_res = s->avoids[i].res; - - if (mod != last_mod) { - last_mod = mod; - x_res = mp_mod_known_integer(x, mod); - } - - if (x_res == avoid_res) { - ok = false; - break; - } - } - - if (!ok) { - mp_free(x); - if (s->one_shot) - return NULL; - continue; /* try a new x */ - } - - /* - * We've found a viable x. Make the final output value. - */ - mp_int *toret = mp_new(s->bits); - mp_mul_into(toret, x, s->factor); - mp_add_into(toret, toret, s->addend); - mp_free(x); - return toret; - } -} - -void pcs_inspect(PrimeCandidateSource *pcs, mp_int **limit_out, - mp_int **factor_out, mp_int **addend_out) -{ - *limit_out = mp_copy(pcs->limit); - *factor_out = mp_copy(pcs->factor); - *addend_out = mp_copy(pcs->addend); -} - -unsigned pcs_get_bits(PrimeCandidateSource *pcs) -{ - return pcs->bits; -} - -unsigned pcs_get_bits_remaining(PrimeCandidateSource *pcs) -{ - return mp_get_nbits(pcs->limit); -} - -mp_int *pcs_get_upper_bound(PrimeCandidateSource *pcs) -{ - /* Compute (limit-1) * factor + addend */ - mp_int *tmp = mp_mul(pcs->limit, pcs->factor); - mp_int *bound = mp_add(tmp, pcs->addend); - mp_free(tmp); - mp_sub_into(bound, bound, pcs->factor); - return bound; -} - -mp_int **pcs_get_known_prime_factors(PrimeCandidateSource *pcs, size_t *nout) -{ - *nout = pcs->nkps; - return pcs->kps; -} diff --git a/keygen/rsa.c b/keygen/rsa.c deleted file mode 100644 index b9676e7ac..000000000 --- a/keygen/rsa.c +++ /dev/null @@ -1,292 +0,0 @@ -/* - * RSA key generation. - */ - -#include - -#include "ssh.h" -#include "sshkeygen.h" -#include "mpint.h" - -#define RSA_EXPONENT 65537 - -#define NFIRSTBITS 13 -static void invent_firstbits(unsigned *one, unsigned *two, - unsigned min_separation); - -typedef struct RSAPrimeDetails RSAPrimeDetails; -struct RSAPrimeDetails { - bool strong; - int bits, bitsm1m1, bitsm1, bitsp1; - unsigned firstbits; - ProgressPhase phase_main, phase_m1m1, phase_m1, phase_p1; -}; - -#define STRONG_MARGIN (20 + NFIRSTBITS) - -static RSAPrimeDetails setup_rsa_prime( - int bits, bool strong, PrimeGenerationContext *pgc, ProgressReceiver *prog) -{ - RSAPrimeDetails pd; - pd.bits = bits; - if (strong) { - pd.bitsm1 = (bits - STRONG_MARGIN) / 2; - pd.bitsp1 = (bits - STRONG_MARGIN) - pd.bitsm1; - pd.bitsm1m1 = (pd.bitsm1 - STRONG_MARGIN) / 2; - if (pd.bitsm1m1 < STRONG_MARGIN) { - /* Absurdly small prime, but we should at least not crash. */ - strong = false; - } - } - pd.strong = strong; - - if (pd.strong) { - pd.phase_m1m1 = primegen_add_progress_phase(pgc, prog, pd.bitsm1m1); - pd.phase_m1 = primegen_add_progress_phase(pgc, prog, pd.bitsm1); - pd.phase_p1 = primegen_add_progress_phase(pgc, prog, pd.bitsp1); - } - pd.phase_main = primegen_add_progress_phase(pgc, prog, pd.bits); - - return pd; -} - -static mp_int *generate_rsa_prime( - RSAPrimeDetails pd, PrimeGenerationContext *pgc, ProgressReceiver *prog) -{ - mp_int *m1m1 = NULL, *m1 = NULL, *p1 = NULL, *p = NULL; - PrimeCandidateSource *pcs; - - if (pd.strong) { - progress_start_phase(prog, pd.phase_m1m1); - pcs = pcs_new_with_firstbits(pd.bitsm1m1, pd.firstbits, NFIRSTBITS); - m1m1 = primegen_generate(pgc, pcs, prog); - progress_report_phase_complete(prog); - - progress_start_phase(prog, pd.phase_m1); - pcs = pcs_new_with_firstbits(pd.bitsm1, pd.firstbits, NFIRSTBITS); - pcs_require_residue_1_mod_prime(pcs, m1m1); - m1 = primegen_generate(pgc, pcs, prog); - progress_report_phase_complete(prog); - - progress_start_phase(prog, pd.phase_p1); - pcs = pcs_new_with_firstbits(pd.bitsp1, pd.firstbits, NFIRSTBITS); - p1 = primegen_generate(pgc, pcs, prog); - progress_report_phase_complete(prog); - } - - progress_start_phase(prog, pd.phase_main); - pcs = pcs_new_with_firstbits(pd.bits, pd.firstbits, NFIRSTBITS); - pcs_avoid_residue_small(pcs, RSA_EXPONENT, 1); - if (pd.strong) { - pcs_require_residue_1_mod_prime(pcs, m1); - mp_int *p1_minus_1 = mp_copy(p1); - mp_sub_integer_into(p1_minus_1, p1, 1); - pcs_require_residue(pcs, p1, p1_minus_1); - mp_free(p1_minus_1); - } - p = primegen_generate(pgc, pcs, prog); - progress_report_phase_complete(prog); - - if (m1m1) - mp_free(m1m1); - if (m1) - mp_free(m1); - if (p1) - mp_free(p1); - - return p; -} - -int rsa_generate(RSAKey *key, int bits, bool strong, - PrimeGenerationContext *pgc, ProgressReceiver *prog) -{ - key->sshk.vt = &ssh_rsa; - - /* - * We don't generate e; we just use a standard one always. - */ - mp_int *exponent = mp_from_integer(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.) - * - * We give a min_separation of 2 to invent_firstbits(), ensuring - * that the two primes won't be very close to each other. (The - * chance of them being _dangerously_ close is negligible - even - * more so than an attacker guessing a whole 256-bit session key - - * but it doesn't cost much to make sure.) - */ - int qbits = bits / 2; - int pbits = bits - qbits; - assert(pbits >= qbits); - - RSAPrimeDetails pd = setup_rsa_prime(pbits, strong, pgc, prog); - RSAPrimeDetails qd = setup_rsa_prime(qbits, strong, pgc, prog); - progress_ready(prog); - - invent_firstbits(&pd.firstbits, &qd.firstbits, 2); - - mp_int *p = generate_rsa_prime(pd, pgc, prog); - mp_int *q = generate_rsa_prime(qd, pgc, prog); - - /* - * Ensure p > q, by swapping them if not. - * - * We only need to do this if the two primes were generated with - * the same number of bits (i.e. if the requested key size is - * even) - otherwise it's already guaranteed! - */ - if (pbits == qbits) { - mp_cond_swap(p, q, mp_cmp_hs(q, p)); - } else { - assert(mp_cmp_hs(p, q)); - } - - /* - * 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). - */ - mp_int *modulus = mp_mul(p, q); - mp_int *pm1 = mp_copy(p); - mp_sub_integer_into(pm1, pm1, 1); - mp_int *qm1 = mp_copy(q); - mp_sub_integer_into(qm1, qm1, 1); - mp_int *phi_n = mp_mul(pm1, qm1); - mp_free(pm1); - mp_free(qm1); - mp_int *private_exponent = mp_invert(exponent, phi_n); - mp_free(phi_n); - mp_int *iqmp = mp_invert(q, p); - - /* - * Populate the returned structure. - */ - key->modulus = modulus; - key->exponent = exponent; - key->private_exponent = private_exponent; - key->p = p; - key->q = q; - key->iqmp = iqmp; - - key->bits = mp_get_nbits(modulus); - key->bytes = (key->bits + 7) / 8; - - return 1; -} - -/* - * Invent a pair of values suitable for use as the 'firstbits' values - * for the two RSA primes, such that their product is at least 2, and - * such that their difference is also at least min_separation. - * - * This is used for generating RSA 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!' - */ -static inline unsigned firstbits_b_min( - unsigned a, unsigned lo, unsigned hi, unsigned min_separation) -{ - /* To get a large enough product, b must be at least this much */ - unsigned b_min = (2*lo*lo + a - 1) / a; - /* Now enforce a hi) - b_min = hi; - return b_min; -} - -static void invent_firstbits(unsigned *one, unsigned *two, - unsigned min_separation) -{ - /* - * We'll pick 12 initial bits (number selected at random) for each - * prime, not counting the leading 1. So we want to return two - * values in the range [2^12,2^13) whose product is at least 2^25. - * - * Strategy: count up all the viable pairs, then select a random - * number in that range and use it to pick a pair. - * - * To keep things simple, we'll ensure a < b, and randomly swap - * them at the end. - */ - const unsigned lo = 1<<12, hi = 1<<13, minproduct = 2*lo*lo; - unsigned a, b; - - /* - * Count up the number of prefixes of b that would be valid for - * each prefix of a. - */ - mp_int *total = mp_new(32); - for (a = lo; a < hi; a++) { - unsigned b_min = firstbits_b_min(a, lo, hi, min_separation); - mp_add_integer_into(total, total, hi - b_min); - } - - /* - * Make up a random number in the range [0,2*total). - */ - mp_int *mlo = mp_from_integer(0), *mhi = mp_new(32); - mp_lshift_fixed_into(mhi, total, 1); - mp_int *randval = mp_random_in_range(mlo, mhi); - mp_free(mlo); - mp_free(mhi); - - /* - * Use the low bit of randval as our swap indicator, leaving the - * rest of it in the range [0,total). - */ - unsigned swap = mp_get_bit(randval, 0); - mp_rshift_fixed_into(randval, randval, 1); - - /* - * Now do the same counting loop again to make the actual choice. - */ - a = b = 0; - for (unsigned a_candidate = lo; a_candidate < hi; a_candidate++) { - unsigned b_min = firstbits_b_min(a_candidate, lo, hi, min_separation); - unsigned limit = hi - b_min; - - unsigned b_candidate = b_min + mp_get_integer(randval); - unsigned use_it = 1 ^ mp_hs_integer(randval, limit); - a ^= (a ^ a_candidate) & -use_it; - b ^= (b ^ b_candidate) & -use_it; - - mp_sub_integer_into(randval, randval, limit); - } - - mp_free(randval); - mp_free(total); - - /* - * Check everything came out right. - */ - assert(lo <= a); - assert(a < hi); - assert(lo <= b); - assert(b < hi); - assert(a * b >= minproduct); - assert(b >= a + min_separation); - - /* - * Last-minute optional swap of a and b. - */ - unsigned diff = (a ^ b) & (-swap); - a ^= diff; - b ^= diff; - - *one = a; - *two = b; -} diff --git a/keygen/smallprimes.c b/keygen/smallprimes.c deleted file mode 100644 index a43b0bde3..000000000 --- a/keygen/smallprimes.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * smallprimes.c: implementation of the array of small primes defined - * in sshkeygen.h. - */ - -#include -#include "ssh.h" -#include "sshkeygen.h" - -/* The real array that stores the primes. It has to be writable in - * this module, but outside this module, we only expose the - * const-qualified pointer 'smallprimes' so that nobody else can - * accidentally overwrite it. */ -static unsigned short smallprimes_array[NSMALLPRIMES]; - -const unsigned short *const smallprimes = smallprimes_array; - -void init_smallprimes(void) -{ - if (smallprimes_array[0]) - return; /* already done */ - - bool A[65536]; - - for (size_t i = 2; i < lenof(A); i++) - A[i] = true; - - for (size_t i = 2; i < lenof(A); i++) { - if (!A[i]) - continue; - for (size_t j = 2*i; j < lenof(A); j += i) - A[j] = false; - } - - size_t pos = 0; - for (size_t i = 2; i < lenof(A); i++) { - if (A[i]) { - assert(pos < NSMALLPRIMES); - smallprimes_array[pos++] = i; - } - } - - assert(pos == NSMALLPRIMES); -} diff --git a/ldisc.c b/ldisc.c deleted file mode 100644 index caff52d08..000000000 --- a/ldisc.c +++ /dev/null @@ -1,578 +0,0 @@ -/* - * 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" - -struct Ldisc_tag { - Terminal *term; - Backend *backend; - Seat *seat; - - /* - * When the backend is not reporting true from sendok(), terminal - * input that comes here is stored in this bufchain instead. When - * the backend later decides it wants session input, we empty the - * queue in ldisc_check_sendok_callback(), passing its contents on - * to the backend. Before then, we also provide data from this - * queue to term_get_userpass_input() via ldisc_get_input_token(), - * to be interpreted as user responses to username and password - * prompts during authentication. - * - * Unfortunately, the data stored in this queue is not all of the - * same type: our output to the backend consists of both raw bytes - * sent to backend_send(), and also session specials such as - * SS_EOL and SS_EC. So we have to encode our queued data in a way - * that can represent both. - * - * The encoding is private to this source file, so we can change - * it if necessary and only have to worry about the encode and - * decode functions here. Currently, it is: - * - * - Bytes other than 0xFF are stored literally. - * - The byte 0xFF itself is stored as 0xFF 0xFF. - * - A session special (code, arg) is stored as 0xFF, followed by - * a big-endian 4-byte integer containing code, followed by - * another big-endian 4-byte integer containing arg. - * - * (This representation relies on session special codes being at - * most 0xFEFFFFFF when represented in 32 bits, so that the first - * byte of the 'code' integer can't be confused with the 0xFF - * followup byte indicating a literal 0xFF, But since session - * special codes are defined by an enum counting up from zero, and - * there are only a couple of dozen of them, that shouldn't be a - * problem! Even so, just in case, an assertion checks that at - * encode time.) - */ - bufchain input_queue; - - IdempotentCallback input_queue_callback; - prompts_t *prompts; - - /* - * Values cached out of conf. - */ - bool telnet_keyboard, telnet_newline; - int protocol, localecho, localedit; - - char *buf; - size_t buflen, bufsiz; - bool quotenext; -}; - -#define ECHOING (ldisc->localecho == FORCE_ON || \ - (ldisc->localecho == AUTO && \ - (backend_ldisc_option_state(ldisc->backend, LD_ECHO)))) -#define EDITING (ldisc->localedit == FORCE_ON || \ - (ldisc->localedit == AUTO && \ - (backend_ldisc_option_state(ldisc->backend, LD_EDIT)))) - -static void c_write(Ldisc *ldisc, const void *buf, int len) -{ - seat_stdout(ldisc->seat, 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, &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 bool char_start(Ldisc *ldisc, unsigned char c) -{ - if (in_utf(ldisc->term)) - return (c < 0x80 || c >= 0xC0); - else - return true; -} - -static void bsb(Ldisc *ldisc, int n) -{ - while (n--) - c_write(ldisc, "\010 \010", 3); -} - -static void ldisc_input_queue_callback(void *ctx); - -#define CTRL(x) (x^'@') -#define KCTRL(x) ((x^'@') | 0x100) - -Ldisc *ldisc_create(Conf *conf, Terminal *term, Backend *backend, Seat *seat) -{ - Ldisc *ldisc = snew(Ldisc); - - ldisc->buf = NULL; - ldisc->buflen = 0; - ldisc->bufsiz = 0; - ldisc->quotenext = false; - - ldisc->backend = backend; - ldisc->term = term; - ldisc->seat = seat; - - bufchain_init(&ldisc->input_queue); - - ldisc->prompts = NULL; - ldisc->input_queue_callback.fn = ldisc_input_queue_callback; - ldisc->input_queue_callback.ctx = ldisc; - ldisc->input_queue_callback.queued = false; - bufchain_set_callback(&ldisc->input_queue, &ldisc->input_queue_callback); - - ldisc_configure(ldisc, conf); - - /* Link ourselves into the backend and the terminal */ - if (term) - term->ldisc = ldisc; - if (backend) - backend_provide_ldisc(backend, ldisc); - - return ldisc; -} - -void ldisc_configure(Ldisc *ldisc, Conf *conf) -{ - ldisc->telnet_keyboard = conf_get_bool(conf, CONF_telnet_keyboard); - ldisc->telnet_newline = conf_get_bool(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(Ldisc *ldisc) -{ - bufchain_clear(&ldisc->input_queue); - if (ldisc->term) - ldisc->term->ldisc = NULL; - if (ldisc->backend) - backend_provide_ldisc(ldisc->backend, NULL); - if (ldisc->buf) - sfree(ldisc->buf); - if (ldisc->prompts && ldisc->prompts->ldisc_ptr_to_us == &ldisc->prompts) - ldisc->prompts->ldisc_ptr_to_us = NULL; - delete_callbacks_for_context(ldisc); - sfree(ldisc); -} - -void ldisc_echoedit_update(Ldisc *ldisc) -{ - seat_echoedit_update(ldisc->seat, ECHOING, EDITING); -} - -void ldisc_enable_prompt_callback(Ldisc *ldisc, prompts_t *prompts) -{ - /* - * Called by the terminal to indicate that there's a prompts_t - * currently in flight, or to indicate that one has just finished - * (by passing NULL). When ldisc->prompts is not null, we notify - * the terminal whenever new data arrives in our input queue, so - * that it can continue the interactive prompting process. - */ - ldisc->prompts = prompts; - if (prompts) - ldisc->prompts->ldisc_ptr_to_us = &ldisc->prompts; -} - -static void ldisc_input_queue_callback(void *ctx) -{ - /* - * Toplevel callback that is triggered whenever the input queue - * lengthens. If we're currently processing an interactive prompt, - * we call back the Terminal to tell it to do some more stuff with - * that prompt based on the new input. - */ - Ldisc *ldisc = (Ldisc *)ctx; - if (ldisc->term && ldisc->prompts) { - /* - * The integer return value from this call is discarded, - * because we have no channel to pass it on to the backend - * that originally wanted it. But that's OK, because if the - * return value is >= 0 (that is, the prompts are either - * completely filled in, or aborted by the user), then the - * terminal will notify the callback in the prompts_t, and - * when that calls term_get_userpass_input again, it will - * return the same answer again. - */ - term_get_userpass_input(ldisc->term, ldisc->prompts); - } -} - -static void ldisc_to_backend_raw( - Ldisc *ldisc, const void *vbuf, size_t len) -{ - if (backend_sendok(ldisc->backend)) { - backend_send(ldisc->backend, vbuf, len); - } else { - const char *buf = (const char *)vbuf; - while (len > 0) { - /* - * Encode raw data in input_queue, by storing large chunks - * as long as they don't include 0xFF, and pausing every - * time they do to escape it. - */ - const char *ff = memchr(buf, '\xFF', len); - size_t this_len = ff ? ff - buf : len; - if (this_len > 0) { - bufchain_add(&ldisc->input_queue, buf, len); - } else { - bufchain_add(&ldisc->input_queue, "\xFF\xFF", 2); - this_len = 1; - } - buf += this_len; - len -= this_len; - } - } -} - -static void ldisc_to_backend_special( - Ldisc *ldisc, SessionSpecialCode code, int arg) -{ - if (backend_sendok(ldisc->backend)) { - backend_special(ldisc->backend, code, arg); - } else { - /* - * Encode a session special in input_queue. - */ - unsigned char data[9]; - data[0] = 0xFF; - PUT_32BIT_MSB_FIRST(data+1, code); - PUT_32BIT_MSB_FIRST(data+5, arg); - assert(data[1] != 0xFF && - "SessionSpecialCode encoding collides with FF FF escape"); - bufchain_add(&ldisc->input_queue, data, 9); - } -} - -bool ldisc_has_input_buffered(Ldisc *ldisc) -{ - return bufchain_size(&ldisc->input_queue) > 0; -} - -LdiscInputToken ldisc_get_input_token(Ldisc *ldisc) -{ - assert(bufchain_size(&ldisc->input_queue) > 0 && - "You're not supposed to call this unless there is buffered input!"); - - LdiscInputToken tok; - - char c; - bufchain_fetch_consume(&ldisc->input_queue, &c, 1); - if (c != '\xFF') { - /* A literal non-FF byte */ - tok.is_special = false; - tok.chr = c; - return tok; - } else { - char data[8]; - - /* See if the byte after the FF is also FF, indicating a literal FF */ - bufchain_fetch_consume(&ldisc->input_queue, data, 1); - if (data[0] == '\xFF') { - tok.is_special = false; - tok.chr = '\xFF'; - return tok; - } - - /* If not, get the rest of an 8-byte chunk and decode a special */ - bufchain_fetch_consume(&ldisc->input_queue, data+1, 7); - tok.is_special = true; - tok.code = GET_32BIT_MSB_FIRST(data); - tok.arg = toint(GET_32BIT_MSB_FIRST(data+4)); - return tok; - } -} - -static void ldisc_check_sendok_callback(void *ctx) -{ - Ldisc *ldisc = (Ldisc *)ctx; - - if (!(ldisc->backend && backend_sendok(ldisc->backend))) - return; - - /* - * Flush the ldisc input queue into the backend, which is now - * willing to receive the data. - */ - while (bufchain_size(&ldisc->input_queue) > 0) { - /* - * Process either a chunk of non-special data, or an FF - * escape, depending on whether the first thing we see is an - * FF byte. - */ - ptrlen data = bufchain_prefix(&ldisc->input_queue); - const char *ff = memchr(data.ptr, '\xFF', data.len); - if (ff != data.ptr) { - /* Send a maximal block of data not containing any - * difficult bytes. */ - if (ff) - data.len = ff - (const char *)data.ptr; - backend_send(ldisc->backend, data.ptr, data.len); - bufchain_consume(&ldisc->input_queue, data.len); - } else { - /* Decode either a special or an escaped FF byte. The - * easiest way to do this is to reuse the decoding code - * already in ldisc_get_input_token. */ - LdiscInputToken tok = ldisc_get_input_token(ldisc); - if (tok.is_special) - backend_special(ldisc->backend, tok.code, tok.arg); - else - backend_send(ldisc->backend, &tok.chr, 1); - } - } -} - -void ldisc_check_sendok(Ldisc *ldisc) -{ - queue_toplevel_callback(ldisc_check_sendok_callback, ldisc); -} - -void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) -{ - const char *buf = (const char *)vbuf; - int keyflag = 0; - - assert(ldisc->term); - - 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_to_backend_special(ldisc, SS_EL, 0); - /* - * 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_to_backend_special(ldisc, SS_IP, 0); - if (c == CTRL('Z')) - ldisc_to_backend_special(ldisc, SS_SUSP, 0); - if (c == CTRL('\\')) - ldisc_to_backend_special(ldisc, SS_ABORT, 0); - 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_to_backend_special(ldisc, SS_EOF, 0); - } else { - ldisc_to_backend_raw(ldisc, 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_to_backend_raw(ldisc, ldisc->buf, ldisc->buflen); - if (ldisc->protocol == PROT_RAW) - ldisc_to_backend_raw(ldisc, "\r\n", 2); - else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) - ldisc_to_backend_special(ldisc, SS_EOL, 0); - else - ldisc_to_backend_raw(ldisc, "\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: - sgrowarray(ldisc->buf, ldisc->bufsiz, ldisc->buflen); - ldisc->buf[ldisc->buflen++] = c; - if (ECHOING) - pwrite(ldisc, (unsigned char) c); - ldisc->quotenext = false; - break; - } - } - } else { - if (ldisc->buflen != 0) { - ldisc_to_backend_raw(ldisc, 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_to_backend_special(ldisc, SS_EOL, 0); - else - ldisc_to_backend_raw(ldisc, "\r", 1); - break; - case CTRL('?'): - case CTRL('H'): - if (ldisc->telnet_keyboard) { - ldisc_to_backend_special(ldisc, SS_EC, 0); - break; - } - case CTRL('C'): - if (ldisc->telnet_keyboard) { - ldisc_to_backend_special(ldisc, SS_IP, 0); - break; - } - case CTRL('Z'): - if (ldisc->telnet_keyboard) { - ldisc_to_backend_special(ldisc, SS_SUSP, 0); - break; - } - - default: - ldisc_to_backend_raw(ldisc, buf, len); - break; - } - } else - ldisc_to_backend_raw(ldisc, buf, len); - } - } -} diff --git a/licence.h b/licence.h deleted file mode 100644 index 9b9335b0f..000000000 --- a/licence.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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-2021 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, Christopher Staite, Lorenz Diener, Christian Brabandt, Jeff Smith, Pavel Kryukov, Maxim Kuznetsov, Svyatoslav Kuzmich, Nico Williams, Viktor Dukhovni, Josh Dersch, Lars Brinkhoff, 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-2021 Simon Tatham" diff --git a/licence.pl b/licence.pl deleted file mode 100644 index f927bcb2d..000000000 --- a/licence.pl +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env perl - -# This script generates licence.h (containing the PuTTY licence in the -# form of macros expanding to C string literals) from the LICENCE -# master file. It also regenerates the licence-related Halibut input -# files. - -use warnings; -use File::Basename; -use Getopt::Long; - -my $usage = "usage: licence.pl (--header|--licencedoc|--copyrightdoc) " . - "[-o OUTFILE]\n"; -my $mode = undef; -my $output = undef; -GetOptions("--header" => sub {$mode = "header"}, - "--licencedoc" => sub {$mode = "licencedoc"}, - "--copyrightdoc" => sub {$mode = "copyrightdoc"}, - "o|output=s" => \$output) - and defined $mode - or die $usage; - -# Read the input file. We expect to find that alongside this script. -my $infile = (dirname __FILE__) . "/LICENCE"; -open my $in, $infile or die "$infile: open: $!\n"; -my @lines = (); -while (<$in>) { - y/\r//d; - chomp; - push @lines, $_; -} -close $in; - -# Format into paragraphs. -my @paras = (); -my $para = undef; -for my $line (@lines) { - if ($line eq "") { - $para = undef; - } elsif (!defined $para) { - push @paras, $line; - $para = \$paras[$#paras]; - } else { - $$para .= " " . $line; - } -} - -# Get the copyright years and short form of copyright holder. -die "bad format of first paragraph\n" - unless $paras[0] =~ m!copyright ([^\.]*)\.!i; -$shortdetails = $1; - -my $out = ""; - -if ($mode eq "header") { - $out .= "/*\n"; - $out .= " * licence.h - macro definitions for the PuTTY licence.\n"; - $out .= " *\n"; - $out .= " * Generated by @{[basename __FILE__]} from $infile.\n"; - $out .= " * You should edit those files rather than editing this one.\n"; - $out .= " */\n"; - $out .= "\n"; - - $out .= "#define LICENCE_TEXT(parsep) \\\n"; - for my $i (0..$#paras) { - my $lit = &stringlit($paras[$i]); - $out .= " parsep \\\n" if $i > 0; - $out .= " \"$lit\""; - $out .= " \\" if $i < $#paras; - $out .= "\n"; - } - $out .= "\n"; - - $out .= sprintf "#define SHORT_COPYRIGHT_DETAILS \"%s\"\n", - &stringlit($shortdetails); -} elsif ($mode eq "licencedoc") { - # Write out doc/licence.but. - - $out .= "\\# Generated by @{[basename __FILE__]} from $infile.\n"; - $out .= "\\# You should edit those files rather than editing this one.\n\n"; - - $out .= "\\A{licence} PuTTY \\ii{Licence}\n\n"; - - for my $i (0..$#paras) { - my $para = &halibutescape($paras[$i]); - if ($i == 0) { - $para =~ s!copyright!\\i{copyright}!; # index term in paragraph 1 - } - $out .= "$para\n\n"; - } -} elsif ($mode eq "copyrightdoc") { - # Write out doc/copy.but, which defines a macro used in the manual - # preamble blurb. - - $out .= "\\# Generated by @{[basename __FILE__]} from $infile.\n"; - $out .= "\\# You should edit those files rather than editing this one.\n\n"; - - $out .= sprintf "\\define{shortcopyrightdetails} %s\n\n", - &halibutescape($shortdetails); -} - -my $outfile; -my $opened = (defined $output) ? - (open $outfile, ">", $output) : (open $outfile, ">-"); -$opened or die "$output: open: $!\n"; -print $outfile $out; -close $outfile; - -sub stringlit { - my ($lit) = @_; - $lit =~ s!\\!\\\\!g; - $lit =~ s!"!\\"!g; - return $lit; -} - -sub halibutescape { - my ($text) = @_; - $text =~ s![\\{}]!\\$&!g; # Halibut escaping - $text =~ s!"([^"]*)"!\\q{$1}!g; # convert quoted strings to \q{} - return $text; -} - -sub write { - my ($filename, $newcontents) = @_; - if (open my $fh, "<", $filename) { - my $oldcontents = ""; - $oldcontents .= $_ while <$fh>; - close $fh; - return if $oldcontents eq $newcontents; - } - open my $fh, ">", $filename or die "$filename: open: $!\n"; - print $fh $newcontents; - close $fh; -} diff --git a/logging.c b/logging.c deleted file mode 100644 index e065f1a49..000000000 --- a/logging.c +++ /dev/null @@ -1,521 +0,0 @@ -/* - * 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; - LogPolicy *lp; - 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(LogContext *ctx, ptrlen data) -{ - /* - * 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.ptr, data.len); - } else if (ctx->state == L_OPEN) { - assert(ctx->lgfp); - if (fwrite(data.ptr, 1, data.len, ctx->lgfp) < data.len) { - logfclose(ctx); - ctx->state = L_ERROR; - lp_eventlog(ctx->lp, "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 PRINTF_LIKE(2, 3) void logprintf(LogContext *ctx, const char *fmt, ...) -{ - va_list ap; - char *data; - - va_start(ap, fmt); - data = dupvprintf(fmt, ap); - va_end(ap); - - logwrite(ctx, ptrlen_from_asciz(data)); - sfree(data); -} - -/* - * Flush any open log file. - */ -void logflush(LogContext *ctx) -{ - if (ctx->logtype > 0) - if (ctx->state == L_OPEN) - fflush(ctx->lgfp); -} - -LogPolicy *log_get_policy(LogContext *ctx) -{ - return ctx->lp; -} - -static void logfopen_callback(void *vctx, int mode) -{ - LogContext *ctx = (LogContext *)vctx; - char buf[256], *event; - struct tm tm; - const char *fmode; - bool 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 && conf_get_bool(ctx->conf, CONF_logheader)) { - /* 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)); - lp_eventlog(ctx->lp, 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. - */ - lp_logging_error(ctx->lp, event); - } - 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)) { - ptrlen data = bufchain_prefix(&ctx->queue); - logwrite(ctx, data); - bufchain_consume(&ctx->queue, data.len); - } - logflush(ctx); -} - -/* - * 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(LogContext *ctx) -{ - struct tm tm; - 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); - - if (open_for_write_would_lose_data(ctx->currlogfilename)) { - int logxfovr = conf_get_int(ctx->conf, CONF_logxfovr); - if (logxfovr != LGXF_ASK) { - mode = ((logxfovr == LGXF_OVR) ? 2 : 1); - } else - mode = lp_askappend(ctx->lp, 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(LogContext *ctx) -{ - if (ctx->lgfp) { - fclose(ctx->lgfp); - ctx->lgfp = NULL; - } - ctx->state = L_CLOSED; -} - -/* - * Log session traffic. - */ -void logtraffic(LogContext *ctx, unsigned char c, int logmode) -{ - if (ctx->logtype > 0) { - if (ctx->logtype == logmode) - logwrite(ctx, make_ptrlen(&c, 1)); - } -} - -static void logevent_internal(LogContext *ctx, const char *event) -{ - if (ctx->logtype == LGTYP_PACKETS || ctx->logtype == LGTYP_SSHRAW) { - logprintf(ctx, "Event Log: %s\r\n", event); - logflush(ctx); - } - lp_eventlog(ctx->lp, event); -} - -void logevent(LogContext *ctx, const char *event) -{ - if (!ctx) - return; - - /* - * Replace newlines in Event Log messages with spaces. (Sometimes - * the same message string is reused for the Event Log and a GUI - * dialog box; newlines are sometimes appropriate in the latter, - * but never in the former.) - */ - if (strchr(event, '\n') || strchr(event, '\r')) { - char *dup = dupstr(event); - char *p = dup, *q = dup; - while (*p) { - if (*p == '\r' || *p == '\n') { - do { - p++; - } while (*p == '\r' || *p == '\n'); - *q++ = ' '; - } else { - *q++ = *p++; - } - } - *q = '\0'; - logevent_internal(ctx, dup); - sfree(dup); - } else { - logevent_internal(ctx, event); - } -} - -void logevent_and_free(LogContext *ctx, char *event) -{ - logevent(ctx, event); - sfree(event); -} - -void logeventvf(LogContext *ctx, const char *fmt, va_list ap) -{ - logevent_and_free(ctx, dupvprintf(fmt, ap)); -} - -void logeventf(LogContext *ctx, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - logeventvf(ctx, fmt, ap); - va_end(ap); -} - -/* - * 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(LogContext *ctx, int direction, int type, - const char *texttype, const void *data, size_t len, - int n_blanks, const struct logblank_t *blanks, - const unsigned long *seq, - unsigned downstream_id, const char *additional_log_text) -{ - char dumpdata[128], smalldata[5]; - size_t 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, " (%"SIZEu" 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, " %08"SIZEx"%*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 = ((const 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)] = (c >= 0x20 && c < 0x7F ? 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, ptrlen_from_asciz(dumpdata)); - output_pos = 0; - } - } - - } - - /* Tidy up */ - if (omitted) - logprintf(ctx, " (%"SIZEu" byte%s omitted)\r\n", - omitted, (omitted==1?"":"s")); - logflush(ctx); -} - -LogContext *log_init(LogPolicy *lp, Conf *conf) -{ - LogContext *ctx = snew(LogContext); - ctx->lgfp = NULL; - ctx->state = L_CLOSED; - ctx->lp = lp; - 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(LogContext *ctx) -{ - logfclose(ctx); - bufchain_clear(&ctx->queue); - if (ctx->currlogfilename) - filename_free(ctx->currlogfilename); - conf_free(ctx->conf); - sfree(ctx); -} - -void log_reconfig(LogContext *ctx, Conf *conf) -{ - bool 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; - strbuf *buffer; - const char *s; - Filename *ret; - - buffer = strbuf_new(); - s = filename_to_str(src); - - while (*s) { - bool 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; - } - while (size-- > 0) { - char c = *bufp++; - if (sanitise) - c = filename_char_sanitise(c); - put_byte(buffer, c); - } - } - - ret = filename_from_str(buffer->s); - strbuf_free(buffer); - return ret; -} diff --git a/WINDOWS/make22.cmd b/make22.cmd similarity index 97% rename from WINDOWS/make22.cmd rename to make22.cmd index 9053912ee..0c46e87d4 100644 --- a/WINDOWS/make22.cmd +++ b/make22.cmd @@ -2,7 +2,8 @@ echo. REM CD to the directory above the script location - which should be the main src path -cd "%~dp0".. +rem cd "%~dp0".. +cd putty echo Starting if exist "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\VsDevCmd.bat" ( diff --git a/marshal.h b/marshal.h deleted file mode 100644 index b9136292e..000000000 --- a/marshal.h +++ /dev/null @@ -1,359 +0,0 @@ -#ifndef PUTTY_MARSHAL_H -#define PUTTY_MARSHAL_H - -#include "defs.h" - -#include -#include - -/* - * A sort of 'abstract base class' or 'interface' or 'trait' which is - * the common feature of all types that want to accept data formatted - * using the SSH binary conventions of uint32, string, mpint etc. - */ -struct BinarySink { - void (*write)(BinarySink *sink, const void *data, size_t len); - void (*writefmtv)(BinarySink *sink, const char *fmt, va_list ap); - BinarySink *binarysink_; -}; - -/* - * To define a structure type as a valid target for binary formatted - * data, put 'BinarySink_IMPLEMENTATION' in its declaration, and when - * an instance is set up, use 'BinarySink_INIT' to initialise the - * 'base class' state, providing a function pointer to be the - * implementation of the write() call above. - */ -#define BinarySink_IMPLEMENTATION BinarySink binarysink_[1] -#define BinarySink_INIT(obj, writefn) \ - ((obj)->binarysink_->write = (writefn), \ - (obj)->binarysink_->writefmtv = NULL, \ - (obj)->binarysink_->binarysink_ = (obj)->binarysink_) - -/* - * To define a larger structure type as a valid BinarySink in such a - * way that it will delegate the write method to some other object, - * put 'BinarySink_DELEGATE_IMPLEMENTATION' in its declaration, and - * when an instance is set up, use 'BinarySink_DELEGATE_INIT' to point - * at the object it wants to delegate to. - * - * In such a delegated structure, you might sometimes want to have the - * delegation stop being valid (e.g. it might be delegating to an - * object that only sometimes exists). You can null out the delegate - * pointer using BinarySink_DELEGATE_CLEAR. - */ -#define BinarySink_DELEGATE_IMPLEMENTATION BinarySink *binarysink_ -#define BinarySink_DELEGATE_INIT(obj, othersink) \ - ((obj)->binarysink_ = BinarySink_UPCAST(othersink)) -#define BinarySink_DELEGATE_CLEAR(obj) ((obj)->binarysink_ = NULL) - -/* - * The implementing type's write function will want to downcast its - * 'BinarySink *' parameter back to the more specific type. Also, - * sometimes you'll want to upcast a pointer to a particular - * implementing type into an abstract 'BinarySink *' to pass to - * generic subroutines not defined in this file. These macros do that - * job. - * - * Importantly, BinarySink_UPCAST can also be applied to a BinarySink - * * itself (and leaves it unchanged). That's achieved by a small - * piece of C trickery: implementing structures and the BinarySink - * structure itself both contain a field called binarysink_, but in - * implementing objects it's a BinarySink[1] whereas in the abstract - * type it's a 'BinarySink *' pointing back to the same structure, - * meaning that you can say 'foo->binarysink_' in either case and get - * a pointer type by different methods. - */ -#define BinarySink_DOWNCAST(object, type) \ - TYPECHECK((object) == ((type *)0)->binarysink_, \ - ((type *)(((char *)(object)) - offsetof(type, binarysink_)))) -#define BinarySink_UPCAST(object) \ - TYPECHECK((object)->binarysink_ == (BinarySink *)0, \ - (object)->binarysink_) - -/* - * If you structure-copy an object that's implementing BinarySink, - * then that tricky self-pointer in its trait subobject will point to - * the wrong place. You could call BinarySink_INIT again, but this - * macro is terser and does all that's needed to fix up the copied - * object. - */ -#define BinarySink_COPIED(obj) \ - ((obj)->binarysink_->binarysink_ = (obj)->binarysink_) - -/* - * The put_* macros are the main client to this system. Any structure - * which implements the BinarySink 'trait' is valid for use as the - * first parameter of any of these put_* macros. - */ - -/* Basic big-endian integer types. */ -#define put_byte(bs, val) \ - BinarySink_put_byte(BinarySink_UPCAST(bs), val) -#define put_uint16(bs, val) \ - BinarySink_put_uint16(BinarySink_UPCAST(bs), val) -#define put_uint32(bs, val) \ - BinarySink_put_uint32(BinarySink_UPCAST(bs), val) -#define put_uint64(bs, val) \ - BinarySink_put_uint64(BinarySink_UPCAST(bs), val) - -/* SSH booleans, encoded as a single byte storing either 0 or 1. */ -#define put_bool(bs, val) \ - BinarySink_put_bool(BinarySink_UPCAST(bs), val) - -/* SSH strings, with a leading uint32 length field. 'stringz' is a - * convenience function that takes an ordinary C zero-terminated - * string as input. 'stringsb' takes a strbuf * as input, and - * finalises it as a side effect (handy for multi-level marshalling in - * which you use these same functions to format an inner blob of data - * that then gets wrapped into a string container in an outer one). */ -#define put_string(bs, val, len) \ - BinarySink_put_string(BinarySink_UPCAST(bs),val,len) -#define put_stringpl(bs, ptrlen) \ - BinarySink_put_stringpl(BinarySink_UPCAST(bs),ptrlen) -#define put_stringz(bs, val) \ - BinarySink_put_stringz(BinarySink_UPCAST(bs), val) -#define put_stringsb(bs, val) \ - BinarySink_put_stringsb(BinarySink_UPCAST(bs), val) - -/* Other string outputs: 'asciz' emits the string data directly into - * the output including the terminating \0, and 'pstring' emits the - * string in Pascal style with a leading _one_-byte length field. - * pstring can fail if the string is too long. */ -#define put_asciz(bs, val) \ - BinarySink_put_asciz(BinarySink_UPCAST(bs), val) -#define put_pstring(bs, val) \ - BinarySink_put_pstring(BinarySink_UPCAST(bs), val) - -/* Multiprecision integers, in both the SSH-1 and SSH-2 formats. */ -#define put_mp_ssh1(bs, val) \ - BinarySink_put_mp_ssh1(BinarySink_UPCAST(bs), val) -#define put_mp_ssh2(bs, val) \ - BinarySink_put_mp_ssh2(BinarySink_UPCAST(bs), val) - -/* Padding with a specified byte. */ -#define put_padding(bs, len, padbyte) \ - BinarySink_put_padding(BinarySink_UPCAST(bs), len, padbyte) - -/* Fallback: just emit raw data bytes, using a syntax that matches the - * rest of these macros. */ -#define put_data(bs, val, len) \ - BinarySink_put_data(BinarySink_UPCAST(bs), val, len) -#define put_datapl(bs, pl) \ - BinarySink_put_datapl(BinarySink_UPCAST(bs), pl) -#define put_dataz(bs, val) \ - BinarySink_put_datapl(BinarySink_UPCAST(bs), ptrlen_from_asciz(val)) -#define put_datalit(bs, val) \ - BinarySink_put_datapl(BinarySink_UPCAST(bs), PTRLEN_LITERAL(val)) - -/* Emit printf-formatted data, with no terminator. */ -#define put_fmt(bs, ...) \ - BinarySink_put_fmt(BinarySink_UPCAST(bs), __VA_ARGS__) -#define put_fmtv(bs, fmt, ap) \ - BinarySink_put_fmtv(BinarySink_UPCAST(bs), fmt, ap) - -/* More complicated function implemented in write_c_string_literal.c */ -#define put_c_string_literal(bs, str) \ - BinarySink_put_c_string_literal(BinarySink_UPCAST(bs), str) - -/* - * The underlying real C functions that implement most of those - * macros. Generally you won't want to call these directly, because - * they have such cumbersome names; you call the wrapper macros above - * instead. - * - * A few functions whose wrapper macros are defined above are actually - * declared in other headers, so as to guarantee that the - * declaration(s) of their other parameter type(s) are in scope. - */ -void BinarySink_put_data(BinarySink *, const void *data, size_t len); -void BinarySink_put_datapl(BinarySink *, ptrlen); -void BinarySink_put_padding(BinarySink *, size_t len, unsigned char padbyte); -void BinarySink_put_byte(BinarySink *, unsigned char); -void BinarySink_put_bool(BinarySink *, bool); -void BinarySink_put_uint16(BinarySink *, unsigned long); -void BinarySink_put_uint32(BinarySink *, unsigned long); -void BinarySink_put_uint64(BinarySink *, uint64_t); -void BinarySink_put_string(BinarySink *, const void *data, size_t len); -void BinarySink_put_stringpl(BinarySink *, ptrlen); -void BinarySink_put_stringz(BinarySink *, const char *str); -void BinarySink_put_stringsb(BinarySink *, strbuf *); -void BinarySink_put_asciz(BinarySink *, const char *str); -bool BinarySink_put_pstring(BinarySink *, const char *str); -void BinarySink_put_mp_ssh1(BinarySink *bs, mp_int *x); -void BinarySink_put_mp_ssh2(BinarySink *bs, mp_int *x); -void BinarySink_put_fmt(BinarySink *, const char *fmt, ...) PRINTF_LIKE(2, 3); -void BinarySink_put_fmtv(BinarySink *, const char *fmt, va_list ap); -void BinarySink_put_c_string_literal(BinarySink *, ptrlen); - -/* ---------------------------------------------------------------------- */ - -/* - * A complementary trait structure for _un_-marshalling. - * - * This structure contains client-visible data fields rather than - * methods, because that seemed more useful than leaving it totally - * opaque. But it's still got the self-pointer system that will allow - * the set of get_* macros to target one of these itself or any other - * type that 'derives' from it. So, for example, an SSH packet - * structure can act as a BinarySource while also having additional - * fields like the packet type. - */ -typedef enum BinarySourceError { - BSE_NO_ERROR, - BSE_OUT_OF_DATA, - BSE_INVALID -} BinarySourceError; -struct BinarySource { - /* - * (data, len) is the data block being decoded. pos is the current - * position within the block. - */ - const void *data; - size_t pos, len; - - /* - * 'err' indicates whether a decoding error has happened at any - * point. Once this has been set to something other than - * BSE_NO_ERROR, it shouldn't be changed by any unmarshalling - * function. So you can safely do a long sequence of get_foo() - * operations and then test err just once at the end, rather than - * having to conditionalise every single get. - * - * The unmarshalling functions should always return some value, - * even if a decoding error occurs. Generally on error they'll - * return zero (if numeric) or the empty string (if string-based), - * or some other appropriate default value for more complicated - * types. - * - * If the usual return value is dynamically allocated (e.g. a - * bignum, or a normal C 'char *' string), then the error value is - * also dynamic in the same way. So you have to free exactly the - * same set of things whether or not there was a decoding error, - * which simplifies exit paths - for example, you could call a big - * pile of get_foo functions, then put the actual handling of the - * results under 'if (!get_err(src))', and then free everything - * outside that if. - */ - BinarySourceError err; - - /* - * Self-pointer for the implicit derivation trick, same as - * BinarySink above. - */ - BinarySource *binarysource_; -}; - -/* - * Implementation macros, similar to BinarySink. - */ -#define BinarySource_IMPLEMENTATION BinarySource binarysource_[1] -static inline void BinarySource_INIT__(BinarySource *src, ptrlen data) -{ - src->data = data.ptr; - src->len = data.len; - src->pos = 0; - src->err = BSE_NO_ERROR; - src->binarysource_ = src; -} -#define BinarySource_BARE_INIT_PL(obj, pl) \ - TYPECHECK(&(obj)->binarysource_ == (BinarySource **)0, \ - BinarySource_INIT__(obj, pl)) -#define BinarySource_BARE_INIT(obj, data_, len_) \ - BinarySource_BARE_INIT_PL(obj, make_ptrlen(data_, len_)) -#define BinarySource_INIT_PL(obj, pl) \ - TYPECHECK(&(obj)->binarysource_ == (BinarySource (*)[1])0, \ - BinarySource_INIT__(BinarySource_UPCAST(obj), pl)) -#define BinarySource_INIT(obj, data_, len_) \ - BinarySource_INIT_PL(obj, make_ptrlen(data_, len_)) -#define BinarySource_DOWNCAST(object, type) \ - TYPECHECK((object) == ((type *)0)->binarysource_, \ - ((type *)(((char *)(object)) - offsetof(type, binarysource_)))) -#define BinarySource_UPCAST(object) \ - TYPECHECK((object)->binarysource_ == (BinarySource *)0, \ - (object)->binarysource_) -#define BinarySource_COPIED(obj) \ - ((obj)->binarysource_->binarysource_ = (obj)->binarysource_) -#define BinarySource_REWIND_TO(src, pos) \ - BinarySource_REWIND_TO__((src)->binarysource_, pos) -#define BinarySource_REWIND(src) \ - BinarySource_REWIND_TO__((src)->binarysource_, 0) - -#define get_data(src, len) \ - BinarySource_get_data(BinarySource_UPCAST(src), len) -#define get_byte(src) \ - BinarySource_get_byte(BinarySource_UPCAST(src)) -#define get_bool(src) \ - BinarySource_get_bool(BinarySource_UPCAST(src)) -#define get_uint16(src) \ - BinarySource_get_uint16(BinarySource_UPCAST(src)) -#define get_uint32(src) \ - BinarySource_get_uint32(BinarySource_UPCAST(src)) -#define get_uint64(src) \ - BinarySource_get_uint64(BinarySource_UPCAST(src)) -#define get_string(src) \ - BinarySource_get_string(BinarySource_UPCAST(src)) -#define get_asciz(src) \ - BinarySource_get_asciz(BinarySource_UPCAST(src)) -#define get_chars(src, include) \ - BinarySource_get_chars(BinarySource_UPCAST(src), include) -#define get_nonchars(src, exclude) \ - BinarySource_get_nonchars(BinarySource_UPCAST(src), exclude) -#define get_chomped_line(src) \ - BinarySource_get_chomped_line(BinarySource_UPCAST(src)) -#define get_pstring(src) \ - BinarySource_get_pstring(BinarySource_UPCAST(src)) -#define get_mp_ssh1(src) \ - BinarySource_get_mp_ssh1(BinarySource_UPCAST(src)) -#define get_mp_ssh2(src) \ - BinarySource_get_mp_ssh2(BinarySource_UPCAST(src)) -#define get_rsa_ssh1_pub(src, rsa, order) \ - BinarySource_get_rsa_ssh1_pub(BinarySource_UPCAST(src), rsa, order) -#define get_rsa_ssh1_priv(src, rsa) \ - BinarySource_get_rsa_ssh1_priv(BinarySource_UPCAST(src), rsa) -#define get_rsa_ssh1_priv_agent(src) \ - BinarySource_get_rsa_ssh1_priv_agent(BinarySource_UPCAST(src)) - -#define get_err(src) (BinarySource_UPCAST(src)->err) -#define get_avail(src) (BinarySource_UPCAST(src)->len - \ - BinarySource_UPCAST(src)->pos) -#define get_ptr(src) \ - ((const void *)( \ - (const unsigned char *)(BinarySource_UPCAST(src)->data) + \ - BinarySource_UPCAST(src)->pos)) - -ptrlen BinarySource_get_data(BinarySource *, size_t); -unsigned char BinarySource_get_byte(BinarySource *); -bool BinarySource_get_bool(BinarySource *); -unsigned BinarySource_get_uint16(BinarySource *); -unsigned long BinarySource_get_uint32(BinarySource *); -uint64_t BinarySource_get_uint64(BinarySource *); -ptrlen BinarySource_get_string(BinarySource *); -const char *BinarySource_get_asciz(BinarySource *); -ptrlen BinarySource_get_chars(BinarySource *, const char *include_set); -ptrlen BinarySource_get_nonchars(BinarySource *, const char *exclude_set); -ptrlen BinarySource_get_chomped_line(BinarySource *); -ptrlen BinarySource_get_pstring(BinarySource *); -mp_int *BinarySource_get_mp_ssh1(BinarySource *src); -mp_int *BinarySource_get_mp_ssh2(BinarySource *src); - -void BinarySource_REWIND_TO__(BinarySource *src, size_t pos); - -/* - * A couple of useful standard BinarySink implementations, which live - * as sensibly here as anywhere else: one that makes a BinarySink - * whose effect is to write to a stdio stream, and one whose effect is - * to append to a bufchain. - */ -struct stdio_sink { - FILE *fp; - BinarySink_IMPLEMENTATION; -}; -struct bufchain_sink { - bufchain *ch; - BinarySink_IMPLEMENTATION; -}; -void stdio_sink_init(stdio_sink *sink, FILE *fp); -void bufchain_sink_init(bufchain_sink *sink, bufchain *ch); - -#endif /* PUTTY_MARSHAL_H */ diff --git a/misc.h b/misc.h deleted file mode 100644 index 1b3d324ad..000000000 --- a/misc.h +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Header for miscellaneous helper functions, mostly defined in the - * utils subdirectory. - */ - -#ifndef PUTTY_MISC_H -#define PUTTY_MISC_H - -#include "defs.h" -#include "puttymem.h" -#include "marshal.h" - -#include /* for FILE * */ -#include /* for va_list */ -#include /* for abort */ -#include /* for struct tm */ -#include /* for INT_MAX/MIN */ -#include /* for assert (obviously) */ - -unsigned long 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_fn(const char *s1, ...); -#define dupcat(...) dupcat_fn(__VA_ARGS__, (const char *)NULL) -char *dupprintf(const char *fmt, ...) PRINTF_LIKE(1, 2); -char *dupvprintf(const char *fmt, va_list ap); -void burnstr(char *string); - -/* - * The visible part of a strbuf structure. There's a surrounding - * implementation struct in strbuf.c, which isn't exposed to client - * code. - */ -struct strbuf { - char *s; - unsigned char *u; - size_t len; - BinarySink_IMPLEMENTATION; -}; - -/* strbuf constructors: strbuf_new_nm and strbuf_new differ in that a - * strbuf constructed using the _nm version will resize itself by - * alloc/copy/smemclr/free instead of realloc. Use that version for - * data sensitive enough that it's worth costing performance to - * avoid copies of it lingering in process memory. */ -strbuf *strbuf_new(void); -strbuf *strbuf_new_nm(void); - -/* Helpers to allocate a strbuf containing an existing string */ -strbuf *strbuf_dup(ptrlen string); -strbuf *strbuf_dup_nm(ptrlen string); - -void strbuf_free(strbuf *buf); -void *strbuf_append(strbuf *buf, size_t len); -void strbuf_shrink_to(strbuf *buf, size_t new_len); -void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove); -char *strbuf_to_str(strbuf *buf); /* does free buf, but you must free result */ -static inline void strbuf_clear(strbuf *buf) { strbuf_shrink_to(buf, 0); } -bool strbuf_chomp(strbuf *buf, char char_to_remove); - -strbuf *strbuf_new_for_agent_query(void); -void strbuf_finalise_agent_query(strbuf *buf); - -/* String-to-Unicode converters that auto-allocate the destination and - * work around the rather deficient interface of mb_to_wc. */ -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); -char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, int len, - const char *defchr); -char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string, - const char *defchr); - -static inline 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', and since it's - * also declared inline, elide it completely in their output. - */ - 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 */ -} - -char *fgetline(FILE *fp); -bool read_file_into(BinarySink *bs, FILE *fp); -char *chomp(char *str); -bool strstartswith(const char *s, const char *t); -bool strendswith(const char *s, const char *t); - -void base64_encode_atom(const unsigned char *data, int n, char *out); -int base64_decode_atom(const char *atom, unsigned char *out); -void base64_decode_bs(BinarySink *bs, ptrlen data); -void base64_decode_fp(FILE *fp, ptrlen data); -strbuf *base64_decode_sb(ptrlen data); -void base64_encode_bs(BinarySink *bs, ptrlen data, int cpl); -void base64_encode_fp(FILE *fp, ptrlen data, int cpl); -strbuf *base64_encode_sb(ptrlen data, int cpl); -bool base64_valid(ptrlen data); - -void percent_encode_bs(BinarySink *bs, ptrlen data, const char *badchars); -void percent_encode_fp(FILE *fp, ptrlen data, const char *badchars); -strbuf *percent_encode_sb(ptrlen data, const char *badchars); -void percent_decode_bs(BinarySink *bs, ptrlen data); -void percent_decode_fp(FILE *fp, ptrlen data); -strbuf *percent_decode_sb(ptrlen data); - -struct bufchain_granule; -struct bufchain_tag { - struct bufchain_granule *head, *tail; - size_t buffersize; /* current amount of buffered data */ - - void (*queue_idempotent_callback)(IdempotentCallback *ic); - IdempotentCallback *ic; -}; - -void bufchain_init(bufchain *ch); -void bufchain_clear(bufchain *ch); -size_t bufchain_size(bufchain *ch); -void bufchain_add(bufchain *ch, const void *data, size_t len); -ptrlen bufchain_prefix(bufchain *ch); -void bufchain_consume(bufchain *ch, size_t len); -void bufchain_fetch(bufchain *ch, void *data, size_t len); -void bufchain_fetch_consume(bufchain *ch, void *data, size_t len); -bool bufchain_try_consume(bufchain *ch, size_t len); -bool bufchain_try_fetch(bufchain *ch, void *data, size_t len); -bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len); -size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len); -void bufchain_set_callback_inner( - bufchain *ch, IdempotentCallback *ic, - void (*queue_idempotent_callback)(IdempotentCallback *ic)); -static inline void bufchain_set_callback(bufchain *ch, IdempotentCallback *ic) -{ - extern void queue_idempotent_callback(struct IdempotentCallback *ic); - /* Wrapper that puts in the standard queue_idempotent_callback - * function. Lives here rather than in bufchain.c so that - * standalone programs can use the bufchain facility without this - * optional callback feature and not need to provide a stub of - * queue_idempotent_callback. */ - bufchain_set_callback_inner(ch, ic, queue_idempotent_callback); -} - -bool validate_manual_hostkey(char *key); - -struct tm ltime(void); - -/* - * Special form of strcmp which can cope with NULL inputs. NULL is - * defined to sort before even the empty string. - */ -int nullstrcmp(const char *a, const char *b); - -static inline ptrlen make_ptrlen(const void *ptr, size_t len) -{ - ptrlen pl; - pl.ptr = ptr; - pl.len = len; - return pl; -} - -static inline const void *ptrlen_end(ptrlen pl) -{ - return (const char *)pl.ptr + pl.len; -} - -static inline ptrlen make_ptrlen_startend(const void *startv, const void *endv) -{ - const char *start = (const char *)startv, *end = (const char *)endv; - assert(end >= start); - ptrlen pl; - pl.ptr = start; - pl.len = end - start; - return pl; -} - -static inline ptrlen ptrlen_from_asciz(const char *str) -{ - return make_ptrlen(str, strlen(str)); -} - -static inline ptrlen ptrlen_from_strbuf(strbuf *sb) -{ - return make_ptrlen(sb->u, sb->len); -} - -bool ptrlen_eq_string(ptrlen pl, const char *str); -bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2); -int ptrlen_strcmp(ptrlen pl1, ptrlen pl2); -/* ptrlen_startswith and ptrlen_endswith write through their 'tail' - * argument if and only if it is non-NULL and they return true. Hence - * you can write ptrlen_startswith(thing, prefix, &thing), writing - * back to the same ptrlen it read from, to remove a prefix if present - * and say whether it did so. */ -bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail); -bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail); -ptrlen ptrlen_get_word(ptrlen *input, const char *separators); -bool ptrlen_contains(ptrlen input, const char *characters); -bool ptrlen_contains_only(ptrlen input, const char *characters); -char *mkstr(ptrlen pl); -int string_length_for_printf(size_t); -/* Derive two printf arguments from a ptrlen, suitable for "%.*s" */ -#define PTRLEN_PRINTF(pl) \ - string_length_for_printf((pl).len), (const char *)(pl).ptr -/* Make a ptrlen out of a compile-time string literal. We try to - * enforce that it _is_ a string literal by token-pasting "" on to it, - * which should provoke a compile error if it's any other kind of - * string. */ -#define PTRLEN_LITERAL(stringlit) \ - TYPECHECK("" stringlit "", make_ptrlen(stringlit, sizeof(stringlit)-1)) -/* Make a ptrlen out of a compile-time string literal in a way that - * allows you to declare the ptrlen itself as a compile-time initialiser. */ -#define PTRLEN_DECL_LITERAL(stringlit) \ - { TYPECHECK("" stringlit "", stringlit), sizeof(stringlit)-1 } -/* Make a ptrlen out of a constant byte array. */ -#define PTRLEN_FROM_CONST_BYTES(a) make_ptrlen(a, sizeof(a)) - -void wordwrap(BinarySink *bs, ptrlen input, size_t maxwid); - -/* 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. */ -unsigned smemeq(const void *av, const void *bv, size_t len); - -/* Encode a single UTF-8 character. Assumes that illegal characters - * (such as things in the surrogate range, or > 0x10FFFF) have already - * been removed. */ -size_t encode_utf8(void *output, unsigned long ch); - -/* Encode a wide-character string into UTF-8. Tolerates surrogates if - * sizeof(wchar_t) == 2, assuming that in that case the wide string is - * encoded in UTF-16. */ -char *encode_wide_string_as_utf8(const wchar_t *wstr); - -/* Decode a single UTF-8 character. Returns U+FFFD for any of the - * illegal cases. */ -unsigned long decode_utf8(const char **utf8); - -/* Decode a single UTF-8 character to an output buffer of the - * platform's wchar_t. May write a pair of surrogates if - * sizeof(wchar_t) == 2, assuming that in that case the wide string is - * encoded in UTF-16. Otherwise, writes one character. Returns the - * number written. */ -size_t decode_utf8_to_wchar(const char **utf8, wchar_t *out); - -/* Write a string out in C string-literal format. */ -void write_c_string_literal(FILE *fp, ptrlen str); - -char *buildinfo(const char *newline); - -/* - * A function you can put at points in the code where execution should - * never reach in the first place. Better than assert(false), or even - * assert(false && "some explanatory message"), because some compilers - * don't interpret assert(false) as a declaration of unreachability, - * so they may still warn about pointless things like some variable - * not being initialised on the unreachable code path. - * - * I follow the assertion with a call to abort() just in case someone - * compiles with -DNDEBUG, and I wrap that abort inside my own - * function labelled NORETURN just in case some unusual kind of system - * header wasn't foresighted enough to label abort() itself that way. - */ -static inline NORETURN void unreachable_internal(void) { abort(); } -#define unreachable(msg) (assert(false && msg), unreachable_internal()) - -/* - * Debugging functions. - * - * Output goes to debug.log - * - * debug() 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(const char *fmt, ...) PRINTF_LIKE(1, 2); -void debug_memdump(const void *buf, int len, bool L); -#define debug(...) (debug_printf(__VA_ARGS__)) -#define dmemdump(buf,len) (debug_memdump(buf, len, false)) -#define dmemdumpl(buf,len) (debug_memdump(buf, len, true)) -#else -#define debug(...) ((void)0) -#define dmemdump(buf,len) ((void)0) -#define dmemdumpl(buf,len) ((void)0) -#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 - -static inline uint64_t GET_64BIT_LSB_FIRST(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - return (((uint64_t)p[0] ) | ((uint64_t)p[1] << 8) | - ((uint64_t)p[2] << 16) | ((uint64_t)p[3] << 24) | - ((uint64_t)p[4] << 32) | ((uint64_t)p[5] << 40) | - ((uint64_t)p[6] << 48) | ((uint64_t)p[7] << 56)); -} - -static inline void PUT_64BIT_LSB_FIRST(void *vp, uint64_t value) -{ - uint8_t *p = (uint8_t *)vp; - p[0] = (uint8_t)(value); - p[1] = (uint8_t)(value >> 8); - p[2] = (uint8_t)(value >> 16); - p[3] = (uint8_t)(value >> 24); - p[4] = (uint8_t)(value >> 32); - p[5] = (uint8_t)(value >> 40); - p[6] = (uint8_t)(value >> 48); - p[7] = (uint8_t)(value >> 56); -} - -static inline uint32_t GET_32BIT_LSB_FIRST(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - return (((uint32_t)p[0] ) | ((uint32_t)p[1] << 8) | - ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24)); -} - -static inline void PUT_32BIT_LSB_FIRST(void *vp, uint32_t value) -{ - uint8_t *p = (uint8_t *)vp; - p[0] = (uint8_t)(value); - p[1] = (uint8_t)(value >> 8); - p[2] = (uint8_t)(value >> 16); - p[3] = (uint8_t)(value >> 24); -} - -static inline uint16_t GET_16BIT_LSB_FIRST(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - return (((uint16_t)p[0] ) | ((uint16_t)p[1] << 8)); -} - -static inline void PUT_16BIT_LSB_FIRST(void *vp, uint16_t value) -{ - uint8_t *p = (uint8_t *)vp; - p[0] = (uint8_t)(value); - p[1] = (uint8_t)(value >> 8); -} - -static inline uint64_t GET_64BIT_MSB_FIRST(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - return (((uint64_t)p[7] ) | ((uint64_t)p[6] << 8) | - ((uint64_t)p[5] << 16) | ((uint64_t)p[4] << 24) | - ((uint64_t)p[3] << 32) | ((uint64_t)p[2] << 40) | - ((uint64_t)p[1] << 48) | ((uint64_t)p[0] << 56)); -} - -static inline void PUT_64BIT_MSB_FIRST(void *vp, uint64_t value) -{ - uint8_t *p = (uint8_t *)vp; - p[7] = (uint8_t)(value); - p[6] = (uint8_t)(value >> 8); - p[5] = (uint8_t)(value >> 16); - p[4] = (uint8_t)(value >> 24); - p[3] = (uint8_t)(value >> 32); - p[2] = (uint8_t)(value >> 40); - p[1] = (uint8_t)(value >> 48); - p[0] = (uint8_t)(value >> 56); -} - -static inline uint32_t GET_32BIT_MSB_FIRST(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - return (((uint32_t)p[3] ) | ((uint32_t)p[2] << 8) | - ((uint32_t)p[1] << 16) | ((uint32_t)p[0] << 24)); -} - -static inline void PUT_32BIT_MSB_FIRST(void *vp, uint32_t value) -{ - uint8_t *p = (uint8_t *)vp; - p[3] = (uint8_t)(value); - p[2] = (uint8_t)(value >> 8); - p[1] = (uint8_t)(value >> 16); - p[0] = (uint8_t)(value >> 24); -} - -static inline uint16_t GET_16BIT_MSB_FIRST(const void *vp) -{ - const uint8_t *p = (const uint8_t *)vp; - return (((uint16_t)p[1] ) | ((uint16_t)p[0] << 8)); -} - -static inline void PUT_16BIT_MSB_FIRST(void *vp, uint16_t value) -{ - uint8_t *p = (uint8_t *)vp; - p[1] = (uint8_t)(value); - p[0] = (uint8_t)(value >> 8); -} - -/* For use in X11-related applications, an endianness-variable form of - * {GET,PUT}_16BIT which expects 'endian' to be either 'B' or 'l' */ - -static inline uint16_t GET_16BIT_X11(char endian, const void *p) -{ - return endian == 'B' ? GET_16BIT_MSB_FIRST(p) : GET_16BIT_LSB_FIRST(p); -} - -static inline void PUT_16BIT_X11(char endian, void *p, uint16_t value) -{ - if (endian == 'B') - PUT_16BIT_MSB_FIRST(p, value); - else - PUT_16BIT_LSB_FIRST(p, 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)) */ -static inline const char *NULLTOEMPTY(const char *s) -{ - return s ? s : ""; -} - -/* StripCtrlChars, defined in stripctrl.c: an adapter you can put on - * the front of one BinarySink and which functions as one in turn. - * Interprets its input as a stream of multibyte characters in the - * system locale, and removes any that are not either printable - * characters or newlines. */ -struct StripCtrlChars { - BinarySink_IMPLEMENTATION; - /* and this is contained in a larger structure */ -}; -StripCtrlChars *stripctrl_new( - BinarySink *bs_out, bool permit_cr, wchar_t substitution); -StripCtrlChars *stripctrl_new_term_fn( - BinarySink *bs_out, bool permit_cr, wchar_t substitution, - Terminal *term, unsigned long (*translate)( - Terminal *, term_utf8_decode *, unsigned char)); -#define stripctrl_new_term(bs, cr, sub, term) \ - stripctrl_new_term_fn(bs, cr, sub, term, term_translate) -void stripctrl_retarget(StripCtrlChars *sccpub, BinarySink *new_bs_out); -void stripctrl_reset(StripCtrlChars *sccpub); -void stripctrl_free(StripCtrlChars *sanpub); -void stripctrl_enable_line_limiting(StripCtrlChars *sccpub); -char *stripctrl_string_ptrlen(StripCtrlChars *sccpub, ptrlen str); -static inline char *stripctrl_string(StripCtrlChars *sccpub, const char *str) -{ - return stripctrl_string_ptrlen(sccpub, ptrlen_from_asciz(str)); -} - -/* - * A mechanism for loading a file from disk into a memory buffer where - * it can be picked apart as a BinarySource. - */ -struct LoadedFile { - char *data; - size_t len, max_size; - BinarySource_IMPLEMENTATION; -}; -typedef enum { - LF_OK, /* file loaded successfully */ - LF_TOO_BIG, /* file didn't fit in buffer */ - LF_ERROR, /* error from stdio layer */ -} LoadFileStatus; -LoadedFile *lf_new(size_t max_size); -void lf_free(LoadedFile *lf); -LoadFileStatus lf_load_fp(LoadedFile *lf, FILE *fp); -LoadFileStatus lf_load(LoadedFile *lf, const Filename *filename); -static inline ptrlen ptrlen_from_lf(LoadedFile *lf) -{ return make_ptrlen(lf->data, lf->len); } - -/* Set the memory block of 'size' bytes at 'out' to the bitwise XOR of - * the two blocks of the same size at 'in1' and 'in2'. - * - * 'out' may point to exactly the same address as one of the inputs, - * but if the input and output blocks overlap in any other way, the - * result of this function is not guaranteed. No memmove-style effort - * is made to handle difficult overlap cases. */ -void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size); - -/* Boolean expressions used in OpenSSH certificate configuration */ -bool cert_expr_valid(const char *expression, - char **error_msg, ptrlen *error_loc); -bool cert_expr_match_str(const char *expression, - const char *hostname, unsigned port); -/* Build a certificate expression out of hostname wildcards. Required - * to handle legacy configuration from early in development, when - * multiple wildcards were stored separately in config, implicitly - * ORed together. */ -CertExprBuilder *cert_expr_builder_new(void); -void cert_expr_builder_free(CertExprBuilder *eb); -void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard); -char *cert_expr_expression(CertExprBuilder *eb); - -#endif diff --git a/mksrcarc.sh b/mksrcarc.sh deleted file mode 100644 index 4d53e80ab..000000000 --- a/mksrcarc.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh - -set -e - -# These are text files. -text=`{ find . -name CVS -prune -o \ - -name .cvsignore -prune -o \ - -name .svn -prune -o \ - -name .git -prune -o \ - -name LATEST.VER -prune -o \ - -name CHECKLST.txt -prune -o \ - -name mksrcarc.sh -prune -o \ - -name '*.chm' -prune -o \ - -name '*.cur' -prune -o \ - -type f -print | sed 's/^\.\///'; } | \ - grep -ivE 'test/.*\.txt|MODULE|website.url' | grep -vF .ico | grep -vF .icns` -# These are files which I'm _sure_ should be treated as text, but -# which zip might complain about, so we direct its moans to -# /dev/null! Apparently its heuristics are doubtful of UTF-8 text -# files. -bintext=test/*.txt -# These are actual binary files which we don't want transforming. -bin=`{ ls -1 windows/*.ico windows/website.url; \ - find . -name '*.chm' -print -o -name '*.cur' -print; }` - -verbosely() { - echo "$@" - "$@" -} - -verbosely zip -l putty-src.zip $text -verbosely zip -l putty-src.zip $bintext -verbosely zip putty-src.zip $bin diff --git a/mkunxarc.sh b/mkunxarc.sh deleted file mode 100644 index cb79c0542..000000000 --- a/mkunxarc.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh - -# Build a Unix source distribution from the PuTTY CVS area. -# -# Expects the following arguments: -# - the suffix to put on the Unix source tarball -# - the options to put on the 'make' command line for the docs - -arcsuffix="$1" - -relver=`cat LATEST.VER` -arcname="putty$arcsuffix" -mkdir uxarc -mkdir uxarc/$arcname -find . -name uxarc -prune -o \ - -name CVS -prune -o \ - -name .svn -prune -o \ - -name . -o \ - -type d -exec mkdir uxarc/$arcname/{} \; -find . -name uxarc -prune -o \ - -name CVS -prune -o \ - -name .cvsignore -prune -o \ - -name .svn -prune -o \ - -name '*.zip' -prune -o \ - -name '*.tar.gz' -prune -o \ - -type f -exec ln -s $PWD/{} uxarc/$arcname/{} \; - -tar -C uxarc -chzof $arcname.tar.gz $arcname -rm -rf uxarc diff --git a/mpint.h b/mpint.h deleted file mode 100644 index 4ddc0e64c..000000000 --- a/mpint.h +++ /dev/null @@ -1,458 +0,0 @@ -#ifndef PUTTY_MPINT_H -#define PUTTY_MPINT_H - -/* - * PuTTY's multiprecision integer library. - * - * This library is written with the aim of avoiding leaking the input - * numbers via timing and cache side channels. This means avoiding - * making any control flow change, or deciding the address of any - * memory access, based on the value of potentially secret input data. - * - * But in a library that has to handle numbers of arbitrary size, you - * can't avoid your control flow depending on the _size_ of the input! - * So the rule is that an mp_int has a nominal size that need not be - * its mathematical size: i.e. if you call (say) mp_from_bytes_be to - * turn an array of 256 bytes into an integer, and all but the last of - * those bytes is zero, then you get an mp_int which has space for 256 - * bytes of data but just happens to store the value 1. So the - * _nominal_ sizes of input data - e.g. the size in bits of some - * public-key modulus - are not considered secret, and control flow is - * allowed to do what it likes based on those sizes. But the same - * function, called with the same _nominally sized_ arguments - * containing different values, should run in the same length of time. - * - * When a function returns an 'mp_int *', it is newly allocated to an - * appropriate nominal size (which, again, depends only on the nominal - * sizes of the inputs). Other functions have 'into' in their name, - * and they instead overwrite the contents of an existing mp_int. - * - * Functions in this API which return values that are logically - * boolean return them as 'unsigned' rather than the C99 bool type. - * That's because C99 bool does an implicit test for non-zero-ness - * when converting any other integer type to it, which compilers might - * well implement using data-dependent control flow. - */ - -/* - * Create and destroy mp_ints. A newly created one is initialised to - * zero. mp_clear also resets an existing number to zero. - */ -mp_int *mp_new(size_t maxbits); -void mp_free(mp_int *); -void mp_clear(mp_int *x); - -/* - * Resize the physical size of existing mp_int, e.g. so that you have - * room to transform it in place to a larger value. Destroys the old - * mp_int in the process. - */ -mp_int *mp_resize(mp_int *, size_t newmaxbits); - -/* - * Create mp_ints from various sources: little- and big-endian binary - * data, an ordinary C unsigned integer type, a decimal or hex string - * (given either as a ptrlen or a C NUL-terminated string), and - * another mp_int. - * - * The decimal and hex conversion functions have running time - * dependent on the length of the input data, of course. - */ -mp_int *mp_from_bytes_le(ptrlen bytes); -mp_int *mp_from_bytes_be(ptrlen bytes); -mp_int *mp_from_integer(uintmax_t n); -mp_int *mp_from_decimal_pl(ptrlen decimal); -mp_int *mp_from_decimal(const char *decimal); -mp_int *mp_from_hex_pl(ptrlen hex); -mp_int *mp_from_hex(const char *hex); -mp_int *mp_copy(mp_int *x); - -/* - * A macro for declaring large fixed numbers in source code (such as - * elliptic curve parameters, or standard Diffie-Hellman moduli). The - * idea is that you just write something like - * - * mp_int *value = MP_LITERAL(0x19284376283754638745693467245); - * - * and it newly allocates you an mp_int containing that number. - * - * Internally, the macro argument is stringified and passed to - * mp_from_hex. That's not as fast as it could be if I had instead set - * up some kind of mp_from_array_of_uint64_t() function, but I think - * this system is valuable for the fact that the literal integers - * appear in a very natural syntax that can be pasted directly out - * into, say, Python if you want to cross-check a calculation. - */ -static inline mp_int *mp__from_string_literal(const char *lit) -{ - /* Don't call this directly; it's not equipped to deal with - * hostile data. Use only via the MP_LITERAL macro. */ - if (lit[0] && (lit[1] == 'x' || lit[1] == 'X')) - return mp_from_hex(lit+2); - else - return mp_from_decimal(lit); -} -#define MP_LITERAL(number) mp__from_string_literal(#number) - -/* - * Create an mp_int with the value 2^power. - */ -mp_int *mp_power_2(size_t power); - -/* - * Retrieve the value of a particular bit or byte of an mp_int. The - * byte / bit index is not considered to be secret data. Out-of-range - * byte/bit indices are handled cleanly and return zero. - */ -uint8_t mp_get_byte(mp_int *x, size_t byte); -unsigned mp_get_bit(mp_int *x, size_t bit); - -/* - * Retrieve the value of an mp_int as a uintmax_t, assuming it's small - * enough to fit. - */ -uintmax_t mp_get_integer(mp_int *x); - -/* - * Set an mp_int bit. Again, the bit index is not considered secret. - * Do not pass an out-of-range index, on pain of assertion failure. - */ -void mp_set_bit(mp_int *x, size_t bit, unsigned val); - -/* - * Return the nominal size of an mp_int, in terms of the maximum - * number of bytes or bits that can fit in it. - */ -size_t mp_max_bytes(mp_int *x); -size_t mp_max_bits(mp_int *x); - -/* - * Return the _mathematical_ bit count of an mp_int (not its nominal - * size), i.e. a value n such that 2^{n-1} <= x < 2^n. - * - * This function is supposed to run in constant time for a given - * nominal input size. Of course it's likely that clients of this - * function will promptly need to use the result as the limit of some - * loop (e.g. marshalling an mp_int into an SSH packet, which doesn't - * permit extra prefix zero bytes). But that's up to the caller to - * decide the safety of. - */ -size_t mp_get_nbits(mp_int *x); - -/* - * Return the value of an mp_int as a decimal or hex string. The - * result is dynamically allocated, and the caller is responsible for - * freeing it. - * - * These functions should run in constant time for a given nominal - * input size, even though the exact number of digits returned is - * variable. They always allocate enough space for the largest output - * that might be needed, but they don't always fill it. - */ -char *mp_get_decimal(mp_int *x); -char *mp_get_hex(mp_int *x); -char *mp_get_hex_uppercase(mp_int *x); - -/* - * Compare two mp_ints, or compare one mp_int against a C integer. The - * 'eq' functions return 1 if the two inputs are equal, or 0 - * otherwise; the 'hs' functions return 1 if the first input is >= the - * second, and 0 otherwise. - */ -unsigned mp_cmp_hs(mp_int *a, mp_int *b); -unsigned mp_cmp_eq(mp_int *a, mp_int *b); -unsigned mp_hs_integer(mp_int *x, uintmax_t n); -unsigned mp_eq_integer(mp_int *x, uintmax_t n); - -/* - * Take the minimum or maximum of two mp_ints, without using a - * conditional branch. - */ -void mp_min_into(mp_int *r, mp_int *x, mp_int *y); -void mp_max_into(mp_int *r, mp_int *x, mp_int *y); -mp_int *mp_min(mp_int *x, mp_int *y); -mp_int *mp_max(mp_int *x, mp_int *y); - -/* - * Diagnostic function. Writes out x in hex to the supplied stdio - * stream, preceded by the string 'prefix' and followed by 'suffix'. - * - * This is useful to put temporarily into code, but it's also - * potentially useful to call from a debugger. - */ -void mp_dump(FILE *fp, const char *prefix, mp_int *x, const char *suffix); - -/* - * Overwrite one mp_int with another, or with a plain integer. - */ -void mp_copy_into(mp_int *dest, mp_int *src); -void mp_copy_integer_into(mp_int *dest, uintmax_t n); - -/* - * Conditional selection. Overwrites dest with either src0 or src1, - * according to the value of 'choose_src1'. choose_src1 should be 0 or - * 1; if it's 1, then dest is set to src1, otherwise src0. - * - * The value of choose_src1 is considered to be secret data, so - * control flow and memory access should not depend on it. - */ -void mp_select_into(mp_int *dest, mp_int *src0, mp_int *src1, - unsigned choose_src1); - -/* - * Addition, subtraction and multiplication, either targeting an - * existing mp_int or making a new one large enough to hold whatever - * the output might be.. - */ -void mp_add_into(mp_int *r, mp_int *a, mp_int *b); -void mp_sub_into(mp_int *r, mp_int *a, mp_int *b); -void mp_mul_into(mp_int *r, mp_int *a, mp_int *b); -mp_int *mp_add(mp_int *x, mp_int *y); -mp_int *mp_sub(mp_int *x, mp_int *y); -mp_int *mp_mul(mp_int *x, mp_int *y); - -/* - * Bitwise operations. - */ -void mp_and_into(mp_int *r, mp_int *a, mp_int *b); -void mp_or_into(mp_int *r, mp_int *a, mp_int *b); -void mp_xor_into(mp_int *r, mp_int *a, mp_int *b); -void mp_bic_into(mp_int *r, mp_int *a, mp_int *b); - -/* - * Addition, subtraction and multiplication with one argument small - * enough to fit in a C integer. For mp_mul_integer_into, it has to be - * even smaller than that. - */ -void mp_add_integer_into(mp_int *r, mp_int *a, uintmax_t n); -void mp_sub_integer_into(mp_int *r, mp_int *a, uintmax_t n); -void mp_mul_integer_into(mp_int *r, mp_int *a, uint16_t n); - -/* - * Conditional addition/subtraction. If yes == 1, sets r to a+b or a-b - * (respectively). If yes == 0, sets r to just a. 'yes' is considered - * secret data. - */ -void mp_cond_add_into(mp_int *r, mp_int *a, mp_int *b, unsigned yes); -void mp_cond_sub_into(mp_int *r, mp_int *a, mp_int *b, unsigned yes); - -/* - * Swap x0 and x1 if swap == 1, and not if swap == 0. 'swap' is - * considered secret. - */ -void mp_cond_swap(mp_int *x0, mp_int *x1, unsigned swap); - -/* - * Set x to 0 if clear == 1, and otherwise leave it unchanged. 'clear' - * is considered secret. - */ -void mp_cond_clear(mp_int *x, unsigned clear); - -/* - * Division. mp_divmod_into divides n by d, and writes the quotient - * into q and the remainder into r. You can pass either of q and r as - * NULL if you don't need one of the outputs. - * - * mp_div and mp_mod are wrappers that return one or other of those - * outputs as a freshly allocated mp_int of the appropriate size. - * - * Division by zero gives no error, and returns a quotient of 0 and a - * remainder of n (so as to still satisfy the division identity that - * n=qd+r). - */ -void mp_divmod_into(mp_int *n, mp_int *d, mp_int *q, mp_int *r); -mp_int *mp_div(mp_int *n, mp_int *d); -mp_int *mp_mod(mp_int *x, mp_int *modulus); - -/* - * Compute the residue of x mod m, where m is a small integer. x is - * kept secret, but m is not. - */ -uint32_t mp_mod_known_integer(mp_int *x, uint32_t m); - -/* - * Integer nth root. mp_nthroot returns the largest integer x such - * that x^n <= y, and if 'remainder' is non-NULL then it fills it with - * the residue (y - x^n). - * - * Currently, n has to be small enough that the largest binomial - * coefficient (n choose k) fits in 16 bits, which works out to at - * most 18. - */ -mp_int *mp_nthroot(mp_int *y, unsigned n, mp_int *remainder); - -/* - * Trivially easy special case of mp_mod: reduce a number mod a power - * of two. - */ -void mp_reduce_mod_2to(mp_int *x, size_t p); - -/* - * Modular inverses. mp_invert computes the inverse of x mod modulus - * (and will expect the two to be coprime). mp_invert_mod_2to computes - * the inverse of x mod 2^p, and is a great deal faster. - */ -mp_int *mp_invert_mod_2to(mp_int *x, size_t p); -mp_int *mp_invert(mp_int *x, mp_int *modulus); - -/* - * Greatest common divisor. - * - * mp_gcd_into also returns a pair of Bezout coefficients, namely A,B - * such that a*A - b*B = gcd. (The minus sign is so that both returned - * coefficients can be positive.) - * - * You can pass any of mp_gcd_into's output pointers as NULL if you - * don't need that output value. - * - * mp_gcd is a wrapper with a less cumbersome API, for the case where - * the only output value you need is the gcd itself. mp_coprime is - * even easier, if all you care about is whether or not that gcd is 1. - */ -mp_int *mp_gcd(mp_int *a, mp_int *b); -void mp_gcd_into(mp_int *a, mp_int *b, - mp_int *gcd_out, mp_int *A_out, mp_int *B_out); -unsigned mp_coprime(mp_int *a, mp_int *b); - -/* - * System for taking square roots modulo an odd prime. - * - * In order to do this efficiently, you need to provide an extra piece - * of information at setup time, namely a number which is not - * congruent mod p to any square. Given p and that non-square, you can - * use modsqrt_new to make a context containing all the necessary - * equipment for actually calculating the square roots, and then you - * can call mp_modsqrt as many times as you like on that context - * before freeing it. - * - * The output parameter '*success' will be filled in with 1 if the - * operation was successful, or 0 if the input number doesn't have a - * square root mod p at all. In the latter case, the returned mp_int - * will be nonsense and you shouldn't depend on it. - * - * ==== WARNING ==== - * - * This function DOES NOT TREAT THE PRIME MODULUS AS SECRET DATA! It - * will protect the number you're taking the square root _of_, but not - * the number you're taking the root of it _mod_. - * - * (This is because the algorithm requires a number of loop iterations - * equal to the number of factors of 2 in p-1. And the expected use of - * this function is for elliptic-curve point decompression, in which - * the modulus is always a well-known one written down in standards - * documents.) - */ -typedef struct ModsqrtContext ModsqrtContext; -ModsqrtContext *modsqrt_new(mp_int *p, mp_int *any_nonsquare_mod_p); -void modsqrt_free(ModsqrtContext *); -mp_int *mp_modsqrt(ModsqrtContext *sc, mp_int *x, unsigned *success); - -/* - * Functions for Montgomery multiplication, a fast technique for doing - * a long series of modular multiplications all with the same modulus - * (which has to be odd). - * - * You start by calling monty_new to set up a context structure - * containing all the precomputed bits and pieces needed by the - * algorithm. Then, any numbers you want to work with must first be - * transformed into the internal Montgomery representation using - * monty_import; having done that, you can use monty_mul and monty_pow - * to operate on them efficiently; and finally, monty_export will - * convert numbers back out of Montgomery representation to give their - * ordinary values. - * - * Addition and subtraction are not optimised by the Montgomery trick, - * but monty_add and monty_sub are provided anyway for convenience. - * - * There are also monty_invert and monty_modsqrt, which are analogues - * of mp_invert and mp_modsqrt which take their inputs in Montgomery - * representation. For mp_modsqrt, the prime modulus of the - * ModsqrtContext must be the same as the modulus of the MontyContext. - * - * The query functions monty_modulus and monty_identity return numbers - * stored inside the MontyContext, without copying them. The returned - * pointers are still owned by the MontyContext, so don't free them! - */ -MontyContext *monty_new(mp_int *modulus); -void monty_free(MontyContext *mc); -mp_int *monty_modulus(MontyContext *mc); /* doesn't transfer ownership */ -mp_int *monty_identity(MontyContext *mc); /* doesn't transfer ownership */ -void monty_import_into(MontyContext *mc, mp_int *r, mp_int *x); -mp_int *monty_import(MontyContext *mc, mp_int *x); -void monty_export_into(MontyContext *mc, mp_int *r, mp_int *x); -mp_int *monty_export(MontyContext *mc, mp_int *x); -void monty_mul_into(MontyContext *, mp_int *r, mp_int *, mp_int *); -mp_int *monty_add(MontyContext *, mp_int *, mp_int *); -mp_int *monty_sub(MontyContext *, mp_int *, mp_int *); -mp_int *monty_mul(MontyContext *, mp_int *, mp_int *); -mp_int *monty_pow(MontyContext *, mp_int *base, mp_int *exponent); -mp_int *monty_invert(MontyContext *, mp_int *); -mp_int *monty_modsqrt(ModsqrtContext *sc, mp_int *mx, unsigned *success); - -/* - * Modular arithmetic functions which don't use an explicit - * MontyContext. mp_modpow will use one internally (on the assumption - * that the exponent is likely to be large enough to make it - * worthwhile); the other three will just do ordinary non-Montgomery- - * optimised modular reduction. Use mp_modmul if you only have one - * product to compute; if you have a lot, consider using a - * MontyContext in the client code. - */ -mp_int *mp_modpow(mp_int *base, mp_int *exponent, mp_int *modulus); -mp_int *mp_modmul(mp_int *x, mp_int *y, mp_int *modulus); -mp_int *mp_modadd(mp_int *x, mp_int *y, mp_int *modulus); -mp_int *mp_modsub(mp_int *x, mp_int *y, mp_int *modulus); - -/* - * Shift an mp_int by a given number of bits. The shift count is - * considered to be secret data, and as a result, the algorithm takes - * O(n log n) time instead of the obvious O(n). - * - * There's no mp_lshift_safe, because the size of mp_int to allocate - * would not be able to avoid depending on the shift count. So if you - * need to behave independently of the size of a left shift, you have - * to know a bound on the space you'll need by some other means. - */ -void mp_lshift_safe_into(mp_int *r, mp_int *x, size_t shift); -void mp_rshift_safe_into(mp_int *r, mp_int *x, size_t shift); -mp_int *mp_rshift_safe(mp_int *x, size_t shift); - -/* - * Shift an mp_int left or right by a fixed number of bits. The shift - * count is NOT considered to be secret data! Use this if you're - * always dividing by 2, for example, but don't use it to shift by a - * variable amount derived from another secret number. - * - * The upside is that these functions run in sensible linear time. - */ -void mp_lshift_fixed_into(mp_int *r, mp_int *a, size_t shift); -void mp_rshift_fixed_into(mp_int *r, mp_int *x, size_t shift); -mp_int *mp_lshift_fixed(mp_int *x, size_t shift); -mp_int *mp_rshift_fixed(mp_int *x, size_t shift); - -/* - * Generate a random mp_int. - * - * The _function_ definitions here will expect to be given a gen_data - * function that provides random data. Normally you'd use this using - * random_read() from sshrand.c, and the macro wrappers automate that. - * - * (This is a bit of a dodge to avoid mpint.c having a link-time - * dependency on sshrand.c, so that programs can link against one but - * not the other: if a client of this header uses one of these macros - * then _they_ have link-time dependencies on both modules.) - * - * mp_random_bits[_fn] returns an integer 0 <= n < 2^bits. - * mp_random_upto[_fn](limit) returns an integer 0 <= n < limit. - * mp_random_in_range[_fn](lo,hi) returns an integer lo <= n < hi. - */ -typedef void (*random_read_fn_t)(void *, size_t); -mp_int *mp_random_bits_fn(size_t bits, random_read_fn_t randfn); -mp_int *mp_random_upto_fn(mp_int *limit, random_read_fn_t randfn); -mp_int *mp_random_in_range_fn( - mp_int *lo_inclusive, mp_int *hi_exclusive, random_read_fn_t randfn); -#define mp_random_bits(bits) mp_random_bits_fn(bits, random_read) -#define mp_random_upto(limit) mp_random_upto_fn(limit, random_read) -#define mp_random_in_range(lo, hi) mp_random_in_range_fn(lo, hi, random_read) - -#endif /* PUTTY_MPINT_H */ diff --git a/network.h b/network.h deleted file mode 100644 index 57e6662d0..000000000 --- a/network.h +++ /dev/null @@ -1,436 +0,0 @@ -/* - * 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 - -#include "defs.h" - -typedef struct SocketVtable SocketVtable; -typedef struct PlugVtable PlugVtable; - -struct Socket { - const struct SocketVtable *vt; -}; - -struct SocketVtable { - 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); - size_t (*write) (Socket *s, const void *data, size_t len); - size_t (*write_oob) (Socket *s, const void *data, size_t len); - void (*write_eof) (Socket *s); - void (*set_frozen) (Socket *s, bool is_frozen); - /* ignored by tcp, but vital for ssl */ - const char *(*socket_error) (Socket *s); - SocketPeerInfo *(*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 { - const struct PlugVtable *vt; -}; - -typedef enum PlugLogType { - PLUGLOG_CONNECT_TRYING, - PLUGLOG_CONNECT_FAILED, - PLUGLOG_CONNECT_SUCCESS, - PLUGLOG_PROXY_MSG, -} PlugLogType; - -typedef enum PlugCloseType { - PLUGCLOSE_NORMAL, - PLUGCLOSE_ERROR, - PLUGCLOSE_BROKEN_PIPE, - PLUGCLOSE_USER_ABORT, -} PlugCloseType; - -struct PlugVtable { - /* - * Passes the client progress reports on the process of setting - * up the connection. - * - * - PLUGLOG_CONNECT_TRYING means we are about to try to connect - * to address `addr' (error_msg and error_code are ignored) - * - * - PLUGLOG_CONNECT_FAILED 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. - * - * - PLUGLOG_CONNECT_SUCCESS means we have succeeded in making a - * connection. `addr' gives the address we connected to, if - * available. (But sometimes, in cases of complicated proxy - * setups, it might not be available, so receivers of this log - * event should be prepared to deal with addr==NULL.) - * - * - PLUGLOG_PROXY_MSG means that error_msg contains a line of - * logging information from whatever the connection is being - * proxied through. This will typically be a wodge of - * standard-error output from a local proxy command, so the - * receiver should probably prefix it to indicate this. - * - * Note that sometimes log messages may be sent even to Socket - * types that don't involve making an outgoing connection, e.g. - * because the same core implementation (such as Windows handle - * sockets) is shared between listening and connecting sockets. So - * all Plugs must implement this method, even if only to ignore - * the logged events. - */ - void (*log)(Plug *p, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code); - - /* - * Notifies the Plug that the socket is closing, and something - * about why. - * - * - PLUGCLOSE_NORMAL means an ordinary non-error closure. In - * this case, error_msg should be ignored (and hopefully - * callers will have passed NULL). - * - * - PLUGCLOSE_ERROR indicates that an OS error occurred, and - * 'error_msg' contains a string describing it, for use in - * diagnostics. (Ownership of the string is not transferred.) - * This error class covers anything other than the special - * case below: - * - * - PLUGCLOSE_BROKEN_PIPE behaves like PLUGCLOSE_ERROR (in - * particular, there's still an error message provided), but - * distinguishes the particular error condition signalled by - * EPIPE / ERROR_BROKEN_PIPE, which ssh/sharing.c needs to - * recognise and handle specially in one situation. - * - * - PLUGCLOSE_USER_ABORT means that the close has happened as a - * result of some kind of deliberate user action (e.g. hitting - * ^C at a password prompt presented by a proxy socket setup - * phase). This can be used to suppress interactive error - * messages sent to the user (such as dialog boxes), on the - * grounds that the user already knows. However, 'error_msg' - * will still contain some appropriate text, so that - * non-interactive error reporting (e.g. event logs) can still - * record why the connection terminated. - */ - void (*closing)(Plug *p, PlugCloseType type, const char *error_msg); - - /* - * Provides incoming socket data to the Plug. Three cases: - * - * - 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 (*receive) (Plug *p, int urgent, const char *data, size_t len); - - /* - * Called when the pending send backlog on a socket is cleared or - * partially cleared. The new backlog size is passed in the - * `bufsize' parameter. - */ - void (*sent) (Plug *p, size_t bufsize); - - /* - * Only called 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. - */ - int (*accepting)(Plug *p, accept_fn_t constructor, accept_ctx_t ctx); -}; - -/* Proxy indirection layer. - * - * Calling new_connection transfers ownership of 'addr': the proxy - * layer is now responsible for freeing it, and the caller shouldn't - * assume it exists any more. - * - * If calling this from a backend with a Seat, you can also give it a - * pointer to the backend's Interactor trait. In that situation, it - * might replace the backend's seat with a temporary seat of its own, - * and give the real Seat to an Interactor somewhere in the proxy - * system so that it can ask for passwords (and, in the case of SSH - * proxying, other prompts like host key checks). If that happens, - * then the resulting 'temp seat' is the backend's property, and it - * will have to remember to free it when cleaning up, or after - * flushing it back into the real seat when the network connection - * attempt completes. - * - * You can free your TempSeat and resume using the real Seat when one - * of two things happens: either your Plug's closing() method is - * called (indicating failure to connect), or its log() method is - * called with PLUGLOG_CONNECT_SUCCESS. In the latter case, you'll - * probably want to flush the TempSeat's contents into the real Seat, - * of course. - */ -Socket *new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, Interactor *interactor); -Socket *new_listener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, Conf *conf, int addressfamily); -SockAddr *name_lookup(const char *host, int port, char **canonicalname, - Conf *conf, int addressfamily, LogContext *logctx, - const char *lookup_reason_for_logging); - -/* platform-dependent callback from new_connection() */ -/* (same caveat about addr as new_connection()) */ -Socket *platform_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, Interactor *itr); - -/* callback for SSH jump-host proxying */ -Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, Interactor *itr); - -/* 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); -bool sk_addr_needs_port(SockAddr *addr); -bool sk_hostname_is_local(const char *name); -bool sk_address_is_local(SockAddr *addr); -bool 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 *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, - bool nodelay, bool keepalive, Plug *p); - -Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, int address_family); - -static inline Plug *sk_plug(Socket *s, Plug *p) -{ return s->vt->plug(s, p); } -static inline void sk_close(Socket *s) -{ s->vt->close(s); } -static inline size_t sk_write(Socket *s, const void *data, size_t len) -{ return s->vt->write(s, data, len); } -static inline size_t sk_write_oob(Socket *s, const void *data, size_t len) -{ return s->vt->write_oob(s, data, len); } -static inline void sk_write_eof(Socket *s) -{ s->vt->write_eof(s); } - -static inline void plug_log( - Plug *p, int type, SockAddr *addr, int port, const char *msg, int code) -{ p->vt->log(p, type, addr, port, msg, code); } -static inline void plug_closing(Plug *p, PlugCloseType type, const char *msg) -{ p->vt->closing(p, type, msg); } -static inline void plug_closing_normal(Plug *p) -{ p->vt->closing(p, PLUGCLOSE_NORMAL, NULL); } -static inline void plug_closing_error(Plug *p, const char *msg) -{ p->vt->closing(p, PLUGCLOSE_ERROR, msg); } -static inline void plug_closing_user_abort(Plug *p) -{ p->vt->closing(p, PLUGCLOSE_USER_ABORT, "User aborted connection setup"); } -static inline void plug_receive(Plug *p, int urg, const char *data, size_t len) -{ p->vt->receive(p, urg, data, len); } -static inline void plug_sent (Plug *p, size_t bufsize) -{ p->vt->sent(p, bufsize); } -static inline int plug_accepting(Plug *p, accept_fn_t cons, accept_ctx_t ctx) -{ return p->vt->accepting(p, cons, ctx); } - -/* - * 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); -static inline const char *sk_socket_error(Socket *s) -{ return s->vt->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. - */ -static inline void sk_set_frozen(Socket *s, bool is_frozen) -{ s->vt->set_frozen(s, is_frozen); } - -/* - * Return a structure giving some information about the other end of - * the socket. May be NULL, if nothing is available at all. If it is - * not NULL, then it is dynamically allocated, and should be freed by - * a call to sk_free_peer_info(). See below for the definition. - */ -static inline SocketPeerInfo *sk_peer_info(Socket *s) -{ return s->vt->peer_info(s); } - -/* - * The structure returned from sk_peer_info, and a function to free - * one (in utils). - */ -struct SocketPeerInfo { - int addressfamily; - - /* - * Text form of the IPv4 or IPv6 address of the other end of the - * socket, if available, in the standard text representation. - */ - const char *addr_text; - - /* - * Binary form of the same address. Filled in if and only if - * addr_text is not NULL. You can tell which branch of the union - * is used by examining 'addressfamily'. - */ - union { - unsigned char ipv6[16]; - unsigned char ipv4[4]; - } addr_bin; - - /* - * Remote port number, or -1 if not available. - */ - int port; - - /* - * Free-form text suitable for putting in log messages. For IP - * sockets, repeats the address and port information from above. - * But it can be completely different, e.g. for Unix-domain - * sockets it gives information about the uid, gid and pid of the - * connecting process. - */ - const char *log_text; -}; -void sk_free_peer_info(SocketPeerInfo *pi); - -/* - * Simple wrapper on getservbyname(), needed by portfwd.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(const 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. - * - * The consume_string variant takes an already-formatted dynamically - * allocated string, and takes over ownership of that string. - */ -Socket *new_error_socket_fmt(Plug *plug, const char *fmt, ...) - PRINTF_LIKE(2, 3); -Socket *new_error_socket_consume_string(Plug *plug, char *errmsg); - -/* - * Trivial plug that does absolutely nothing. Found in nullplug.c. - */ -extern Plug *const nullplug; - -/* - * Some trivial no-op plug functions, also in nullplug.c; exposed here - * so that other Plug implementations can use them too. - * - * In particular, nullplug_log is useful to Plugs that don't need to - * worry about logging. - */ -void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *err_msg, int err_code); -void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg); -void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len); -void nullplug_sent(Plug *plug, size_t bufsize); - -/* ---------------------------------------------------------------------- - * Functions defined outside the network code, which have to be - * declared in this header file rather than the main putty.h because - * they use types defined here. - */ - -void backend_socket_log(Seat *seat, LogContext *logctx, - PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code, Conf *conf, - bool session_started); - -typedef struct ProxyStderrBuf { - char buf[8192]; - size_t size; - const char *prefix; /* must be statically allocated */ -} ProxyStderrBuf; -void psb_init(ProxyStderrBuf *psb); -void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix); -void log_proxy_stderr( - Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len); - -/* ---------------------------------------------------------------------- - * The DeferredSocketOpener trait. This is a thing that some Socket - * implementations may choose to own if they need to delay actually - * setting up the underlying connection. For example, sockets used in - * local-proxy handling (Unix FdSocket / Windows HandleSocket) might - * need to do this if they have to prompt the user interactively for - * parts of the command they'll run. - * - * Mostly, a DeferredSocketOpener implementation will keep to itself, - * arrange its own callbacks in order to do whatever setup it needs, - * and when it's ready, call back to its parent Socket via some - * implementation-specific API of its own. So the shared API here - * requires almost nothing: the only thing we need is a free function, - * so that if the owner of a Socket of this kind needs to close it - * before the deferred connection process is finished, the Socket can - * also clean up the DeferredSocketOpener dangling off it. - */ - -struct DeferredSocketOpener { - const DeferredSocketOpenerVtable *vt; -}; -struct DeferredSocketOpenerVtable { - void (*free)(DeferredSocketOpener *); -}; -static inline void deferred_socket_opener_free(DeferredSocketOpener *dso) -{ dso->vt->free(dso); } - -DeferredSocketOpener *null_deferred_socket_opener(void); - -#endif diff --git a/otherbackends/CMakeLists.txt b/otherbackends/CMakeLists.txt deleted file mode 100644 index 099d1253d..000000000 --- a/otherbackends/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -add_sources_from_current_dir(otherbackends - raw.c - rlogin.c - supdup.c - telnet.c - testback.c) diff --git a/otherbackends/raw.c b/otherbackends/raw.c deleted file mode 100644 index 7f35d206a..000000000 --- a/otherbackends/raw.c +++ /dev/null @@ -1,378 +0,0 @@ -/* - * "Raw" backend. - */ - -#include -#include -#include - -#include "putty.h" - -#define RAW_MAX_BACKLOG 4096 - -typedef struct Raw Raw; -struct Raw { - Socket *s; - bool closed_on_socket_error; - size_t bufsize; - Seat *seat; - LogContext *logctx; - Ldisc *ldisc; - bool sent_console_eof, sent_socket_eof, socket_connected; - char *description; - - Conf *conf; - - Plug plug; - Backend backend; - Interactor interactor; -}; - -static void raw_size(Backend *be, int width, int height); - -static void c_write(Raw *raw, const void *buf, size_t len) -{ - size_t backlog = seat_stdout(raw->seat, buf, len); - sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); -} - -static void raw_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - Raw *raw = container_of(plug, Raw, plug); - backend_socket_log(raw->seat, raw->logctx, type, addr, port, error_msg, - error_code, raw->conf, raw->socket_connected); - if (type == PLUGLOG_CONNECT_SUCCESS) { - raw->socket_connected = true; - if (raw->ldisc) - ldisc_check_sendok(raw->ldisc); - } -} - -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; - seat_notify_remote_exit(raw->seat); - seat_notify_remote_disconnect(raw->seat); - } - } -} - -static void raw_closing(Plug *plug, PlugCloseType type, const char *error_msg) -{ - Raw *raw = container_of(plug, Raw, plug); - - if (type != PLUGCLOSE_NORMAL) { - /* A socket error has occurred. */ - if (raw->s) { - sk_close(raw->s); - raw->s = NULL; - raw->closed_on_socket_error = true; - seat_notify_remote_exit(raw->seat); - seat_notify_remote_disconnect(raw->seat); - } - logevent(raw->logctx, error_msg); - if (type != PLUGCLOSE_USER_ABORT) - seat_connection_fatal(raw->seat, "%s", error_msg); - } else { - /* Otherwise, the remote side closed the connection normally. */ - if (!raw->sent_console_eof && seat_eof(raw->seat)) { - /* - * 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); - } -} - -static void raw_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - Raw *raw = container_of(plug, Raw, plug); - c_write(raw, data, len); -} - -static void raw_sent(Plug *plug, size_t bufsize) -{ - Raw *raw = container_of(plug, Raw, plug); - raw->bufsize = bufsize; - seat_sent(raw->seat, raw->bufsize); -} - -static const PlugVtable Raw_plugvt = { - .log = raw_log, - .closing = raw_closing, - .receive = raw_receive, - .sent = raw_sent, -}; - -static char *raw_description(Interactor *itr) -{ - Raw *raw = container_of(itr, Raw, interactor); - return dupstr(raw->description); -} - -static LogPolicy *raw_logpolicy(Interactor *itr) -{ - Raw *raw = container_of(itr, Raw, interactor); - return log_get_policy(raw->logctx); -} - -static Seat *raw_get_seat(Interactor *itr) -{ - Raw *raw = container_of(itr, Raw, interactor); - return raw->seat; -} - -static void raw_set_seat(Interactor *itr, Seat *seat) -{ - Raw *raw = container_of(itr, Raw, interactor); - raw->seat = seat; -} - -static const InteractorVtable Raw_interactorvt = { - .description = raw_description, - .logpolicy = raw_logpolicy, - .get_seat = raw_get_seat, - .set_seat = raw_set_seat, -}; - -/* - * 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 char *raw_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - SockAddr *addr; - const char *err; - Raw *raw; - int addressfamily; - char *loghost; - - raw = snew(Raw); - memset(raw, 0, sizeof(Raw)); - raw->plug.vt = &Raw_plugvt; - raw->backend.vt = vt; - raw->interactor.vt = &Raw_interactorvt; - raw->backend.interactor = &raw->interactor; - raw->s = NULL; - raw->closed_on_socket_error = false; - *backend_handle = &raw->backend; - raw->sent_console_eof = raw->sent_socket_eof = false; - raw->bufsize = 0; - raw->socket_connected = false; - raw->conf = conf_copy(conf); - raw->description = default_description(vt, host, port); - - raw->seat = seat; - raw->logctx = logctx; - - addressfamily = conf_get_int(conf, CONF_addressfamily); - /* - * Try to find host. - */ - addr = name_lookup(host, port, realhost, conf, addressfamily, - raw->logctx, "main connection"); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return dupstr(err); - } - - if (port < 0) - port = 23; /* default telnet port */ - - /* - * Open socket. - */ - raw->s = new_connection(addr, *realhost, port, false, true, nodelay, - keepalive, &raw->plug, conf, &raw->interactor); - if ((err = sk_socket_error(raw->s)) != NULL) - return dupstr(err); - - /* No local authentication phase in this protocol */ - seat_set_trust_status(raw->seat, false); - - 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(Backend *be) -{ - Raw *raw = container_of(be, Raw, backend); - - if (is_tempseat(raw->seat)) - tempseat_free(raw->seat); - if (raw->s) - sk_close(raw->s); - conf_free(raw->conf); - sfree(raw->description); - sfree(raw); -} - -/* - * Stub routine (we don't have any need to reconfigure this backend). - */ -static void raw_reconfig(Backend *be, Conf *conf) -{ -} - -/* - * Called to send data down the raw connection. - */ -static void raw_send(Backend *be, const char *buf, size_t len) -{ - Raw *raw = container_of(be, Raw, backend); - - if (raw->s == NULL) - return; - - raw->bufsize = sk_write(raw->s, buf, len); -} - -/* - * Called to query the current socket sendability status. - */ -static size_t raw_sendbuffer(Backend *be) -{ - Raw *raw = container_of(be, Raw, backend); - return raw->bufsize; -} - -/* - * Called to set the size of the window - */ -static void raw_size(Backend *be, int width, int height) -{ - /* Do nothing! */ - return; -} - -/* - * Send raw special codes. We only handle outgoing EOF here. - */ -static void raw_special(Backend *be, SessionSpecialCode code, int arg) -{ - Raw *raw = container_of(be, Raw, backend); - if (code == SS_EOF && raw->s) { - if (!raw->sent_socket_eof) - 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 SessionSpecial *raw_get_specials(Backend *be) -{ - return NULL; -} - -static bool raw_connected(Backend *be) -{ - Raw *raw = container_of(be, Raw, backend); - return raw->s != NULL; -} - -static bool raw_sendok(Backend *be) -{ - Raw *raw = container_of(be, Raw, backend); - return raw->socket_connected; -} - -static void raw_unthrottle(Backend *be, size_t backlog) -{ - Raw *raw = container_of(be, Raw, backend); - sk_set_frozen(raw->s, backlog > RAW_MAX_BACKLOG); -} - -static bool raw_ldisc(Backend *be, int option) -{ - if (option == LD_EDIT || option == LD_ECHO) - return true; - return false; -} - -static void raw_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - Raw *raw = container_of(be, Raw, backend); - raw->ldisc = ldisc; -} - -static int raw_exitcode(Backend *be) -{ - Raw *raw = container_of(be, Raw, backend); - 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(Backend *be) -{ - return 0; -} - -const BackendVtable raw_backend = { - .init = raw_init, - .free = raw_free, - .reconfig = raw_reconfig, - .send = raw_send, - .sendbuffer = raw_sendbuffer, - .size = raw_size, - .special = raw_special, - .get_specials = raw_get_specials, - .connected = raw_connected, - .exitcode = raw_exitcode, - .sendok = raw_sendok, - .ldisc_option_state = raw_ldisc, - .provide_ldisc = raw_provide_ldisc, - .unthrottle = raw_unthrottle, - .cfg_info = raw_cfg_info, - .id = "raw", - .displayname_tc = "Raw", - .displayname_lc = "raw", - .protocol = PROT_RAW, - .default_port = 0, -}; diff --git a/otherbackends/rlogin.c b/otherbackends/rlogin.c deleted file mode 100644 index 370872579..000000000 --- a/otherbackends/rlogin.c +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Rlogin backend. - */ - -#include -#include -#include -#include - -#include "putty.h" - -#define RLOGIN_MAX_BACKLOG 4096 - -typedef struct Rlogin Rlogin; -struct Rlogin { - Socket *s; - bool closed_on_socket_error; - int bufsize; - bool socket_connected; - bool firstbyte; - bool cansize; - int term_width, term_height; - Seat *seat; - LogContext *logctx; - Ldisc *ldisc; - char *description; - - Conf *conf; - - /* In case we need to read a username from the terminal before starting */ - prompts_t *prompt; - - Plug plug; - Backend backend; - Interactor interactor; -}; - -static void rlogin_startup(Rlogin *rlogin, SeatPromptResult spr, - const char *ruser); -static void rlogin_try_username_prompt(void *ctx); - -static void c_write(Rlogin *rlogin, const void *buf, size_t len) -{ - size_t backlog = seat_stdout(rlogin->seat, buf, len); - sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); -} - -static void rlogin_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - Rlogin *rlogin = container_of(plug, Rlogin, plug); - backend_socket_log(rlogin->seat, rlogin->logctx, type, addr, port, - error_msg, error_code, - rlogin->conf, rlogin->socket_connected); - if (type == PLUGLOG_CONNECT_SUCCESS) { - rlogin->socket_connected = true; - - char *ruser = get_remote_username(rlogin->conf); - if (ruser) { - /* - * If we already know the remote username, call - * rlogin_startup, which will send the initial protocol - * greeting including local username, remote username, - * terminal type and terminal speed. - */ - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - rlogin_startup(rlogin, SPR_OK, ruser); - sfree(ruser); - } else { - /* - * Otherwise, set up a prompts_t asking for the local - * username. If it completes synchronously, call - * rlogin_startup as above; otherwise, wait until it does. - */ - rlogin->prompt = new_prompts(); - rlogin->prompt->to_server = true; - rlogin->prompt->from_server = false; - rlogin->prompt->name = dupstr("Rlogin login name"); - rlogin->prompt->callback = rlogin_try_username_prompt; - rlogin->prompt->callback_ctx = rlogin; - add_prompt(rlogin->prompt, dupstr("rlogin username: "), true); - rlogin_try_username_prompt(rlogin); - } - } -} - -static void rlogin_closing(Plug *plug, PlugCloseType type, - const char *error_msg) -{ - Rlogin *rlogin = container_of(plug, 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; - seat_notify_remote_exit(rlogin->seat); - seat_notify_remote_disconnect(rlogin->seat); - } - if (type != PLUGCLOSE_NORMAL) { - /* A socket error has occurred. */ - logevent(rlogin->logctx, error_msg); - if (type != PLUGCLOSE_USER_ABORT) - seat_connection_fatal(rlogin->seat, "%s", error_msg); - } - /* Otherwise, the remote side closed the connection normally. */ -} - -static void rlogin_receive( - Plug *plug, int urgent, const char *data, size_t len) -{ - Rlogin *rlogin = container_of(plug, Rlogin, plug); - if (len == 0) - return; - if (urgent == 2) { - char c; - - c = *data++; - len--; - if (c == '\x80') { - rlogin->cansize = true; - backend_size(&rlogin->backend, - 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 = false; - } - if (len > 0) - c_write(rlogin, data, len); - } -} - -static void rlogin_sent(Plug *plug, size_t bufsize) -{ - Rlogin *rlogin = container_of(plug, Rlogin, plug); - rlogin->bufsize = bufsize; - seat_sent(rlogin->seat, rlogin->bufsize); -} - -static void rlogin_startup(Rlogin *rlogin, SeatPromptResult spr, - const char *ruser) -{ - char z = 0; - char *p; - - if (spr.kind == SPRK_USER_ABORT) { - /* User aborted at the username prompt. */ - sk_close(rlogin->s); - rlogin->s = NULL; - seat_notify_remote_exit(rlogin->seat); - } else if (spr.kind == SPRK_SW_ABORT) { - /* Something else went wrong at the username prompt, so we - * have to show some kind of error. */ - sk_close(rlogin->s); - rlogin->s = NULL; - char *err = spr_get_error_message(spr); - seat_connection_fatal(rlogin->seat, "%s", err); - sfree(err); - } else { - 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; - if (rlogin->ldisc) - ldisc_check_sendok(rlogin->ldisc); -} - -static const PlugVtable Rlogin_plugvt = { - .log = rlogin_log, - .closing = rlogin_closing, - .receive = rlogin_receive, - .sent = rlogin_sent, -}; - -static char *rlogin_description(Interactor *itr) -{ - Rlogin *rlogin = container_of(itr, Rlogin, interactor); - return dupstr(rlogin->description); -} - -static LogPolicy *rlogin_logpolicy(Interactor *itr) -{ - Rlogin *rlogin = container_of(itr, Rlogin, interactor); - return log_get_policy(rlogin->logctx); -} - -static Seat *rlogin_get_seat(Interactor *itr) -{ - Rlogin *rlogin = container_of(itr, Rlogin, interactor); - return rlogin->seat; -} - -static void rlogin_set_seat(Interactor *itr, Seat *seat) -{ - Rlogin *rlogin = container_of(itr, Rlogin, interactor); - rlogin->seat = seat; -} - -static const InteractorVtable Rlogin_interactorvt = { - .description = rlogin_description, - .logpolicy = rlogin_logpolicy, - .get_seat = rlogin_get_seat, - .set_seat = rlogin_set_seat, -}; - -/* - * 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 char *rlogin_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - SockAddr *addr; - const char *err; - Rlogin *rlogin; - int addressfamily; - char *loghost; - - rlogin = snew(Rlogin); - memset(rlogin, 0, sizeof(Rlogin)); - rlogin->plug.vt = &Rlogin_plugvt; - rlogin->backend.vt = vt; - rlogin->interactor.vt = &Rlogin_interactorvt; - rlogin->backend.interactor = &rlogin->interactor; - rlogin->s = NULL; - rlogin->closed_on_socket_error = false; - rlogin->seat = seat; - rlogin->logctx = logctx; - rlogin->term_width = conf_get_int(conf, CONF_width); - rlogin->term_height = conf_get_int(conf, CONF_height); - rlogin->socket_connected = false; - rlogin->firstbyte = true; - rlogin->cansize = false; - rlogin->prompt = NULL; - rlogin->conf = conf_copy(conf); - rlogin->description = default_description(vt, host, port); - *backend_handle = &rlogin->backend; - - addressfamily = conf_get_int(conf, CONF_addressfamily); - /* - * Try to find host. - */ - addr = name_lookup(host, port, realhost, conf, addressfamily, - rlogin->logctx, "rlogin connection"); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return dupstr(err); - } - - if (port < 0) - port = 513; /* default rlogin port */ - - /* - * Open socket. - */ - rlogin->s = new_connection(addr, *realhost, port, true, false, - nodelay, keepalive, &rlogin->plug, conf, - &rlogin->interactor); - if ((err = sk_socket_error(rlogin->s)) != NULL) - return dupstr(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 rlogin_free(Backend *be) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - - if (is_tempseat(rlogin->seat)) - tempseat_free(rlogin->seat); - if (rlogin->prompt) - free_prompts(rlogin->prompt); - if (rlogin->s) - sk_close(rlogin->s); - conf_free(rlogin->conf); - sfree(rlogin->description); - sfree(rlogin); -} - -/* - * Stub routine (we don't have any need to reconfigure this backend). - */ -static void rlogin_reconfig(Backend *be, Conf *conf) -{ -} - -static void rlogin_try_username_prompt(void *ctx) -{ - Rlogin *rlogin = (Rlogin *)ctx; - - SeatPromptResult spr = seat_get_userpass_input( - interactor_announce(&rlogin->interactor), rlogin->prompt); - if (spr.kind == SPRK_INCOMPLETE) - return; - - /* Next terminal output will come from server */ - seat_set_trust_status(rlogin->seat, false); - - /* Send the rlogin setup protocol data, and then we're ready to - * start receiving normal input to send down the wire, which - * rlogin_startup will signal to rlogin_sendok by nulling out - * rlogin->prompt. */ - rlogin_startup( - rlogin, spr, prompt_get_result_ref(rlogin->prompt->prompts[0])); -} - -/* - * Called to send data down the rlogin connection. - */ -static void rlogin_send(Backend *be, const char *buf, size_t len) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - - if (rlogin->s == NULL) - return; - - rlogin->bufsize = sk_write(rlogin->s, buf, len); -} - -/* - * Called to query the current socket sendability status. - */ -static size_t rlogin_sendbuffer(Backend *be) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - return rlogin->bufsize; -} - -/* - * Called to set the size of the window - */ -static void rlogin_size(Backend *be, int width, int height) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - 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(Backend *be, SessionSpecialCode code, int arg) -{ - /* Do nothing! */ - return; -} - -/* - * Return a list of the special codes that make sense in this - * protocol. - */ -static const SessionSpecial *rlogin_get_specials(Backend *be) -{ - return NULL; -} - -static bool rlogin_connected(Backend *be) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - return rlogin->s != NULL; -} - -static bool rlogin_sendok(Backend *be) -{ - /* - * We only want to receive input data if the socket is connected - * and we're not still at the username prompt stage. - */ - Rlogin *rlogin = container_of(be, Rlogin, backend); - return rlogin->socket_connected && !rlogin->prompt; -} - -static void rlogin_unthrottle(Backend *be, size_t backlog) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - sk_set_frozen(rlogin->s, backlog > RLOGIN_MAX_BACKLOG); -} - -static bool rlogin_ldisc(Backend *be, int option) -{ - /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ - return false; -} - -static void rlogin_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - rlogin->ldisc = ldisc; -} - -static int rlogin_exitcode(Backend *be) -{ - Rlogin *rlogin = container_of(be, Rlogin, backend); - 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(Backend *be) -{ - return 0; -} - -const BackendVtable rlogin_backend = { - .init = rlogin_init, - .free = rlogin_free, - .reconfig = rlogin_reconfig, - .send = rlogin_send, - .sendbuffer = rlogin_sendbuffer, - .size = rlogin_size, - .special = rlogin_special, - .get_specials = rlogin_get_specials, - .connected = rlogin_connected, - .exitcode = rlogin_exitcode, - .sendok = rlogin_sendok, - .ldisc_option_state = rlogin_ldisc, - .provide_ldisc = rlogin_provide_ldisc, - .unthrottle = rlogin_unthrottle, - .cfg_info = rlogin_cfg_info, - .id = "rlogin", - .displayname_tc = "Rlogin", - .displayname_lc = "Rlogin", /* proper name, so capitalise it anyway */ - .protocol = PROT_RLOGIN, - .default_port = 513, -}; diff --git a/otherbackends/supdup.c b/otherbackends/supdup.c deleted file mode 100644 index 9aaf1d4f9..000000000 --- a/otherbackends/supdup.c +++ /dev/null @@ -1,978 +0,0 @@ -/* - * Supdup backend - */ - -#include -#include -#include - -#include "putty.h" - -/* - * TTYOPT FUNCTION BITS (36-bit bitmasks) - */ -#define TOALT 0200000000000LL // Characters 0175 and 0176 are converted to altmode (0033) on input -#define TOCLC 0100000000000LL // (user option bit) Convert lower-case input to upper-case -#define TOERS 0040000000000LL // Selective erase is supported -#define TOMVB 0010000000000LL // Backspacing is supported -#define TOSAI 0004000000000LL // Stanford/ITS extended ASCII graphics character set is supported -#define TOSA1 0002000000000LL // (user option bit) Characters 0001-0037 displayed using Stanford/ITS chars -#define TOOVR 0001000000000LL // Overprinting is supported -#define TOMVU 0000400000000LL // Moving cursor upwards is supported -#define TOMOR 0000200000000LL // (user option bit) System should provide **MORE** processing -#define TOROL 0000100000000LL // (user option bit) Terminal should scroll instead of wrapping -#define TOLWR 0000020000000LL // Lowercase characters are supported -#define TOFCI 0000010000000LL // Terminal can generate CONTROL and META characters -#define TOLID 0000002000000LL // Line insert/delete operations supported -#define TOCID 0000001000000LL // Character insert/delete operations supported -#define TPCBS 0000000000040LL // Terminal is using the "intelligent terminal protocol" (must be on) -#define TPORS 0000000000010LL // Server should process output resets - -// Initialization words (36-bit constants) -#define WORDS 0777773000000 // Negative number of config words to send (6) in high 18 bits -#define TCTYP 0000000000007 // Defines the terminal type (MUST be 7) -#define TTYROL 0000000000001 // Scroll amount for terminal (1 line at a time) - - -// %TD opcodes -// -#define TDMOV 0200 // Cursor positioning -#define TDMV1 0201 // Internal cursor positioning -#define TDEOF 0202 // Erase to end of screen -#define TDEOL 0203 // Erase to end of line -#define TDDLF 0204 // Clear the character the cursor is on -#define TDCRL 0207 // Carriage return -#define TDNOP 0210 // No-op; should be ignored. -#define TDBS 0211 // Backspace (not in official SUPDUP spec) -#define TDLF 0212 // Linefeed (not in official SUPDUP spec) -#define TDCR 0213 // Carriage Return (ditto) -#define TDORS 0214 // Output reset -#define TDQOT 0215 // Quotes the following character -#define TDFS 0216 // Non-destructive forward space -#define TDMV0 0217 // General cursor positioning code -#define TDCLR 0220 // Erase the screen, home cursor -#define TDBEL 0221 // Generate an audio tone, bell, whatever -#define TDILP 0223 // Insert blank lines at the cursor -#define TDDLP 0224 // Delete lines at the cursor -#define TDICP 0225 // Insert blanks at cursor -#define TDDCP 0226 // Delete characters at cursor -#define TDBOW 0227 // Display black chars on white screen -#define TDRST 0230 // Reset %TDBOW - -/* Maximum number of octets following a %TD code. */ -#define TD_ARGS_MAX 4 - -typedef struct supdup_tag Supdup; -struct supdup_tag -{ - Socket *s; - bool socket_connected; - bool closed_on_socket_error; - - Seat *seat; - LogContext *logctx; - Ldisc *ldisc; - int term_width, term_height; - char *description; - - long long ttyopt; - long tcmxv; - long tcmxh; - - bool sent_location; - - Conf *conf; - - int bufsize; - - enum { - CONNECTING, // waiting for %TDNOP from server after sending connection params - CONNECTED // %TDNOP received, connected. - } state; - - enum { - TD_TOPLEVEL, - TD_ARGS, - TD_ARGSDONE - } tdstate; - - int td_code; - int td_argcount; - char td_args[TD_ARGS_MAX]; - int td_argindex; - - void (*print) (strbuf *outbuf, int c); - - Pinger *pinger; - - Plug plug; - Backend backend; - Interactor interactor; -}; - -#define SUPDUP_MAX_BACKLOG 4096 - -static void c_write(Supdup *supdup, unsigned char *buf, int len) -{ - size_t backlog = seat_stdout(supdup->seat, buf, len); - sk_set_frozen(supdup->s, backlog > SUPDUP_MAX_BACKLOG); -} - -static void supdup_send_location(Supdup *supdup) -{ - char locHeader[] = { 0300, 0302 }; - char* locString = conf_get_str(supdup->conf, CONF_supdup_location); - - sk_write(supdup->s, locHeader, sizeof(locHeader)); - sk_write(supdup->s, locString, strlen(locString) + 1); // include NULL terminator -} - -static void print_ascii(strbuf *outbuf, int c) -{ - /* In ASCII mode, ignore control characters. The server shouldn't - send them. */ - if (c >= 040 && c < 0177) - put_byte (outbuf, c); -} - -static void print_its(strbuf *outbuf, int c) -{ - /* The ITS character set is documented in RFC 734. */ - static const char *map[] = { - "\xc2\xb7", "\342\206\223", "\316\261", "\316\262", - "\342\210\247", "\302\254", "\316\265", "\317\200", - "\316\273", "\xce\xb3", "\xce\xb4", "\xe2\x86\x91", - "\xc2\xb1", "\xe2\x8a\x95", "\342\210\236", "\342\210\202", - "\342\212\202", "\342\212\203", "\342\210\251", "\342\210\252", - "\342\210\200", "\342\210\203", "\xe2\x8a\x97", "\342\206\224", - "\xe2\x86\x90", "\342\206\222", "\xe2\x89\xa0", "\xe2\x97\x8a", - "\342\211\244", "\342\211\245", "\342\211\241", "\342\210\250", - " ", "!", "\"", "#", "$", "%", "&", "'", - "(", ")", "*", "+", ",", "-", ".", "/", - "0", "1", "2", "3", "4", "5", "6", "7", - "8", "9", ":", ";", "<", "=", ">", "?", - "@", "A", "B", "C", "D", "E", "F", "G", - "H", "I", "J", "K", "L", "M", "N", "O", - "P", "Q", "R", "S", "T", "U", "V", "W", - "X", "Y", "Z", "[", "\\", "]", "^", "_", - "`", "a", "b", "c", "d", "e", "f", "g", - "h", "i", "j", "k", "l", "m", "n", "o", - "p", "q", "r", "s", "t", "u", "v", "w", - "x", "y", "z", "{", "|", "}", "~", "\xe2\x88\xab" - }; - - put_data (outbuf, map[c], strlen(map[c])); -} - -static void print_waits(strbuf *outbuf, int c) -{ - /* The WAITS character set used at the Stanford AI Lab is documented - here: https://www.saildart.org/allow/sail-charset-utf8.html */ - static const char *map[] = { - "", "\342\206\223", "\316\261", "\316\262", - "\342\210\247", "\302\254", "\316\265", "\317\200", - "\316\273", "", "", "", - "", "", "\342\210\236", "\342\210\202", - "\342\212\202", "\342\212\203", "\342\210\251", "\342\210\252", - "\342\210\200", "\342\210\203", "\xe2\x8a\x97", "\342\206\224", - "_", "\342\206\222", "~", "\xe2\x89\xa0", - "\342\211\244", "\342\211\245", "\342\211\241", "\342\210\250", - " ", "!", "\"", "#", "$", "%", "&", "'", - "(", ")", "*", "+", ",", "-", ".", "/", - "0", "1", "2", "3", "4", "5", "6", "7", - "8", "9", ":", ";", "<", "=", ">", "?", - "@", "A", "B", "C", "D", "E", "F", "G", - "H", "I", "J", "K", "L", "M", "N", "O", - "P", "Q", "R", "S", "T", "U", "V", "W", - "X", "Y", "Z", "[", "\\", "]", "\xe2\x86\x91", "\xe2\x86\x90", - "`", "a", "b", "c", "d", "e", "f", "g", - "h", "i", "j", "k", "l", "m", "n", "o", - "p", "q", "r", "s", "t", "u", "v", "w", - "x", "y", "z", "{", "|", "\xe2\x97\x8a", "}", "" - }; - - put_data (outbuf, map[c], strlen(map[c])); -} - -static void do_toplevel(Supdup *supdup, strbuf *outbuf, int c) -{ - // Toplevel: Waiting for a %TD code or a printable character - if (c >= 0200) { - // Handle SUPDUP %TD codes (codes greater than or equal to 200) - supdup->td_argindex = 0; - supdup->td_code = c; - switch (c) { - case TDMOV: - // %TD codes using 4 arguments - supdup->td_argcount = 4; - supdup->tdstate = TD_ARGS; - break; - - case TDMV0: - case TDMV1: - // %TD codes using 2 arguments - supdup->td_argcount = 2; - supdup->tdstate = TD_ARGS; - break; - - case TDQOT: - case TDILP: - case TDDLP: - case TDICP: - case TDDCP: - // %TD codes using 1 argument - supdup->td_argcount = 1; - supdup->tdstate = TD_ARGS; - break; - - case TDEOF: - case TDEOL: - case TDDLF: - case TDCRL: - case TDNOP: - case TDORS: - case TDFS: - case TDCLR: - case TDBEL: - case TDBOW: - case TDRST: - case TDBS: - case TDCR: - case TDLF: - // %TD codes using 0 arguments - supdup->td_argcount = 0; - supdup->tdstate = TD_ARGSDONE; - break; - - default: - // Unhandled, ignore - break; - } - } else { - supdup->print(outbuf, c); - } -} - -static void do_args(Supdup *supdup, strbuf *outbuf, int c) -{ - // Collect up args for %TD code - if (supdup->td_argindex < TD_ARGS_MAX) { - supdup->td_args[supdup->td_argindex] = c; - supdup->td_argindex++; - - if (supdup->td_argcount == supdup->td_argindex) { - // No more args, %TD code is ready to go. - supdup->tdstate = TD_ARGSDONE; - } - } else { - // Should never hit this state, if we do we will just - // return to TOPLEVEL. - supdup->tdstate = TD_TOPLEVEL; - } -} - -static void do_argsdone(Supdup *supdup, strbuf *outbuf, int c) -{ - char buf[4]; - int x, y; - - // Arguments for %TD code have been collected; dispatch based - // on the %TD code we're handling. - switch (supdup->td_code) { - case TDMOV: - /* - General cursor position code. Followed by four bytes; - the first two are the "old" vertical and horizontal - positions and may be ignored. The next two are the new - vertical and horizontal positions. The cursor should be - moved to this position. - */ - - // We only care about the new position. - put_fmt(outbuf, "\033[%d;%dH", supdup->td_args[2]+1, supdup->td_args[3]+1); - break; - - case TDMV0: - case TDMV1: - /* - General cursor position code. Followed by two bytes; - the new vertical and horizontal positions. - */ - put_fmt(outbuf, "\033[%d;%dH", supdup->td_args[0]+1, supdup->td_args[1]+1); - break; - - case TDEOF: - /* - Erase to end of screen. This is an optional function - since many terminals do not support this. If the - terminal does not support this function, it should be - treated the same as %TDEOL. - - %TDEOF does an erase to end of line, then erases all - lines lower on the screen than the cursor. The cursor - does not move. - */ - put_fmt(outbuf, "\033[J"); - break; - - case TDEOL: - /* - Erase to end of line. This erases the character - position the cursor is at and all positions to the right - on the same line. The cursor does not move. - */ - put_fmt(outbuf, "\033[K"); - break; - - case TDDLF: - /* - Clear the character position the cursor is on. The - cursor does not move. - */ - put_fmt(outbuf, "\033[X"); - break; - - case TDCRL: - /* - If the cursor is not on the bottom line of the screen, - move cursor to the beginning of the next line and clear - that line. If the cursor is at the bottom line, scroll - up. - */ - put_fmt(outbuf, "\015\012"); - break; - - case TDNOP: - /* - No-op; should be ignored. - */ - break; - - case TDORS: - /* - Output reset. This code serves as a data mark for - aborting output much as IAC DM does in the ordinary - TELNET protocol. - */ - outbuf->len = 0; - if (!seat_get_cursor_position(supdup->seat, &x, &y)) - x = y = 0; - buf[0] = 034; - buf[1] = 020; - buf[2] = y; - buf[3] = x; - sk_write(supdup->s, buf, 4); - break; - - case TDQOT: - /* - Quotes the following character. This is used when - sending 8-bit codes which are not %TD codes, for - instance when loading programs into an intelligent - terminal. The following character should be passed - through intact to the terminal. - */ - - put_byte(outbuf, supdup->td_args[0]); - break; - - case TDFS: - /* - Non-destructive forward space. The cursor moves right - one position; this code will not be sent at the end of a - line. - */ - - put_fmt(outbuf, "\033[C"); - break; - - case TDCLR: - /* - Erase the screen. Home the cursor to the top left hand - corner of the screen. - */ - put_fmt(outbuf, "\033[2J\033[H"); - break; - - case TDBEL: - /* - Generate an audio tone, bell, whatever. - */ - - put_fmt(outbuf, "\007"); - break; - - case TDILP: - /* - Insert blank lines at the cursor; followed by a byte - containing a count of the number of blank lines to - insert. The cursor is unmoved. The line the cursor is - on and all lines below it move down; lines moved off the - bottom of the screen are lost. - */ - put_fmt(outbuf, "\033[%dL", supdup->td_args[0]); - break; - - case TDDLP: - /* - Delete lines at the cursor; followed by a count. The - cursor is unmoved. The first line deleted is the one - the cursor is on. Lines below those deleted move up. - Newly- created lines at the bottom of the screen are - blank. - */ - put_fmt(outbuf, "\033[%dM", supdup->td_args[0]); - break; - - case TDICP: - /* - Insert blank character positions at the cursor; followed - by a count. The cursor is unmoved. The character the - cursor is on and all characters to the right on the - current line move to the right; characters moved off the - end of the line are lost. - */ - put_fmt(outbuf, "\033[%d@", supdup->td_args[0]); - break; - - case TDDCP: - /* - Delete characters at the cursor; followed by a count. - The cursor is unmoved. The first character deleted is - the one the cursor is on. Newly-created characters at - the end of the line are blank. - */ - put_fmt(outbuf, "\033[%dP", supdup->td_args[0]); - break; - - case TDBOW: - case TDRST: - /* - Display black characters on white screen. - HIGHLY OPTIONAL. - */ - - // Since this is HIGHLY OPTIONAL, I'm not going - // to implement it yet. - break; - - /* - * Non-standard (whatever "standard" means here) SUPDUP - * commands. These are used (at the very least) by - * Genera's SUPDUP implementation. Cannot find any - * official documentation, behavior is based on UNIX - * SUPDUP implementation from MIT. - */ - case TDBS: - /* - * Backspace -- move cursor back one character (does not - * appear to wrap...) - */ - put_byte(outbuf, '\010'); - break; - - case TDLF: - /* - * Linefeed -- move cursor down one line (again, no wrapping) - */ - put_byte(outbuf, '\012'); - break; - - case TDCR: - /* - * Carriage return -- move cursor to start of current line. - */ - put_byte(outbuf, '\015'); - break; - } - - // Return to top level to pick up the next %TD code or - // printable character. - supdup->tdstate = TD_TOPLEVEL; -} - -static void term_out_supdup(Supdup *supdup, strbuf *outbuf, int c) -{ - if (supdup->tdstate == TD_TOPLEVEL) { - do_toplevel (supdup, outbuf, c); - } else if (supdup->tdstate == TD_ARGS) { - do_args (supdup, outbuf, c); - } - - // If all arguments for a %TD code are ready, we will execute the code now. - if (supdup->tdstate == TD_ARGSDONE) { - do_argsdone (supdup, outbuf, c); - } -} - -static void do_supdup_read(Supdup *supdup, const char *buf, size_t len) -{ - strbuf *outbuf = strbuf_new(); - - while (len--) { - int c = (unsigned char)*buf++; - switch (supdup->state) { - case CONNECTING: - // "Following the transmission of the terminal options by - // the user, the server should respond with an ASCII - // greeting message, terminated with a %TDNOP code..." - if (TDNOP == c) { - // Greeting done, switch to the CONNECTED state. - supdup->state = CONNECTED; - supdup->tdstate = TD_TOPLEVEL; - } else { - // Forward the greeting message (which is straight - // ASCII, no controls) on so it gets displayed TODO: - // filter out only printable chars? - put_byte(outbuf, c); - } - break; - - case CONNECTED: - // "All transmissions from the server after the %TDNOP - // [see above] are either printing characters or virtual - // terminal display codes." Forward these on to the - // frontend which will decide what to do with them. - term_out_supdup(supdup, outbuf, c); - /* - * Hack to make Symbolics Genera SUPDUP happy: Wait until - * after we're connected (finished the initial handshake - * and have gotten additional data) before sending the - * location string. For some reason doing so earlier - * causes the Symbolics SUPDUP to end up in an odd state. - */ - if (!supdup->sent_location) { - supdup_send_location(supdup); - supdup->sent_location = true; - } - break; - } - - if (outbuf->len >= 4096) { - c_write(supdup, outbuf->u, outbuf->len); - outbuf->len = 0; - } - } - - if (outbuf->len) - c_write(supdup, outbuf->u, outbuf->len); - strbuf_free(outbuf); -} - -static void supdup_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - Supdup *supdup = container_of(plug, Supdup, plug); - backend_socket_log(supdup->seat, supdup->logctx, type, addr, port, - error_msg, error_code, - supdup->conf, supdup->socket_connected); - if (type == PLUGLOG_CONNECT_SUCCESS) { - supdup->socket_connected = true; - if (supdup->ldisc) - ldisc_check_sendok(supdup->ldisc); - } -} - -static void supdup_closing(Plug *plug, PlugCloseType type, - const char *error_msg) -{ - Supdup *supdup = container_of(plug, Supdup, 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 (supdup->s) { - sk_close(supdup->s); - supdup->s = NULL; - if (error_msg) - supdup->closed_on_socket_error = true; - seat_notify_remote_exit(supdup->seat); - seat_notify_remote_disconnect(supdup->seat); - } - if (type != PLUGCLOSE_NORMAL) { - logevent(supdup->logctx, error_msg); - if (type != PLUGCLOSE_USER_ABORT) - seat_connection_fatal(supdup->seat, "%s", error_msg); - } - /* Otherwise, the remote side closed the connection normally. */ -} - -static void supdup_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - Supdup *supdup = container_of(plug, Supdup, plug); - do_supdup_read(supdup, data, len); -} - -static void supdup_sent(Plug *plug, size_t bufsize) -{ - Supdup *supdup = container_of(plug, Supdup, plug); - supdup->bufsize = bufsize; - seat_sent(supdup->seat, supdup->bufsize); -} - -static void supdup_send_36bits(Supdup *supdup, unsigned long long thirtysix) -{ - // - // From RFC734: - // "Each word is sent through the 8-bit connection as six - // 6-bit bytes, most-significant first." - // - // Split the 36-bit word into 6 6-bit "bytes", packed into - // 8-bit bytes and send, most-significant byte first. - // - for (int i = 5; i >= 0; i--) { - char sixBits = (thirtysix >> (i * 6)) & 077; - sk_write(supdup->s, &sixBits, 1); - } -} - -static void supdup_send_config(Supdup *supdup) -{ - supdup_send_36bits(supdup, WORDS); // negative length - supdup_send_36bits(supdup, TCTYP); // terminal type - supdup_send_36bits(supdup, supdup->ttyopt); // options - supdup_send_36bits(supdup, supdup->tcmxv); // height - supdup_send_36bits(supdup, supdup->tcmxh); // width - supdup_send_36bits(supdup, TTYROL); // scroll amount -} - -static char *supdup_description(Interactor *itr) -{ - Supdup *supdup = container_of(itr, Supdup, interactor); - return dupstr(supdup->description); -} - -static LogPolicy *supdup_logpolicy(Interactor *itr) -{ - Supdup *supdup = container_of(itr, Supdup, interactor); - return log_get_policy(supdup->logctx); -} - -static Seat *supdup_get_seat(Interactor *itr) -{ - Supdup *supdup = container_of(itr, Supdup, interactor); - return supdup->seat; -} - -static void supdup_set_seat(Interactor *itr, Seat *seat) -{ - Supdup *supdup = container_of(itr, Supdup, interactor); - supdup->seat = seat; -} - -static const InteractorVtable Supdup_interactorvt = { - .description = supdup_description, - .logpolicy = supdup_logpolicy, - .get_seat = supdup_get_seat, - .set_seat = supdup_set_seat, -}; - -/* - * Called to set up the Supdup 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 char *supdup_init(const BackendVtable *x, Seat *seat, - Backend **backend_handle, - LogContext *logctx, Conf *conf, - const char *host, int port, char **realhost, - bool nodelay, bool keepalive) -{ - static const PlugVtable fn_table = { - .log = supdup_log, - .closing = supdup_closing, - .receive = supdup_receive, - .sent = supdup_sent, - }; - SockAddr *addr; - const char *err; - Supdup *supdup; - char *loghost; - int addressfamily; - const char *utf8 = "\033%G"; - - supdup = snew(struct supdup_tag); - memset(supdup, 0, sizeof(Supdup)); - supdup->plug.vt = &fn_table; - supdup->backend.vt = &supdup_backend; - supdup->interactor.vt = &Supdup_interactorvt; - supdup->backend.interactor = &supdup->interactor; - supdup->logctx = logctx; - supdup->conf = conf_copy(conf); - supdup->s = NULL; - supdup->socket_connected = false; - supdup->closed_on_socket_error = false; - supdup->seat = seat; - supdup->term_width = conf_get_int(supdup->conf, CONF_width); - supdup->term_height = conf_get_int(supdup->conf, CONF_height); - supdup->pinger = NULL; - supdup->sent_location = false; - supdup->description = default_description(supdup->backend.vt, host, port); - *backend_handle = &supdup->backend; - - switch (conf_get_int(supdup->conf, CONF_supdup_ascii_set)) { - case SUPDUP_CHARSET_ASCII: - supdup->print = print_ascii; - break; - case SUPDUP_CHARSET_ITS: - supdup->print = print_its; - break; - case SUPDUP_CHARSET_WAITS: - supdup->print = print_waits; - break; - } - - /* - * Try to find host. - */ - { - char *buf; - addressfamily = conf_get_int(supdup->conf, CONF_addressfamily); - buf = dupprintf("Looking up host \"%s\"%s", host, - (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : - (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : - ""))); - logevent(supdup->logctx, buf); - sfree(buf); - } - addr = name_lookup(host, port, realhost, supdup->conf, addressfamily, NULL, ""); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return dupstr(err); - } - - if (port < 0) - port = 0137; /* default supdup port */ - - /* - * Open socket. - */ - supdup->s = new_connection(addr, *realhost, port, false, true, - nodelay, keepalive, &supdup->plug, supdup->conf, - &supdup->interactor); - if ((err = sk_socket_error(supdup->s)) != NULL) - return dupstr(err); - - supdup->pinger = pinger_new(supdup->conf, &supdup->backend); - - /* - * We can send special commands from the start. - */ - seat_update_specials_menu(supdup->seat); - - /* - * loghost overrides realhost, if specified. - */ - loghost = conf_get_str(supdup->conf, CONF_loghost); - if (*loghost) { - char *colon; - - sfree(*realhost); - *realhost = dupstr(loghost); - - colon = host_strrchr(*realhost, ':'); - if (colon) - *colon++ = '\0'; - } - - /* - * Set up TTYOPTS based on config - */ - int ascii_set = conf_get_int(supdup->conf, CONF_supdup_ascii_set); - int more_processing = conf_get_bool(supdup->conf, CONF_supdup_more); - int scrolling = conf_get_bool(supdup->conf, CONF_supdup_scroll); - supdup->ttyopt = - TOERS | - TOMVB | - (ascii_set == SUPDUP_CHARSET_ASCII ? 0 : TOSAI | TOSA1) | - TOMVU | - TOLWR | - TOLID | - TOCID | - TPCBS | - (scrolling ? TOROL : 0) | - (more_processing ? TOMOR : 0) | - TPORS; - - supdup->tcmxh = supdup->term_width - 1; // -1 "..one column is used to indicate line continuation." - supdup->tcmxv = supdup->term_height; - - /* - * Send our configuration words to the server - */ - supdup_send_config(supdup); - - /* - * We next expect a connection message followed by %TDNOP from the server - */ - supdup->state = CONNECTING; - seat_set_trust_status(supdup->seat, false); - - /* Make sure the terminal is in UTF-8 mode. */ - c_write(supdup, (unsigned char *)utf8, strlen(utf8)); - - return NULL; -} - - -static void supdup_free(Backend *be) -{ - Supdup *supdup = container_of(be, Supdup, backend); - - if (is_tempseat(supdup->seat)) - tempseat_free(supdup->seat); - if (supdup->s) - sk_close(supdup->s); - if (supdup->pinger) - pinger_free(supdup->pinger); - conf_free(supdup->conf); - sfree(supdup->description); - sfree(supdup); -} - -/* - * Reconfigure the Supdup backend. - */ -static void supdup_reconfig(Backend *be, Conf *conf) -{ - /* Nothing to do; SUPDUP cannot be reconfigured while running. */ -} - -/* - * Called to send data down the Supdup connection. - */ -static void supdup_send(Backend *be, const char *buf, size_t len) -{ - Supdup *supdup = container_of(be, Supdup, backend); - char c; - int i; - - if (supdup->s == NULL) - return; - - for (i = 0; i < len; i++) { - if (buf[i] == 034) - supdup->bufsize = sk_write(supdup->s, "\034\034", 2); - else { - c = buf[i] & 0177; - supdup->bufsize = sk_write(supdup->s, &c, 1); - } - } -} - -/* - * Called to query the current socket sendability status. - */ -static size_t supdup_sendbuffer(Backend *be) -{ - Supdup *supdup = container_of(be, Supdup, backend); - return supdup->bufsize; -} - -/* - * Called to set the size of the window from Supdup's POV. - */ -static void supdup_size(Backend *be, int width, int height) -{ - Supdup *supdup = container_of(be, Supdup, backend); - - supdup->term_width = width; - supdup->term_height = height; - - // - // SUPDUP does not support resizing the terminal after connection - // establishment. - // -} - -/* - * Send Telnet special codes. - */ -static void supdup_special(Backend *be, SessionSpecialCode code, int arg) -{ -} - -static const SessionSpecial *supdup_get_specials(Backend *be) -{ - return NULL; -} - -static bool supdup_connected(Backend *be) -{ - Supdup *supdup = container_of(be, Supdup, backend); - return supdup->s != NULL; -} - -static bool supdup_sendok(Backend *be) -{ - Supdup *supdup = container_of(be, Supdup, backend); - return supdup->socket_connected; -} - -static void supdup_unthrottle(Backend *be, size_t backlog) -{ - Supdup *supdup = container_of(be, Supdup, backend); - sk_set_frozen(supdup->s, backlog > SUPDUP_MAX_BACKLOG); -} - -static bool supdup_ldisc(Backend *be, int option) -{ - /* No support for echoing or local editing. */ - return false; -} - -static void supdup_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - Supdup *supdup = container_of(be, Supdup, backend); - supdup->ldisc = ldisc; -} - -static int supdup_exitcode(Backend *be) -{ - Supdup *supdup = container_of(be, Supdup, backend); - if (supdup->s != NULL) - return -1; /* still connected */ - else if (supdup->closed_on_socket_error) - return INT_MAX; /* a socket error counts as an unclean exit */ - else - /* Supdup doesn't transmit exit codes back to the client */ - return 0; -} - -/* - * cfg_info for Supdup does nothing at all. - */ -static int supdup_cfg_info(Backend *be) -{ - return 0; -} - -const BackendVtable supdup_backend = { - .init = supdup_init, - .free = supdup_free, - .reconfig = supdup_reconfig, - .send = supdup_send, - .sendbuffer = supdup_sendbuffer, - .size = supdup_size, - .special = supdup_special, - .get_specials = supdup_get_specials, - .connected = supdup_connected, - .exitcode = supdup_exitcode, - .sendok = supdup_sendok, - .ldisc_option_state = supdup_ldisc, - .provide_ldisc = supdup_provide_ldisc, - .unthrottle = supdup_unthrottle, - .cfg_info = supdup_cfg_info, - .id = "supdup", - .displayname_tc = "SUPDUP", - .displayname_lc = "SUPDUP", /* proper name, so capitalise it anyway */ - .protocol = PROT_SUPDUP, - .default_port = 0137, - .flags = BACKEND_RESIZE_FORBIDDEN | BACKEND_NEEDS_TERMINAL, -}; diff --git a/otherbackends/telnet.c b/otherbackends/telnet.c deleted file mode 100644 index f43520a3e..000000000 --- a/otherbackends/telnet.c +++ /dev/null @@ -1,1106 +0,0 @@ -/* - * Telnet backend. - */ - -#include -#include -#include - -#include "putty.h" - -#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 const char *telopt(int opt) -{ -#define telnet_str(x,y) case TELOPT_##x: return #x; - switch (opt) { - TELOPTS(telnet_str) - default: - return ""; - } -#undef telnet_str -} - -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 Telnet; -struct Telnet { - Socket *s; - bool socket_connected; - bool closed_on_socket_error; - - Seat *seat; - LogContext *logctx; - Ldisc *ldisc; - int term_width, term_height; - char *description; - - int opt_states[NUM_OPTS]; - - bool echoing, editing; - bool activated; - size_t bufsize; - bool in_synch; - int sb_opt; - strbuf *sb_buf; - - enum { - TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, - SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR - } state; - - Conf *conf; - - Pinger *pinger; - - Plug plug; - Backend backend; - Interactor interactor; -}; - -#define TELNET_MAX_BACKLOG 4096 - -#define SB_DELTA 1024 - -static void c_write(Telnet *telnet, const void *buf, size_t len) -{ - size_t backlog = seat_stdout(telnet->seat, buf, len); - sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); -} - -static void log_option(Telnet *telnet, const char *sender, int cmd, int option) -{ - /* - * The strange-looking "" below is there to avoid a - * trigraph - a double question mark followed by > maps to a - * closing brace character! - */ - logeventf(telnet->logctx, "%s negotiation: %s %s", sender, - (cmd == WILL ? "WILL" : cmd == WONT ? "WONT" : - cmd == DO ? "DO" : cmd == DONT ? "DONT" : ""), - telopt(option)); -} - -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, 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, bool 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_echoedit_update(telnet->ldisc); - - /* 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) - backend_size(&telnet->backend, - 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, true); -} - -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, false); -} - -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, false); - 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 *p, *q; - int var, value; - - switch (telnet->sb_opt) { - case TELOPT_TSPEED: - if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { - char *termspeed = conf_get_str(telnet->conf, CONF_termspeed); - strbuf *sb = strbuf_new(); - put_byte(sb, IAC); - put_byte(sb, SB); - put_byte(sb, TELOPT_TSPEED); - put_byte(sb, TELQUAL_IS); - put_datapl(sb, ptrlen_from_asciz(termspeed)); - put_byte(sb, IAC); - put_byte(sb, SE); - telnet->bufsize = sk_write(telnet->s, sb->s, sb->len); - logevent(telnet->logctx, "server subnegotiation: SB TSPEED SEND"); - logeventf(telnet->logctx, - "client subnegotiation: SB TSPEED IS %s", termspeed); - strbuf_free(sb); - } else - logevent(telnet->logctx, - "server subnegotiation: SB TSPEED "); - break; - case TELOPT_TTYPE: - if (telnet->sb_buf->len == 1 && telnet->sb_buf->u[0] == TELQUAL_SEND) { - char *termtype = conf_get_str(telnet->conf, CONF_termtype); - strbuf *sb = strbuf_new(); - put_byte(sb, IAC); - put_byte(sb, SB); - put_byte(sb, TELOPT_TTYPE); - put_byte(sb, TELQUAL_IS); - size_t tt_start = sb->len; - for (size_t n = 0; termtype[n]; n++) - put_byte(sb, (termtype[n] >= 'a' && termtype[n] <= 'z' ? - termtype[n] + 'A' - 'a' : termtype[n])); - size_t tt_end = sb->len; - put_byte(sb, IAC); - put_byte(sb, SE); - telnet->bufsize = sk_write(telnet->s, sb->s, sb->len); - strbuf_shrink_to(sb, tt_end); - logevent(telnet->logctx, "server subnegotiation: SB TTYPE SEND"); - logeventf(telnet->logctx, "client subnegotiation: SB TTYPE IS %s", - sb->s + tt_start); - strbuf_free(sb); - } else - logevent(telnet->logctx, - "server subnegotiation: SB TTYPE \r\n"); - break; - case TELOPT_OLD_ENVIRON: - case TELOPT_NEW_ENVIRON: - p = telnet->sb_buf->u; - q = p + telnet->sb_buf->len; - if (p < q && *p == TELQUAL_SEND) { - p++; - logeventf(telnet->logctx, "server subnegotiation: SB %s SEND", - telopt(telnet->sb_opt)); - if (telnet->sb_opt == TELOPT_OLD_ENVIRON) { - if (conf_get_bool(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; - } - - strbuf *sb = strbuf_new(); - put_byte(sb, IAC); - put_byte(sb, SB); - put_byte(sb, telnet->sb_opt); - put_byte(sb, TELQUAL_IS); - char *ekey, *eval; - 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)) { - put_byte(sb, var); - put_datapl(sb, ptrlen_from_asciz(ekey)); - put_byte(sb, value); - put_datapl(sb, ptrlen_from_asciz(eval)); - } - char *user = get_remote_username(telnet->conf); - if (user) { - put_byte(sb, var); - put_datalit(sb, "USER"); - put_byte(sb, value); - put_datapl(sb, ptrlen_from_asciz(user)); - } - put_byte(sb, IAC); - put_byte(sb, SE); - telnet->bufsize = sk_write(telnet->s, sb->s, sb->len); - if (sb->len == 6) { - logeventf(telnet->logctx, - "client subnegotiation: SB %s IS ", - telopt(telnet->sb_opt)); - } else { - logeventf(telnet->logctx, "client subnegotiation: SB %s IS:", - telopt(telnet->sb_opt)); - 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)) { - logeventf(telnet->logctx, " %s=%s", ekey, eval); - } - if (user) - logeventf(telnet->logctx, " USER=%s", user); - } - strbuf_free(sb); - sfree(user); - } - break; - } -} - -static void do_telnet_read(Telnet *telnet, const char *buf, size_t len) -{ - strbuf *outbuf = strbuf_new_nm(); - - 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) - put_byte(outbuf, 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 = false; -#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 = false; - telnet->state = TOP_LEVEL; - } else { - /* ignore everything else; print it if it's IAC */ - if (c == IAC) { - put_byte(outbuf, 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; - strbuf_clear(telnet->sb_buf); - telnet->state = SUBNEGOT; - break; - case SUBNEGOT: - if (c == IAC) - telnet->state = SUBNEG_IAC; - else { - subneg_addchar: - put_byte(telnet->sb_buf, 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 (outbuf->len >= 4096) { - c_write(telnet, outbuf->u, outbuf->len); - strbuf_clear(outbuf); - } - } - - if (outbuf->len) - c_write(telnet, outbuf->u, outbuf->len); - strbuf_free(outbuf); -} - -static void telnet_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - Telnet *telnet = container_of(plug, Telnet, plug); - backend_socket_log(telnet->seat, telnet->logctx, type, addr, port, - error_msg, error_code, telnet->conf, - telnet->socket_connected); - if (type == PLUGLOG_CONNECT_SUCCESS) { - telnet->socket_connected = true; - if (telnet->ldisc) - ldisc_check_sendok(telnet->ldisc); - } -} - -static void telnet_closing(Plug *plug, PlugCloseType type, - const char *error_msg) -{ - Telnet *telnet = container_of(plug, 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; - seat_notify_remote_exit(telnet->seat); - seat_notify_remote_disconnect(telnet->seat); - } - if (type != PLUGCLOSE_NORMAL) { - logevent(telnet->logctx, error_msg); - if (type != PLUGCLOSE_USER_ABORT) - seat_connection_fatal(telnet->seat, "%s", error_msg); - } - /* Otherwise, the remote side closed the connection normally. */ -} - -static void telnet_receive( - Plug *plug, int urgent, const char *data, size_t len) -{ - Telnet *telnet = container_of(plug, Telnet, plug); - if (urgent) - telnet->in_synch = true; - do_telnet_read(telnet, data, len); -} - -static void telnet_sent(Plug *plug, size_t bufsize) -{ - Telnet *telnet = container_of(plug, Telnet, plug); - telnet->bufsize = bufsize; - seat_sent(telnet->seat, telnet->bufsize); -} - -static const PlugVtable Telnet_plugvt = { - .log = telnet_log, - .closing = telnet_closing, - .receive = telnet_receive, - .sent = telnet_sent, -}; - -static char *telnet_description(Interactor *itr) -{ - Telnet *telnet = container_of(itr, Telnet, interactor); - return dupstr(telnet->description); -} - -static LogPolicy *telnet_logpolicy(Interactor *itr) -{ - Telnet *telnet = container_of(itr, Telnet, interactor); - return log_get_policy(telnet->logctx); -} - -static Seat *telnet_get_seat(Interactor *itr) -{ - Telnet *telnet = container_of(itr, Telnet, interactor); - return telnet->seat; -} - -static void telnet_set_seat(Interactor *itr, Seat *seat) -{ - Telnet *telnet = container_of(itr, Telnet, interactor); - telnet->seat = seat; -} - -static const InteractorVtable Telnet_interactorvt = { - .description = telnet_description, - .logpolicy = telnet_logpolicy, - .get_seat = telnet_get_seat, - .set_seat = telnet_set_seat, -}; - -/* - * 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 char *telnet_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - SockAddr *addr; - const char *err; - Telnet *telnet; - char *loghost; - int addressfamily; - - telnet = snew(Telnet); - memset(telnet, 0, sizeof(Telnet)); - telnet->plug.vt = &Telnet_plugvt; - telnet->backend.vt = vt; - telnet->interactor.vt = &Telnet_interactorvt; - telnet->backend.interactor = &telnet->interactor; - telnet->conf = conf_copy(conf); - telnet->s = NULL; - telnet->socket_connected = false; - telnet->closed_on_socket_error = false; - telnet->echoing = true; - telnet->editing = true; - telnet->activated = false; - telnet->sb_buf = strbuf_new(); - telnet->seat = seat; - telnet->logctx = logctx; - 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; - telnet->description = default_description(vt, host, port); - *backend_handle = &telnet->backend; - - /* - * Try to find host. - */ - addressfamily = conf_get_int(telnet->conf, CONF_addressfamily); - addr = name_lookup(host, port, realhost, telnet->conf, addressfamily, - telnet->logctx, "Telnet connection"); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return dupstr(err); - } - - if (port < 0) - port = 23; /* default telnet port */ - - /* - * Open socket. - */ - telnet->s = new_connection(addr, *realhost, port, false, true, nodelay, - keepalive, &telnet->plug, telnet->conf, - &telnet->interactor); - if ((err = sk_socket_error(telnet->s)) != NULL) - return dupstr(err); - - /* No local authentication phase in this protocol */ - seat_set_trust_status(telnet->seat, false); - - telnet->pinger = pinger_new(telnet->conf, &telnet->backend); - - /* - * Initialise option states. - */ - if (conf_get_bool(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. - */ - seat_update_specials_menu(telnet->seat); - - /* - * 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(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - - if (is_tempseat(telnet->seat)) - tempseat_free(telnet->seat); - strbuf_free(telnet->sb_buf); - if (telnet->s) - sk_close(telnet->s); - if (telnet->pinger) - pinger_free(telnet->pinger); - conf_free(telnet->conf); - sfree(telnet->description); - 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(Backend *be, Conf *conf) -{ - Telnet *telnet = container_of(be, Telnet, backend); - 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 void telnet_send(Backend *be, const char *buf, size_t len) -{ - Telnet *telnet = container_of(be, Telnet, backend); - 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; - - 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, q, p - q); - - while (p < end && !iswritable(*p)) { - telnet->bufsize = - sk_write(telnet->s, *p == IAC ? iac : cr, 2); - p++; - } - } -} - -/* - * Called to query the current socket sendability status. - */ -static size_t telnet_sendbuffer(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - return telnet->bufsize; -} - -/* - * Called to set the size of the window from Telnet's POV. - */ -static void telnet_size(Backend *be, int width, int height) -{ - Telnet *telnet = container_of(be, Telnet, backend); - unsigned char b[24]; - int n; - - 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, b, n); - logeventf(telnet->logctx, "client subnegotiation: SB NAWS %d,%d", - telnet->term_width, telnet->term_height); -} - -/* - * Send Telnet special codes. - */ -static void telnet_special(Backend *be, SessionSpecialCode code, int arg) -{ - Telnet *telnet = container_of(be, Telnet, backend); - unsigned char b[2]; - - if (telnet->s == NULL) - return; - - b[0] = IAC; - switch (code) { - case SS_AYT: - b[1] = AYT; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_BRK: - b[1] = BREAK; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EC: - b[1] = EC; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EL: - b[1] = EL; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_GA: - b[1] = GA; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_NOP: - b[1] = NOP; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_ABORT: - b[1] = ABORT; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_AO: - b[1] = AO; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_IP: - b[1] = IP; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_SUSP: - b[1] = SUSP; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EOR: - b[1] = EOR; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_EOF: - b[1] = xEOF; - telnet->bufsize = sk_write(telnet->s, b, 2); - break; - case SS_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 SS_SYNCH: - b[1] = DM; - telnet->bufsize = sk_write(telnet->s, b, 1); - telnet->bufsize = sk_write_oob(telnet->s, b + 1, 1); - break; - case SS_PING: - if (telnet->opt_states[o_they_sga.index] == ACTIVE) { - b[1] = NOP; - telnet->bufsize = sk_write(telnet->s, b, 2); - } - break; - default: - break; /* never heard of it */ - } -} - -static const SessionSpecial *telnet_get_specials(Backend *be) -{ - static const SessionSpecial specials[] = { - {"Are You There", SS_AYT}, - {"Break", SS_BRK}, - {"Synch", SS_SYNCH}, - {"Erase Character", SS_EC}, - {"Erase Line", SS_EL}, - {"Go Ahead", SS_GA}, - {"No Operation", SS_NOP}, - {NULL, SS_SEP}, - {"Abort Process", SS_ABORT}, - {"Abort Output", SS_AO}, - {"Interrupt Process", SS_IP}, - {"Suspend Process", SS_SUSP}, - {NULL, SS_SEP}, - {"End Of Record", SS_EOR}, - {"End Of File", SS_EOF}, - {NULL, SS_EXITMENU} - }; - return specials; -} - -static bool telnet_connected(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - return telnet->s != NULL; -} - -static bool telnet_sendok(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - return telnet->socket_connected; -} - -static void telnet_unthrottle(Backend *be, size_t backlog) -{ - Telnet *telnet = container_of(be, Telnet, backend); - sk_set_frozen(telnet->s, backlog > TELNET_MAX_BACKLOG); -} - -static bool telnet_ldisc(Backend *be, int option) -{ - Telnet *telnet = container_of(be, Telnet, backend); - if (option == LD_ECHO) - return telnet->echoing; - if (option == LD_EDIT) - return telnet->editing; - return false; -} - -static void telnet_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - Telnet *telnet = container_of(be, Telnet, backend); - telnet->ldisc = ldisc; -} - -static int telnet_exitcode(Backend *be) -{ - Telnet *telnet = container_of(be, Telnet, backend); - 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(Backend *be) -{ - return 0; -} - -const BackendVtable telnet_backend = { - .init = telnet_init, - .free = telnet_free, - .reconfig = telnet_reconfig, - .send = telnet_send, - .sendbuffer = telnet_sendbuffer, - .size = telnet_size, - .special = telnet_special, - .get_specials = telnet_get_specials, - .connected = telnet_connected, - .exitcode = telnet_exitcode, - .sendok = telnet_sendok, - .ldisc_option_state = telnet_ldisc, - .provide_ldisc = telnet_provide_ldisc, - .unthrottle = telnet_unthrottle, - .cfg_info = telnet_cfg_info, - .id = "telnet", - .displayname_tc = "Telnet", - .displayname_lc = "Telnet", /* proper name, so capitalise it anyway */ - .protocol = PROT_TELNET, - .default_port = 23, -}; diff --git a/otherbackends/testback.c b/otherbackends/testback.c deleted file mode 100644 index f46d1d989..000000000 --- a/otherbackends/testback.c +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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 char *loop_init(const BackendVtable *, Seat *, Backend **, LogContext *, - Conf *, const char *, int, char **, bool, bool); -static void loop_free(Backend *); -static void null_reconfig(Backend *, Conf *); -static void null_send(Backend *, const char *, size_t); -static void loop_send(Backend *, const char *, size_t); -static size_t null_sendbuffer(Backend *); -static size_t loop_sendbuffer(Backend *); -static void null_size(Backend *, int, int); -static void null_special(Backend *, SessionSpecialCode, int); -static const SessionSpecial *null_get_specials(Backend *); -static bool null_connected(Backend *); -static int null_exitcode(Backend *); -static bool null_sendok(Backend *); -static bool null_ldisc(Backend *, int); -static void null_provide_ldisc(Backend *, Ldisc *); -static void null_unthrottle(Backend *, size_t); -static int null_cfg_info(Backend *); - -const BackendVtable null_backend = { - .init = loop_init, - .free = loop_free, - .reconfig = null_reconfig, - .send = null_send, - .sendbuffer = null_sendbuffer, - .size = null_size, - .special = null_special, - .get_specials = null_get_specials, - .connected = null_connected, - .exitcode = null_exitcode, - .sendok = null_sendok, - .ldisc_option_state = null_ldisc, - .provide_ldisc = null_provide_ldisc, - .unthrottle = null_unthrottle, - .cfg_info = null_cfg_info, - .id = "null", - .displayname_tc = "Null", - .displayname_lc = "null", - .protocol = -1, - .default_port = 0, -}; - -const BackendVtable loop_backend = { - .init = loop_init, - .free = loop_free, - .reconfig = null_reconfig, - .send = loop_send, - .sendbuffer = loop_sendbuffer, - .size = null_size, - .special = null_special, - .get_specials = null_get_specials, - .connected = null_connected, - .exitcode = null_exitcode, - .sendok = null_sendok, - .ldisc_option_state = null_ldisc, - .provide_ldisc = null_provide_ldisc, - .unthrottle = null_unthrottle, - .cfg_info = null_cfg_info, - .id = "loop", - .displayname_tc = "Loop", - .displayname_lc = "loop", - .protocol = -1, - .default_port = 0, -}; - -struct loop_state { - Seat *seat; - Backend backend; - size_t sendbuffer; -}; - -static char *loop_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) { - struct loop_state *st = snew(struct loop_state); - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - st->seat = seat; - st->backend.vt = vt; - *backend_handle = &st->backend; - - *realhost = dupstr(host); - - return NULL; -} - -static void loop_free(Backend *be) -{ - struct loop_state *st = container_of(be, struct loop_state, backend); - - sfree(st); -} - -static void null_reconfig(Backend *be, Conf *conf) { - -} - -static void null_send(Backend *be, const char *buf, size_t len) { - -} - -static void loop_send(Backend *be, const char *buf, size_t len) { - struct loop_state *st = container_of(be, struct loop_state, backend); - - st->sendbuffer = seat_output(st->seat, 0, buf, len); -} - -static size_t null_sendbuffer(Backend *be) { - - return 0; -} - -static size_t loop_sendbuffer(Backend *be) { - struct loop_state *st = container_of(be, struct loop_state, backend); - - return st->sendbuffer; -} - -static void null_size(Backend *be, int width, int height) { - -} - -static void null_special(Backend *be, SessionSpecialCode code, int arg) { - -} - -static const SessionSpecial *null_get_specials (Backend *be) { - - return NULL; -} - -static bool null_connected(Backend *be) { - - return false; -} - -static int null_exitcode(Backend *be) { - - return 0; -} - -static bool null_sendok(Backend *be) { - - return true; -} - -static void null_unthrottle(Backend *be, size_t backlog) { - -} - -static bool null_ldisc(Backend *be, int option) { - - return false; -} - -static void null_provide_ldisc (Backend *be, Ldisc *ldisc) { - -} - -static int null_cfg_info(Backend *be) -{ - return 0; -} diff --git a/pageant.c b/pageant.c deleted file mode 100644 index 455e434fb..000000000 --- a/pageant.c +++ /dev/null @@ -1,2691 +0,0 @@ -/* - * pageant.c: cross-platform code to implement Pageant. - */ - -#include -#include -#include - -#include "putty.h" -#include "mpint.h" -#include "ssh.h" -#include "sshcr.h" -#include "pageant.h" - -/* - * We need this to link with the RSA code, because rsa_ssh1_encrypt() - * pads its data with random bytes. Since we only use rsa_ssh1_decrypt() - * and the signing functions, which are deterministic, this should - * never be called. - * - * If it _is_ called, there is a _serious_ problem, because it - * won't generate true random numbers. So we must scream, panic, - * and exit immediately if that should happen. - */ -void random_read(void *buf, size_t size) -{ - modalfatalbox("Internal error: attempt to use random numbers in Pageant"); -} - -static bool pageant_local = false; - -struct PageantClientDialogId { - int dummy; -}; - -typedef struct PageantPrivateKeySort PageantPrivateKeySort; -typedef struct PageantPublicKeySort PageantPublicKeySort; -typedef struct PageantPrivateKey PageantPrivateKey; -typedef struct PageantPublicKey PageantPublicKey; -typedef struct PageantAsyncOp PageantAsyncOp; -typedef struct PageantAsyncOpVtable PageantAsyncOpVtable; -typedef struct PageantClientRequestNode PageantClientRequestNode; -typedef struct PageantKeyRequestNode PageantKeyRequestNode; - -struct PageantClientRequestNode { - PageantClientRequestNode *prev, *next; -}; -struct PageantKeyRequestNode { - PageantKeyRequestNode *prev, *next; -}; - -struct PageantClientInfo { - PageantClient *pc; /* goes to NULL when client is unregistered */ - PageantClientRequestNode head; -}; - -struct PageantAsyncOp { - const PageantAsyncOpVtable *vt; - PageantClientInfo *info; - PageantClientRequestNode cr; - PageantClientRequestId *reqid; -}; -struct PageantAsyncOpVtable { - void (*coroutine)(PageantAsyncOp *pao); - void (*free)(PageantAsyncOp *pao); -}; -static inline void pageant_async_op_coroutine(PageantAsyncOp *pao) -{ pao->vt->coroutine(pao); } -static inline void pageant_async_op_free(PageantAsyncOp *pao) -{ - delete_callbacks_for_context(pao); - pao->vt->free(pao); -} -static inline void pageant_async_op_unlink(PageantAsyncOp *pao) -{ - pao->cr.prev->next = pao->cr.next; - pao->cr.next->prev = pao->cr.prev; -} -static inline void pageant_async_op_unlink_and_free(PageantAsyncOp *pao) -{ - pageant_async_op_unlink(pao); - pageant_async_op_free(pao); -} -static void pageant_async_op_callback(void *vctx) -{ - pageant_async_op_coroutine((PageantAsyncOp *)vctx); -} - -/* - * Master lists of all the keys we have stored, in any form at all. - * - * We store private and public keys in separate lists, because - * multiple public keys can share the same private key (due to one - * having a certificate and the other not, or having more than one - * different certificate). And when we decrypt or re-encrypt a private - * key, we don't really want to faff about doing it multiple times if - * there's more than one public key it goes with. If someone tries to - * re-encrypt a key to make their machine safer against unattended - * access, then it would be embarrassing to find they'd forgotten to - * re-encrypt the _other_ copy of it; conversely, once you've - * decrypted a key, it's pointless to make someone type yet another - * passphrase. - * - * (Causing multiple keys to become decrypted in one go isn't a - * security hole in its own right, because the signatures generated by - * certified and uncertified keys are identical. So an attacker - * gaining access to an agent containing one encrypted and one - * cleartext key with the same private half would still be *able* to - * generate signatures that went with the encrypted one, even if the - * agent refused to hand them out in response to the most obvious kind - * of request.) - */ -struct PageantPrivateKeySort { - /* - * Information used by the sorting criterion for the private key - * tree. - */ - int ssh_version; /* 1 or 2; primary sort key */ - ptrlen base_pub; /* secondary sort key; never includes a certificate */ -}; -static int privkey_cmpfn(void *av, void *bv) -{ - PageantPrivateKeySort *a = (PageantPrivateKeySort *)av; - PageantPrivateKeySort *b = (PageantPrivateKeySort *)bv; - - if (a->ssh_version != b->ssh_version) - return a->ssh_version < b->ssh_version ? -1 : +1; - else - return ptrlen_strcmp(a->base_pub, b->base_pub); -} - -struct PageantPublicKeySort { - /* - * Information used by the sorting criterion for the public key - * tree. Begins with the private key sorting criterion, so that - * all the public keys sharing a private key appear adjacent in - * the tree. That's a reasonably sensible order to list them in - * for the user, and more importantly, it makes it easy to - * discover when we're deleting the last public key that goes with - * a particular private one, so as to delete that too. Easier than - * messing about with fragile reference counts. - */ - PageantPrivateKeySort priv; - ptrlen full_pub; /* may match priv.base_pub, or may include a cert */ -}; -static int pubkey_cmpfn(void *av, void *bv) -{ - PageantPublicKeySort *a = (PageantPublicKeySort *)av; - PageantPublicKeySort *b = (PageantPublicKeySort *)bv; - - int c = privkey_cmpfn(&a->priv, &b->priv); - if (c) - return c; - else - return ptrlen_strcmp(a->full_pub, b->full_pub); -} - -struct PageantPrivateKey { - PageantPrivateKeySort sort; - strbuf *base_pub; /* the true owner of sort.base_pub */ - union { - RSAKey *rkey; /* if sort.priv.ssh_version == 1 */ - ssh_key *skey; /* if sort.priv.ssh_version == 2 */ - }; - strbuf *encrypted_key_file; - /* encrypted_key_comment stores the comment belonging to the - * encrypted key file. This is used when presenting deferred - * decryption prompts, because if the user had encrypted their - * uncert and cert keys with different passphrases, the passphrase - * prompt must reliably signal which file they're supposed to be - * entering the passphrase for. */ - char *encrypted_key_comment; - bool decryption_prompt_active; - PageantKeyRequestNode blocked_requests; - PageantClientDialogId dlgid; -}; -static tree234 *privkeytree; - -struct PageantPublicKey { - PageantPublicKeySort sort; - strbuf *base_pub; /* the true owner of sort.priv.base_pub */ - strbuf *full_pub; /* the true owner of sort.full_pub */ - char *comment; -}; -static tree234 *pubkeytree; - -typedef struct PageantSignOp PageantSignOp; -struct PageantSignOp { - PageantPrivateKey *priv; - strbuf *data_to_sign; - unsigned flags; - int crLine; - unsigned char failure_type; - - PageantKeyRequestNode pkr; - PageantAsyncOp pao; -}; - -/* Master lock that indicates whether a GUI request is currently in - * progress */ -static bool gui_request_in_progress = false; -static PageantKeyRequestNode requests_blocked_on_gui = - { &requests_blocked_on_gui, &requests_blocked_on_gui }; - -static void failure(PageantClient *pc, PageantClientRequestId *reqid, - strbuf *sb, unsigned char type, const char *fmt, ...); -static void fail_requests_for_key(PageantPrivateKey *priv, const char *reason); -static PageantPublicKey *pageant_nth_pubkey(int ssh_version, int i); - -static void pk_priv_free(PageantPrivateKey *priv) -{ - if (priv->base_pub) - strbuf_free(priv->base_pub); - if (priv->sort.ssh_version == 1 && priv->rkey) { - freersakey(priv->rkey); - sfree(priv->rkey); - } - if (priv->sort.ssh_version == 2 && priv->skey) { - ssh_key_free(priv->skey); - } - if (priv->encrypted_key_file) - strbuf_free(priv->encrypted_key_file); - if (priv->encrypted_key_comment) - sfree(priv->encrypted_key_comment); - fail_requests_for_key(priv, "key deleted from Pageant while signing " - "request was pending"); - sfree(priv); -} - -static void pk_pub_free(PageantPublicKey *pub) -{ - if (pub->full_pub) - strbuf_free(pub->full_pub); - sfree(pub->comment); - sfree(pub); -} - -static strbuf *makeblob1(RSAKey *rkey) -{ - strbuf *blob = strbuf_new(); - rsa_ssh1_public_blob(BinarySink_UPCAST(blob), rkey, - RSA_SSH1_EXPONENT_FIRST); - return blob; -} - -static strbuf *makeblob2full(ssh_key *key) -{ - strbuf *blob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(blob)); - return blob; -} - -static strbuf *makeblob2base(ssh_key *key) -{ - strbuf *blob = strbuf_new(); - ssh_key_public_blob(ssh_key_base_key(key), BinarySink_UPCAST(blob)); - return blob; -} - -static PageantPrivateKey *pub_to_priv(PageantPublicKey *pub) -{ - PageantPrivateKey *priv = find234(privkeytree, &pub->sort.priv, NULL); - assert(priv && "Public and private trees out of sync!"); - return priv; -} - -static PageantPublicKey *findpubkey1(RSAKey *reqkey) -{ - strbuf *blob = makeblob1(reqkey); - PageantPublicKeySort sort; - sort.priv.ssh_version = 1; - sort.priv.base_pub = ptrlen_from_strbuf(blob); - sort.full_pub = ptrlen_from_strbuf(blob); - PageantPublicKey *toret = find234(pubkeytree, &sort, NULL); - strbuf_free(blob); - return toret; -} - -/* - * Constructs the base_pub element of a PageantPublicKeySort, starting - * from full_pub. This may involve allocating a strbuf to store it in, - * which must survive until after you've finished using the resulting - * PageantPublicKeySort. Hence, the strbuf (if any) is returned from - * this function, and if it's non-NULL then the caller must eventually - * free it. - */ -static strbuf *make_base_pub_2(PageantPublicKeySort *sort) -{ - /* Start with the fallback option of making base_pub equal full_pub */ - sort->priv.base_pub = sort->full_pub; - - /* Now reconstruct a distinct base_pub without a cert, if possible - * and necessary */ - strbuf *base_pub = NULL; - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, sort->full_pub); - ptrlen algname = get_string(src); - const ssh_keyalg *alg = find_pubkey_alg_len(algname); - if (alg && alg->is_certificate) { - ssh_key *key = ssh_key_new_pub(alg, sort->full_pub); - if (key) { - base_pub = strbuf_new(); - ssh_key_public_blob(ssh_key_base_key(key), - BinarySink_UPCAST(base_pub)); - sort->priv.base_pub = ptrlen_from_strbuf(base_pub); - ssh_key_free(key); - } - } - - return base_pub; /* caller must free once they're done with sort */ -} - -static PageantPublicKey *findpubkey2(ptrlen full_pub) -{ - PageantPublicKeySort sort; - sort.priv.ssh_version = 2; - sort.full_pub = full_pub; - strbuf *base_pub = make_base_pub_2(&sort); - PageantPublicKey *toret = find234(pubkeytree, &sort, NULL); - if (base_pub) - strbuf_free(base_pub); - return toret; -} - -static int find_first_pubkey_for_version(int ssh_version) -{ - PageantPublicKeySort sort; - sort.priv.ssh_version = ssh_version; - sort.priv.base_pub = PTRLEN_LITERAL(""); - sort.full_pub = PTRLEN_LITERAL(""); - int pos; - if (findrelpos234(pubkeytree, &sort, NULL, REL234_GE, &pos)) - return pos; - return count234(pubkeytree); -} - -static int count_keys(int ssh_version) -{ - return (find_first_pubkey_for_version(ssh_version + 1) - - find_first_pubkey_for_version(ssh_version)); -} -int pageant_count_ssh1_keys(void) { return count_keys(1); } -int pageant_count_ssh2_keys(void) { return count_keys(2); } - -/* - * Common code to add a key to the trees. We fill in as many fields - * here as we can share between SSH versions: the ptrlens in the - * sorting field, the whole of pub->sort.priv, and the linked list of - * blocked requests. - */ -static bool pageant_add_key_common(PageantPublicKey *pub, - PageantPrivateKey *priv) -{ - int ssh_version = priv->sort.ssh_version; - - priv->sort.base_pub = ptrlen_from_strbuf(priv->base_pub); - - pub->base_pub = strbuf_dup(priv->sort.base_pub); - pub->sort.priv.ssh_version = priv->sort.ssh_version; - pub->sort.priv.base_pub = ptrlen_from_strbuf(pub->base_pub); - pub->sort.full_pub = ptrlen_from_strbuf(pub->full_pub); - priv->blocked_requests.next = priv->blocked_requests.prev = - &priv->blocked_requests; - - /* - * Try to add the private key to privkeytree, or combine new parts - * of it with what's already there. - */ - PageantPrivateKey *priv_in_tree = add234(privkeytree, priv); - if (priv_in_tree == priv) { - /* The key wasn't in the tree at all, and we've just added it. */ - } else { - /* The key was already in the tree, so we'll be freeing priv. */ - - if (ssh_version == 2 && priv->skey && !priv_in_tree->skey) { - /* The key was only stored encrypted, and now we have an - * unencrypted version to add to the existing record. */ - priv_in_tree->skey = priv->skey; - priv->skey = NULL; /* so pk_priv_free won't free it */ - } - - if (ssh_version == 2 && priv->encrypted_key_file && - !priv_in_tree->encrypted_key_file) { - /* Conversely, the key was only stored in clear, and now - * we have an encrypted version to add to it. */ - priv_in_tree->encrypted_key_file = priv->encrypted_key_file; - priv->encrypted_key_file = NULL; - priv_in_tree->encrypted_key_comment = priv->encrypted_key_comment; - priv->encrypted_key_comment = NULL; - } - - pk_priv_free(priv); - } - - /* - * Try to add the public key. - */ - PageantPublicKey *pub_in_tree = add234(pubkeytree, pub); - if (pub_in_tree == pub) { - /* Successfully added a new key. */ - return true; - } else { - /* This public key was already there. */ - pk_pub_free(pub); - return false; - } -} - -static bool pageant_add_ssh1_key(RSAKey *rkey) -{ - PageantPublicKey *pub = snew(PageantPublicKey); - memset(pub, 0, sizeof(PageantPublicKey)); - PageantPrivateKey *priv = snew(PageantPrivateKey); - memset(priv, 0, sizeof(PageantPrivateKey)); - - priv->sort.ssh_version = 1; - priv->base_pub = makeblob1(rkey); - pub->full_pub = makeblob1(rkey); - - if (rkey->comment) - pub->comment = dupstr(rkey->comment); - - priv->rkey = snew(RSAKey); - duprsakey(priv->rkey, rkey); - - return pageant_add_key_common(pub, priv); -} - -static bool pageant_add_ssh2_key(ssh2_userkey *skey) -{ - PageantPublicKey *pub = snew(PageantPublicKey); - memset(pub, 0, sizeof(PageantPublicKey)); - PageantPrivateKey *priv = snew(PageantPrivateKey); - memset(priv, 0, sizeof(PageantPrivateKey)); - - priv->sort.ssh_version = 2; - priv->base_pub = makeblob2base(skey->key); - pub->full_pub = makeblob2full(skey->key); - - if (skey->comment) - pub->comment = dupstr(skey->comment); - - /* Duplicate the ssh_key to go in priv */ - { - strbuf *tmp = strbuf_new_nm(); - ssh_key_openssh_blob(skey->key, BinarySink_UPCAST(tmp)); - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(tmp)); - priv->skey = ssh_key_new_priv_openssh(ssh_key_alg(skey->key), src); - strbuf_free(tmp); - } - - return pageant_add_key_common(pub, priv); -} - -static bool pageant_add_ssh2_key_encrypted(PageantPublicKeySort sort, - const char *comment, ptrlen keyfile) -{ - PageantPublicKey *pub = snew(PageantPublicKey); - memset(pub, 0, sizeof(PageantPublicKey)); - PageantPrivateKey *priv = snew(PageantPrivateKey); - memset(priv, 0, sizeof(PageantPrivateKey)); - - assert(sort.priv.ssh_version == 2); - priv->sort.ssh_version = sort.priv.ssh_version; - priv->base_pub = strbuf_dup(sort.priv.base_pub); - pub->full_pub = strbuf_dup(sort.full_pub); - - pub->comment = dupstr(comment); - - priv->encrypted_key_file = strbuf_dup_nm(keyfile); - priv->encrypted_key_comment = dupstr(comment); - - return pageant_add_key_common(pub, priv); -} - -static void remove_pubkey_cleanup(PageantPublicKey *pub) -{ - /* Common function called when we've just removed a public key - * from pubkeytree: we must also check whether that was the last - * public key sharing a private half, and if so, remove the - * corresponding private entry too. */ - - PageantPublicKeySort pubsearch; - pubsearch.priv = pub->sort.priv; - pubsearch.full_pub = PTRLEN_LITERAL(""); - PageantPublicKey *pubfound = findrel234( - pubkeytree, &pubsearch, NULL, REL234_GE); - - if (pubfound && !privkey_cmpfn(&pub->sort.priv, &pubfound->sort.priv)) { - /* There's still a public key which has the same sort.priv as - * the one we've just removed. We're good. */ - } else { - /* We've just removed the last public key of the family, so - * delete the private half as well. */ - PageantPrivateKey *priv = del234(privkeytree, &pub->sort.priv); - assert(priv); - assert(!privkey_cmpfn(&priv->sort, &pub->sort.priv)); - pk_priv_free(priv); - } -} - -static PageantPublicKey *del_pubkey_pos(int pos) -{ - PageantPublicKey *deleted = delpos234(pubkeytree, pos); - remove_pubkey_cleanup(deleted); - return deleted; -} - -static void del_pubkey(PageantPublicKey *to_delete) -{ - PageantPublicKey *deleted = del234(pubkeytree, to_delete); - remove_pubkey_cleanup(deleted); -} - -static void remove_all_keys(int ssh_version) -{ - int start = find_first_pubkey_for_version(ssh_version); - int end = find_first_pubkey_for_version(ssh_version + 1); - while (end > start) { - PageantPublicKey *pub = del_pubkey_pos(--end); - assert(pub->sort.priv.ssh_version == ssh_version); - pk_pub_free(pub); - } -} - -static void list_keys(BinarySink *bs, int ssh_version, bool extended) -{ - int i; - PageantPublicKey *pub; - - put_uint32(bs, count_keys(ssh_version)); - for (i = find_first_pubkey_for_version(ssh_version); - NULL != (pub = index234(pubkeytree, i)); i++) { - if (pub->sort.priv.ssh_version != ssh_version) - break; - - if (ssh_version > 1) - put_stringpl(bs, pub->sort.full_pub); - else - put_datapl(bs, pub->sort.full_pub); /* no header */ - - put_stringpl(bs, ptrlen_from_asciz(pub->comment)); - - if (extended) { - assert(ssh_version == 2); /* extended lists not supported in v1 */ - - /* - * Append to each key entry a string containing extension - * data. This string begins with a flags word, and may in - * future contain further data if flag bits are set saying - * that it does. Hence, it's wrapped in a containing - * string, so that clients that only partially understand - * it can still find the parts they do understand. - */ - PageantPrivateKey *priv = pub_to_priv(pub); - - strbuf *sb = strbuf_new(); - - uint32_t flags = 0; - if (!priv->skey) - flags |= LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY; - if (priv->encrypted_key_file) - flags |= LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE; - put_uint32(sb, flags); - - put_stringsb(bs, sb); - } - } -} - -void pageant_make_keylist1(BinarySink *bs) { list_keys(bs, 1, false); } -void pageant_make_keylist2(BinarySink *bs) { list_keys(bs, 2, false); } -void pageant_make_keylist_extended(BinarySink *bs) { list_keys(bs, 2, true); } - -void pageant_register_client(PageantClient *pc) -{ - pc->info = snew(PageantClientInfo); - pc->info->pc = pc; - pc->info->head.prev = pc->info->head.next = &pc->info->head; -} - -void pageant_unregister_client(PageantClient *pc) -{ - PageantClientInfo *info = pc->info; - assert(info); - assert(info->pc == pc); - - while (pc->info->head.next != &pc->info->head) { - PageantAsyncOp *pao = container_of(pc->info->head.next, - PageantAsyncOp, cr); - pageant_async_op_unlink_and_free(pao); - } - - sfree(pc->info); -} - -static PRINTF_LIKE(5, 6) void failure( - PageantClient *pc, PageantClientRequestId *reqid, strbuf *sb, - unsigned char type, const char *fmt, ...) -{ - strbuf_clear(sb); - put_byte(sb, type); - if (!pc->suppress_logging) { - va_list ap; - va_start(ap, fmt); - char *msg = dupvprintf(fmt, ap); - va_end(ap); - pageant_client_log(pc, reqid, "reply: SSH_AGENT_FAILURE (%s)", msg); - sfree(msg); - } -} - -static void signop_link_to_key(PageantSignOp *so) -{ - assert(!so->pkr.prev); - assert(!so->pkr.next); - - so->pkr.prev = so->priv->blocked_requests.prev; - so->pkr.next = &so->priv->blocked_requests; - so->pkr.prev->next = &so->pkr; - so->pkr.next->prev = &so->pkr; -} - -static void signop_link_to_pending_gui_request(PageantSignOp *so) -{ - assert(!so->pkr.prev); - assert(!so->pkr.next); - - so->pkr.prev = requests_blocked_on_gui.prev; - so->pkr.next = &requests_blocked_on_gui; - so->pkr.prev->next = &so->pkr; - so->pkr.next->prev = &so->pkr; -} - -static void signop_unlink(PageantSignOp *so) -{ - if (so->pkr.next) { - assert(so->pkr.prev); - so->pkr.next->prev = so->pkr.prev; - so->pkr.prev->next = so->pkr.next; - so->pkr.prev = so->pkr.next = NULL; - } else { - assert(!so->pkr.prev); - } -} - -static void signop_free(PageantAsyncOp *pao) -{ - PageantSignOp *so = container_of(pao, PageantSignOp, pao); - strbuf_free(so->data_to_sign); - sfree(so); -} - -static bool request_passphrase(PageantClient *pc, PageantPrivateKey *priv) -{ - if (!priv->decryption_prompt_active) { - assert(!gui_request_in_progress); - - bool created_dlg = pageant_client_ask_passphrase( - pc, &priv->dlgid, priv->encrypted_key_comment); - - if (!created_dlg) - return false; - - gui_request_in_progress = true; - priv->decryption_prompt_active = true; - } - - return true; -} - -static void signop_coroutine(PageantAsyncOp *pao) -{ - PageantSignOp *so = container_of(pao, PageantSignOp, pao); - strbuf *response; - - crBegin(so->crLine); - - while (!so->priv->skey && gui_request_in_progress) { - signop_link_to_pending_gui_request(so); - crReturnV; - signop_unlink(so); - } - - if (!so->priv->skey) { - assert(so->priv->encrypted_key_file); - - if (!request_passphrase(so->pao.info->pc, so->priv)) { - response = strbuf_new(); - failure(so->pao.info->pc, so->pao.reqid, response, - so->failure_type, "on-demand decryption could not " - "prompt for a passphrase"); - goto respond; - } - - signop_link_to_key(so); - crReturnV; - signop_unlink(so); - } - - uint32_t supported_flags = ssh_key_supported_flags(so->priv->skey); - if (so->flags & ~supported_flags) { - /* - * We MUST reject any message containing flags we don't - * understand. - */ - response = strbuf_new(); - failure(so->pao.info->pc, so->pao.reqid, response, so->failure_type, - "unsupported flag bits 0x%08"PRIx32, - so->flags & ~supported_flags); - goto respond; - } - - char *invalid = ssh_key_invalid(so->priv->skey, so->flags); - if (invalid) { - response = strbuf_new(); - failure(so->pao.info->pc, so->pao.reqid, response, so->failure_type, - "key invalid: %s", invalid); - sfree(invalid); - goto respond; - } - - strbuf *signature = strbuf_new(); - ssh_key_sign(so->priv->skey, ptrlen_from_strbuf(so->data_to_sign), - so->flags, BinarySink_UPCAST(signature)); - - response = strbuf_new(); - put_byte(response, SSH2_AGENT_SIGN_RESPONSE); - put_stringsb(response, signature); - - respond: - pageant_client_got_response(so->pao.info->pc, so->pao.reqid, - ptrlen_from_strbuf(response)); - strbuf_free(response); - - pageant_async_op_unlink_and_free(&so->pao); - crFinishFreedV; -} - -static const PageantAsyncOpVtable signop_vtable = { - .coroutine = signop_coroutine, - .free = signop_free, -}; - -static void fail_requests_for_key(PageantPrivateKey *priv, const char *reason) -{ - while (priv->blocked_requests.next != &priv->blocked_requests) { - PageantSignOp *so = container_of(priv->blocked_requests.next, - PageantSignOp, pkr); - signop_unlink(so); - strbuf *sb = strbuf_new(); - failure(so->pao.info->pc, so->pao.reqid, sb, so->failure_type, - "%s", reason); - pageant_client_got_response(so->pao.info->pc, so->pao.reqid, - ptrlen_from_strbuf(sb)); - strbuf_free(sb); - pageant_async_op_unlink_and_free(&so->pao); - } -} - -static void unblock_requests_for_key(PageantPrivateKey *priv) -{ - for (PageantKeyRequestNode *pkr = priv->blocked_requests.next; - pkr != &priv->blocked_requests; pkr = pkr->next) { - PageantSignOp *so = container_of(pkr, PageantSignOp, pkr); - queue_toplevel_callback(pageant_async_op_callback, &so->pao); - } -} - -static void unblock_pending_gui_requests(void) -{ - for (PageantKeyRequestNode *pkr = requests_blocked_on_gui.next; - pkr != &requests_blocked_on_gui; pkr = pkr->next) { - PageantSignOp *so = container_of(pkr, PageantSignOp, pkr); - queue_toplevel_callback(pageant_async_op_callback, &so->pao); - } -} - -void pageant_passphrase_request_success(PageantClientDialogId *dlgid, - ptrlen passphrase) -{ - PageantPrivateKey *priv = container_of(dlgid, PageantPrivateKey, dlgid); - - assert(gui_request_in_progress); - gui_request_in_progress = false; - priv->decryption_prompt_active = false; - - if (!priv->skey) { - const char *error; - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( - priv->encrypted_key_file)); - - strbuf *ppsb = strbuf_dup_nm(passphrase); - ssh2_userkey *skey = ppk_load_s(src, ppsb->s, &error); - strbuf_free(ppsb); - - if (!skey) { - fail_requests_for_key(priv, "unable to decrypt key"); - return; - } else if (skey == SSH2_WRONG_PASSPHRASE) { - /* - * Find a PageantClient to use for another attempt at - * request_passphrase. - */ - PageantKeyRequestNode *pkr = priv->blocked_requests.next; - if (pkr == &priv->blocked_requests) { - /* - * Special case: if all the requests have gone away at - * this point, we need not bother putting up a request - * at all any more. - */ - return; - } - - PageantSignOp *so = container_of(priv->blocked_requests.next, - PageantSignOp, pkr); - - priv->decryption_prompt_active = false; - if (!request_passphrase(so->pao.info->pc, so->priv)) { - fail_requests_for_key(priv, "unable to continue creating " - "passphrase prompts"); - } - return; - } else { - priv->skey = skey->key; - sfree(skey->comment); - sfree(skey); - keylist_update(); - } - } - - unblock_requests_for_key(priv); - - unblock_pending_gui_requests(); -} - -void pageant_passphrase_request_refused(PageantClientDialogId *dlgid) -{ - PageantPrivateKey *priv = container_of(dlgid, PageantPrivateKey, dlgid); - - assert(gui_request_in_progress); - gui_request_in_progress = false; - priv->decryption_prompt_active = false; - - fail_requests_for_key(priv, "user refused to supply passphrase"); - - unblock_pending_gui_requests(); -} - -typedef struct PageantImmOp PageantImmOp; -struct PageantImmOp { - int crLine; - strbuf *response; - - PageantAsyncOp pao; -}; - -static void immop_free(PageantAsyncOp *pao) -{ - PageantImmOp *io = container_of(pao, PageantImmOp, pao); - if (io->response) - strbuf_free(io->response); - sfree(io); -} - -static void immop_coroutine(PageantAsyncOp *pao) -{ - PageantImmOp *io = container_of(pao, PageantImmOp, pao); - - crBegin(io->crLine); - - if (0) crReturnV; - - pageant_client_got_response(io->pao.info->pc, io->pao.reqid, - ptrlen_from_strbuf(io->response)); - pageant_async_op_unlink_and_free(&io->pao); - crFinishFreedV; -} - -static const PageantAsyncOpVtable immop_vtable = { - .coroutine = immop_coroutine, - .free = immop_free, -}; - -static bool reencrypt_key(PageantPublicKey *pub) -{ - PageantPrivateKey *priv = pub_to_priv(pub); - - if (priv->sort.ssh_version != 2) { - /* - * We don't support storing SSH-1 keys in encrypted form at - * all. - */ - return false; - } - - if (!priv->encrypted_key_file) { - /* - * We can't re-encrypt a key if it doesn't have an encrypted - * form. (We could make one up, of course - but with what - * passphrase that we could expect the user to know later?) - */ - return false; - } - - /* Only actually free priv->skey if it exists. But we return success - * regardless, so that 'please ensure this key isn't stored - * decrypted' is idempotent. */ - if (priv->skey) { - ssh_key_free(priv->skey); - priv->skey = NULL; - } - - return true; -} - -#define DECL_EXT_ENUM(id, name) id, -enum Extension { KNOWN_EXTENSIONS(DECL_EXT_ENUM) EXT_UNKNOWN }; -#define DEF_EXT_NAMES(id, name) PTRLEN_DECL_LITERAL(name), -static const ptrlen extension_names[] = { KNOWN_EXTENSIONS(DEF_EXT_NAMES) }; - -static PageantAsyncOp *pageant_make_op( - PageantClient *pc, PageantClientRequestId *reqid, ptrlen msgpl) -{ - BinarySource msg[1]; - strbuf *sb = strbuf_new_nm(); - unsigned char failure_type = SSH_AGENT_FAILURE; - int type; - -#define fail(...) failure(pc, reqid, sb, failure_type, __VA_ARGS__) - - BinarySource_BARE_INIT_PL(msg, msgpl); - - type = get_byte(msg); - if (get_err(msg)) { - fail("message contained no type code"); - goto responded; - } - - switch (type) { - case SSH1_AGENTC_REQUEST_RSA_IDENTITIES: { - /* - * Reply with SSH1_AGENT_RSA_IDENTITIES_ANSWER. - */ - pageant_client_log(pc, reqid, - "request: SSH1_AGENTC_REQUEST_RSA_IDENTITIES"); - - put_byte(sb, SSH1_AGENT_RSA_IDENTITIES_ANSWER); - pageant_make_keylist1(BinarySink_UPCAST(sb)); - - pageant_client_log(pc, reqid, - "reply: SSH1_AGENT_RSA_IDENTITIES_ANSWER"); - if (!pc->suppress_logging) { - int i; - PageantPublicKey *pub; - for (i = 0; NULL != (pub = pageant_nth_pubkey(1, i)); i++) { - PageantPrivateKey *priv = pub_to_priv(pub); - char *fingerprint = rsa_ssh1_fingerprint(priv->rkey); - pageant_client_log(pc, reqid, "returned key: %s", - fingerprint); - sfree(fingerprint); - } - } - break; - } - case SSH2_AGENTC_REQUEST_IDENTITIES: { - /* - * Reply with SSH2_AGENT_IDENTITIES_ANSWER. - */ - pageant_client_log(pc, reqid, - "request: SSH2_AGENTC_REQUEST_IDENTITIES"); - - put_byte(sb, SSH2_AGENT_IDENTITIES_ANSWER); - pageant_make_keylist2(BinarySink_UPCAST(sb)); - - pageant_client_log(pc, reqid, "reply: SSH2_AGENT_IDENTITIES_ANSWER"); - if (!pc->suppress_logging) { - int i; - PageantPublicKey *pub; - for (i = 0; NULL != (pub = pageant_nth_pubkey(2, i)); i++) { - char *fingerprint = ssh2_double_fingerprint_blob( - pub->sort.full_pub, SSH_FPTYPE_DEFAULT); - pageant_client_log(pc, reqid, "returned key: %s %s", - fingerprint, pub->comment); - sfree(fingerprint); - } - } - break; - } - case SSH1_AGENTC_RSA_CHALLENGE: { - /* - * Reply with either SSH1_AGENT_RSA_RESPONSE or - * SSH_AGENT_FAILURE, depending on whether we have that key - * or not. - */ - RSAKey reqkey; - PageantPublicKey *pub; - PageantPrivateKey *priv; - mp_int *challenge, *response; - ptrlen session_id; - unsigned response_type; - unsigned char response_md5[16]; - int i; - - pageant_client_log(pc, reqid, "request: SSH1_AGENTC_RSA_CHALLENGE"); - - response = NULL; - memset(&reqkey, 0, sizeof(reqkey)); - - get_rsa_ssh1_pub(msg, &reqkey, RSA_SSH1_EXPONENT_FIRST); - challenge = get_mp_ssh1(msg); - session_id = get_data(msg, 16); - response_type = get_uint32(msg); - - if (get_err(msg)) { - fail("unable to decode request"); - goto challenge1_cleanup; - } - if (response_type != 1) { - fail("response type other than 1 not supported"); - goto challenge1_cleanup; - } - - if (!pc->suppress_logging) { - char *fingerprint; - reqkey.comment = NULL; - fingerprint = rsa_ssh1_fingerprint(&reqkey); - pageant_client_log(pc, reqid, "requested key: %s", fingerprint); - sfree(fingerprint); - } - - if ((pub = findpubkey1(&reqkey)) == NULL) { - fail("key not found"); - goto challenge1_cleanup; - } - priv = pub_to_priv(pub); - response = rsa_ssh1_decrypt(challenge, priv->rkey); - - { - ssh_hash *h = ssh_hash_new(&ssh_md5); - for (i = 0; i < 32; i++) - put_byte(h, mp_get_byte(response, 31 - i)); - put_datapl(h, session_id); - ssh_hash_final(h, response_md5); - } - - put_byte(sb, SSH1_AGENT_RSA_RESPONSE); - put_data(sb, response_md5, 16); - - pageant_client_log(pc, reqid, "reply: SSH1_AGENT_RSA_RESPONSE"); - - challenge1_cleanup: - if (response) - mp_free(response); - mp_free(challenge); - freersakey(&reqkey); - break; - } - case SSH2_AGENTC_SIGN_REQUEST: { - /* - * Reply with either SSH2_AGENT_SIGN_RESPONSE or - * SSH_AGENT_FAILURE, depending on whether we have that key - * or not. - */ - PageantPublicKey *pub; - ptrlen keyblob, sigdata; - uint32_t flags; - - pageant_client_log(pc, reqid, "request: SSH2_AGENTC_SIGN_REQUEST"); - - keyblob = get_string(msg); - sigdata = get_string(msg); - - if (get_err(msg)) { - fail("unable to decode request"); - goto responded; - } - - /* - * Later versions of the agent protocol added a flags word - * on the end of the sign request. That hasn't always been - * there, so we don't complain if we don't find it. - * - * get_uint32 will default to returning zero if no data is - * available. - */ - bool have_flags = false; - flags = get_uint32(msg); - if (!get_err(msg)) - have_flags = true; - - if (!pc->suppress_logging) { - char *fingerprint = ssh2_double_fingerprint_blob( - keyblob, SSH_FPTYPE_DEFAULT); - pageant_client_log(pc, reqid, "requested key: %s", fingerprint); - sfree(fingerprint); - } - if ((pub = findpubkey2(keyblob)) == NULL) { - fail("key not found"); - goto responded; - } - - if (have_flags) - pageant_client_log(pc, reqid, "signature flags = 0x%08"PRIx32, - flags); - else - pageant_client_log(pc, reqid, "no signature flags"); - - strbuf_free(sb); /* no immediate response */ - - PageantSignOp *so = snew(PageantSignOp); - so->pao.vt = &signop_vtable; - so->pao.info = pc->info; - so->pao.cr.prev = pc->info->head.prev; - so->pao.cr.next = &pc->info->head; - so->pao.cr.prev->next = so->pao.cr.next->prev = &so->pao.cr; - so->pao.reqid = reqid; - so->priv = pub_to_priv(pub); - so->pkr.prev = so->pkr.next = NULL; - so->data_to_sign = strbuf_dup(sigdata); - so->flags = flags; - so->failure_type = failure_type; - so->crLine = 0; - return &so->pao; - break; - } - case SSH1_AGENTC_ADD_RSA_IDENTITY: { - /* - * Add to the list and return SSH_AGENT_SUCCESS, or - * SSH_AGENT_FAILURE if the key was malformed. - */ - RSAKey *key; - - pageant_client_log(pc, reqid, "request: SSH1_AGENTC_ADD_RSA_IDENTITY"); - - key = get_rsa_ssh1_priv_agent(msg); - key->comment = mkstr(get_string(msg)); - - if (get_err(msg)) { - fail("unable to decode request"); - goto add1_cleanup; - } - - if (!rsa_verify(key)) { - fail("key is invalid"); - goto add1_cleanup; - } - - if (!pc->suppress_logging) { - char *fingerprint = rsa_ssh1_fingerprint(key); - pageant_client_log(pc, reqid, - "submitted key: %s", fingerprint); - sfree(fingerprint); - } - - if (pageant_add_ssh1_key(key)) { - keylist_update(); - put_byte(sb, SSH_AGENT_SUCCESS); - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); - key = NULL; /* don't free it in cleanup */ - } else { - fail("key already present"); - } - - add1_cleanup: - if (key) { - freersakey(key); - sfree(key); - } - break; - } - case SSH2_AGENTC_ADD_IDENTITY: { - /* - * Add to the list and return SSH_AGENT_SUCCESS, or - * SSH_AGENT_FAILURE if the key was malformed. - */ - ssh2_userkey *key = NULL; - ptrlen algpl; - const ssh_keyalg *alg; - - pageant_client_log(pc, reqid, "request: SSH2_AGENTC_ADD_IDENTITY"); - - algpl = get_string(msg); - - key = snew(ssh2_userkey); - key->key = NULL; - key->comment = NULL; - alg = find_pubkey_alg_len(algpl); - if (!alg) { - fail("algorithm unknown"); - goto add2_cleanup; - } - - key->key = ssh_key_new_priv_openssh(alg, msg); - - if (!key->key) { - fail("key setup failed"); - goto add2_cleanup; - } - - key->comment = mkstr(get_string(msg)); - - if (get_err(msg)) { - fail("unable to decode request"); - goto add2_cleanup; - } - - if (!pc->suppress_logging) { - char *fingerprint = ssh2_fingerprint(key->key, SSH_FPTYPE_DEFAULT); - pageant_client_log(pc, reqid, "submitted key: %s %s", - fingerprint, key->comment); - sfree(fingerprint); - } - - if (pageant_add_ssh2_key(key)) { - keylist_update(); - put_byte(sb, SSH_AGENT_SUCCESS); - - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); - - key = NULL; /* don't clean it up */ - } else { - fail("key already present"); - } - - add2_cleanup: - if (key) { - if (key->key) - ssh_key_free(key->key); - if (key->comment) - sfree(key->comment); - sfree(key); - } - break; - } - case SSH1_AGENTC_REMOVE_RSA_IDENTITY: { - /* - * Remove from the list and return SSH_AGENT_SUCCESS, or - * perhaps SSH_AGENT_FAILURE if it wasn't in the list to - * start with. - */ - RSAKey reqkey; - PageantPublicKey *pub; - - pageant_client_log(pc, reqid, - "request: SSH1_AGENTC_REMOVE_RSA_IDENTITY"); - - memset(&reqkey, 0, sizeof(reqkey)); - get_rsa_ssh1_pub(msg, &reqkey, RSA_SSH1_EXPONENT_FIRST); - - if (get_err(msg)) { - fail("unable to decode request"); - freersakey(&reqkey); - goto responded; - } - - if (!pc->suppress_logging) { - char *fingerprint; - reqkey.comment = NULL; - fingerprint = rsa_ssh1_fingerprint(&reqkey); - pageant_client_log(pc, reqid, "unwanted key: %s", fingerprint); - sfree(fingerprint); - } - - pub = findpubkey1(&reqkey); - freersakey(&reqkey); - if (pub) { - pageant_client_log(pc, reqid, "found with comment: %s", - pub->comment); - - del_pubkey(pub); - keylist_update(); - pk_pub_free(pub); - put_byte(sb, SSH_AGENT_SUCCESS); - - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); - } else { - fail("key not found"); - } - break; - } - case SSH2_AGENTC_REMOVE_IDENTITY: { - /* - * Remove from the list and return SSH_AGENT_SUCCESS, or - * perhaps SSH_AGENT_FAILURE if it wasn't in the list to - * start with. - */ - PageantPublicKey *pub; - ptrlen blob; - - pageant_client_log(pc, reqid, "request: SSH2_AGENTC_REMOVE_IDENTITY"); - - blob = get_string(msg); - - if (get_err(msg)) { - fail("unable to decode request"); - goto responded; - } - - if (!pc->suppress_logging) { - char *fingerprint = ssh2_double_fingerprint_blob( - blob, SSH_FPTYPE_DEFAULT); - pageant_client_log(pc, reqid, "unwanted key: %s", fingerprint); - sfree(fingerprint); - } - - pub = findpubkey2(blob); - if (!pub) { - fail("key not found"); - goto responded; - } - - pageant_client_log(pc, reqid, "found with comment: %s", pub->comment); - - del_pubkey(pub); - keylist_update(); - pk_pub_free(pub); - put_byte(sb, SSH_AGENT_SUCCESS); - - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); - break; - } - case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES: { - /* - * Remove all SSH-1 keys. Always returns success. - */ - pageant_client_log(pc, reqid, - "request: SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES"); - - remove_all_keys(1); - keylist_update(); - - put_byte(sb, SSH_AGENT_SUCCESS); - - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); - break; - } - case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: { - /* - * Remove all SSH-2 keys. Always returns success. - */ - pageant_client_log(pc, reqid, - "request: SSH2_AGENTC_REMOVE_ALL_IDENTITIES"); - - remove_all_keys(2); - keylist_update(); - - put_byte(sb, SSH_AGENT_SUCCESS); - - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); - break; - } - case SSH2_AGENTC_EXTENSION: { - enum Extension exttype = EXT_UNKNOWN; - ptrlen extname = get_string(msg); - pageant_client_log(pc, reqid, - "request: SSH2_AGENTC_EXTENSION \"%.*s\"", - PTRLEN_PRINTF(extname)); - - for (size_t i = 0; i < lenof(extension_names); i++) - if (ptrlen_eq_ptrlen(extname, extension_names[i])) { - exttype = i; - - /* - * For SSH_AGENTC_EXTENSION requests, the message - * code SSH_AGENT_FAILURE is reserved for "I don't - * recognise this extension name at all". For any - * other kind of failure while processing an - * extension we _do_ recognise, we must switch to - * returning a different failure code, with - * semantics "I understood the extension name, but - * something else went wrong". - */ - failure_type = SSH_AGENT_EXTENSION_FAILURE; - break; - } - - switch (exttype) { - case EXT_UNKNOWN: - fail("unrecognised extension name '%.*s'", - PTRLEN_PRINTF(extname)); - break; - - case EXT_QUERY: - /* Standard request to list the supported extensions. */ - put_byte(sb, SSH_AGENT_SUCCESS); - for (size_t i = 0; i < lenof(extension_names); i++) - put_stringpl(sb, extension_names[i]); - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS + names"); - break; - - case EXT_ADD_PPK: { - ptrlen keyfile = get_string(msg); - - if (get_err(msg)) { - fail("unable to decode request"); - goto responded; - } - - strbuf *base_pub = NULL; - strbuf *full_pub = NULL; - BinarySource src[1]; - const char *error; - - full_pub = strbuf_new(); - char *comment; - - BinarySource_BARE_INIT_PL(src, keyfile); - if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(full_pub), - &comment, &error)) { - fail("failed to extract public key blob: %s", error); - goto add_ppk_cleanup; - } - - if (!pc->suppress_logging) { - char *fingerprint = ssh2_double_fingerprint_blob( - ptrlen_from_strbuf(full_pub), SSH_FPTYPE_DEFAULT); - pageant_client_log(pc, reqid, "add-ppk: %s %s", - fingerprint, comment); - sfree(fingerprint); - } - - BinarySource_BARE_INIT_PL(src, keyfile); - bool encrypted = ppk_encrypted_s(src, NULL); - - if (!encrypted) { - /* If the key isn't encrypted, then we should just - * load and add it in the obvious way. */ - BinarySource_BARE_INIT_PL(src, keyfile); - ssh2_userkey *skey = ppk_load_s(src, NULL, &error); - if (!skey) { - fail("failed to decode private key: %s", error); - } else if (pageant_add_ssh2_key(skey)) { - keylist_update(); - put_byte(sb, SSH_AGENT_SUCCESS); - - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS" - " (loaded unencrypted PPK)"); - } else { - fail("key already present"); - if (skey->key) - ssh_key_free(skey->key); - if (skey->comment) - sfree(skey->comment); - sfree(skey); - } - goto add_ppk_cleanup; - } - - PageantPublicKeySort sort; - sort.priv.ssh_version = 2; - sort.full_pub = ptrlen_from_strbuf(full_pub); - base_pub = make_base_pub_2(&sort); - - pageant_add_ssh2_key_encrypted(sort, comment, keyfile); - keylist_update(); - put_byte(sb, SSH_AGENT_SUCCESS); - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); - - add_ppk_cleanup: - if (full_pub) - strbuf_free(full_pub); - if (base_pub) - strbuf_free(base_pub); - sfree(comment); - break; - } - - case EXT_REENCRYPT: { - /* - * Re-encrypt a single key, in the sense of deleting - * its unencrypted copy, returning it to the state of - * only having the encrypted PPK form stored, so that - * the next attempt to use it will have to re-prompt - * for the passphrase. - */ - ptrlen blob = get_string(msg); - - if (get_err(msg)) { - fail("unable to decode request"); - goto responded; - } - - if (!pc->suppress_logging) { - char *fingerprint = ssh2_double_fingerprint_blob( - blob, SSH_FPTYPE_DEFAULT); - pageant_client_log(pc, reqid, "key to re-encrypt: %s", - fingerprint); - sfree(fingerprint); - } - - PageantPublicKey *pub = findpubkey2(blob); - if (!pub) { - fail("key not found"); - goto responded; - } - - pageant_client_log(pc, reqid, - "found with comment: %s", pub->comment); - - if (!reencrypt_key(pub)) { - fail("this key couldn't be re-encrypted"); - goto responded; - } - - keylist_update(); - put_byte(sb, SSH_AGENT_SUCCESS); - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"); - break; - } - - case EXT_REENCRYPT_ALL: { - /* - * Re-encrypt all keys that have an encrypted form - * stored. Usually, returns success, but with a uint32 - * appended indicating how many keys remain - * unencrypted. The exception is if there is at least - * one key in the agent and _no_ key was successfully - * re-encrypted; in that situation we've done nothing, - * and the client didn't _want_ us to do nothing, so - * we return failure. - * - * (Rationale: the 'failure' message ought to be - * atomic, that is, you shouldn't return failure - * having made a state change.) - */ - unsigned nfailures = 0, nsuccesses = 0; - PageantPublicKey *pub; - - for (int i = 0; (pub = index234(pubkeytree, i)) != NULL; i++) { - if (reencrypt_key(pub)) - nsuccesses++; - else - nfailures++; - } - - if (nsuccesses == 0 && nfailures > 0) { - fail("no key could be re-encrypted"); - } else { - keylist_update(); - put_byte(sb, SSH_AGENT_SUCCESS); - put_uint32(sb, nfailures); - pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS " - "(%u keys re-encrypted, %u failures)", - nsuccesses, nfailures); - } - break; - } - - case EXT_LIST_EXTENDED: { - /* - * Return a key list like SSH2_AGENTC_REQUEST_IDENTITIES, - * except that each key is annotated with extra - * information such as whether it's currently encrypted. - * - * The return message type is AGENT_SUCCESS with auxiliary - * data, which is more like other extension messages. I - * think it would be confusing to reuse IDENTITIES_ANSWER - * for a reply message with an incompatible format. - */ - put_byte(sb, SSH_AGENT_SUCCESS); - pageant_make_keylist_extended(BinarySink_UPCAST(sb)); - - pageant_client_log(pc, reqid, - "reply: SSH2_AGENT_SUCCESS + key list"); - if (!pc->suppress_logging) { - int i; - PageantPublicKey *pub; - for (i = 0; NULL != (pub = pageant_nth_pubkey(2, i)); i++) { - char *fingerprint = ssh2_double_fingerprint_blob( - ptrlen_from_strbuf(pub->full_pub), - SSH_FPTYPE_DEFAULT); - pageant_client_log(pc, reqid, "returned key: %s %s", - fingerprint, pub->comment); - sfree(fingerprint); - } - } - break; - } - } - break; - } - default: - pageant_client_log(pc, reqid, "request: unknown message type %d", - type); - fail("unrecognised message"); - break; - } - -#undef fail - - responded:; - - PageantImmOp *io = snew(PageantImmOp); - io->pao.vt = &immop_vtable; - io->pao.info = pc->info; - io->pao.cr.prev = pc->info->head.prev; - io->pao.cr.next = &pc->info->head; - io->pao.cr.prev->next = io->pao.cr.next->prev = &io->pao.cr; - io->pao.reqid = reqid; - io->response = sb; - io->crLine = 0; - return &io->pao; -} - -void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid, - ptrlen msgpl) -{ - PageantAsyncOp *pao = pageant_make_op(pc, reqid, msgpl); - queue_toplevel_callback(pageant_async_op_callback, pao); -} - -void pageant_init(void) -{ - pageant_local = true; - pubkeytree = newtree234(pubkey_cmpfn); - privkeytree = newtree234(privkey_cmpfn); -} - -static PageantPublicKey *pageant_nth_pubkey(int ssh_version, int i) -{ - PageantPublicKey *pub = index234( - pubkeytree, find_first_pubkey_for_version(ssh_version) + i); - if (pub && pub->sort.priv.ssh_version == ssh_version) - return pub; - else - return NULL; -} - -bool pageant_delete_nth_ssh1_key(int i) -{ - PageantPublicKey *pub = del_pubkey_pos( - find_first_pubkey_for_version(1) + i); - if (!pub) - return false; - pk_pub_free(pub); - return true; -} - -bool pageant_delete_nth_ssh2_key(int i) -{ - PageantPublicKey *pub = del_pubkey_pos( - find_first_pubkey_for_version(2) + i); - if (!pub) - return false; - pk_pub_free(pub); - return true; -} - -bool pageant_reencrypt_nth_ssh2_key(int i) -{ - PageantPublicKey *pub = index234( - pubkeytree, find_first_pubkey_for_version(2) + i); - if (!pub) - return false; - return reencrypt_key(pub); -} - -void pageant_delete_all(void) -{ - remove_all_keys(1); - remove_all_keys(2); -} - -void pageant_reencrypt_all(void) -{ - PageantPublicKey *pub; - for (int i = 0; (pub = index234(pubkeytree, i)) != NULL; i++) - reencrypt_key(pub); -} - -/* ---------------------------------------------------------------------- - * The agent plug. - */ - -/* - * An extra coroutine macro, specific to this code which is consuming - * 'const char *data'. - */ -#define crGetChar(c) do \ - { \ - while (len == 0) { \ - *crLine =__LINE__; return; case __LINE__:; \ - } \ - len--; \ - (c) = (unsigned char)*data++; \ - } while (0) - -struct pageant_conn_queued_response { - struct pageant_conn_queued_response *next, *prev; - size_t req_index; /* for indexing requests in log messages */ - strbuf *sb; - PageantClientRequestId reqid; -}; - -struct pageant_conn_state { - Socket *connsock; - PageantListenerClient *plc; - unsigned char lenbuf[4], pktbuf[AGENT_MAX_MSGLEN]; - unsigned len, got; - bool real_packet; - size_t conn_index; /* for indexing connections in log messages */ - size_t req_index; /* for indexing requests in log messages */ - int crLine; /* for coroutine in pageant_conn_receive */ - - struct pageant_conn_queued_response response_queue; - - PageantClient pc; - Plug plug; -}; - -static void pageant_conn_closing(Plug *plug, PlugCloseType type, - const char *error_msg) -{ - struct pageant_conn_state *pc = container_of( - plug, struct pageant_conn_state, plug); - if (type != PLUGCLOSE_NORMAL) - pageant_listener_client_log(pc->plc, "c#%"SIZEu": error: %s", - pc->conn_index, error_msg); - else - pageant_listener_client_log(pc->plc, "c#%"SIZEu": connection closed", - pc->conn_index); - sk_close(pc->connsock); - pageant_unregister_client(&pc->pc); - sfree(pc); -} - -static void pageant_conn_sent(Plug *plug, size_t bufsize) -{ - /* struct pageant_conn_state *pc = container_of( - plug, struct pageant_conn_state, plug); */ - - /* - * We do nothing here, because we expect that there won't be a - * need to throttle and unthrottle the connection to an agent - - * clients will typically not send many requests, and will wait - * until they receive each reply before sending a new request. - */ -} - -static void pageant_conn_log(PageantClient *pc, PageantClientRequestId *reqid, - const char *fmt, va_list ap) -{ - struct pageant_conn_state *pcs = - container_of(pc, struct pageant_conn_state, pc); - struct pageant_conn_queued_response *qr = - container_of(reqid, struct pageant_conn_queued_response, reqid); - - char *formatted = dupvprintf(fmt, ap); - pageant_listener_client_log(pcs->plc, "c#%"SIZEu",r#%"SIZEu": %s", - pcs->conn_index, qr->req_index, formatted); - sfree(formatted); -} - -static void pageant_conn_got_response( - PageantClient *pc, PageantClientRequestId *reqid, ptrlen response) -{ - struct pageant_conn_state *pcs = - container_of(pc, struct pageant_conn_state, pc); - struct pageant_conn_queued_response *qr = - container_of(reqid, struct pageant_conn_queued_response, reqid); - - qr->sb = strbuf_new_nm(); - put_stringpl(qr->sb, response); - - while (pcs->response_queue.next != &pcs->response_queue && - pcs->response_queue.next->sb) { - qr = pcs->response_queue.next; - sk_write(pcs->connsock, qr->sb->u, qr->sb->len); - qr->next->prev = qr->prev; - qr->prev->next = qr->next; - strbuf_free(qr->sb); - sfree(qr); - } -} - -static bool pageant_conn_ask_passphrase( - PageantClient *pc, PageantClientDialogId *dlgid, const char *comment) -{ - struct pageant_conn_state *pcs = - container_of(pc, struct pageant_conn_state, pc); - return pageant_listener_client_ask_passphrase(pcs->plc, dlgid, comment); -} - -static const PageantClientVtable pageant_connection_clientvt = { - .log = pageant_conn_log, - .got_response = pageant_conn_got_response, - .ask_passphrase = pageant_conn_ask_passphrase, -}; - -static void pageant_conn_receive( - Plug *plug, int urgent, const char *data, size_t len) -{ - struct pageant_conn_state *pc = container_of( - plug, struct pageant_conn_state, plug); - char c; - - crBegin(pc->crLine); - - while (len > 0) { - pc->got = 0; - while (pc->got < 4) { - crGetChar(c); - pc->lenbuf[pc->got++] = c; - } - - pc->len = GET_32BIT_MSB_FIRST(pc->lenbuf); - pc->got = 0; - pc->real_packet = (pc->len < AGENT_MAX_MSGLEN-4); - - { - struct pageant_conn_queued_response *qr = - snew(struct pageant_conn_queued_response); - qr->prev = pc->response_queue.prev; - qr->next = &pc->response_queue; - qr->prev->next = qr->next->prev = qr; - qr->sb = NULL; - qr->req_index = pc->req_index++; - } - - if (!pc->real_packet) { - /* - * Send failure immediately, before consuming the packet - * data. That way we notify the client reasonably early - * even if the data channel has just started spewing - * nonsense. - */ - pageant_client_log(&pc->pc, &pc->response_queue.prev->reqid, - "early reply: SSH_AGENT_FAILURE " - "(overlong message, length %u)", pc->len); - static const unsigned char failure[] = { SSH_AGENT_FAILURE }; - pageant_conn_got_response(&pc->pc, &pc->response_queue.prev->reqid, - make_ptrlen(failure, lenof(failure))); - } - - while (pc->got < pc->len) { - crGetChar(c); - if (pc->real_packet) - pc->pktbuf[pc->got] = c; - pc->got++; - } - - if (pc->real_packet) - pageant_handle_msg(&pc->pc, &pc->response_queue.prev->reqid, - make_ptrlen(pc->pktbuf, pc->len)); - } - - crFinishV; -} - -struct pageant_listen_state { - Socket *listensock; - PageantListenerClient *plc; - size_t conn_index; /* for indexing connections in log messages */ - - Plug plug; -}; - -static void pageant_listen_closing(Plug *plug, PlugCloseType type, - const char *error_msg) -{ - struct pageant_listen_state *pl = container_of( - plug, struct pageant_listen_state, plug); - if (type != PLUGCLOSE_NORMAL) - pageant_listener_client_log(pl->plc, "listening socket: error: %s", - error_msg); - sk_close(pl->listensock); - pl->listensock = NULL; -} - -static const PlugVtable pageant_connection_plugvt = { - .closing = pageant_conn_closing, - .receive = pageant_conn_receive, - .sent = pageant_conn_sent, - .log = nullplug_log, -}; - -static int pageant_listen_accepting(Plug *plug, - accept_fn_t constructor, accept_ctx_t ctx) -{ - struct pageant_listen_state *pl = container_of( - plug, struct pageant_listen_state, plug); - struct pageant_conn_state *pc; - const char *err; - SocketPeerInfo *peerinfo; - - pc = snew(struct pageant_conn_state); - pc->plug.vt = &pageant_connection_plugvt; - pc->pc.vt = &pageant_connection_clientvt; - pc->plc = pl->plc; - pc->response_queue.next = pc->response_queue.prev = &pc->response_queue; - pc->conn_index = pl->conn_index++; - pc->req_index = 0; - pc->crLine = 0; - - pc->connsock = constructor(ctx, &pc->plug); - if ((err = sk_socket_error(pc->connsock)) != NULL) { - sk_close(pc->connsock); - sfree(pc); - return 1; - } - - sk_set_frozen(pc->connsock, false); - - peerinfo = sk_peer_info(pc->connsock); - if (peerinfo && peerinfo->log_text) { - pageant_listener_client_log(pl->plc, - "c#%"SIZEu": new connection from %s", - pc->conn_index, peerinfo->log_text); - } else { - pageant_listener_client_log(pl->plc, "c#%"SIZEu": new connection", - pc->conn_index); - } - sk_free_peer_info(peerinfo); - - pageant_register_client(&pc->pc); - - return 0; -} - -static const PlugVtable pageant_listener_plugvt = { - .closing = pageant_listen_closing, - .accepting = pageant_listen_accepting, - .log = nullplug_log, -}; - -struct pageant_listen_state *pageant_listener_new( - Plug **plug, PageantListenerClient *plc) -{ - struct pageant_listen_state *pl = snew(struct pageant_listen_state); - pl->plug.vt = &pageant_listener_plugvt; - pl->plc = plc; - pl->listensock = NULL; - pl->conn_index = 0; - *plug = &pl->plug; - return pl; -} - -void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket *sock) -{ - pl->listensock = sock; -} - -void pageant_listener_free(struct pageant_listen_state *pl) -{ - if (pl->listensock) - sk_close(pl->listensock); - sfree(pl); -} - -/* ---------------------------------------------------------------------- - * Code to perform agent operations either as a client, or within the - * same process as the running agent. - */ - -static tree234 *passphrases = NULL; - -typedef struct PageantInternalClient { - strbuf *response; - bool got_response; - PageantClient pc; -} PageantInternalClient; - -static void internal_client_got_response( - PageantClient *pc, PageantClientRequestId *reqid, ptrlen response) -{ - PageantInternalClient *pic = container_of(pc, PageantInternalClient, pc); - strbuf_clear(pic->response); - put_datapl(pic->response, response); - pic->got_response = true; -} - -static bool internal_client_ask_passphrase( - PageantClient *pc, PageantClientDialogId *dlgid, const char *comment) -{ - /* No delaying operations are permitted in this mode */ - return false; -} - -static const PageantClientVtable internal_clientvt = { - .log = NULL, - .got_response = internal_client_got_response, - .ask_passphrase = internal_client_ask_passphrase, -}; - -typedef struct PageantClientOp { - strbuf *buf; - bool request_made; - BinarySink_DELEGATE_IMPLEMENTATION; - BinarySource_IMPLEMENTATION; -} PageantClientOp; - -static PageantClientOp *pageant_client_op_new(void) -{ - PageantClientOp *pco = snew(PageantClientOp); - pco->buf = strbuf_new_for_agent_query(); - pco->request_made = false; - BinarySink_DELEGATE_INIT(pco, pco->buf); - BinarySource_INIT(pco, "", 0); - return pco; -} - -static void pageant_client_op_free(PageantClientOp *pco) -{ - if (pco->buf) - strbuf_free(pco->buf); - sfree(pco); -} - -static unsigned pageant_client_op_query(PageantClientOp *pco) -{ - /* Since we use the same strbuf for the request and the response, - * check by assertion that we aren't embarrassingly sending a - * previous response back to the agent */ - assert(!pco->request_made); - pco->request_made = true; - - if (!pageant_local) { - void *response_raw; - int resplen_raw; - agent_query_synchronous(pco->buf, &response_raw, &resplen_raw); - strbuf_clear(pco->buf); - put_data(pco->buf, response_raw, resplen_raw); - sfree(response_raw); - - /* The data coming back from agent_query_synchronous will have - * its length field prepended. So we start by parsing it as an - * SSH-formatted string, and then reinitialise our - * BinarySource with the interior of that string. */ - BinarySource_INIT_PL(pco, ptrlen_from_strbuf(pco->buf)); - BinarySource_INIT_PL(pco, get_string(pco)); - } else { - PageantInternalClient pic; - PageantClientRequestId reqid; - - pic.pc.vt = &internal_clientvt; - pic.pc.suppress_logging = true; - pic.response = pco->buf; - pic.got_response = false; - pageant_register_client(&pic.pc); - - assert(pco->buf->len > 4); - PageantAsyncOp *pao = pageant_make_op( - &pic.pc, &reqid, make_ptrlen(pco->buf->s + 4, pco->buf->len - 4)); - while (!pic.got_response) - pageant_async_op_coroutine(pao); - - pageant_unregister_client(&pic.pc); - - BinarySource_INIT_PL(pco, ptrlen_from_strbuf(pco->buf)); - } - - /* Strip off and directly return the type byte, which every client - * will need, to save a boilerplate get_byte at each call site */ - unsigned reply_type = get_byte(pco); - if (get_err(pco)) - reply_type = 256; /* out-of-range code */ - return reply_type; -} - -/* - * After processing a list of filenames, we want to forget the - * passphrases. - */ -void pageant_forget_passphrases(void) -{ - if (!passphrases) /* in case we never set it up at all */ - return; - - while (count234(passphrases) > 0) { - char *pp = index234(passphrases, 0); - smemclr(pp, strlen(pp)); - delpos234(passphrases, 0); - sfree(pp); - } -} - -typedef struct KeyListEntry { - ptrlen blob, comment; - uint32_t flags; -} KeyListEntry; -typedef struct KeyList { - strbuf *raw_data; - KeyListEntry *keys; - size_t nkeys; - bool broken; -} KeyList; - -static void keylist_free(KeyList *kl) -{ - sfree(kl->keys); - strbuf_free(kl->raw_data); - sfree(kl); -} - -static PageantClientOp *pageant_request_keylist_1(void) -{ - PageantClientOp *pco = pageant_client_op_new(); - put_byte(pco, SSH1_AGENTC_REQUEST_RSA_IDENTITIES); - if (pageant_client_op_query(pco) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) - return pco; - pageant_client_op_free(pco); - return NULL; -} - -static PageantClientOp *pageant_request_keylist_2(void) -{ - PageantClientOp *pco = pageant_client_op_new(); - put_byte(pco, SSH2_AGENTC_REQUEST_IDENTITIES); - if (pageant_client_op_query(pco) == SSH2_AGENT_IDENTITIES_ANSWER) - return pco; - pageant_client_op_free(pco); - return NULL; -} - -static PageantClientOp *pageant_request_keylist_extended(void) -{ - PageantClientOp *pco = pageant_client_op_new(); - put_byte(pco, SSH2_AGENTC_EXTENSION); - put_stringpl(pco, extension_names[EXT_LIST_EXTENDED]); - if (pageant_client_op_query(pco) == SSH_AGENT_SUCCESS) - return pco; - pageant_client_op_free(pco); - return NULL; -} - -static KeyList *pageant_get_keylist(unsigned ssh_version) -{ - PageantClientOp *pco; - bool list_is_extended = false; - - if (ssh_version == 1) { - pco = pageant_request_keylist_1(); - } else { - if ((pco = pageant_request_keylist_extended()) != NULL) - list_is_extended = true; - else - pco = pageant_request_keylist_2(); - } - - if (!pco) - return NULL; - - KeyList *kl = snew(KeyList); - kl->nkeys = get_uint32(pco); - kl->keys = snewn(kl->nkeys, struct KeyListEntry); - kl->broken = false; - - for (size_t i = 0; i < kl->nkeys && !get_err(pco); i++) { - if (ssh_version == 1) { - int bloblen = rsa_ssh1_public_blob_len( - make_ptrlen(get_ptr(pco), get_avail(pco))); - if (bloblen < 0) { - kl->broken = true; - bloblen = 0; - } - kl->keys[i].blob = get_data(pco, bloblen); - } else { - kl->keys[i].blob = get_string(pco); - } - kl->keys[i].comment = get_string(pco); - - if (list_is_extended) { - ptrlen key_ext_info = get_string(pco); - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, key_ext_info); - - kl->keys[i].flags = get_uint32(src); - } else { - kl->keys[i].flags = 0; - } - } - - if (get_err(pco)) - kl->broken = true; - kl->raw_data = pco->buf; - pco->buf = NULL; - pageant_client_op_free(pco); - return kl; -} - -int pageant_add_keyfile(Filename *filename, const char *passphrase, - char **retstr, bool add_encrypted) -{ - RSAKey *rkey = NULL; - ssh2_userkey *skey = NULL; - bool needs_pass; - int ret; - int attempts; - char *comment; - const char *this_passphrase; - const char *error = NULL; - int type; - - if (!passphrases) { - passphrases = newtree234(NULL); - } - - *retstr = NULL; - - type = key_type(filename); - if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) { - *retstr = dupprintf("Couldn't load this key (%s)", - key_type_to_str(type)); - return PAGEANT_ACTION_FAILURE; - } - - if (add_encrypted && type == SSH_KEYTYPE_SSH1) { - *retstr = dupprintf("Can't add SSH-1 keys in encrypted form"); - return PAGEANT_ACTION_FAILURE; - } - - /* - * See if the key is already loaded (in the primary Pageant, - * which may or may not be us). - */ - { - strbuf *blob = strbuf_new(); - KeyList *kl; - - if (type == SSH_KEYTYPE_SSH1) { - if (!rsa1_loadpub_f(filename, BinarySink_UPCAST(blob), - NULL, &error)) { - *retstr = dupprintf("Couldn't load private key (%s)", error); - strbuf_free(blob); - return PAGEANT_ACTION_FAILURE; - } - kl = pageant_get_keylist(1); - } else { - if (!ppk_loadpub_f(filename, NULL, BinarySink_UPCAST(blob), - NULL, &error)) { - *retstr = dupprintf("Couldn't load private key (%s)", error); - strbuf_free(blob); - return PAGEANT_ACTION_FAILURE; - } - kl = pageant_get_keylist(2); - } - - if (kl) { - if (kl->broken) { - *retstr = dupstr("Received broken key list from agent"); - keylist_free(kl); - strbuf_free(blob); - return PAGEANT_ACTION_FAILURE; - } - - for (size_t i = 0; i < kl->nkeys; i++) { - /* - * If the key already exists in the agent, we're done, - * except in the following special cases: - * - * It's encrypted in the agent, and we're being asked - * to add it unencrypted, in which case we still want - * to upload the unencrypted version to cause the key - * to become decrypted. - * (Rationale: if you know in advance you're going to - * want it, and don't want to be interrupted at an - * unpredictable moment to be asked for the - * passphrase.) - * - * The agent only has cleartext, and we're being asked - * to add it encrypted, in which case we'll add the - * encrypted form. - * (Rationale: if you might want to re-encrypt the key - * at some future point, but it happened to have been - * initially added in cleartext, perhaps by something - * other than Pageant.) - */ - if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(blob), - kl->keys[i].blob)) { - bool have_unencrypted = - !(kl->keys[i].flags & - LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY); - bool have_encrypted = - (kl->keys[i].flags & - LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE); - if ((have_unencrypted && !add_encrypted) - || (have_encrypted && add_encrypted)) { - /* Key is already present in the desired form; - * we can now leave. */ - keylist_free(kl); - strbuf_free(blob); - return PAGEANT_ACTION_OK; - } - } - } - - keylist_free(kl); - } - - strbuf_free(blob); - } - - if (add_encrypted) { - const char *load_error; - LoadedFile *lf = lf_load_keyfile(filename, &load_error); - if (!lf) { - *retstr = dupstr(load_error); - return PAGEANT_ACTION_FAILURE; - } - - PageantClientOp *pco = pageant_client_op_new(); - put_byte(pco, SSH2_AGENTC_EXTENSION); - put_stringpl(pco, extension_names[EXT_ADD_PPK]); - put_string(pco, lf->data, lf->len); - - lf_free(lf); - - unsigned reply = pageant_client_op_query(pco); - pageant_client_op_free(pco); - - if (reply != SSH_AGENT_SUCCESS) { - if (reply == SSH_AGENT_FAILURE) { - /* The agent didn't understand the protocol extension - * at all. */ - *retstr = dupstr("Agent doesn't support adding " - "encrypted keys"); - } else { - *retstr = dupstr("The already running agent " - "refused to add the key."); - } - return PAGEANT_ACTION_FAILURE; - } - - return PAGEANT_ACTION_OK; - } - - error = NULL; - if (type == SSH_KEYTYPE_SSH1) - needs_pass = rsa1_encrypted_f(filename, &comment); - else - needs_pass = ppk_encrypted_f(filename, &comment); - attempts = 0; - if (type == SSH_KEYTYPE_SSH1) - rkey = snew(RSAKey); - - /* - * Loop round repeatedly trying to load the key, until we either - * succeed, fail for some serious reason, or run out of - * passphrases to try. - */ - while (1) { - if (needs_pass) { - - /* - * If we've been given a passphrase on input, try using - * it. Otherwise, try one from our tree234 of previously - * useful passphrases. - */ - if (passphrase) { - this_passphrase = (attempts == 0 ? passphrase : NULL); - } else { - this_passphrase = (const char *)index234(passphrases, attempts); - } - - if (!this_passphrase) { - /* - * Run out of passphrases to try. - */ - *retstr = comment; - sfree(rkey); - return PAGEANT_ACTION_NEED_PP; - } - } else - this_passphrase = ""; - - if (type == SSH_KEYTYPE_SSH1) - ret = rsa1_load_f(filename, rkey, this_passphrase, &error); - else { - skey = ppk_load_f(filename, this_passphrase, &error); - if (skey == SSH2_WRONG_PASSPHRASE) - ret = -1; - else if (!skey) - ret = 0; - else - ret = 1; - } - - if (ret == 0) { - /* - * Failed to load the key file, for some reason other than - * a bad passphrase. - */ - *retstr = dupstr(error); - sfree(rkey); - if (comment) - sfree(comment); - return PAGEANT_ACTION_FAILURE; - } else if (ret == 1) { - /* - * Successfully loaded the key file. - */ - break; - } else { - /* - * Passphrase wasn't right; go round again. - */ - attempts++; - } - } - - /* - * If we get here, we've successfully loaded the key into - * rkey/skey, but not yet added it to the agent. - */ - - /* - * If the key was successfully decrypted, save the passphrase for - * use with other keys we try to load. - */ - { - char *pp_copy = dupstr(this_passphrase); - if (addpos234(passphrases, pp_copy, 0) != pp_copy) { - /* No need; it was already there. */ - smemclr(pp_copy, strlen(pp_copy)); - sfree(pp_copy); - } - } - - if (comment) - sfree(comment); - - if (type == SSH_KEYTYPE_SSH1) { - PageantClientOp *pco = pageant_client_op_new(); - put_byte(pco, SSH1_AGENTC_ADD_RSA_IDENTITY); - rsa_ssh1_private_blob_agent(BinarySink_UPCAST(pco), rkey); - put_stringz(pco, rkey->comment); - unsigned reply = pageant_client_op_query(pco); - pageant_client_op_free(pco); - - freersakey(rkey); - sfree(rkey); - - if (reply != SSH_AGENT_SUCCESS) { - *retstr = dupstr("The already running agent " - "refused to add the key."); - return PAGEANT_ACTION_FAILURE; - } - } else { - PageantClientOp *pco = pageant_client_op_new(); - put_byte(pco, SSH2_AGENTC_ADD_IDENTITY); - put_stringz(pco, ssh_key_ssh_id(skey->key)); - ssh_key_openssh_blob(skey->key, BinarySink_UPCAST(pco)); - put_stringz(pco, skey->comment); - unsigned reply = pageant_client_op_query(pco); - pageant_client_op_free(pco); - - sfree(skey->comment); - ssh_key_free(skey->key); - sfree(skey); - - if (reply != SSH_AGENT_SUCCESS) { - *retstr = dupstr("The already running agent " - "refused to add the key."); - return PAGEANT_ACTION_FAILURE; - } - } - return PAGEANT_ACTION_OK; -} - -int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, - char **retstr) -{ - KeyList *kl1 = NULL, *kl2 = NULL; - struct pageant_pubkey cbkey; - int toret = PAGEANT_ACTION_FAILURE; - - kl1 = pageant_get_keylist(1); - if (kl1 && kl1->broken) { - *retstr = dupstr("Received broken SSH-1 key list from agent"); - goto out; - } - - kl2 = pageant_get_keylist(2); - if (kl2 && kl2->broken) { - *retstr = dupstr("Received broken SSH-2 key list from agent"); - goto out; - } - - if (kl1) { - for (size_t i = 0; i < kl1->nkeys; i++) { - cbkey.blob = strbuf_dup(kl1->keys[i].blob); - cbkey.comment = mkstr(kl1->keys[i].comment); - cbkey.ssh_version = 1; - - /* Decode public blob into a key in order to fingerprint it */ - RSAKey rkey; - memset(&rkey, 0, sizeof(rkey)); - { - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, kl1->keys[i].blob); - get_rsa_ssh1_pub(src, &rkey, RSA_SSH1_EXPONENT_FIRST); - if (get_err(src)) { - *retstr = dupstr( - "Received an invalid SSH-1 key from agent"); - goto out; - } - } - char **fingerprints = rsa_ssh1_fake_all_fingerprints(&rkey); - freersakey(&rkey); - - callback(callback_ctx, fingerprints, cbkey.comment, - kl1->keys[i].flags, &cbkey); - - strbuf_free(cbkey.blob); - sfree(cbkey.comment); - ssh2_free_all_fingerprints(fingerprints); - } - } - - if (kl2) { - for (size_t i = 0; i < kl2->nkeys; i++) { - cbkey.blob = strbuf_dup(kl2->keys[i].blob); - cbkey.comment = mkstr(kl2->keys[i].comment); - cbkey.ssh_version = 2; - - char **fingerprints = - ssh2_all_fingerprints_for_blob(kl2->keys[i].blob); - - callback(callback_ctx, fingerprints, cbkey.comment, - kl2->keys[i].flags, &cbkey); - - ssh2_free_all_fingerprints(fingerprints); - sfree(cbkey.comment); - strbuf_free(cbkey.blob); - } - } - - *retstr = NULL; - toret = PAGEANT_ACTION_OK; - out: - if (kl1) - keylist_free(kl1); - if (kl2) - keylist_free(kl2); - return toret; -} - -int pageant_delete_key(struct pageant_pubkey *key, char **retstr) -{ - PageantClientOp *pco = pageant_client_op_new(); - - if (key->ssh_version == 1) { - put_byte(pco, SSH1_AGENTC_REMOVE_RSA_IDENTITY); - put_data(pco, key->blob->s, key->blob->len); - } else { - put_byte(pco, SSH2_AGENTC_REMOVE_IDENTITY); - put_string(pco, key->blob->s, key->blob->len); - } - - unsigned reply = pageant_client_op_query(pco); - pageant_client_op_free(pco); - - if (reply != SSH_AGENT_SUCCESS) { - *retstr = dupstr("Agent failed to delete key"); - return PAGEANT_ACTION_FAILURE; - } else { - *retstr = NULL; - return PAGEANT_ACTION_OK; - } -} - -int pageant_delete_all_keys(char **retstr) -{ - PageantClientOp *pco; - unsigned reply; - - pco = pageant_client_op_new(); - put_byte(pco, SSH2_AGENTC_REMOVE_ALL_IDENTITIES); - reply = pageant_client_op_query(pco); - pageant_client_op_free(pco); - if (reply != SSH_AGENT_SUCCESS) { - *retstr = dupstr("Agent failed to delete SSH-2 keys"); - return PAGEANT_ACTION_FAILURE; - } - - pco = pageant_client_op_new(); - put_byte(pco, SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES); - reply = pageant_client_op_query(pco); - pageant_client_op_free(pco); - if (reply != SSH_AGENT_SUCCESS) { - *retstr = dupstr("Agent failed to delete SSH-1 keys"); - return PAGEANT_ACTION_FAILURE; - } - - *retstr = NULL; - return PAGEANT_ACTION_OK; -} - -int pageant_reencrypt_key(struct pageant_pubkey *key, char **retstr) -{ - PageantClientOp *pco = pageant_client_op_new(); - - if (key->ssh_version == 1) { - *retstr = dupstr("Can't re-encrypt an SSH-1 key"); - pageant_client_op_free(pco); - return PAGEANT_ACTION_FAILURE; - } else { - put_byte(pco, SSH2_AGENTC_EXTENSION); - put_stringpl(pco, extension_names[EXT_REENCRYPT]); - put_string(pco, key->blob->s, key->blob->len); - } - - unsigned reply = pageant_client_op_query(pco); - pageant_client_op_free(pco); - - if (reply != SSH_AGENT_SUCCESS) { - if (reply == SSH_AGENT_FAILURE) { - /* The agent didn't understand the protocol extension at all. */ - *retstr = dupstr("Agent doesn't support encrypted keys"); - } else { - *retstr = dupstr("Agent failed to re-encrypt key"); - } - return PAGEANT_ACTION_FAILURE; - } else { - *retstr = NULL; - return PAGEANT_ACTION_OK; - } -} - -int pageant_reencrypt_all_keys(char **retstr) -{ - PageantClientOp *pco = pageant_client_op_new(); - put_byte(pco, SSH2_AGENTC_EXTENSION); - put_stringpl(pco, extension_names[EXT_REENCRYPT_ALL]); - unsigned reply = pageant_client_op_query(pco); - uint32_t failures = get_uint32(pco); - pageant_client_op_free(pco); - if (reply != SSH_AGENT_SUCCESS) { - if (reply == SSH_AGENT_FAILURE) { - /* The agent didn't understand the protocol extension at all. */ - *retstr = dupstr("Agent doesn't support encrypted keys"); - } else { - *retstr = dupstr("Agent failed to re-encrypt any keys"); - } - return PAGEANT_ACTION_FAILURE; - } else if (failures == 1) { - /* special case for English grammar */ - *retstr = dupstr("1 key remains unencrypted"); - return PAGEANT_ACTION_WARNING; - } else if (failures > 0) { - *retstr = dupprintf("%"PRIu32" keys remain unencrypted", failures); - return PAGEANT_ACTION_WARNING; - } else { - *retstr = NULL; - return PAGEANT_ACTION_OK; - } -} - -int pageant_sign(struct pageant_pubkey *key, ptrlen message, strbuf *out, - uint32_t flags, char **retstr) -{ - PageantClientOp *pco = pageant_client_op_new(); - put_byte(pco, SSH2_AGENTC_SIGN_REQUEST); - put_string(pco, key->blob->s, key->blob->len); - put_stringpl(pco, message); - put_uint32(pco, flags); - unsigned reply = pageant_client_op_query(pco); - ptrlen signature = get_string(pco); - - if (reply == SSH2_AGENT_SIGN_RESPONSE && !get_err(pco)) { - *retstr = NULL; - put_datapl(out, signature); - pageant_client_op_free(pco); - return PAGEANT_ACTION_OK; - } else { - *retstr = dupstr("Agent failed to create signature"); - pageant_client_op_free(pco); - return PAGEANT_ACTION_FAILURE; - } -} - -struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key) -{ - struct pageant_pubkey *ret = snew(struct pageant_pubkey); - ret->blob = strbuf_new(); - put_data(ret->blob, key->blob->s, key->blob->len); - ret->comment = key->comment ? dupstr(key->comment) : NULL; - ret->ssh_version = key->ssh_version; - return ret; -} - -void pageant_pubkey_free(struct pageant_pubkey *key) -{ - sfree(key->comment); - strbuf_free(key->blob); - sfree(key); -} diff --git a/pageant.h b/pageant.h deleted file mode 100644 index fe6f2ff9f..000000000 --- a/pageant.h +++ /dev/null @@ -1,257 +0,0 @@ -/* - * pageant.h: header for pageant.c. - */ - -#include - -/* - * Upper limit on length of any agent message. Used as a basic sanity - * check on messages' length fields, and used by the Windows Pageant - * client IPC to decide how large a file mapping to allocate. - */ -#define AGENT_MAX_MSGLEN 262144 - -typedef struct PageantClientVtable PageantClientVtable; -typedef struct PageantClient PageantClient; -typedef struct PageantClientInfo PageantClientInfo; -typedef struct PageantClientRequestId PageantClientRequestId; -typedef struct PageantClientDialogId PageantClientDialogId; -struct PageantClient { - const struct PageantClientVtable *vt; - PageantClientInfo *info; /* used by the central Pageant code */ - - /* Setting this flag prevents the 'log' vtable entry from ever - * being called, so that it's safe to make it NULL. This also - * allows optimisations in the core code (it can avoid entire - * loops that are only used for logging purposes). So you can also - * set it dynamically if you find out at run time that you're not - * doing logging. */ - bool suppress_logging; -}; -struct PageantClientVtable { - void (*log)(PageantClient *pc, PageantClientRequestId *reqid, - const char *fmt, va_list ap); - void (*got_response)(PageantClient *pc, PageantClientRequestId *reqid, - ptrlen response); - bool (*ask_passphrase)(PageantClient *pc, PageantClientDialogId *dlgid, - const char *key_comment); -}; - -static inline void pageant_client_log_v( - PageantClient *pc, PageantClientRequestId *reqid, - const char *fmt, va_list ap) -{ - if (!pc->suppress_logging) - pc->vt->log(pc, reqid, fmt, ap); -} -static inline PRINTF_LIKE(3, 4) void pageant_client_log( - PageantClient *pc, PageantClientRequestId *reqid, const char *fmt, ...) -{ - if (!pc->suppress_logging) { - va_list ap; - va_start(ap, fmt); - pc->vt->log(pc, reqid, fmt, ap); - va_end(ap); - } -} -static inline void pageant_client_got_response( - PageantClient *pc, PageantClientRequestId *reqid, ptrlen response) -{ pc->vt->got_response(pc, reqid, response); } -static inline bool pageant_client_ask_passphrase( - PageantClient *pc, PageantClientDialogId *dlgid, const char *comment) -{ return pc->vt->ask_passphrase(pc, dlgid, comment); } - -/* PageantClientRequestId is used to match up responses to the agent - * requests they refer to. A client may allocate one of these for each - * call to pageant_handle_request, (probably as a subfield of some - * larger struct on the client side) and expect the same pointer to be - * passed back in pageant_client_got_response. */ -struct PageantClientRequestId { int unused_; }; - -/* - * Initial setup. - */ -void pageant_init(void); - -/* - * Register and unregister PageantClients. This is necessary so that - * when a PageantClient goes away, any unfinished asynchronous - * requests can be cleaned up. - * - * pageant_register_client will fill in pc->id. The client itself - * should not touch that field. - */ -void pageant_register_client(PageantClient *pc); -void pageant_unregister_client(PageantClient *pc); - -/* - * The main agent function that answers messages. - * - * Expects a message/length pair as input, minus its initial length - * field but still with its type code on the front. - * - * When a response is ready, the got_response method in the - * PageantClient vtable will be passed it in the form of a ptrlen, - * again minus its length field. - */ -void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid, - ptrlen msg); - -/* - * Send the core Pageant code a response to a passphrase request. - */ -void pageant_passphrase_request_success(PageantClientDialogId *dlgid, - ptrlen passphrase); -void pageant_passphrase_request_refused(PageantClientDialogId *dlgid); - -/* - * Construct a list of public keys, just as the two LIST_IDENTITIES - * requests would have returned them. - */ -void pageant_make_keylist1(BinarySink *); -void pageant_make_keylist2(BinarySink *); - -/* - * Accessor functions for Pageant's internal key lists, used by GUI - * Pageant, to count the keys, to delete a key, or to re-encrypt a - * decrypted-on-demand key (SSH-2 only). - */ -int pageant_count_ssh1_keys(void); -int pageant_count_ssh2_keys(void); -bool pageant_delete_nth_ssh1_key(int i); -bool pageant_delete_nth_ssh2_key(int i); -bool pageant_reencrypt_nth_ssh2_key(int i); -void pageant_delete_all(void); -void pageant_reencrypt_all(void); - -/* - * This callback must be provided by the Pageant front end code. - * pageant_handle_msg calls it to indicate that the message it's just - * handled has changed the list of keys held by the agent. Front ends - * which expose that key list through dedicated UI may need to refresh - * that UI's state in this function; other front ends can leave it - * empty. - */ -void keylist_update(void); - -/* - * Functions to establish a listening socket speaking the SSH agent - * protocol. Call pageant_listener_new() to set up a state; then - * create a socket using the returned Plug; then call - * pageant_listener_got_socket() to give the listening state its own - * socket pointer. Also, provide a logging function later if you want - * to. - */ -typedef struct PageantListenerClientVtable PageantListenerClientVtable; -typedef struct PageantListenerClient PageantListenerClient; -struct PageantListenerClient { - const PageantListenerClientVtable *vt; - /* suppress_logging flag works similarly to the one in - * PageantClient, but it is only read when a new connection comes - * in. So if you do need to change it in mid-run, expect existing - * agent connections to still use the old value. */ - bool suppress_logging; -}; -struct PageantListenerClientVtable { - void (*log)(PageantListenerClient *, const char *fmt, va_list ap); - bool (*ask_passphrase)(PageantListenerClient *pc, - PageantClientDialogId *dlgid, - const char *key_comment); -}; - -static inline void pageant_listener_client_log_v( - PageantListenerClient *plc, const char *fmt, va_list ap) -{ - if (!plc->suppress_logging) - plc->vt->log(plc, fmt, ap); -} -static inline PRINTF_LIKE(2, 3) void pageant_listener_client_log( - PageantListenerClient *plc, const char *fmt, ...) -{ - if (!plc->suppress_logging) { - va_list ap; - va_start(ap, fmt); - plc->vt->log(plc, fmt, ap); - va_end(ap); - } -} -static inline bool pageant_listener_client_ask_passphrase( - PageantListenerClient *plc, PageantClientDialogId *dlgid, - const char *comment) -{ return plc->vt->ask_passphrase(plc, dlgid, comment); } - -struct pageant_listen_state; -struct pageant_listen_state *pageant_listener_new( - Plug **plug, PageantListenerClient *plc); -void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket *); -void pageant_listener_free(struct pageant_listen_state *pl); - -/* - * Functions to perform specific key actions, either as a client of an - * ssh-agent running elsewhere, or directly on the agent state in this - * process. (On at least one platform we want to do this in an - * agnostic way between the two situations.) - * - * pageant_add_keyfile() is used to load a private key from a file and - * add it to the agent. Initially, you should call it with passphrase - * NULL, and it will check if the key is already in the agent, and - * whether a passphrase is required. Return values are given in the - * enum below. On return, *retstr will either be NULL, or a - * dynamically allocated string containing a key comment or an error - * message. - * - * pageant_add_keyfile() also remembers passphrases with which it's - * successfully decrypted keys (because if you try to add multiple - * keys in one go, you might very well have used the same passphrase - * for keys that have the same trust properties). Call - * pageant_forget_passphrases() to get rid of them all. - */ -enum { - PAGEANT_ACTION_OK, /* success; no further action needed */ - PAGEANT_ACTION_FAILURE, /* failure; *retstr is error message */ - PAGEANT_ACTION_NEED_PP, /* need passphrase: *retstr is key comment */ - PAGEANT_ACTION_WARNING, /* success but with a warning message; - * *retstr is warning message */ -}; -int pageant_add_keyfile(Filename *filename, const char *passphrase, - char **retstr, bool add_encrypted); -void pageant_forget_passphrases(void); - -struct pageant_pubkey { - /* Everything needed to identify a public key found by - * pageant_enum_keys and pass it back to the agent or other code - * later */ - strbuf *blob; - char *comment; - int ssh_version; -}; -struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key); -void pageant_pubkey_free(struct pageant_pubkey *key); - -typedef void (*pageant_key_enum_fn_t)(void *ctx, char **fingerprints, - const char *comment, uint32_t ext_flags, - struct pageant_pubkey *key); -int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, - char **retstr); -int pageant_delete_key(struct pageant_pubkey *key, char **retstr); -int pageant_delete_all_keys(char **retstr); -int pageant_reencrypt_key(struct pageant_pubkey *key, char **retstr); -int pageant_reencrypt_all_keys(char **retstr); -int pageant_sign(struct pageant_pubkey *key, ptrlen message, strbuf *out, - uint32_t flags, char **retstr); - -/* - * Definitions for agent protocol extensions. - */ -#define PUTTYEXT(base) base "@putty.projects.tartarus.org" - -#define KNOWN_EXTENSIONS(X) \ - X(EXT_QUERY, "query") \ - X(EXT_ADD_PPK, PUTTYEXT("add-ppk")) \ - X(EXT_REENCRYPT, PUTTYEXT("reencrypt")) \ - X(EXT_REENCRYPT_ALL, PUTTYEXT("reencrypt-all")) \ - X(EXT_LIST_EXTENDED, PUTTYEXT("list-extended")) \ - /* end of list */ - -#define LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE 1 -#define LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY 2 diff --git a/pinger.c b/pinger.c deleted file mode 100644 index 7bf9b41bb..000000000 --- a/pinger.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - * pinger.c: centralised module that deals with sending SS_PING - * keepalives, to avoid replicating this code in multiple backends. - */ - -#include "putty.h" - -struct Pinger { - int interval; - bool pending; - unsigned long when_set, next; - Backend *backend; -}; - -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) { - backend_special(pinger->backend, SS_PING, 0); - pinger->pending = false; - pinger_schedule(pinger); - } -} - -static void pinger_schedule(Pinger *pinger) -{ - unsigned long 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->when_set) < (pinger->next - pinger->when_set)) { - pinger->next = next; - pinger->when_set = timing_last_clock(); - pinger->pending = true; - } -} - -Pinger *pinger_new(Conf *conf, Backend *backend) -{ - Pinger *pinger = snew(Pinger); - - pinger->interval = conf_get_int(conf, CONF_ping_interval); - pinger->pending = false; - pinger->backend = backend; - 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/proxy/cproxy.c b/proxy/cproxy.c deleted file mode 100644 index 40a2f609f..000000000 --- a/proxy/cproxy.c +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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 - -#include "putty.h" -#include "ssh.h" /* For MD5 support */ -#include "network.h" -#include "proxy.h" -#include "marshal.h" - -const bool socks5_chap_available = true; -const bool http_digest_available = true; - -strbuf *chap_response(ptrlen challenge, ptrlen password) -{ - strbuf *sb = strbuf_new_nm(); - const ssh2_macalg *alg = &ssh_hmac_md5; - mac_simple(alg, password, challenge, strbuf_append(sb, alg->len)); - return sb; -} - -static void BinarySink_put_hex_data(BinarySink *bs, const void *vptr, - size_t len) -{ - const unsigned char *p = (const unsigned char *)vptr; - const char *hexdigits = "0123456789abcdef"; - while (len-- > 0) { - unsigned c = *p++; - put_byte(bs, hexdigits[0xF & (c >> 4)]); - put_byte(bs, hexdigits[0xF & (c )]); - } -} - -#define put_hex_data(bs, p, len) \ - BinarySink_put_hex_data(BinarySink_UPCAST(bs), p, len) - -const char *const httphashnames[] = { - #define DECL_ARRAY(id, str, alg, bits, accepted) str, - HTTP_DIGEST_HASHES(DECL_ARRAY) - #undef DECL_ARRAY -}; - -const bool httphashaccepted[] = { - #define DECL_ARRAY(id, str, alg, bits, accepted) accepted, - HTTP_DIGEST_HASHES(DECL_ARRAY) - #undef DECL_ARRAY -}; - -static const ssh_hashalg *const httphashalgs[] = { - #define DECL_ARRAY(id, str, alg, bits, accepted) alg, - HTTP_DIGEST_HASHES(DECL_ARRAY) - #undef DECL_ARRAY -}; -static const size_t httphashlengths[] = { - #define DECL_ARRAY(id, str, alg, bits, accepted) bits/8, - HTTP_DIGEST_HASHES(DECL_ARRAY) - #undef DECL_ARRAY -}; - -void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password, - ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop, - ptrlen nonce, ptrlen opaque, uint32_t nonce_count, - HttpDigestHash hash, bool hash_username) -{ - unsigned char a1hash[MAX_HASH_LEN]; - unsigned char a2hash[MAX_HASH_LEN]; - unsigned char rsphash[MAX_HASH_LEN]; - const ssh_hashalg *alg = httphashalgs[hash]; - size_t hashlen = httphashlengths[hash]; - - unsigned char ncbuf[4]; - PUT_32BIT_MSB_FIRST(ncbuf, nonce_count); - - unsigned char client_nonce_raw[33]; - random_read(client_nonce_raw, lenof(client_nonce_raw)); - char client_nonce_base64[lenof(client_nonce_raw) / 3 * 4]; - for (unsigned i = 0; i < lenof(client_nonce_raw)/3; i++) - base64_encode_atom(client_nonce_raw + 3*i, 3, - client_nonce_base64 + 4*i); - - /* - * RFC 7616 section 3.4.2: the hash "A1" is a hash of - * username:realm:password (in the absence of hash names like - * "MD5-sess" which as far as I know don't sensibly apply to - * proxies and HTTP CONNECT). - */ - ssh_hash *h = ssh_hash_new(alg); - put_datapl(h, username); - put_byte(h, ':'); - put_datapl(h, realm); - put_byte(h, ':'); - put_datapl(h, password); - ssh_hash_digest_nondestructive(h, a1hash); - - /* - * RFC 7616 section 3.4.3: the hash "A2" is a hash of method:uri - * (in the absence of more interesting quality-of-protection - * schemes than plain "auth" - e.g. "auth-int" hashes the entire - * document as well - which again I don't think make sense in the - * context of proxies and CONNECT). - */ - ssh_hash_reset(h); - put_datapl(h, method); - put_byte(h, ':'); - put_datapl(h, uri); - ssh_hash_digest_nondestructive(h, a2hash); - - /* - * RFC 7616 section 3.4.1: the overall output hash in the - * "response" parameter of the authorization header is a hash of - * A1:nonce:nonce-count:client-nonce:qop:A2, where A1 and A2 are - * the hashes computed above. - */ - ssh_hash_reset(h); - put_hex_data(h, a1hash, hashlen); - put_byte(h, ':'); - put_datapl(h, nonce); - put_byte(h, ':'); - put_hex_data(h, ncbuf, 4); - put_byte(h, ':'); - put_data(h, client_nonce_base64, lenof(client_nonce_base64)); - put_byte(h, ':'); - put_datapl(h, qop); - put_byte(h, ':'); - put_hex_data(h, a2hash, hashlen); - ssh_hash_final(h, rsphash); - - /* - * Now construct the output header (everything after the initial - * "Proxy-Authorization: Digest ") and write it to the provided - * BinarySink. - */ - put_datalit(bs, "username=\""); - if (hash_username) { - /* - * RFC 7616 section 3.4.4: if we're hashing the username, we - * actually hash username:realm (like a truncated version of - * A1 above). - */ - ssh_hash *h = ssh_hash_new(alg); - put_datapl(h, username); - put_byte(h, ':'); - put_datapl(h, realm); - ssh_hash_final(h, a1hash); - put_hex_data(bs, a1hash, hashlen); - } else { - put_datapl(bs, username); - } - put_datalit(bs, "\", realm=\""); - put_datapl(bs, realm); - put_datalit(bs, "\", uri=\""); - put_datapl(bs, uri); - put_datalit(bs, "\", algorithm="); - put_dataz(bs, httphashnames[hash]); - put_datalit(bs, ", nonce=\""); - put_datapl(bs, nonce); - put_datalit(bs, "\", nc="); - put_hex_data(bs, ncbuf, 4); - put_datalit(bs, ", cnonce=\""); - put_data(bs, client_nonce_base64, lenof(client_nonce_base64)); - put_datalit(bs, "\", qop="); - put_datapl(bs, qop); - put_datalit(bs, ", response=\""); - put_hex_data(bs, rsphash, hashlen); - put_datalit(bs, "\""); - - if (opaque.ptr) { - put_datalit(bs, ", opaque=\""); - put_datapl(bs, opaque); - put_datalit(bs, "\""); - } - - if (hash_username) { - put_datalit(bs, ", userhash=true"); - } - - smemclr(a1hash, lenof(a1hash)); - smemclr(a2hash, lenof(a2hash)); - smemclr(rsphash, lenof(rsphash)); - smemclr(client_nonce_raw, lenof(client_nonce_raw)); - smemclr(client_nonce_base64, lenof(client_nonce_base64)); -} diff --git a/proxy/cproxy.h b/proxy/cproxy.h deleted file mode 100644 index 34058dd8e..000000000 --- a/proxy/cproxy.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Header for the interaction between proxy.c and cproxy.c. Separated - * from proxy.h proper so that testcrypt can include it conveniently. - */ - -extern const bool socks5_chap_available; -strbuf *chap_response(ptrlen challenge, ptrlen password); -extern const bool http_digest_available; - -/* - * List macro for the various hash functions defined for HTTP Digest. - * - * Of these, MD5 is the original one; SHA-256 is unambiguous; but - * SHA-512-256 seems to be controversial. - * - * RFC 7616 doesn't provide a normative reference, or any text - * explaining what they mean by it. They apparently expect you to - * already know. The problem with that is that there are two plausible - * things they _might_ have meant: - * - * 1. Ordinary SHA-512, truncated to 256 bits by discarding the - * second half of the hash output, per FIPS 180-4 section 7 (which - * says that in general it's OK to truncate hash functions like - * that if you need to). FIPS 180-4 assigns no particular specific - * spelling to this kind of truncated hash. - * - * 2. The same except that the initial state of the SHA-512 algorithm - * is reset to a different 512-bit vector to ensure that it's a - * distinguishable hash function in its own right, per FIPS 180-4 - * section 6.7 (which in turn refers to section 5.3.6.2 for the - * actual initial values). FIPS 180-4 spells this "SHA-512/256". - * - * The text of RFC 7616 is totally silent as to which of these they - * meant. Their spelling is inconsistent: the protocol identifier is - * "SHA-512-256", but in some places in the RFC they say - * "SHA-512/256", matching FIPS's spelling for the hash in option 2 - * above. On the other hand, the example authentication exchange in - * section 3.9.2 of the RFC contains hashes that are consistent with - * option 1 above (a truncation of plain SHA-512). - * - * Erratum 4897, https://www.rfc-editor.org/errata/eid4897, points out - * this ambiguity, and suggests correcting the example exchange to be - * consistent with option 2. However, as of 2021-11-27, that erratum - * is shown on the RFC Editor website in state "Reported", with no - * response (positive _or_ negative) from the RFC authors or anyone - * else. (And it was reported in 2016, so it's not as if they haven't - * had time.) - * - * So, which hash should we implement? Perhaps there's a consensus - * among existing implementations in the wild? - * - * I rigged up an HTTP server to present a SHA-512-256 Digest auth - * request, and tried various HTTP clients against it. The only HTTP - * client I found that accepts 'algorithm="SHA-512-256"' and sends - * back an auth attempt quoting the same hash is curl - and curl, - * bizarrely, seems to treat "SHA-512-256" as _neither_ of the above - * options, but as simply an alias for SHA-256! - * - * Therefore, I think the only safe answer is to refuse to support - * that hash at all: it's too confusing. - * - * However, I keep it in the list of hashes here, so that we can check - * the test case from RFC 7616, because that test case is also the - * only test of username hashing. So we reject it in proxy/http.c, but - * accept it in the internal function http_digest_response(), and - * treat it as option 1 (truncated SHA-512). - * - * Therefore, the parameters to each invocation of X in the following - * list macro are: - * - * - internal enum id for the hash - * - protocol identifier string - * - algorithm to use for computing it (as a const ssh_hashalg *) - * - length to truncate the output to - * - whether we accept it in http.c or not. - * - * Finally, the ordering of the accepted hashes is our preference - * order among them if the server offers a choice. - */ -#define HTTP_DIGEST_HASHES(X) \ - X(HTTP_DIGEST_MD5, "MD5", &ssh_md5, 128, true) \ - X(HTTP_DIGEST_SHA256, "SHA-256", &ssh_sha256, 256, true) \ - X(HTTP_DIGEST_SHA512_256, "SHA-512-256", &ssh_sha512, 256, false) \ - /* end of list */ - -typedef enum HttpDigestHash { - #define DECL_ENUM(id, str, alg, bits, accepted) id, - HTTP_DIGEST_HASHES(DECL_ENUM) - #undef DECL_ENUM - N_HTTP_DIGEST_HASHES -} HttpDigestHash; - -extern const char *const httphashnames[]; -extern const bool httphashaccepted[]; - -void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password, - ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop, - ptrlen nonce, ptrlen opaque, uint32_t nonce_count, - HttpDigestHash hash, bool hash_username); diff --git a/proxy/http.c b/proxy/http.c deleted file mode 100644 index 0738e37d6..000000000 --- a/proxy/http.c +++ /dev/null @@ -1,781 +0,0 @@ -/* - * HTTP CONNECT proxy negotiation. - */ - -#include "putty.h" -#include "network.h" -#include "proxy.h" -#include "sshcr.h" - -static bool read_line(bufchain *input, strbuf *output, bool is_header) -{ - char c; - - while (bufchain_try_fetch(input, &c, 1)) { - if (is_header && output->len > 0 && - output->s[output->len - 1] == '\n') { - /* - * A newline terminates the header, provided we're sure it - * is _not_ followed by a space or a tab. - */ - if (c != ' ' && c != '\t') - goto done; /* we have a complete header line */ - } else { - put_byte(output, c); - bufchain_consume(input, 1); - - if (!is_header && output->len > 0 && - output->s[output->len - 1] == '\n') { - /* If we're looking for just a line, not an HTTP - * header, then any newline terminates it. */ - goto done; - } - } - } - - return false; - - done: - strbuf_chomp(output, '\n'); - strbuf_chomp(output, '\r'); - return true; -} - -/* Types of HTTP authentication, in preference order. */ -typedef enum HttpAuthType { - AUTH_ERROR, /* if an HttpAuthDetails was never satisfactorily filled in */ - AUTH_NONE, /* if no auth header is seen, assume no auth required */ - AUTH_BASIC, /* username + password sent in clear (only keyless base64) */ - AUTH_DIGEST, /* cryptographic hash, most preferred if available */ -} HttpAuthType; - -typedef struct HttpAuthDetails { - HttpAuthType auth_type; - bool digest_nonce_was_stale; - HttpDigestHash digest_hash; - strbuf *realm, *nonce, *opaque, *error; - bool got_opaque; - bool hash_username; -} HttpAuthDetails; - -typedef struct HttpProxyNegotiator { - int crLine; - strbuf *response, *header, *token; - int http_status_pos; - size_t header_pos; - strbuf *username, *password; - int http_status; - bool connection_close; - HttpAuthDetails *next_auth; - bool try_auth_from_conf; - strbuf *uri; - uint32_t nonce_count; - prompts_t *prompts; - int username_prompt_index, password_prompt_index; - size_t content_length, chunk_length; - bool chunked_transfer; - ProxyNegotiator pn; -} HttpProxyNegotiator; - -static inline HttpAuthDetails *auth_error(HttpAuthDetails *d, - const char *fmt, ...) -{ - d->auth_type = AUTH_ERROR; - put_fmt(d->error, "Unable to parse auth header from HTTP proxy"); - if (fmt) { - va_list ap; - va_start(ap, fmt); - put_datalit(d->error, ": "); - put_fmtv(d->error, fmt, ap); - va_end(ap); - } - return d; -} - -static HttpAuthDetails *http_auth_details_new(void) -{ - HttpAuthDetails *d = snew(HttpAuthDetails); - memset(d, 0, sizeof(*d)); - d->realm = strbuf_new(); - d->nonce = strbuf_new(); - d->opaque = strbuf_new(); - d->error = strbuf_new(); - return d; -} - -static void http_auth_details_free(HttpAuthDetails *d) -{ - strbuf_free(d->realm); - strbuf_free(d->nonce); - strbuf_free(d->opaque); - strbuf_free(d->error); - sfree(d); -} - -static ProxyNegotiator *proxy_http_new(const ProxyNegotiatorVT *vt) -{ - HttpProxyNegotiator *s = snew(HttpProxyNegotiator); - memset(s, 0, sizeof(*s)); - s->pn.vt = vt; - s->response = strbuf_new(); - s->header = strbuf_new(); - s->token = strbuf_new(); - s->username = strbuf_new(); - s->password = strbuf_new_nm(); - s->uri = strbuf_new(); - s->nonce_count = 0; - /* - * Always start with a CONNECT request containing no auth. If the - * proxy rejects that, it will tell us what kind of auth it would - * prefer. - */ - s->next_auth = http_auth_details_new(); - s->next_auth->auth_type = AUTH_NONE; - return &s->pn; -} - -static void proxy_http_free(ProxyNegotiator *pn) -{ - HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn); - strbuf_free(s->response); - strbuf_free(s->header); - strbuf_free(s->token); - strbuf_free(s->username); - strbuf_free(s->password); - strbuf_free(s->uri); - http_auth_details_free(s->next_auth); - if (s->prompts) - free_prompts(s->prompts); - sfree(s); -} - -#define HTTP_HEADER_LIST(X) \ - X(HDR_CONNECTION, "Connection") \ - X(HDR_CONTENT_LENGTH, "Content-Length") \ - X(HDR_TRANSFER_ENCODING, "Transfer-Encoding") \ - X(HDR_PROXY_AUTHENTICATE, "Proxy-Authenticate") \ - X(HDR_PROXY_CONNECTION, "Proxy-Connection") \ - /* end of list */ - -typedef enum HttpHeader { - #define ENUM_DEF(id, string) id, - HTTP_HEADER_LIST(ENUM_DEF) - #undef ENUM_DEF - HDR_UNKNOWN -} HttpHeader; - -static inline bool is_whitespace(char c) -{ - return (c == ' ' || c == '\t' || c == '\n'); -} - -static inline bool is_separator(char c) -{ - return (c == '(' || c == ')' || c == '<' || c == '>' || c == '@' || - c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' || - c == '/' || c == '[' || c == ']' || c == '?' || c == '=' || - c == '{' || c == '}'); -} - -#define HTTP_SEPARATORS - -static bool get_end_of_header(HttpProxyNegotiator *s) -{ - size_t pos = s->header_pos; - - while (pos < s->header->len && is_whitespace(s->header->s[pos])) - pos++; - - if (pos == s->header->len) { - s->header_pos = pos; - return true; - } - - return false; -} - -static bool get_token(HttpProxyNegotiator *s) -{ - size_t pos = s->header_pos; - - while (pos < s->header->len && is_whitespace(s->header->s[pos])) - pos++; - - if (pos == s->header->len) - return false; /* end of string */ - - if (is_separator(s->header->s[pos])) - return false; - - strbuf_clear(s->token); - while (pos < s->header->len && - !is_whitespace(s->header->s[pos]) && - !is_separator(s->header->s[pos])) - put_byte(s->token, s->header->s[pos++]); - - s->header_pos = pos; - return true; -} - -static bool get_separator(HttpProxyNegotiator *s, char sep) -{ - size_t pos = s->header_pos; - - while (pos < s->header->len && is_whitespace(s->header->s[pos])) - pos++; - - if (pos == s->header->len) - return false; /* end of string */ - - if (s->header->s[pos] != sep) - return false; - - s->header_pos = ++pos; - return true; -} - -static bool get_quoted_string(HttpProxyNegotiator *s) -{ - size_t pos = s->header_pos; - - while (pos < s->header->len && is_whitespace(s->header->s[pos])) - pos++; - - if (pos == s->header->len) - return false; /* end of string */ - - if (s->header->s[pos] != '"') - return false; - pos++; - - strbuf_clear(s->token); - while (pos < s->header->len && s->header->s[pos] != '"') { - if (s->header->s[pos] == '\\') { - /* Backslash makes the next char literal, even if it's " or \ */ - pos++; - if (pos == s->header->len) - return false; /* unexpected end of string */ - } - put_byte(s->token, s->header->s[pos++]); - } - - if (pos == s->header->len) - return false; /* no closing quote */ - pos++; - - s->header_pos = pos; - return true; -} - -static HttpAuthDetails *parse_http_auth_header(HttpProxyNegotiator *s) -{ - HttpAuthDetails *d = http_auth_details_new(); - - /* Default hash for HTTP Digest is MD5, if none specified explicitly */ - d->digest_hash = HTTP_DIGEST_MD5; - - if (!get_token(s)) - return auth_error(d, "parse error"); - - if (!stricmp(s->token->s, "Basic")) { - /* For Basic authentication, we don't need anything else. The - * realm string is not required for the protocol. */ - d->auth_type = AUTH_BASIC; - return d; - } - - if (!stricmp(s->token->s, "Digest")) { - /* Parse all the additional parts of the Digest header. */ - if (!http_digest_available) - return auth_error(d, "Digest authentication not supported"); - - /* Parse the rest of the Digest header */ - while (true) { - if (!get_token(s)) - return auth_error(d, "parse error in Digest header"); - - if (!stricmp(s->token->s, "realm")) { - if (!get_separator(s, '=') || - !get_quoted_string(s)) - return auth_error(d, "parse error in Digest realm field"); - put_datapl(d->realm, ptrlen_from_strbuf(s->token)); - } else if (!stricmp(s->token->s, "nonce")) { - if (!get_separator(s, '=') || - !get_quoted_string(s)) - return auth_error(d, "parse error in Digest nonce field"); - put_datapl(d->nonce, ptrlen_from_strbuf(s->token)); - } else if (!stricmp(s->token->s, "opaque")) { - if (!get_separator(s, '=') || - !get_quoted_string(s)) - return auth_error(d, "parse error in Digest opaque field"); - put_datapl(d->opaque, - ptrlen_from_strbuf(s->token)); - d->got_opaque = true; - } else if (!stricmp(s->token->s, "stale")) { - if (!get_separator(s, '=') || - !get_token(s)) - return auth_error(d, "parse error in Digest stale field"); - d->digest_nonce_was_stale = !stricmp( - s->token->s, "true"); - } else if (!stricmp(s->token->s, "userhash")) { - if (!get_separator(s, '=') || - !get_token(s)) - return auth_error(d, "parse error in Digest userhash " - "field"); - d->hash_username = !stricmp(s->token->s, "true"); - } else if (!stricmp(s->token->s, "algorithm")) { - if (!get_separator(s, '=') || - (!get_token(s) && !get_quoted_string(s))) - return auth_error(d, "parse error in Digest algorithm " - "field"); - bool found = false; - size_t i; - - for (i = 0; i < N_HTTP_DIGEST_HASHES; i++) { - if (!stricmp(s->token->s, httphashnames[i])) { - found = true; - break; - } - } - - if (!found) { - /* We don't even recognise the name */ - return auth_error(d, "Digest hash algorithm '%s' not " - "recognised", s->token->s); - } - - if (!httphashaccepted[i]) { - /* We do recognise the name but we - * don't like it (see comment in cproxy.h) */ - return auth_error(d, "Digest hash algorithm '%s' not " - "supported", s->token->s); - } - - d->digest_hash = i; - } else if (!stricmp(s->token->s, "qop")) { - if (!get_separator(s, '=') || - !get_quoted_string(s)) - return auth_error(d, "parse error in Digest qop field"); - if (stricmp(s->token->s, "auth")) - return auth_error(d, "quality-of-protection type '%s' not " - "supported", s->token->s); - } else { - /* Ignore any other auth-param */ - if (!get_separator(s, '=') || - (!get_quoted_string(s) && !get_token(s))) - return auth_error(d, "parse error in Digest header"); - } - - if (get_end_of_header(s)) - break; - if (!get_separator(s, ',')) - return auth_error(d, "parse error in Digest header"); - } - d->auth_type = AUTH_DIGEST; - return d; - } - - return auth_error(d, "authentication type '%s' not supported", - s->token->s); -} - -static void proxy_http_process_queue(ProxyNegotiator *pn) -{ - HttpProxyNegotiator *s = container_of(pn, HttpProxyNegotiator, pn); - - crBegin(s->crLine); - - /* - * Initialise our username and password strbufs from the Conf. - */ - put_dataz(s->username, conf_get_str(pn->ps->conf, CONF_proxy_username)); - put_dataz(s->password, conf_get_str(pn->ps->conf, CONF_proxy_password)); - if (s->username->len || s->password->len) - s->try_auth_from_conf = true; - - /* - * Set up the host:port string we're trying to connect to, also - * used as the URI string in HTTP Digest auth. - */ - { - char dest[512]; - sk_getaddr(pn->ps->remote_addr, dest, lenof(dest)); - put_fmt(s->uri, "%s:%d", dest, pn->ps->remote_port); - } - - while (true) { - /* - * Standard prefix for the HTTP CONNECT request. - */ - put_fmt(pn->output, - "CONNECT %s HTTP/1.1\r\n" - "Host: %s\r\n", s->uri->s, s->uri->s); - - /* - * Add an auth header, if we're planning to this time round. - */ - if (s->next_auth->auth_type == AUTH_BASIC) { - put_datalit(pn->output, "Proxy-Authorization: Basic "); - - strbuf *base64_input = strbuf_new_nm(); - put_datapl(base64_input, ptrlen_from_strbuf(s->username)); - put_byte(base64_input, ':'); - put_datapl(base64_input, ptrlen_from_strbuf(s->password)); - - char base64_output[4]; - for (size_t i = 0, e = base64_input->len; i < e; i += 3) { - base64_encode_atom(base64_input->u + i, - e-i > 3 ? 3 : e-i, base64_output); - put_data(pn->output, base64_output, 4); - } - strbuf_free(base64_input); - smemclr(base64_output, sizeof(base64_output)); - put_datalit(pn->output, "\r\n"); - } else if (s->next_auth->auth_type == AUTH_DIGEST) { - put_datalit(pn->output, "Proxy-Authorization: Digest "); - - /* If we have a fresh nonce, reset the - * nonce count. Otherwise, keep incrementing it. */ - if (!ptrlen_eq_ptrlen(ptrlen_from_strbuf(s->token), - ptrlen_from_strbuf(s->next_auth->nonce))) - s->nonce_count = 0; - - http_digest_response(BinarySink_UPCAST(pn->output), - ptrlen_from_strbuf(s->username), - ptrlen_from_strbuf(s->password), - ptrlen_from_strbuf(s->next_auth->realm), - PTRLEN_LITERAL("CONNECT"), - ptrlen_from_strbuf(s->uri), - PTRLEN_LITERAL("auth"), - ptrlen_from_strbuf(s->next_auth->nonce), - (s->next_auth->got_opaque ? - ptrlen_from_strbuf(s->next_auth->opaque) : - make_ptrlen(NULL, 0)), - ++s->nonce_count, s->next_auth->digest_hash, - s->next_auth->hash_username); - put_datalit(pn->output, "\r\n"); - } - - /* - * Blank line to terminate the HTTP request. - */ - put_datalit(pn->output, "\r\n"); - crReturnV; - - s->content_length = 0; - s->chunked_transfer = false; - s->connection_close = false; - - /* - * Read and parse the HTTP status line, and check if it's a 2xx - * for success. - */ - strbuf_clear(s->response); - crMaybeWaitUntilV(read_line(pn->input, s->response, false)); - { - int maj_ver, min_ver, n_scanned; - n_scanned = sscanf( - s->response->s, "HTTP/%d.%d %n%d", - &maj_ver, &min_ver, &s->http_status_pos, &s->http_status); - - if (n_scanned < 3) { - pn->error = dupstr("HTTP response was absent or malformed"); - crStopV; - } - - if (maj_ver < 1 || (maj_ver == 1 && min_ver < 1)) { - /* Before HTTP/1.1, connections close by default */ - s->connection_close = true; - } - } - - if (s->http_status == 407) { - /* - * If this is going to be an auth request, we expect to - * see at least one Proxy-Authorization header offering us - * auth options. Start by preloading s->next_auth with a - * fallback error message, which will be used if nothing - * better is available. - */ - http_auth_details_free(s->next_auth); - s->next_auth = http_auth_details_new(); - auth_error(s->next_auth, "no Proxy-Authorization header seen in " - "HTTP 407 Proxy Authentication Required response"); - } - - /* - * Read the HTTP response header section. - */ - do { - strbuf_clear(s->header); - crMaybeWaitUntilV(read_line(pn->input, s->header, true)); - s->header_pos = 0; - - if (!get_token(s)) { - /* Possibly we ought to panic if we see an HTTP header - * we can't make any sense of at all? But whatever, - * ignore it and hope the next one makes more sense */ - continue; - } - - /* Parse the header name */ - HttpHeader hdr = HDR_UNKNOWN; - { - #define CHECK_HEADER(id, string) \ - if (!stricmp(s->token->s, string)) hdr = id; - HTTP_HEADER_LIST(CHECK_HEADER); - #undef CHECK_HEADER - } - - if (!get_separator(s, ':')) - continue; - - if (hdr == HDR_CONTENT_LENGTH) { - if (!get_token(s)) - continue; - s->content_length = strtoumax(s->token->s, NULL, 10); - } else if (hdr == HDR_TRANSFER_ENCODING) { - /* - * The Transfer-Encoding header value should be a - * comma-separated list of keywords including - * "chunked", "deflate" and "gzip". We parse it in the - * most superficial way, by just looking for "chunked" - * and ignoring everything else. - * - * It's OK to do that because we're not actually - * _using_ the error document - we only have to skip - * over it to find the end of the HTTP response. So we - * don't care if it's gzipped or not. - */ - while (get_token(s)) { - if (!stricmp(s->token->s, "chunked")) - s->chunked_transfer = true; - } - } else if (hdr == HDR_CONNECTION || - hdr == HDR_PROXY_CONNECTION) { - if (!get_token(s)) - continue; - if (!stricmp(s->token->s, "close")) - s->connection_close = true; - else if (!stricmp(s->token->s, "keep-alive")) - s->connection_close = false; - } else if (hdr == HDR_PROXY_AUTHENTICATE) { - HttpAuthDetails *auth = parse_http_auth_header(s); - - /* - * See if we prefer this set of auth details to the - * previous one we had (either from a previous auth - * header, or the fallback when no auth header is - * provided at all). - */ - bool change; - - if (auth->auth_type != s->next_auth->auth_type) { - /* Use the preference order implied by the enum */ - change = auth->auth_type > s->next_auth->auth_type; - } else if (auth->auth_type == AUTH_DIGEST && - auth->digest_hash != s->next_auth->digest_hash) { - /* Choose based on the hash functions */ - change = auth->digest_hash > s->next_auth->digest_hash; - } else { - /* - * If in doubt, go with the later one of the - * headers. - * - * The main reason for this is so that an error in - * interpreting an auth header will supersede the - * default error we preload saying 'no header - * found', because that would be a particularly - * bad error to report if there _was_ one. - * - * But we're in a tie-breaking situation by now, - * so there's no other reason to choose - we might - * as well apply the same policy everywhere else - * too. - */ - change = true; - } - - if (change) { - http_auth_details_free(s->next_auth); - s->next_auth = auth; - } else { - http_auth_details_free(auth); - } - } - } while (s->header->len > 0); - - /* Read and ignore the entire response document */ - if (!s->chunked_transfer) { - /* Simple approach: read exactly Content-Length bytes */ - crMaybeWaitUntilV(bufchain_try_consume( - pn->input, s->content_length)); - } else { - /* Chunked transfer: read a sequence of - * \r\n\r\n chunks, terminating in one with - * zero length */ - do { - /* - * Expect a chunk length - */ - s->chunk_length = 0; - while (true) { - char c; - crMaybeWaitUntilV(bufchain_try_fetch_consume( - pn->input, &c, 1)); - if (c == '\r') { - continue; - } else if (c == '\n') { - break; - } else if ('0' <= c && c <= '9') { - s->chunk_length = s->chunk_length*16 + (c-'0'); - } else if ('A' <= c && c <= 'F') { - s->chunk_length = s->chunk_length*16 + (c-'A'+10); - } else if ('a' <= c && c <= 'f') { - s->chunk_length = s->chunk_length*16 + (c-'a'+10); - } else { - pn->error = dupprintf( - "Received bad character 0x%02X in chunk length " - "during HTTP chunked transfer encoding", - (unsigned)(unsigned char)c); - crStopV; - } - } - - /* - * Expect that many bytes of chunked data - */ - crMaybeWaitUntilV(bufchain_try_consume( - pn->input, s->chunk_length)); - - /* Now expect \r\n */ - { - char buf[2]; - crMaybeWaitUntilV(bufchain_try_fetch_consume( - pn->input, buf, 2)); - if (memcmp(buf, "\r\n", 2)) { - pn->error = dupprintf( - "Missing CRLF after chunk " - "during HTTP chunked transfer encoding"); - crStopV; - } - } - } while (s->chunk_length); - } - - if (200 <= s->http_status && s->http_status < 300) { - /* Any 2xx HTTP response means we're done */ - goto authenticated; - } else if (s->http_status == 407) { - /* 407 is Proxy Authentication Required, which we may be - * able to do something about. */ - if (s->connection_close) { - /* If we got 407 + connection closed, reconnect before - * sending our next request. */ - pn->reconnect = true; - } - - /* If the best we can do is report some kind of error from - * a Proxy-Auth header (or an error saying there wasn't - * one at all), and no successful parsing of an auth - * header superseded that, then just throw that error and - * die. */ - if (s->next_auth->auth_type == AUTH_ERROR) { - pn->error = dupstr(s->next_auth->error->s); - crStopV; - } - - /* If we have auth details from the Conf and haven't tried - * them yet, that's our first step. */ - if (s->try_auth_from_conf) { - s->try_auth_from_conf = false; - continue; - } - - /* If the server sent us stale="true" in a Digest auth - * header, that means we _don't_ need to request a new - * password yet; just try again with the existing details - * and the fresh nonce it sent us. */ - if (s->next_auth->digest_nonce_was_stale) - continue; - - /* Either we never had a password in the first place, or - * the one we already presented was rejected. We can only - * proceed from here if we have a way to ask the user - * questions. */ - if (!pn->itr) { - pn->error = dupprintf("HTTP proxy requested authentication " - "which we do not have"); - crStopV; - } - - /* - * Send some prompts to the user. We'll assume the - * password is always required (since it's just been - * rejected, even if we did send one before), and we'll - * prompt for the username only if we don't have one from - * the Conf. - */ - s->prompts = proxy_new_prompts(pn->ps); - s->prompts->to_server = true; - s->prompts->from_server = false; - s->prompts->name = dupstr("HTTP proxy authentication"); - if (!s->username->len) { - s->username_prompt_index = s->prompts->n_prompts; - add_prompt(s->prompts, dupstr("Proxy username: "), true); - } else { - s->username_prompt_index = -1; - } - - s->password_prompt_index = s->prompts->n_prompts; - add_prompt(s->prompts, dupstr("Proxy password: "), false); - - while (true) { - SeatPromptResult spr = seat_get_userpass_input( - interactor_announce(pn->itr), s->prompts); - if (spr.kind == SPRK_OK) { - break; - } else if (spr_is_abort(spr)) { - proxy_spr_abort(pn, spr); - crStopV; - } - crReturnV; - } - - if (s->username_prompt_index != -1) { - strbuf_clear(s->username); - put_dataz(s->username, - prompt_get_result_ref( - s->prompts->prompts[s->username_prompt_index])); - } - - strbuf_clear(s->password); - put_dataz(s->password, - prompt_get_result_ref( - s->prompts->prompts[s->password_prompt_index])); - - free_prompts(s->prompts); - s->prompts = NULL; - } else { - /* Any other HTTP response is treated as permanent failure */ - pn->error = dupprintf("HTTP response %s", - s->response->s + s->http_status_pos); - crStopV; - } - } - - authenticated: - /* - * Success! Hand over to the main connection. - */ - pn->done = true; - - crFinishV; -} - -const struct ProxyNegotiatorVT http_proxy_negotiator_vt = { - .new = proxy_http_new, - .free = proxy_http_free, - .process_queue = proxy_http_process_queue, - .type = "HTTP", -}; diff --git a/proxy/interactor.c b/proxy/interactor.c deleted file mode 100644 index d069d2268..000000000 --- a/proxy/interactor.c +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Centralised functions for the Interactor trait. - */ - -#include "putty.h" - -Seat *interactor_borrow_seat(Interactor *itr) -{ - Seat *clientseat = interactor_get_seat(itr); - if (!clientseat) - return NULL; - - /* If the client has already had its Seat borrowed, then look - * through the existing TempSeat to find the underlying one. */ - if (is_tempseat(clientseat)) - return tempseat_get_real(clientseat); - - /* Otherwise, make a new TempSeat and give that to the client. */ - Seat *tempseat = tempseat_new(clientseat); - interactor_set_seat(itr, tempseat); - return clientseat; -} - -static Interactor *interactor_toplevel(Interactor *itr, unsigned *level_out) -{ - /* - * Find the Interactor at the top of the chain, so that all the - * Interactors in a stack can share that one's last-to-talk field. - * Also, count how far we had to go to get to it, to put in the - * message. - */ - Interactor *itr_top = itr; - unsigned level = 0; - while (itr_top->parent) { - itr_top = itr_top->parent; - level++; - } - - if (level_out) - *level_out = level; - return itr_top; -} - -void interactor_return_seat(Interactor *itr) -{ - Seat *tempseat = interactor_get_seat(itr); - if (!is_tempseat(tempseat)) - return; /* no-op */ - - /* - * We're about to hand this seat back to the parent Interactor to - * do its own thing with. It will typically expect to start in the - * same state as if the seat had never been borrowed, i.e. in the - * starting trust state. - * - * However, this may be overridden by the tempseat_flush call. - */ - Seat *realseat = tempseat_get_real(tempseat); - seat_set_trust_status(realseat, true); - - tempseat_flush(tempseat); - interactor_set_seat(itr, realseat); - tempseat_free(tempseat); - - /* - * If we have a parent Interactor, and anyone has ever called - * interactor_announce, then all Interactors from now on will - * announce themselves even if they have nothing to say. - */ - Interactor *itr_top = interactor_toplevel(itr, NULL); - if (itr_top->last_to_talk) - interactor_announce(itr); -} - -InteractionReadySeat interactor_announce(Interactor *itr) -{ - Seat *seat = interactor_get_seat(itr); - assert(!is_tempseat(seat) && - "Shouldn't call announce when someone else is using our seat"); - - InteractionReadySeat iseat; - iseat.seat = seat; - - unsigned level; - Interactor *itr_top = interactor_toplevel(itr, &level); - - /* - * Generally, we should announce ourself if the previous - * Interactor that said anything was not us. That includes if - * there was no previous Interactor to talk (i.e. if we're the - * first to say anything) - *except* that the primary Interactor - * doesn't need to announce itself, if no proxy has intervened - * before it. - */ - bool need_announcement = (itr_top->last_to_talk != itr); - if (!itr->parent && !itr_top->last_to_talk) - need_announcement = false; - - if (need_announcement) { - const char *prefix = ""; - if (itr_top->last_to_talk != NULL) - seat_antispoof_msg(iseat, ""); /* leave a separating blank line */ - - char *desc = interactor_description(itr); - char *adjective = (level == 0 ? dupstr("primary") : - level == 1 ? dupstr("proxy") : - dupprintf("proxy^%u", level)); - char *msg = dupprintf("%sMaking %s %s", prefix, adjective, desc); - sfree(adjective); - sfree(desc); - - seat_antispoof_msg(iseat, msg); - sfree(msg); - - itr_top->last_to_talk = itr; - } - - return iseat; -} diff --git a/proxy/local.c b/proxy/local.c deleted file mode 100644 index 3b2d130c5..000000000 --- a/proxy/local.c +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Implement LocalProxyOpener, a centralised system for setting up the - * command string to be run by platform-specific local-subprocess - * proxy types. - * - * The platform-specific local proxy code is expected to use this - * system by calling local_proxy_opener() from - * platform_new_connection(); then using the resulting - * DeferredSocketOpener to make a deferred version of whatever local - * socket type is used for talking to subcommands (Unix FdSocket, - * Windows HandleSocket); then passing the 'Socket *' back to us via - * local_proxy_opener_set_socket(). - * - * The LocalProxyOpener object implemented by this code will set - * itself up as an Interactor if possible, so that it can prompt for - * the proxy username and/or password if they're referred to in the - * command string but not given in the config (exactly as the Telnet - * proxy does). Once it knows the exact command it wants to run - - * whether that was done immediately or after user interaction - it - * calls back to platform_setup_local_proxy() with the full command, - * which is expected to actually start the subprocess and fill in the - * missing details in the deferred socket, freeing the - * LocalProxyOpener as a side effect. - */ - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "sshcr.h" -#include "proxy/proxy.h" - -typedef struct LocalProxyOpener { - int crLine; - - Socket *socket; - char *formatted_cmd; - Plug *plug; - SockAddr *addr; - int port; - Conf *conf; - - Interactor *clientitr; - LogPolicy *clientlp; - Seat *clientseat; - prompts_t *prompts; - int username_prompt_index, password_prompt_index; - - Interactor interactor; - DeferredSocketOpener opener; -} LocalProxyOpener; - -static void local_proxy_opener_free(DeferredSocketOpener *opener) -{ - LocalProxyOpener *lp = container_of(opener, LocalProxyOpener, opener); - burnstr(lp->formatted_cmd); - if (lp->prompts) - free_prompts(lp->prompts); - sk_addr_free(lp->addr); - conf_free(lp->conf); - sfree(lp); -} - -static const DeferredSocketOpenerVtable LocalProxyOpener_openervt = { - .free = local_proxy_opener_free, -}; - -static char *local_proxy_opener_description(Interactor *itr) -{ - return dupstr("connection via local command"); -} - -static LogPolicy *local_proxy_opener_logpolicy(Interactor *itr) -{ - LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor); - return lp->clientlp; -} - -static Seat *local_proxy_opener_get_seat(Interactor *itr) -{ - LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor); - return lp->clientseat; -} - -static void local_proxy_opener_set_seat(Interactor *itr, Seat *seat) -{ - LocalProxyOpener *lp = container_of(itr, LocalProxyOpener, interactor); - lp->clientseat = seat; -} - -static const InteractorVtable LocalProxyOpener_interactorvt = { - .description = local_proxy_opener_description, - .logpolicy = local_proxy_opener_logpolicy, - .get_seat = local_proxy_opener_get_seat, - .set_seat = local_proxy_opener_set_seat, -}; - -static void local_proxy_opener_cleanup_interactor(LocalProxyOpener *lp) -{ - if (lp->clientseat) { - interactor_return_seat(lp->clientitr); - lp->clientitr = NULL; - lp->clientseat = NULL; - } -} - -static void local_proxy_opener_coroutine(void *vctx) -{ - LocalProxyOpener *lp = (LocalProxyOpener *)vctx; - - crBegin(lp->crLine); - - /* - * Make an initial attempt to figure out the command we want, and - * see if it tried to include a username or password that we don't - * have. - */ - { - unsigned flags; - lp->formatted_cmd = format_telnet_command( - lp->addr, lp->port, lp->conf, &flags); - - if (lp->clientseat && (flags & (TELNET_CMD_MISSING_USERNAME | - TELNET_CMD_MISSING_PASSWORD))) { - burnstr(lp->formatted_cmd); - lp->formatted_cmd = NULL; - - /* - * We're missing at least one of the two parts, and we - * have an Interactor we can use to prompt for them, so - * try it. - */ - lp->prompts = new_prompts(); - lp->prompts->callback = local_proxy_opener_coroutine; - lp->prompts->callback_ctx = lp; - lp->prompts->to_server = true; - lp->prompts->from_server = false; - lp->prompts->name = dupstr("Local proxy authentication"); - if (flags & TELNET_CMD_MISSING_USERNAME) { - lp->username_prompt_index = lp->prompts->n_prompts; - add_prompt(lp->prompts, dupstr("Proxy username: "), true); - } else { - lp->username_prompt_index = -1; - } - if (flags & TELNET_CMD_MISSING_PASSWORD) { - lp->password_prompt_index = lp->prompts->n_prompts; - add_prompt(lp->prompts, dupstr("Proxy password: "), false); - } else { - lp->password_prompt_index = -1; - } - - while (true) { - SeatPromptResult spr = seat_get_userpass_input( - interactor_announce(&lp->interactor), lp->prompts); - if (spr.kind == SPRK_OK) { - break; - } else if (spr.kind == SPRK_USER_ABORT) { - local_proxy_opener_cleanup_interactor(lp); - plug_closing_user_abort(lp->plug); - /* That will have freed us, so we must just return - * without calling any crStop */ - return; - } else if (spr.kind == SPRK_SW_ABORT) { - local_proxy_opener_cleanup_interactor(lp); - char *err = spr_get_error_message(spr); - plug_closing_error(lp->plug, err); - sfree(err); - return; /* without crStop, as above */ - } - crReturnV; - } - - if (lp->username_prompt_index != -1) { - conf_set_str( - lp->conf, CONF_proxy_username, - prompt_get_result_ref( - lp->prompts->prompts[lp->username_prompt_index])); - } - - if (lp->password_prompt_index != -1) { - conf_set_str( - lp->conf, CONF_proxy_password, - prompt_get_result_ref( - lp->prompts->prompts[lp->password_prompt_index])); - } - - free_prompts(lp->prompts); - lp->prompts = NULL; - } - - /* - * Now format the command a second time, with the results of - * those prompts written into lp->conf. - */ - lp->formatted_cmd = format_telnet_command( - lp->addr, lp->port, lp->conf, NULL); - } - - /* - * Log the command, with some changes. Firstly, we regenerate it - * with the password masked; secondly, we escape control - * characters so that the log message is printable. - */ - conf_set_str(lp->conf, CONF_proxy_password, "*password*"); - { - char *censored_cmd = format_telnet_command( - lp->addr, lp->port, lp->conf, NULL); - - strbuf *logmsg = strbuf_new(); - put_datapl(logmsg, PTRLEN_LITERAL("Starting local proxy command: ")); - put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd)); - - plug_log(lp->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); - strbuf_free(logmsg); - sfree(censored_cmd); - } - - /* - * Now we're ready to actually do the platform-specific socket - * setup. - */ - char *cmd = lp->formatted_cmd; - lp->formatted_cmd = NULL; - - local_proxy_opener_cleanup_interactor(lp); - - char *error_msg = platform_setup_local_proxy(lp->socket, cmd); - burnstr(cmd); - - if (error_msg) { - plug_closing_error(lp->plug, error_msg); - sfree(error_msg); - } else { - /* If error_msg was NULL, there was no error in setup, - * which means that platform_setup_local_proxy will have - * called back to free us. So return without calling any - * crStop. */ - return; - } - - crFinishV; -} - -DeferredSocketOpener *local_proxy_opener( - SockAddr *addr, int port, Plug *plug, Conf *conf, Interactor *itr) -{ - LocalProxyOpener *lp = snew(LocalProxyOpener); - memset(lp, 0, sizeof(*lp)); - lp->plug = plug; - lp->opener.vt = &LocalProxyOpener_openervt; - lp->interactor.vt = &LocalProxyOpener_interactorvt; - lp->addr = sk_addr_dup(addr); - lp->port = port; - lp->conf = conf_copy(conf); - - if (itr) { - lp->clientitr = itr; - interactor_set_child(lp->clientitr, &lp->interactor); - lp->clientlp = interactor_logpolicy(lp->clientitr); - lp->clientseat = interactor_borrow_seat(lp->clientitr); - } - - return &lp->opener; -} - -void local_proxy_opener_set_socket(DeferredSocketOpener *opener, - Socket *socket) -{ - assert(opener->vt == &LocalProxyOpener_openervt); - LocalProxyOpener *lp = container_of(opener, LocalProxyOpener, opener); - lp->socket = socket; - queue_toplevel_callback(local_proxy_opener_coroutine, lp); -} diff --git a/proxy/nocproxy.c b/proxy/nocproxy.c deleted file mode 100644 index 893414893..000000000 --- a/proxy/nocproxy.c +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 - -#include "putty.h" -#include "network.h" -#include "proxy.h" - -const bool socks5_chap_available = false; -const bool http_digest_available = false; - -strbuf *chap_response(ptrlen challenge, ptrlen password) -{ - unreachable("CHAP is not built into this binary"); -} - -/* dummy arrays to prevent link error */ -const char *const httphashnames[] = { NULL }; -const bool httphashaccepted[] = { false }; - -void http_digest_response(BinarySink *bs, ptrlen username, ptrlen password, - ptrlen realm, ptrlen method, ptrlen uri, ptrlen qop, - ptrlen nonce, ptrlen opaque, uint32_t nonce_count, - HttpDigestHash hash, bool hash_username) -{ - unreachable("HTTP DIGEST is not built into this binary"); -} diff --git a/proxy/noproxy.c b/proxy/noproxy.c deleted file mode 100644 index 248688e00..000000000 --- a/proxy/noproxy.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * noproxy.c: an alternative to proxy.c, for use by auxiliary programs - * that need to make network connections but don't want to include all - * the full-on support for endless network proxies (and its - * configuration requirements). Implements the primary APIs of - * proxy.c, but maps them straight to the underlying network layer. - */ - -#include "putty.h" -#include "network.h" -#include "proxy.h" - -SockAddr *name_lookup(const char *host, int port, char **canonicalname, - Conf *conf, int addressfamily, LogContext *logctx, - const char *reason) -{ - return sk_namelookup(host, canonicalname, addressfamily); -} - -Socket *new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, Interactor *itr) -{ - return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); -} - -Socket *new_listener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, Conf *conf, int addressfamily) -{ - return sk_newlistener(srcaddr, port, plug, local_host_only, addressfamily); -} diff --git a/proxy/nosshproxy.c b/proxy/nosshproxy.c deleted file mode 100644 index 1160b8de5..000000000 --- a/proxy/nosshproxy.c +++ /dev/null @@ -1,16 +0,0 @@ -/* - * nosshproxy.c: stub implementation of sshproxy_new_connection(). - */ - -#include "putty.h" -#include "network.h" - -const bool ssh_proxy_supported = false; - -Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, Interactor *itr) -{ - return NULL; -} diff --git a/proxy/pproxy.c b/proxy/pproxy.c deleted file mode 100644 index 1712ae8c9..000000000 --- a/proxy/pproxy.c +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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, const char *hostname, - int port, int privport, - int oobinline, int nodelay, int keepalive, - Plug *plug, Conf *conf, Interactor *itr) -{ - return NULL; -} diff --git a/proxy/proxy.c b/proxy/proxy.c deleted file mode 100644 index d6811f211..000000000 --- a/proxy/proxy.c +++ /dev/null @@ -1,655 +0,0 @@ -/* - * Network proxy abstraction in PuTTY - * - * A proxy layer, if necessary, wedges itself between the network - * code and the higher level backend. - */ - -#include -#include -#include - -#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)) - -static void proxy_negotiator_cleanup(ProxySocket *ps) -{ - if (ps->pn) { - proxy_negotiator_free(ps->pn); - ps->pn = NULL; - } - if (ps->clientseat) { - interactor_return_seat(ps->clientitr); - ps->clientitr = NULL; - ps->clientseat = NULL; - } -} - -/* - * Call this when proxy negotiation is complete, so that this - * socket can begin working normally. - */ -static void proxy_activate(ProxySocket *ps) -{ - size_t output_before, output_after; - - proxy_negotiator_cleanup(ps); - - plug_log(ps->plug, PLUGLOG_CONNECT_SUCCESS, NULL, 0, NULL, 0); - - /* we want to ignore new receive events until we have sent - * all of our buffered receive data. - */ - sk_set_frozen(ps->sub_socket, true); - - /* how many bytes of output have we buffered? */ - output_before = bufchain_size(&ps->pending_oob_output_data) + - bufchain_size(&ps->pending_output_data); - /* and keep track of how many bytes do not get sent. */ - output_after = 0; - - /* send buffered OOB writes */ - while (bufchain_size(&ps->pending_oob_output_data) > 0) { - ptrlen data = bufchain_prefix(&ps->pending_oob_output_data); - output_after += sk_write_oob(ps->sub_socket, data.ptr, data.len); - bufchain_consume(&ps->pending_oob_output_data, data.len); - } - - /* send buffered normal writes */ - while (bufchain_size(&ps->pending_output_data) > 0) { - ptrlen data = bufchain_prefix(&ps->pending_output_data); - output_after += sk_write(ps->sub_socket, data.ptr, data.len); - bufchain_consume(&ps->pending_output_data, data.len); - } - - /* if we managed to send any data, let the higher levels know. */ - if (output_after < output_before) - plug_sent(ps->plug, output_after); - - /* if we have a pending EOF to send, send it */ - if (ps->pending_eof) sk_write_eof(ps->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 (!ps->freeze) - sk_set_frozen(&ps->sock, false); -} - -/* basic proxy socket functions */ - -static Plug *sk_proxy_plug (Socket *s, Plug *p) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - Plug *ret = ps->plug; - if (p) - ps->plug = p; - return ret; -} - -static void sk_proxy_close (Socket *s) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - sk_close(ps->sub_socket); - sk_addr_free(ps->proxy_addr); - sk_addr_free(ps->remote_addr); - proxy_negotiator_cleanup(ps); - bufchain_clear(&ps->output_from_negotiator); - sfree(ps); -} - -static size_t sk_proxy_write (Socket *s, const void *data, size_t len) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - if (ps->pn) { - bufchain_add(&ps->pending_output_data, data, len); - return bufchain_size(&ps->pending_output_data); - } - return sk_write(ps->sub_socket, data, len); -} - -static size_t sk_proxy_write_oob (Socket *s, const void *data, size_t len) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - if (ps->pn) { - 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) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - if (ps->pn) { - ps->pending_eof = true; - return; - } - sk_write_eof(ps->sub_socket); -} - -static void sk_proxy_set_frozen (Socket *s, bool is_frozen) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - - if (ps->pn) { - 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) { - char databuf[512]; - ptrlen data = bufchain_prefix(&ps->pending_input_data); - if (data.len > lenof(databuf)) - data.len = lenof(databuf); - memcpy(databuf, data.ptr, data.len); - bufchain_consume(&ps->pending_input_data, data.len); - plug_receive(ps->plug, 0, databuf, data.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) -{ - ProxySocket *ps = container_of(s, ProxySocket, sock); - 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, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code) -{ - ProxySocket *ps = container_of(plug, ProxySocket, plugimpl); - - plug_log(ps->plug, type, addr, port, error_msg, error_code); -} - -static void plug_proxy_closing(Plug *p, PlugCloseType type, - const char *error_msg) -{ - ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - - proxy_negotiator_cleanup(ps); - plug_closing(ps->plug, type, error_msg); -} - -static void proxy_negotiate(ProxySocket *ps) -{ - assert(ps->pn); - proxy_negotiator_process_queue(ps->pn); - - if (ps->pn->error) { - char *err = dupprintf("Proxy error: %s", ps->pn->error); - sfree(ps->pn->error); - proxy_negotiator_cleanup(ps); - plug_closing_error(ps->plug, err); - sfree(err); - return; - } else if (ps->pn->aborted) { - proxy_negotiator_cleanup(ps); - plug_closing_user_abort(ps->plug); - return; - } - - if (ps->pn->reconnect) { - sk_close(ps->sub_socket); - SockAddr *proxy_addr = sk_addr_dup(ps->proxy_addr); - ps->sub_socket = sk_new(proxy_addr, ps->proxy_port, - ps->proxy_privport, ps->proxy_oobinline, - ps->proxy_nodelay, ps->proxy_keepalive, - &ps->plugimpl); - ps->pn->reconnect = false; - /* If the negotiator has asked us to reconnect, they are - * expecting that on the next call their input queue will - * consist entirely of data from the _new_ connection, without - * any remaining data buffered from the old one. (If they'd - * wanted the latter, they could have read it out of the input - * queue before asking us to close the connection.) */ - bufchain_clear(&ps->pending_input_data); - } - - while (bufchain_size(&ps->output_from_negotiator)) { - ptrlen data = bufchain_prefix(&ps->output_from_negotiator); - sk_write(ps->sub_socket, data.ptr, data.len); - bufchain_consume(&ps->output_from_negotiator, data.len); - } - if (ps->pn->done) - proxy_activate(ps); -} - -static void plug_proxy_receive( - Plug *p, int urgent, const char *data, size_t len) -{ - ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - - if (ps->pn) { - /* 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); - proxy_negotiate(ps); - } else { - plug_receive(ps->plug, urgent, data, len); - } -} - -static void plug_proxy_sent (Plug *p, size_t bufsize) -{ - ProxySocket *ps = container_of(p, ProxySocket, plugimpl); - - if (ps->pn) - return; - plug_sent(ps->plug, bufsize); -} - -static int plug_proxy_accepting(Plug *p, - accept_fn_t constructor, accept_ctx_t ctx) -{ - unreachable("ProxySockets never create listening Sockets"); -} - -/* - * This function can accept a NULL pointer as `addr', in which case - * it will only check the host name. - */ -static bool 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 false; /* do not proxy */ - - /* - * Check the host name and IP against the hard-coded - * representations of `localhost'. - */ - if (!conf_get_bool(conf, CONF_even_proxy_localhost) && - (sk_hostname_is_local(hostname) || - (addr && sk_address_is_local(addr)))) - return false; /* 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) { - /* IP/hostname range excluded. do not use proxy. */ - return false; - } - } 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) { - /* IP/hostname range excluded. do not use proxy. */ - return false; - } - } 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 false; /* IP/hostname excluded. do not use proxy. */ - if (strnicmp(hostname, exclude_list + s, e - s) == 0) - return false; /* 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 true; -} - -static char *dns_log_msg(const char *host, int addressfamily, - const char *reason) -{ - return dupprintf("Looking up host \"%s\"%s for %s", host, - (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : - addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : - ""), reason); -} - -SockAddr *name_lookup(const char *host, int port, char **canonicalname, - Conf *conf, int addressfamily, LogContext *logctx, - const char *reason) -{ - if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && - do_proxy_dns(conf) && - proxy_for_destination(NULL, host, port, conf)) { - - if (logctx) - logeventf(logctx, "Leaving host lookup to proxy of \"%s\"" - " (for %s)", host, reason); - - *canonicalname = dupstr(host); - return sk_nonamelookup(host); - } else { - if (logctx) - logevent_and_free( - logctx, dns_log_msg(host, addressfamily, reason)); - - return sk_namelookup(host, canonicalname, addressfamily); - } -} - -static const SocketVtable ProxySocket_sockvt = { - .plug = sk_proxy_plug, - .close = sk_proxy_close, - .write = sk_proxy_write, - .write_oob = sk_proxy_write_oob, - .write_eof = sk_proxy_write_eof, - .set_frozen = sk_proxy_set_frozen, - .socket_error = sk_proxy_socket_error, - .peer_info = NULL, -}; - -static const PlugVtable ProxySocket_plugvt = { - .log = plug_proxy_log, - .closing = plug_proxy_closing, - .receive = plug_proxy_receive, - .sent = plug_proxy_sent, - .accepting = plug_proxy_accepting -}; - -static char *proxy_description(Interactor *itr) -{ - ProxySocket *ps = container_of(itr, ProxySocket, interactor); - assert(ps->pn); - return dupprintf("%s connection to %s port %d", ps->pn->vt->type, - conf_get_str(ps->conf, CONF_proxy_host), - conf_get_int(ps->conf, CONF_proxy_port)); -} - -static LogPolicy *proxy_logpolicy(Interactor *itr) -{ - ProxySocket *ps = container_of(itr, ProxySocket, interactor); - return ps->clientlp; -} - -static Seat *proxy_get_seat(Interactor *itr) -{ - ProxySocket *ps = container_of(itr, ProxySocket, interactor); - return ps->clientseat; -} - -static void proxy_set_seat(Interactor *itr, Seat *seat) -{ - ProxySocket *ps = container_of(itr, ProxySocket, interactor); - ps->clientseat = seat; -} - -static const InteractorVtable ProxySocket_interactorvt = { - .description = proxy_description, - .logpolicy = proxy_logpolicy, - .get_seat = proxy_get_seat, - .set_seat = proxy_set_seat, -}; - -static void proxy_prompts_callback(void *ctx) -{ - proxy_negotiate((ProxySocket *)ctx); -} - -prompts_t *proxy_new_prompts(ProxySocket *ps) -{ - prompts_t *prs = new_prompts(); - prs->callback = proxy_prompts_callback; - prs->callback_ctx = ps; - return prs; -} - -void proxy_spr_abort(ProxyNegotiator *pn, SeatPromptResult spr) -{ - if (spr.kind == SPRK_SW_ABORT) { - pn->error = spr_get_error_message(spr); - } else { - assert(spr.kind == SPRK_USER_ABORT); - pn->aborted = true; - } -} - -Socket *new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, Interactor *itr) -{ - int type = conf_get_int(conf, CONF_proxy_type); - - if (type != PROXY_NONE && - proxy_for_destination(addr, hostname, port, conf)) { - ProxySocket *ps; - SockAddr *proxy_addr; - char *proxy_canonical_name; - Socket *sret; - - if ((type == PROXY_SSH_TCPIP || - type == PROXY_SSH_EXEC || - type == PROXY_SSH_SUBSYSTEM) && - (sret = sshproxy_new_connection(addr, hostname, port, privport, - oobinline, nodelay, keepalive, - plug, conf, itr)) != NULL) - return sret; - - if ((sret = platform_new_connection(addr, hostname, port, privport, - oobinline, nodelay, keepalive, - plug, conf, itr)) != NULL) - return sret; - - ps = snew(ProxySocket); - ps->sock.vt = &ProxySocket_sockvt; - ps->plugimpl.vt = &ProxySocket_plugvt; - ps->interactor.vt = &ProxySocket_interactorvt; - ps->conf = conf_copy(conf); - ps->plug = plug; - ps->remote_addr = addr; /* will need to be freed on close */ - ps->remote_port = port; - - ps->error = NULL; - ps->pending_eof = false; - ps->freeze = false; - - bufchain_init(&ps->pending_input_data); - bufchain_init(&ps->pending_output_data); - bufchain_init(&ps->pending_oob_output_data); - bufchain_init(&ps->output_from_negotiator); - - ps->sub_socket = NULL; - - /* - * If we've been given an Interactor by the caller, set ourselves - * up to work with it. - */ - if (itr) { - ps->clientitr = itr; - interactor_set_child(ps->clientitr, &ps->interactor); - ps->clientlp = interactor_logpolicy(ps->clientitr); - ps->clientseat = interactor_borrow_seat(ps->clientitr); - } - - const ProxyNegotiatorVT *vt; - switch (type) { - case PROXY_HTTP: - vt = &http_proxy_negotiator_vt; - break; - case PROXY_SOCKS4: - vt = &socks4_proxy_negotiator_vt; - break; - case PROXY_SOCKS5: - vt = &socks5_proxy_negotiator_vt; - break; - case PROXY_TELNET: - vt = &telnet_proxy_negotiator_vt; - break; - default: - ps->error = "Proxy error: Unknown proxy method"; - return &ps->sock; - } - ps->pn = proxy_negotiator_new(vt); - ps->pn->ps = ps; - ps->pn->done = false; - ps->pn->error = NULL; - ps->pn->aborted = false; - ps->pn->input = &ps->pending_input_data; - /* Provide an Interactor to the negotiator if and only if we - * are usefully able to ask interactive questions of the user */ - ps->pn->itr = ps->clientseat ? &ps->interactor : NULL; - bufchain_sink_init(ps->pn->output, &ps->output_from_negotiator); - - { - char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect" - " to %s:%d", vt->type, - conf_get_str(conf, CONF_proxy_host), - conf_get_int(conf, CONF_proxy_port), - hostname, port); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); - sfree(logmsg); - } - - { - char *logmsg = dns_log_msg(conf_get_str(conf, CONF_proxy_host), - conf_get_int(conf, CONF_addressfamily), - "proxy"); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); - sfree(logmsg); - } - - /* 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) { - ps->error = "Proxy error: Unable to resolve proxy host name"; - sk_addr_free(proxy_addr); - return &ps->sock; - } - sfree(proxy_canonical_name); - - { - char addrbuf[256], *logmsg; - sk_getaddr(proxy_addr, addrbuf, lenof(addrbuf)); - logmsg = dupprintf("Connecting to %s proxy at %s port %d", - vt->type, addrbuf, - conf_get_int(conf, CONF_proxy_port)); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); - sfree(logmsg); - } - - /* create the actual socket we will be using, - * connected to our proxy server and port. - */ - ps->proxy_addr = sk_addr_dup(proxy_addr); - ps->proxy_port = conf_get_int(conf, CONF_proxy_port); - ps->proxy_privport = privport; - ps->proxy_oobinline = oobinline; - ps->proxy_nodelay = nodelay; - ps->proxy_keepalive = keepalive; - ps->sub_socket = sk_new(proxy_addr, ps->proxy_port, - ps->proxy_privport, ps->proxy_oobinline, - ps->proxy_nodelay, ps->proxy_keepalive, - &ps->plugimpl); - if (sk_socket_error(ps->sub_socket) != NULL) - return &ps->sock; - - /* start the proxy negotiation process... */ - sk_set_frozen(ps->sub_socket, false); - proxy_negotiate(ps); - - return &ps->sock; - } - - /* no proxy, so just return the direct socket */ - return sk_new(addr, port, privport, oobinline, nodelay, keepalive, plug); -} - -Socket *new_listener(const char *srcaddr, int port, Plug *plug, - bool 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); -} diff --git a/proxy/proxy.h b/proxy/proxy.h deleted file mode 100644 index 72d06d497..000000000 --- a/proxy/proxy.h +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 - -typedef struct ProxySocket ProxySocket; -typedef struct ProxyNegotiator ProxyNegotiator; -typedef struct ProxyNegotiatorVT ProxyNegotiatorVT; - -struct ProxySocket { - const char *error; - - Socket *sub_socket; - Plug *plug; - SockAddr *remote_addr; - int remote_port; - - /* Parameters needed to make further connections to the proxy */ - SockAddr *proxy_addr; - int proxy_port; - bool proxy_privport, proxy_oobinline, proxy_nodelay, proxy_keepalive; - - bufchain pending_output_data; - bufchain pending_oob_output_data; - bufchain pending_input_data; - bool pending_eof; - - bool 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. - */ - - ProxyNegotiator *pn; /* non-NULL if still negotiating */ - bufchain output_from_negotiator; - - /* configuration, used to look up proxy settings */ - Conf *conf; - - /* for interaction with the Seat */ - Interactor *clientitr; - LogPolicy *clientlp; - Seat *clientseat; - - Socket sock; - Plug plugimpl; - Interactor interactor; -}; - -struct ProxyNegotiator { - const ProxyNegotiatorVT *vt; - - /* Standard fields for any ProxyNegotiator. new() and free() don't - * have to set these up; that's done centrally, to save duplication. */ - ProxySocket *ps; - bufchain *input; - bufchain_sink output[1]; - Interactor *itr; /* NULL if we are not able to interact with the user */ - - /* Set to report success during proxy negotiation. */ - bool done; - - /* Set to report an error during proxy negotiation. The main - * ProxySocket will free it, and will then guarantee never to call - * process_queue again. */ - char *error; - - /* Set to report user abort during proxy negotiation. */ - bool aborted; - - /* Set to request the centralised code to make a fresh connection - * to the proxy server, e.g. because an HTTP proxy slammed the - * connection shut after sending 407 Proxy Auth Required. */ - bool reconnect; -}; - -struct ProxyNegotiatorVT { - ProxyNegotiator *(*new)(const ProxyNegotiatorVT *); - void (*process_queue)(ProxyNegotiator *); - void (*free)(ProxyNegotiator *); - const char *type; -}; - -static inline ProxyNegotiator *proxy_negotiator_new( - const ProxyNegotiatorVT *vt) -{ return vt->new(vt); } -static inline void proxy_negotiator_process_queue(ProxyNegotiator *pn) -{ pn->vt->process_queue(pn); } -static inline void proxy_negotiator_free(ProxyNegotiator *pn) -{ pn->vt->free(pn); } - -extern const ProxyNegotiatorVT http_proxy_negotiator_vt; -extern const ProxyNegotiatorVT socks4_proxy_negotiator_vt; -extern const ProxyNegotiatorVT socks5_proxy_negotiator_vt; -extern const ProxyNegotiatorVT telnet_proxy_negotiator_vt; - -/* - * Centralised functions to allow ProxyNegotiators to get hold of a - * prompts_t, and to deal with SeatPromptResults coming back. - */ -prompts_t *proxy_new_prompts(ProxySocket *ps); -void proxy_spr_abort(ProxyNegotiator *pn, SeatPromptResult spr); - -/* - * This may be reused by local-command proxies on individual - * platforms. - */ -#define TELNET_CMD_MISSING_USERNAME 0x0001 -#define TELNET_CMD_MISSING_PASSWORD 0x0002 -char *format_telnet_command(SockAddr *addr, int port, Conf *conf, - unsigned *flags_out); - -DeferredSocketOpener *local_proxy_opener( - SockAddr *addr, int port, Plug *plug, Conf *conf, Interactor *itr); -void local_proxy_opener_set_socket(DeferredSocketOpener *opener, - Socket *socket); -char *platform_setup_local_proxy(Socket *socket, const char *cmd); - -#include "cproxy.h" - -#endif diff --git a/proxy/socks.h b/proxy/socks.h deleted file mode 100644 index 3e86ae237..000000000 --- a/proxy/socks.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Constants used in the SOCKS protocols. - */ - -/* Command codes common to both versions */ -#define SOCKS_CMD_CONNECT 1 -#define SOCKS_CMD_BIND 2 - -/* SOCKS 4 definitions */ - -#define SOCKS4_REQUEST_VERSION 4 -#define SOCKS4_REPLY_VERSION 0 - -#define SOCKS4_RESP_SUCCESS 90 -#define SOCKS4_RESP_FAILURE 91 -#define SOCKS4_RESP_WANT_IDENTD 92 -#define SOCKS4_RESP_IDENTD_MISMATCH 93 - -/* - * Special nonsense IP address range, used as a signal to indicate - * that an ASCIZ hostname follows the user id field. - * - * Strictly speaking, the use of this extension indicates that we're - * speaking SOCKS 4A rather than vanilla SOCKS 4, although we don't - * bother to draw the distinction. - */ -#define SOCKS4A_NAME_FOLLOWS_BASE 0x00000001 /* inclusive */ -#define SOCKS4A_NAME_FOLLOWS_LIMIT 0x00000100 /* exclusive */ - -/* SOCKS 5 definitions */ - -#define SOCKS5_REQUEST_VERSION 5 -#define SOCKS5_REPLY_VERSION 5 - -/* Extra command codes extending the SOCKS_CMD_* list above */ -#define SOCKS5_CMD_UDP_ASSOCIATE 3 - -#define SOCKS5_AUTH_NONE 0 -#define SOCKS5_AUTH_GSSAPI 1 -#define SOCKS5_AUTH_PASSWORD 2 -#define SOCKS5_AUTH_CHAP 3 -#define SOCKS5_AUTH_REJECTED 0xFF /* used in reply to indicate 'no - * acceptable method offered' */ - -#define SOCKS5_AUTH_PASSWORD_VERSION 1 - -#define SOCKS5_AUTH_CHAP_VERSION 1 - -#define SOCKS5_AUTH_CHAP_ATTR_STATUS 0x00 -#define SOCKS5_AUTH_CHAP_ATTR_INFO 0x01 -#define SOCKS5_AUTH_CHAP_ATTR_USERNAME 0x02 -#define SOCKS5_AUTH_CHAP_ATTR_CHALLENGE 0x03 -#define SOCKS5_AUTH_CHAP_ATTR_RESPONSE 0x04 -#define SOCKS5_AUTH_CHAP_ATTR_CHARSET 0x05 -#define SOCKS5_AUTH_CHAP_ATTR_IDENTIFIER 0x10 -#define SOCKS5_AUTH_CHAP_ATTR_ALGLIST 0x11 - -#define SOCKS5_AUTH_CHAP_ALG_HMACMD5 0x85 - -#define SOCKS5_ADDR_IPV4 1 -#define SOCKS5_ADDR_IPV6 4 -#define SOCKS5_ADDR_HOSTNAME 3 - -#define SOCKS5_RESP_SUCCESS 0 -#define SOCKS5_RESP_FAILURE 1 -#define SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET 2 -#define SOCKS5_RESP_NETWORK_UNREACHABLE 3 -#define SOCKS5_RESP_HOST_UNREACHABLE 4 -#define SOCKS5_RESP_CONNECTION_REFUSED 5 -#define SOCKS5_RESP_TTL_EXPIRED 6 -#define SOCKS5_RESP_COMMAND_NOT_SUPPORTED 7 -#define SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED 8 diff --git a/proxy/socks4.c b/proxy/socks4.c deleted file mode 100644 index 8cd08d84c..000000000 --- a/proxy/socks4.c +++ /dev/null @@ -1,136 +0,0 @@ -/* - * SOCKS 4 proxy negotiation. - */ - -#include "putty.h" -#include "network.h" -#include "proxy.h" -#include "socks.h" -#include "sshcr.h" - -typedef struct Socks4ProxyNegotiator { - int crLine; - ProxyNegotiator pn; -} Socks4ProxyNegotiator; - -static ProxyNegotiator *proxy_socks4_new(const ProxyNegotiatorVT *vt) -{ - Socks4ProxyNegotiator *s = snew(Socks4ProxyNegotiator); - s->pn.vt = vt; - s->crLine = 0; - return &s->pn; -} - -static void proxy_socks4_free(ProxyNegotiator *pn) -{ - Socks4ProxyNegotiator *s = container_of(pn, Socks4ProxyNegotiator, pn); - sfree(s); -} - -static void proxy_socks4_process_queue(ProxyNegotiator *pn) -{ - Socks4ProxyNegotiator *s = container_of(pn, Socks4ProxyNegotiator, pn); - - crBegin(s->crLine); - - { - char hostname[512]; - bool write_hostname = false; - - /* - * SOCKS 4 request packet: - * - * byte version - * byte command - * uint16 destination port number - * uint32 destination IPv4 address (or something in the - * SOCKS4A_NAME_FOLLOWS range) - * asciz username - * asciz destination hostname (if we sent SOCKS4A_NAME_FOLLOWS_*) - */ - - put_byte(pn->output, SOCKS4_REQUEST_VERSION); - put_byte(pn->output, SOCKS_CMD_CONNECT); - put_uint16(pn->output, pn->ps->remote_port); - - switch (sk_addrtype(pn->ps->remote_addr)) { - case ADDRTYPE_IPV4: { - char addr[4]; - sk_addrcopy(pn->ps->remote_addr, addr); - put_data(pn->output, addr, 4); - break; - } - case ADDRTYPE_NAME: - put_uint32(pn->output, SOCKS4A_NAME_FOLLOWS_BASE); - sk_getaddr(pn->ps->remote_addr, hostname, lenof(hostname)); - write_hostname = true; - break; - case ADDRTYPE_IPV6: - pn->error = dupstr("SOCKS version 4 does not support IPv6"); - crStopV; - } - - put_asciz(pn->output, conf_get_str(pn->ps->conf, CONF_proxy_username)); - - if (write_hostname) - put_asciz(pn->output, hostname); - } - - crReturnV; - - { - unsigned char data[8]; - crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 8)); - - /* - * SOCKS 4 response packet: - * - * byte version - * byte status - * uint16 port number - * uint32 IPv4 address - * - * We don't need to worry about the port and destination address. - */ - - if (data[0] != SOCKS4_REPLY_VERSION) { - pn->error = dupprintf("SOCKS proxy response contained reply " - "version number %d (expected %d)", - (int)data[0], SOCKS4_REPLY_VERSION); - crStopV; - } - - switch (data[1]) { - case SOCKS4_RESP_SUCCESS: - pn->done = true; - break; - - case SOCKS4_RESP_FAILURE: - pn->error = dupstr("SOCKS server reported failure to connect"); - break; - - case SOCKS4_RESP_WANT_IDENTD: - pn->error = dupstr("SOCKS server wanted IDENTD on client"); - break; - - case SOCKS4_RESP_IDENTD_MISMATCH: - pn->error = dupstr("Username and IDENTD on client don't agree"); - break; - - default: - pn->error = dupprintf("SOCKS server sent unrecognised error " - "code %d", (int)data[1]); - break; - } - crStopV; - } - - crFinishV; -} - -const struct ProxyNegotiatorVT socks4_proxy_negotiator_vt = { - .new = proxy_socks4_new, - .free = proxy_socks4_free, - .process_queue = proxy_socks4_process_queue, - .type = "SOCKS 4", -}; diff --git a/proxy/socks5.c b/proxy/socks5.c deleted file mode 100644 index 49331476d..000000000 --- a/proxy/socks5.c +++ /dev/null @@ -1,498 +0,0 @@ -/* - * SOCKS 5 proxy negotiation. - */ - -#include "putty.h" -#include "network.h" -#include "proxy.h" -#include "socks.h" -#include "sshcr.h" - -static inline const char *socks5_auth_name(unsigned char m) -{ - switch (m) { - case SOCKS5_AUTH_NONE: return "none"; - case SOCKS5_AUTH_GSSAPI: return "GSSAPI"; - case SOCKS5_AUTH_PASSWORD: return "password"; - case SOCKS5_AUTH_CHAP: return "CHAP"; - default: return "unknown"; - } -} - -static inline const char *socks5_response_text(unsigned char m) -{ - switch (m) { - case SOCKS5_RESP_SUCCESS: return "success"; - case SOCKS5_RESP_FAILURE: return "unspecified failure"; - case SOCKS5_RESP_CONNECTION_NOT_ALLOWED_BY_RULESET: - return "connection not allowed by ruleset"; - case SOCKS5_RESP_NETWORK_UNREACHABLE: return "network unreachable"; - case SOCKS5_RESP_HOST_UNREACHABLE: return "host unreachable"; - case SOCKS5_RESP_CONNECTION_REFUSED: return "connection refused"; - case SOCKS5_RESP_TTL_EXPIRED: return "TTL expired"; - case SOCKS5_RESP_COMMAND_NOT_SUPPORTED: return "command not supported"; - case SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED: - return "address type not supported"; - default: return "unknown"; - } -} - -typedef struct Socks5ProxyNegotiator { - int crLine; - strbuf *auth_methods_offered; - unsigned char auth_method; - unsigned n_chap_attrs; - unsigned chap_attr, chap_attr_len; - unsigned char chap_buf[256]; - strbuf *username, *password; - prompts_t *prompts; - int username_prompt_index, password_prompt_index; - int response_addr_length; - ProxyNegotiator pn; -} Socks5ProxyNegotiator; - -static ProxyNegotiator *proxy_socks5_new(const ProxyNegotiatorVT *vt) -{ - Socks5ProxyNegotiator *s = snew(Socks5ProxyNegotiator); - memset(s, 0, sizeof(*s)); - s->pn.vt = vt; - s->auth_methods_offered = strbuf_new(); - s->username = strbuf_new(); - s->password = strbuf_new_nm(); - return &s->pn; -} - -static void proxy_socks5_free(ProxyNegotiator *pn) -{ - Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn); - strbuf_free(s->auth_methods_offered); - strbuf_free(s->username); - strbuf_free(s->password); - if (s->prompts) - free_prompts(s->prompts); - smemclr(s, sizeof(*s)); - sfree(s); -} - -static void proxy_socks5_process_queue(ProxyNegotiator *pn) -{ - Socks5ProxyNegotiator *s = container_of(pn, Socks5ProxyNegotiator, pn); - - crBegin(s->crLine); - - /* - * SOCKS 5 initial client packet: - * - * byte version - * byte number of available auth methods - * byte[] that many bytes indicating auth types - */ - - put_byte(pn->output, SOCKS5_REQUEST_VERSION); - - strbuf_clear(s->auth_methods_offered); - - /* - * We have two basic kinds of authentication to offer: none at - * all, and password-based systems (whether the password is sent - * in cleartext or proved via CHAP). - * - * We always offer 'none' as an option. We offer 'password' if we - * either have a username and password already from the Conf, or - * we have a Seat available to ask for them interactively. If - * neither, we don't offer those options in the first place. - */ - put_byte(s->auth_methods_offered, SOCKS5_AUTH_NONE); - - put_dataz(s->username, conf_get_str(pn->ps->conf, CONF_proxy_username)); - put_dataz(s->password, conf_get_str(pn->ps->conf, CONF_proxy_password)); - if (pn->itr || (s->username->len && s->password->len)) { - if (socks5_chap_available) - put_byte(s->auth_methods_offered, SOCKS5_AUTH_CHAP); - - put_byte(s->auth_methods_offered, SOCKS5_AUTH_PASSWORD); - } - - put_byte(pn->output, s->auth_methods_offered->len); - put_datapl(pn->output, ptrlen_from_strbuf(s->auth_methods_offered)); - - crReturnV; - - /* - * SOCKS 5 initial server packet: - * - * byte version - * byte selected auth method, or SOCKS5_AUTH_REJECTED - */ - { - unsigned char data[2]; - crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 2)); - - if (data[0] != SOCKS5_REPLY_VERSION) { - pn->error = dupprintf("SOCKS proxy returned unexpected " - "reply version %d (expected %d)", - (int)data[0], SOCKS5_REPLY_VERSION); - crStopV; - } - - if (data[1] == SOCKS5_AUTH_REJECTED) { - pn->error = dupstr("SOCKS server rejected every authentication " - "method we offered"); - crStopV; - } - - { - bool found = false; - for (size_t i = 0; i < s->auth_methods_offered->len; i++) - if (s->auth_methods_offered->u[i] == data[1]) { - found = true; - break; - } - - if (!found) { - pn->error = dupprintf("SOCKS server asked for auth method %d " - "(%s), which we did not offer", - (int)data[1], socks5_auth_name(data[1])); - crStopV; - } - } - - s->auth_method = data[1]; - } - - /* - * The 'none' auth option requires no further negotiation. If that - * was the one we selected, go straight to making the connection. - */ - if (s->auth_method == SOCKS5_AUTH_NONE) - goto authenticated; - - /* - * Otherwise, we're going to need a username and password, so this - * is the moment to stop and ask for one if we don't already have - * them. - */ - if (pn->itr && (!s->username->len || !s->password->len)) { - s->prompts = proxy_new_prompts(pn->ps); - s->prompts->to_server = true; - s->prompts->from_server = false; - s->prompts->name = dupstr("SOCKS proxy authentication"); - if (!s->username->len) { - s->username_prompt_index = s->prompts->n_prompts; - add_prompt(s->prompts, dupstr("Proxy username: "), true); - } else { - s->username_prompt_index = -1; - } - if (!s->password->len) { - s->password_prompt_index = s->prompts->n_prompts; - add_prompt(s->prompts, dupstr("Proxy password: "), false); - } else { - s->password_prompt_index = -1; - } - - while (true) { - SeatPromptResult spr = seat_get_userpass_input( - interactor_announce(pn->itr), s->prompts); - if (spr.kind == SPRK_OK) { - break; - } else if (spr_is_abort(spr)) { - proxy_spr_abort(pn, spr); - crStopV; - } - crReturnV; - } - - if (s->username_prompt_index != -1) { - strbuf_clear(s->username); - put_dataz(s->username, - prompt_get_result_ref( - s->prompts->prompts[s->username_prompt_index])); - } - - if (s->password_prompt_index != -1) { - strbuf_clear(s->password); - put_dataz(s->password, - prompt_get_result_ref( - s->prompts->prompts[s->password_prompt_index])); - } - - free_prompts(s->prompts); - s->prompts = NULL; - } - - /* - * Now process the different auth methods that will use that - * username and password. Note we can't do this using the natural - * idiom of a switch statement, because there are crReturns inside - * some cases. - */ - if (s->auth_method == SOCKS5_AUTH_PASSWORD) { - /* - * SOCKS 5 password auth packet: - * - * byte version - * pstring username - * pstring password - */ - put_byte(pn->output, SOCKS5_AUTH_PASSWORD_VERSION); - if (!put_pstring(pn->output, s->username->s)) { - pn->error = dupstr("SOCKS 5 authentication cannot support " - "usernames longer than 255 chars"); - crStopV; - } - if (!put_pstring(pn->output, s->password->s)) { - pn->error = dupstr("SOCKS 5 authentication cannot support " - "passwords longer than 255 chars"); - crStopV; - } - - /* - * SOCKS 5 password reply packet: - * - * byte version - * byte 0 for success, >0 for failure - */ - unsigned char data[2]; - crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 2)); - - if (data[0] != SOCKS5_AUTH_PASSWORD_VERSION) { - pn->error = dupprintf( - "SOCKS 5 password reply had version number %d (expected " - "%d)", (int)data[0], SOCKS5_AUTH_PASSWORD_VERSION); - crStopV; - } - - if (data[1] != 0) { - pn->error = dupstr("SOCKS 5 server rejected our password"); - crStopV; - } - } else if (s->auth_method == SOCKS5_AUTH_CHAP) { - assert(socks5_chap_available); - - /* - * All CHAP packets, in both directions, have the same - * overall format: - * - * byte version - * byte number of attributes - * - * and then for each attribute: - * - * byte attribute type - * byte length - * byte[] that many bytes of payload - * - * In the initial outgoing packet we send two attributes: - * the list of supported algorithm names, and the - * username. - * - * (It's possible that we ought to delay sending the - * username until the second packet, in case the proxy - * sent back an attribute indicating which character set - * it would like us to use.) - */ - put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION); - put_byte(pn->output, 2); /* number of attributes */ - - put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_ALGLIST); - put_byte(pn->output, 1); /* string length */ - put_byte(pn->output, SOCKS5_AUTH_CHAP_ALG_HMACMD5); - - /* Second attribute: username */ - { - put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_USERNAME); - if (!put_pstring(pn->output, s->username->s)) { - pn->error = dupstr( - "SOCKS 5 CHAP authentication cannot support " - "usernames longer than 255 chars"); - crStopV; - } - } - - while (true) { - /* - * Process a CHAP response packet, which has the same - * overall format as the outgoing packet shown above. - */ - unsigned char data[2]; - crMaybeWaitUntilV(bufchain_try_fetch_consume( - pn->input, data, 2)); - if (data[0] != SOCKS5_AUTH_CHAP_VERSION) { - pn->error = dupprintf( - "SOCKS 5 CHAP reply had version number %d (expected " - "%d)", (int)data[0], SOCKS5_AUTH_CHAP_VERSION); - crStopV; - } - - s->n_chap_attrs = data[1]; - if (s->n_chap_attrs == 0) { - /* - * If we receive a CHAP packet containing no - * attributes, then we have nothing we didn't have - * before, and can't make further progress. - */ - pn->error = dupprintf( - "SOCKS 5 CHAP reply sent no attributes"); - crStopV; - } - while (s->n_chap_attrs-- > 0) { - unsigned char data[2]; - crMaybeWaitUntilV(bufchain_try_fetch_consume( - pn->input, data, 2)); - s->chap_attr = data[0]; - s->chap_attr_len = data[1]; - crMaybeWaitUntilV(bufchain_try_fetch_consume( - pn->input, s->chap_buf, s->chap_attr_len)); - - if (s->chap_attr == SOCKS5_AUTH_CHAP_ATTR_STATUS) { - if (s->chap_attr_len == 1 && s->chap_buf[0] == 0) { - /* Status 0 means success: we are authenticated! */ - goto authenticated; - } else { - pn->error = dupstr( - "SOCKS 5 CHAP authentication failed"); - crStopV; - } - } else if (s->chap_attr == SOCKS5_AUTH_CHAP_ATTR_CHALLENGE) { - /* The CHAP challenge string. Send the response */ - strbuf *response = chap_response( - make_ptrlen(s->chap_buf, s->chap_attr_len), - ptrlen_from_strbuf(s->password)); - - put_byte(pn->output, SOCKS5_AUTH_CHAP_VERSION); - put_byte(pn->output, 1); /* number of attributes */ - put_byte(pn->output, SOCKS5_AUTH_CHAP_ATTR_RESPONSE); - put_byte(pn->output, response->len); - put_datapl(pn->output, ptrlen_from_strbuf(response)); - - strbuf_free(response); - } else { - /* ignore all other attributes */ - } - } - } - } else { - unreachable("bad auth method in SOCKS 5 negotiation"); - } - - authenticated: - - /* - * SOCKS 5 connection command: - * - * byte version - * byte command - * byte reserved (send as zero) - * byte address type - * byte[] address, with variable size (see below) - * uint16 port - */ - put_byte(pn->output, SOCKS5_REQUEST_VERSION); - put_byte(pn->output, SOCKS_CMD_CONNECT); - put_byte(pn->output, 0); /* reserved byte */ - - switch (sk_addrtype(pn->ps->remote_addr)) { - case ADDRTYPE_IPV4: { - /* IPv4: address is 4 raw bytes */ - put_byte(pn->output, SOCKS5_ADDR_IPV4); - char buf[4]; - sk_addrcopy(pn->ps->remote_addr, buf); - put_data(pn->output, buf, sizeof(buf)); - break; - } - case ADDRTYPE_IPV6: { - /* IPv6: address is 16 raw bytes */ - put_byte(pn->output, SOCKS5_ADDR_IPV6); - char buf[16]; - sk_addrcopy(pn->ps->remote_addr, buf); - put_data(pn->output, buf, sizeof(buf)); - break; - } - case ADDRTYPE_NAME: { - /* Hostname: address is a pstring (Pascal-style string, - * unterminated but with a one-byte prefix length) */ - put_byte(pn->output, SOCKS5_ADDR_HOSTNAME); - char hostname[512]; - sk_getaddr(pn->ps->remote_addr, hostname, lenof(hostname)); - if (!put_pstring(pn->output, hostname)) { - pn->error = dupstr( - "SOCKS 5 cannot support host names longer than 255 chars"); - crStopV; - } - break; - } - default: - unreachable("Unexpected addrtype in SOCKS 5 proxy"); - } - - put_uint16(pn->output, pn->ps->remote_port); - crReturnV; - - /* - * SOCKS 5 connection response: - * - * byte version - * byte status - * byte reserved - * byte address type - * byte[] address bound to (same formats as in connection request) - * uint16 port - * - * We read the first four bytes and then decide what to do next. - */ - { - unsigned char data[4]; - crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, data, 4)); - - if (data[0] != SOCKS5_REPLY_VERSION) { - pn->error = dupprintf("SOCKS proxy returned unexpected " - "reply version %d (expected %d)", - (int)data[0], SOCKS5_REPLY_VERSION); - crStopV; - } - - if (data[1] != SOCKS5_RESP_SUCCESS) { - pn->error = dupprintf("SOCKS proxy failed to connect, error %d " - "(%s)", (int)data[1], - socks5_response_text(data[1])); - crStopV; - } - - /* - * Process each address type to find out the size of the rest - * of the packet. Note we can't do this using the natural - * idiom of a switch statement, because there are crReturns - * inside some cases. - */ - if (data[3] == SOCKS5_ADDR_IPV4) { - s->response_addr_length = 4; - } else if (data[3] == SOCKS5_ADDR_IPV6) { - s->response_addr_length = 16; - } else if (data[3] == SOCKS5_ADDR_HOSTNAME) { - /* Read the hostname length byte to find out how much to read */ - unsigned char len; - crMaybeWaitUntilV(bufchain_try_fetch_consume(pn->input, &len, 1)); - s->response_addr_length = len; - break; - } else { - pn->error = dupprintf("SOCKS proxy response included unknown " - "address type %d", (int)data[3]); - crStopV; - } - - /* Read and ignore the address and port fields */ - crMaybeWaitUntilV(bufchain_try_consume( - pn->input, s->response_addr_length + 2)); - } - - /* And we're done! */ - pn->done = true; - crFinishV; -} - -const struct ProxyNegotiatorVT socks5_proxy_negotiator_vt = { - .new = proxy_socks5_new, - .free = proxy_socks5_free, - .process_queue = proxy_socks5_process_queue, - .type = "SOCKS 5", -}; diff --git a/proxy/sshproxy.c b/proxy/sshproxy.c deleted file mode 100644 index 8a20d0acb..000000000 --- a/proxy/sshproxy.c +++ /dev/null @@ -1,783 +0,0 @@ -/* - * sshproxy.c: implement a Socket type that talks to an entire - * subsidiary SSH connection (sometimes called a 'jump host'). - */ - -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "network.h" -#include "storage.h" -#include "proxy.h" - -const bool ssh_proxy_supported = true; - -typedef struct SshProxy { - char *errmsg; - Conf *conf; - LogContext *logctx; - Backend *backend; - LogPolicy *clientlp; - Seat *clientseat; - Interactor *clientitr; - - bool got_proxy_password, tried_proxy_password; - char *proxy_password; - - ProxyStderrBuf psb; - Plug *plug; - - bool frozen; - bufchain ssh_to_socket; - bool rcvd_eof_ssh_to_socket, sent_eof_ssh_to_socket; - bool conn_established; - - SockAddr *addr; - int port; - - /* Traits implemented: we're a Socket from the point of view of - * the client connection, and a Seat from the POV of the SSH - * backend we instantiate. */ - Socket sock; - LogPolicy logpolicy; - Seat seat; -} SshProxy; - -static Plug *sshproxy_plug(Socket *s, Plug *p) -{ - SshProxy *sp = container_of(s, SshProxy, sock); - Plug *oldplug = sp->plug; - if (p) - sp->plug = p; - return oldplug; -} - -static void sshproxy_close(Socket *s) -{ - SshProxy *sp = container_of(s, SshProxy, sock); - - sk_addr_free(sp->addr); - sfree(sp->errmsg); - conf_free(sp->conf); - if (sp->backend) - backend_free(sp->backend); - if (sp->logctx) - log_free(sp->logctx); - if (sp->proxy_password) - burnstr(sp->proxy_password); - bufchain_clear(&sp->ssh_to_socket); - - delete_callbacks_for_context(sp); - sfree(sp); -} - -static size_t sshproxy_write(Socket *s, const void *data, size_t len) -{ - SshProxy *sp = container_of(s, SshProxy, sock); - if (!sp->backend) - return 0; - backend_send(sp->backend, data, len); - return backend_sendbuffer(sp->backend); -} - -static size_t sshproxy_write_oob(Socket *s, const void *data, size_t len) -{ - /* - * oob data is treated as inband; nasty, but nothing really - * better we can do - */ - return sshproxy_write(s, data, len); -} - -static void sshproxy_write_eof(Socket *s) -{ - SshProxy *sp = container_of(s, SshProxy, sock); - if (!sp->backend) - return; - backend_special(sp->backend, SS_EOF, 0); -} - -static void try_send_ssh_to_socket(void *ctx); - -static void try_send_ssh_to_socket_cb(void *ctx) -{ - SshProxy *sp = (SshProxy *)ctx; - try_send_ssh_to_socket(sp); - if (sp->backend) - backend_unthrottle(sp->backend, bufchain_size(&sp->ssh_to_socket)); -} - -static void sshproxy_set_frozen(Socket *s, bool is_frozen) -{ - SshProxy *sp = container_of(s, SshProxy, sock); - sp->frozen = is_frozen; - if (!sp->frozen) - queue_toplevel_callback(try_send_ssh_to_socket_cb, sp); -} - -static const char *sshproxy_socket_error(Socket *s) -{ - SshProxy *sp = container_of(s, SshProxy, sock); - return sp->errmsg; -} - -static SocketPeerInfo *sshproxy_peer_info(Socket *s) -{ - return NULL; -} - -static const SocketVtable SshProxy_sock_vt = { - .plug = sshproxy_plug, - .close = sshproxy_close, - .write = sshproxy_write, - .write_oob = sshproxy_write_oob, - .write_eof = sshproxy_write_eof, - .set_frozen = sshproxy_set_frozen, - .socket_error = sshproxy_socket_error, - .peer_info = sshproxy_peer_info, -}; - -static void sshproxy_eventlog(LogPolicy *lp, const char *event) -{ - SshProxy *sp = container_of(lp, SshProxy, logpolicy); - log_proxy_stderr(sp->plug, &sp->psb, event, strlen(event)); - log_proxy_stderr(sp->plug, &sp->psb, "\n", 1); -} - -static int sshproxy_askappend(LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), - void *ctx) -{ - SshProxy *sp = container_of(lp, SshProxy, logpolicy); - - /* - * If we have access to the outer LogPolicy, pass on this request - * to the end user. - */ - if (sp->clientlp) - return lp_askappend(sp->clientlp, filename, callback, ctx); - - /* - * Otherwise, fall back to the safe noninteractive assumption. - */ - char *msg = dupprintf("Log file \"%s\" already exists; logging cancelled", - filename_to_str(filename)); - sshproxy_eventlog(lp, msg); - sfree(msg); - return 0; -} - -static void sshproxy_logging_error(LogPolicy *lp, const char *event) -{ - SshProxy *sp = container_of(lp, SshProxy, logpolicy); - - /* - * If we have access to the outer LogPolicy, pass on this request - * to it. - */ - if (sp->clientlp) { - lp_logging_error(sp->clientlp, event); - return; - } - - /* - * Otherwise, the best we can do is to put it in the outer SSH - * connection's Event Log. - */ - char *msg = dupprintf("Logging error: %s", event); - sshproxy_eventlog(lp, msg); - sfree(msg); -} - -static const LogPolicyVtable SshProxy_logpolicy_vt = { - .eventlog = sshproxy_eventlog, - .askappend = sshproxy_askappend, - .logging_error = sshproxy_logging_error, - .verbose = null_lp_verbose_no, -}; - -/* - * Function called when we encounter an error during connection setup that's - * likely to be the cause of terminating the proxy SSH connection. Putting it - * in the Event Log is useful on general principles; also putting it in - * sp->errmsg meaks that it will be passed back through plug_closing when the - * proxy SSH connection actually terminates, so that the end user will see - * what went wrong in the proxy connection. - */ -static void sshproxy_error(SshProxy *sp, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - char *msg = dupvprintf(fmt, ap); - va_end(ap); - - if (!sp->errmsg) - sp->errmsg = dupstr(msg); - - sshproxy_eventlog(&sp->logpolicy, msg); - sfree(msg); -} - -static void try_send_ssh_to_socket(void *ctx) -{ - SshProxy *sp = (SshProxy *)ctx; - - if (sp->frozen) - return; - - while (bufchain_size(&sp->ssh_to_socket)) { - ptrlen pl = bufchain_prefix(&sp->ssh_to_socket); - plug_receive(sp->plug, 0, pl.ptr, pl.len); - bufchain_consume(&sp->ssh_to_socket, pl.len); - } - - if (sp->rcvd_eof_ssh_to_socket && - !sp->sent_eof_ssh_to_socket) { - sp->sent_eof_ssh_to_socket = true; - plug_closing_normal(sp->plug); - } -} - -static void sshproxy_notify_session_started(Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - - if (sp->clientseat) - interactor_return_seat(sp->clientitr); - sp->conn_established = true; - - plug_log(sp->plug, PLUGLOG_CONNECT_SUCCESS, sp->addr, sp->port, NULL, 0); -} - -static size_t sshproxy_output(Seat *seat, SeatOutputType type, - const void *data, size_t len) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - switch (type) { - case SEAT_OUTPUT_STDOUT: - bufchain_add(&sp->ssh_to_socket, data, len); - try_send_ssh_to_socket(sp); - break; - case SEAT_OUTPUT_STDERR: - log_proxy_stderr(sp->plug, &sp->psb, data, len); - break; - } - return bufchain_size(&sp->ssh_to_socket); -} - -static inline InteractionReadySeat wrap(Seat *seat) -{ - /* - * When we receive interaction requests from the proxy and want to - * pass them on to our client Seat, we have to present them to the - * latter in the form of an InteractionReadySeat. This forwarding - * scenario is the one case where we _mustn't_ get an - * InteractionReadySeat by calling interactor_announce(), because - * the point is that we're _not_ the originating Interactor, we're - * just forwarding the request from the real one, which has - * already announced itself. - * - * So, just here in the code, it really is the right thing to make - * an InteractionReadySeat out of a plain Seat * without an - * announcement. - */ - InteractionReadySeat iseat; - iseat.seat = seat; - return iseat; -} - -static size_t sshproxy_banner(Seat *seat, const void *data, size_t len) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - if (sp->clientseat) { - /* - * If we have access to the outer Seat, pass the SSH login - * banner on to it. - */ - return seat_banner(wrap(sp->clientseat), data, len); - } else { - return 0; - } -} - -static bool sshproxy_eof(Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - sp->rcvd_eof_ssh_to_socket = true; - try_send_ssh_to_socket(sp); - return false; -} - -static void sshproxy_sent(Seat *seat, size_t new_bufsize) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - plug_sent(sp->plug, new_bufsize); -} - -static void sshproxy_send_close(SshProxy *sp) -{ - if (sp->clientseat) - interactor_return_seat(sp->clientitr); - - if (!sp->conn_established) - plug_log(sp->plug, PLUGLOG_CONNECT_FAILED, sp->addr, sp->port, - sp->errmsg, 0); - - if (sp->errmsg) - plug_closing_error(sp->plug, sp->errmsg); - else if (!sp->conn_established && backend_exitcode(sp->backend) == 0) - plug_closing_user_abort(sp->plug); - else - plug_closing_normal(sp->plug); -} - -static void sshproxy_notify_remote_disconnect_callback(void *vctx) -{ - SshProxy *sp = (SshProxy *)vctx; - - /* notify_remote_disconnect can be called redundantly, so first - * check if the backend really has become disconnected */ - if (backend_connected(sp->backend)) - return; - - sshproxy_send_close(sp); -} - -static void sshproxy_notify_remote_disconnect(Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - queue_toplevel_callback(sshproxy_notify_remote_disconnect_callback, sp); -} - -static SeatPromptResult sshproxy_get_userpass_input(Seat *seat, prompts_t *p) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - - /* - * If we have a stored proxy_password, use that, via logic similar - * to cmdline_get_passwd_input: we only try it if we're given a - * prompts_t containing exactly one prompt, and that prompt is set - * to non-echoing. - */ - if (sp->got_proxy_password && !sp->tried_proxy_password && - p->n_prompts == 1 && !p->prompts[0]->echo) { - prompt_set_result(p->prompts[0], sp->proxy_password); - burnstr(sp->proxy_password); - sp->proxy_password = NULL; - sp->tried_proxy_password = true; - return SPR_OK; - } - - if (sp->clientseat) { - /* - * If we have access to the outer Seat, pass this prompt - * request on to it. - */ - return seat_get_userpass_input(wrap(sp->clientseat), p); - } - - /* - * Otherwise, behave as if noninteractive (like plink -batch): - * reject all attempts to present a prompt to the user, and log in - * the Event Log to say why not. - */ - sshproxy_error(sp, "Unable to provide interactive authentication " - "requested by proxy SSH connection"); - return SPR_SW_ABORT("Noninteractive SSH proxy cannot perform " - "interactive authentication"); -} - -static void sshproxy_connection_fatal_callback(void *vctx) -{ - SshProxy *sp = (SshProxy *)vctx; - sshproxy_send_close(sp); -} - -static void sshproxy_connection_fatal(Seat *seat, const char *message) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - if (!sp->errmsg) { - sp->errmsg = dupprintf( - "fatal error in proxy SSH connection: %s", message); - queue_toplevel_callback(sshproxy_connection_fatal_callback, sp); - } -} - -static void sshproxy_nonfatal(Seat *seat, const char *message) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - if (sp->clientseat) - seat_nonfatal(sp->clientseat, "error in proxy SSH connection: %s", - message); -} - -static SeatPromptResult sshproxy_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - - if (sp->clientseat) { - /* - * If we have access to the outer Seat, pass this prompt - * request on to it. - */ - return seat_confirm_ssh_host_key( - wrap(sp->clientseat), host, port, keytype, keystr, text, - helpctx, callback, ctx); - } - - /* - * Otherwise, behave as if we're in batch mode, i.e. take the safe - * option in the absence of interactive confirmation, i.e. abort - * the connection. - */ - return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm host key"); -} - -static void sshproxy_format_seatdialogtext(strbuf *sb, SeatDialogText *text) -{ - for (SeatDialogTextItem *item = text->items, - *end = item+text->nitems; item < end; item++) { - switch (item->type) { - case SDT_SCARY_HEADING: - case SDT_PARA: - case SDT_DISPLAY: - put_stringz(sb, item->text); - put_byte(sb, '\n'); - break; - case SDT_BATCH_ABORT: - put_stringz(sb, item->text); - put_byte(sb, '\n'); - goto endloop; - default: - break; - } - } - - endloop: - while (strbuf_chomp(sb, '\n')); -} - -static SeatPromptResult sshproxy_confirm_weak_crypto_primitive( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - - if (sp->clientseat) { - /* - * If we have access to the outer Seat, pass this prompt - * request on to it. - */ - return seat_confirm_weak_crypto_primitive( - wrap(sp->clientseat), text, callback, ctx); - } - - /* - * Otherwise, behave as if we're in batch mode: take the safest - * option. - */ - strbuf *sb = strbuf_new(); - sshproxy_format_seatdialogtext(sb, text); - sshproxy_error(sp, sb->s); - strbuf_free(sb); - - return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm " - "weak crypto primitive"); -} - -static SeatPromptResult sshproxy_confirm_weak_cached_hostkey( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - - if (sp->clientseat) { - /* - * If we have access to the outer Seat, pass this prompt - * request on to it. - */ - return seat_confirm_weak_cached_hostkey( - wrap(sp->clientseat), text, callback, ctx); - } - - /* - * Otherwise, behave as if we're in batch mode: take the safest - * option. - */ - strbuf *sb = strbuf_new(); - sshproxy_format_seatdialogtext(sb, text); - sshproxy_error(sp, sb->s); - strbuf_free(sb); - - return SPR_SW_ABORT("Noninteractive SSH proxy cannot confirm " - "weak cached host key"); -} - -static const SeatDialogPromptDescriptions *sshproxy_prompt_descriptions( - Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - - /* If we have a client seat, return their prompt descriptions, so - * that prompts passed on to them will make sense. */ - if (sp->clientseat) - return seat_prompt_descriptions(sp->clientseat); - - /* Otherwise, it doesn't matter what we return, so do the easiest thing. */ - return nullseat_prompt_descriptions(NULL); -} - -static StripCtrlChars *sshproxy_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - if (sp->clientseat) - return seat_stripctrl_new(sp->clientseat, bs_out, sic); - else - return NULL; -} - -static void sshproxy_set_trust_status(Seat *seat, bool trusted) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - if (sp->clientseat) - seat_set_trust_status(sp->clientseat, trusted); -} - -static bool sshproxy_can_set_trust_status(Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - return sp->clientseat && seat_can_set_trust_status(sp->clientseat); -} - -static bool sshproxy_verbose(Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - return sp->clientseat && seat_verbose(sp->clientseat); -} - -static bool sshproxy_interactive(Seat *seat) -{ - SshProxy *sp = container_of(seat, SshProxy, seat); - return sp->clientseat && seat_interactive(sp->clientseat); -} - -static const SeatVtable SshProxy_seat_vt = { - .output = sshproxy_output, - .eof = sshproxy_eof, - .sent = sshproxy_sent, - .banner = sshproxy_banner, - .get_userpass_input = sshproxy_get_userpass_input, - .notify_session_started = sshproxy_notify_session_started, - .notify_remote_exit = nullseat_notify_remote_exit, - .notify_remote_disconnect = sshproxy_notify_remote_disconnect, - .connection_fatal = sshproxy_connection_fatal, - .nonfatal = sshproxy_nonfatal, - .update_specials_menu = nullseat_update_specials_menu, - .get_ttymode = nullseat_get_ttymode, - .set_busy_status = nullseat_set_busy_status, - .confirm_ssh_host_key = sshproxy_confirm_ssh_host_key, - .confirm_weak_crypto_primitive = sshproxy_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = sshproxy_confirm_weak_cached_hostkey, - .prompt_descriptions = sshproxy_prompt_descriptions, - .is_utf8 = nullseat_is_never_utf8, - .echoedit_update = nullseat_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = nullseat_get_window_pixel_size, - .stripctrl_new = sshproxy_stripctrl_new, - .set_trust_status = sshproxy_set_trust_status, - .can_set_trust_status = sshproxy_can_set_trust_status, - .has_mixed_input_stream = nullseat_has_mixed_input_stream_no, - .verbose = sshproxy_verbose, - .interactive = sshproxy_interactive, - .get_cursor_position = nullseat_get_cursor_position, -}; - -Socket *sshproxy_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *clientconf, - Interactor *clientitr) -{ - SshProxy *sp = snew(SshProxy); - memset(sp, 0, sizeof(*sp)); - - sp->sock.vt = &SshProxy_sock_vt; - sp->logpolicy.vt = &SshProxy_logpolicy_vt; - sp->seat.vt = &SshProxy_seat_vt; - sp->plug = plug; - psb_init(&sp->psb); - bufchain_init(&sp->ssh_to_socket); - - sp->addr = addr; - sp->port = port; - - sp->conf = conf_new(); - /* Try to treat proxy_hostname as the title of a saved session. If - * that fails, set up a default Conf of our own treating it as a - * hostname. */ - const char *proxy_hostname = conf_get_str(clientconf, CONF_proxy_host); - if (do_defaults(proxy_hostname, sp->conf)) { - if (!conf_launchable(sp->conf)) { - sp->errmsg = dupprintf("saved session '%s' is not launchable", - proxy_hostname); - return &sp->sock; - } - } else { - do_defaults(NULL, sp->conf); - /* In hostname mode, we default to PROT_SSH. This is more useful than - * the obvious approach of defaulting to the protocol defined in - * Default Settings, because only SSH (ok, and bare ssh-connection) - * can be used for this kind of proxy. */ - conf_set_int(sp->conf, CONF_protocol, PROT_SSH); - conf_set_str(sp->conf, CONF_host, proxy_hostname); - conf_set_int(sp->conf, CONF_port, - conf_get_int(clientconf, CONF_proxy_port)); - } - const char *proxy_username = conf_get_str(clientconf, CONF_proxy_username); - if (*proxy_username) - conf_set_str(sp->conf, CONF_username, proxy_username); - - const char *proxy_password = conf_get_str(clientconf, CONF_proxy_password); - if (*proxy_password) { - sp->proxy_password = dupstr(proxy_password); - sp->got_proxy_password = true; - } - - const struct BackendVtable *backvt = backend_vt_from_proto( - conf_get_int(sp->conf, CONF_protocol)); - - /* - * We don't actually need an _SSH_ session specifically: it's also - * OK to use PROT_SSHCONN, because really, the criterion is - * whether setting CONF_ssh_nc_host will do anything useful. So - * our check is for whether the backend sets the flag promising - * that it does. - */ - if (!backvt || !(backvt->flags & BACKEND_SUPPORTS_NC_HOST)) { - sp->errmsg = dupprintf("saved session '%s' is not an SSH session", - proxy_hostname); - return &sp->sock; - } - - /* - * We also expect that the backend will announce a willingness to - * notify us that the session has started. Any backend providing - * NC_HOST should also provide this. - */ - assert(backvt->flags & BACKEND_NOTIFIES_SESSION_START && - "Backend provides NC_HOST without SESSION_START!"); - - /* - * Turn off SSH features we definitely don't want. It would be - * awkward and counterintuitive to have the proxy SSH connection - * become a connection-sharing upstream (but it's fine to have it - * be a downstream, if that's configured). And we don't want to - * open X forwardings, agent forwardings or (other) port - * forwardings as a side effect of this one operation. - */ - conf_set_bool(sp->conf, CONF_ssh_connection_sharing_upstream, false); - conf_set_bool(sp->conf, CONF_x11_forward, false); - conf_set_bool(sp->conf, CONF_agentfwd, false); - for (const char *subkey; - (subkey = conf_get_str_nthstrkey(sp->conf, CONF_portfwd, 0)) != NULL;) - conf_del_str_str(sp->conf, CONF_portfwd, subkey); - - /* - * We'll only be running one channel through this connection - * (since we've just turned off all the other things we might have - * done with it), so we can configure it as simple. - */ - conf_set_bool(sp->conf, CONF_ssh_simple, true); - - int proxy_type = conf_get_int(clientconf, CONF_proxy_type); - switch (proxy_type) { - case PROXY_SSH_TCPIP: - /* - * Configure the main channel of this SSH session to be a - * direct-tcpip connection to the destination host/port. - */ - conf_set_str(sp->conf, CONF_ssh_nc_host, hostname); - conf_set_int(sp->conf, CONF_ssh_nc_port, port); - break; - - case PROXY_SSH_SUBSYSTEM: - case PROXY_SSH_EXEC: { - Conf *cmd_conf = conf_copy(clientconf); - - /* - * Unlike the Telnet and Local proxy types, we don't use the - * proxy username and password fields in the formatted - * command, because if we use them at all, it's for - * authenticating to the proxy SSH server. - */ - conf_set_str(cmd_conf, CONF_proxy_username, ""); - conf_set_str(cmd_conf, CONF_proxy_password, ""); - - char *cmd = format_telnet_command(sp->addr, sp->port, cmd_conf, NULL); - conf_free(cmd_conf); - - conf_set_str(sp->conf, CONF_remote_cmd, cmd); - sfree(cmd); - - conf_set_bool(sp->conf, CONF_nopty, true); - - if (proxy_type == PROXY_SSH_SUBSYSTEM) - conf_set_bool(sp->conf, CONF_ssh_subsys, true); - - break; - } - - default: - unreachable("bad SSH proxy type"); - } - - /* - * Do the usual normalisation of things in the Conf like a "user@" - * prefix on the hostname field. - */ - prepare_session(sp->conf); - - sp->logctx = log_init(&sp->logpolicy, sp->conf); - - char *error, *realhost; - error = backend_init(backvt, &sp->seat, &sp->backend, sp->logctx, sp->conf, - conf_get_str(sp->conf, CONF_host), - conf_get_int(sp->conf, CONF_port), - &realhost, nodelay, - conf_get_bool(sp->conf, CONF_tcp_keepalives)); - if (error) { - sp->errmsg = dupprintf("unable to open SSH proxy connection: %s", - error); - return &sp->sock; - } - - sfree(realhost); - - /* - * If we've been given an Interactor by the caller, set ourselves - * up to work with it. - */ - if (clientitr) { - sp->clientitr = clientitr; - interactor_set_child(sp->clientitr, sp->backend->interactor); - - sp->clientlp = interactor_logpolicy(clientitr); - - /* - * We can only borrow the client's Seat if our own backend - * will tell us when to give it back. (SSH-based backends - * _should_ do that, but we check the flag here anyway.) - */ - if (backvt->flags & BACKEND_NOTIFIES_SESSION_START) - sp->clientseat = interactor_borrow_seat(clientitr); - } - - return &sp->sock; -} diff --git a/proxy/telnet.c b/proxy/telnet.c deleted file mode 100644 index a2efb7b43..000000000 --- a/proxy/telnet.c +++ /dev/null @@ -1,368 +0,0 @@ -/* - * "Telnet" proxy negotiation. - * - * (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.) - */ - -#include "putty.h" -#include "network.h" -#include "proxy.h" -#include "sshcr.h" - -char *format_telnet_command(SockAddr *addr, int port, Conf *conf, - unsigned *flags_out) -{ - char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); - int so = 0, eo = 0; - strbuf *buf = strbuf_new(); - unsigned flags = 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) - put_data(buf, fmt + so, 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 '\\': - put_byte(buf, '\\'); - eo++; - break; - - case '%': - put_byte(buf, '%'); - eo++; - break; - - case 'r': - put_byte(buf, '\r'); - eo++; - break; - - case 'n': - put_byte(buf, '\n'); - eo++; - break; - - case 't': - put_byte(buf, '\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) - */ - put_byte(buf, '\\'); - eo = so + 1; - break; - } - - /* we only extract two hex characters */ - if (i == 1) { - put_byte(buf, v); - eo++; - break; - } - - i++; - v <<= 4; - } - break; - } - - default: - put_data(buf, fmt + so, 2); - eo++; - break; - } - } else { - - /* % escape. we recognize %%, %host, %port, %user, %pass. - * %proxyhost, %proxyport. Anything else we just send - * unescaped (including the %). - */ - - if (fmt[eo] == '%') { - put_byte(buf, '%'); - eo++; - } - else if (strnicmp(fmt + eo, "host", 4) == 0) { - char dest[512]; - sk_getaddr(addr, dest, lenof(dest)); - put_data(buf, dest, strlen(dest)); - eo += 4; - } - else if (strnicmp(fmt + eo, "port", 4) == 0) { - put_fmt(buf, "%d", port); - eo += 4; - } - else if (strnicmp(fmt + eo, "user", 4) == 0) { - const char *username = conf_get_str(conf, CONF_proxy_username); - put_data(buf, username, strlen(username)); - eo += 4; - if (!*username) - flags |= TELNET_CMD_MISSING_USERNAME; - } - else if (strnicmp(fmt + eo, "pass", 4) == 0) { - const char *password = conf_get_str(conf, CONF_proxy_password); - put_data(buf, password, strlen(password)); - eo += 4; - if (!*password) - flags |= TELNET_CMD_MISSING_PASSWORD; - } - else if (strnicmp(fmt + eo, "proxyhost", 9) == 0) { - const char *host = conf_get_str(conf, CONF_proxy_host); - put_data(buf, host, strlen(host)); - eo += 9; - } - else if (strnicmp(fmt + eo, "proxyport", 9) == 0) { - int port = conf_get_int(conf, CONF_proxy_port); - put_fmt(buf, "%d", port); - 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. - */ - put_byte(buf, '%'); - } - } - - /* 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) { - put_data(buf, fmt + so, eo - so); - } - - if (flags_out) - *flags_out = flags; - return strbuf_to_str(buf); -} - -typedef struct TelnetProxyNegotiator { - int crLine; - Conf *conf; - char *formatted_cmd; - prompts_t *prompts; - int username_prompt_index, password_prompt_index; - ProxyNegotiator pn; -} TelnetProxyNegotiator; - -static ProxyNegotiator *proxy_telnet_new(const ProxyNegotiatorVT *vt) -{ - TelnetProxyNegotiator *s = snew(TelnetProxyNegotiator); - memset(s, 0, sizeof(*s)); - s->pn.vt = vt; - return &s->pn; -} - -static void proxy_telnet_free(ProxyNegotiator *pn) -{ - TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn); - if (s->conf) - conf_free(s->conf); - if (s->prompts) - free_prompts(s->prompts); - burnstr(s->formatted_cmd); - delete_callbacks_for_context(s); - sfree(s); -} - -static void proxy_telnet_process_queue_callback(void *vctx) -{ - TelnetProxyNegotiator *s = (TelnetProxyNegotiator *)vctx; - proxy_negotiator_process_queue(&s->pn); -} - -static void proxy_telnet_process_queue(ProxyNegotiator *pn) -{ - TelnetProxyNegotiator *s = container_of(pn, TelnetProxyNegotiator, pn); - - crBegin(s->crLine); - - s->conf = conf_copy(pn->ps->conf); - - /* - * Make an initial attempt to figure out the command we want, and - * see if it tried to include a username or password that we don't - * have. - */ - { - unsigned flags; - s->formatted_cmd = format_telnet_command( - pn->ps->remote_addr, pn->ps->remote_port, s->conf, &flags); - - if (pn->itr && (flags & (TELNET_CMD_MISSING_USERNAME | - TELNET_CMD_MISSING_PASSWORD))) { - burnstr(s->formatted_cmd); - s->formatted_cmd = NULL; - - /* - * We're missing at least one of the two parts, and we - * have an Interactor we can use to prompt for them, so - * try it. - */ - s->prompts = proxy_new_prompts(pn->ps); - s->prompts->to_server = true; - s->prompts->from_server = false; - s->prompts->name = dupstr("Telnet proxy authentication"); - if (flags & TELNET_CMD_MISSING_USERNAME) { - s->username_prompt_index = s->prompts->n_prompts; - add_prompt(s->prompts, dupstr("Proxy username: "), true); - } else { - s->username_prompt_index = -1; - } - if (flags & TELNET_CMD_MISSING_PASSWORD) { - s->password_prompt_index = s->prompts->n_prompts; - add_prompt(s->prompts, dupstr("Proxy password: "), false); - } else { - s->password_prompt_index = -1; - } - - /* - * This prompt is presented extremely early in PuTTY's - * setup. (Very promptly, you might say.) - * - * In particular, we can get here through a chain of - * synchronous calls from backend_init, which means (in - * GUI PuTTY) that the terminal we'll be sending this - * prompt to may not have its Ldisc set up yet (due to - * cyclic dependencies among all the things that have to - * be initialised). - * - * So we'll start by having ourself called back via a - * toplevel callback, to make sure we don't call - * seat_get_userpass_input until we've returned from - * backend_init and the frontend has finished getting - * everything ready. - */ - queue_toplevel_callback(proxy_telnet_process_queue_callback, s); - crReturnV; - - while (true) { - SeatPromptResult spr = seat_get_userpass_input( - interactor_announce(pn->itr), s->prompts); - if (spr.kind == SPRK_OK) { - break; - } else if (spr_is_abort(spr)) { - proxy_spr_abort(pn, spr); - crStopV; - } - crReturnV; - } - - if (s->username_prompt_index != -1) { - conf_set_str( - s->conf, CONF_proxy_username, - prompt_get_result_ref( - s->prompts->prompts[s->username_prompt_index])); - } - - if (s->password_prompt_index != -1) { - conf_set_str( - s->conf, CONF_proxy_password, - prompt_get_result_ref( - s->prompts->prompts[s->password_prompt_index])); - } - - free_prompts(s->prompts); - s->prompts = NULL; - } - - /* - * Now format the command a second time, with the results of - * those prompts written into s->conf. - */ - s->formatted_cmd = format_telnet_command( - pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL); - } - - /* - * Log the command, with some changes. Firstly, we regenerate it - * with the password masked; secondly, we escape control - * characters so that the log message is printable. - */ - conf_set_str(s->conf, CONF_proxy_password, "*password*"); - { - char *censored_cmd = format_telnet_command( - pn->ps->remote_addr, pn->ps->remote_port, s->conf, NULL); - - strbuf *logmsg = strbuf_new(); - put_datapl(logmsg, PTRLEN_LITERAL("Sending Telnet proxy command: ")); - put_c_string_literal(logmsg, ptrlen_from_asciz(censored_cmd)); - - plug_log(pn->ps->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg->s, 0); - strbuf_free(logmsg); - sfree(censored_cmd); - } - - /* - * Actually send the command. - */ - put_dataz(pn->output, s->formatted_cmd); - - /* - * Unconditionally report success. We don't hang around waiting - * for error messages from the proxy, because this proxy type is - * so ad-hoc that we wouldn't know how to even recognise an error - * message if we saw one, let alone what to do about it. - */ - pn->done = true; - - crFinishV; -} - -const struct ProxyNegotiatorVT telnet_proxy_negotiator_vt = { - .new = proxy_telnet_new, - .free = proxy_telnet_free, - .process_queue = proxy_telnet_process_queue, - .type = "Telnet", -}; diff --git a/pscp.c b/pscp.c deleted file mode 100644 index 77de1cd99..000000000 --- a/pscp.c +++ /dev/null @@ -1,2374 +0,0 @@ -/* - * pscp.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 - -#include "putty.h" -#include "psftp.h" -#include "ssh.h" -#include "ssh/sftp.h" -#include "storage.h" - -static bool list = false; -static bool verbose = false; -static bool recursive = false; -static bool preserve = false; -static bool targetshouldbedirectory = false; -static bool statistics = true; -static int prev_stats_len = 0; -static bool scp_unsafe_mode = false; -static int errs = 0; -static bool try_scp = true; -static bool try_sftp = true; -static bool main_cmd_is_sftp = false; -static bool fallback_cmd_is_sftp = false; -static bool using_sftp = false; -static bool uploading = false; - -static Backend *backend; -static Conf *conf; -static bool sent_eof = false; - -static void source(const char *src); -static void rsource(const char *src); -static void sink(const char *targ, const char *src); - -/* - * 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_echoedit_update(Ldisc *ldisc) { } -void ldisc_check_sendok(Ldisc *ldisc) { } - -static size_t pscp_output(Seat *, SeatOutputType type, const void *, size_t); -static bool pscp_eof(Seat *); - -static const SeatVtable pscp_seat_vt = { - .output = pscp_output, - .eof = pscp_eof, - .sent = nullseat_sent, - .banner = nullseat_banner_to_stderr, - .get_userpass_input = filexfer_get_userpass_input, - .notify_session_started = nullseat_notify_session_started, - .notify_remote_exit = nullseat_notify_remote_exit, - .notify_remote_disconnect = nullseat_notify_remote_disconnect, - .connection_fatal = console_connection_fatal, - .update_specials_menu = nullseat_update_specials_menu, - .get_ttymode = nullseat_get_ttymode, - .set_busy_status = nullseat_set_busy_status, - .confirm_ssh_host_key = console_confirm_ssh_host_key, - .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, - .prompt_descriptions = console_prompt_descriptions, - .is_utf8 = nullseat_is_never_utf8, - .echoedit_update = nullseat_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = nullseat_get_window_pixel_size, - .stripctrl_new = console_stripctrl_new, - .set_trust_status = nullseat_set_trust_status, - .can_set_trust_status = nullseat_can_set_trust_status_yes, - .has_mixed_input_stream = nullseat_has_mixed_input_stream_no, - .verbose = cmdline_seat_verbose, - .interactive = nullseat_interactive_no, - .get_cursor_position = nullseat_get_cursor_position, -}; -static Seat pscp_seat[1] = {{ &pscp_seat_vt }}; - -static void tell_char(FILE *stream, char c) -{ - fputc(c, stream); -} - -static void tell_str(FILE *stream, const char *str) -{ - unsigned int i; - - for (i = 0; i < strlen(str); ++i) - tell_char(stream, str[i]); -} - -static void abandon_stats(void) -{ - /* - * Output a \n to stdout (which is where we've been sending - * transfer statistics) so that the cursor will move to the next - * line. We should do this before displaying any other kind of - * output like an error message. - */ - if (prev_stats_len) { - putchar('\n'); - fflush(stdout); - prev_stats_len = 0; - } -} - -static PRINTF_LIKE(2, 3) void tell_user(FILE *stream, const char *fmt, ...) -{ - char *str, *str2; - va_list ap; - va_start(ap, fmt); - str = dupvprintf(fmt, ap); - va_end(ap); - str2 = dupcat(str, "\n"); - sfree(str); - abandon_stats(); - tell_str(stream, str2); - sfree(str2); -} - -/* - * 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 pscp_output() function to catch the data that comes back. We do - * this until we have enough data. - */ - -static bufchain received_data; -static BinarySink *stderr_bs; -static size_t pscp_output( - Seat *seat, SeatOutputType type, const void *data, size_t len) -{ - /* - * Non-stdout data (both stderr and SSH auth banners) is just - * spouted to local stderr (optionally via a sanitiser) and - * otherwise ignored. - */ - if (type != SEAT_OUTPUT_STDOUT) { - put_data(stderr_bs, data, len); - return 0; - } - - bufchain_add(&received_data, data, len); - return 0; -} -static bool pscp_eof(Seat *seat) -{ - /* - * 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) { - seat_connection_fatal( - pscp_seat, "Received unexpected end-of-file from server"); - } - return false; -} -static bool ssh_scp_recv(void *vbuf, size_t len) -{ - char *buf = (char *)vbuf; - while (len > 0) { - while (bufchain_size(&received_data) == 0) { - if (backend_exitcode(backend) >= 0 || - ssh_sftp_loop_iteration() < 0) - return false; /* doom */ - } - - size_t got = bufchain_fetch_consume_up_to(&received_data, buf, len); - buf += got; - len -= got; - } - - return true; -} - -/* - * Loop through the ssh connection and authentication process. - */ -static void ssh_scp_init(void) -{ - while (!backend_sendok(backend)) { - if (backend_exitcode(backend) >= 0) { - errs++; - return; - } - if (ssh_sftp_loop_iteration() < 0) { - errs++; - return; /* doom */ - } - } - - /* Work out which backend we ended up using. */ - if (!ssh_fallback_cmd(backend)) - 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 NORETURN PRINTF_LIKE(1, 2) void bump(const char *fmt, ...) -{ - char *str, *str2; - va_list ap; - va_start(ap, fmt); - str = dupvprintf(fmt, ap); - va_end(ap); - str2 = dupcat(str, "\n"); - sfree(str); - abandon_stats(); - tell_str(stderr, str2); - sfree(str2); - errs++; - - if (backend && backend_connected(backend)) { - char ch; - backend_special(backend, SS_EOF, 0); - sent_eof = true; - ssh_scp_recv(&ch, 1); - } - - cleanup_exit(1); -} - -/* - * A nasty loop macro that lets me get an escape-sequence sanitised - * version of a string for display, and free it automatically - * afterwards. - */ -static StripCtrlChars *string_scc; -#define with_stripctrl(varname, input) \ - for (char *varname = stripctrl_string(string_scc, input); varname; \ - sfree(varname), varname = NULL) - -/* - * 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 seat_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) { - seat_connection_fatal( - pscp_seat, "did not receive SFTP response packet from server"); - } - rreq = sftp_find_request(pktin); - if (rreq != req) { - seat_connection_fatal( - pscp_seat, - "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; - LogContext *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 (!cmdline_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 protocol to SSH if the user has somehow contrived to - * select one we don't support (e.g. by loading an inappropriate - * saved session). In that situation we assume the port number is - * useless too.) - */ - if (!backend_vt_from_proto(conf_get_int(conf, CONF_protocol))) { - 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); - } - } - - /* - * Force protocol to SSH if the user has somehow contrived to - * select one we don't support (e.g. by loading an inappropriate - * saved session). In that situation we assume the port number is - * useless too.) - */ - if (!backend_vt_from_proto(conf_get_int(conf, CONF_protocol))) { - conf_set_int(conf, CONF_protocol, PROT_SSH); - conf_set_int(conf, CONF_port, 22); - } - - /* - * Disable scary things which shouldn't be enabled for simple - * things like SCP and SFTP: agent forwarding, port forwarding, - * X forwarding. - */ - conf_set_bool(conf, CONF_x11_forward, false); - conf_set_bool(conf, CONF_agentfwd, false); - conf_set_bool(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 = true; - conf_set_str(conf, CONF_remote_cmd, "sftp"); - conf_set_bool(conf, CONF_ssh_subsys, true); - if (try_scp) { - /* Fallback is to use the provided scp command. */ - fallback_cmd_is_sftp = false; - conf_set_str(conf, CONF_remote_cmd2, cmd); - conf_set_bool(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 = true; - /* 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_bool(conf, CONF_ssh_subsys2, false); - } - } else { - /* Don't try SFTP at all; just try the scp command. */ - main_cmd_is_sftp = false; - conf_set_str(conf, CONF_remote_cmd, cmd); - conf_set_bool(conf, CONF_ssh_subsys, false); - } - conf_set_bool(conf, CONF_nopty, true); - - logctx = log_init(console_cli_logpolicy, conf); - - platform_psftp_pre_conn_setup(console_cli_logpolicy); - - err = backend_init(backend_vt_from_proto( - conf_get_int(conf, CONF_protocol)), - pscp_seat, &backend, logctx, conf, - conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), - &realhost, 0, - conf_get_bool(conf, CONF_tcp_keepalives)); - if (err != NULL) - bump("ssh_init: %s", err); - 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(const char *name, uint64_t size, uint64_t done, - time_t start, time_t now) -{ - float ratebs; - unsigned long eta; - char *etastr; - int pct; - int len; - int elap; - - elap = (unsigned long) difftime(now, start); - - if (now > start) - ratebs = (float)done / elap; - else - ratebs = (float)done; - - if (ratebs < 1.0) - eta = size - done; - else - eta = (unsigned long)((size - done) / ratebs); - - etastr = dupprintf("%02ld:%02ld:%02ld", - eta / 3600, (eta % 3600) / 60, eta % 60); - - pct = (int) (100.0 * done / size); - - { - /* divide by 1024 to provide kB */ - len = printf("\r%-25.25s | %"PRIu64" kB | %5.1f kB/s | " - "ETA: %8s | %3d%%", name, done >> 10, - ratebs / 1024.0, etastr, pct); - if (len < prev_stats_len) - printf("%*s", prev_stats_len - len, ""); - prev_stats_len = len; - - if (done == size) - abandon_stats(); - - 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); -} - -/* - * Determine whether a string is entirely composed of dots. - */ -static bool 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(&resp, 1)) - 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(&ch, 1)) - 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); - } -} - -bool sftp_recvdata(char *buf, size_t len) -{ - return ssh_scp_recv(buf, len); -} -bool sftp_senddata(const char *buf, size_t len) -{ - backend_send(backend, buf, len); - return true; -} -size_t sftp_sendbuffer(void) -{ - return backend_sendbuffer(backend); -} - -/* ---------------------------------------------------------------------- - * sftp-based replacement for the hacky `pscp -ls'. - */ -void list_directory_from_sftp_warn_unsorted(void) -{ - fprintf(stderr, - "Directory is too large to sort; writing file names unsorted\n"); -} - -void list_directory_from_sftp_print(struct fxp_name *name) -{ - with_stripctrl(san, name->longname) - printf("%s\n", san); -} - -void scp_sftp_listdir(const char *dirname) -{ - struct fxp_handle *dirh; - struct fxp_names *names; - struct sftp_packet *pktin; - struct sftp_request *req; - - 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) { - tell_user(stderr, "Unable to open %s: %s\n", dirname, fxp_error()); - errs++; - } else { - struct list_directory_from_sftp_ctx *ctx = - list_directory_from_sftp_new(); - - 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; - } - - for (size_t i = 0; i < names->nnames; i++) - list_directory_from_sftp_feed(ctx, &names->names[i]); - - fxp_free_names(names); - } - req = fxp_close_send(dirh); - pktin = sftp_wait_for_reply(req); - fxp_close_recv(pktin, req); - - list_directory_from_sftp_finish(ctx); - list_directory_from_sftp_free(ctx); - } -} - -/* ---------------------------------------------------------------------- - * 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; - bool 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 bool scp_sftp_targetisdir, scp_sftp_donethistarget; -static bool scp_sftp_preserve, scp_sftp_recursive; -static unsigned long scp_sftp_mtime, scp_sftp_atime; -static bool scp_has_times; -static struct fxp_handle *scp_sftp_filehandle; -static struct fxp_xfer *scp_sftp_xfer; -static uint64_t scp_sftp_fileoffset; - -int scp_source_setup(const char *target, bool 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; - bool 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 = false; - 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 = false; - } 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 { - backend_send(backend, "\001", 1);/* scp protocol error prefix */ - backend_send(backend, 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 = true; - return 0; - } else { - char buf[80]; - sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime); - backend_send(backend, buf, strlen(buf)); - return response(); - } -} - -int scp_send_filename(const char *name, uint64_t 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); - } 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 = 0; - scp_sftp_xfer = xfer_upload_init(scp_sftp_filehandle, - scp_sftp_fileoffset); - sfree(fullname); - return 0; - } else { - char *buf; - if (permissions < 0) - permissions = 0644; - buf = dupprintf("C%04o %"PRIu64" ", (int)(permissions & 07777), size); - backend_send(backend, buf, strlen(buf)); - sfree(buf); - backend_send(backend, name, strlen(name)); - backend_send(backend, "\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)) { - if (toplevel_callback_pending()) { - /* If we have pending callbacks, they might make - * xfer_upload_ready start to return true. So we should - * run them and then re-check xfer_upload_ready, before - * we go as far as waiting for an entire packet to - * arrive. */ - run_toplevel_callbacks(); - continue; - } - 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 += len; - return 0; - } else { - backend_send(backend, data, len); - int bufsize = backend_sendbuffer(backend); - - /* - * 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 = backend_sendbuffer(backend); - } - - return 0; - } -} - -int scp_send_finish(void) -{ - if (using_sftp) { - struct fxp_attrs attrs; - struct sftp_packet *pktin; - struct sftp_request *req; - - while (!xfer_done(scp_sftp_xfer)) { - pktin = sftp_recv(); - int 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); - bool 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 = false; - return 0; - } else { - backend_send(backend, "", 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(const char *name, int modes) -{ - if (using_sftp) { - char *fullname; - char const *err; - struct fxp_attrs attrs; - struct sftp_packet *pktin; - struct sftp_request *req; - bool ret; - - if (scp_sftp_targetisdir) { - fullname = dupcat(scp_sftp_remotepath, "/", name); - } 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, NULL); - 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); - backend_send(backend, buf, strlen(buf)); - backend_send(backend, name, strlen(name)); - backend_send(backend, "\n", 1); - return response(); - } -} - -int scp_send_enddir(void) -{ - if (using_sftp) { - sfree(scp_sftp_remotepath); - return 0; - } else { - backend_send(backend, "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(const char *source, bool preserve, bool 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, false); - 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 = false; - scp_sftp_dirstack_head = NULL; - } - return 0; -} - -int scp_sink_init(void) -{ - if (!using_sftp) { - backend_send(backend, "", 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 */ - strbuf *buf; /* will need freeing after use */ - char *name; /* filename or dirname (not ENDDIR) */ - long permissions; /* access permissions (not ENDDIR) */ - uint64_t size; /* file size (not ENDDIR) */ - bool settime; /* true 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; - bool must_free_fname; - struct fxp_attrs attrs; - struct sftp_packet *pktin; - struct sftp_request *req; - bool 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 = false; - scp_sftp_donethistarget = true; - } 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 = true; - fname = dupcat(head->dirpath, "/", - head->names[head->namepos++].filename); - must_free_fname = true; - } 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)) { - with_stripctrl(san, fname) - tell_user(stderr, "unable to identify %s: %s", san, - 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; - size_t 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) { - with_stripctrl(san, fname) - tell_user(stderr, "pscp: %s: is a directory", san); - 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) { - with_stripctrl(san, fname) - tell_user(stderr, "pscp: unable to open directory %s: %s", - san, 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; - with_stripctrl(san, fname) - tell_user(stderr, "pscp: reading directory %s: %s", - san, 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; - } - sgrowarrayn(ournames, namesize, nnames, names->nnames); - 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)) { - with_stripctrl(san, names->names[i].filename) - tell_user(stderr, "ignoring potentially dangerous " - "server-supplied filename '%s'", san); - } 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 = false; - 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; - strbuf_clear(act->buf); - put_asciz(act->buf, stripslashes(fname, false)); - act->name = act->buf->s; - act->size = 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 = true; - } else - act->settime = false; - } - return 0; - - } else { - /* - * It's a file. Return SCP_SINK_FILE. - */ - act->action = SCP_SINK_FILE; - strbuf_clear(act->buf); - put_asciz(act->buf, stripslashes(fname, false)); - act->name = act->buf->s; - if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) { - act->size = attrs.size; - } else - act->size = UINT64_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 = true; - } else - act->settime = false; - if (must_free_fname) - scp_sftp_currentname = fname; - else - scp_sftp_currentname = dupstr(fname); - return 0; - } - - } else { - bool done = false; - int action; - char ch; - - act->settime = false; - strbuf_clear(act->buf); - - while (!done) { - if (!ssh_scp_recv(&ch, 1)) - return 1; - if (ch == '\n') - bump("Protocol error: Unexpected newline"); - action = ch; - while (1) { - if (!ssh_scp_recv(&ch, 1)) - bump("Lost connection"); - if (ch == '\n') - break; - put_byte(act->buf, ch); - } - switch (action) { - case '\01': /* error */ - with_stripctrl(san, act->buf->s) - tell_user(stderr, "%s", san); - errs++; - continue; /* go round again */ - case '\02': /* fatal error */ - with_stripctrl(san, act->buf->s) - bump("%s", san); - case 'E': - backend_send(backend, "", 1); - act->action = SCP_SINK_ENDDIR; - return 0; - case 'T': - if (sscanf(act->buf->s, "%lu %*d %lu %*d", - &act->mtime, &act->atime) == 2) { - act->settime = true; - backend_send(backend, "", 1); - strbuf_clear(act->buf); - continue; /* go round again */ - } - bump("Protocol error: Illegal time format"); - case 'C': - case 'D': - act->action = (action == 'C' ? SCP_SINK_FILE : SCP_SINK_DIR); - if (act->action == SCP_SINK_DIR && !recursive) { - bump("security violation: remote host attempted to create " - "a subdirectory in a non-recursive copy!"); - } - break; - default: - bump("Protocol error: Expected control record"); - } - /* - * We will go round this loop only once, unless we hit - * `continue' above. - */ - done = true; - } - - /* - * If we get here, we must have seen SCP_SINK_FILE or - * SCP_SINK_DIR. - */ - { - int i; - if (sscanf(act->buf->s, "%lo %"SCNu64" %n", &act->permissions, - &act->size, &i) != 2) - bump("Protocol error: Illegal file descriptor format"); - act->name = act->buf->s + 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) { - with_stripctrl(san, scp_sftp_currentname) - tell_user(stderr, "pscp: unable to open %s: %s", - san, fxp_error()); - errs++; - return 1; - } - scp_sftp_fileoffset = 0; - scp_sftp_xfer = xfer_download_init(scp_sftp_filehandle, - scp_sftp_fileoffset); - sfree(scp_sftp_currentname); - return 0; - } else { - backend_send(backend, "", 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)) { - if (actuallen <= 0) { - tell_user(stderr, "pscp: end of file while reading"); - errs++; - sfree(vbuf); - return -1; - } - /* - * 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 += actuallen; - - return actuallen; - } else { - return ssh_scp_recv(data, len) ? len : 0; - } -} - -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 { - backend_send(backend, "", 1); - return response(); - } -} - -/* ---------------------------------------------------------------------- - * Send an error message to the other side and to the screen. - * Increment error counter. - */ -static PRINTF_LIKE(1, 2) 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"); - sfree(str); - scp_send_errmsg(str2); - abandon_stats(); - tell_user(stderr, "%s", str2); - va_end(ap); - sfree(str2); -} - -/* - * Execute the source part of the SCP protocol. - */ -static void source(const char *src) -{ - uint64_t size; - unsigned long mtime, atime; - long permissions; - const char *last; - RFile *f; - int attr; - uint64_t i; - uint64_t 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. - */ - const 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) { - tell_user(stderr, "Sending file %s, size=%"PRIu64, last, size); - } - if (scp_send_filename(last, size, permissions)) { - close_rfile(f); - return; - } - - stat_bytes = 0; - stat_starttime = time(NULL); - stat_lasttime = 0; - -#define PSCP_SEND_BLOCK 4096 - for (i = 0; i < size; i += PSCP_SEND_BLOCK) { - char transbuf[PSCP_SEND_BLOCK]; - int j, k = PSCP_SEND_BLOCK; - - if (i + k > size) - k = size - i; - if ((j = read_from_file(f, transbuf, k)) != k) { - bump("%s: Read error", src); - } - if (scp_send_filedata(transbuf, k)) - bump("%s: Network error occurred", src); - - if (statistics) { - stat_bytes += k; - if (time(NULL) != stat_lasttime || i + k == size) { - 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(const char *src) -{ - const 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; - - const char *opendir_err; - dir = open_directory(src, &opendir_err); - if (dir != NULL) { - char *filename; - while ((filename = read_filename(dir)) != NULL) { - char *foundfile = dupcat(src, "/", filename); - source(foundfile); - sfree(foundfile); - sfree(filename); - } - close_directory(dir); - } else { - tell_user(stderr, "Error opening directory %s: %s", src, opendir_err); - } - - (void) scp_send_enddir(); - - scp_restore_remotepath(save_target); -} - -/* - * Execute the sink part of the SCP protocol. - */ -static void sink(const char *targ, const char *src) -{ - char *destfname; - bool targisdir = false; - bool exists; - int attr; - WFile *f; - uint64_t received; - bool wrerror = false; - uint64_t stat_bytes; - time_t stat_starttime, stat_lasttime; - char *stat_name; - - attr = file_type(targ); - if (attr == FILE_TYPE_DIRECTORY) - targisdir = true; - - if (targetshouldbedirectory && !targisdir) - bump("%s: Not a directory", targ); - - scp_sink_init(); - - struct scp_sink_action act; - act.buf = strbuf_new(); - - while (1) { - - if (scp_get_sink_action(&act)) - goto out; - - if (act.action == SCP_SINK_ENDDIR) - goto out; - - 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, true); - if (striptarget != act.name) { - with_stripctrl(sanname, act.name) { - with_stripctrl(santarg, striptarget) { - tell_user(stderr, "warning: remote host sent a" - " compound pathname '%s'", sanname); - tell_user(stderr, " renaming local" - " file to '%s'", santarg); - } - } - } - - /* - * 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, true); - if (strcmp(striptarget, stripsrc) && - !using_sftp && !scp_unsafe_mode) { - with_stripctrl(san, striptarget) - tell_user(stderr, "warning: remote host tried to " - "write to a file called '%s'", san); - 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) { - with_stripctrl(san, destfname) - run_err("%s: Not a directory", san); - sfree(destfname); - continue; - } - if (!exists) { - if (!create_directory(destfname)) { - with_stripctrl(san, destfname) - run_err("%s: Cannot create directory", san); - 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) { - with_stripctrl(san, destfname) - run_err("%s: Cannot create file", san); - sfree(destfname); - continue; - } - - if (scp_accept_filexfer()) { - sfree(destfname); - close_wfile(f); - goto out; - } - - stat_bytes = 0; - stat_starttime = time(NULL); - stat_lasttime = 0; - stat_name = stripctrl_string( - string_scc, stripslashes(destfname, true)); - - received = 0; - while (received < act.size) { - char transbuf[32768]; - uint64_t blksize; - int read; - blksize = 32768; - if (blksize > act.size - received) - blksize = act.size - received; - read = scp_recv_filedata(transbuf, (int)blksize); - if (read <= 0) - bump("Lost connection"); - if (wrerror) { - received += read; - continue; - } - if (write_to_file(f, transbuf, read) != (int)read) { - wrerror = true; - /* 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"); - received += read; - continue; - } - if (statistics) { - stat_bytes += read; - if (time(NULL) > stat_lasttime || - received + read == act.size) { - stat_lasttime = time(NULL); - print_stats(stat_name, act.size, stat_bytes, - stat_starttime, stat_lasttime); - } - } - received += read; - } - if (act.settime) { - set_file_times(f, act.mtime, act.atime); - } - - close_wfile(f); - if (wrerror) { - with_stripctrl(san, destfname) - run_err("%s: Write error", san); - sfree(destfname); - continue; - } - (void) scp_finish_filerecv(); - sfree(stat_name); - sfree(destfname); - } - out: - strbuf_free(act.buf); -} - -/* - * We will copy local files to a remote server. - */ -static void toremote(int argc, char *argv[]) -{ - char *src, *wtarg, *host, *user; - const char *targ; - char *cmd; - int i, wc_type; - - uploading = true; - - wtarg = argv[argc - 1]; - - /* Separate host from filename */ - host = wtarg; - wtarg = colon(wtarg); - if (wtarg == NULL) - bump("wtarg == NULL in toremote()"); - *wtarg++ = '\0'; - /* Substitute "." for empty target */ - if (*wtarg == '\0') - targ = "."; - else - targ = wtarg; - - /* 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], true); - if (wc_type == WCTYPE_NONEXISTENT) - bump("%s: No such file or directory\n", argv[0]); - else if (wc_type == WCTYPE_WILDCARD) - targetshouldbedirectory = true; - } - - 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, true); - 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 *wsrc, *host, *user; - const char *src, *targ; - char *cmd; - - uploading = false; - - if (argc != 2) - bump("More than one remote source not supported"); - - wsrc = argv[0]; - targ = argv[1]; - - /* Separate host from filename */ - host = wsrc; - wsrc = colon(wsrc); - if (wsrc == NULL) - bump("Local to local copy not supported"); - *wsrc++ = '\0'; - /* Substitute "." for empty filename */ - if (*wsrc == '\0') - src = "."; - else - src = wsrc; - - /* 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 *wsrc, *host, *user; - const char *src; - char *cmd, *p; - const char *q; - char c; - - wsrc = argv[0]; - - /* Separate host from filename */ - host = wsrc; - wsrc = colon(wsrc); - if (wsrc == NULL) - bump("Local file listing not supported"); - *wsrc++ = '\0'; - /* Substitute "." for empty filename */ - if (*wsrc == '\0') - src = "."; - else - src = wsrc; - - /* 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 { - stdio_sink ss; - stdio_sink_init(&ss, stdout); - StripCtrlChars *scc = stripctrl_new( - BinarySink_UPCAST(&ss), false, L'\0'); - while (ssh_scp_recv(&c, 1)) - put_byte(scc, c); - stripctrl_free(scc); - } -} - -/* - * 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(" -pwfile file login with password read from specified file\n"); - printf(" -1 -2 force use of particular SSH protocol version\n"); - printf(" -ssh -ssh-connection\n"); - printf(" force use of particular SSH protocol variant\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(" -no-trivial-auth\n"); - printf(" disconnect if SSH authentication succeeds trivially\n"); - printf(" -hostkey keyid\n"); - printf(" manually specify a host key (may be repeated)\n"); - printf(" -batch disable all interactive prompts\n"); - printf(" -no-sanitise-stderr don't strip control chars from" - " standard error\n"); - printf(" -proxycmd command\n"); - printf(" use 'command' as local proxy\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"); - printf(" -logoverwrite\n"); - printf(" -logappend\n"); - printf(" control what happens when a log file already exists\n"); - cleanup_exit(1); -} - -void version(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("pscp: %s\n%s\n", ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); -} - -void cmdline_error(const 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 bool share_can_be_downstream = true; -const bool share_can_be_upstream = false; - -static stdio_sink stderr_ss; -static StripCtrlChars *stderr_scc; - -const unsigned cmdline_tooltype = TOOLTYPE_FILETRANSFER; - -/* - * 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; - bool sanitise_stderr = true; - - sk_init(); - - /* Load Default Settings before doing anything else. */ - conf = conf_new(); - do_defaults(NULL, conf); - - for (i = 1; i < argc; i++) { - int ret; - if (argv[i][0] != '-') - break; - ret = cmdline_process_param(argv[i], i+1 2) - targetshouldbedirectory = true; - - if (colon(argv[argc - 1]) != NULL) - toremote(argc, argv); - else - tolocal(argc, argv); - } - - if (backend && backend_connected(backend)) { - char ch; - backend_special(backend, SS_EOF, 0); - sent_eof = true; - ssh_scp_recv(&ch, 1); - } - random_save_seed(); - - cmdline_cleanup(); - if (backend) { - backend_free(backend); - backend = NULL; - } - sk_cleanup(); - return (errs == 0 ? 0 : 1); -} - -/* end */ diff --git a/psftp.c b/psftp.c deleted file mode 100644 index d8b5c400e..000000000 --- a/psftp.c +++ /dev/null @@ -1,2917 +0,0 @@ -/* - * psftp.c: (platform-independent) front end for PSFTP. - */ - -#include -#include -#include -#include -#include - -#include "putty.h" -#include "psftp.h" -#include "storage.h" -#include "ssh.h" -#include "ssh/sftp.h" - -/* - * 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); -static void do_sftp_cleanup(void); - -/* ---------------------------------------------------------------------- - * sftp client state. - */ - -static char *pwd, *homedir; -static LogContext *psftp_logctx = NULL; -static Backend *backend; -static Conf *conf; -static bool sent_eof = false; - -/* ------------------------------------------------------------ - * Seat vtable. - */ - -static size_t psftp_output(Seat *, SeatOutputType type, const void *, size_t); -static bool psftp_eof(Seat *); - -static const SeatVtable psftp_seat_vt = { - .output = psftp_output, - .eof = psftp_eof, - .sent = nullseat_sent, - .banner = nullseat_banner_to_stderr, - .get_userpass_input = filexfer_get_userpass_input, - .notify_session_started = nullseat_notify_session_started, - .notify_remote_exit = nullseat_notify_remote_exit, - .notify_remote_disconnect = nullseat_notify_remote_disconnect, - .connection_fatal = console_connection_fatal, - .update_specials_menu = nullseat_update_specials_menu, - .get_ttymode = nullseat_get_ttymode, - .set_busy_status = nullseat_set_busy_status, - .confirm_ssh_host_key = console_confirm_ssh_host_key, - .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, - .prompt_descriptions = console_prompt_descriptions, - .is_utf8 = nullseat_is_never_utf8, - .echoedit_update = nullseat_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = nullseat_get_window_pixel_size, - .stripctrl_new = console_stripctrl_new, - .set_trust_status = nullseat_set_trust_status, - .can_set_trust_status = nullseat_can_set_trust_status_yes, - .has_mixed_input_stream = nullseat_has_mixed_input_stream_no, - .verbose = cmdline_seat_verbose, - .interactive = nullseat_interactive_yes, - .get_cursor_position = nullseat_get_cursor_position, -}; -static Seat psftp_seat[1] = {{ &psftp_seat_vt }}; - -/* ---------------------------------------------------------------------- - * A nasty loop macro that lets me get an escape-sequence sanitised - * version of a string for display, and free it automatically - * afterwards. - */ -static StripCtrlChars *string_scc; -#define with_stripctrl(varname, input) \ - for (char *varname = stripctrl_string(string_scc, input); varname; \ - sfree(varname), varname = NULL) - -/* ---------------------------------------------------------------------- - * 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) { - seat_connection_fatal( - psftp_seat, "did not receive SFTP response packet from server"); - } - rreq = sftp_find_request(pktin); - if (rreq != req) { - seat_connection_fatal( - psftp_seat, - "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(const char *name) -{ - char *fullname, *canonname; - struct sftp_packet *pktin; - struct sftp_request *req; - - if (name[0] == '/') { - fullname = dupstr(name); - } else { - const char *slash; - if (pwd[strlen(pwd) - 1] == '/') - slash = ""; - else - slash = "/"; - fullname = dupcat(pwd, slash, name); - } - - 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, - (strendswith(canonname, "/") ? "" : "/"), - fullname + i + 1); - sfree(fullname); - sfree(canonname); - return returnname; - } -} - -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. - */ -bool sftp_get_file(char *fname, char *outfname, bool recurse, bool restart) -{ - struct fxp_handle *fh; - struct sftp_packet *pktin; - struct sftp_request *req; - struct fxp_xfer *xfer; - uint64_t offset; - WFile *file; - bool toret, 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) { - bool 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; - size_t 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)) { - with_stripctrl(san, outfname) - printf("%s: Cannot create directory\n", san); - return false; - } - - /* - * 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) { - with_stripctrl(san, fname) - printf("%s: unable to open directory: %s\n", - san, fxp_error()); - return false; - } - 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; - with_stripctrl(san, fname) - printf("%s: reading directory: %s\n", - san, fxp_error()); - - req = fxp_close_send(dirhandle); - pktin = sftp_wait_for_reply(req); - fxp_close_recv(pktin, req); - - sfree(ournames); - return false; - } - if (names->nnames == 0) { - fxp_free_names(names); - break; - } - sgrowarrayn(ournames, namesize, nnames, names->nnames); - 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)) { - with_stripctrl(san, names->names[i].filename) - printf("ignoring potentially dangerous server-" - "supplied filename '%s'\n", san); - } 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; - bool nonexistent; - nextoutfname = dir_file_cat(outfname, - ournames[i]->filename); - nonexistent = (file_type(nextoutfname) == - FILE_TYPE_NONEXISTENT); - sfree(nextoutfname); - if (nonexistent) - 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; - bool retd; - - nextfname = dupcat(fname, "/", ournames[i]->filename); - nextoutfname = dir_file_cat(outfname, ournames[i]->filename); - retd = sftp_get_file( - nextfname, nextoutfname, recurse, restart); - restart = false; /* after first partial file, do full */ - sfree(nextoutfname); - sfree(nextfname); - if (!retd) { - for (i = 0; i < nnames; i++) { - fxp_free_name(ournames[i]); - } - sfree(ournames); - return false; - } - } - - /* - * Done this recursion level. Free everything. - */ - for (i = 0; i < nnames; i++) { - fxp_free_name(ournames[i]); - } - sfree(ournames); - - return true; - } - } - - 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) { - with_stripctrl(san, fname) - printf("%s: open for read: %s\n", san, fxp_error()); - return false; - } - - if (restart) { - file = open_existing_wfile(outfname, NULL); - } else { - file = open_new_file(outfname, GET_PERMISSIONS(attrs, -1)); - } - - if (!file) { - with_stripctrl(san, outfname) - printf("local: unable to open %s\n", san); - - req = fxp_close_send(fh); - pktin = sftp_wait_for_reply(req); - fxp_close_recv(pktin, req); - - return false; - } - - if (restart) { - if (seek_file(file, 0, FROM_END) == -1) { - close_wfile(file); - with_stripctrl(san, outfname) - printf("reget: cannot restart %s - file too large\n", san); - req = fxp_close_send(fh); - pktin = sftp_wait_for_reply(req); - fxp_close_recv(pktin, req); - - return false; - } - - offset = get_file_posn(file); - printf("reget: restarting at file position %"PRIu64"\n", offset); - } else { - offset = 0; - } - - with_stripctrl(san, fname) { - with_stripctrl(sano, outfname) - printf("remote:%s => local:%s\n", san, sano); - } - - /* - * FIXME: we can use FXP_FSTAT here to get the file size, and - * thus put up a progress bar. - */ - toret = true; - xfer = xfer_download_init(fh, offset); - while (!xfer_done(xfer)) { - void *vbuf; - int retd, len; - int wpos, wlen; - - xfer_download_queue(xfer); - pktin = sftp_recv(); - retd = xfer_download_gotpkt(xfer, pktin); - if (retd <= 0) { - if (!shown_err) { - printf("error while reading: %s\n", fxp_error()); - shown_err = true; - } - if (retd == INT_MIN) /* pktin not even freed */ - sfree(pktin); - toret = false; - } - - 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"); - toret = false; - xfer_set_error(xfer); - break; - } - wpos += wlen; - } - if (wpos < len) { /* we had an error */ - toret = false; - 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 toret; -} - -bool sftp_put_file(char *fname, char *outfname, bool recurse, bool restart) -{ - struct fxp_handle *fh; - struct fxp_xfer *xfer; - struct sftp_packet *pktin; - struct sftp_request *req; - uint64_t offset; - RFile *file; - bool err = false, 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) { - bool result; - size_t nnames, namesize; - char *name, **ournames; - const char *opendir_err; - DirHandle *dh; - size_t 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, NULL); - pktin = sftp_wait_for_reply(req); - result = fxp_mkdir_recv(pktin, req); - - if (!result) { - printf("%s: create directory: %s\n", - outfname, fxp_error()); - return false; - } - } - - /* - * Now get the list of filenames in the local directory. - */ - nnames = namesize = 0; - ournames = NULL; - - dh = open_directory(fname, &opendir_err); - if (!dh) { - printf("%s: unable to open directory: %s\n", fname, opendir_err); - return false; - } - while ((name = read_filename(dh)) != NULL) { - sgrowarray(ournames, namesize, nnames); - 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]); - 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; - bool retd; - - nextfname = dir_file_cat(fname, ournames[i]); - nextoutfname = dupcat(outfname, "/", ournames[i]); - retd = sftp_put_file(nextfname, nextoutfname, recurse, restart); - restart = false; /* after first partial file, do full */ - sfree(nextoutfname); - sfree(nextfname); - if (!retd) { - for (size_t i = 0; i < nnames; i++) { - sfree(ournames[i]); - } - sfree(ournames); - return false; - } - } - - /* - * Done this recursion level. Free everything. - */ - for (size_t i = 0; i < nnames; i++) { - sfree(ournames[i]); - } - sfree(ournames); - - return true; - } - - file = open_existing_file(fname, NULL, NULL, NULL, &permissions); - if (!file) { - printf("local: unable to open %s\n", fname); - return false; - } - 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 false; - } - - if (restart) { - struct fxp_attrs attrs; - bool retd; - - req = fxp_fstat_send(fh); - pktin = sftp_wait_for_reply(req); - retd = fxp_fstat_recv(pktin, req, &attrs); - - if (!retd) { - printf("read size of %s: %s\n", outfname, fxp_error()); - err = true; - goto cleanup; - } - if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { - printf("read size of %s: size was not given\n", outfname); - err = true; - goto cleanup; - } - offset = attrs.size; - printf("reput: restarting at file position %"PRIu64"\n", offset); - - if (seek_file((WFile *)file, offset, FROM_START) != 0) - seek_file((WFile *)file, 0, FROM_END); /* *shrug* */ - } else { - offset = 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. - */ - xfer = xfer_upload_init(fh, offset); - eof = false; - 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 = true; - } else if (len == 0) { - eof = true; - } else { - xfer_upload_data(xfer, buffer, len); - } - } - - if (toplevel_callback_pending() && !err && !eof) { - /* If we have pending callbacks, they might make - * xfer_upload_ready start to return true. So we should - * run them and then re-check xfer_upload_ready, before - * we go as far as waiting for an entire packet to - * arrive. */ - run_toplevel_callbacks(); - continue; - } - - 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 = true; - } - } - } - } - - xfer_cleanup(xfer); - - cleanup: - req = fxp_close_send(fh); - pktin = sftp_wait_for_reply(req); - if (!fxp_close_recv(pktin, req)) { - if (!err) { - printf("error while closing: %s", fxp_error()); - err = true; - } - } - - close_rfile(file); - - return !err; -} - -/* ---------------------------------------------------------------------- - * 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; - bool 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, false); - - 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) { - with_stripctrl(san, swcm->prefix) - printf("%s: reading directory: %s\n", - san, 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)) { - with_stripctrl(san, name->filename) - printf("ignoring potentially dangerous server-" - "supplied filename '%s'\n", san); - 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). - */ -bool wildcard_iterate(char *filename, bool (*func)(void *, char *), void *ctx) -{ - char *unwcfname, *newname, *cname; - bool is_wc, toret; - - unwcfname = snewn(strlen(filename)+1, char); - is_wc = !wc_unescape(unwcfname, filename); - - if (is_wc) { - SftpWildcardMatcher *swcm = sftp_begin_wildcard_matching(filename); - bool matched = false; - sfree(unwcfname); - - if (!swcm) - return false; - - toret = true; - - while ( (newname = sftp_wildcard_get_filename(swcm)) != NULL ) { - cname = canonify(newname); - sfree(newname); - matched = true; - if (!func(ctx, cname)) - toret = false; - 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); - toret = func(ctx, cname); - sfree(cname); - sfree(unwcfname); - } - - return toret; -} - -/* - * Handy helper function. - */ -bool is_wildcard(char *name) -{ - char *unwcfname = snewn(strlen(name)+1, char); - bool is_wc = !wc_unescape(unwcfname, name); - sfree(unwcfname); - return is_wc; -} - -/* ---------------------------------------------------------------------- - * Actual sftp commands. - */ -struct sftp_command { - char **words; - size_t 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 (!backend) { - not_connected(); - return 0; - } - - if (backend_connected(backend)) { - char ch; - backend_special(backend, SS_EOF, 0); - sent_eof = true; - sftp_recvdata(&ch, 1); - } - do_sftp_cleanup(); - - return 0; -} - -void list_directory_from_sftp_warn_unsorted(void) -{ - printf("Directory is too large to sort; writing file names unsorted\n"); -} - -void list_directory_from_sftp_print(struct fxp_name *name) -{ - with_stripctrl(san, name->longname) - printf("%s\n", san); -} - -/* - * 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; - const char *dir; - char *cdir, *unwcdir, *wildcard; - struct sftp_packet *pktin; - struct sftp_request *req; - - if (!backend) { - 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; - bool check; - - sfree(unwcdir); - wildcard = stripslashes(dir, false); - 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); - - with_stripctrl(san, cdir) - printf("Listing directory %s\n", san); - - 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()); - sfree(cdir); - sfree(unwcdir); - return 0; - } else { - struct list_directory_from_sftp_ctx *ctx = - list_directory_from_sftp_new(); - - 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; - } - - for (size_t i = 0; i < names->nnames; i++) - if (!wildcard || wc_match(wildcard, names->names[i].filename)) - list_directory_from_sftp_feed(ctx, &names->names[i]); - - fxp_free_names(names); - } - - req = fxp_close_send(dirh); - pktin = sftp_wait_for_reply(req); - fxp_close_recv(pktin, req); - - list_directory_from_sftp_finish(ctx); - list_directory_from_sftp_free(ctx); - } - - 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 (!backend) { - not_connected(); - return 0; - } - - if (cmd->nwords < 2) - dir = dupstr(homedir); - else { - dir = canonify(cmd->words[1]); - } - - req = fxp_opendir_send(dir); - pktin = sftp_wait_for_reply(req); - dirh = fxp_opendir_recv(pktin, req); - - if (!dirh) { - with_stripctrl(san, dir) - printf("Directory %s: %s\n", san, 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; - with_stripctrl(san, pwd) - printf("Remote directory is now %s\n", san); - - return 1; -} - -/* - * Print current directory. Easy as pie. - */ -int sftp_cmd_pwd(struct sftp_command *cmd) -{ - if (!backend) { - not_connected(); - return 0; - } - - with_stripctrl(san, pwd) - printf("Remote directory is %s\n", san); - 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, bool restart, bool multiple) -{ - char *fname, *unwcfname, *origfname, *origwfname, *outfname; - int i, toret; - bool recurse = false; - - if (!backend) { - 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; - } - - toret = 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 (!multiple && i < cmd->nwords) - outfname = cmd->words[i++]; - else - outfname = stripslashes(origwfname, false); - - toret = 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 (!toret) - return toret; - - } while (multiple && i < cmd->nwords); - - return toret; -} -int sftp_cmd_get(struct sftp_command *cmd) -{ - return sftp_general_get(cmd, false, false); -} -int sftp_cmd_mget(struct sftp_command *cmd) -{ - return sftp_general_get(cmd, false, true); -} -int sftp_cmd_reget(struct sftp_command *cmd) -{ - return sftp_general_get(cmd, true, false); -} - -/* - * 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, bool restart, bool multiple) -{ - char *fname, *wfname, *origoutfname, *outfname; - int i; - int toret; - bool recurse = false; - - if (!backend) { - 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; - } - - toret = 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, true); - - outfname = canonify(origoutfname); - toret = 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 (!toret) - return toret; - - } while (multiple && i < cmd->nwords); - - return toret; -} -int sftp_cmd_put(struct sftp_command *cmd) -{ - return sftp_general_put(cmd, false, false); -} -int sftp_cmd_mput(struct sftp_command *cmd) -{ - return sftp_general_put(cmd, false, true); -} -int sftp_cmd_reput(struct sftp_command *cmd) -{ - return sftp_general_put(cmd, true, false); -} - -int sftp_cmd_mkdir(struct sftp_command *cmd) -{ - char *dir; - struct sftp_packet *pktin; - struct sftp_request *req; - bool result; - int i, ret; - - if (!backend) { - 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]); - - req = fxp_mkdir_send(dir, NULL); - pktin = sftp_wait_for_reply(req); - result = fxp_mkdir_recv(pktin, req); - - if (!result) { - with_stripctrl(san, dir) - printf("mkdir %s: %s\n", san, fxp_error()); - ret = 0; - } else - with_stripctrl(san, dir) - printf("mkdir %s: OK\n", san); - - sfree(dir); - } - - return ret; -} - -static bool sftp_action_rmdir(void *vctx, char *dir) -{ - struct sftp_packet *pktin; - struct sftp_request *req; - bool 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 false; - } - - printf("rmdir %s: OK\n", dir); - - return true; -} - -int sftp_cmd_rmdir(struct sftp_command *cmd) -{ - int i, ret; - - if (!backend) { - 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 bool sftp_action_rm(void *vctx, char *fname) -{ - struct sftp_packet *pktin; - struct sftp_request *req; - bool 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 false; - } - - printf("rm %s: OK\n", fname); - - return true; -} - -int sftp_cmd_rm(struct sftp_command *cmd) -{ - int i, ret; - - if (!backend) { - 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 bool check_is_dir(char *dstfname) -{ - struct sftp_packet *pktin; - struct sftp_request *req; - struct fxp_attrs attrs; - bool 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; - bool dest_is_dir; -}; - -static bool 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; - bool toret, 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); - newcanon = canonify(newname); - 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) { - with_stripctrl(san, finalfname) - printf("mv %s %s: %s\n", srcfname, san, error); - toret = false; - } else { - with_stripctrl(san, finalfname) - printf("%s -> %s\n", srcfname, san); - toret = true; - } - - sfree(newcanon); - return toret; -} - -int sftp_cmd_mv(struct sftp_command *cmd) -{ - struct sftp_context_mv ctx[1]; - int i, ret; - - if (!backend) { - 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 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 bool sftp_action_chmod(void *vctx, char *fname) -{ - struct fxp_attrs attrs; - struct sftp_packet *pktin; - struct sftp_request *req; - bool 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 false; - } - - 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 true; /* 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 false; - } - - printf("%s: %04o -> %04o\n", fname, oldperms, newperms); - - return true; -} - -int sftp_cmd_chmod(struct sftp_command *cmd) -{ - char *mode; - int i, ret; - struct sftp_context_chmod ctx[1]; - - if (!backend) { - 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 (backend) { - 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)) { - backend = 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 { - const 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. - */ - bool listed; /* do we list this in primary help? */ - const char *shorthelp; - const 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(const char *name) -{ - int i, j, k, cmp; - - i = -1; - j = lenof(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 < lenof(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 < lenof(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; - bool 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> ", !backend); - } - - 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 = 2; - sgrowarrayn(cmd->words, cmd->wordssize, cmd->nwords, 0); - 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 = false; - 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'; - sgrowarray(cmd->words, cmd->wordssize, cmd->nwords); - 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 void sftp_cmd_free(struct sftp_command *cmd) -{ - if (cmd->words) { - for (size_t i = 0; i < cmd->nwords; i++) - sfree(cmd->words[i]); - sfree(cmd->words); - } - sfree(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 { - with_stripctrl(san, homedir) - printf("Remote working directory is %s\n", san); - } - pwd = dupstr(homedir); - return 0; -} - -static void do_sftp_cleanup(void) -{ - char ch; - if (backend) { - backend_special(backend, SS_EOF, 0); - sent_eof = true; - sftp_recvdata(&ch, 1); - backend_free(backend); - sftp_cleanup_request(); - backend = 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); - sftp_cmd_free(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); - sftp_cmd_free(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 bool verbose = false; - -void ldisc_echoedit_update(Ldisc *ldisc) { } -void ldisc_check_sendok(Ldisc *ldisc) { } - -/* - * 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 psftp_output() function to catch the data that comes back. We - * do this until we have enough data. - */ -static bufchain received_data; -static BinarySink *stderr_bs; -static size_t psftp_output( - Seat *seat, SeatOutputType type, const void *data, size_t len) -{ - /* - * Non-stdout data (both stderr and SSH auth banners) is just - * spouted to local stderr (optionally via a sanitiser) and - * otherwise ignored. - */ - if (type != SEAT_OUTPUT_STDOUT) { - put_data(stderr_bs, data, len); - return 0; - } - - bufchain_add(&received_data, data, len); - return 0; -} - -static bool psftp_eof(Seat *seat) -{ - /* - * 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) { - seat_connection_fatal( - psftp_seat, "Received unexpected end-of-file from SFTP server"); - } - return false; -} - -bool sftp_recvdata(char *buf, size_t len) -{ - while (len > 0) { - while (bufchain_size(&received_data) == 0) { - if (backend_exitcode(backend) >= 0 || - ssh_sftp_loop_iteration() < 0) - return false; /* doom */ - } - - size_t got = bufchain_fetch_consume_up_to(&received_data, buf, len); - buf += got; - len -= got; - } - - return true; -} -bool sftp_senddata(const char *buf, size_t len) -{ - backend_send(backend, buf, len); - return true; -} -size_t sftp_sendbuffer(void) -{ - return backend_sendbuffer(backend); -} - -/* - * 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(" -pwfile file login with password read from specified file\n"); - printf(" -1 -2 force use of particular SSH protocol version\n"); - printf(" -ssh -ssh-connection\n"); - printf(" force use of particular SSH protocol variant\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(" -no-trivial-auth\n"); - printf(" disconnect if SSH authentication succeeds trivially\n"); - printf(" -hostkey keyid\n"); - printf(" manually specify a host key (may be repeated)\n"); - printf(" -batch disable all interactive prompts\n"); - printf(" -no-sanitise-stderr don't strip control chars from" - " standard error\n"); - printf(" -proxycmd command\n"); - printf(" use 'command' as local proxy\n"); - printf(" -sshlog file\n"); - printf(" -sshrawlog file\n"); - printf(" log protocol details to a file\n"); - printf(" -logoverwrite\n"); - printf(" -logappend\n"); - printf(" control what happens when a log file already exists\n"); - cleanup_exit(1); -} - -static void version(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("psftp: %s\n%s\n", ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); -} - -/* - * Connect to a host. - */ -static int psftp_connect(char *userhost, char *user, int portnumber) -{ - char *host, *realhost; - const char *err; - - /* 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 (!cmdline_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 protocol to SSH if the user has somehow contrived to - * select one we don't support (e.g. by loading an inappropriate - * saved session). In that situation we assume the port number is - * useless too.) - */ - if (!backend_vt_from_proto(conf_get_int(conf, CONF_protocol))) { - 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_bool(conf, CONF_x11_forward, false); - conf_set_bool(conf, CONF_agentfwd, false); - conf_set_bool(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_bool(conf, CONF_ssh_subsys, true); - conf_set_bool(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_bool(conf, CONF_ssh_subsys2, false); - - psftp_logctx = log_init(console_cli_logpolicy, conf); - - platform_psftp_pre_conn_setup(console_cli_logpolicy); - - err = backend_init(backend_vt_from_proto( - conf_get_int(conf, CONF_protocol)), - psftp_seat, &backend, psftp_logctx, conf, - conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), - &realhost, 0, - conf_get_bool(conf, CONF_tcp_keepalives)); - if (err != NULL) { - fprintf(stderr, "ssh_init: %s\n", err); - return 1; - } - while (!backend_sendok(backend)) { - if (backend_exitcode(backend) >= 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(const 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 bool share_can_be_downstream = true; -const bool share_can_be_upstream = false; - -static stdio_sink stderr_ss; -static StripCtrlChars *stderr_scc; - -const unsigned cmdline_tooltype = TOOLTYPE_FILETRANSFER; - -/* - * Main program. Parse arguments etc. - */ -int psftp_main(int argc, char *argv[]) -{ - int i, toret; - int portnumber = 0; - char *userhost, *user; - int mode = 0; - int modeflags = 0; - bool sanitise_stderr = true; - char *batchfile = NULL; - - sk_init(); - - userhost = user = NULL; - - /* Load Default Settings before doing anything else. */ - conf = conf_new(); - do_defaults(NULL, conf); - - for (i = 1; i < argc; i++) { - int retd; - if (argv[i][0] != '-') { - if (userhost) - usage(); - else - userhost = dupstr(argv[i]); - continue; - } - retd = cmdline_process_param( - argv[i], i+1 < argc ? argv[i+1] : NULL, 1, conf); - if (retd == -2) { - cmdline_error("option \"%s\" requires an argument", argv[i]); - } else if (retd == 2) { - i++; /* skip next argument */ - } else if (retd == 1) { - /* We have our own verbosity in addition to `flags'. */ - if (cmdline_verbose()) - verbose = true; - } else if (strcmp(argv[i], "-h") == 0 || - strcmp(argv[i], "-?") == 0 || - strcmp(argv[i], "--help") == 0) { - usage(); - } else if (strcmp(argv[i], "-pgpfp") == 0) { - pgp_fingerprints(); - return 1; - } else if (strcmp(argv[i], "-V") == 0 || - strcmp(argv[i], "--version") == 0) { - version(); - } else if (strcmp(argv[i], "-batch") == 0) { - console_batch_mode = true; - } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) { - mode = 1; - batchfile = argv[++i]; - } else if (strcmp(argv[i], "-bc") == 0) { - modeflags = modeflags | 1; - } else if (strcmp(argv[i], "-be") == 0) { - modeflags = modeflags | 2; - } else if (strcmp(argv[i], "-sanitise-stderr") == 0) { - sanitise_stderr = true; - } else if (strcmp(argv[i], "-no-sanitise-stderr") == 0) { - sanitise_stderr = false; - } else if (strcmp(argv[i], "--") == 0) { - i++; - break; - } else { - cmdline_error("unknown option \"%s\"", argv[i]); - } - } - argc -= i; - argv += i; - backend = NULL; - - stdio_sink_init(&stderr_ss, stderr); - stderr_bs = BinarySink_UPCAST(&stderr_ss); - if (sanitise_stderr) { - stderr_scc = stripctrl_new(stderr_bs, false, L'\0'); - stderr_bs = BinarySink_UPCAST(stderr_scc); - } - - string_scc = stripctrl_new(NULL, false, L'\0'); - - /* - * If the loaded session provides a hostname, and a hostname has not - * otherwise been specified, pop it in `userhost' so that - * `psftp -load sessname' is sufficient to start a session. - */ - if (!userhost && conf_get_str(conf, CONF_host)[0] != '\0') { - userhost = dupstr(conf_get_str(conf, CONF_host)); - } - - /* - * If a user@host string has already been provided, connect to - * it now. - */ - if (userhost) { - int retd; - retd = psftp_connect(userhost, user, portnumber); - sfree(userhost); - if (retd) - return 1; - if (do_sftp_init()) - return 1; - } else { - printf("psftp: no hostname specified; use \"open host.name\"" - " to connect\n"); - } - - toret = do_sftp(mode, modeflags, batchfile); - - if (backend && backend_connected(backend)) { - char ch; - backend_special(backend, SS_EOF, 0); - sent_eof = true; - sftp_recvdata(&ch, 1); - } - do_sftp_cleanup(); - random_save_seed(); - cmdline_cleanup(); - sk_cleanup(); - - stripctrl_free(string_scc); - stripctrl_free(stderr_scc); - - if (psftp_logctx) - log_free(psftp_logctx); - - return toret; -} diff --git a/psftp.h b/psftp.h deleted file mode 100644 index 79327e7aa..000000000 --- a/psftp.h +++ /dev/null @@ -1,227 +0,0 @@ -/* - * psftp.h: interface between psftp.c / pscp.c, psftpcommon.c, and - * each platform-specific SFTP module. - */ - -#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(const char *prompt, bool backend_required); - -/* - * Platform-specific function called when we're about to make a - * network connection. - */ -void platform_psftp_pre_conn_setup(LogPolicy *lp); - -/* - * 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(const 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(const 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(const char *name, uint64_t *size, - unsigned long *mtime, unsigned long *atime, - long *perms); -WFile *open_existing_wfile(const char *name, uint64_t *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(const 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_t offset, int whence); -/* Get file position */ -uint64_t 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(const char *name); - -/* - * Read all the file names out of a directory. - */ -typedef struct DirHandle DirHandle; -DirHandle *open_directory(const char *name, const char **errmsg); -/* 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(const char *name, bool cmdline); - -/* - * Actually return matching file names for a local wildcard. - */ -typedef struct WildcardMatcher WildcardMatcher; -WildcardMatcher *begin_wildcard_matching(const 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. - */ -bool vet_filename(const char *name); - -/* - * Create a directory. Returns true on success, false on error. - */ -bool create_directory(const char *name); - -/* - * Concatenate a directory name and a file name. The way this is - * done will depend on the OS. - */ -char *dir_file_cat(const char *dir, const char *file); - -/* - * Return a pointer to the portion of str that comes after the last - * path component separator. - * - * If 'local' is false, path component separators are taken to just be - * '/', on the assumption that we're discussing the path syntax on the - * server. But if 'local' is true, the separators are whatever the - * local OS will treat that way - so that includes '\' and ':' on - * Windows. - * - * This function has the annoying strstr() property of taking a const - * char * and returning a char *. You should treat it as if it was a - * pair of overloaded functions, one mapping mutable->mutable and the - * other const->const :-( - */ -char *stripslashes(const char *str, bool local); - -/* ---------------------------------------------------------------------- - * In psftpcommon.c - */ - -/* - * qsort comparison routine for fxp_name structures. Sorts by real - * file name. - */ -int sftp_name_compare(const void *av, const void *bv); - -/* - * Shared code for outputting a directory listing in response to a - * stream of name structures from FXP_READDIR operations. Used by - * psftp's ls command and pscp -ls. - */ -struct list_directory_from_sftp_ctx; -struct fxp_name; /* in sftp.h */ -struct list_directory_from_sftp_ctx *list_directory_from_sftp_new(void); -void list_directory_from_sftp_feed(struct list_directory_from_sftp_ctx *ctx, - struct fxp_name *name); -void list_directory_from_sftp_finish(struct list_directory_from_sftp_ctx *ctx); -void list_directory_from_sftp_free(struct list_directory_from_sftp_ctx *ctx); -/* Callbacks provided by the tool front end */ -void list_directory_from_sftp_warn_unsorted(void); -void list_directory_from_sftp_print(struct fxp_name *name); - -#endif /* PUTTY_PSFTP_H */ diff --git a/psftpcommon.c b/psftpcommon.c deleted file mode 100644 index e6c8e2d87..000000000 --- a/psftpcommon.c +++ /dev/null @@ -1,102 +0,0 @@ -/* - * psftpcommon.c: front-end functionality shared between both file - * transfer tools across platforms. (As opposed to sftpcommon.c, which - * has *protocol*-level common code.) - */ - -#include -#include - -#include "putty.h" -#include "ssh/sftp.h" -#include "psftp.h" - -#define MAX_NAMES_MEMORY ((size_t)8 << 20) - -/* - * qsort comparison routine for fxp_name structures. Sorts by real - * file name. - */ -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); -} - -struct list_directory_from_sftp_ctx { - size_t nnames, namesize, total_memory; - struct fxp_name **names; - bool sorting; -}; - -struct list_directory_from_sftp_ctx *list_directory_from_sftp_new(void) -{ - struct list_directory_from_sftp_ctx *ctx = - snew(struct list_directory_from_sftp_ctx); - memset(ctx, 0, sizeof(*ctx)); - ctx->sorting = true; - return ctx; -} - -void list_directory_from_sftp_free(struct list_directory_from_sftp_ctx *ctx) -{ - for (size_t i = 0; i < ctx->nnames; i++) - fxp_free_name(ctx->names[i]); - sfree(ctx->names); - sfree(ctx); -} - -void list_directory_from_sftp_feed(struct list_directory_from_sftp_ctx *ctx, - struct fxp_name *name) -{ - if (ctx->sorting) { - /* - * Accumulate these filenames into an array that we'll sort - - * unless the array gets _really_ big, in which case, to avoid - * consuming all the client's memory, we fall back to - * outputting the directory listing unsorted. - */ - size_t this_name_memory = - sizeof(*ctx->names) + sizeof(**ctx->names) + - strlen(name->filename) + - strlen(name->longname); - - if (MAX_NAMES_MEMORY - ctx->total_memory < this_name_memory) { - list_directory_from_sftp_warn_unsorted(); - - /* Output all the previously stored names. */ - for (size_t i = 0; i < ctx->nnames; i++) { - list_directory_from_sftp_print(ctx->names[i]); - fxp_free_name(ctx->names[i]); - } - - /* Don't store further names in that array. */ - sfree(ctx->names); - ctx->names = NULL; - ctx->nnames = 0; - ctx->namesize = 0; - ctx->sorting = false; - - /* And don't forget to output the name passed in this - * actual function call. */ - list_directory_from_sftp_print(name); - } else { - sgrowarray(ctx->names, ctx->namesize, ctx->nnames); - ctx->names[ctx->nnames++] = fxp_dup_name(name); - ctx->total_memory += this_name_memory; - } - } else { - list_directory_from_sftp_print(name); - } -} - -void list_directory_from_sftp_finish(struct list_directory_from_sftp_ctx *ctx) -{ - if (ctx->nnames > 0) { - assert(ctx->sorting); - qsort(ctx->names, ctx->nnames, sizeof(*ctx->names), sftp_name_compare); - for (size_t i = 0; i < ctx->nnames; i++) - list_directory_from_sftp_print(ctx->names[i]); - } -} diff --git a/psocks.c b/psocks.c deleted file mode 100644 index eb7c8f019..000000000 --- a/psocks.c +++ /dev/null @@ -1,569 +0,0 @@ -/* - * Platform-independent parts of a standalone SOCKS server program - * based on the PuTTY SOCKS code. - */ - -#include -#include - -#include "putty.h" -#include "storage.h" -#include "misc.h" -#include "ssh.h" -#include "ssh/channel.h" -#include "psocks.h" - -/* - * Possible later TODOs: - * - * - verbosity setting for log messages - * - * - could import proxy.c and use name_lookup rather than - * sk_namelookup, to allow forwarding via some other proxy type - */ - -#define BUFLIMIT 16384 - -#define LOGBITS(X) \ - X(CONNSTATUS) \ - X(DIALOGUE) \ - /* end of list */ - -#define BITINDEX_ENUM(x) LOG_##x##_bitindex, -enum { LOGBITS(BITINDEX_ENUM) }; -#define BITFLAG_ENUM(x) LOG_##x = 1 << LOG_##x##_bitindex, -enum { LOGBITS(BITFLAG_ENUM) }; - -typedef struct psocks_connection psocks_connection; - -typedef enum RecordDestination { - REC_NONE, REC_FILE, REC_PIPE -} RecordDestination; - -struct psocks_state { - const PsocksPlatform *platform; - int listen_port; - bool acceptall; - PortFwdManager *portfwdmgr; - uint64_t next_conn_index; - FILE *logging_fp; - unsigned log_flags; - RecordDestination rec_dest; - char *rec_cmd; - strbuf *subcmd; - - ConnectionLayer cl; -}; - -struct psocks_connection { - psocks_state *ps; - Channel *chan; - char *host, *realhost; - int port; - SockAddr *addr; - Socket *socket; - bool connecting, eof_pfmgr_to_socket, eof_socket_to_pfmgr; - uint64_t index; - PsocksDataSink *rec_sink; - - Plug plug; - SshChannel sc; -}; - -static SshChannel *psocks_lportfwd_open( - ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan); - -static const ConnectionLayerVtable psocks_clvt = { - .lportfwd_open = psocks_lportfwd_open, - /* everything else is NULL */ -}; - -static size_t psocks_sc_write(SshChannel *sc, bool is_stderr, const void *, - size_t); -static void psocks_sc_write_eof(SshChannel *sc); -static void psocks_sc_initiate_close(SshChannel *sc, const char *err); -static void psocks_sc_unthrottle(SshChannel *sc, size_t bufsize); - -static const SshChannelVtable psocks_scvt = { - .write = psocks_sc_write, - .write_eof = psocks_sc_write_eof, - .initiate_close = psocks_sc_initiate_close, - .unthrottle = psocks_sc_unthrottle, - /* all the rest are NULL */ -}; - -static void psocks_plug_log(Plug *p, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code); -static void psocks_plug_closing(Plug *p, PlugCloseType, const char *error_msg); -static void psocks_plug_receive(Plug *p, int urgent, - const char *data, size_t len); -static void psocks_plug_sent(Plug *p, size_t bufsize); - -static const PlugVtable psocks_plugvt = { - .log = psocks_plug_log, - .closing = psocks_plug_closing, - .receive = psocks_plug_receive, - .sent = psocks_plug_sent, -}; - -static void psocks_conn_log(psocks_connection *conn, const char *fmt, ...) -{ - if (!conn->ps->logging_fp) - return; - - va_list ap; - va_start(ap, fmt); - char *msg = dupvprintf(fmt, ap); - va_end(ap); - fprintf(conn->ps->logging_fp, "c#%"PRIu64": %s\n", conn->index, msg); - sfree(msg); - fflush(conn->ps->logging_fp); -} - -static void psocks_conn_log_data(psocks_connection *conn, PsocksDirection dir, - const void *vdata, size_t len) -{ - if ((conn->ps->log_flags & LOG_DIALOGUE) && conn->ps->logging_fp) { - const char *data = vdata; - while (len > 0) { - const char *nl = memchr(data, '\n', len); - size_t thislen = nl ? (nl+1) - data : len; - const char *thisdata = data; - data += thislen; - len -= thislen; - - static const char *const direction_names[2] = { - [UP] = "send", [DN] = "recv" }; - - fprintf(conn->ps->logging_fp, "c#%"PRIu64": %s \"", conn->index, - direction_names[dir]); - write_c_string_literal(conn->ps->logging_fp, - make_ptrlen(thisdata, thislen)); - fprintf(conn->ps->logging_fp, "\"\n"); - } - - fflush(conn->ps->logging_fp); - } - - if (conn->rec_sink) - put_data(conn->rec_sink->s[dir], vdata, len); -} - -static void psocks_connection_establish(void *vctx); - -static SshChannel *psocks_lportfwd_open( - ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan) -{ - psocks_state *ps = container_of(cl, psocks_state, cl); - psocks_connection *conn = snew(psocks_connection); - memset(conn, 0, sizeof(*conn)); - conn->ps = ps; - conn->sc.vt = &psocks_scvt; - conn->plug.vt = &psocks_plugvt; - conn->chan = chan; - conn->host = dupstr(hostname); - conn->port = port; - conn->index = ps->next_conn_index++; - if (conn->ps->log_flags & LOG_CONNSTATUS) - psocks_conn_log(conn, "request from %s for %s port %d", - pi->log_text, hostname, port); - switch (conn->ps->rec_dest) { - case REC_FILE: - { - char *fnames[2]; - FILE *fp[2]; - bool ok = true; - - static const char *const direction_names[2] = { - [UP] = "sockout", [DN] = "sockin" }; - - for (size_t i = 0; i < 2; i++) { - fnames[i] = dupprintf("%s.%"PRIu64, direction_names[i], - conn->index); - fp[i] = fopen(fnames[i], "wb"); - if (!fp[i]) { - psocks_conn_log(conn, "cannot log this connection: " - "creating file '%s': %s", - fnames[i], strerror(errno)); - ok = false; - } - } - if (ok) { - if (conn->ps->log_flags & LOG_CONNSTATUS) - psocks_conn_log(conn, "logging to '%s' / '%s'", - fnames[0], fnames[1]); - conn->rec_sink = pds_stdio(fp); - } else { - for (size_t i = 0; i < 2; i++) { - if (fp[i]) { - remove(fnames[i]); - fclose(fp[i]); - } - } - } - for (size_t i = 0; i < 2; i++) - sfree(fnames[i]); - } - break; - case REC_PIPE: - { - static const char *const direction_args[2] = { - [UP] = "out", [DN] = "in" }; - char *index_arg = dupprintf("%"PRIu64, conn->index); - char *err; - conn->rec_sink = conn->ps->platform->open_pipes( - conn->ps->rec_cmd, direction_args, index_arg, &err); - if (!conn->rec_sink) { - psocks_conn_log(conn, "cannot log this connection: " - "creating pipes: %s", err); - sfree(err); - } - sfree(index_arg); - } - break; - default: - break; - } - queue_toplevel_callback(psocks_connection_establish, conn); - return &conn->sc; -} - -static void psocks_conn_free(psocks_connection *conn) -{ - if (conn->ps->log_flags & LOG_CONNSTATUS) - psocks_conn_log(conn, "closed"); - - sfree(conn->host); - sfree(conn->realhost); - if (conn->socket) - sk_close(conn->socket); - if (conn->chan) - chan_free(conn->chan); - if (conn->rec_sink) - pds_free(conn->rec_sink); - delete_callbacks_for_context(conn); - sfree(conn); -} - -static void psocks_connection_establish(void *vctx) -{ - psocks_connection *conn = (psocks_connection *)vctx; - - /* - * Look up destination host name. - */ - conn->addr = sk_namelookup(conn->host, &conn->realhost, ADDRTYPE_UNSPEC); - - const char *err = sk_addr_error(conn->addr); - if (err) { - char *msg = dupprintf("name lookup failed: %s", err); - chan_open_failed(conn->chan, msg); - sfree(msg); - - psocks_conn_free(conn); - return; - } - - /* - * Make the connection. - */ - conn->connecting = true; - conn->socket = sk_new(conn->addr, conn->port, false, false, false, false, - &conn->plug); -} - -static size_t psocks_sc_write(SshChannel *sc, bool is_stderr, - const void *data, size_t len) -{ - psocks_connection *conn = container_of(sc, psocks_connection, sc); - if (!conn->socket) return 0; - - psocks_conn_log_data(conn, UP, data, len); - - return sk_write(conn->socket, data, len); -} - -static void psocks_check_close(void *vctx) -{ - psocks_connection *conn = (psocks_connection *)vctx; - if (chan_want_close(conn->chan, conn->eof_pfmgr_to_socket, - conn->eof_socket_to_pfmgr)) - psocks_conn_free(conn); -} - -static void psocks_sc_write_eof(SshChannel *sc) -{ - psocks_connection *conn = container_of(sc, psocks_connection, sc); - if (!conn->socket) return; - sk_write_eof(conn->socket); - conn->eof_pfmgr_to_socket = true; - - if (conn->ps->log_flags & LOG_DIALOGUE) - psocks_conn_log(conn, "send eof"); - - queue_toplevel_callback(psocks_check_close, conn); -} - -static void psocks_sc_initiate_close(SshChannel *sc, const char *err) -{ - psocks_connection *conn = container_of(sc, psocks_connection, sc); - sk_close(conn->socket); - conn->socket = NULL; -} - -static void psocks_sc_unthrottle(SshChannel *sc, size_t bufsize) -{ - psocks_connection *conn = container_of(sc, psocks_connection, sc); - if (bufsize < BUFLIMIT) - sk_set_frozen(conn->socket, false); -} - -static void psocks_plug_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code) -{ - psocks_connection *conn = container_of(plug, psocks_connection, plug); - char addrbuf[256]; - - if (!(conn->ps->log_flags & LOG_CONNSTATUS)) - return; - - switch (type) { - case PLUGLOG_CONNECT_TRYING: - sk_getaddr(addr, addrbuf, sizeof(addrbuf)); - if (sk_addr_needs_port(addr)) - psocks_conn_log(conn, "trying to connect to %s port %d", - addrbuf, port); - else - psocks_conn_log(conn, "trying to connect to %s", addrbuf); - break; - case PLUGLOG_CONNECT_FAILED: - psocks_conn_log(conn, "connection attempt failed: %s", error_msg); - break; - case PLUGLOG_CONNECT_SUCCESS: - psocks_conn_log(conn, "connection established", error_msg); - if (conn->connecting) { - chan_open_confirmation(conn->chan); - conn->connecting = false; - } - break; - case PLUGLOG_PROXY_MSG: - psocks_conn_log(conn, "connection setup: %s", error_msg); - break; - }; -} - -static void psocks_plug_closing(Plug *plug, PlugCloseType type, - const char *error_msg) -{ - psocks_connection *conn = container_of(plug, psocks_connection, plug); - if (conn->connecting) { - if (conn->ps->log_flags & LOG_CONNSTATUS) - psocks_conn_log(conn, "unable to connect: %s", error_msg); - - chan_open_failed(conn->chan, error_msg); - conn->eof_socket_to_pfmgr = true; - conn->eof_pfmgr_to_socket = true; - conn->connecting = false; - } else { - if (conn->ps->log_flags & LOG_DIALOGUE) - psocks_conn_log(conn, "recv eof"); - - chan_send_eof(conn->chan); - conn->eof_socket_to_pfmgr = true; - } - queue_toplevel_callback(psocks_check_close, conn); -} - -static void psocks_plug_receive(Plug *plug, int urgent, - const char *data, size_t len) -{ - psocks_connection *conn = container_of(plug, psocks_connection, plug); - size_t bufsize = chan_send(conn->chan, false, data, len); - sk_set_frozen(conn->socket, bufsize > BUFLIMIT); - - psocks_conn_log_data(conn, DN, data, len); -} - -static void psocks_plug_sent(Plug *plug, size_t bufsize) -{ - psocks_connection *conn = container_of(plug, psocks_connection, plug); - sk_set_frozen(conn->socket, bufsize > BUFLIMIT); -} - -psocks_state *psocks_new(const PsocksPlatform *platform) -{ - psocks_state *ps = snew(psocks_state); - memset(ps, 0, sizeof(*ps)); - - ps->listen_port = 1080; - ps->acceptall = false; - - ps->cl.vt = &psocks_clvt; - ps->portfwdmgr = portfwdmgr_new(&ps->cl); - - ps->logging_fp = stderr; /* could make this configurable later */ - ps->log_flags = LOG_CONNSTATUS; - ps->rec_dest = REC_NONE; - ps->platform = platform; - ps->subcmd = strbuf_new(); - - return ps; -} - -void psocks_free(psocks_state *ps) -{ - portfwdmgr_free(ps->portfwdmgr); - strbuf_free(ps->subcmd); - sfree(ps->rec_cmd); - sfree(ps); -} - -void psocks_cmdline(psocks_state *ps, int argc, char **argv) -{ - bool doing_opts = true; - bool accumulating_exec_args = false; - size_t args_seen = 0; - - while (--argc > 0) { - const char *p = *++argv; - - if (doing_opts && p[0] == '-' && p[1]) { - if (!strcmp(p, "--")) { - doing_opts = false; - } else if (!strcmp(p, "-g")) { - ps->acceptall = true; - } else if (!strcmp(p, "-d")) { - ps->log_flags |= LOG_DIALOGUE; - } else if (!strcmp(p, "-f")) { - ps->rec_dest = REC_FILE; - } else if (!strcmp(p, "-p")) { - if (!ps->platform->open_pipes) { - fprintf(stderr, "psocks: '-p' is not supported on this " - "platform\n"); - exit(1); - } - if (--argc > 0) { - ps->rec_cmd = dupstr(*++argv); - } else { - fprintf(stderr, "psocks: expected an argument to '-p'\n"); - exit(1); - } - ps->rec_dest = REC_PIPE; - } else if (!strcmp(p, "--exec")) { - if (!ps->platform->start_subcommand) { - fprintf(stderr, "psocks: running a subcommand is not " - "supported on this platform\n"); - exit(1); - } - accumulating_exec_args = true; - /* Now consume all further argv words for the - * subcommand, even if they look like options */ - doing_opts = false; - } else if (!strcmp(p, "--help")) { - printf("usage: psocks [ -d ] [ -f"); - if (ps->platform->open_pipes) - printf(" | -p pipe-cmd"); - printf(" ] [ -g ] port-number"); - printf("\n"); - printf("where: -d log all connection contents to" - " standard output\n"); - printf(" -f record each half-connection to " - "a file sockin.N/sockout.N\n"); - if (ps->platform->open_pipes) - printf(" -p pipe-cmd pipe each half-connection" - " to 'pipe-cmd [in|out] N'\n"); - printf(" -g accept connections from anywhere," - " not just localhost\n"); - if (ps->platform->start_subcommand) - printf(" --exec subcmd [args...] run command, and " - "terminate when it exits\n"); - printf(" port-number listen on this port" - " (default 1080)\n"); - printf("also: psocks --help display this help text\n"); - exit(0); - } else { - fprintf(stderr, "psocks: unrecognised option '%s'\n", p); - exit(1); - } - } else { - if (accumulating_exec_args) { - put_asciz(ps->subcmd, p); - } else switch (args_seen++) { - case 0: - ps->listen_port = atoi(p); - break; - default: - fprintf(stderr, "psocks: unexpected extra argument '%s'\n", p); - exit(1); - break; - } - } - } -} - -void psocks_start(psocks_state *ps) -{ - Conf *conf = conf_new(); - conf_set_bool(conf, CONF_lport_acceptall, ps->acceptall); - char *key = dupprintf("AL%d", ps->listen_port); - conf_set_str_str(conf, CONF_portfwd, key, "D"); - sfree(key); - - portfwdmgr_config(ps->portfwdmgr, conf); - - if (ps->subcmd->len) - ps->platform->start_subcommand(ps->subcmd); - - conf_free(conf); -} - -/* - * Some stubs that are needed to link against PuTTY modules. - */ - -int check_stored_host_key(const char *hostname, int port, - const char *keytype, const char *key) -{ - unreachable("host keys not handled in this tool"); -} - -void store_host_key(const char *hostname, int port, - const char *keytype, const char *key) -{ - unreachable("host keys not handled in this tool"); -} - -/* - * stdio-targeted PsocksDataSink. - */ -typedef struct PsocksDataSinkStdio { - stdio_sink sink[2]; - PsocksDataSink pds; -} PsocksDataSinkStdio; - -static void stdio_free(PsocksDataSink *pds) -{ - PsocksDataSinkStdio *pdss = container_of(pds, PsocksDataSinkStdio, pds); - - for (size_t i = 0; i < 2; i++) - fclose(pdss->sink[i].fp); - - sfree(pdss); -} - -PsocksDataSink *pds_stdio(FILE *fp[2]) -{ - PsocksDataSinkStdio *pdss = snew(PsocksDataSinkStdio); - - for (size_t i = 0; i < 2; i++) { - setvbuf(fp[i], NULL, _IONBF, 0); - stdio_sink_init(&pdss->sink[i], fp[i]); - pdss->pds.s[i] = BinarySink_UPCAST(&pdss->sink[i]); - } - - pdss->pds.free = stdio_free; - - return &pdss->pds; -} diff --git a/psocks.h b/psocks.h deleted file mode 100644 index d1120a36b..000000000 --- a/psocks.h +++ /dev/null @@ -1,28 +0,0 @@ -typedef struct psocks_state psocks_state; - -typedef struct PsocksPlatform PsocksPlatform; -typedef struct PsocksDataSink PsocksDataSink; - -/* indices into PsocksDataSink arrays */ -typedef enum PsocksDirection { UP, DN } PsocksDirection; - -typedef struct PsocksDataSink { - void (*free)(PsocksDataSink *); - BinarySink *s[2]; -} PsocksDataSink; -static inline void pds_free(PsocksDataSink *pds) -{ pds->free(pds); } - -PsocksDataSink *pds_stdio(FILE *fp[2]); - -struct PsocksPlatform { - PsocksDataSink *(*open_pipes)( - const char *cmd, const char *const *direction_args, - const char *index_arg, char **err); - void (*start_subcommand)(strbuf *args); -}; - -psocks_state *psocks_new(const PsocksPlatform *); -void psocks_free(psocks_state *ps); -void psocks_cmdline(psocks_state *ps, int argc, char **argv); -void psocks_start(psocks_state *ps); diff --git a/putty.h b/putty.h deleted file mode 100644 index bfb705c2b..000000000 --- a/putty.h +++ /dev/null @@ -1,2952 +0,0 @@ -#ifndef PUTTY_PUTTY_H -#define PUTTY_PUTTY_H - -#include /* for wchar_t */ -#include /* for INT_MAX */ - -#include "defs.h" -#include "platform.h" -#include "network.h" -#include "misc.h" -#include "marshal.h" - -/* - * We express various time intervals in unsigned long minutes, but may need to - * clip some values so that the resulting number of ticks does not overflow an - * integer value. - */ -#define MAX_TICK_MINS (INT_MAX / (60 * TICKSPERSEC)) - -/* - * Fingerprints of the current and previous PGP master keys, to - * establish a trust path between an executable and other files. - */ -#define PGP_MASTER_KEY_YEAR "2021" -#define PGP_MASTER_KEY_DETAILS "RSA, 3072-bit" -#define PGP_MASTER_KEY_FP \ - "A872 D42F 1660 890F 0E05 223E DD43 55EA AC11 19DE" -#define PGP_PREV_MASTER_KEY_YEAR "2018" -#define PGP_PREV_MASTER_KEY_DETAILS "RSA, 4096-bit" -#define PGP_PREV_MASTER_KEY_FP \ - "24E1 B1C5 75EA 3C9F F752 A922 76BC 7FE4 EBFD 2D9E" - -/* - * Definitions of three separate indexing schemes for colour palette - * entries. - * - * Why three? Because history, sorry. - * - * Two of the colour indexings are used in escape sequences. The - * Linux-console style OSC P sequences for setting the palette use an - * indexing in which the eight standard ANSI SGR colours come first, - * then their bold versions, and then six extra colours for default - * fg/bg and the terminal cursor. And the xterm OSC 4 sequences for - * querying the palette use a related indexing in which the six extra - * colours are pushed up to indices 256 and onwards, with the previous - * 16 being the first part of the xterm 256-colour space, and 240 - * additional terminal-accessible colours inserted in the middle. - * - * The third indexing is the order that the colours appear in the - * PuTTY configuration panel, and also the order in which they're - * described in the saved session files. This order specifies the same - * set of colours as the OSC P encoding, but in a different order, - * with the default fg/bg colours (which users are most likely to want - * to reconfigure) at the start, and the ANSI SGR colours coming - * later. - * - * So all three indices really are needed, because all three appear in - * protocols or file formats outside the PuTTY binary. (Changing the - * saved-session encoding would have a backwards-compatibility impact; - * also, if we ever do, it would be better to replace the numeric - * indices with descriptive keywords.) - * - * Since the OSC 4 encoding contains the full set of colours used in - * the terminal display, that's the encoding used by front ends to - * store any actual data associated with their palette entries. So the - * TermWin palette_set and palette_get_overrides methods use that - * encoding, and so does the bitwise encoding of attribute words used - * in terminal redraw operations. - * - * The Conf encoding, of course, is used by config.c and settings.c. - * - * The aim is that those two sections of the code should never need to - * come directly into contact, and the only module that should have to - * deal directly with the mapping between these colour encodings - or - * to deal _at all_ with the intermediate OSC P encoding - is - * terminal.c itself. - */ - -#define CONF_NCOLOURS 22 /* 16 + 6 special ones */ -#define OSCP_NCOLOURS 22 /* same as CONF, but different order */ -#define OSC4_NCOLOURS 262 /* 256 + the same 6 special ones */ - -/* The list macro for the conf colours also gives the textual names - * used in the GUI configurer */ -#define CONF_COLOUR_LIST(X) \ - X(fg, "Default Foreground") \ - X(fg_bold, "Default Bold Foreground") \ - X(bg, "Default Background") \ - X(bg_bold, "Default Bold Background") \ - X(cursor_fg, "Cursor Text") \ - X(cursor_bg, "Cursor Colour") \ - X(black, "ANSI Black") \ - X(black_bold, "ANSI Black Bold") \ - X(red, "ANSI Red") \ - X(red_bold, "ANSI Red Bold") \ - X(green, "ANSI Green") \ - X(green_bold, "ANSI Green Bold") \ - X(yellow, "ANSI Yellow") \ - X(yellow_bold, "ANSI Yellow Bold") \ - X(blue, "ANSI Blue") \ - X(blue_bold, "ANSI Blue Bold") \ - X(magenta, "ANSI Magenta") \ - X(magenta_bold, "ANSI Magenta Bold") \ - X(cyan, "ANSI Cyan") \ - X(cyan_bold, "ANSI Cyan Bold") \ - X(white, "ANSI White") \ - X(white_bold, "ANSI White Bold") \ - /* end of list */ - -#define OSCP_COLOUR_LIST(X) \ - X(black) \ - X(red) \ - X(green) \ - X(yellow) \ - X(blue) \ - X(magenta) \ - X(cyan) \ - X(white) \ - X(black_bold) \ - X(red_bold) \ - X(green_bold) \ - X(yellow_bold) \ - X(blue_bold) \ - X(magenta_bold) \ - X(cyan_bold) \ - X(white_bold) \ - /* - * In the OSC 4 indexing, this is where the extra 240 colours go. - * They consist of: - * - * - 216 colours forming a 6x6x6 cube, with R the most - * significant colour and G the least. In other words, these - * occupy the space of indices 16 <= i < 232, with each - * individual colour found as i = 16 + 36*r + 6*g + b, for all - * 0 <= r,g,b <= 5. - * - * - The remaining indices, 232 <= i < 256, consist of a uniform - * series of grey shades running between black and white (but - * not including either, since actual black and white are - * already provided in the previous colour cube). - * - * After that, we have the remaining 6 special colours: - */ \ - X(fg) \ - X(fg_bold) \ - X(bg) \ - X(bg_bold) \ - X(cursor_fg) \ - X(cursor_bg) \ - /* end of list */ - -/* Enumerations of the colour lists. These are available everywhere in - * the code. The OSC P encoding shouldn't be used outside terminal.c, - * but the easiest way to define the OSC 4 enum is to have the OSC P - * one available to compute with. */ -enum { - #define ENUM_DECL(id,name) CONF_COLOUR_##id, - CONF_COLOUR_LIST(ENUM_DECL) - #undef ENUM_DECL -}; -enum { - #define ENUM_DECL(id) OSCP_COLOUR_##id, - OSCP_COLOUR_LIST(ENUM_DECL) - #undef ENUM_DECL -}; -enum { - #define ENUM_DECL(id) OSC4_COLOUR_##id = \ - OSCP_COLOUR_##id + (OSCP_COLOUR_##id >= 16 ? 240 : 0), - OSCP_COLOUR_LIST(ENUM_DECL) - #undef ENUM_DECL -}; - -/* Mapping tables defined in terminal.c */ -extern const int colour_indices_conf_to_oscp[CONF_NCOLOURS]; -extern const int colour_indices_conf_to_osc4[CONF_NCOLOURS]; -extern const int colour_indices_oscp_to_osc4[OSCP_NCOLOURS]; - -/* 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 - -/* Use the DC00 page for direct to font. */ -#define CSET_OEMCP 0x0000DC00UL /* OEM Codepage DTF */ -#define CSET_ACP 0x0000DD00UL /* 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)==0xDC00) - -#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 0x0800000U -#define ATTR_WIDE 0x0400000U -#define ATTR_BOLD 0x0040000U -#define ATTR_UNDER 0x0080000U -#define ATTR_REVERSE 0x0100000U -#define ATTR_BLINK 0x0200000U -#define ATTR_FGMASK 0x00001FFU /* stores a colour in OSC 4 indexing */ -#define ATTR_BGMASK 0x003FE00U /* stores a colour in OSC 4 indexing */ -#define ATTR_COLOURS 0x003FFFFU -#define ATTR_DIM 0x1000000U -#define ATTR_STRIKE 0x2000000U -#define ATTR_FGSHIFT 0 -#define ATTR_BGSHIFT 9 - -#define ATTR_DEFFG (OSC4_COLOUR_fg << ATTR_FGSHIFT) -#define ATTR_DEFBG (OSC4_COLOUR_bg << ATTR_BGSHIFT) -#define ATTR_DEFAULT (ATTR_DEFFG | ATTR_DEFBG) - -struct sesslist { - int nsessions; - const char **sessions; - char *buffer; /* so memory can be freed later */ -}; - -struct unicode_data { - bool 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 */ - -/* - * Enumeration of 'special commands' that can be sent during a - * session, separately from the byte stream of ordinary session data. - */ -typedef enum { - /* - * Commands that are generally useful in multiple backends. - */ - SS_BRK, /* serial-line break */ - SS_EOF, /* end-of-file on session input */ - SS_NOP, /* transmit data with no effect */ - SS_PING, /* try to keep the session alive (probably, but not - * necessarily, implemented as SS_NOP) */ - - /* - * Commands specific to Telnet. - */ - SS_AYT, /* Are You There */ - SS_SYNCH, /* Synch */ - SS_EC, /* Erase Character */ - SS_EL, /* Erase Line */ - SS_GA, /* Go Ahead */ - SS_ABORT, /* Abort Process */ - SS_AO, /* Abort Output */ - SS_IP, /* Interrupt Process */ - SS_SUSP, /* Suspend Process */ - SS_EOR, /* End Of Record */ - SS_EOL, /* Telnet end-of-line sequence (CRLF, as opposed to CR - * NUL that escapes a literal CR) */ - - /* - * Commands specific to SSH. - */ - SS_REKEY, /* trigger an immediate repeat key exchange */ - SS_XCERT, /* cross-certify another host key ('arg' indicates which) */ - - /* - * Send a POSIX-style signal. (Useful in SSH and also pterm.) - * - * We use the master list in ssh/signal-list.h to define these enum - * values, which will come out looking like names of the form - * SS_SIGABRT, SS_SIGINT etc. - */ - #define SIGNAL_MAIN(name, text) SS_SIG ## name, - #define SIGNAL_SUB(name) SS_SIG ## name, - #include "ssh/signal-list.h" - #undef SIGNAL_MAIN - #undef SIGNAL_SUB - - /* - * These aren't really special commands, but they appear in the - * enumeration because the list returned from - * backend_get_specials() will use them to specify the structure - * of the GUI specials menu. - */ - SS_SEP, /* Separator */ - SS_SUBMENU, /* Start a new submenu with specified name */ - SS_EXITMENU, /* Exit current submenu, or end of entire specials list */ -} SessionSpecialCode; - -/* - * The structure type returned from backend_get_specials. - */ -struct SessionSpecial { - const char *name; - SessionSpecialCode code; - int arg; -}; - -/* Needed by both ssh/channel.h and ssh/ppl.h */ -typedef void (*add_special_fn_t)( - void *ctx, const char *text, SessionSpecialCode code, int arg); - -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_DHGROUP15, - KEX_DHGROUP16, - KEX_DHGROUP17, - KEX_DHGROUP18, - KEX_DHGEX, - KEX_RSA, - KEX_ECDH, - KEX_NTRU_HYBRID, - KEX_MAX -}; - -enum { - /* - * SSH-2 host key algorithms - */ - HK_WARN, - HK_RSA, - HK_DSA, - HK_ECDSA, - HK_ED25519, - HK_ED448, - HK_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_CHACHA20, - CIPHER_AESGCM, - CIPHER_MAX /* no. ciphers (inc warn) */ -}; - -enum TriState { - /* - * 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, proxy terminal logging, 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, PROXY_SSH_TCPIP, - PROXY_SSH_EXEC, PROXY_SSH_SUBSYSTEM, - PROXY_FUZZ -}; - -enum { - /* - * Line discipline options which the backend might try to control. - */ - LD_EDIT, /* local line editing */ - LD_ECHO, /* local echo */ - LD_N_OPTIONS -}; - -enum { - /* Actions on remote window title query */ - TITLE_NONE, TITLE_EMPTY, TITLE_REAL -}; - -enum { - /* SUPDUP character set options */ - SUPDUP_CHARSET_ASCII, SUPDUP_CHARSET_ITS, SUPDUP_CHARSET_WAITS -}; - -enum { - /* Protocol back ends. (CONF_protocol) */ - PROT_RAW, PROT_TELNET, PROT_RLOGIN, PROT_SSH, PROT_SSHCONN, - /* PROT_SERIAL is supported on a subset of platforms, but it doesn't - * hurt to define it globally. */ - PROT_SERIAL, - /* PROT_SUPDUP is the historical RFC 734 protocol. */ - PROT_SUPDUP, - PROTOCOL_LIMIT, /* upper bound on number of protocols */ -}; - -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, - FUNKY_XTERM_216 -}; - -enum { - /* Shifted arrow key types (CONF_sharrow_type) */ - SHARROW_APPLICATION, /* Ctrl flips between ESC O A and ESC [ A */ - SHARROW_BITMAP /* ESC [ 1 ; n A, where n = 1 + bitmap of CAS */ -}; - -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. - */ - const 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_LOCAL, /* e.g. Unix domain socket, or Windows named pipe */ - ADDRTYPE_NAME /* SockAddr storing an unresolved host name */ -}; - -/* Backend flags */ -#define BACKEND_RESIZE_FORBIDDEN 0x01 /* Backend does not allow - resizing terminal */ -#define BACKEND_NEEDS_TERMINAL 0x02 /* Backend must have terminal */ -#define BACKEND_SUPPORTS_NC_HOST 0x04 /* Backend can honour - CONF_ssh_nc_host */ -#define BACKEND_NOTIFIES_SESSION_START 0x08 /* Backend will call - seat_notify_session_started */ - -/* In (no)sshproxy.c */ -extern const bool ssh_proxy_supported; - -/* - * This structure type wraps a Seat pointer, in a way that has no - * purpose except to be a different type. - * - * The Seat wrapper functions that present interactive prompts all - * expect one of these in place of their ordinary Seat pointer. You - * get one by calling interactor_announce (defined below), which will - * print a message (if not already done) identifying the Interactor - * that originated the prompt. - * - * This arranges that the C type system itself will check that no call - * to any of those Seat methods has omitted the mandatory call to - * interactor_announce beforehand. - */ -struct InteractionReadySeat { - Seat *seat; -}; - -/* - * The Interactor trait is implemented by anything that is capable of - * presenting interactive prompts or questions to the user during - * network connection setup. Every Backend that ever needs to do this - * is an Interactor, but also, while a Backend is making its initial - * network connection, it may go via network proxy code which is also - * an Interactor and can ask questions of its own. - */ -struct Interactor { - const InteractorVtable *vt; - - /* The parent Interactor that we are a proxy for, if any. */ - Interactor *parent; - - /* - * If we're the top-level Interactor (parent==NULL), then this - * field records the last Interactor that actually did anything - * interactive, so that we know when to announce a changeover - * between levels of proxying. - * - * If parent != NULL, this field is not used. - */ - Interactor *last_to_talk; -}; - -struct InteractorVtable { - /* - * Returns a user-facing description of the nature of the network - * connection being made. Used in interactive proxy authentication - * to announce which connection attempt is now in control of the - * Seat. - * - * The idea is not just to be written in natural language, but to - * connect with the user's idea of _why_ they think some - * connection is being made. For example, instead of saying 'TCP - * connection to 123.45.67.89 port 22', you might say 'SSH - * connection to [logical host name for SSH host key purposes]'. - * - * The returned string must be freed by the caller. - */ - char *(*description)(Interactor *itr); - - /* - * Returns the LogPolicy associated with this Interactor. (A - * Backend can derive this from its logging context; a proxy - * Interactor inherits it from the Interactor for the parent - * network connection.) - */ - LogPolicy *(*logpolicy)(Interactor *itr); - - /* - * Gets and sets the Seat that this Interactor talks to. When a - * Seat is borrowed and replaced with a TempSeat, this will be the - * mechanism by which that replacement happens. - */ - Seat *(*get_seat)(Interactor *itr); - void (*set_seat)(Interactor *itr, Seat *seat); -}; - -static inline char *interactor_description(Interactor *itr) -{ return itr->vt->description(itr); } -static inline LogPolicy *interactor_logpolicy(Interactor *itr) -{ return itr->vt->logpolicy(itr); } -static inline Seat *interactor_get_seat(Interactor *itr) -{ return itr->vt->get_seat(itr); } -static inline void interactor_set_seat(Interactor *itr, Seat *seat) -{ itr->vt->set_seat(itr, seat); } - -static inline void interactor_set_child(Interactor *parent, Interactor *child) -{ child->parent = parent; } -Seat *interactor_borrow_seat(Interactor *itr); -void interactor_return_seat(Interactor *itr); -InteractionReadySeat interactor_announce(Interactor *itr); - -/* Interactors that are Backends will find this helper function useful - * in constructing their description strings */ -char *default_description(const BackendVtable *backvt, - const char *host, int port); - -/* - * The Backend trait is the top-level one that governs each of the - * user-facing main modes that PuTTY can use to talk to some - * destination: SSH, Telnet, serial port, pty, etc. - */ - -struct Backend { - const BackendVtable *vt; - - /* Many Backends are also Interactors. If this one is, a pointer - * to its Interactor trait lives here. */ - Interactor *interactor; -}; -struct BackendVtable { - char *(*init) (const BackendVtable *vt, Seat *seat, - Backend **backend_out, LogContext *logctx, Conf *conf, - const char *host, int port, char **realhost, - bool nodelay, bool keepalive); - - void (*free) (Backend *be); - /* Pass in a replacement configuration. */ - void (*reconfig) (Backend *be, Conf *conf); - void (*send) (Backend *be, const char *buf, size_t len); - /* sendbuffer() returns the current amount of buffered data */ - size_t (*sendbuffer) (Backend *be); - void (*size) (Backend *be, int width, int height); - void (*special) (Backend *be, SessionSpecialCode code, int arg); - const SessionSpecial *(*get_specials) (Backend *be); - bool (*connected) (Backend *be); - int (*exitcode) (Backend *be); - /* If back->sendok() returns false, the backend doesn't currently - * want input data, so the frontend should avoid acquiring any if - * possible (passing back-pressure on to its sender). - * - * Policy rule: no backend shall return true from sendok() while - * its network connection attempt is still ongoing. This ensures - * that if making the network connection involves a proxy type - * which wants to interact with the user via the terminal, the - * proxy implementation and the backend itself won't fight over - * who gets the terminal input. */ - bool (*sendok) (Backend *be); - bool (*ldisc_option_state) (Backend *be, int); - void (*provide_ldisc) (Backend *be, Ldisc *ldisc); - /* Tells the back end that the front end buffer is clearing. */ - void (*unthrottle) (Backend *be, size_t bufsize); - int (*cfg_info) (Backend *be); - - /* Only implemented in the SSH protocol: check whether a - * connection-sharing upstream exists for a given configuration. */ - bool (*test_for_upstream)(const char *host, int port, Conf *conf); - /* Special-purpose function to return additional information to put - * in a "are you sure you want to close this session" dialog; - * return NULL if no such info, otherwise caller must free. - * Only implemented in the SSH protocol, to warn about downstream - * connections that would be lost if this one were terminated. */ - char *(*close_warn_text)(Backend *be); - - /* 'id' is a machine-readable name for the backend, used in - * saved-session storage. 'displayname_tc' and 'displayname_lc' - * are human-readable names, one in title-case for config boxes, - * and one in lower-case for use in mid-sentence. */ - const char *id, *displayname_tc, *displayname_lc; - - int protocol; - int default_port; - unsigned flags; - - /* Only relevant for the serial protocol: bit masks of which - * parity and flow control settings are supported. */ - unsigned serial_parity_mask, serial_flow_mask; -}; - -static inline char *backend_init( - const BackendVtable *vt, Seat *seat, Backend **out, LogContext *logctx, - Conf *conf, const char *host, int port, char **rhost, bool nd, bool ka) -{ return vt->init(vt, seat, out, logctx, conf, host, port, rhost, nd, ka); } -static inline void backend_free(Backend *be) -{ be->vt->free(be); } -static inline void backend_reconfig(Backend *be, Conf *conf) -{ be->vt->reconfig(be, conf); } -static inline void backend_send(Backend *be, const char *buf, size_t len) -{ be->vt->send(be, buf, len); } -static inline size_t backend_sendbuffer(Backend *be) -{ return be->vt->sendbuffer(be); } -static inline void backend_size(Backend *be, int width, int height) -{ be->vt->size(be, width, height); } -static inline void backend_special( - Backend *be, SessionSpecialCode code, int arg) -{ be->vt->special(be, code, arg); } -static inline const SessionSpecial *backend_get_specials(Backend *be) -{ return be->vt->get_specials(be); } -static inline bool backend_connected(Backend *be) -{ return be->vt->connected(be); } -static inline int backend_exitcode(Backend *be) -{ return be->vt->exitcode(be); } -static inline bool backend_sendok(Backend *be) -{ return be->vt->sendok(be); } -static inline bool backend_ldisc_option_state(Backend *be, int state) -{ return be->vt->ldisc_option_state(be, state); } -static inline void backend_provide_ldisc(Backend *be, Ldisc *ldisc) -{ be->vt->provide_ldisc(be, ldisc); } -static inline void backend_unthrottle(Backend *be, size_t bufsize) -{ be->vt->unthrottle(be, bufsize); } -static inline int backend_cfg_info(Backend *be) -{ return be->vt->cfg_info(be); } - -extern const struct BackendVtable *const backends[]; -/* - * In programs with a config UI, only the first few members of - * backends[] will be displayed at the top-level; the others will be - * relegated to a drop-down. - */ -extern const size_t n_ui_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; - -#ifdef PUTTYNG -int hwnd_parent; -#define IsZoomed(hWnd) TRUE -#endif // PUTTYNG - -/* - * Used by callback.c; declared up here so that prompts_t can use it - */ -typedef void (*toplevel_callback_fn_t)(void *ctx); - -/* Enum of result types in SeatPromptResult below */ -typedef enum SeatPromptResultKind { - /* Answer not yet available at all; either try again later or wait - * for a callback (depending on the request's API) */ - SPRK_INCOMPLETE, - - /* We're abandoning the connection because the user interactively - * told us to. (Hence, no need to present an error message - * telling the user we're doing that: they already know.) */ - SPRK_USER_ABORT, - - /* We're abandoning the connection for some other reason (e.g. we - * were unable to present the prompt at all, or a batch-mode - * configuration told us to give the answer no). This may - * ultimately have stemmed from some user configuration, but they - * didn't _tell us right now_ to abandon this connection, so we - * still need to inform them that we've done so. */ - SPRK_SW_ABORT, - - /* We're proceeding with the connection and have all requested - * information (if any) */ - SPRK_OK -} SeatPromptResultKind; - -/* Small struct to present the results of interactive requests from - * backend to Seat (see below) */ -struct SeatPromptResult { - SeatPromptResultKind kind; - - /* - * In the case of SPRK_SW_ABORT, the frontend provides an error - * message to present to the user. But dynamically allocating it - * up front would mean having to make sure it got freed at any - * call site where one of these structs is received (and freed - * _once_ no matter how many times the struct is copied). So - * instead we provide a function that will generate the error - * message into a BinarySink. - */ - void (*errfn)(SeatPromptResult, BinarySink *); - - /* - * And some fields the error function can use to construct the - * message (holding, e.g. an OS error code). - */ - const char *errdata_lit; /* statically allocated, e.g. a string literal */ - unsigned errdata_u; -}; - -/* Helper function to construct the simple versions of these - * structures inline */ -static inline SeatPromptResult make_spr_simple(SeatPromptResultKind kind) -{ - SeatPromptResult spr; - spr.kind = kind; - spr.errdata_lit = NULL; - return spr; -} - -/* Most common constructor function for SPRK_SW_ABORT errors */ -SeatPromptResult make_spr_sw_abort_static(const char *); - -/* Convenience macros wrapping those constructors in turn */ -#define SPR_INCOMPLETE make_spr_simple(SPRK_INCOMPLETE) -#define SPR_USER_ABORT make_spr_simple(SPRK_USER_ABORT) -#define SPR_SW_ABORT(lit) make_spr_sw_abort_static(lit) -#define SPR_OK make_spr_simple(SPRK_OK) - -/* Query function that folds both kinds of abort together */ -static inline bool spr_is_abort(SeatPromptResult spr) -{ - return spr.kind == SPRK_USER_ABORT || spr.kind == SPRK_SW_ABORT; -} - -/* Function to return a dynamically allocated copy of the error message */ -char *spr_get_error_message(SeatPromptResult spr); - -/* - * 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; - bool echo; - strbuf *result; -} prompt_t; -typedef struct prompts_t prompts_t; -struct prompts_t { - /* - * 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). - */ - bool to_server; - - /* - * Indicates whether the prompts originated _at_ the server, so - * that the front end can display some kind of trust sigil that - * distinguishes (say) a legit private-key passphrase prompt from - * a fake one sent by a malicious server. - */ - bool from_server; - - char *name; /* Short description, perhaps for dialog box title */ - bool name_reqd; /* Display of `name' required or optional? */ - char *instruction; /* Long description, maybe with embedded newlines */ - bool 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) */ - size_t prompts_size; /* allocated storage capacity for prompts[] */ - prompt_t **prompts; - void *data; /* slot for housekeeping data, managed by - * seat_get_userpass_input(); initially NULL */ - SeatPromptResult spr; /* some implementations need to cache one of these */ - - /* - * Callback you can fill in to be notified when all the prompts' - * responses are available. After you receive this notification, a - * further call to the get_userpass_input function will return the - * final state of the prompts system, which is guaranteed not to - * be negative for 'still ongoing'. - */ - toplevel_callback_fn_t callback; - void *callback_ctx; - - /* - * When this prompts_t is known to an Ldisc, we might need to - * break the connection if things get freed in an emergency. So - * this is a pointer to the Ldisc's pointer to us. - */ - prompts_t **ldisc_ptr_to_us; -}; -prompts_t *new_prompts(void); -void add_prompt(prompts_t *p, char *promptstr, bool echo); -void prompt_set_result(prompt_t *pr, const char *newstr); -char *prompt_get_result(prompt_t *pr); -const char *prompt_get_result_ref(prompt_t *pr); -void free_prompts(prompts_t *p); - -/* - * Data type definitions for true-colour terminal display. - * 'optionalrgb' describes a single RGB colour, which overrides the - * other colour settings if 'enabled' is nonzero, and is ignored - * otherwise. 'truecolour' contains a pair of those for foreground and - * background. - */ -typedef struct optionalrgb { - bool enabled; - unsigned char r, g, b; -} optionalrgb; -extern const optionalrgb optionalrgb_none; -typedef struct truecolour { - optionalrgb fg, bg; -} truecolour; -#define optionalrgb_equal(r1,r2) ( \ - (r1).enabled==(r2).enabled && \ - (r1).r==(r2).r && (r1).g==(r2).g && (r1).b==(r2).b) -#define truecolour_equal(c1,c2) ( \ - optionalrgb_equal((c1).fg, (c2).fg) && \ - optionalrgb_equal((c1).bg, (c2).bg)) - -/* - * Enumeration of clipboards. We provide some standard ones cross- - * platform, and then permit each platform to extend this enumeration - * further by defining PLATFORM_CLIPBOARDS in its own header file. - * - * CLIP_NULL is a non-clipboard, writes to which are ignored and reads - * from which return no data. - * - * CLIP_LOCAL refers to a buffer within terminal.c, which - * unconditionally saves the last data selected in the terminal. In - * configurations where a system clipboard is not written - * automatically on selection but instead by an explicit UI action, - * this is where the code responding to that action can find the data - * to write to the clipboard in question. - */ -#define CROSS_PLATFORM_CLIPBOARDS(X) \ - X(CLIP_NULL, "null clipboard") \ - X(CLIP_LOCAL, "last text selected in terminal") \ - /* end of list */ - -#define ALL_CLIPBOARDS(X) \ - CROSS_PLATFORM_CLIPBOARDS(X) \ - PLATFORM_CLIPBOARDS(X) \ - /* end of list */ - -#define CLIP_ID(id,name) id, -enum { ALL_CLIPBOARDS(CLIP_ID) N_CLIPBOARDS }; -#undef CLIP_ID - -/* Hint from backend to frontend about time-consuming operations, used - * by seat_set_busy_status. Initial state is assumed to be - * BUSY_NOT. */ -typedef enum BusyStatus { - 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 */ -} BusyStatus; - -typedef enum SeatInteractionContext { - SIC_BANNER, SIC_KI_PROMPTS -} SeatInteractionContext; - -typedef enum SeatOutputType { - SEAT_OUTPUT_STDOUT, SEAT_OUTPUT_STDERR -} SeatOutputType; - -typedef enum SeatDialogTextType { - SDT_PARA, SDT_DISPLAY, SDT_SCARY_HEADING, - SDT_TITLE, SDT_PROMPT, SDT_BATCH_ABORT, - SDT_MORE_INFO_KEY, SDT_MORE_INFO_VALUE_SHORT, SDT_MORE_INFO_VALUE_BLOB -} SeatDialogTextType; -struct SeatDialogTextItem { - SeatDialogTextType type; - char *text; -}; -struct SeatDialogText { - size_t nitems, itemsize; - SeatDialogTextItem *items; -}; -SeatDialogText *seat_dialog_text_new(void); -void seat_dialog_text_free(SeatDialogText *sdt); -PRINTF_LIKE(3, 4) void seat_dialog_text_append( - SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, ...); - -/* - * Data type 'Seat', which is an API intended to contain essentially - * everything that a back end might need to talk to its client for: - * session output, password prompts, SSH warnings about host keys and - * weak cryptography, notifications of events like the remote process - * exiting or the GUI specials menu needing an update. - */ -struct Seat { - const struct SeatVtable *vt; -}; -struct SeatVtable { - /* - * Provide output from the remote session. 'type' indicates the - * type of the output (stdout or stderr), which can be used to - * split the output into separate message channels, if the seat - * wants to handle them differently. But combining the channels - * into one is OK too; that's what terminal-window based seats do. - * - * The return value is the current size of the output backlog. - */ - size_t (*output)(Seat *seat, SeatOutputType type, - const void *data, size_t 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. - */ - bool (*eof)(Seat *seat); - - /* - * Called by the back end to notify that the output backlog has - * changed size. A front end in control of the event loop won't - * necessarily need this (they can just keep checking it via - * backend_sendbuffer at every opportunity), but one buried in the - * depths of something else (like an SSH proxy) will need to be - * proactively notified that the amount of buffered data has - * become smaller. - */ - void (*sent)(Seat *seat, size_t new_sendbuffer); - - /* - * Provide authentication-banner output from the session setup. - * End-user Seats can treat this as very similar to 'output', but - * intermediate Seats in complex proxying situations will want to - * implement this and 'output' differently. - */ - size_t (*banner)(Seat *seat, const void *data, size_t len); - - /* - * Try to get answers from a set of interactive login prompts. The - * prompts are provided in 'p'. - * - * (FIXME: it would be nice to distinguish two classes of user- - * abort action, so the user could specify 'I want to abandon this - * entire attempt to start a session' or the milder 'I want to - * abandon this particular form of authentication and fall back to - * a different one' - e.g. if you turn out not to be able to - * remember your private key passphrase then perhaps you'd rather - * fall back to password auth rather than aborting the whole - * session.) - */ - SeatPromptResult (*get_userpass_input)(Seat *seat, prompts_t *p); - - /* - * Notify the seat that the main session channel has been - * successfully set up. - * - * This is only used as part of the SSH proxying system, so it's - * not necessary to implement it in all backends. A backend must - * call this if it advertises the BACKEND_NOTIFIES_SESSION_START - * flag, and otherwise, doesn't have to. - */ - void (*notify_session_started)(Seat *seat); - - /* - * Notify the seat that the process running at the other end of - * the connection has finished. - */ - void (*notify_remote_exit)(Seat *seat); - - /* - * Notify the seat that the whole connection has finished. - * (Distinct from notify_remote_exit, e.g. in the case where you - * have port forwardings still active when the main foreground - * session goes away: then you'd get notify_remote_exit when the - * foreground session dies, but notify_remote_disconnect when the - * last forwarding vanishes and the network connection actually - * closes.) - * - * This function might be called multiple times by accident; seats - * should be prepared to cope. - * - * More precisely: this function notifies the seat that - * backend_connected() might now return false where previously it - * returned true. (Note the 'might': an accidental duplicate call - * might happen when backend_connected() was already returning - * false. Or even, in weird situations, when it hadn't stopped - * returning true yet. The point is, when you get this - * notification, all it's really telling you is that it's worth - * _checking_ backend_connected, if you weren't already.) - */ - void (*notify_remote_disconnect)(Seat *seat); - - /* - * Notify the seat that the connection has suffered a fatal error. - */ - void (*connection_fatal)(Seat *seat, const char *message); - - /* - * Notify the seat that the list of special commands available - * from backend_get_specials() has changed, so that it might want - * to call that function to repopulate its menu. - * - * Seats are not expected to call backend_get_specials() - * proactively; they may start by assuming that the backend - * provides no special commands at all, so if the backend does - * provide any, then it should use this notification at startup - * time. Of course 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)(Seat *seat); - - /* - * Get the seat's preferred value for an SSH terminal mode - * setting. Returning NULL indicates no preference (i.e. the SSH - * connection will not attempt to set the mode at all). - * - * The returned value is dynamically allocated, and the caller - * should free it. - */ - char *(*get_ttymode)(Seat *seat, const char *mode); - - /* - * Tell the seat whether the backend is currently doing anything - * CPU-intensive (typically a cryptographic key exchange). See - * BusyStatus enumeration above. - */ - void (*set_busy_status)(Seat *seat, BusyStatus status); - - /* - * Ask the seat whether a given SSH host key should be accepted. - * This is called after we've already checked it by any means we - * can do ourselves, such as checking against host key - * fingerprints in the Conf or the host key cache on disk: once we - * call this function, we've already decided there's nothing for - * it but to prompt the user. - * - * 'mismatch' reports the result of checking the host key cache: - * it is true if the server has presented a host key different - * from the one we expected, and false if we had no expectation in - * the first place. - * - * This call may prompt the user synchronously and not return - * until the answer is available, or it may present the prompt and - * return immediately, giving the answer later via the provided - * callback. - * - * Return values: - * - * - +1 means `user approved the key, so continue with the - * connection' - * - * - 0 means `user rejected the key, 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'. - */ - SeatPromptResult (*confirm_ssh_host_key)( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); - - /* - * Check with the seat whether it's OK to use a cryptographic - * primitive from below the 'warn below this line' threshold in - * the input Conf. Return values are the same as - * confirm_ssh_host_key above. - */ - SeatPromptResult (*confirm_weak_crypto_primitive)( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); - - /* - * Variant form of confirm_weak_crypto_primitive, which prints a - * slightly different message but otherwise has the same - * semantics. - * - * This form is used in the case where we're using a host key - * below the warning threshold because that's the best one we have - * cached, but at least one host key algorithm *above* the - * threshold is available that we don't have cached. 'betteralgs' - * lists the better algorithm(s). - */ - SeatPromptResult (*confirm_weak_cached_hostkey)( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); - - /* - * Some snippets of text describing the UI actions in host key - * prompts / dialog boxes, to be used in ssh/common.c when it - * assembles the full text of those prompts. - */ - const SeatDialogPromptDescriptions *(*prompt_descriptions)(Seat *seat); - - /* - * Indicates whether the seat is expecting to interact with the - * user in the UTF-8 character set. (Affects e.g. visual erase - * handling in local line editing.) - */ - bool (*is_utf8)(Seat *seat); - - /* - * Notify the seat that the back end, and/or the ldisc between - * them, have changed their idea of whether they currently want - * local echo and/or local line editing enabled. - */ - void (*echoedit_update)(Seat *seat, bool echoing, bool editing); - - /* - * Return the local X display string relevant to a seat, or NULL - * if there isn't one or if the concept is meaningless. - */ - const char *(*get_x_display)(Seat *seat); - - /* - * Return the X11 id of the X terminal window relevant to a seat, - * by returning true and filling in the output pointer. Return - * false if there isn't one or if the concept is meaningless. - */ - bool (*get_windowid)(Seat *seat, long *id_out); - - /* - * Return the size of the terminal window in pixels. If the - * concept is meaningless or the information is unavailable, - * return false; otherwise fill in the output pointers and return - * true. - */ - bool (*get_window_pixel_size)(Seat *seat, int *width, int *height); - - /* - * Return a StripCtrlChars appropriate for sanitising untrusted - * terminal data (e.g. SSH banners, prompts) being sent to the - * user of this seat. May return NULL if no sanitisation is - * needed. - */ - StripCtrlChars *(*stripctrl_new)( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); - - /* - * Set the seat's current idea of where output is coming from. - * True means that output is being generated by our own code base - * (and hence, can be trusted if it's asking you for secrets such - * as your passphrase); false means output is coming from the - * server. - */ - void (*set_trust_status)(Seat *seat, bool trusted); - - /* - * Query whether this Seat can do anything user-visible in - * response to set_trust_status. - * - * Returns true if the seat has a way to indicate this - * distinction. Returns false if not, in which case the backend - * should use a fallback defence against spoofing of PuTTY's local - * prompts by malicious servers. - */ - bool (*can_set_trust_status)(Seat *seat); - - /* - * Query whether this Seat's interactive prompt responses and its - * session input come from the same place. - * - * If false, this is used to suppress the final 'Press Return to - * begin session' anti-spoofing prompt in Plink. For example, - * Plink itself sets this flag if its standard input is redirected - * (and therefore not coming from the same place as the console - * it's sending its prompts to). - */ - bool (*has_mixed_input_stream)(Seat *seat); - - /* - * Ask the seat whether it would like verbose messages. - */ - bool (*verbose)(Seat *seat); - - /* - * Ask the seat whether it's an interactive program. - */ - bool (*interactive)(Seat *seat); - - /* - * Return the seat's current idea of where the output cursor is. - * - * Returns true if the seat has a cursor. Returns false if not. - */ - bool (*get_cursor_position)(Seat *seat, int *x, int *y); -}; - -static inline size_t seat_output( - Seat *seat, SeatOutputType type, const void *data, size_t len) -{ return seat->vt->output(seat, type, data, len); } -static inline bool seat_eof(Seat *seat) -{ return seat->vt->eof(seat); } -static inline void seat_sent(Seat *seat, size_t bufsize) -{ seat->vt->sent(seat, bufsize); } -static inline size_t seat_banner( - InteractionReadySeat iseat, const void *data, size_t len) -{ return iseat.seat->vt->banner(iseat.seat, data, len); } -static inline SeatPromptResult seat_get_userpass_input( - InteractionReadySeat iseat, prompts_t *p) -{ return iseat.seat->vt->get_userpass_input(iseat.seat, p); } -static inline void seat_notify_session_started(Seat *seat) -{ seat->vt->notify_session_started(seat); } -static inline void seat_notify_remote_exit(Seat *seat) -{ seat->vt->notify_remote_exit(seat); } -static inline void seat_notify_remote_disconnect(Seat *seat) -{ seat->vt->notify_remote_disconnect(seat); } -static inline void seat_update_specials_menu(Seat *seat) -{ seat->vt->update_specials_menu(seat); } -static inline char *seat_get_ttymode(Seat *seat, const char *mode) -{ return seat->vt->get_ttymode(seat, mode); } -static inline void seat_set_busy_status(Seat *seat, BusyStatus status) -{ seat->vt->set_busy_status(seat, status); } -static inline SeatPromptResult seat_confirm_ssh_host_key( - InteractionReadySeat iseat, const char *h, int p, const char *ktyp, - char *kstr, SeatDialogText *text, HelpCtx helpctx, - void (*cb)(void *ctx, SeatPromptResult result), void *ctx) -{ return iseat.seat->vt->confirm_ssh_host_key( - iseat.seat, h, p, ktyp, kstr, text, helpctx, cb, ctx); } -static inline SeatPromptResult seat_confirm_weak_crypto_primitive( - InteractionReadySeat iseat, const char *atyp, const char *aname, - void (*cb)(void *ctx, SeatPromptResult result), void *ctx) -{ return iseat.seat->vt->confirm_weak_crypto_primitive( - iseat.seat, atyp, aname, cb, ctx); } -static inline SeatPromptResult seat_confirm_weak_cached_hostkey( - InteractionReadySeat iseat, const char *aname, const char *better, - void (*cb)(void *ctx, SeatPromptResult result), void *ctx) -{ return iseat.seat->vt->confirm_weak_cached_hostkey( - iseat.seat, aname, better, cb, ctx); } -static inline const SeatDialogPromptDescriptions *seat_prompt_descriptions( - Seat *seat) -{ return seat->vt->prompt_descriptions(seat); } -static inline bool seat_is_utf8(Seat *seat) -{ return seat->vt->is_utf8(seat); } -static inline void seat_echoedit_update(Seat *seat, bool ec, bool ed) -{ seat->vt->echoedit_update(seat, ec, ed); } -static inline const char *seat_get_x_display(Seat *seat) -{ return seat->vt->get_x_display(seat); } -static inline bool seat_get_windowid(Seat *seat, long *id_out) -{ return seat->vt->get_windowid(seat, id_out); } -static inline bool seat_get_window_pixel_size(Seat *seat, int *w, int *h) -{ return seat->vt->get_window_pixel_size(seat, w, h); } -static inline StripCtrlChars *seat_stripctrl_new( - Seat *seat, BinarySink *bs, SeatInteractionContext sic) -{ return seat->vt->stripctrl_new(seat, bs, sic); } -static inline void seat_set_trust_status(Seat *seat, bool trusted) -{ seat->vt->set_trust_status(seat, trusted); } -static inline bool seat_can_set_trust_status(Seat *seat) -{ return seat->vt->can_set_trust_status(seat); } -static inline bool seat_has_mixed_input_stream(Seat *seat) -{ return seat->vt->has_mixed_input_stream(seat); } -static inline bool seat_verbose(Seat *seat) -{ return seat->vt->verbose(seat); } -static inline bool seat_interactive(Seat *seat) -{ return seat->vt->interactive(seat); } -static inline bool seat_get_cursor_position(Seat *seat, int *x, int *y) -{ return seat->vt->get_cursor_position(seat, x, y); } - -/* Unlike the seat's actual method, the public entry point - * seat_connection_fatal is a wrapper function with a printf-like API, - * defined in utils. */ -void seat_connection_fatal(Seat *seat, const char *fmt, ...) PRINTF_LIKE(2, 3); - -/* Handy aliases for seat_output which set is_stderr to a fixed value. */ -static inline size_t seat_stdout(Seat *seat, const void *data, size_t len) -{ return seat_output(seat, SEAT_OUTPUT_STDOUT, data, len); } -static inline size_t seat_stdout_pl(Seat *seat, ptrlen data) -{ return seat_output(seat, SEAT_OUTPUT_STDOUT, data.ptr, data.len); } -static inline size_t seat_stderr(Seat *seat, const void *data, size_t len) -{ return seat_output(seat, SEAT_OUTPUT_STDERR, data, len); } -static inline size_t seat_stderr_pl(Seat *seat, ptrlen data) -{ return seat_output(seat, SEAT_OUTPUT_STDERR, data.ptr, data.len); } - -/* Alternative API for seat_banner taking a ptrlen */ -static inline size_t seat_banner_pl(InteractionReadySeat iseat, ptrlen data) -{ return iseat.seat->vt->banner(iseat.seat, data.ptr, data.len); } - -struct SeatDialogPromptDescriptions { - const char *hk_accept_action; - const char *hk_connect_once_action; - const char *hk_cancel_action, *hk_cancel_action_Participle; -}; - -/* In the utils subdir: print a message to the Seat which can't be - * spoofed by server-supplied auth-time output such as SSH banners */ -void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg); - -/* - * Stub methods for seat implementations that want to use the obvious - * null handling for a given method. - * - * These are generally obvious, except for is_utf8, where you might - * plausibly want to return either fixed answer 'no' or 'yes'. - */ -size_t nullseat_output( - Seat *seat, SeatOutputType type, const void *data, size_t len); -bool nullseat_eof(Seat *seat); -void nullseat_sent(Seat *seat, size_t bufsize); -size_t nullseat_banner(Seat *seat, const void *data, size_t len); -size_t nullseat_banner_to_stderr(Seat *seat, const void *data, size_t len); -SeatPromptResult nullseat_get_userpass_input(Seat *seat, prompts_t *p); -void nullseat_notify_session_started(Seat *seat); -void nullseat_notify_remote_exit(Seat *seat); -void nullseat_notify_remote_disconnect(Seat *seat); -void nullseat_connection_fatal(Seat *seat, const char *message); -void nullseat_update_specials_menu(Seat *seat); -char *nullseat_get_ttymode(Seat *seat, const char *mode); -void nullseat_set_busy_status(Seat *seat, BusyStatus status); -SeatPromptResult nullseat_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); -SeatPromptResult nullseat_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); -SeatPromptResult nullseat_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); -const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat); -bool nullseat_is_never_utf8(Seat *seat); -bool nullseat_is_always_utf8(Seat *seat); -void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing); -const char *nullseat_get_x_display(Seat *seat); -bool nullseat_get_windowid(Seat *seat, long *id_out); -bool nullseat_get_window_pixel_size(Seat *seat, int *width, int *height); -StripCtrlChars *nullseat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); -void nullseat_set_trust_status(Seat *seat, bool trusted); -bool nullseat_can_set_trust_status_yes(Seat *seat); -bool nullseat_can_set_trust_status_no(Seat *seat); -bool nullseat_has_mixed_input_stream_yes(Seat *seat); -bool nullseat_has_mixed_input_stream_no(Seat *seat); -bool nullseat_verbose_no(Seat *seat); -bool nullseat_verbose_yes(Seat *seat); -bool nullseat_interactive_no(Seat *seat); -bool nullseat_interactive_yes(Seat *seat); -bool nullseat_get_cursor_position(Seat *seat, int *x, int *y); - -/* - * Seat functions provided by the platform's console-application - * support module (console.c in each platform subdirectory). - */ - -void console_connection_fatal(Seat *seat, const char *message); -SeatPromptResult console_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); -SeatPromptResult console_confirm_weak_crypto_primitive( - Seat *seat, const char *algtype, const char *algname, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); -SeatPromptResult console_confirm_weak_cached_hostkey( - Seat *seat, const char *algname, const char *betteralgs, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); -StripCtrlChars *console_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); -void console_set_trust_status(Seat *seat, bool trusted); -bool console_can_set_trust_status(Seat *seat); -bool console_has_mixed_input_stream(Seat *seat); -const SeatDialogPromptDescriptions *console_prompt_descriptions(Seat *seat); - -/* - * Other centralised seat functions. - */ -SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p); -bool cmdline_seat_verbose(Seat *seat); - -/* - * TempSeat: a seat implementation that can be given to a backend - * temporarily while network proxy setup is using the real seat. - * Buffers output and trust-status changes until the real seat is - * available again. - */ - -/* Called by the proxy code to make a TempSeat. */ -Seat *tempseat_new(Seat *real); - -/* Query functions to tell if a Seat _is_ temporary, and if so, to - * return the underlying real Seat. */ -bool is_tempseat(Seat *seat); -Seat *tempseat_get_real(Seat *seat); - -/* Called by interactor_return_seat once the proxy connection has - * finished setting up (or failed), to pass on any buffered stuff to - * the real seat. */ -void tempseat_flush(Seat *ts); - -/* Frees a TempSeat, without flushing anything it has buffered. (Call - * this after tempseat_flush, or alternatively, when you were going to - * abandon the whole connection anyway.) */ -void tempseat_free(Seat *ts); - -typedef struct rgb { - uint8_t r, g, b; -} rgb; - -/* - * Data type 'TermWin', which is a vtable encapsulating all the - * functionality that Terminal expects from its containing terminal - * window. - */ -struct TermWin { - const struct TermWinVtable *vt; -}; -struct TermWinVtable { - /* - * All functions listed here between setup_draw_ctx and - * free_draw_ctx expect to be _called_ between them too, so that - * the TermWin has a drawing context currently available. - * - * (Yes, even char_width, because e.g. the Windows implementation - * of TermWin handles it by loading the currently configured font - * into the HDC and doing a GDI query.) - */ - bool (*setup_draw_ctx)(TermWin *); - /* Draw text in the window, during a painting operation */ - void (*draw_text)(TermWin *, int x, int y, wchar_t *text, int len, - unsigned long attrs, int line_attrs, truecolour tc); - /* Draw the visible cursor. Expects you to have called do_text - * first (because it might just draw an underline over a character - * presumed to exist already), but also expects you to pass in all - * the details of the character under the cursor (because it might - * redraw it in different colours). */ - void (*draw_cursor)(TermWin *, int x, int y, wchar_t *text, int len, - unsigned long attrs, int line_attrs, truecolour tc); - /* Draw the sigil indicating that a line of text has come from - * PuTTY itself rather than the far end (defence against end-of- - * authentication spoofing) */ - void (*draw_trust_sigil)(TermWin *, int x, int y); - int (*char_width)(TermWin *, int uc); - void (*free_draw_ctx)(TermWin *); - - void (*set_cursor_pos)(TermWin *, int x, int y); - - /* set_raw_mouse_mode instructs the front end to start sending mouse events - * in raw mode suitable for translating into mouse-tracking terminal data - * (e.g. include scroll-wheel events and don't bother to identify double- - * and triple-clicks). set_raw_mouse_mode_pointer instructs the front end - * to change the mouse pointer shape to *indicate* raw mouse mode. */ - void (*set_raw_mouse_mode)(TermWin *, bool enable); - void (*set_raw_mouse_mode_pointer)(TermWin *, bool enable); - - void (*set_scrollbar)(TermWin *, int total, int start, int page); - - void (*bell)(TermWin *, int mode); - - void (*clip_write)(TermWin *, int clipboard, wchar_t *text, int *attrs, - truecolour *colours, int len, bool must_deselect); - void (*clip_request_paste)(TermWin *, int clipboard); - - void (*refresh)(TermWin *); - - /* request_resize asks the front end if the terminal can please be - * resized to (w,h) in characters. The front end MAY call - * term_size() in response to tell the terminal its new size - * (which MAY be the requested size, or some other size if the - * requested one can't be achieved). The front end MAY also not - * call term_size() at all. But the front end MUST reply to this - * request by calling term_resize_request_completed(), after the - * responding resize event has taken place (if any). - * - * The calls to term_size and term_resize_request_completed may be - * synchronous callbacks from within the call to request_resize(). */ - void (*request_resize)(TermWin *, int w, int h); - - void (*set_title)(TermWin *, const char *title, int codepage); - void (*set_icon_title)(TermWin *, const char *icontitle, int codepage); - - /* set_minimised and set_maximised are assumed to set two - * independent settings, rather than a single three-way - * {min,normal,max} switch. The idea is that when you un-minimise - * the window it remembers whether to go back to normal or - * maximised. */ - void (*set_minimised)(TermWin *, bool minimised); - void (*set_maximised)(TermWin *, bool maximised); - void (*move)(TermWin *, int x, int y); - void (*set_zorder)(TermWin *, bool top); - - /* Set the colour palette that the TermWin will use to display - * text. One call to this function sets 'ncolours' consecutive - * colours in the OSC 4 sequence, starting at 'start'. */ - void (*palette_set)(TermWin *, unsigned start, unsigned ncolours, - const rgb *colours); - - /* Query the front end for any OS-local overrides to the default - * colours stored in Conf. The front end should set any it cares - * about by calling term_palette_override. - * - * The Terminal object is passed in as a parameter, because this - * can be called as a callback from term_init(). So the TermWin - * itself won't yet have been told where to find its Terminal - * object, because that doesn't happen until term_init - * returns. */ - void (*palette_get_overrides)(TermWin *, Terminal *); - - /* Notify the front end that the terminal's buffer of unprocessed - * output has reduced. (Front ends will likely pass this straight - * on to backend_unthrottle.) */ - void (*unthrottle)(TermWin *, size_t bufsize); -}; - -static inline bool win_setup_draw_ctx(TermWin *win) -{ return win->vt->setup_draw_ctx(win); } -static inline void win_draw_text( - TermWin *win, int x, int y, wchar_t *text, int len, - unsigned long attrs, int line_attrs, truecolour tc) -{ win->vt->draw_text(win, x, y, text, len, attrs, line_attrs, tc); } -static inline void win_draw_cursor( - TermWin *win, int x, int y, wchar_t *text, int len, - unsigned long attrs, int line_attrs, truecolour tc) -{ win->vt->draw_cursor(win, x, y, text, len, attrs, line_attrs, tc); } -static inline void win_draw_trust_sigil(TermWin *win, int x, int y) -{ win->vt->draw_trust_sigil(win, x, y); } -static inline int win_char_width(TermWin *win, int uc) -{ return win->vt->char_width(win, uc); } -static inline void win_free_draw_ctx(TermWin *win) -{ win->vt->free_draw_ctx(win); } -static inline void win_set_cursor_pos(TermWin *win, int x, int y) -{ win->vt->set_cursor_pos(win, x, y); } -static inline void win_set_raw_mouse_mode(TermWin *win, bool enable) -{ win->vt->set_raw_mouse_mode(win, enable); } -static inline void win_set_raw_mouse_mode_pointer(TermWin *win, bool enable) -{ win->vt->set_raw_mouse_mode_pointer(win, enable); } -static inline void win_set_scrollbar(TermWin *win, int t, int s, int p) -{ win->vt->set_scrollbar(win, t, s, p); } -static inline void win_bell(TermWin *win, int mode) -{ win->vt->bell(win, mode); } -static inline void win_clip_write( - TermWin *win, int clipboard, wchar_t *text, int *attrs, - truecolour *colours, int len, bool deselect) -{ win->vt->clip_write(win, clipboard, text, attrs, colours, len, deselect); } -static inline void win_clip_request_paste(TermWin *win, int clipboard) -{ win->vt->clip_request_paste(win, clipboard); } -static inline void win_refresh(TermWin *win) -{ win->vt->refresh(win); } -static inline void win_request_resize(TermWin *win, int w, int h) -{ win->vt->request_resize(win, w, h); } -static inline void win_set_title(TermWin *win, const char *title, int codepage) -{ win->vt->set_title(win, title, codepage); } -static inline void win_set_icon_title(TermWin *win, const char *icontitle, - int codepage) -{ win->vt->set_icon_title(win, icontitle, codepage); } -static inline void win_set_minimised(TermWin *win, bool minimised) -{ win->vt->set_minimised(win, minimised); } -static inline void win_set_maximised(TermWin *win, bool maximised) -{ win->vt->set_maximised(win, maximised); } -static inline void win_move(TermWin *win, int x, int y) -{ win->vt->move(win, x, y); } -static inline void win_set_zorder(TermWin *win, bool top) -{ win->vt->set_zorder(win, top); } -static inline void win_palette_set( - TermWin *win, unsigned start, unsigned ncolours, const rgb *colours) -{ win->vt->palette_set(win, start, ncolours, colours); } -static inline void win_palette_get_overrides(TermWin *win, Terminal *term) -{ win->vt->palette_get_overrides(win, term); } -static inline void win_unthrottle(TermWin *win, size_t size) -{ win->vt->unthrottle(win, size); } - -/* - * Global functions not specific to a connection instance. - */ -void nonfatal(const char *, ...) PRINTF_LIKE(1, 2); -NORETURN void modalfatalbox(const char *, ...) PRINTF_LIKE(1, 2); -NORETURN 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) /* PROT_SSH, PROT_TELNET etc */ \ - X(INT, NONE, addressfamily) /* ADDRTYPE_IPV[46] or ADDRTYPE_UNSPEC */ \ - X(INT, NONE, close_on_exit) /* FORCE_ON, FORCE_OFF, AUTO */ \ - X(BOOL, NONE, warn_on_close) \ - X(INT, NONE, ping_interval) /* in seconds */ \ - X(BOOL, NONE, tcp_nodelay) \ - X(BOOL, 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) /* FORCE_ON, FORCE_OFF, AUTO */ \ - X(BOOL, NONE, even_proxy_localhost) \ - X(INT, NONE, proxy_type) /* PROXY_NONE, PROXY_SOCKS4, ... */ \ - 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) \ - X(INT, NONE, proxy_log_to_term) /* FORCE_ON, FORCE_OFF, AUTO */ \ - /* SSH options */ \ - X(STR, NONE, remote_cmd) \ - X(STR, NONE, remote_cmd2) /* fallback if remote_cmd fails; never loaded or saved */ \ - X(BOOL, NONE, nopty) \ - X(BOOL, NONE, compression) \ - X(INT, INT, ssh_kexlist) \ - X(INT, INT, ssh_hklist) \ - X(BOOL, NONE, ssh_prefer_known_hostkeys) \ - X(INT, NONE, ssh_rekey_time) /* in minutes */ \ - X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \ - X(BOOL, NONE, tryagent) \ - X(BOOL, NONE, agentfwd) \ - X(BOOL, NONE, change_username) /* allow username switching in SSH-2 */ \ - X(INT, INT, ssh_cipherlist) \ - X(FILENAME, NONE, keyfile) \ - X(FILENAME, NONE, detached_cert) \ - X(STR, NONE, auth_plugin) \ - /* \ - * Which SSH protocol to use. \ - * For historical reasons, the current legal values for CONF_sshprot \ - * are: \ - * 0 = SSH-1 only \ - * 3 = SSH-2 only \ - * We used to also support \ - * 1 = SSH-1 with fallback to SSH-2 \ - * 2 = SSH-2 with fallback to SSH-1 \ - * and we continue to use 0/3 in storage formats rather than the more \ - * obvious 1/2 to avoid surprises if someone saves a session and later \ - * downgrades PuTTY. So it's easier to use these numbers internally too. \ - */ \ - X(INT, NONE, sshprot) \ - X(BOOL, NONE, ssh2_des_cbc) /* "des-cbc" unrecommended SSH-2 cipher */ \ - X(BOOL, NONE, ssh_no_userauth) /* bypass "ssh-userauth" (SSH-2 only) */ \ - X(BOOL, NONE, ssh_no_trivial_userauth) /* disable trivial types of auth */ \ - X(BOOL, NONE, ssh_show_banner) /* show USERAUTH_BANNERs (SSH-2 only) */ \ - X(BOOL, NONE, try_tis_auth) \ - X(BOOL, NONE, try_ki_auth) \ - X(BOOL, NONE, try_gssapi_auth) /* attempt gssapi auth via ssh userauth */ \ - X(BOOL, NONE, try_gssapi_kex) /* attempt gssapi auth via ssh kex */ \ - X(BOOL, NONE, gssapifwd) /* forward tgt via gss */ \ - X(INT, NONE, gssapirekey) /* KEXGSS refresh interval (mins) */ \ - X(INT, INT, ssh_gsslist) /* preference order for local GSS libs */ \ - X(FILENAME, NONE, ssh_gss_custom) \ - X(BOOL, NONE, ssh_subsys) /* run a subsystem rather than a command */ \ - X(BOOL, NONE, ssh_subsys2) /* fallback to go with remote_cmd_ptr2 */ \ - X(BOOL, 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(BOOL, NONE, username_from_env) \ - X(STR, NONE, localusername) \ - X(BOOL, NONE, rfc_environ) \ - X(BOOL, 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) /* SER_PAR_NONE, SER_PAR_ODD, ... */ \ - X(INT, NONE, serflow) /* SER_FLOW_NONE, SER_FLOW_XONXOFF, ... */ \ - /* Supdup options */ \ - X(STR, NONE, supdup_location) \ - X(INT, NONE, supdup_ascii_set) \ - X(BOOL, NONE, supdup_more) \ - X(BOOL, NONE, supdup_scroll) \ - /* Keyboard options */ \ - X(BOOL, NONE, bksp_is_delete) \ - X(BOOL, NONE, rxvt_homeend) \ - X(INT, NONE, funky_type) /* FUNKY_XTERM, FUNKY_LINUX, ... */ \ - X(INT, NONE, sharrow_type) /* SHARROW_APPLICATION, SHARROW_BITMAP, ... */ \ - X(BOOL, NONE, no_applic_c) /* totally disable app cursor keys */ \ - X(BOOL, NONE, no_applic_k) /* totally disable app keypad */ \ - X(BOOL, NONE, no_mouse_rep) /* totally disable mouse reporting */ \ - X(BOOL, NONE, no_remote_resize) /* disable remote resizing */ \ - X(BOOL, NONE, no_alt_screen) /* disable alternate screen */ \ - X(BOOL, NONE, no_remote_wintitle) /* disable remote retitling */ \ - X(BOOL, NONE, no_remote_clearscroll) /* disable ESC[3J */ \ - X(BOOL, NONE, no_dbackspace) /* disable destructive backspace */ \ - X(BOOL, NONE, no_remote_charset) /* disable remote charset config */ \ - X(INT, NONE, remote_qtitle_action) /* remote win title query action - * (TITLE_NONE, TITLE_EMPTY, ...) */ \ - X(BOOL, NONE, app_cursor) \ - X(BOOL, NONE, app_keypad) \ - X(BOOL, NONE, nethack_keypad) \ - X(BOOL, NONE, telnet_keyboard) \ - X(BOOL, NONE, telnet_newline) \ - X(BOOL, NONE, alt_f4) /* is it special? */ \ - X(BOOL, NONE, alt_space) /* is it special? */ \ - X(BOOL, NONE, alt_only) /* is it special? */ \ - X(INT, NONE, localecho) /* FORCE_ON, FORCE_OFF, AUTO */ \ - X(INT, NONE, localedit) /* FORCE_ON, FORCE_OFF, AUTO */ \ - X(BOOL, NONE, alwaysontop) \ - X(BOOL, NONE, fullscreenonaltenter) \ - X(BOOL, NONE, scroll_on_key) \ - X(BOOL, NONE, scroll_on_disp) \ - X(BOOL, NONE, erase_to_scrollback) \ - X(BOOL, NONE, compose_key) \ - X(BOOL, NONE, ctrlaltkeys) \ - X(BOOL, NONE, osx_option_meta) \ - X(BOOL, NONE, osx_command_meta) \ - X(STR, NONE, wintitle) /* initial window title */ \ - /* Terminal options */ \ - X(INT, NONE, savelines) \ - X(BOOL, NONE, dec_om) \ - X(BOOL, NONE, wrap_mode) \ - X(BOOL, NONE, lfhascr) \ - X(INT, NONE, cursor_type) /* 0=block 1=underline 2=vertical */ \ - X(BOOL, NONE, blink_cur) \ - X(INT, NONE, beep) /* BELL_DISABLED, BELL_DEFAULT, ... */ \ - X(INT, NONE, beep_ind) /* B_IND_DISABLED, B_IND_FLASH, ... */ \ - X(BOOL, 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(BOOL, NONE, scrollbar) \ - X(BOOL, NONE, scrollbar_in_fullscreen) \ - X(INT, NONE, resize_action) /* RESIZE_TERM, RESIZE_DISABLED, ... */ \ - X(BOOL, NONE, bce) \ - X(BOOL, NONE, blinktext) \ - X(BOOL, NONE, win_name_always) \ - X(INT, NONE, width) \ - X(INT, NONE, height) \ - X(FONT, NONE, font) \ - X(INT, NONE, font_quality) /* FQ_DEFAULT, FQ_ANTIALIASED, ... */ \ - X(FILENAME, NONE, logfilename) \ - X(INT, NONE, logtype) /* LGTYP_NONE, LGTYPE_ASCII, ... */ \ - X(INT, NONE, logxfovr) /* LGXF_OVR, LGXF_APN, LGXF_ASK */ \ - X(BOOL, NONE, logflush) \ - X(BOOL, NONE, logheader) \ - X(BOOL, NONE, logomitpass) \ - X(BOOL, NONE, logomitdata) \ - X(BOOL, NONE, hide_mouseptr) \ - X(BOOL, NONE, sunken_edge) \ - X(INT, NONE, window_border) /* in pixels */ \ - X(STR, NONE, answerback) \ - X(STR, NONE, printer) \ - X(BOOL, NONE, no_arabicshaping) \ - X(BOOL, NONE, no_bidi) \ - /* Colour options */ \ - X(BOOL, NONE, ansi_colour) \ - X(BOOL, NONE, xterm_256_colour) \ - X(BOOL, NONE, true_colour) \ - X(BOOL, NONE, system_colour) \ - X(BOOL, NONE, try_palette) \ - X(INT, NONE, bold_style) /* 1=font 2=colour (3=both) */ \ - X(INT, INT, colours) /* indexed by the CONF_COLOUR_* enum encoding */ \ - /* Selection options */ \ - X(INT, NONE, mouse_is_xterm) /* 0=compromise 1=xterm 2=Windows */ \ - X(BOOL, NONE, rect_select) \ - X(BOOL, NONE, paste_controls) \ - X(BOOL, NONE, rawcnp) \ - X(BOOL, NONE, utf8linedraw) \ - X(BOOL, NONE, rtf_paste) \ - X(BOOL, NONE, mouse_override) \ - X(INT, INT, wordness) \ - X(BOOL, NONE, mouseautocopy) \ - X(INT, NONE, mousepaste) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \ - X(INT, NONE, ctrlshiftins) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \ - X(INT, NONE, ctrlshiftcv) /* CLIPUI_IMPLICIT, CLIPUI_EXPLICIT, ... */ \ - X(STR, NONE, mousepaste_custom) \ - X(STR, NONE, ctrlshiftins_custom) \ - X(STR, NONE, ctrlshiftcv_custom) \ - /* translations */ \ - X(INT, NONE, vtmode) /* VT_XWINDOWS, VT_OEMANSI, ... */ \ - X(STR, NONE, line_codepage) \ - X(BOOL, NONE, cjk_ambig_wide) \ - X(BOOL, NONE, utf8_override) \ - X(BOOL, NONE, xlat_capslockcyr) \ - /* X11 forwarding */ \ - X(BOOL, NONE, x11_forward) \ - X(STR, NONE, x11_display) \ - X(INT, NONE, x11_auth) /* X11_NO_AUTH, X11_MIT, X11_XDM */ \ - X(FILENAME, NONE, xauthfile) \ - /* port forwarding */ \ - X(BOOL, NONE, lport_acceptall) /* accept conns from hosts other than localhost */ \ - X(BOOL, 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. All FORCE_ON/FORCE_OFF/AUTO */ \ - 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) \ - X(INT, NONE, sshbug_dropstart) \ - X(INT, NONE, sshbug_filter_kexinit) \ - /* \ - * 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(BOOL, NONE, ssh_simple) \ - X(BOOL, NONE, ssh_connection_sharing) \ - X(BOOL, NONE, ssh_connection_sharing_upstream) \ - X(BOOL, 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(BOOL, NONE, stamp_utmp) \ - X(BOOL, NONE, login_shell) \ - X(BOOL, NONE, scrollbar_on_left) \ - X(BOOL, NONE, shadowbold) \ - X(FONT, NONE, boldfont) \ - X(FONT, NONE, widefont) \ - X(FONT, NONE, wideboldfont) \ - X(INT, NONE, shadowboldoffset) /* in pixels */ \ - X(BOOL, NONE, crhaslf) \ - X(STR, NONE, winclass) \ - /* end of list */ - -/* 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 - -/* 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. */ -bool conf_get_bool(Conf *conf, int key); -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_bool(Conf *conf, int key, bool value); -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 */ -void conf_serialise(BinarySink *bs, Conf *conf); -bool conf_deserialise(Conf *conf, BinarySource *src);/*returns true on success*/ - -/* - * Functions to copy, free, serialise and deserialise FontSpecs. - * Provided per-platform, to go with the platform's idea of a - * FontSpec's contents. - */ -FontSpec *fontspec_copy(const FontSpec *f); -void fontspec_free(FontSpec *f); -void fontspec_serialise(BinarySink *bs, FontSpec *f); -FontSpec *fontspec_deserialise(BinarySource *src); - -/* - * Exports from each platform's noise.c. - */ -typedef enum NoiseSourceId { - NOISE_SOURCE_TIME, - NOISE_SOURCE_IOID, - NOISE_SOURCE_IOLEN, - NOISE_SOURCE_KEY, - NOISE_SOURCE_MOUSEBUTTON, - NOISE_SOURCE_MOUSEPOS, - NOISE_SOURCE_MEMINFO, - NOISE_SOURCE_STAT, - NOISE_SOURCE_RUSAGE, - NOISE_SOURCE_FGWINDOW, - NOISE_SOURCE_CAPTURE, - NOISE_SOURCE_CLIPBOARD, - NOISE_SOURCE_QUEUE, - NOISE_SOURCE_CURSORPOS, - NOISE_SOURCE_THREADTIME, - NOISE_SOURCE_PROCTIME, - NOISE_SOURCE_PERFCOUNT, - NOISE_MAX_SOURCES -} NoiseSourceId; -void noise_get_heavy(void (*func) (void *, int)); -void noise_get_light(void (*func) (void *, int)); -void noise_regular(void); -void noise_ultralight(NoiseSourceId id, unsigned long data); - -/* - * Exports from sshrand.c. - */ -void random_save_seed(void); -void random_destroy_seed(void); - -/* - * Exports from settings.c. - * - * load_settings() and do_defaults() return false if the provided - * session name didn't actually exist. But they still fill in the - * provided Conf with _something_. - */ -const struct BackendVtable *backend_vt_from_name(const char *name); -const struct BackendVtable *backend_vt_from_proto(int proto); -char *get_remote_username(Conf *conf); /* dynamically allocated */ -char *save_settings(const char *section, Conf *conf); -void save_open_settings(settings_w *sesskey, Conf *conf); -bool load_settings(const char *section, Conf *conf); -void load_open_settings(settings_r *sesskey, Conf *conf); -void get_sesslist(struct sesslist *, bool allocate); -bool do_defaults(const char *, Conf *); -void registry_cleanup(void); -void settings_set_default_protocol(int); -void settings_set_default_port(int); - -/* - * 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); -bool platform_default_b(const char *name, bool def); -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 *, TermWin *); -void term_free(Terminal *); -void term_size(Terminal *, int, int, int); -void term_resize_request_completed(Terminal *); -void term_paint(Terminal *, int, int, int, int, bool); -void term_scroll(Terminal *, int, int); -void term_scroll_to_selection(Terminal *, int); -void term_pwron(Terminal *, bool); -void term_clrsb(Terminal *); -void term_mouse(Terminal *, Mouse_Button, Mouse_Button, Mouse_Action, - int, int, bool, bool, bool); -void term_cancel_selection_drag(Terminal *); -void term_key(Terminal *, Key_Sym, wchar_t *, size_t, unsigned int, - unsigned int); -void term_lost_clipboard_ownership(Terminal *, int clipboard); -void term_update(Terminal *); -void term_invalidate(Terminal *); -void term_blink(Terminal *, bool set_cursor); -void term_do_paste(Terminal *, const wchar_t *, int); -void term_nopaste(Terminal *); -void term_copyall(Terminal *, const int *, int); -void term_pre_reconfig(Terminal *, Conf *); -void term_reconfig(Terminal *, Conf *); -void term_request_copy(Terminal *, const int *clipboards, int n_clipboards); -void term_request_paste(Terminal *, int clipboard); -void term_seen_key_event(Terminal *); -size_t term_data(Terminal *, const void *data, size_t len); -void term_provide_backend(Terminal *term, Backend *backend); -void term_provide_logctx(Terminal *term, LogContext *logctx); -void term_set_focus(Terminal *term, bool has_focus); -char *term_get_ttymode(Terminal *term, const char *mode); -SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p); -void term_set_trust_status(Terminal *term, bool trusted); -void term_keyinput(Terminal *, int codepage, const void *buf, int len); -void term_keyinputw(Terminal *, const wchar_t *widebuf, int len); -void term_get_cursor_position(Terminal *term, int *x, int *y); -void term_setup_window_titles(Terminal *term, const char *title_hostname); -void term_notify_minimised(Terminal *term, bool minimised); -void term_notify_palette_changed(Terminal *term); -void term_notify_window_pos(Terminal *term, int x, int y); -void term_notify_window_size_pixels(Terminal *term, int x, int y); -void term_palette_override(Terminal *term, unsigned osc4_index, rgb rgb); - -typedef enum SmallKeypadKey { - SKK_HOME, SKK_END, SKK_INSERT, SKK_DELETE, SKK_PGUP, SKK_PGDN, -} SmallKeypadKey; -int format_arrow_key(char *buf, Terminal *term, int xkey, - bool shift, bool ctrl, bool alt, bool *consumed_alt); -int format_function_key(char *buf, Terminal *term, int key_number, - bool shift, bool ctrl, bool alt, bool *consumed_alt); -int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key, - bool shift, bool ctrl, bool alt, - bool *consumed_alt); -int format_numeric_keypad_key(char *buf, Terminal *term, char key, - bool shift, bool ctrl); - -/* - * Exports from logging.c. - */ -struct LogPolicyVtable { - /* - * Pass Event Log entries on from LogContext to the front end, - * which might write them to standard error or save them for a GUI - * list box or other things. - */ - void (*eventlog)(LogPolicy *lp, const char *event); - - /* - * Ask what to do about the specified output log file already - * existing. 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, and callback() will be called with one - * of those options. - */ - int (*askappend)(LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx); - - /* - * Emergency logging when the log file itself can't be opened, - * which typically means we want to shout about it more loudly - * than a mere Event Log entry. - * - * One reasonable option is to send 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). In many cases this - * is unlikely to cause this error message 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 :-) - */ - void (*logging_error)(LogPolicy *lp, const char *event); - - /* - * Ask whether extra verbose log messages are required. - */ - bool (*verbose)(LogPolicy *lp); -}; -struct LogPolicy { - const LogPolicyVtable *vt; -}; - -static inline void lp_eventlog(LogPolicy *lp, const char *event) -{ lp->vt->eventlog(lp, event); } -static inline int lp_askappend( - LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) -{ return lp->vt->askappend(lp, filename, callback, ctx); } -static inline void lp_logging_error(LogPolicy *lp, const char *event) -{ lp->vt->logging_error(lp, event); } -static inline bool lp_verbose(LogPolicy *lp) -{ return lp->vt->verbose(lp); } - -/* Defined in clicons.c, used in several console command-line tools */ -extern LogPolicy console_cli_logpolicy[]; - -int console_askappend(LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx); -void console_logging_error(LogPolicy *lp, const char *string); -void console_eventlog(LogPolicy *lp, const char *string); -bool null_lp_verbose_yes(LogPolicy *lp); -bool null_lp_verbose_no(LogPolicy *lp); -bool cmdline_lp_verbose(LogPolicy *lp); - -LogContext *log_init(LogPolicy *lp, Conf *conf); -void log_free(LogContext *logctx); -void log_reconfig(LogContext *logctx, Conf *conf); -void logfopen(LogContext *logctx); -void logfclose(LogContext *logctx); -void logtraffic(LogContext *logctx, unsigned char c, int logmode); -void logflush(LogContext *logctx); -LogPolicy *log_get_policy(LogContext *logctx); -void logevent(LogContext *logctx, const char *event); -void logeventf(LogContext *logctx, const char *fmt, ...) PRINTF_LIKE(2, 3); -void logeventvf(LogContext *logctx, const char *fmt, va_list ap); - -/* - * Pass a dynamically allocated string to logevent and immediately - * free it. Intended for use by wrapper macros which pass the return - * value of dupprintf straight to this. - */ -void logevent_and_free(LogContext *logctx, char *event); -enum { PKT_INCOMING, PKT_OUTGOING }; -enum { PKTLOG_EMIT, PKTLOG_BLANK, PKTLOG_OMIT }; -struct logblank_t { - int offset; - int len; - int type; -}; -void log_packet(LogContext *logctx, int direction, int type, - const char *texttype, const void *data, size_t 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 const struct BackendVtable null_backend; -extern const struct BackendVtable loop_backend; - -/* - * Exports from raw.c. - */ - -extern const struct BackendVtable raw_backend; - -/* - * Exports from rlogin.c. - */ - -extern const struct BackendVtable rlogin_backend; - -/* - * Exports from telnet.c. - */ - -extern const struct BackendVtable telnet_backend; - -/* - * Exports from ssh/ssh.c. - */ -extern const struct BackendVtable ssh_backend; -extern const struct BackendVtable sshconn_backend; - -/* - * Exports from supdup.c. - */ -extern const struct BackendVtable supdup_backend; - -/* - * Exports from ldisc.c. - */ -Ldisc *ldisc_create(Conf *, Terminal *, Backend *, Seat *); -void ldisc_configure(Ldisc *, Conf *); -void ldisc_free(Ldisc *); -void ldisc_send(Ldisc *, const void *buf, int len, bool interactive); -void ldisc_echoedit_update(Ldisc *); -typedef struct LdiscInputToken { - /* - * Structure that encodes any single item of data that Ldisc can - * buffer: either a single character of raw data, or a session - * special. - */ - bool is_special; - union { - struct { - /* if is_special == false */ - char chr; - }; - struct { - /* if is_special == true */ - SessionSpecialCode code; - int arg; - }; - }; -} LdiscInputToken; -bool ldisc_has_input_buffered(Ldisc *); -LdiscInputToken ldisc_get_input_token(Ldisc *); /* asserts there is input */ -void ldisc_enable_prompt_callback(Ldisc *, prompts_t *); -void ldisc_check_sendok(Ldisc *); - -/* - * Exports from sshrand.c. - */ - -void random_add_noise(NoiseSourceId source, const void *noise, int length); -void random_read(void *buf, size_t size); -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); -/* random_clear is equivalent to calling random_unref as many times as - * necessary to shut down the global PRNG instance completely. It's - * not needed in normal applications, but the command-line PuTTYgen - * test finds it useful to clean up after each invocation of the - * logical main() no matter whether it needed random numbers or - * not. */ -void random_clear(void); -/* random_setup_custom sets up the process-global random number - * generator specially, with a hash function of your choice. */ -void random_setup_custom(const ssh_hashalg *hash); -/* random_setup_special() is a macro wrapper on that, which makes an - * extra-big one based on the largest hash function we have. It's - * defined this way to avoid what would otherwise be an unnecessary - * module dependency from sshrand.c to a hash function implementation. */ -#define random_setup_special() random_setup_custom(&ssh_shake256_114bytes) -/* Manually drop a random seed into the random number generator, e.g. - * just before generating a key. */ -void random_reseed(ptrlen seed); -/* Limit on how much entropy is worth putting into the generator (bits). */ -size_t random_seed_bits(void); - -/* - * Exports from pinger.c. - */ -typedef struct Pinger Pinger; -Pinger *pinger_new(Conf *conf, Backend *backend); -void pinger_reconfig(Pinger *, Conf *oldconf, Conf *newconf); -void pinger_free(Pinger *); - -/* - * Exports from modules in utils. - */ - -#include "misc.h" -bool conf_launchable(Conf *conf); -char const *conf_dest(Conf *conf); - -/* - * Exports from sessprep.c. - */ -void prepare_session(Conf *conf); - -/* - * Exports from version.c and cmake_commit.c. - */ -extern const char ver[]; -extern const char commitid[]; - -/* - * Exports from unicode.c in platform subdirs. - */ -#ifndef CP_UTF8 -#define CP_UTF8 65001 -#endif -/* void init_ucs(void); -- this is now in platform-specific headers */ -bool 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, const char *defchr); -wchar_t xlat_uskbd2cyrllic(int ch); -int check_compose(int first, int second); -int decode_codepage(const 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 agent-client.c in platform subdirs. - * - * agent_query returns NULL for here's-a-response, and non-NULL 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. - * - * When the return from agent_query is not NULL, it identifies the - * in-progress query in case it needs to be cancelled. If - * agent_cancel_query is called, then the pending query is destroyed - * and the callback will not be called. (E.g. if you're going to throw - * away the thing you were using as callback_ctx.) - * - * Passing a null pointer as callback forces agent_query to behave - * synchronously, i.e. it will block if necessary, and guarantee to - * return NULL. The wrapper function agent_query_synchronous() - * (defined in its own module aqsync.c) makes this easier. - */ -typedef struct agent_pending_query agent_pending_query; -agent_pending_query *agent_query( - strbuf *in, void **out, int *outlen, - void (*callback)(void *, void *, int), void *callback_ctx); -void agent_cancel_query(agent_pending_query *); -void agent_query_synchronous(strbuf *in, void **out, int *outlen); -bool agent_exists(void); - -/* For stream-oriented agent connections, if available. */ -Socket *agent_connect(Plug *plug); - -/* - * Exports from wildcard.c - */ -const char *wc_error(int value); -int wc_match_pl(const char *wildcard, ptrlen target); -int wc_match(const char *wildcard, const char *target); -bool wc_unescape(char *output, const char *wildcard); - -/* - * Exports from frontend (dialog.c etc) - */ -void pgp_fingerprints(void); -/* - * have_ssh_host_key() just returns true if a key of that type is - * already cached and false otherwise. - */ -bool have_ssh_host_key(const char *host, int port, const char *keytype); - -/* - * Exports from console frontends (console.c in platform subdirs) - * that aren't equivalents to things in windlg.c et al. - */ -extern bool console_batch_mode, console_antispoof_prompt; -SeatPromptResult console_get_userpass_input(prompts_t *p); -bool is_interactive(void); -void console_print_error_msg(const char *prefix, const char *msg); -void console_print_error_msg_fmt_v( - const char *prefix, const char *fmt, va_list ap); -void console_print_error_msg_fmt(const char *prefix, const char *fmt, ...) - PRINTF_LIKE(2, 3); - -/* - * Exports from printing.c in platform subdirs. - */ -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 *, const void *, size_t); -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). - * - * Note that cmdline_process_param takes a const option string, but a - * writable argument string. That's not a mistake - that's so it can - * zero out password arguments in the hope of not having them show up - * avoidably in Unix 'ps'. - */ -struct cmdline_get_passwd_input_state { bool tried; }; -#define CMDLINE_GET_PASSWD_INPUT_STATE_INIT { .tried = false } -extern const cmdline_get_passwd_input_state cmdline_get_passwd_input_state_new; - -int cmdline_process_param(const char *, char *, int, Conf *); -void cmdline_run_saved(Conf *); -void cmdline_cleanup(void); -SeatPromptResult cmdline_get_passwd_input( - prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable); -bool cmdline_host_ok(Conf *); -bool cmdline_verbose(void); -bool cmdline_loaded_session(void); - -/* - * Here we have a flags word provided by each tool, which describes - * the capabilities of that tool that cmdline.c needs to know about. - * It 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/poll infrastructure to make them work). - */ -extern const unsigned cmdline_tooltype; - -/* Bit flags for the above */ -#define TOOLTYPE_LIST(X) \ - X(TOOLTYPE_FILETRANSFER) \ - X(TOOLTYPE_NONNETWORK) \ - X(TOOLTYPE_HOST_ARG) \ - X(TOOLTYPE_HOST_ARG_CAN_BE_SESSION) \ - X(TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX) \ - X(TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD) \ - X(TOOLTYPE_PORT_ARG) \ - X(TOOLTYPE_NO_VERBOSE_OPTION) \ - /* end of list */ -#define BITFLAG_INDEX(val) val ## _bitflag_index, -enum { TOOLTYPE_LIST(BITFLAG_INDEX) }; -#define BITFLAG_DEF(val) val = 1U << (val ## _bitflag_index), -enum { TOOLTYPE_LIST(BITFLAG_DEF) }; - -void cmdline_error(const char *, ...) PRINTF_LIKE(1, 2); - -/* - * Exports from config.c. - */ -struct controlbox; -void conf_radiobutton_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event); -#define CHECKBOX_INVERT (1<<30) -void conf_checkbox_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event); -void conf_editbox_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event); -void conf_filesel_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event); -void conf_fontsel_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event); - -struct conf_editbox_handler_type { - /* Structure passed as context2 to conf_editbox_handler */ - enum { EDIT_STR, EDIT_INT, EDIT_FIXEDPOINT } type; - union { - /* - * EDIT_STR means the edit box is connected to a string - * field in Conf. No further parameters needed. - */ - - /* - * EDIT_INT means the edit box is connected to an int field in - * Conf, and the input string is interpreted as decimal. No - * further parameters needed. (But we could add one here later - * if for some reason we wanted int fields in hex.) - */ - - /* - * EDIT_FIXEDPOINT means the edit box is connected to an int - * field in Conf, but the input string is interpreted as - * _floating point_, and converted to/from the output int by - * means of a fixed denominator. That is, - * - * (floating value in edit box) * denominator = value in Conf - */ - struct { - double denominator; - }; - }; -}; - -extern const struct conf_editbox_handler_type conf_editbox_str; -extern const struct conf_editbox_handler_type conf_editbox_int; -#define ED_STR CP(&conf_editbox_str) -#define ED_INT CP(&conf_editbox_int) - -void setup_config_box(struct controlbox *b, bool midsession, - int protocol, int protcfginfo); - -void setup_ca_config_box(struct controlbox *b); - -/* Platforms provide this to be called from config.c */ -void show_ca_config_box(dlgparam *dlg); -extern const bool has_ca_config_box; /* false if, e.g., we're PuTTYtel */ - -/* Visible outside config.c so that platforms can use it to recognise - * the proxy type control */ -void proxy_type_handler(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event); -/* And then they'll set this flag in its generic.context.i */ -#define PROXY_UI_FLAG_LOCAL 1 /* has a local proxy */ - -/* - * Exports from bidi.c. - */ -#define BIDI_CHAR_INDEX_NONE ((unsigned short)-1) -typedef struct bidi_char { - unsigned int origwc, wc; - unsigned short index, nchars; -} bidi_char; -BidiContext *bidi_new_context(void); -void bidi_free_context(BidiContext *ctx); -void do_bidi(BidiContext *ctx, bidi_char *line, size_t count); -int do_shape(bidi_char *line, bidi_char *to, int count); -bool 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[X11_NAUTHS]; - -/* - * An enum for the copy-paste UI action configuration. - */ -enum { - CLIPUI_NONE, /* UI action has no copy/paste effect */ - CLIPUI_IMPLICIT, /* use the default clipboard implicit in mouse actions */ - CLIPUI_EXPLICIT, /* use the default clipboard for explicit Copy/Paste */ - CLIPUI_CUSTOM, /* use a named clipboard (on systems that support it) */ -}; - -/* - * 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); -bool filename_equal(const Filename *f1, const Filename *f2); -bool filename_is_null(const Filename *fn); -Filename *filename_copy(const Filename *fn); -void filename_free(Filename *fn); -void filename_serialise(BinarySink *bs, const Filename *f); -Filename *filename_deserialise(BinarySource *src); -char *get_username(void); /* return value needs freeing */ -char *get_random_data(int bytes, const char *device); /* used in cmdgen.c */ -char filename_char_sanitise(char c); /* rewrite special pathname chars */ -bool open_for_write_would_lose_data(const Filename *fn); - -/* - * 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 poll() 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); -bool run_timers(unsigned long now, unsigned long *next); -void timer_change_notify(unsigned long next); -unsigned long timing_last_clock(void); - -/* - * 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 poll() - * call) then it can call toplevel_callback_pending(), which will - * return true if at least one callback is in the queue. - * - * run_toplevel_callbacks() returns true if it ran any actual code. - * This can be used as a means of speculatively terminating a poll - * loop, as in PSFTP, for example - if a callback has run then perhaps - * it might have done whatever the loop's caller was waiting for. - */ -void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx); -bool run_toplevel_callbacks(void); -bool toplevel_callback_pending(void); -void delete_callbacks_for_context(void *ctx); - -/* - * Another facility in callback.c deals with 'idempotent' callbacks, - * defined as those which never need to be scheduled again if they are - * already scheduled and have not yet run. (An example would be one - * which, when called, empties a queue of data completely: when data - * is added to the queue, you must ensure a run of the queue-consuming - * function has been scheduled, but if one is already pending, you - * don't need to schedule a second one.) - */ -struct IdempotentCallback { - toplevel_callback_fn_t fn; - void *ctx; - bool queued; -}; -void queue_idempotent_callback(struct IdempotentCallback *ic); - -typedef void (*toplevel_callback_notify_fn_t)(void *ctx); -void request_callback_notifications(toplevel_callback_notify_fn_t notify, - void *ctx); - -/* - * Facility provided by the platform to spawn a parallel subprocess - * and present its stdio via a Socket. - * - * 'prefix' indicates the prefix that should appear on messages passed - * to plug_log to provide stderr output from the process. - */ -Socket *platform_start_subprocess(const char *cmd, Plug *plug, - const char *prefix); - -/* - * 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 */ -#ifndef HIGH_SURROGATE_START /* in some toolchains defines these */ -#define HIGH_SURROGATE_START 0xd800 -#define HIGH_SURROGATE_END 0xdbff -#define LOW_SURROGATE_START 0xdc00 -#define LOW_SURROGATE_END 0xdfff -#endif - -/* 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)) - -#endif diff --git a/puttymem.h b/puttymem.h deleted file mode 100644 index 11c1f83ae..000000000 --- a/puttymem.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * PuTTY memory-handling header. - */ - -#ifndef PUTTY_PUTTYMEM_H -#define PUTTY_PUTTYMEM_H - -#include /* for size_t */ -#include /* for memcpy() */ - -#include "defs.h" - -#define smalloc(z) safemalloc(z,1,0) -#define snmalloc safemalloc -#define srealloc(y,z) saferealloc(y,z,1) -#define snrealloc saferealloc -#define sfree safefree - -void *safemalloc(size_t factor1, size_t factor2, size_t addend); -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. sresize also uses - * TYPECHECK to verify that the _input_ pointer is a pointer to the - * correct type. - */ -#define snew(type) ((type *)snmalloc(1, sizeof(type), 0)) -#define snewn(n, type) ((type *)snmalloc((n), sizeof(type), 0)) -#define sresize(ptr, n, type) TYPECHECK((type *)0 == (ptr), \ - ((type *)snrealloc((ptr), (n), sizeof(type)))) - -/* - * For cases where you want to allocate a struct plus a subsidiary - * data buffer in one step, this macro lets you add a constant to the - * amount malloced. - * - * Since the return value is already cast to the struct type, a - * pointer to that many bytes of extra data can be conveniently - * obtained by simply adding 1 to the returned pointer! - * snew_plus_get_aux is a handy macro that does that and casts the - * result to void *, so you can assign it straight to wherever you - * wanted it. - */ -#define snew_plus(type, extra) ((type *)snmalloc(1, sizeof(type), (extra))) -#define snew_plus_get_aux(ptr) ((void *)((ptr) + 1)) - -/* - * Helper macros to deal with the common use case of growing an array. - * - * The common setup is that 'array' is a pointer to the first element - * of a dynamic array of some type, and 'size' represents the current - * allocated size of that array (in elements). Both of those macro - * parameters are implicitly written back to. - * - * Then sgrowarray(array, size, n) means: make sure the nth element of - * the array exists (i.e. the size is at least n+1). You call that - * before writing to the nth element, if you're looping round - * appending to the array. - * - * If you need to grow the array by more than one element, you can - * instead call sgrowarrayn(array, size, n, m), which will ensure the - * size of the array is at least n+m. (So sgrowarray is just the - * special case of that in which m == 1.) - * - * It's common to call sgrowarrayn with one of n,m equal to the - * previous logical length of the array, and the other equal to the - * new number of logical entries you want to add, so that n <= size on - * entry. But that's not actually a mandatory precondition: the two - * length parameters are just arbitrary integers that get added - * together with an initial check for overflow, and the semantics are - * simply 'make sure the array is big enough to take their sum, no - * matter how big it was to start with'.) - * - * Another occasionally useful idiom is to call sgrowarray with n == - * size, i.e. sgrowarray(array, size, size). That just means: make - * array bigger by _some_ amount, I don't particularly mind how much. - * You might use that style if you were repeatedly calling an API - * function outside your control, which would either fill your buffer - * and return success, or else return a 'too big' error without - * telling you how much bigger it needed to be. - * - * The _nm variants of the macro set the 'private' flag in the - * underlying function, which forces array resizes to be done by a - * manual allocate/copy/free instead of realloc, with careful clearing - * of the previous memory block before we free it. This costs - * performance, but if the block contains important secrets such as - * private keys or passwords, it avoids the risk that a realloc that - * moves the memory block might leave a copy of the data visible in - * the freed memory at the previous location. - */ -void *safegrowarray(void *array, size_t *size, size_t eltsize, - size_t oldlen, size_t extralen, bool private); - -/* The master macro wrapper, of which all others are special cases */ -#define sgrowarray_general(array, size, n, m, priv) \ - ((array) = safegrowarray(array, &(size), sizeof(*array), n, m, priv)) - -/* The special-case macros that are easier to use in most situations */ -#define sgrowarrayn( a, s, n, m) sgrowarray_general(a, s, n, m, false) -#define sgrowarray( a, s, n ) sgrowarray_general(a, s, n, 1, false) -#define sgrowarrayn_nm(a, s, n, m) sgrowarray_general(a, s, n, m, true ) -#define sgrowarray_nm( a, s, n ) sgrowarray_general(a, s, n, 1, true ) - -/* - * This function is called by the innermost safemalloc/saferealloc - * functions when allocation fails. Usually it's provided by an - * implementation in utils, which ties it into an application's - * existing modalfatalbox() system, but standalone test applications - * can reimplement it some other way if they prefer. - */ -NORETURN void out_of_memory(void); - -#ifdef MINEFIELD -/* - * Definitions for Minefield, PuTTY's own Windows-specific malloc - * debugger in the style of Electric Fence. Implemented in - * windows/utils/minefield.c, and referred to by the main malloc - * wrappers in memory.c. - */ -void *minefield_c_malloc(size_t size); -void minefield_c_free(void *p); -void *minefield_c_realloc(void *p, size_t size); -#endif - -#endif diff --git a/release.pl b/release.pl deleted file mode 100644 index f9507a13a..000000000 --- a/release.pl +++ /dev/null @@ -1,223 +0,0 @@ -#!/usr/bin/perl - -# Script to automate some easy-to-mess-up parts of the PuTTY release -# procedure. - -use strict; -use warnings; -use Getopt::Long; -use File::Find; -use File::Temp qw/tempdir/; -use LWP::UserAgent; - -my $version = undef; -my $setver = 0; -my $upload = 0; -my $precheck = 0; -my $postcheck = 0; -GetOptions("version=s" => \$version, - "setver" => \$setver, - "upload" => \$upload, - "precheck" => \$precheck, - "postcheck" => \$postcheck) - or &usage(); - -# --setver: construct a local commit which updates the version -# number, and the command-line help transcripts in the docs. -if ($setver) { - defined $version or die "use --version"; - 0 == system "git", "diff-index", "--quiet", "--cached", "HEAD" - or die "index is dirty"; - 0 == system "git", "diff-files", "--quiet" or die "working tree is dirty"; - my $builddir = tempdir(DIR => ".", CLEANUP => 1); - 0 == system "git archive --format=tar HEAD | ( cd $builddir && tar xf - )" - or die; - 0 == system "cd $builddir && cmake . -DCMAKE_C_FLAGS=-DRELEASE=${version}" or die; - 0 == system "cd $builddir && cmake --build . -t pscp -t plink -j" or die; - our $pscp_transcript = `cd $builddir && ./pscp --help`; - $pscp_transcript =~ s/^Unidentified build/Release ${version}/m or die; - $pscp_transcript =~ s/^/\\c /mg; - our $plink_transcript = `cd $builddir && ./plink --help`; - $plink_transcript =~ s/^Unidentified build/Release ${version}/m or die; - $plink_transcript =~ s/^/\\c /mg; - &transform("LATEST.VER", sub { s/^\d+\.\d+$/$version/ }); - our $transforming = 0; - &transform("doc/pscp.but", sub { - if (/^\\c.*>pscp$/) { $transforming = 1; $_ .= $pscp_transcript; } - elsif (!/^\\c/) { $transforming = 0; } - elsif ($transforming) { $_=""; } - }); - $transforming = 0; - &transform("doc/plink.but", sub { - if (/^\\c.*>plink$/) { $transforming = 1; $_ .= $plink_transcript; } - elsif (!/^\\c/) { $transforming = 0; } - elsif ($transforming) { $_=""; } - }); - &transform("Buildscr", sub { - s!^(set Epoch )\d+!$1 . sprintf "%d", time/86400 - 1000!e }); - 0 == system ("git", "commit", "-a", "-m", - "Update version number for ${version} release.") or die; - exit 0; -} - -# --upload: upload the release to all the places it should live, and -# check all signatures and md5sums once it arrives there. -if ($upload) { - defined $version or die "use --version"; - - # Run this inside the build.out directory. - -d "maps" or die "no maps directory in cwd"; - -d "putty" or die "no putty directory in cwd"; - - 0 == system("rsync", "-av", "maps/", - "thyestes:src/putty-local/maps-$version") - or die "could not upload link maps"; - - for my $location (["thyestes", "www/putty/$version"], - ["the", "www/putty/$version"]) { - my ($host, $path) = @$location; - 0 == system("rsync", "-av", "putty/", "$host:$path") - or die "could not upload release to $host"; - open my $pipe, "|-", "ssh", $host, "cd $path && sh"; - print $pipe "set -e\n"; - print $pipe "pwd\n"; - find({ wanted => sub - { - if (m!^putty/(.*).gpg!) { - my $file = $1; - print $pipe "echo verifying $file\n"; - if ($file =~ /sums$/) { - print $pipe "gpg --verify $file.gpg\n"; - } else { - print $pipe "gpg --verify $file.gpg $file\n"; - } - } elsif (m!^putty/(.*sum)s!) { - print $pipe "echo checking ${1}s\n"; - print $pipe "grep -vF ' (installer version)' ${1}s | grep . | $1 -c\n"; - } - }, no_chdir => 1}, "putty"); - print $pipe "echo all verified ok\n"; - close $pipe; - die "VERIFICATION FAILED on $host" if $? != 0; - } - - print "Uploaded $version OK!\n"; - exit 0; -} - -# --precheck and --postcheck: attempt to download the release from its -# various locations. -if ($precheck || $postcheck) { - defined $version or die "use --version"; - - # Run this inside the build.out directory, so we can check the - # downloaded files against the exact contents they should have. - -d "putty" or die "no putty directory in cwd"; - - my $httpprefix = "https://the.earth.li/~sgtatham/putty/"; - - # Go through all the files in build.out. - find({ wanted => sub - { - if (-f $_) { - die unless (m!^putty/(.*)$!); - my $path = $1; - - # Don't try to check .htaccess - web servers will - # treat it weirdly. - return if $path =~ m!^(.*/)?.htaccess$!; - - print "Checking $path\n"; - - my $real_content = ""; - open my $fh, "<", $_ or die "$_: open local file: $!"; - $real_content .= $_ while <$fh>; - close $fh; - - my $http_numbered = "${httpprefix}$version/$path"; - my $http_latest = "${httpprefix}latest/$path"; - - my $http_uri; - - if ($precheck) { - # Before the 'latest' links/redirects update, - # we just download from explicitly version- - # numbered URLs. - $http_uri = $http_numbered; - } - if ($postcheck) { - # After 'latest' is updated, we're testing that - # the redirects work, so we download from the - # URLs with 'latest' in them. - $http_uri = $http_latest; - } - - # Now test-download the files themselves. - my $ua = LWP::UserAgent->new; - my $httpresponse = $ua->get($http_uri); - my $httpdata = $httpresponse->{_content}; - printf " got %d bytes via HTTP", length $httpdata; - die "HTTP download for $http_uri did not match" - if $httpdata ne $real_content; - print ", ok\n"; - - # Check content types on any files likely to go - # wrong. - my $ct = $httpresponse->{_headers}->{"content-type"}; - if (defined $ct) { - printf " got content-type %s", $ct; - } else { - printf " got no content-type"; - } - my $right_ct = undef; - if ($path =~ m/\.(hlp|cnt|chm)$/) { - $right_ct = "application/octet-stream"; - } elsif ($path =~ /\.gpg$/) { - $right_ct = "application/pgp-signature"; - } - if (defined $right_ct) { - if ($ct ne $right_ct) { - die "content-type $ct should be $right_ct"; - } else { - print ", ok\n"; - } - } else { - print "\n"; - } - - if ($postcheck) { - # Finally, if we're testing the 'latest' URL, - # also check that the HTTP redirect header was - # present and correct. - my $redirected = $httpresponse->{_request}->{_uri}; - printf " redirect -> %s\n", $redirected; - die "redirect header wrong for $http_uri" - if $redirected ne $http_numbered; - } - } - }, no_chdir => 1}, "putty"); - - print "Check OK\n"; - exit 0; -} - -&usage(); - -sub transform { - my ($filename, $proc) = @_; - my $file; - open $file, "<", $filename or die "$file: open for read: $!\n"; - my $data = ""; - while (<$file>) { - $proc->(); - $data .= $_; - } - close $file; - open $file, ">", $filename or die "$file: open for write: $!\n"; - print $file $data; - close $file or die "$file: close after write: $!\n";; -} - -sub usage { - die "usage: release.pl --set-version=X.YZ\n"; -} diff --git a/settings.c b/settings.c deleted file mode 100644 index cd286eb4d..000000000 --- a/settings.c +++ /dev/null @@ -1,1389 +0,0 @@ -/* - * settings.c: read and write saved sessions. (platform-independent) - */ - -#include -#include -#include -#include "putty.h" -#include "storage.h" -#ifndef NO_GSSAPI -#include "ssh/gssc.h" -#include "ssh/gss.h" -#endif - - -/* The cipher order given here is the default order. */ -static const struct keyvalwhere ciphernames[] = { - { "aes", CIPHER_AES, -1, -1 }, - { "chacha20", CIPHER_CHACHA20, CIPHER_AES, +1 }, - { "aesgcm", CIPHER_AESGCM, CIPHER_CHACHA20, +1 }, - { "3des", CIPHER_3DES, -1, -1 }, - { "WARN", CIPHER_WARN, -1, -1 }, - { "des", CIPHER_DES, -1, -1 }, - { "blowfish", CIPHER_BLOWFISH, -1, -1 }, - { "arcfour", CIPHER_ARCFOUR, -1, -1 }, -}; - -/* The default order here is sometimes overridden by the backward- - * compatibility warts in load_open_settings(), and should be kept - * in sync with those. */ -static const struct keyvalwhere kexnames[] = { - { "ntru-curve25519", KEX_NTRU_HYBRID, -1, +1 }, - { "ecdh", KEX_ECDH, -1, +1 }, - /* This name is misleading: it covers both SHA-256 and SHA-1 variants */ - { "dh-gex-sha1", KEX_DHGEX, -1, -1 }, - /* Again, this covers both SHA-256 and SHA-1, despite the name: */ - { "dh-group14-sha1", KEX_DHGROUP14, -1, -1 }, - /* This one really is only SHA-1, though: */ - { "dh-group1-sha1", KEX_DHGROUP1, KEX_WARN, +1 }, - { "rsa", KEX_RSA, KEX_WARN, -1 }, - /* Larger fixed DH groups: prefer the larger 15 and 16 over 14, - * but by default the even larger 17 and 18 go below 16. - * Rationale: diminishing returns of improving the DH strength are - * outweighed by increased CPU cost. Group 18 is painful on a slow - * machine. Users can override if they need to. */ - { "dh-group15-sha512", KEX_DHGROUP15, KEX_DHGROUP14, -1 }, - { "dh-group16-sha512", KEX_DHGROUP16, KEX_DHGROUP15, -1 }, - { "dh-group17-sha512", KEX_DHGROUP17, KEX_DHGROUP16, +1 }, - { "dh-group18-sha512", KEX_DHGROUP18, KEX_DHGROUP17, +1 }, - { "WARN", KEX_WARN, -1, -1 } -}; - -static const struct keyvalwhere hknames[] = { - { "ed25519", HK_ED25519, -1, +1 }, - { "ed448", HK_ED448, -1, +1 }, - { "ecdsa", HK_ECDSA, -1, -1 }, - { "dsa", HK_DSA, -1, -1 }, - { "rsa", HK_RSA, -1, -1 }, - { "WARN", HK_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/ttymode-list.h, but could in principle differ if other backends - * started to support tty modes (e.g., the pty backend). - * The set of modes in in this array is currently significant for - * settings migration from old versions; if they change, review the - * gppmap() invocation for "TerminalModes". - */ -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", - "IUTF8", "ISIG", "ICANON", "XCASE", "ECHO", - "ECHOE", "ECHOK", "ECHONL", "NOFLSH", "TOSTOP", - "IEXTEN", "ECHOCTL", "ECHOKE", "PENDIN", "OPOST", - "OLCUC", "ONLCR", "OCRNL", "ONOCR", "ONLRET", - "CS7", "CS8", "PARENB", "PARODD", NULL -}; - -static int default_protocol, default_port; -void settings_set_default_protocol(int newval) { default_protocol = newval; } -void settings_set_default_port(int newval) { default_port = newval; } - -/* - * Convenience functions to access the backends[] array - * (which is only present in tools that manage settings). - */ - -const struct BackendVtable *backend_vt_from_name(const char *name) -{ - const struct BackendVtable *const *p; - for (p = backends; *p != NULL; p++) - if (!strcmp((*p)->id, name)) - return *p; - return NULL; -} - -const struct BackendVtable *backend_vt_from_proto(int proto) -{ - const struct BackendVtable *const *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_bool(conf, CONF_username_from_env)) { - /* Use local username. */ - return get_username(); /* might still be NULL */ - } else { - return NULL; - } -} - -static char *gpps_raw(settings_r *sesskey, const char *name, const char *def) -{ - char *ret = read_setting_s(sesskey, 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(settings_r *sesskey, const char *name, const char *def, - Conf *conf, int primary) -{ - char *val = gpps_raw(sesskey, 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(settings_r *sesskey, char *name, - Conf *conf, int primary) -{ - FontSpec *result = read_setting_fontspec(sesskey, name); - if (!result) - result = platform_default_fontspec(name); - conf_set_fontspec(conf, primary, result); - fontspec_free(result); -} -static void gppfile(settings_r *sesskey, const char *name, - Conf *conf, int primary) -{ - Filename *result = read_setting_filename(sesskey, name); - if (!result) - result = platform_default_filename(name); - conf_set_filename(conf, primary, result); - filename_free(result); -} - -static bool gppb_raw(settings_r *sesskey, const char *name, bool def) -{ - def = platform_default_b(name, def); - return sesskey ? read_setting_i(sesskey, name, def) != 0 : def; -} - -static void gppb(settings_r *sesskey, const char *name, bool def, - Conf *conf, int primary) -{ - conf_set_bool(conf, primary, gppb_raw(sesskey, name, def)); -} - -static int gppi_raw(settings_r *sesskey, const char *name, int def) -{ - def = platform_default_i(name, def); - return read_setting_i(sesskey, name, def); -} - -static void gppi(settings_r *sesskey, const char *name, int def, - Conf *conf, int primary) -{ - conf_set_int(conf, primary, gppi_raw(sesskey, 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 bool gppmap(settings_r *sesskey, const 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(sesskey, 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(settings_w *sesskey, char const *outkey, Conf *conf, - int primary, bool include_values) -{ - char *buf, *p, *key, *realkey; - const char *val, *q; - 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(sesskey, 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_from_str(const char *str, - const struct keyvalwhere *mapping, int nvals, - Conf *conf, int primary) -{ - char *commalist = dupstr(str); - char *p, *q; - int i, j, n, v, pos; - unsigned long seen = 0; /* bitmap for weeding dups etc */ - - /* - * 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 >= 0); - 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); - seen |= (1 << mapping[i].v); - n++; - } - } - } -} - -/* - * Read a preference list. - */ -static void gprefs(settings_r *sesskey, const char *name, const char *def, - const struct keyvalwhere *mapping, int nvals, - Conf *conf, int primary) -{ - /* - * Fetch the string which we'll parse as a comma-separated list. - */ - char *value = gpps_raw(sesskey, name, def); - gprefs_from_str(value, mapping, nvals, conf, primary); - sfree(value); -} - -/* - * Write out a preference list. - */ -static void wprefs(settings_w *sesskey, const 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); -} - -static void write_setting_b(settings_w *handle, const char *key, bool value) -{ - write_setting_i(handle, key, value ? 1 : 0); -} - -static void write_clip_setting(settings_w *sesskey, const char *savekey, - Conf *conf, int confkey, int strconfkey) -{ - int val = conf_get_int(conf, confkey); - switch (val) { - case CLIPUI_NONE: - default: - write_setting_s(sesskey, savekey, "none"); - break; - case CLIPUI_IMPLICIT: - write_setting_s(sesskey, savekey, "implicit"); - break; - case CLIPUI_EXPLICIT: - write_setting_s(sesskey, savekey, "explicit"); - break; - case CLIPUI_CUSTOM: { - char *sval = dupcat("custom:", conf_get_str(conf, strconfkey)); - write_setting_s(sesskey, savekey, sval); - sfree(sval); - break; - } - } -} - -static void read_clip_setting(settings_r *sesskey, char *savekey, - int def, Conf *conf, int confkey, int strconfkey) -{ - char *setting = read_setting_s(sesskey, savekey); - int val; - - conf_set_str(conf, strconfkey, ""); - if (!setting) { - val = def; - } else if (!strcmp(setting, "implicit")) { - val = CLIPUI_IMPLICIT; - } else if (!strcmp(setting, "explicit")) { - val = CLIPUI_EXPLICIT; - } else if (!strncmp(setting, "custom:", 7)) { - val = CLIPUI_CUSTOM; - conf_set_str(conf, strconfkey, setting + 7); - } else { - val = CLIPUI_NONE; - } - conf_set_int(conf, confkey, val); - sfree(setting); -} - -char *save_settings(const char *section, Conf *conf) -{ - struct settings_w *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(settings_w *sesskey, Conf *conf) -{ - int i; - const 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_b(sesskey, "LogFlush", conf_get_bool(conf, CONF_logflush)); - write_setting_b(sesskey, "LogHeader", conf_get_bool(conf, CONF_logheader)); - write_setting_b(sesskey, "SSHLogOmitPasswords", conf_get_bool(conf, CONF_logomitpass)); - write_setting_b(sesskey, "SSHLogOmitData", conf_get_bool(conf, CONF_logomitdata)); - p = "raw"; - { - const struct BackendVtable *vt = - backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); - if (vt) - p = vt->id; - } - 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_b(sesskey, "WarnOnClose", !!conf_get_bool(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_b(sesskey, "TCPNoDelay", conf_get_bool(conf, CONF_tcp_nodelay)); - write_setting_b(sesskey, "TCPKeepalives", conf_get_bool(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_b(sesskey, "ProxyLocalhost", conf_get_bool(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)); - write_setting_i(sesskey, "ProxyLogToTerm", conf_get_int(conf, CONF_proxy_log_to_term)); - wmap(sesskey, "Environment", conf, CONF_environmt, true); - write_setting_s(sesskey, "UserName", conf_get_str(conf, CONF_username)); - write_setting_b(sesskey, "UserNameFromEnvironment", conf_get_bool(conf, CONF_username_from_env)); - write_setting_s(sesskey, "LocalUserName", conf_get_str(conf, CONF_localusername)); - write_setting_b(sesskey, "NoPTY", conf_get_bool(conf, CONF_nopty)); - write_setting_b(sesskey, "Compression", conf_get_bool(conf, CONF_compression)); - write_setting_b(sesskey, "TryAgent", conf_get_bool(conf, CONF_tryagent)); - write_setting_b(sesskey, "AgentFwd", conf_get_bool(conf, CONF_agentfwd)); -#ifndef NO_GSSAPI - write_setting_b(sesskey, "GssapiFwd", conf_get_bool(conf, CONF_gssapifwd)); -#endif - write_setting_b(sesskey, "ChangeUsername", conf_get_bool(conf, CONF_change_username)); - wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); - wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist); - wprefs(sesskey, "HostKey", hknames, HK_MAX, conf, CONF_ssh_hklist); - write_setting_b(sesskey, "PreferKnownHostKeys", conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys)); - write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time)); -#ifndef NO_GSSAPI - write_setting_i(sesskey, "GssapiRekey", conf_get_int(conf, CONF_gssapirekey)); -#endif - write_setting_s(sesskey, "RekeyBytes", conf_get_str(conf, CONF_ssh_rekey_data)); - write_setting_b(sesskey, "SshNoAuth", conf_get_bool(conf, CONF_ssh_no_userauth)); - write_setting_b(sesskey, "SshNoTrivialAuth", conf_get_bool(conf, CONF_ssh_no_trivial_userauth)); - write_setting_b(sesskey, "SshBanner", conf_get_bool(conf, CONF_ssh_show_banner)); - write_setting_b(sesskey, "AuthTIS", conf_get_bool(conf, CONF_try_tis_auth)); - write_setting_b(sesskey, "AuthKI", conf_get_bool(conf, CONF_try_ki_auth)); -#ifndef NO_GSSAPI - write_setting_b(sesskey, "AuthGSSAPI", conf_get_bool(conf, CONF_try_gssapi_auth)); - write_setting_b(sesskey, "AuthGSSAPIKEX", conf_get_bool(conf, CONF_try_gssapi_kex)); - 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_b(sesskey, "SshNoShell", conf_get_bool(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_b(sesskey, "SSH2DES", conf_get_bool(conf, CONF_ssh2_des_cbc)); - write_setting_filename(sesskey, "PublicKeyFile", conf_get_filename(conf, CONF_keyfile)); - write_setting_filename(sesskey, "DetachedCertificate", conf_get_filename(conf, CONF_detached_cert)); - write_setting_s(sesskey, "AuthPlugin", conf_get_str(conf, CONF_auth_plugin)); - write_setting_s(sesskey, "RemoteCommand", conf_get_str(conf, CONF_remote_cmd)); - write_setting_b(sesskey, "RFCEnviron", conf_get_bool(conf, CONF_rfc_environ)); - write_setting_b(sesskey, "PassiveTelnet", conf_get_bool(conf, CONF_passive_telnet)); - write_setting_b(sesskey, "BackspaceIsDelete", conf_get_bool(conf, CONF_bksp_is_delete)); - write_setting_b(sesskey, "RXVTHomeEnd", conf_get_bool(conf, CONF_rxvt_homeend)); - write_setting_i(sesskey, "LinuxFunctionKeys", conf_get_int(conf, CONF_funky_type)); - write_setting_i(sesskey, "ShiftedArrowKeys", conf_get_int(conf, CONF_sharrow_type)); - write_setting_b(sesskey, "NoApplicationKeys", conf_get_bool(conf, CONF_no_applic_k)); - write_setting_b(sesskey, "NoApplicationCursors", conf_get_bool(conf, CONF_no_applic_c)); - write_setting_b(sesskey, "NoMouseReporting", conf_get_bool(conf, CONF_no_mouse_rep)); - write_setting_b(sesskey, "NoRemoteResize", conf_get_bool(conf, CONF_no_remote_resize)); - write_setting_b(sesskey, "NoAltScreen", conf_get_bool(conf, CONF_no_alt_screen)); - write_setting_b(sesskey, "NoRemoteWinTitle", conf_get_bool(conf, CONF_no_remote_wintitle)); - write_setting_b(sesskey, "NoRemoteClearScroll", conf_get_bool(conf, CONF_no_remote_clearscroll)); - write_setting_i(sesskey, "RemoteQTitleAction", conf_get_int(conf, CONF_remote_qtitle_action)); - write_setting_b(sesskey, "NoDBackspace", conf_get_bool(conf, CONF_no_dbackspace)); - write_setting_b(sesskey, "NoRemoteCharset", conf_get_bool(conf, CONF_no_remote_charset)); - write_setting_b(sesskey, "ApplicationCursorKeys", conf_get_bool(conf, CONF_app_cursor)); - write_setting_b(sesskey, "ApplicationKeypad", conf_get_bool(conf, CONF_app_keypad)); - write_setting_b(sesskey, "NetHackKeypad", conf_get_bool(conf, CONF_nethack_keypad)); - write_setting_b(sesskey, "AltF4", conf_get_bool(conf, CONF_alt_f4)); - write_setting_b(sesskey, "AltSpace", conf_get_bool(conf, CONF_alt_space)); - write_setting_b(sesskey, "AltOnly", conf_get_bool(conf, CONF_alt_only)); - write_setting_b(sesskey, "ComposeKey", conf_get_bool(conf, CONF_compose_key)); - write_setting_b(sesskey, "CtrlAltKeys", conf_get_bool(conf, CONF_ctrlaltkeys)); -#ifdef OSX_META_KEY_CONFIG - write_setting_b(sesskey, "OSXOptionMeta", conf_get_bool(conf, CONF_osx_option_meta)); - write_setting_b(sesskey, "OSXCommandMeta", conf_get_bool(conf, CONF_osx_command_meta)); -#endif - write_setting_b(sesskey, "TelnetKey", conf_get_bool(conf, CONF_telnet_keyboard)); - write_setting_b(sesskey, "TelnetRet", conf_get_bool(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_b(sesskey, "AlwaysOnTop", conf_get_bool(conf, CONF_alwaysontop)); - write_setting_b(sesskey, "FullScreenOnAltEnter", conf_get_bool(conf, CONF_fullscreenonaltenter)); - write_setting_b(sesskey, "HideMousePtr", conf_get_bool(conf, CONF_hide_mouseptr)); - write_setting_b(sesskey, "SunkenEdge", conf_get_bool(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_b(sesskey, "BlinkCur", conf_get_bool(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_b(sesskey, "BellOverload", conf_get_bool(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_b(sesskey, "DECOriginMode", conf_get_bool(conf, CONF_dec_om)); - write_setting_b(sesskey, "AutoWrapMode", conf_get_bool(conf, CONF_wrap_mode)); - write_setting_b(sesskey, "LFImpliesCR", conf_get_bool(conf, CONF_lfhascr)); - write_setting_b(sesskey, "CRImpliesLF", conf_get_bool(conf, CONF_crhaslf)); - write_setting_b(sesskey, "DisableArabicShaping", conf_get_bool(conf, CONF_no_arabicshaping)); - write_setting_b(sesskey, "DisableBidi", conf_get_bool(conf, CONF_no_bidi)); - write_setting_b(sesskey, "WinNameAlways", conf_get_bool(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_b(sesskey, "UseSystemColours", conf_get_bool(conf, CONF_system_colour)); - write_setting_b(sesskey, "TryPalette", conf_get_bool(conf, CONF_try_palette)); - write_setting_b(sesskey, "ANSIColour", conf_get_bool(conf, CONF_ansi_colour)); - write_setting_b(sesskey, "Xterm256Colour", conf_get_bool(conf, CONF_xterm_256_colour)); - write_setting_b(sesskey, "TrueColour", conf_get_bool(conf, CONF_true_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_b(sesskey, "RawCNP", conf_get_bool(conf, CONF_rawcnp)); - write_setting_b(sesskey, "UTF8linedraw", conf_get_bool(conf, CONF_utf8linedraw)); - write_setting_b(sesskey, "PasteRTF", conf_get_bool(conf, CONF_rtf_paste)); - write_setting_i(sesskey, "MouseIsXterm", conf_get_int(conf, CONF_mouse_is_xterm)); - write_setting_b(sesskey, "RectSelect", conf_get_bool(conf, CONF_rect_select)); - write_setting_b(sesskey, "PasteControls", conf_get_bool(conf, CONF_paste_controls)); - write_setting_b(sesskey, "MouseOverride", conf_get_bool(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_b(sesskey, "MouseAutocopy", - conf_get_bool(conf, CONF_mouseautocopy)); - write_clip_setting(sesskey, "MousePaste", conf, - CONF_mousepaste, CONF_mousepaste_custom); - write_clip_setting(sesskey, "CtrlShiftIns", conf, - CONF_ctrlshiftins, CONF_ctrlshiftins_custom); - write_clip_setting(sesskey, "CtrlShiftCV", conf, - CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); - write_setting_s(sesskey, "LineCodePage", conf_get_str(conf, CONF_line_codepage)); - write_setting_b(sesskey, "CJKAmbigWide", conf_get_bool(conf, CONF_cjk_ambig_wide)); - write_setting_b(sesskey, "UTF8Override", conf_get_bool(conf, CONF_utf8_override)); - write_setting_s(sesskey, "Printer", conf_get_str(conf, CONF_printer)); - write_setting_b(sesskey, "CapsLockCyr", conf_get_bool(conf, CONF_xlat_capslockcyr)); - write_setting_b(sesskey, "ScrollBar", conf_get_bool(conf, CONF_scrollbar)); - write_setting_b(sesskey, "ScrollBarFullScreen", conf_get_bool(conf, CONF_scrollbar_in_fullscreen)); - write_setting_b(sesskey, "ScrollOnKey", conf_get_bool(conf, CONF_scroll_on_key)); - write_setting_b(sesskey, "ScrollOnDisp", conf_get_bool(conf, CONF_scroll_on_disp)); - write_setting_b(sesskey, "EraseToScrollback", conf_get_bool(conf, CONF_erase_to_scrollback)); - write_setting_i(sesskey, "LockSize", conf_get_int(conf, CONF_resize_action)); - write_setting_b(sesskey, "BCE", conf_get_bool(conf, CONF_bce)); - write_setting_b(sesskey, "BlinkText", conf_get_bool(conf, CONF_blinktext)); - write_setting_b(sesskey, "X11Forward", conf_get_bool(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_b(sesskey, "LocalPortAcceptAll", conf_get_bool(conf, CONF_lport_acceptall)); - write_setting_b(sesskey, "RemotePortAcceptAll", conf_get_bool(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, "BugDropStart", 2-conf_get_int(conf, CONF_sshbug_dropstart)); - write_setting_i(sesskey, "BugFilterKexinit", 2-conf_get_int(conf, CONF_sshbug_filter_kexinit)); - write_setting_b(sesskey, "StampUtmp", conf_get_bool(conf, CONF_stamp_utmp)); - write_setting_b(sesskey, "LoginShell", conf_get_bool(conf, CONF_login_shell)); - write_setting_b(sesskey, "ScrollbarOnLeft", conf_get_bool(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_b(sesskey, "ShadowBold", conf_get_bool(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_b(sesskey, "ConnectionSharing", conf_get_bool(conf, CONF_ssh_connection_sharing)); - write_setting_b(sesskey, "ConnectionSharingUpstream", conf_get_bool(conf, CONF_ssh_connection_sharing_upstream)); - write_setting_b(sesskey, "ConnectionSharingDownstream", conf_get_bool(conf, CONF_ssh_connection_sharing_downstream)); - wmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys, false); - - /* - * SUPDUP settings - */ - write_setting_s(sesskey, "SUPDUPLocation", conf_get_str(conf, CONF_supdup_location)); - write_setting_i(sesskey, "SUPDUPCharset", conf_get_int(conf, CONF_supdup_ascii_set)); - write_setting_b(sesskey, "SUPDUPMoreProcessing", conf_get_bool(conf, CONF_supdup_more)); - write_setting_b(sesskey, "SUPDUPScrolling", conf_get_bool(conf, CONF_supdup_scroll)); -} - -bool load_settings(const char *section, Conf *conf) -{ - settings_r *sesskey; - - sesskey = open_settings_r(section); - bool exists = (sesskey != NULL); - load_open_settings(sesskey, conf); - close_settings_r(sesskey); - - if (exists && conf_launchable(conf)) - add_session_to_jumplist(section); - - return exists; -} - -void load_open_settings(settings_r *sesskey, Conf *conf) -{ - int i; - char *prot; - - conf_set_bool(conf, CONF_ssh_subsys, false); /* 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); - gppb(sesskey, "LogFlush", true, conf, CONF_logflush); - gppb(sesskey, "LogHeader", true, conf, CONF_logheader); - gppb(sesskey, "SSHLogOmitPasswords", true, conf, CONF_logomitpass); - gppb(sesskey, "SSHLogOmitData", false, 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 struct BackendVtable *vt = backend_vt_from_name(prot); - if (vt) { - conf_set_int(conf, CONF_protocol, vt->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); - gppb(sesskey, "WarnOnClose", true, 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); - } - gppb(sesskey, "TCPNoDelay", true, conf, CONF_tcp_nodelay); - gppb(sesskey, "TCPKeepalives", false, 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)) { - /* - * Backwards compatibility with old saved settings. - * - * From the invention of this setting through 0.67, the set of - * terminal modes was fixed, and absence of a mode from this - * setting meant the user had explicitly removed it from the - * UI and we shouldn't send it. - * - * In 0.68, the IUTF8 mode was added, and in handling old - * settings we inadvertently removed the ability to not send - * a mode. Any mode not mentioned was treated as if it was - * set to 'auto' (A). - * - * After 0.68, we added explicit notation to the setting format - * when the user removes a known terminal mode from the list. - * - * So: if any of the modes from the original set is missing, we - * assume this was an intentional removal by the user and add - * an explicit removal ('N'); but if IUTF8 (or any other mode - * added after 0.67) is missing, we assume that its absence is - * due to the setting being old rather than intentional, and - * add it with its default setting. - * - * (This does mean that if a 0.68 user explicitly removed IUTF8, - * we add it back; but removing IUTF8 had no effect in 0.68, so - * we're preserving behaviour, which is the best we can do.) - */ - for (i = 0; ttymodes[i]; i++) { - if (!conf_get_str_str_opt(conf, CONF_ttymodes, ttymodes[i])) { - /* Mode not mentioned in setting. */ - const char *def; - if (!strcmp(ttymodes[i], "IUTF8")) { - /* Any new modes we add in future should be treated - * this way too. */ - def = "A"; /* same as new-setting default below */ - } else { - /* One of the original modes. Absence is probably - * deliberate. */ - def = "N"; /* don't send */ - } - conf_set_str_str(conf, CONF_ttymodes, ttymodes[i], def); - } - } - } else { - /* 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); - gppb(sesskey, "ProxyLocalhost", false, 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); - gppi(sesskey, "ProxyLogToTerm", FORCE_OFF, conf, CONF_proxy_log_to_term); - gppmap(sesskey, "Environment", conf, CONF_environmt); - gpps(sesskey, "UserName", "", conf, CONF_username); - gppb(sesskey, "UserNameFromEnvironment", false, - conf, CONF_username_from_env); - gpps(sesskey, "LocalUserName", "", conf, CONF_localusername); - gppb(sesskey, "NoPTY", false, conf, CONF_nopty); - gppb(sesskey, "Compression", false, conf, CONF_compression); - gppb(sesskey, "TryAgent", true, conf, CONF_tryagent); - gppb(sesskey, "AgentFwd", false, conf, CONF_agentfwd); - gppb(sesskey, "ChangeUsername", false, conf, CONF_change_username); -#ifndef NO_GSSAPI - gppb(sesskey, "GssapiFwd", false, conf, CONF_gssapifwd); -#endif - gprefs(sesskey, "Cipher", "\0", - ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); - { - /* Backward-compatibility: before 0.58 (when the "KEX" - * preference was first added), we had 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. */ - const char *default_kexes, - *normal_default = "ecdh,dh-gex-sha1,dh-group18-sha512,dh-group17-sha512,dh-group16-sha512,dh-group15-sha512,dh-group14-sha1,rsa," - "WARN,dh-group1-sha1", - *bugdhgex2_default = "ecdh,dh-group18-sha512,dh-group17-sha512,dh-group16-sha512,dh-group15-sha512,dh-group14-sha1,rsa," - "WARN,dh-group1-sha1,dh-gex-sha1"; - char *raw; - i = 2 - gppi_raw(sesskey, "BugDHGEx2", 0); - if (i == FORCE_ON) - default_kexes = bugdhgex2_default; - else - default_kexes = normal_default; - /* Migration: after 0.67 we decided we didn't like - * dh-group1-sha1. If it looks like the user never changed - * the defaults, quietly upgrade their settings to demote it. - * (If they did, they're on their own.) */ - raw = gpps_raw(sesskey, "KEX", default_kexes); - assert(raw != NULL); - /* Lack of 'ecdh' tells us this was saved by 0.58-0.67 - * inclusive. If it was saved by a later version, we need - * to leave it alone. */ - if (strcmp(raw, "dh-group14-sha1,dh-group1-sha1,rsa," - "WARN,dh-gex-sha1") == 0) { - /* Previously migrated from BugDHGEx2. */ - sfree(raw); - raw = dupstr(bugdhgex2_default); - } else if (strcmp(raw, "dh-gex-sha1,dh-group14-sha1," - "dh-group1-sha1,rsa,WARN") == 0) { - /* Untouched old default setting. */ - sfree(raw); - raw = dupstr(normal_default); - } - /* (For the record: after 0.70, the default algorithm list - * very briefly contained the string 'gss-sha1-krb5'; this was - * never used in any committed version of code, but was left - * over from a pre-commit version of GSS key exchange. - * Mentioned here as it is remotely possible that it will turn - * up in someone's saved settings in future.) */ - - gprefs_from_str(raw, kexnames, KEX_MAX, conf, CONF_ssh_kexlist); - sfree(raw); - } - gprefs(sesskey, "HostKey", "ed25519,ecdsa,rsa,dsa,WARN", - hknames, HK_MAX, conf, CONF_ssh_hklist); - gppb(sesskey, "PreferKnownHostKeys", true, conf, CONF_ssh_prefer_known_hostkeys); - gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time); -#ifndef NO_GSSAPI - gppi(sesskey, "GssapiRekey", GSS_DEF_REKEY_MINS, conf, CONF_gssapirekey); -#endif - gpps(sesskey, "RekeyBytes", "1G", conf, CONF_ssh_rekey_data); - { - /* SSH-2 only by default */ - int sshprot = gppi_raw(sesskey, "SshProt", 3); - /* Old sessions may contain the values corresponding to the fallbacks - * we used to allow; migrate them */ - if (sshprot == 1) sshprot = 0; /* => "SSH-1 only" */ - else if (sshprot == 2) sshprot = 3; /* => "SSH-2 only" */ - conf_set_int(conf, CONF_sshprot, sshprot); - } - gpps(sesskey, "LogHost", "", conf, CONF_loghost); - gppb(sesskey, "SSH2DES", false, conf, CONF_ssh2_des_cbc); - gppb(sesskey, "SshNoAuth", false, conf, CONF_ssh_no_userauth); - gppb(sesskey, "SshNoTrivialAuth", false, conf, CONF_ssh_no_trivial_userauth); - gppb(sesskey, "SshBanner", true, conf, CONF_ssh_show_banner); - gppb(sesskey, "AuthTIS", false, conf, CONF_try_tis_auth); - gppb(sesskey, "AuthKI", true, conf, CONF_try_ki_auth); -#ifndef NO_GSSAPI - gppb(sesskey, "AuthGSSAPI", true, conf, CONF_try_gssapi_auth); - gppb(sesskey, "AuthGSSAPIKEX", true, conf, CONF_try_gssapi_kex); - gprefs(sesskey, "GSSLibs", "\0", - gsslibkeywords, ngsslibs, conf, CONF_ssh_gsslist); - gppfile(sesskey, "GSSCustom", conf, CONF_ssh_gss_custom); -#endif - gppb(sesskey, "SshNoShell", false, conf, CONF_ssh_no_shell); - gppfile(sesskey, "PublicKeyFile", conf, CONF_keyfile); - gppfile(sesskey, "DetachedCertificate", conf, CONF_detached_cert); - gpps(sesskey, "AuthPlugin", "", conf, CONF_auth_plugin); - gpps(sesskey, "RemoteCommand", "", conf, CONF_remote_cmd); - gppb(sesskey, "RFCEnviron", false, conf, CONF_rfc_environ); - gppb(sesskey, "PassiveTelnet", false, conf, CONF_passive_telnet); - gppb(sesskey, "BackspaceIsDelete", true, conf, CONF_bksp_is_delete); - gppb(sesskey, "RXVTHomeEnd", false, conf, CONF_rxvt_homeend); - gppi(sesskey, "LinuxFunctionKeys", 0, conf, CONF_funky_type); - gppi(sesskey, "ShiftedArrowKeys", SHARROW_APPLICATION, conf, - CONF_sharrow_type); - gppb(sesskey, "NoApplicationKeys", false, conf, CONF_no_applic_k); - gppb(sesskey, "NoApplicationCursors", false, conf, CONF_no_applic_c); - gppb(sesskey, "NoMouseReporting", false, conf, CONF_no_mouse_rep); - gppb(sesskey, "NoRemoteResize", false, conf, CONF_no_remote_resize); - gppb(sesskey, "NoAltScreen", false, conf, CONF_no_alt_screen); - gppb(sesskey, "NoRemoteWinTitle", false, conf, CONF_no_remote_wintitle); - gppb(sesskey, "NoRemoteClearScroll", false, - conf, CONF_no_remote_clearscroll); - { - /* 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); - } - gppb(sesskey, "NoDBackspace", false, conf, CONF_no_dbackspace); - gppb(sesskey, "NoRemoteCharset", false, conf, CONF_no_remote_charset); - gppb(sesskey, "ApplicationCursorKeys", false, conf, CONF_app_cursor); - gppb(sesskey, "ApplicationKeypad", false, conf, CONF_app_keypad); - gppb(sesskey, "NetHackKeypad", false, conf, CONF_nethack_keypad); - gppb(sesskey, "AltF4", true, conf, CONF_alt_f4); - gppb(sesskey, "AltSpace", false, conf, CONF_alt_space); - gppb(sesskey, "AltOnly", false, conf, CONF_alt_only); - gppb(sesskey, "ComposeKey", false, conf, CONF_compose_key); - gppb(sesskey, "CtrlAltKeys", true, conf, CONF_ctrlaltkeys); -#ifdef OSX_META_KEY_CONFIG - gppb(sesskey, "OSXOptionMeta", true, conf, CONF_osx_option_meta); - gppb(sesskey, "OSXCommandMeta", false, conf, CONF_osx_command_meta); -#endif - gppb(sesskey, "TelnetKey", false, conf, CONF_telnet_keyboard); - gppb(sesskey, "TelnetRet", true, 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); - gppb(sesskey, "AlwaysOnTop", false, conf, CONF_alwaysontop); - gppb(sesskey, "FullScreenOnAltEnter", false, - conf, CONF_fullscreenonaltenter); - gppb(sesskey, "HideMousePtr", false, conf, CONF_hide_mouseptr); - gppb(sesskey, "SunkenEdge", false, conf, CONF_sunken_edge); - gppi(sesskey, "WindowBorder", 1, conf, CONF_window_border); - gppi(sesskey, "CurType", 0, conf, CONF_cursor_type); - gppb(sesskey, "BlinkCur", false, 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); - gppb(sesskey, "BellOverload", true, 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); - gppb(sesskey, "DECOriginMode", false, conf, CONF_dec_om); - gppb(sesskey, "AutoWrapMode", true, conf, CONF_wrap_mode); - gppb(sesskey, "LFImpliesCR", false, conf, CONF_lfhascr); - gppb(sesskey, "CRImpliesLF", false, conf, CONF_crhaslf); - gppb(sesskey, "DisableArabicShaping", false, conf, CONF_no_arabicshaping); - gppb(sesskey, "DisableBidi", false, conf, CONF_no_bidi); - gppb(sesskey, "WinNameAlways", true, 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); - gppb(sesskey, "UseSystemColours", false, conf, CONF_system_colour); - gppb(sesskey, "TryPalette", false, conf, CONF_try_palette); - gppb(sesskey, "ANSIColour", true, conf, CONF_ansi_colour); - gppb(sesskey, "Xterm256Colour", true, conf, CONF_xterm_256_colour); - gppb(sesskey, "TrueColour", true, conf, CONF_true_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); - } - gppb(sesskey, "RawCNP", false, conf, CONF_rawcnp); - gppb(sesskey, "UTF8linedraw", false, conf, CONF_utf8linedraw); - gppb(sesskey, "PasteRTF", false, conf, CONF_rtf_paste); - gppi(sesskey, "MouseIsXterm", 0, conf, CONF_mouse_is_xterm); - gppb(sesskey, "RectSelect", false, conf, CONF_rect_select); - gppb(sesskey, "PasteControls", false, conf, CONF_paste_controls); - gppb(sesskey, "MouseOverride", true, 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); - } - gppb(sesskey, "MouseAutocopy", CLIPUI_DEFAULT_AUTOCOPY, - conf, CONF_mouseautocopy); - read_clip_setting(sesskey, "MousePaste", CLIPUI_DEFAULT_MOUSE, - conf, CONF_mousepaste, CONF_mousepaste_custom); - read_clip_setting(sesskey, "CtrlShiftIns", CLIPUI_DEFAULT_INS, - conf, CONF_ctrlshiftins, CONF_ctrlshiftins_custom); - read_clip_setting(sesskey, "CtrlShiftCV", CLIPUI_NONE, - conf, CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); - /* - * The empty default for LineCodePage will be converted later - * into a plausible default for the locale. - */ - gpps(sesskey, "LineCodePage", "", conf, CONF_line_codepage); - gppb(sesskey, "CJKAmbigWide", false, conf, CONF_cjk_ambig_wide); - gppb(sesskey, "UTF8Override", true, conf, CONF_utf8_override); - gpps(sesskey, "Printer", "", conf, CONF_printer); - gppb(sesskey, "CapsLockCyr", false, conf, CONF_xlat_capslockcyr); - gppb(sesskey, "ScrollBar", true, conf, CONF_scrollbar); - gppb(sesskey, "ScrollBarFullScreen", false, - conf, CONF_scrollbar_in_fullscreen); - gppb(sesskey, "ScrollOnKey", false, conf, CONF_scroll_on_key); - gppb(sesskey, "ScrollOnDisp", true, conf, CONF_scroll_on_disp); - gppb(sesskey, "EraseToScrollback", true, conf, CONF_erase_to_scrollback); - gppi(sesskey, "LockSize", 0, conf, CONF_resize_action); - gppb(sesskey, "BCE", true, conf, CONF_bce); - gppb(sesskey, "BlinkText", false, conf, CONF_blinktext); - gppb(sesskey, "X11Forward", false, 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); - - gppb(sesskey, "LocalPortAcceptAll", false, conf, CONF_lport_acceptall); - gppb(sesskey, "RemotePortAcceptAll", false, 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); - i = gppi_raw(sesskey, "BugDropStart", 1); conf_set_int(conf, CONF_sshbug_dropstart, 2-i); - i = gppi_raw(sesskey, "BugFilterKexinit", 1); conf_set_int(conf, CONF_sshbug_filter_kexinit, 2-i); - conf_set_bool(conf, CONF_ssh_simple, false); - gppb(sesskey, "StampUtmp", true, conf, CONF_stamp_utmp); - gppb(sesskey, "LoginShell", true, conf, CONF_login_shell); - gppb(sesskey, "ScrollbarOnLeft", false, conf, CONF_scrollbar_on_left); - gppb(sesskey, "ShadowBold", false, 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); - gppb(sesskey, "ConnectionSharing", false, - conf, CONF_ssh_connection_sharing); - gppb(sesskey, "ConnectionSharingUpstream", true, - conf, CONF_ssh_connection_sharing_upstream); - gppb(sesskey, "ConnectionSharingDownstream", true, - conf, CONF_ssh_connection_sharing_downstream); - gppmap(sesskey, "SSHManualHostKeys", conf, CONF_ssh_manual_hostkeys); - - /* - * SUPDUP settings - */ - gpps(sesskey, "SUPDUPLocation", "The Internet", conf, CONF_supdup_location); - gppi(sesskey, "SUPDUPCharset", false, conf, CONF_supdup_ascii_set); - gppb(sesskey, "SUPDUPMoreProcessing", false, conf, CONF_supdup_more); - gppb(sesskey, "SUPDUPScrolling", false, conf, CONF_supdup_scroll); -} - -bool do_defaults(const char *session, Conf *conf) -{ - return 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 */ -} - -bool sesslist_demo_mode = false; - -void get_sesslist(struct sesslist *list, bool allocate) -{ - int i; - char *p; - settings_e *handle; - - if (allocate) { - strbuf *sb = strbuf_new(); - - if (sesslist_demo_mode) { - put_asciz(sb, "demo-server"); - put_asciz(sb, "demo-server-2"); - } else { - if ((handle = enum_settings_start()) != NULL) { - while (enum_settings_next(handle, sb)) - put_byte(sb, '\0'); - enum_settings_finish(handle); - } - put_byte(sb, '\0'); - } - - list->buffer = strbuf_to_str(sb); - - /* - * 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, const 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(const char *), sessioncmp); - } else { - sfree(list->buffer); - sfree(list->sessions); - list->buffer = NULL; - list->sessions = NULL; - } -} diff --git a/sign.sh b/sign.sh deleted file mode 100644 index b40c2d471..000000000 --- a/sign.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh - -# Generate GPG signatures on a PuTTY release/snapshot directory as -# delivered by Buildscr. - -# Usage: sh sign.sh [-r] -# e.g. sh sign.sh putty (probably in the build.out directory) -# or sh sign.sh -r 0.60 (-r means use the release keys) - -set -e - -keyname=B43979F89F446CFD -preliminary=false - -while :; do - case "$1" in - -r) - shift - keyname=E4F83EA2AA4915EC - ;; - -p) - shift - preliminary=true - ;; - -*) - echo "Unknown option '$1'" >&2 - exit 1 - ;; - *) - break - ;; - esac -done - -sign() { - # Check for the prior existence of the signature, so we can - # re-run this script if it encounters an error part way - # through. - echo "----- Signing $2 with key '$keyname'" - test -f "$3" || \ - gpg --load-extension=idea "$1" -u "$keyname" -o "$3" "$2" -} - -cd "$1" -echo "===== Signing with key '$keyname'" -if $preliminary; then - sign --clearsign sha512sums ../sha512sums-preliminary.gpg -else - for i in putty*src.zip putty*.tar.gz \ - w32/*.exe w32/*.zip w32/*.msi \ - w64/*.exe w64/*.zip w64/*.msi \ - wa32/*.exe wa32/*.zip wa32/*.msi \ - wa64/*.exe wa64/*.zip wa64/*.msi \ - w32old/*.exe w32old/*.zip; do - sign --detach-sign "$i" "$i.gpg" - done - for i in md5sums sha1sums sha256sums sha512sums; do - sign --clearsign "$i" "$i.gpg" - done -fi diff --git a/ssh.h b/ssh.h deleted file mode 100644 index 5ecef0cb8..000000000 --- a/ssh.h +++ /dev/null @@ -1,1951 +0,0 @@ -#include -#include - -#include "puttymem.h" -#include "tree234.h" -#include "network.h" -#include "misc.h" - -struct ssh_channel; - -/* - * 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 default 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 - -typedef struct PacketQueueNode PacketQueueNode; -struct PacketQueueNode { - PacketQueueNode *next, *prev; - size_t formal_size; /* contribution to PacketQueueBase's total_size */ - bool on_free_queue; /* is this packet scheduled for freeing? */ -}; - -typedef struct PktIn { - int type; - unsigned long sequence; /* SSH-2 incoming sequence number */ - PacketQueueNode qnode; /* for linking this packet on to a queue */ - BinarySource_IMPLEMENTATION; -} PktIn; - -typedef struct PktOut { - size_t prefix; /* bytes up to and including type field */ - size_t length; /* total bytes, including prefix */ - int type; - size_t minlen; /* SSH-2: ensure wire length is at least this */ - unsigned char *data; /* allocated storage */ - size_t maxlen; /* amount of storage allocated for `data' */ - - /* 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; - - PacketQueueNode qnode; /* for linking this packet on to a queue */ - BinarySink_IMPLEMENTATION; -} PktOut; - -typedef struct PacketQueueBase { - PacketQueueNode end; - size_t total_size; /* sum of all formal_size fields on the queue */ - struct IdempotentCallback *ic; -} PacketQueueBase; - -typedef struct PktInQueue { - PacketQueueBase pqb; - PktIn *(*after)(PacketQueueBase *, PacketQueueNode *prev, bool pop); -} PktInQueue; - -typedef struct PktOutQueue { - PacketQueueBase pqb; - PktOut *(*after)(PacketQueueBase *, PacketQueueNode *prev, bool pop); -} PktOutQueue; - -void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node); -void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node); -void pq_base_concatenate(PacketQueueBase *dest, - PacketQueueBase *q1, PacketQueueBase *q2); - -void pq_in_init(PktInQueue *pq); -void pq_out_init(PktOutQueue *pq); -void pq_in_clear(PktInQueue *pq); -void pq_out_clear(PktOutQueue *pq); - -#define pq_push(pq, pkt) \ - TYPECHECK((pq)->after(&(pq)->pqb, NULL, false) == pkt, \ - pq_base_push(&(pq)->pqb, &(pkt)->qnode)) -#define pq_push_front(pq, pkt) \ - TYPECHECK((pq)->after(&(pq)->pqb, NULL, false) == pkt, \ - pq_base_push_front(&(pq)->pqb, &(pkt)->qnode)) -#define pq_peek(pq) ((pq)->after(&(pq)->pqb, &(pq)->pqb.end, false)) -#define pq_pop(pq) ((pq)->after(&(pq)->pqb, &(pq)->pqb.end, true)) -#define pq_concatenate(dst, q1, q2) \ - TYPECHECK((q1)->after(&(q1)->pqb, NULL, false) == \ - (dst)->after(&(dst)->pqb, NULL, false) && \ - (q2)->after(&(q2)->pqb, NULL, false) == \ - (dst)->after(&(dst)->pqb, NULL, false), \ - pq_base_concatenate(&(dst)->pqb, &(q1)->pqb, &(q2)->pqb)) - -#define pq_first(pq) pq_peek(pq) -#define pq_next(pq, pkt) ((pq)->after(&(pq)->pqb, &(pkt)->qnode, false)) - -/* - * 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_ECDHKEX, - SSH2_PKTCTX_GSSKEX, - SSH2_PKTCTX_RSAKEX -} Pkt_KCtx; -typedef enum { - SSH2_PKTCTX_NOAUTH, - SSH2_PKTCTX_PUBLICKEY, - SSH2_PKTCTX_PASSWORD, - SSH2_PKTCTX_GSSAPI, - SSH2_PKTCTX_KBDINTER -} Pkt_ACtx; - -typedef struct PacketLogSettings { - bool omit_passwords, omit_data; - Pkt_KCtx kctx; - Pkt_ACtx actx; -} PacketLogSettings; - -#define MAX_BLANKS 4 /* no packet needs more censored sections than this */ -int ssh1_censor_packet( - const PacketLogSettings *pls, int type, bool sender_is_client, - ptrlen pkt, logblank_t *blanks); -int ssh2_censor_packet( - const PacketLogSettings *pls, int type, bool sender_is_client, - ptrlen pkt, logblank_t *blanks); - -PktOut *ssh_new_packet(void); -void ssh_free_pktout(PktOut *pkt); - -Socket *ssh_connection_sharing_init( - const char *host, int port, Conf *conf, LogContext *logctx, - Plug *sshplug, ssh_sharing_state **state); -void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, - ConnectionLayer *cl); -bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf); -void share_got_pkt_from_server(ssh_sharing_connstate *ctx, int type, - const void *pkt, int pktlen); -void share_activate(ssh_sharing_state *sharestate, - const char *server_verstring); -void sharestate_free(ssh_sharing_state *state); -int share_ndownstreams(ssh_sharing_state *state); - -void ssh_connshare_log(Ssh *ssh, int event, const char *logtext, - const char *ds_err, const char *us_err); -void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan, - 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); - -/* Per-application overrides for what roles we can take in connection - * sharing, regardless of user configuration (e.g. pscp will never be - * an upstream) */ -extern const bool share_can_be_downstream; -extern const bool share_can_be_upstream; - -struct X11Display; -struct X11FakeAuth; - -/* Structure definition centralised here because the SSH-1 and SSH-2 - * connection layers both use it. But the client module (portfwd.c) - * should not try to look inside here. */ -struct ssh_rportfwd { - unsigned sport, dport; - char *shost, *dhost; - int addressfamily; - char *log_description; /* name of remote listening port, for logging */ - ssh_sharing_connstate *share_ctx; - PortFwdRecord *pfr; -}; -void free_rportfwd(struct ssh_rportfwd *rpf); - -typedef struct ConnectionLayerVtable ConnectionLayerVtable; - -struct ConnectionLayerVtable { - /* Allocate and free remote-to-local port forwardings, called by - * PortFwdManager or by connection sharing */ - struct ssh_rportfwd *(*rportfwd_alloc)( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx); - void (*rportfwd_remove)(ConnectionLayer *cl, struct ssh_rportfwd *rpf); - - /* Open a local-to-remote port forwarding channel, called by - * PortFwdManager */ - SshChannel *(*lportfwd_open)( - ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *peerinfo, - Channel *chan); - - /* Initiate opening of a 'session'-type channel */ - SshChannel *(*session_open)(ConnectionLayer *cl, Channel *chan); - - /* Open outgoing channels for X and agent forwarding. (Used in the - * SSH server.) */ - SshChannel *(*serverside_x11_open)(ConnectionLayer *cl, Channel *chan, - const SocketPeerInfo *pi); - SshChannel *(*serverside_agent_open)(ConnectionLayer *cl, Channel *chan); - - /* Add an X11 display for ordinary X forwarding */ - struct X11FakeAuth *(*add_x11_display)( - ConnectionLayer *cl, int authtype, struct X11Display *x11disp); - - /* Add and remove X11 displays for connection sharing downstreams */ - struct X11FakeAuth *(*add_sharing_x11_display)( - ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, - share_channel *share_chan); - void (*remove_sharing_x11_display)( - ConnectionLayer *cl, struct X11FakeAuth *auth); - - /* Pass through an outgoing SSH packet from a downstream */ - void (*send_packet_from_downstream)( - ConnectionLayer *cl, unsigned id, int type, - const void *pkt, int pktlen, const char *additional_log_text); - - /* Allocate/free an upstream channel number associated with a - * sharing downstream */ - unsigned (*alloc_sharing_channel)(ConnectionLayer *cl, - ssh_sharing_connstate *connstate); - void (*delete_sharing_channel)(ConnectionLayer *cl, unsigned localid); - - /* Indicate that a downstream has sent a global request with the - * want-reply flag, so that when a reply arrives it will be passed - * back to that downstrean */ - void (*sharing_queue_global_request)( - ConnectionLayer *cl, ssh_sharing_connstate *connstate); - - /* Indicate that the last downstream has disconnected */ - void (*sharing_no_more_downstreams)(ConnectionLayer *cl); - - /* Query whether the connection layer is doing agent forwarding */ - bool (*agent_forwarding_permitted)(ConnectionLayer *cl); - - /* Set the size of the main terminal window (if any) */ - void (*terminal_size)(ConnectionLayer *cl, int width, int height); - - /* Indicate that the backlog on standard output has cleared */ - void (*stdout_unthrottle)(ConnectionLayer *cl, size_t bufsize); - - /* Query the size of the backlog on standard _input_ */ - size_t (*stdin_backlog)(ConnectionLayer *cl); - - /* Tell the connection layer that the SSH connection itself has - * backed up, so it should tell all currently open channels to - * cease reading from their local input sources if they can. (Or - * tell it that that state of affairs has gone away again.) */ - void (*throttle_all_channels)(ConnectionLayer *cl, bool throttled); - - /* Ask the connection layer about its current preference for - * line-discipline options. */ - bool (*ldisc_option)(ConnectionLayer *cl, int option); - - /* Communicate _to_ the connection layer (from the main session - * channel) what its preference for line-discipline options is. */ - void (*set_ldisc_option)(ConnectionLayer *cl, int option, bool value); - - /* Communicate to the connection layer whether X forwarding was - * successfully enabled (for purposes of knowing whether to accept - * subsequent channel-opens). */ - void (*enable_x_fwd)(ConnectionLayer *cl); - - /* Communicate / query whether the main session channel currently - * wants user input. The set function is called by mainchan; the - * query function is called by the top-level ssh.c. */ - void (*set_wants_user_input)(ConnectionLayer *cl, bool wanted); - bool (*get_wants_user_input)(ConnectionLayer *cl); - - /* Notify the connection layer that more data has been added to - * the user input queue. */ - void (*got_user_input)(ConnectionLayer *cl); -}; - -struct ConnectionLayer { - LogContext *logctx; - const struct ConnectionLayerVtable *vt; -}; - -static inline struct ssh_rportfwd *ssh_rportfwd_alloc( - ConnectionLayer *cl, const char *sh, int sp, const char *dh, int dp, - int af, const char *log, PortFwdRecord *pfr, ssh_sharing_connstate *cs) -{ return cl->vt->rportfwd_alloc(cl, sh, sp, dh, dp, af, log, pfr, cs); } -static inline void ssh_rportfwd_remove( - ConnectionLayer *cl, struct ssh_rportfwd *rpf) -{ cl->vt->rportfwd_remove(cl, rpf); } -static inline SshChannel *ssh_lportfwd_open( - ConnectionLayer *cl, const char *host, int port, - const char *desc, const SocketPeerInfo *pi, Channel *chan) -{ return cl->vt->lportfwd_open(cl, host, port, desc, pi, chan); } -static inline SshChannel *ssh_session_open(ConnectionLayer *cl, Channel *chan) -{ return cl->vt->session_open(cl, chan); } -static inline SshChannel *ssh_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) -{ return cl->vt->serverside_x11_open(cl, chan, pi); } -static inline SshChannel *ssh_serverside_agent_open( - ConnectionLayer *cl, Channel *chan) -{ return cl->vt->serverside_agent_open(cl, chan); } -static inline struct X11FakeAuth *ssh_add_x11_display( - ConnectionLayer *cl, int authtype, struct X11Display *x11disp) -{ return cl->vt->add_x11_display(cl, authtype, x11disp); } -static inline struct X11FakeAuth *ssh_add_sharing_x11_display( - ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, - share_channel *share_chan) -{ return cl->vt->add_sharing_x11_display(cl, authtype, share_cs, share_chan); } -static inline void ssh_remove_sharing_x11_display( - ConnectionLayer *cl, struct X11FakeAuth *auth) -{ cl->vt->remove_sharing_x11_display(cl, auth); } -static inline void ssh_send_packet_from_downstream( - ConnectionLayer *cl, unsigned id, int type, - const void *pkt, int len, const char *log) -{ cl->vt->send_packet_from_downstream(cl, id, type, pkt, len, log); } -static inline unsigned ssh_alloc_sharing_channel( - ConnectionLayer *cl, ssh_sharing_connstate *connstate) -{ return cl->vt->alloc_sharing_channel(cl, connstate); } -static inline void ssh_delete_sharing_channel( - ConnectionLayer *cl, unsigned localid) -{ cl->vt->delete_sharing_channel(cl, localid); } -static inline void ssh_sharing_queue_global_request( - ConnectionLayer *cl, ssh_sharing_connstate *connstate) -{ cl->vt->sharing_queue_global_request(cl, connstate); } -static inline void ssh_sharing_no_more_downstreams(ConnectionLayer *cl) -{ cl->vt->sharing_no_more_downstreams(cl); } -static inline bool ssh_agent_forwarding_permitted(ConnectionLayer *cl) -{ return cl->vt->agent_forwarding_permitted(cl); } -static inline void ssh_terminal_size(ConnectionLayer *cl, int w, int h) -{ cl->vt->terminal_size(cl, w, h); } -static inline void ssh_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize) -{ cl->vt->stdout_unthrottle(cl, bufsize); } -static inline size_t ssh_stdin_backlog(ConnectionLayer *cl) -{ return cl->vt->stdin_backlog(cl); } -static inline void ssh_throttle_all_channels(ConnectionLayer *cl, bool thr) -{ cl->vt->throttle_all_channels(cl, thr); } -static inline bool ssh_ldisc_option(ConnectionLayer *cl, int option) -{ return cl->vt->ldisc_option(cl, option); } -static inline void ssh_set_ldisc_option(ConnectionLayer *cl, int opt, bool val) -{ cl->vt->set_ldisc_option(cl, opt, val); } -static inline void ssh_enable_x_fwd(ConnectionLayer *cl) -{ cl->vt->enable_x_fwd(cl); } -static inline void ssh_set_wants_user_input(ConnectionLayer *cl, bool wanted) -{ cl->vt->set_wants_user_input(cl, wanted); } -static inline bool ssh_get_wants_user_input(ConnectionLayer *cl) -{ return cl->vt->get_wants_user_input(cl); } -static inline void ssh_got_user_input(ConnectionLayer *cl) -{ cl->vt->got_user_input(cl); } - -/* Exports from portfwd.c */ -PortFwdManager *portfwdmgr_new(ConnectionLayer *cl); -void portfwdmgr_free(PortFwdManager *mgr); -void portfwdmgr_config(PortFwdManager *mgr, Conf *conf); -void portfwdmgr_close(PortFwdManager *mgr, PortFwdRecord *pfr); -void portfwdmgr_close_all(PortFwdManager *mgr); -char *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, - char *hostname, int port, SshChannel *c, - int addressfamily); -bool portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port, - const char *keyhost, int keyport, Conf *conf); -bool portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port); -Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug, bool start_ready); -void portfwd_raw_free(Channel *pfchan); -void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc); - -Socket *platform_make_agent_socket(Plug *plug, const char *dirprefix, - char **error, char **name); - -LogContext *ssh_get_logctx(Ssh *ssh); - -/* Communications back to ssh.c from connection layers */ -void ssh_throttle_conn(Ssh *ssh, int adjust); -void ssh_got_exitcode(Ssh *ssh, int status); -void ssh_ldisc_update(Ssh *ssh); -void ssh_check_sendok(Ssh *ssh); -void ssh_got_fallback_cmd(Ssh *ssh); -bool ssh_is_bare(Ssh *ssh); - -/* Communications back to ssh.c from the BPP */ -void ssh_conn_processed_data(Ssh *ssh); -void ssh_sendbuffer_changed(Ssh *ssh); -void ssh_check_frozen(Ssh *ssh); - -/* Functions to abort the connection, for various reasons. */ -void ssh_remote_error(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); -void ssh_remote_eof(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); -void ssh_proto_error(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); -void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); -void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); -void ssh_user_close(Ssh *ssh, const char *fmt, ...) PRINTF_LIKE(2, 3); -void ssh_spr_close(Ssh *ssh, SeatPromptResult spr, const char *context); - -/* Bit positions in the SSH-1 cipher protocol word */ -#define SSH1_CIPHER_IDEA 1 -#define SSH1_CIPHER_DES 2 -#define SSH1_CIPHER_3DES 3 -#define SSH1_CIPHER_BLOWFISH 6 - -/* The subset of those that we support, with names for selecting them - * on Uppity's command line */ -#define SSH1_SUPPORTED_CIPHER_LIST(X) \ - X(SSH1_CIPHER_3DES, "3des") \ - X(SSH1_CIPHER_BLOWFISH, "blowfish") \ - X(SSH1_CIPHER_DES, "des") \ - /* end of list */ -#define SSH1_CIPHER_LIST_MAKE_MASK(bitpos, name) | (1U << bitpos) -#define SSH1_SUPPORTED_CIPHER_MASK \ - (0 SSH1_SUPPORTED_CIPHER_LIST(SSH1_CIPHER_LIST_MAKE_MASK)) - -struct ssh_key { - const ssh_keyalg *vt; -}; - -struct RSAKey { - int bits; - int bytes; - mp_int *modulus; - mp_int *exponent; - mp_int *private_exponent; - mp_int *p; - mp_int *q; - mp_int *iqmp; - char *comment; - ssh_key sshk; -}; - -struct dsa_key { - mp_int *p, *q, *g, *y, *x; - ssh_key sshk; -}; - -struct ec_curve; - -/* Weierstrass form curve */ -struct ec_wcurve -{ - WeierstrassCurve *wc; - WeierstrassPoint *G; - mp_int *G_order; -}; - -/* Montgomery form curve */ -struct ec_mcurve -{ - MontgomeryCurve *mc; - MontgomeryPoint *G; - unsigned log2_cofactor; -}; - -/* Edwards form curve */ -struct ec_ecurve -{ - EdwardsCurve *ec; - EdwardsPoint *G; - mp_int *G_order; - unsigned log2_cofactor; -}; - -typedef enum EllipticCurveType { - EC_WEIERSTRASS, EC_MONTGOMERY, EC_EDWARDS -} EllipticCurveType; - -struct ec_curve { - EllipticCurveType 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; - size_t fieldBits, fieldBytes; - mp_int *p; - union { - struct ec_wcurve w; - struct ec_mcurve m; - struct ec_ecurve e; - }; -}; - -const ssh_keyalg *ec_alg_by_oid(int len, const void *oid, - const struct ec_curve **curve); -const unsigned char *ec_alg_oid(const ssh_keyalg *alg, int *oidlen); -extern const int ec_nist_curve_lengths[], n_ec_nist_curve_lengths; -extern const int ec_ed_curve_lengths[], n_ec_ed_curve_lengths; -bool ec_nist_alg_and_curve_by_bits(int bits, - const struct ec_curve **curve, - const ssh_keyalg **alg); -bool ec_ed_alg_and_curve_by_bits(int bits, - const struct ec_curve **curve, - const ssh_keyalg **alg); - -struct ecdsa_key { - const struct ec_curve *curve; - WeierstrassPoint *publicKey; - mp_int *privateKey; - ssh_key sshk; -}; -struct eddsa_key { - const struct ec_curve *curve; - EdwardsPoint *publicKey; - mp_int *privateKey; - ssh_key sshk; -}; - -WeierstrassPoint *ecdsa_public(mp_int *private_key, const ssh_keyalg *alg); -EdwardsPoint *eddsa_public(mp_int *private_key, const ssh_keyalg *alg); - -typedef enum KeyComponentType { - KCT_TEXT, KCT_BINARY, KCT_MPINT -} KeyComponentType; -typedef struct key_component { - char *name; - KeyComponentType type; - union { - strbuf *str; /* used for KCT_TEXT and KCT_BINARY */ - mp_int *mp; /* used for KCT_MPINT */ - }; -} key_component; -typedef struct key_components { - size_t ncomponents, componentsize; - key_component *components; -} key_components; -key_components *key_components_new(void); -void key_components_add_text(key_components *kc, - const char *name, const char *value); -void key_components_add_text_pl(key_components *kc, - const char *name, ptrlen value); -void key_components_add_binary(key_components *kc, - const char *name, ptrlen value); -void key_components_add_mp(key_components *kc, - const char *name, mp_int *value); -void key_components_add_uint(key_components *kc, - const char *name, uintmax_t value); -void key_components_add_copy(key_components *kc, - const char *name, const key_component *value); -void key_components_free(key_components *kc); - -/* - * SSH-1 never quite decided which order to store the two components - * of an RSA key. During connection setup, the server sends its host - * and server keys with the exponent first; private key files store - * the modulus first. The agent protocol is even more confusing, - * because the client specifies a key to the server in one order and - * the server lists the keys it knows about in the other order! - */ -typedef enum { RSA_SSH1_EXPONENT_FIRST, RSA_SSH1_MODULUS_FIRST } RsaSsh1Order; - -void BinarySource_get_rsa_ssh1_pub( - BinarySource *src, RSAKey *result, RsaSsh1Order order); -void BinarySource_get_rsa_ssh1_priv( - BinarySource *src, RSAKey *rsa); -RSAKey *BinarySource_get_rsa_ssh1_priv_agent(BinarySource *src); -bool rsa_ssh1_encrypt(unsigned char *data, int length, RSAKey *key); -mp_int *rsa_ssh1_decrypt(mp_int *input, RSAKey *key); -bool rsa_ssh1_decrypt_pkcs1(mp_int *input, RSAKey *key, strbuf *outbuf); -char *rsastr_fmt(RSAKey *key); -char *rsa_ssh1_fingerprint(RSAKey *key); -char **rsa_ssh1_fake_all_fingerprints(RSAKey *key); -bool rsa_verify(RSAKey *key); -void rsa_ssh1_public_blob(BinarySink *bs, RSAKey *key, RsaSsh1Order order); -int rsa_ssh1_public_blob_len(ptrlen data); -void rsa_ssh1_private_blob_agent(BinarySink *bs, RSAKey *key); -void duprsakey(RSAKey *dst, const RSAKey *src); -void freersapriv(RSAKey *key); -void freersakey(RSAKey *key); -key_components *rsa_components(RSAKey *key); - -uint32_t crc32_rfc1662(ptrlen data); -uint32_t crc32_ssh1(ptrlen data); -uint32_t crc32_update(uint32_t crc_input, ptrlen data); - -/* SSH CRC compensation attack detector */ -struct crcda_ctx; -struct crcda_ctx *crcda_make_context(void); -void crcda_free_context(struct crcda_ctx *ctx); -bool detect_attack(struct crcda_ctx *ctx, - const unsigned char *buf, uint32_t len, - const unsigned char *IV); - -/* - * SSH2 RSA key exchange functions - */ -struct ssh_rsa_kex_extra { - int minklen; -}; -RSAKey *ssh_rsakex_newkey(ptrlen data); -void ssh_rsakex_freekey(RSAKey *key); -int ssh_rsakex_klen(RSAKey *key); -strbuf *ssh_rsakex_encrypt( - RSAKey *key, const ssh_hashalg *h, ptrlen plaintext); -mp_int *ssh_rsakex_decrypt( - RSAKey *key, const ssh_hashalg *h, ptrlen ciphertext); - -/* - * Helper function for k generation in DSA, reused in ECDSA - */ -mp_int *dsa_gen_k(const char *id_string, - mp_int *modulus, mp_int *private_key, - unsigned char *digest, int digest_len); - -struct ssh_cipher { - const ssh_cipheralg *vt; -}; - -struct ssh_cipheralg { - ssh_cipher *(*new)(const ssh_cipheralg *alg); - void (*free)(ssh_cipher *); - void (*setiv)(ssh_cipher *, const void *iv); - void (*setkey)(ssh_cipher *, const void *key); - void (*encrypt)(ssh_cipher *, void *blk, int len); - void (*decrypt)(ssh_cipher *, void *blk, int len); - /* Ignored unless SSH_CIPHER_SEPARATE_LENGTH flag set */ - void (*encrypt_length)(ssh_cipher *, void *blk, int len, - unsigned long seq); - void (*decrypt_length)(ssh_cipher *, void *blk, int len, - unsigned long seq); - /* For ciphers that update their state per logical message - * (typically, per unit independently MACed) */ - void (*next_message)(ssh_cipher *); - const char *ssh2_id; - int blksize; - /* real_keybits is the number of bits of entropy genuinely used by - * the cipher scheme; it's used for deciding how big a - * Diffie-Hellman group is needed to exchange a key for the - * cipher. */ - int real_keybits; - /* padded_keybytes is the number of bytes of key data expected as - * input to the setkey function; it's used for deciding how much - * data needs to be generated from the post-kex generation of key - * material. In a sensible cipher which uses all its key bytes for - * real work, this will just be real_keybits/8, but in DES-type - * ciphers which ignore one bit in each byte, it'll be slightly - * different. */ - int padded_keybytes; - unsigned int flags; -#define SSH_CIPHER_IS_CBC 1 -#define SSH_CIPHER_SEPARATE_LENGTH 2 - const char *text_name; - /* If set, this takes priority over other MAC. */ - const ssh2_macalg *required_mac; - - /* Pointer to any extra data used by a particular implementation. */ - const void *extra; -}; - -static inline ssh_cipher *ssh_cipher_new(const ssh_cipheralg *alg) -{ return alg->new(alg); } -static inline void ssh_cipher_free(ssh_cipher *c) -{ c->vt->free(c); } -static inline void ssh_cipher_setiv(ssh_cipher *c, const void *iv) -{ c->vt->setiv(c, iv); } -static inline void ssh_cipher_setkey(ssh_cipher *c, const void *key) -{ c->vt->setkey(c, key); } -static inline void ssh_cipher_encrypt(ssh_cipher *c, void *blk, int len) -{ c->vt->encrypt(c, blk, len); } -static inline void ssh_cipher_decrypt(ssh_cipher *c, void *blk, int len) -{ c->vt->decrypt(c, blk, len); } -static inline void ssh_cipher_encrypt_length( - ssh_cipher *c, void *blk, int len, unsigned long seq) -{ c->vt->encrypt_length(c, blk, len, seq); } -static inline void ssh_cipher_decrypt_length( - ssh_cipher *c, void *blk, int len, unsigned long seq) -{ c->vt->decrypt_length(c, blk, len, seq); } -static inline void ssh_cipher_next_message(ssh_cipher *c) -{ c->vt->next_message(c); } -static inline const struct ssh_cipheralg *ssh_cipher_alg(ssh_cipher *c) -{ return c->vt; } - -void nullcipher_next_message(ssh_cipher *); - -struct ssh2_ciphers { - int nciphers; - const ssh_cipheralg *const *list; -}; - -struct ssh2_mac { - const ssh2_macalg *vt; - BinarySink_DELEGATE_IMPLEMENTATION; -}; - -struct ssh2_macalg { - /* Passes in the cipher context */ - ssh2_mac *(*new)(const ssh2_macalg *alg, ssh_cipher *cipher); - void (*free)(ssh2_mac *); - void (*setkey)(ssh2_mac *, ptrlen key); - void (*start)(ssh2_mac *); - void (*genresult)(ssh2_mac *, unsigned char *); - void (*next_message)(ssh2_mac *); - const char *(*text_name)(ssh2_mac *); - const char *name, *etm_name; - int len, keylen; - - /* Pointer to any extra data used by a particular implementation. */ - const void *extra; -}; - -static inline ssh2_mac *ssh2_mac_new( - const ssh2_macalg *alg, ssh_cipher *cipher) -{ return alg->new(alg, cipher); } -static inline void ssh2_mac_free(ssh2_mac *m) -{ m->vt->free(m); } -static inline void ssh2_mac_setkey(ssh2_mac *m, ptrlen key) -{ m->vt->setkey(m, key); } -static inline void ssh2_mac_start(ssh2_mac *m) -{ m->vt->start(m); } -static inline void ssh2_mac_genresult(ssh2_mac *m, unsigned char *out) -{ m->vt->genresult(m, out); } -static inline void ssh2_mac_next_message(ssh2_mac *m) -{ m->vt->next_message(m); } -static inline const char *ssh2_mac_text_name(ssh2_mac *m) -{ return m->vt->text_name(m); } -static inline const ssh2_macalg *ssh2_mac_alg(ssh2_mac *m) -{ return m->vt; } - -/* Centralised 'methods' for ssh2_mac, defined in mac.c. These run - * the MAC in a specifically SSH-2 style, i.e. taking account of a - * packet sequence number as well as the data to be authenticated. */ -bool ssh2_mac_verresult(ssh2_mac *, const void *); -void ssh2_mac_generate(ssh2_mac *, void *, int, unsigned long seq); -bool ssh2_mac_verify(ssh2_mac *, const void *, int, unsigned long seq); - -void nullmac_next_message(ssh2_mac *m); - -/* Use a MAC in its raw form, outside SSH-2 context, to MAC a given - * string with a given key in the most obvious way. */ -void mac_simple(const ssh2_macalg *alg, ptrlen key, ptrlen data, void *output); - -struct ssh_hash { - const ssh_hashalg *vt; - BinarySink_DELEGATE_IMPLEMENTATION; -}; - -struct ssh_hashalg { - ssh_hash *(*new)(const ssh_hashalg *alg); - void (*reset)(ssh_hash *); - void (*copyfrom)(ssh_hash *dest, ssh_hash *src); - void (*digest)(ssh_hash *, unsigned char *); - void (*free)(ssh_hash *); - size_t hlen; /* output length in bytes */ - size_t blocklen; /* length of the hash's input block, or 0 for N/A */ - const char *text_basename; /* the semantic name of the hash */ - const char *annotation; /* extra info, e.g. which of multiple impls */ - const char *text_name; /* both combined, e.g. "SHA-n (unaccelerated)" */ - const void *extra; /* private to the hash implementation */ -}; - -static inline ssh_hash *ssh_hash_new(const ssh_hashalg *alg) -{ ssh_hash *h = alg->new(alg); if (h) h->vt->reset(h); return h; } -static inline ssh_hash *ssh_hash_copy(ssh_hash *orig) -{ ssh_hash *h = orig->vt->new(orig->vt); h->vt->copyfrom(h, orig); return h; } -static inline void ssh_hash_digest(ssh_hash *h, unsigned char *out) -{ h->vt->digest(h, out); } -static inline void ssh_hash_free(ssh_hash *h) -{ h->vt->free(h); } -static inline const ssh_hashalg *ssh_hash_alg(ssh_hash *h) -{ return h->vt; } - -/* The reset and copyfrom vtable methods return void. But for call-site - * convenience, these wrappers return their input pointer. */ -static inline ssh_hash *ssh_hash_reset(ssh_hash *h) -{ h->vt->reset(h); return h; } -static inline ssh_hash *ssh_hash_copyfrom(ssh_hash *dest, ssh_hash *src) -{ dest->vt->copyfrom(dest, src); return dest; } - -/* ssh_hash_final emits the digest _and_ frees the ssh_hash */ -static inline void ssh_hash_final(ssh_hash *h, unsigned char *out) -{ h->vt->digest(h, out); h->vt->free(h); } - -/* ssh_hash_digest_nondestructive generates a finalised hash from the - * given object without changing its state, so you can continue - * appending data to get a hash of an extended string. */ -static inline void ssh_hash_digest_nondestructive(ssh_hash *h, - unsigned char *out) -{ ssh_hash_final(ssh_hash_copy(h), out); } - -/* Handy macros for defining all those text-name fields at once */ -#define HASHALG_NAMES_BARE(base) \ - .text_basename = base, .annotation = NULL, .text_name = base -#define HASHALG_NAMES_ANNOTATED(base, ann) \ - .text_basename = base, .annotation = ann, .text_name = base " (" ann ")" - -void hash_simple(const ssh_hashalg *alg, ptrlen data, void *output); - -struct ssh_kex { - const char *name, *groupname; - enum { KEXTYPE_DH, KEXTYPE_RSA, KEXTYPE_ECDH, - KEXTYPE_GSS, KEXTYPE_GSS_ECDH } main_type; - const ssh_hashalg *hash; - union { /* publicly visible data for each type */ - const ecdh_keyalg *ecdh_vt; /* for KEXTYPE_ECDH, KEXTYPE_GSS_ECDH */ - }; - const void *extra; /* private to the kex methods */ -}; - -static inline bool kex_is_gss(const struct ssh_kex *kex) -{ - return kex->main_type == KEXTYPE_GSS || kex->main_type == KEXTYPE_GSS_ECDH; -} - -struct ssh_kexes { - int nkexes; - const ssh_kex *const *list; -}; - -/* Indices of the negotiation strings in the KEXINIT packet */ -enum kexlist { - KEXLIST_KEX, KEXLIST_HOSTKEY, KEXLIST_CSCIPHER, KEXLIST_SCCIPHER, - KEXLIST_CSMAC, KEXLIST_SCMAC, KEXLIST_CSCOMP, KEXLIST_SCCOMP, - NKEXLIST -}; - -struct ssh_keyalg { - /* Constructors that create an ssh_key */ - ssh_key *(*new_pub) (const ssh_keyalg *self, ptrlen pub); - ssh_key *(*new_priv) (const ssh_keyalg *self, ptrlen pub, ptrlen priv); - ssh_key *(*new_priv_openssh) (const ssh_keyalg *self, BinarySource *); - - /* Methods that operate on an existing ssh_key */ - void (*freekey) (ssh_key *key); - char *(*invalid) (ssh_key *key, unsigned flags); - void (*sign) (ssh_key *key, ptrlen data, unsigned flags, BinarySink *); - bool (*verify) (ssh_key *key, ptrlen sig, ptrlen data); - void (*public_blob)(ssh_key *key, BinarySink *); - void (*private_blob)(ssh_key *key, BinarySink *); - void (*openssh_blob) (ssh_key *key, BinarySink *); - bool (*has_private) (ssh_key *key); - char *(*cache_str) (ssh_key *key); - key_components *(*components) (ssh_key *key); - ssh_key *(*base_key) (ssh_key *key); /* does not confer ownership */ - /* The following methods can be NULL if !is_certificate */ - void (*ca_public_blob)(ssh_key *key, BinarySink *); - bool (*check_cert)(ssh_key *key, bool host, ptrlen principal, - uint64_t time, const ca_options *opts, - BinarySink *error); - void (*cert_id_string)(ssh_key *key, BinarySink *); - SeatDialogText *(*cert_info)(ssh_key *key); - - /* 'Class methods' that don't deal with an ssh_key at all */ - int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob); - unsigned (*supported_flags) (const ssh_keyalg *self); - const char *(*alternate_ssh_id) (const ssh_keyalg *self, unsigned flags); - char *(*alg_desc)(const ssh_keyalg *self); - bool (*variable_size)(const ssh_keyalg *self); - /* The following methods can be NULL if !is_certificate */ - const ssh_keyalg *(*related_alg)(const ssh_keyalg *self, - const ssh_keyalg *base); - - /* Constant data fields giving information about the key type */ - const char *ssh_id; /* string identifier in the SSH protocol */ - const char *cache_id; /* identifier used in PuTTY's host key cache */ - const void *extra; /* private to the public key methods */ - bool is_certificate; /* is this a certified key type? */ - const ssh_keyalg *base_alg; /* if so, for what underlying key alg? */ -}; - -static inline ssh_key *ssh_key_new_pub(const ssh_keyalg *self, ptrlen pub) -{ return self->new_pub(self, pub); } -static inline ssh_key *ssh_key_new_priv( - const ssh_keyalg *self, ptrlen pub, ptrlen priv) -{ return self->new_priv(self, pub, priv); } -static inline ssh_key *ssh_key_new_priv_openssh( - const ssh_keyalg *self, BinarySource *src) -{ return self->new_priv_openssh(self, src); } -static inline void ssh_key_free(ssh_key *key) -{ key->vt->freekey(key); } -static inline char *ssh_key_invalid(ssh_key *key, unsigned flags) -{ return key->vt->invalid(key, flags); } -static inline void ssh_key_sign( - ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) -{ key->vt->sign(key, data, flags, bs); } -static inline bool ssh_key_verify(ssh_key *key, ptrlen sig, ptrlen data) -{ return key->vt->verify(key, sig, data); } -static inline void ssh_key_public_blob(ssh_key *key, BinarySink *bs) -{ key->vt->public_blob(key, bs); } -static inline void ssh_key_private_blob(ssh_key *key, BinarySink *bs) -{ key->vt->private_blob(key, bs); } -static inline void ssh_key_openssh_blob(ssh_key *key, BinarySink *bs) -{ key->vt->openssh_blob(key, bs); } -static inline bool ssh_key_has_private(ssh_key *key) -{ return key->vt->has_private(key); } -static inline char *ssh_key_cache_str(ssh_key *key) -{ return key->vt->cache_str(key); } -static inline key_components *ssh_key_components(ssh_key *key) -{ return key->vt->components(key); } -static inline ssh_key *ssh_key_base_key(ssh_key *key) -{ return key->vt->base_key(key); } -static inline void ssh_key_ca_public_blob(ssh_key *key, BinarySink *bs) -{ key->vt->ca_public_blob(key, bs); } -static inline void ssh_key_cert_id_string(ssh_key *key, BinarySink *bs) -{ key->vt->cert_id_string(key, bs); } -static inline SeatDialogText *ssh_key_cert_info(ssh_key *key) -{ return key->vt->cert_info(key); } -static inline bool ssh_key_check_cert( - ssh_key *key, bool host, ptrlen principal, uint64_t time, - const ca_options *opts, BinarySink *error) -{ return key->vt->check_cert(key, host, principal, time, opts, error); } -static inline int ssh_key_public_bits(const ssh_keyalg *self, ptrlen blob) -{ return self->pubkey_bits(self, blob); } -static inline const ssh_keyalg *ssh_key_alg(ssh_key *key) -{ return key->vt; } -static inline const char *ssh_key_ssh_id(ssh_key *key) -{ return key->vt->ssh_id; } -static inline const char *ssh_key_cache_id(ssh_key *key) -{ return key->vt->cache_id; } -static inline unsigned ssh_key_supported_flags(ssh_key *key) -{ return key->vt->supported_flags(key->vt); } -static inline unsigned ssh_keyalg_supported_flags(const ssh_keyalg *self) -{ return self->supported_flags(self); } -static inline const char *ssh_keyalg_alternate_ssh_id( - const ssh_keyalg *self, unsigned flags) -{ return self->alternate_ssh_id(self, flags); } -static inline char *ssh_keyalg_desc(const ssh_keyalg *self) -{ return self->alg_desc(self); } -static inline bool ssh_keyalg_variable_size(const ssh_keyalg *self) -{ return self->variable_size(self); } -static inline const ssh_keyalg *ssh_keyalg_related_alg( - const ssh_keyalg *self, const ssh_keyalg *base) -{ return self->related_alg(self, base); } - -/* Stub functions shared between multiple key types */ -unsigned nullkey_supported_flags(const ssh_keyalg *self); -const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags); -ssh_key *nullkey_base_key(ssh_key *key); -bool nullkey_variable_size_no(const ssh_keyalg *self); -bool nullkey_variable_size_yes(const ssh_keyalg *self); - -/* Utility functions implemented centrally */ -ssh_key *ssh_key_clone(ssh_key *key); - -/* - * SSH2 ECDH key exchange vtable - */ -struct ecdh_key { - const ecdh_keyalg *vt; -}; -struct ecdh_keyalg { - /* Unusually, the 'new' method here doesn't directly take a vt - * pointer, because it will also need the containing ssh_kex - * structure for top-level parameters, and since that contains a - * vt pointer anyway, we might as well _only_ pass that. */ - ecdh_key *(*new)(const ssh_kex *kex, bool is_server); - void (*free)(ecdh_key *key); - void (*getpublic)(ecdh_key *key, BinarySink *bs); - bool (*getkey)(ecdh_key *key, ptrlen remoteKey, BinarySink *bs); - char *(*description)(const ssh_kex *kex); -}; -static inline ecdh_key *ecdh_key_new(const ssh_kex *kex, bool is_server) -{ return kex->ecdh_vt->new(kex, is_server); } -static inline void ecdh_key_free(ecdh_key *key) -{ key->vt->free(key); } -static inline void ecdh_key_getpublic(ecdh_key *key, BinarySink *bs) -{ key->vt->getpublic(key, bs); } -static inline bool ecdh_key_getkey(ecdh_key *key, ptrlen remoteKey, - BinarySink *bs) -{ return key->vt->getkey(key, remoteKey, bs); } -static inline char *ecdh_keyalg_description(const ssh_kex *kex) -{ return kex->ecdh_vt->description(kex); } - -/* - * Suffix on GSSAPI SSH protocol identifiers that indicates Kerberos 5 - * as the mechanism. - * - * This suffix is the base64-encoded MD5 hash of the byte sequence - * 06 09 2A 86 48 86 F7 12 01 02 02, which in turn is the ASN.1 DER - * encoding of the object ID 1.2.840.113554.1.2.2 which designates - * Kerberos v5. - * - * (The same encoded OID, minus the two-byte DER header, is defined in - * ssh/pgssapi.c as GSS_MECH_KRB5.) - */ -#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" - -/* - * Enumeration of signature flags from draft-miller-ssh-agent-02 - */ -#define SSH_AGENT_RSA_SHA2_256 2 -#define SSH_AGENT_RSA_SHA2_512 4 - -struct ssh_compressor { - const ssh_compression_alg *vt; -}; -struct ssh_decompressor { - const ssh_compression_alg *vt; -}; - -struct ssh_compression_alg { - const char *name; - /* For zlib@openssh.com: if non-NULL, this name will be considered once - * userauth has completed successfully. */ - const char *delayed_name; - ssh_compressor *(*compress_new)(void); - void (*compress_free)(ssh_compressor *); - void (*compress)(ssh_compressor *, const unsigned char *block, int len, - unsigned char **outblock, int *outlen, - int minlen); - ssh_decompressor *(*decompress_new)(void); - void (*decompress_free)(ssh_decompressor *); - bool (*decompress)(ssh_decompressor *, const unsigned char *block, int len, - unsigned char **outblock, int *outlen); - const char *text_name; -}; - -static inline ssh_compressor *ssh_compressor_new( - const ssh_compression_alg *alg) -{ return alg->compress_new(); } -static inline ssh_decompressor *ssh_decompressor_new( - const ssh_compression_alg *alg) -{ return alg->decompress_new(); } -static inline void ssh_compressor_free(ssh_compressor *c) -{ c->vt->compress_free(c); } -static inline void ssh_decompressor_free(ssh_decompressor *d) -{ d->vt->decompress_free(d); } -static inline void ssh_compressor_compress( - ssh_compressor *c, const unsigned char *block, int len, - unsigned char **outblock, int *outlen, int minlen) -{ c->vt->compress(c, block, len, outblock, outlen, minlen); } -static inline bool ssh_decompressor_decompress( - ssh_decompressor *d, const unsigned char *block, int len, - unsigned char **outblock, int *outlen) -{ return d->vt->decompress(d, block, len, outblock, outlen); } -static inline const ssh_compression_alg *ssh_compressor_alg( - ssh_compressor *c) -{ return c->vt; } -static inline const ssh_compression_alg *ssh_decompressor_alg( - ssh_decompressor *d) -{ return d->vt; } - -struct ssh2_userkey { - ssh_key *key; /* the key itself */ - char *comment; /* the key comment */ -}; - -/* Argon2 password hashing function */ -typedef enum { Argon2d = 0, Argon2i = 1, Argon2id = 2 } Argon2Flavour; -void argon2(Argon2Flavour, uint32_t mem, uint32_t passes, - uint32_t parallel, uint32_t taglen, - ptrlen P, ptrlen S, ptrlen K, ptrlen X, strbuf *out); -void argon2_choose_passes( - Argon2Flavour, uint32_t mem, uint32_t milliseconds, uint32_t *passes, - uint32_t parallel, uint32_t taglen, ptrlen P, ptrlen S, ptrlen K, ptrlen X, - strbuf *out); -/* The H' hash defined in Argon2, exposed just for testcrypt */ -strbuf *argon2_long_hash(unsigned length, ptrlen data); - -/* The maximum length of any hash algorithm. (bytes) */ -#define MAX_HASH_LEN (114) /* longest is SHAKE256 with 114-byte output */ - -extern const ssh_cipheralg ssh_3des_ssh1; -extern const ssh_cipheralg ssh_blowfish_ssh1; -extern const ssh_cipheralg ssh_3des_ssh2_ctr; -extern const ssh_cipheralg ssh_3des_ssh2; -extern const ssh_cipheralg ssh_des; -extern const ssh_cipheralg ssh_des_sshcom_ssh2; -extern const ssh_cipheralg ssh_aes256_sdctr; -extern const ssh_cipheralg ssh_aes256_sdctr_ni; -extern const ssh_cipheralg ssh_aes256_sdctr_neon; -extern const ssh_cipheralg ssh_aes256_sdctr_sw; -extern const ssh_cipheralg ssh_aes256_gcm; -extern const ssh_cipheralg ssh_aes256_gcm_ni; -extern const ssh_cipheralg ssh_aes256_gcm_neon; -extern const ssh_cipheralg ssh_aes256_gcm_sw; -extern const ssh_cipheralg ssh_aes256_cbc; -extern const ssh_cipheralg ssh_aes256_cbc_ni; -extern const ssh_cipheralg ssh_aes256_cbc_neon; -extern const ssh_cipheralg ssh_aes256_cbc_sw; -extern const ssh_cipheralg ssh_aes192_sdctr; -extern const ssh_cipheralg ssh_aes192_sdctr_ni; -extern const ssh_cipheralg ssh_aes192_sdctr_neon; -extern const ssh_cipheralg ssh_aes192_sdctr_sw; -extern const ssh_cipheralg ssh_aes192_gcm; -extern const ssh_cipheralg ssh_aes192_gcm_ni; -extern const ssh_cipheralg ssh_aes192_gcm_neon; -extern const ssh_cipheralg ssh_aes192_gcm_sw; -extern const ssh_cipheralg ssh_aes192_cbc; -extern const ssh_cipheralg ssh_aes192_cbc_ni; -extern const ssh_cipheralg ssh_aes192_cbc_neon; -extern const ssh_cipheralg ssh_aes192_cbc_sw; -extern const ssh_cipheralg ssh_aes128_sdctr; -extern const ssh_cipheralg ssh_aes128_sdctr_ni; -extern const ssh_cipheralg ssh_aes128_sdctr_neon; -extern const ssh_cipheralg ssh_aes128_sdctr_sw; -extern const ssh_cipheralg ssh_aes128_gcm; -extern const ssh_cipheralg ssh_aes128_gcm_ni; -extern const ssh_cipheralg ssh_aes128_gcm_neon; -extern const ssh_cipheralg ssh_aes128_gcm_sw; -extern const ssh_cipheralg ssh_aes128_cbc; -extern const ssh_cipheralg ssh_aes128_cbc_ni; -extern const ssh_cipheralg ssh_aes128_cbc_neon; -extern const ssh_cipheralg ssh_aes128_cbc_sw; -extern const ssh_cipheralg ssh_blowfish_ssh2_ctr; -extern const ssh_cipheralg ssh_blowfish_ssh2; -extern const ssh_cipheralg ssh_arcfour256_ssh2; -extern const ssh_cipheralg ssh_arcfour128_ssh2; -extern const ssh_cipheralg ssh2_chacha20_poly1305; -extern const ssh2_ciphers ssh2_3des; -extern const ssh2_ciphers ssh2_des; -extern const ssh2_ciphers ssh2_aes; -extern const ssh2_ciphers ssh2_blowfish; -extern const ssh2_ciphers ssh2_arcfour; -extern const ssh2_ciphers ssh2_ccp; -extern const ssh2_ciphers ssh2_aesgcm; -extern const ssh_hashalg ssh_md5; -extern const ssh_hashalg ssh_sha1; -extern const ssh_hashalg ssh_sha1_ni; -extern const ssh_hashalg ssh_sha1_neon; -extern const ssh_hashalg ssh_sha1_sw; -extern const ssh_hashalg ssh_sha256; -extern const ssh_hashalg ssh_sha256_ni; -extern const ssh_hashalg ssh_sha256_neon; -extern const ssh_hashalg ssh_sha256_sw; -extern const ssh_hashalg ssh_sha384; -extern const ssh_hashalg ssh_sha384_neon; -extern const ssh_hashalg ssh_sha384_sw; -extern const ssh_hashalg ssh_sha512; -extern const ssh_hashalg ssh_sha512_neon; -extern const ssh_hashalg ssh_sha512_sw; -extern const ssh_hashalg ssh_sha3_224; -extern const ssh_hashalg ssh_sha3_256; -extern const ssh_hashalg ssh_sha3_384; -extern const ssh_hashalg ssh_sha3_512; -extern const ssh_hashalg ssh_shake256_114bytes; -extern const ssh_hashalg ssh_blake2b; -extern const ssh_kexes ssh_diffiehellman_group1; -extern const ssh_kexes ssh_diffiehellman_group14; -extern const ssh_kexes ssh_diffiehellman_group15; -extern const ssh_kexes ssh_diffiehellman_group16; -extern const ssh_kexes ssh_diffiehellman_group17; -extern const ssh_kexes ssh_diffiehellman_group18; -extern const ssh_kexes ssh_diffiehellman_gex; -extern const ssh_kex ssh_diffiehellman_group1_sha1; -extern const ssh_kex ssh_diffiehellman_group14_sha256; -extern const ssh_kex ssh_diffiehellman_group14_sha1; -extern const ssh_kex ssh_diffiehellman_group15_sha512; -extern const ssh_kex ssh_diffiehellman_group16_sha512; -extern const ssh_kex ssh_diffiehellman_group17_sha512; -extern const ssh_kex ssh_diffiehellman_group18_sha512; -extern const ssh_kexes ssh_gssk5_sha1_kex; -extern const ssh_kexes ssh_gssk5_sha2_kex; -extern const ssh_kexes ssh_gssk5_ecdh_kex; -extern const ssh_kexes ssh_rsa_kex; -extern const ssh_kex ssh_ec_kex_curve25519; -extern const ssh_kex ssh_ec_kex_curve448; -extern const ssh_kex ssh_ec_kex_nistp256; -extern const ssh_kex ssh_ec_kex_nistp384; -extern const ssh_kex ssh_ec_kex_nistp521; -extern const ssh_kexes ssh_ecdh_kex; -extern const ssh_kexes ssh_ntru_hybrid_kex; -extern const ssh_keyalg ssh_dsa; -extern const ssh_keyalg ssh_rsa; -extern const ssh_keyalg ssh_rsa_sha256; -extern const ssh_keyalg ssh_rsa_sha512; -extern const ssh_keyalg ssh_ecdsa_ed25519; -extern const ssh_keyalg ssh_ecdsa_ed448; -extern const ssh_keyalg ssh_ecdsa_nistp256; -extern const ssh_keyalg ssh_ecdsa_nistp384; -extern const ssh_keyalg ssh_ecdsa_nistp521; -extern const ssh_keyalg opensshcert_ssh_dsa; -extern const ssh_keyalg opensshcert_ssh_rsa; -extern const ssh_keyalg opensshcert_ssh_rsa_sha256; -extern const ssh_keyalg opensshcert_ssh_rsa_sha512; -extern const ssh_keyalg opensshcert_ssh_ecdsa_ed25519; -extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp256; -extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp384; -extern const ssh_keyalg opensshcert_ssh_ecdsa_nistp521; -extern const ssh2_macalg ssh_hmac_md5; -extern const ssh2_macalg ssh_hmac_sha1; -extern const ssh2_macalg ssh_hmac_sha1_buggy; -extern const ssh2_macalg ssh_hmac_sha1_96; -extern const ssh2_macalg ssh_hmac_sha1_96_buggy; -extern const ssh2_macalg ssh_hmac_sha256; -extern const ssh2_macalg ssh2_poly1305; -extern const ssh2_macalg ssh2_aesgcm_mac; -extern const ssh2_macalg ssh2_aesgcm_mac_sw; -extern const ssh2_macalg ssh2_aesgcm_mac_ref_poly; -extern const ssh2_macalg ssh2_aesgcm_mac_clmul; -extern const ssh2_macalg ssh2_aesgcm_mac_neon; -extern const ssh_compression_alg ssh_zlib; - -/* Special constructor: BLAKE2b can be instantiated with any hash - * length up to 128 bytes */ -ssh_hash *blake2b_new_general(unsigned hashlen); - -/* Special test function for AES-GCM */ -void aesgcm_set_prefix_lengths(ssh2_mac *mac, size_t skip, size_t aad); - -/* - * On some systems, you have to detect hardware crypto acceleration by - * asking the local OS API rather than OS-agnostically asking the CPU - * itself. If so, then this function should be implemented in each - * platform subdirectory. - */ -bool platform_aes_neon_available(void); -bool platform_pmull_neon_available(void); -bool platform_sha256_neon_available(void); -bool platform_sha1_neon_available(void); -bool platform_sha512_neon_available(void); - -/* - * PuTTY version number formatted as an SSH version string. - */ -extern const 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 pscp.c can reach - * into the SSH code and find out which one it got. - */ -extern bool ssh_fallback_cmd(Backend *backend); - -/* - * The PRNG type, defined in prng.c. Visible data fields are - * 'savesize', which suggests how many random bytes you should request - * from a particular PRNG instance to write to putty.rnd, and a - * BinarySink implementation which you can use to write seed data in - * between calling prng_seed_{begin,finish}. - */ -struct prng { - size_t savesize; - BinarySink_IMPLEMENTATION; - /* (also there's a surrounding implementation struct in prng.c) */ -}; -prng *prng_new(const ssh_hashalg *hashalg); -void prng_free(prng *p); -void prng_seed_begin(prng *p); -void prng_seed_finish(prng *p); -void prng_read(prng *p, void *vout, size_t size); -void prng_add_entropy(prng *p, unsigned source_id, ptrlen data); -size_t prng_seed_bits(prng *p); - -/* This function must be implemented by the platform, and returns a - * timer in milliseconds that the PRNG can use to know whether it's - * been reseeded too recently to do it again. - * - * The PRNG system has its own special timing function not because its - * timing needs are unusual in the real applications, but simply so - * that testcrypt can mock it to keep the tests deterministic. */ -uint64_t prng_reseed_time_ms(void); - -void random_read(void *out, size_t size); - -/* 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 */ - bool 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; - ssh_sharing_connstate *share_cs; - share_channel *share_chan; -}; -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. - * - * If the returned pointer is NULL, then *error_msg will contain a - * dynamically allocated error message string. - */ -extern struct X11Display *x11_setup_display(const char *display, Conf *, - char **error_msg); -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); -Channel *x11_new_channel(tree234 *authtree, SshChannel *c, - const char *peeraddr, int peerport, - bool connection_sharing_possible); -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 bool 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 */ -/* X11-related helper functions in utils */ -/* - * 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); -void x11_format_auth_for_authfile( - BinarySink *bs, SockAddr *addr, int display_no, - ptrlen authproto, ptrlen authdata); -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_identify_auth_proto(ptrlen protoname); -void *x11_dehexify(ptrlen hex, int *outlen); -bool x11_parse_ip(const char *addr_string, unsigned long *ip); - -Channel *agentf_new(SshChannel *c); - -bool dh_is_gex(const ssh_kex *kex); -dh_ctx *dh_setup_group(const ssh_kex *kex); -dh_ctx *dh_setup_gex(mp_int *pval, mp_int *gval); -int dh_modulus_bit_size(const dh_ctx *ctx); -void dh_cleanup(dh_ctx *); -mp_int *dh_create_e(dh_ctx *); -const char *dh_validate_f(dh_ctx *, mp_int *f); -mp_int *dh_find_K(dh_ctx *, mp_int *f); - -static inline bool is_base64_char(char c) -{ - return ((c >= '0' && c <= '9') || - (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - c == '+' || c == '/' || c == '='); -} - -extern int base64_lines(int datalen); - -/* ppk_load_* can return this as an error */ -extern ssh2_userkey ssh2_wrong_passphrase; -#define SSH2_WRONG_PASSPHRASE (&ssh2_wrong_passphrase) - -bool ppk_encrypted_s(BinarySource *src, char **comment); -bool ppk_encrypted_f(const Filename *filename, char **comment); -bool rsa1_encrypted_s(BinarySource *src, char **comment); -bool rsa1_encrypted_f(const Filename *filename, char **comment); - -ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase, - const char **errorstr); -ssh2_userkey *ppk_load_f(const Filename *filename, const char *passphrase, - const char **errorstr); -int rsa1_load_s(BinarySource *src, RSAKey *key, - const char *passphrase, const char **errorstr); -int rsa1_load_f(const Filename *filename, RSAKey *key, - const char *passphrase, const char **errorstr); - -typedef struct ppk_save_parameters { - unsigned fmt_version; /* currently 2 or 3 */ - - /* - * Parameters for fmt_version == 3 - */ - Argon2Flavour argon2_flavour; - uint32_t argon2_mem; /* in Kbyte */ - bool argon2_passes_auto; - union { - uint32_t argon2_passes; /* if auto == false */ - uint32_t argon2_milliseconds; /* if auto == true */ - }; - uint32_t argon2_parallelism; - - /* The ability to choose a specific salt is only intended for the - * use of the automated test of PuTTYgen. It's a (mild) security - * risk to do it with any passphrase you actually care about, - * because it invalidates the entire point of having a salt in the - * first place. */ - const uint8_t *salt; - size_t saltlen; -} ppk_save_parameters; -extern const ppk_save_parameters ppk_save_default_parameters; - -strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase, - const ppk_save_parameters *params); -bool ppk_save_f(const Filename *filename, ssh2_userkey *key, - const char *passphrase, const ppk_save_parameters *params); -strbuf *rsa1_save_sb(RSAKey *key, const char *passphrase); -bool rsa1_save_f(const Filename *filename, RSAKey *key, - const char *passphrase); - -bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs, - char **commentptr, const char **errorstr); -bool ppk_loadpub_f(const Filename *filename, char **algorithm, BinarySink *bs, - char **commentptr, const char **errorstr); -int rsa1_loadpub_s(BinarySource *src, BinarySink *bs, - char **commentptr, const char **errorstr); -int rsa1_loadpub_f(const Filename *filename, BinarySink *bs, - char **commentptr, const char **errorstr); - -extern const ssh_keyalg *const all_keyalgs[]; -extern const size_t n_keyalgs; -const ssh_keyalg *find_pubkey_alg(const char *name); -const ssh_keyalg *find_pubkey_alg_len(ptrlen name); - -ptrlen pubkey_blob_to_alg_name(ptrlen blob); -const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob); - -/* Convenient wrappers on the LoadedFile mechanism suitable for key files */ -LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr); -LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr); - -enum { - SSH_KEYTYPE_UNOPENABLE, - SSH_KEYTYPE_UNKNOWN, - SSH_KEYTYPE_SSH1, SSH_KEYTYPE_SSH2, - /* - * The OpenSSH key types deserve a little explanation. OpenSSH has - * two physical formats for private key storage: an old PEM-based - * one largely dictated by their use of OpenSSL and full of ASN.1, - * and a new one using the same private key formats used over the - * wire for talking to ssh-agent. The old format can only support - * a subset of the key types, because it needs redesign for each - * key type, and after a while they decided to move to the new - * format so as not to have to do that. - * - * On input, key files are identified as either - * SSH_KEYTYPE_OPENSSH_PEM or SSH_KEYTYPE_OPENSSH_NEW, describing - * accurately which actual format the keys are stored in. - * - * On output, however, we default to following OpenSSH's own - * policy of writing out PEM-style keys for maximum backwards - * compatibility if the key type supports it, and otherwise - * switching to the new format. So the formats you can select for - * output are SSH_KEYTYPE_OPENSSH_NEW (forcing the new format for - * any key type), and SSH_KEYTYPE_OPENSSH_AUTO to use the oldest - * format supported by whatever key type you're writing out. - * - * So we have three type codes, but only two of them usable in any - * given circumstance. An input key file will never be identified - * as AUTO, only PEM or NEW; key export UIs should not be able to - * select PEM, only AUTO or NEW. - */ - SSH_KEYTYPE_OPENSSH_AUTO, - SSH_KEYTYPE_OPENSSH_PEM, - SSH_KEYTYPE_OPENSSH_NEW, - SSH_KEYTYPE_SSHCOM, - /* - * Public-key-only formats, which we still want to be able to read - * for various purposes. - */ - SSH_KEYTYPE_SSH1_PUBLIC, - SSH_KEYTYPE_SSH2_PUBLIC_RFC4716, - SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH -}; - -typedef enum { - /* Default fingerprint types strip off a certificate to show you - * the fingerprint of the underlying public key */ - SSH_FPTYPE_MD5, - SSH_FPTYPE_SHA256, - /* Non-default version of each fingerprint type which is 'raw', - * giving you the true hash of the public key blob even if it - * includes a certificate */ - SSH_FPTYPE_MD5_CERT, - SSH_FPTYPE_SHA256_CERT, -} FingerprintType; - -static inline bool ssh_fptype_is_cert(FingerprintType fptype) -{ - return fptype >= SSH_FPTYPE_MD5_CERT; -} -static inline FingerprintType ssh_fptype_from_cert(FingerprintType fptype) -{ - if (ssh_fptype_is_cert(fptype)) - fptype -= (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5); - return fptype; -} -static inline FingerprintType ssh_fptype_to_cert(FingerprintType fptype) -{ - if (!ssh_fptype_is_cert(fptype)) - fptype += (SSH_FPTYPE_MD5_CERT - SSH_FPTYPE_MD5); - return fptype; -} - -#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256_CERT + 1) -#define SSH_FPTYPE_DEFAULT SSH_FPTYPE_SHA256 - -FingerprintType ssh2_pick_fingerprint(char **fingerprints, - FingerprintType preferred_type); -FingerprintType ssh2_pick_default_fingerprint(char **fingerprints); - -char *ssh1_pubkey_str(RSAKey *ssh1key); -void ssh1_write_pubkey(FILE *fp, RSAKey *ssh1key); -char *ssh2_pubkey_openssh_str(ssh2_userkey *key); -void ssh2_write_pubkey(FILE *fp, const char *comment, - const void *v_pub_blob, int pub_len, - int keytype); -char *ssh2_fingerprint_blob(ptrlen, FingerprintType); -char *ssh2_fingerprint(ssh_key *key, FingerprintType); -char *ssh2_double_fingerprint_blob(ptrlen, FingerprintType); -char *ssh2_double_fingerprint(ssh_key *key, FingerprintType); -char **ssh2_all_fingerprints_for_blob(ptrlen); -char **ssh2_all_fingerprints(ssh_key *key); -void ssh2_free_all_fingerprints(char **); -int key_type(const Filename *filename); -int key_type_s(BinarySource *src); -const char *key_type_to_str(int type); - -bool import_possible(int type); -int import_target_type(int type); -bool import_encrypted(const Filename *filename, int type, char **comment); -bool import_encrypted_s(const Filename *filename, BinarySource *src, - int type, char **comment); -int import_ssh1(const Filename *filename, int type, - RSAKey *key, char *passphrase, const char **errmsg_p); -int import_ssh1_s(BinarySource *src, int type, - RSAKey *key, char *passphrase, const char **errmsg_p); -ssh2_userkey *import_ssh2(const Filename *filename, int type, - char *passphrase, const char **errmsg_p); -ssh2_userkey *import_ssh2_s(BinarySource *src, int type, - char *passphrase, const char **errmsg_p); -bool export_ssh1(const Filename *filename, int type, - RSAKey *key, char *passphrase); -bool export_ssh2(const Filename *filename, int type, - ssh2_userkey *key, char *passphrase); - -void des3_decrypt_pubkey(const void *key, void *blk, int len); -void des3_encrypt_pubkey(const void *key, void *blk, int len); -void des3_decrypt_pubkey_ossh(const void *key, const void *iv, - void *blk, int len); -void des3_encrypt_pubkey_ossh(const void *key, const void *iv, - void *blk, int len); -void aes256_encrypt_pubkey(const void *key, const void *iv, - void *blk, int len); -void aes256_decrypt_pubkey(const void *key, const void *iv, - void *blk, int len); - -void des_encrypt_xdmauth(const void *key, void *blk, int len); -void des_decrypt_xdmauth(const void *key, void *blk, int len); - -void openssh_bcrypt(ptrlen passphrase, ptrlen salt, - int rounds, unsigned char *out, int outbytes); - -/* - * 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, - bool can_upstream, bool can_downstream); -void platform_ssh_share_cleanup(const char *name); - -/* - * List macro defining the SSH-1 message type codes. - */ -#define SSH1_MESSAGE_TYPES(X, y) \ - X(y, SSH1_MSG_DISCONNECT, 1) \ - X(y, SSH1_SMSG_PUBLIC_KEY, 2) \ - X(y, SSH1_CMSG_SESSION_KEY, 3) \ - X(y, SSH1_CMSG_USER, 4) \ - X(y, SSH1_CMSG_AUTH_RSA, 6) \ - X(y, SSH1_SMSG_AUTH_RSA_CHALLENGE, 7) \ - X(y, SSH1_CMSG_AUTH_RSA_RESPONSE, 8) \ - X(y, SSH1_CMSG_AUTH_PASSWORD, 9) \ - X(y, SSH1_CMSG_REQUEST_PTY, 10) \ - X(y, SSH1_CMSG_WINDOW_SIZE, 11) \ - X(y, SSH1_CMSG_EXEC_SHELL, 12) \ - X(y, SSH1_CMSG_EXEC_CMD, 13) \ - X(y, SSH1_SMSG_SUCCESS, 14) \ - X(y, SSH1_SMSG_FAILURE, 15) \ - X(y, SSH1_CMSG_STDIN_DATA, 16) \ - X(y, SSH1_SMSG_STDOUT_DATA, 17) \ - X(y, SSH1_SMSG_STDERR_DATA, 18) \ - X(y, SSH1_CMSG_EOF, 19) \ - X(y, SSH1_SMSG_EXIT_STATUS, 20) \ - X(y, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION, 21) \ - X(y, SSH1_MSG_CHANNEL_OPEN_FAILURE, 22) \ - X(y, SSH1_MSG_CHANNEL_DATA, 23) \ - X(y, SSH1_MSG_CHANNEL_CLOSE, 24) \ - X(y, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION, 25) \ - X(y, SSH1_SMSG_X11_OPEN, 27) \ - X(y, SSH1_CMSG_PORT_FORWARD_REQUEST, 28) \ - X(y, SSH1_MSG_PORT_OPEN, 29) \ - X(y, SSH1_CMSG_AGENT_REQUEST_FORWARDING, 30) \ - X(y, SSH1_SMSG_AGENT_OPEN, 31) \ - X(y, SSH1_MSG_IGNORE, 32) \ - X(y, SSH1_CMSG_EXIT_CONFIRMATION, 33) \ - X(y, SSH1_CMSG_X11_REQUEST_FORWARDING, 34) \ - X(y, SSH1_CMSG_AUTH_RHOSTS_RSA, 35) \ - X(y, SSH1_MSG_DEBUG, 36) \ - X(y, SSH1_CMSG_REQUEST_COMPRESSION, 37) \ - X(y, SSH1_CMSG_AUTH_TIS, 39) \ - X(y, SSH1_SMSG_AUTH_TIS_CHALLENGE, 40) \ - X(y, SSH1_CMSG_AUTH_TIS_RESPONSE, 41) \ - X(y, SSH1_CMSG_AUTH_CCARD, 70) \ - X(y, SSH1_SMSG_AUTH_CCARD_CHALLENGE, 71) \ - X(y, SSH1_CMSG_AUTH_CCARD_RESPONSE, 72) \ - /* end of list */ - -#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 */ - -/* - * List macro defining SSH-2 message type codes. Some of these depend - * on particular contexts (i.e. a previously negotiated kex or auth - * method) - */ -#define SSH2_MESSAGE_TYPES(X, K, A, y) \ - X(y, SSH2_MSG_DISCONNECT, 1) \ - X(y, SSH2_MSG_IGNORE, 2) \ - X(y, SSH2_MSG_UNIMPLEMENTED, 3) \ - X(y, SSH2_MSG_DEBUG, 4) \ - X(y, SSH2_MSG_SERVICE_REQUEST, 5) \ - X(y, SSH2_MSG_SERVICE_ACCEPT, 6) \ - X(y, SSH2_MSG_EXT_INFO, 7) \ - X(y, SSH2_MSG_KEXINIT, 20) \ - X(y, SSH2_MSG_NEWKEYS, 21) \ - K(y, SSH2_MSG_KEXDH_INIT, 30, SSH2_PKTCTX_DHGROUP) \ - K(y, SSH2_MSG_KEXDH_REPLY, 31, SSH2_PKTCTX_DHGROUP) \ - K(y, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, 30, SSH2_PKTCTX_DHGEX) \ - K(y, SSH2_MSG_KEX_DH_GEX_REQUEST, 34, SSH2_PKTCTX_DHGEX) \ - K(y, SSH2_MSG_KEX_DH_GEX_GROUP, 31, SSH2_PKTCTX_DHGEX) \ - K(y, SSH2_MSG_KEX_DH_GEX_INIT, 32, SSH2_PKTCTX_DHGEX) \ - K(y, SSH2_MSG_KEX_DH_GEX_REPLY, 33, SSH2_PKTCTX_DHGEX) \ - K(y, SSH2_MSG_KEXGSS_INIT, 30, SSH2_PKTCTX_GSSKEX) \ - K(y, SSH2_MSG_KEXGSS_CONTINUE, 31, SSH2_PKTCTX_GSSKEX) \ - K(y, SSH2_MSG_KEXGSS_COMPLETE, 32, SSH2_PKTCTX_GSSKEX) \ - K(y, SSH2_MSG_KEXGSS_HOSTKEY, 33, SSH2_PKTCTX_GSSKEX) \ - K(y, SSH2_MSG_KEXGSS_ERROR, 34, SSH2_PKTCTX_GSSKEX) \ - K(y, SSH2_MSG_KEXGSS_GROUPREQ, 40, SSH2_PKTCTX_GSSKEX) \ - K(y, SSH2_MSG_KEXGSS_GROUP, 41, SSH2_PKTCTX_GSSKEX) \ - K(y, SSH2_MSG_KEXRSA_PUBKEY, 30, SSH2_PKTCTX_RSAKEX) \ - K(y, SSH2_MSG_KEXRSA_SECRET, 31, SSH2_PKTCTX_RSAKEX) \ - K(y, SSH2_MSG_KEXRSA_DONE, 32, SSH2_PKTCTX_RSAKEX) \ - K(y, SSH2_MSG_KEX_ECDH_INIT, 30, SSH2_PKTCTX_ECDHKEX) \ - K(y, SSH2_MSG_KEX_ECDH_REPLY, 31, SSH2_PKTCTX_ECDHKEX) \ - X(y, SSH2_MSG_USERAUTH_REQUEST, 50) \ - X(y, SSH2_MSG_USERAUTH_FAILURE, 51) \ - X(y, SSH2_MSG_USERAUTH_SUCCESS, 52) \ - X(y, SSH2_MSG_USERAUTH_BANNER, 53) \ - A(y, SSH2_MSG_USERAUTH_PK_OK, 60, SSH2_PKTCTX_PUBLICKEY) \ - A(y, SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, 60, SSH2_PKTCTX_PASSWORD) \ - A(y, SSH2_MSG_USERAUTH_INFO_REQUEST, 60, SSH2_PKTCTX_KBDINTER) \ - A(y, SSH2_MSG_USERAUTH_INFO_RESPONSE, 61, SSH2_PKTCTX_KBDINTER) \ - A(y, SSH2_MSG_USERAUTH_GSSAPI_RESPONSE, 60, SSH2_PKTCTX_GSSAPI) \ - A(y, SSH2_MSG_USERAUTH_GSSAPI_TOKEN, 61, SSH2_PKTCTX_GSSAPI) \ - A(y, SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, 63, SSH2_PKTCTX_GSSAPI) \ - A(y, SSH2_MSG_USERAUTH_GSSAPI_ERROR, 64, SSH2_PKTCTX_GSSAPI) \ - A(y, SSH2_MSG_USERAUTH_GSSAPI_ERRTOK, 65, SSH2_PKTCTX_GSSAPI) \ - A(y, SSH2_MSG_USERAUTH_GSSAPI_MIC, 66, SSH2_PKTCTX_GSSAPI) \ - X(y, SSH2_MSG_GLOBAL_REQUEST, 80) \ - X(y, SSH2_MSG_REQUEST_SUCCESS, 81) \ - X(y, SSH2_MSG_REQUEST_FAILURE, 82) \ - X(y, SSH2_MSG_CHANNEL_OPEN, 90) \ - X(y, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, 91) \ - X(y, SSH2_MSG_CHANNEL_OPEN_FAILURE, 92) \ - X(y, SSH2_MSG_CHANNEL_WINDOW_ADJUST, 93) \ - X(y, SSH2_MSG_CHANNEL_DATA, 94) \ - X(y, SSH2_MSG_CHANNEL_EXTENDED_DATA, 95) \ - X(y, SSH2_MSG_CHANNEL_EOF, 96) \ - X(y, SSH2_MSG_CHANNEL_CLOSE, 97) \ - X(y, SSH2_MSG_CHANNEL_REQUEST, 98) \ - X(y, SSH2_MSG_CHANNEL_SUCCESS, 99) \ - X(y, SSH2_MSG_CHANNEL_FAILURE, 100) \ - /* end of list */ - -#define DEF_ENUM_UNIVERSAL(y, name, value) name = value, -#define DEF_ENUM_CONTEXTUAL(y, name, value, context) name = value, -enum { - SSH1_MESSAGE_TYPES(DEF_ENUM_UNIVERSAL, y) - SSH2_MESSAGE_TYPES(DEF_ENUM_UNIVERSAL, - DEF_ENUM_CONTEXTUAL, DEF_ENUM_CONTEXTUAL, y) - /* Virtual packet type, for packets too short to even have a type */ - SSH_MSG_NO_TYPE_CODE = 256 -}; -#undef DEF_ENUM_UNIVERSAL -#undef DEF_ENUM_CONTEXTUAL - -/* - * 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 -#define SSH2_AGENTC_EXTENSION 27 -#define SSH_AGENT_EXTENSION_FAILURE 28 - -/* - * 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 */ - -enum { - /* TTY modes with opcodes defined consistently in the SSH specs. */ - #define TTYMODE_CHAR(name, val, index) SSH_TTYMODE_##name = val, - #define TTYMODE_FLAG(name, val, field, mask) SSH_TTYMODE_##name = val, - #include "ssh/ttymode-list.h" - #undef TTYMODE_CHAR - #undef TTYMODE_FLAG - - /* Modes encoded differently between SSH-1 and SSH-2, for which we - * make up our own dummy opcodes to avoid confusion. */ - TTYMODE_dummy = 255, - TTYMODE_ISPEED, TTYMODE_OSPEED, - - /* Limiting value that we can use as an array bound below */ - TTYMODE_LIMIT, - - /* The real opcodes for terminal speeds. */ - TTYMODE_ISPEED_SSH1 = 192, - TTYMODE_OSPEED_SSH1 = 193, - TTYMODE_ISPEED_SSH2 = 128, - TTYMODE_OSPEED_SSH2 = 129, - - /* And the opcode that ends a list. */ - TTYMODE_END_OF_LIST = 0 -}; - -struct ssh_ttymodes { - /* A boolean per mode, indicating whether it's set. */ - bool have_mode[TTYMODE_LIMIT]; - - /* The actual value for each mode. */ - unsigned mode_val[TTYMODE_LIMIT]; -}; - -struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf); -struct ssh_ttymodes read_ttymodes_from_packet( - BinarySource *bs, int ssh_version); -void write_ttymodes_to_packet(BinarySink *bs, int ssh_version, - struct ssh_ttymodes modes); - -const char *ssh1_pkt_type(int type); -const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type); -bool ssh2_pkt_type_code_valid(unsigned type); - -/* - * Need this to warn about support for the original SSH-2 keyfile - * format. - */ -void old_keyfile_warning(void); - -/* - * Flags indicating implementation bugs that we know how to mitigate - * if we think the other end has them. - */ -#define SSH_IMPL_BUG_LIST(X) \ - X(BUG_CHOKES_ON_SSH1_IGNORE) \ - X(BUG_SSH2_HMAC) \ - X(BUG_NEEDS_SSH1_PLAIN_PASSWORD) \ - X(BUG_CHOKES_ON_RSA) \ - X(BUG_SSH2_RSA_PADDING) \ - X(BUG_SSH2_DERIVEKEY) \ - X(BUG_SSH2_REKEY) \ - X(BUG_SSH2_PK_SESSIONID) \ - X(BUG_SSH2_MAXPKT) \ - X(BUG_CHOKES_ON_SSH2_IGNORE) \ - X(BUG_CHOKES_ON_WINADJ) \ - X(BUG_SENDS_LATE_REQUEST_REPLY) \ - X(BUG_SSH2_OLDGEX) \ - X(BUG_REQUIRES_FILTERED_KEXINIT) \ - /* end of list */ -#define TMP_DECLARE_LOG2_ENUM(thing) log2_##thing, -enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_LOG2_ENUM) }; -#undef TMP_DECLARE_LOG2_ENUM -#define TMP_DECLARE_REAL_ENUM(thing) thing = 1 << log2_##thing, -enum { SSH_IMPL_BUG_LIST(TMP_DECLARE_REAL_ENUM) }; -#undef TMP_DECLARE_REAL_ENUM - -/* Shared system for allocating local SSH channel ids. Expects to be - * passed a tree full of structs that have a field called 'localid' of - * type unsigned, and will check that! */ -unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset); -#define alloc_channel_id(tree, type) \ - TYPECHECK(&((type *)0)->localid == (unsigned *)0, \ - alloc_channel_id_general(tree, offsetof(type, localid))) - -void add_to_commasep(strbuf *buf, const char *data); -void add_to_commasep_pl(strbuf *buf, ptrlen data); -bool get_commasep_word(ptrlen *list, ptrlen *word); - -SeatPromptResult verify_ssh_host_key( - InteractionReadySeat iseat, Conf *conf, const char *host, int port, - ssh_key *key, const char *keytype, char *keystr, const char *keydisp, - char **fingerprints, int ca_count, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); - -typedef struct ssh_transient_hostkey_cache ssh_transient_hostkey_cache; -ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void); -void ssh_transient_hostkey_cache_free(ssh_transient_hostkey_cache *thc); -void ssh_transient_hostkey_cache_add( - ssh_transient_hostkey_cache *thc, ssh_key *key); -bool ssh_transient_hostkey_cache_verify( - ssh_transient_hostkey_cache *thc, ssh_key *key); -bool ssh_transient_hostkey_cache_has( - ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg); -bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc); - -/* - * Protocol definitions for authentication helper plugins - */ - -#define AUTHPLUGIN_MSG_NAMES(X) \ - X(PLUGIN_INIT, 1) \ - X(PLUGIN_INIT_RESPONSE, 2) \ - X(PLUGIN_PROTOCOL, 3) \ - X(PLUGIN_PROTOCOL_ACCEPT, 4) \ - X(PLUGIN_PROTOCOL_REJECT, 5) \ - X(PLUGIN_AUTH_SUCCESS, 6) \ - X(PLUGIN_AUTH_FAILURE, 7) \ - X(PLUGIN_INIT_FAILURE, 8) \ - X(PLUGIN_KI_SERVER_REQUEST, 20) \ - X(PLUGIN_KI_SERVER_RESPONSE, 21) \ - X(PLUGIN_KI_USER_REQUEST, 22) \ - X(PLUGIN_KI_USER_RESPONSE, 23) \ - /* end of list */ - -#define PLUGIN_PROTOCOL_MAX_VERSION 2 /* the highest version we speak */ - -enum { - #define ENUMDECL(name, value) name = value, - AUTHPLUGIN_MSG_NAMES(ENUMDECL) - #undef ENUMDECL - - /* Error codes internal to this implementation, indicating failure - * to receive a meaningful packet at all */ - PLUGIN_NOTYPE = 256, /* packet too short to have a type */ - PLUGIN_EOF = 257 /* EOF from auth plugin */ -}; diff --git a/ssh/CMakeLists.txt b/ssh/CMakeLists.txt deleted file mode 100644 index d2b35311a..000000000 --- a/ssh/CMakeLists.txt +++ /dev/null @@ -1,52 +0,0 @@ -add_library(sshcommon OBJECT - bpp1.c - bpp2.c - bpp-bare.c - ca-config.c - censor1.c - censor2.c - common.c - connection1.c - connection2.c - crc-attack-detector.c - gssc.c - login1.c - pgssapi.c - portfwd.c - ../sshpubk.c - ../sshrand.c - transient-hostkey-cache.c - transport2.c - verstring.c - x11fwd.c - zlib.c) - -add_library(sftpcommon OBJECT sftpcommon.c) - -add_library(sshclient STATIC - agentf.c - connection1-client.c - connection2-client.c - kex2-client.c - mainchan.c - sharing.c - ssh.c - userauth2-client.c - $ - $ - $) - -add_library(sshserver STATIC - connection1-server.c - connection2-server.c - kex2-server.c - login1-server.c - server.c - sesschan.c - sftpserver.c - userauth2-server.c - $ - $) - -add_sources_from_current_dir(sftpclient sftp.c) -target_sources(sftpclient PRIVATE $) diff --git a/ssh/agentf.c b/ssh/agentf.c deleted file mode 100644 index 6a5ecee54..000000000 --- a/ssh/agentf.c +++ /dev/null @@ -1,249 +0,0 @@ -/* - * SSH agent forwarding. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "pageant.h" -#include "channel.h" - -typedef struct agentf { - SshChannel *c; - bufchain inbuffer; - agent_pending_query *pending; - bool input_wanted; - bool rcvd_eof; - - Channel chan; -} agentf; - -static void agentf_got_response(agentf *af, void *reply, int replylen) -{ - af->pending = NULL; - - if (!reply) { - /* The real agent didn't send any kind of reply at all for - * some reason, so fake an SSH_AGENT_FAILURE. */ - reply = "\0\0\0\1\5"; - replylen = 5; - } - - sshfwd_write(af->c, reply, replylen); -} - -static void agentf_callback(void *vctx, void *reply, int replylen); - -static void agentf_try_forward(agentf *af) -{ - size_t datalen, length; - strbuf *message; - unsigned char msglen[4]; - void *reply; - int replylen; - - /* - * Don't try to parallelise agent requests. Wait for each one to - * return before attempting the next. - */ - if (af->pending) - return; - - /* - * If the outgoing side of the channel connection is currently - * throttled, don't submit any new forwarded requests to the real - * agent. This causes the input side of the agent forwarding not - * to be emptied, exerting the required back-pressure on the - * remote client, and encouraging it to read our responses before - * sending too many more requests. - */ - if (!af->input_wanted) - return; - - while (1) { - /* - * Try to extract a complete message from the input buffer. - */ - datalen = bufchain_size(&af->inbuffer); - if (datalen < 4) - break; /* not even a length field available yet */ - - bufchain_fetch(&af->inbuffer, msglen, 4); - length = GET_32BIT_MSB_FIRST(msglen); - - if (length > AGENT_MAX_MSGLEN-4) { - /* - * If the remote has sent a message that's just _too_ - * long, we should reject it in advance of seeing the rest - * of the incoming message, and also close the connection - * for good measure (which avoids us having to faff about - * with carefully ignoring just the right number of bytes - * from the overlong message). - */ - agentf_got_response(af, NULL, 0); - sshfwd_write_eof(af->c); - return; - } - - if (length > datalen - 4) - break; /* a whole message is not yet available */ - - bufchain_consume(&af->inbuffer, 4); - - message = strbuf_new_for_agent_query(); - bufchain_fetch_consume( - &af->inbuffer, strbuf_append(message, length), length); - af->pending = agent_query( - message, &reply, &replylen, agentf_callback, af); - strbuf_free(message); - - if (af->pending) - return; /* agent_query promised to reply in due course */ - - /* - * If the agent gave us an answer immediately, pass it - * straight on and go round this loop again. - */ - agentf_got_response(af, reply, replylen); - sfree(reply); - } - - /* - * If we get here (i.e. we left the above while loop via 'break' - * rather than 'return'), that means we've determined that the - * input buffer for the agent forwarding connection doesn't - * contain a complete request. - * - * So if there's potentially more data to come, we can return now, - * and wait for the remote client to send it. But if the remote - * has sent EOF, it would be a mistake to do that, because we'd be - * waiting a long time. So this is the moment to check for EOF, - * and respond appropriately. - */ - if (af->rcvd_eof) - sshfwd_write_eof(af->c); -} - -static void agentf_callback(void *vctx, void *reply, int replylen) -{ - agentf *af = (agentf *)vctx; - - agentf_got_response(af, reply, replylen); - sfree(reply); - - /* - * Now try to extract and send further messages from the channel's - * input-side buffer. - */ - agentf_try_forward(af); -} - -static void agentf_free(Channel *chan); -static size_t agentf_send(Channel *chan, bool is_stderr, const void *, size_t); -static void agentf_send_eof(Channel *chan); -static char *agentf_log_close_msg(Channel *chan); -static void agentf_set_input_wanted(Channel *chan, bool wanted); - -static const ChannelVtable agentf_channelvt = { - .free = agentf_free, - .open_confirmation = chan_remotely_opened_confirmation, - .open_failed = chan_remotely_opened_failure, - .send = agentf_send, - .send_eof = agentf_send_eof, - .set_input_wanted = agentf_set_input_wanted, - .log_close_msg = agentf_log_close_msg, - .want_close = chan_default_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = chan_no_request_response, -}; - -Channel *agentf_new(SshChannel *c) -{ - agentf *af = snew(agentf); - af->c = c; - af->chan.vt = &agentf_channelvt; - af->chan.initial_fixed_window_size = 0; - af->rcvd_eof = false; - bufchain_init(&af->inbuffer); - af->pending = NULL; - af->input_wanted = true; - return &af->chan; -} - -static void agentf_free(Channel *chan) -{ - assert(chan->vt == &agentf_channelvt); - agentf *af = container_of(chan, agentf, chan); - - if (af->pending) - agent_cancel_query(af->pending); - bufchain_clear(&af->inbuffer); - sfree(af); -} - -static size_t agentf_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - assert(chan->vt == &agentf_channelvt); - agentf *af = container_of(chan, agentf, chan); - bufchain_add(&af->inbuffer, data, length); - agentf_try_forward(af); - - /* - * We exert back-pressure on an agent forwarding client if and - * only if we're waiting for the response to an asynchronous agent - * request. This prevents the client running out of window while - * receiving the _first_ message, but means that if any message - * takes time to process, the client will be discouraged from - * sending an endless stream of further ones after it. - */ - return (af->pending ? bufchain_size(&af->inbuffer) : 0); -} - -static void agentf_send_eof(Channel *chan) -{ - assert(chan->vt == &agentf_channelvt); - agentf *af = container_of(chan, agentf, chan); - - af->rcvd_eof = true; - - /* Call try_forward, which will respond to the EOF now if - * appropriate, or wait until the queue of outstanding requests is - * dealt with if not. */ - agentf_try_forward(af); -} - -static char *agentf_log_close_msg(Channel *chan) -{ - return dupstr("Agent-forwarding connection closed"); -} - -static void agentf_set_input_wanted(Channel *chan, bool wanted) -{ - assert(chan->vt == &agentf_channelvt); - agentf *af = container_of(chan, agentf, chan); - - af->input_wanted = wanted; - - /* Agent forwarding channels are buffer-managed by not asking the - * agent questions if the SSH channel isn't accepting input. So if - * it's started again, we should ask a question if we have one - * pending.. */ - if (wanted) - agentf_try_forward(af); -} diff --git a/ssh/bpp-bare.c b/ssh/bpp-bare.c deleted file mode 100644 index f1a889aad..000000000 --- a/ssh/bpp-bare.c +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Trivial binary packet protocol for the 'bare' ssh-connection - * protocol used in PuTTY's SSH-2 connection sharing system. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "sshcr.h" - -struct ssh2_bare_bpp_state { - int crState; - long packetlen, maxlen; - unsigned char *data; - unsigned long incoming_sequence, outgoing_sequence; - PktIn *pktin; - - BinaryPacketProtocol bpp; -}; - -static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp); -static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp); -static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp); -static PktOut *ssh2_bare_bpp_new_pktout(int type); - -static const BinaryPacketProtocolVtable ssh2_bare_bpp_vtable = { - .free = ssh2_bare_bpp_free, - .handle_input = ssh2_bare_bpp_handle_input, - .handle_output = ssh2_bare_bpp_handle_output, - .new_pktout = ssh2_bare_bpp_new_pktout, - .queue_disconnect = ssh2_bpp_queue_disconnect, /* in common.c */ - - /* packet size limit, per protocol spec in sharing.c comment */ - .packet_size_limit = 0x4000, -}; - -BinaryPacketProtocol *ssh2_bare_bpp_new(LogContext *logctx) -{ - struct ssh2_bare_bpp_state *s = snew(struct ssh2_bare_bpp_state); - memset(s, 0, sizeof(*s)); - s->bpp.vt = &ssh2_bare_bpp_vtable; - s->bpp.logctx = logctx; - ssh_bpp_common_setup(&s->bpp); - return &s->bpp; -} - -static void ssh2_bare_bpp_free(BinaryPacketProtocol *bpp) -{ - struct ssh2_bare_bpp_state *s = - container_of(bpp, struct ssh2_bare_bpp_state, bpp); - sfree(s->pktin); - sfree(s); -} - -#define BPP_READ(ptr, len) do \ - { \ - bool success; \ - crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \ - s->bpp.in_raw, ptr, len)) || \ - s->bpp.input_eof); \ - if (!success) \ - goto eof; \ - ssh_check_frozen(s->bpp.ssh); \ - } while (0) - -static void ssh2_bare_bpp_handle_input(BinaryPacketProtocol *bpp) -{ - struct ssh2_bare_bpp_state *s = - container_of(bpp, struct ssh2_bare_bpp_state, bpp); - - crBegin(s->crState); - - while (1) { - /* Read the length field. */ - { - unsigned char lenbuf[4]; - BPP_READ(lenbuf, 4); - s->packetlen = toint(GET_32BIT_MSB_FIRST(lenbuf)); - } - - if (s->packetlen <= 0 || s->packetlen >= (long)OUR_V2_PACKETLIMIT) { - ssh_sw_abort(s->bpp.ssh, "Invalid packet length received"); - crStopV; - } - - /* - * Allocate the packet to return, now we know its length. - */ - s->pktin = snew_plus(PktIn, s->packetlen); - s->pktin->qnode.prev = s->pktin->qnode.next = NULL; - s->pktin->qnode.on_free_queue = false; - s->maxlen = 0; - s->data = snew_plus_get_aux(s->pktin); - - s->pktin->sequence = s->incoming_sequence++; - - /* - * Read the remainder of the packet. - */ - BPP_READ(s->data, s->packetlen); - - /* - * The data we just read is precisely the initial type byte - * followed by the packet payload. - */ - s->pktin->type = s->data[0]; - s->data++; - s->packetlen--; - BinarySource_INIT(s->pktin, s->data, s->packetlen); - - if (s->pktin->type == SSH2_MSG_EXT_INFO) { - /* - * Mild layer violation: EXT_INFO is not permitted in the - * bare ssh-connection protocol. Faulting it here means - * that ssh2_common_filter_queue doesn't receive it in the - * first place unless it's legal to have sent it. - */ - ssh_proto_error(s->bpp.ssh, "Remote side sent SSH2_MSG_EXT_INFO " - "in bare connection protocol"); - return; - } - - /* - * Log incoming packet, possibly omitting sensitive fields. - */ - if (s->bpp.logctx) { - logblank_t blanks[MAX_BLANKS]; - int nblanks = ssh2_censor_packet( - s->bpp.pls, s->pktin->type, false, - make_ptrlen(s->data, s->packetlen), blanks); - log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, - ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, - s->pktin->type), - get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks, - &s->pktin->sequence, 0, NULL); - } - - if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) { - sfree(s->pktin); - s->pktin = NULL; - continue; - } - - s->pktin->qnode.formal_size = get_avail(s->pktin); - pq_push(&s->bpp.in_pq, s->pktin); - s->pktin = NULL; - } - - eof: - if (!s->bpp.expect_close) { - ssh_remote_error(s->bpp.ssh, - "Remote side unexpectedly closed network connection"); - } else { - ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection"); - } - return; /* avoid touching s now it's been freed */ - - crFinishV; -} - -static PktOut *ssh2_bare_bpp_new_pktout(int pkt_type) -{ - PktOut *pkt = ssh_new_packet(); - pkt->length = 4; /* space for packet length */ - pkt->type = pkt_type; - put_byte(pkt, pkt_type); - return pkt; -} - -static void ssh2_bare_bpp_format_packet(struct ssh2_bare_bpp_state *s, - PktOut *pkt) -{ - if (s->bpp.logctx) { - ptrlen pktdata = make_ptrlen(pkt->data + 5, pkt->length - 5); - logblank_t blanks[MAX_BLANKS]; - int nblanks = ssh2_censor_packet( - s->bpp.pls, pkt->type, true, pktdata, blanks); - log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, - ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, - pkt->type), - pktdata.ptr, pktdata.len, nblanks, blanks, - &s->outgoing_sequence, - pkt->downstream_id, pkt->additional_log_text); - } - - s->outgoing_sequence++; /* only for diagnostics, really */ - - PUT_32BIT_MSB_FIRST(pkt->data, pkt->length - 4); - bufchain_add(s->bpp.out_raw, pkt->data, pkt->length); -} - -static void ssh2_bare_bpp_handle_output(BinaryPacketProtocol *bpp) -{ - struct ssh2_bare_bpp_state *s = - container_of(bpp, struct ssh2_bare_bpp_state, bpp); - PktOut *pkt; - - while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) { - ssh2_bare_bpp_format_packet(s, pkt); - ssh_free_pktout(pkt); - } - - ssh_sendbuffer_changed(bpp->ssh); -} diff --git a/ssh/bpp.h b/ssh/bpp.h deleted file mode 100644 index 23af52368..000000000 --- a/ssh/bpp.h +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Abstraction of the binary packet protocols used in SSH. - */ - -#ifndef PUTTY_SSHBPP_H -#define PUTTY_SSHBPP_H - -typedef struct BinaryPacketProtocolVtable BinaryPacketProtocolVtable; - -struct BinaryPacketProtocolVtable { - void (*free)(BinaryPacketProtocol *); - void (*handle_input)(BinaryPacketProtocol *); - void (*handle_output)(BinaryPacketProtocol *); - PktOut *(*new_pktout)(int type); - void (*queue_disconnect)(BinaryPacketProtocol *, - const char *msg, int category); - uint32_t packet_size_limit; -}; - -struct BinaryPacketProtocol { - const struct BinaryPacketProtocolVtable *vt; - bufchain *in_raw, *out_raw; - bool input_eof; /* set this if in_raw will never be added to again */ - PktInQueue in_pq; - PktOutQueue out_pq; - PacketLogSettings *pls; - LogContext *logctx; - Ssh *ssh; - - /* ic_in_raw is filled in by the BPP (probably by calling - * ssh_bpp_common_setup). The BPP's owner triggers it when data is - * added to in_raw, and also when the BPP is newly created. */ - IdempotentCallback ic_in_raw; - - /* ic_out_pq is entirely internal to the BPP itself; it's used as - * the callback on out_pq. */ - IdempotentCallback ic_out_pq; - - /* Information that all packet layers sharing this BPP will - * potentially be interested in. */ - int remote_bugs; - bool ext_info_rsa_sha256_ok, ext_info_rsa_sha512_ok; - - /* Set this if remote connection closure should not generate an - * error message (either because it's not to be treated as an - * error at all, or because some other error message has already - * been emitted). */ - bool expect_close; -}; - -static inline void ssh_bpp_handle_input(BinaryPacketProtocol *bpp) -{ bpp->vt->handle_input(bpp); } -static inline void ssh_bpp_handle_output(BinaryPacketProtocol *bpp) -{ bpp->vt->handle_output(bpp); } -static inline PktOut *ssh_bpp_new_pktout(BinaryPacketProtocol *bpp, int type) -{ return bpp->vt->new_pktout(type); } -static inline void ssh_bpp_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category) -{ bpp->vt->queue_disconnect(bpp, msg, category); } - -/* ssh_bpp_free is more than just a macro wrapper on the vtable; it - * does centralised parts of the freeing too. */ -void ssh_bpp_free(BinaryPacketProtocol *bpp); - -BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx); -void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp, - const ssh_cipheralg *cipher, - const void *session_key); -/* This is only called from outside the BPP in server mode; in client - * mode the BPP detects compression start time automatically by - * snooping message types */ -void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp); - -/* Helper routine which does common BPP initialisation, e.g. setting - * up in_pq and out_pq, and initialising input_consumer. */ -void ssh_bpp_common_setup(BinaryPacketProtocol *); - -/* Common helper functions between the SSH-2 full and bare BPPs */ -void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category); -bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin); - -/* Convenience macro for BPPs to send formatted strings to the Event - * Log. Assumes a function parameter called 'bpp' is in scope. */ -#define bpp_logevent(...) ( \ - logevent_and_free((bpp)->logctx, dupprintf(__VA_ARGS__))) - -/* - * Structure that tracks how much data is sent and received, for - * purposes of triggering an SSH-2 rekey when either one gets over a - * configured limit. In each direction, the flag 'running' indicates - * that we haven't hit the limit yet, and 'remaining' tracks how much - * longer until we do. The function dts_consume() subtracts a given - * amount from the counter in a particular direction, and sets - * 'expired' if the limit has been hit. - * - * The limit is sticky: once 'running' has flipped to false, - * 'remaining' is no longer decremented, so it shouldn't dangerously - * wrap round. - */ -struct DataTransferStatsDirection { - bool running, expired; - unsigned long remaining; -}; -struct DataTransferStats { - struct DataTransferStatsDirection in, out; -}; -static inline void dts_consume(struct DataTransferStatsDirection *s, - unsigned long size_consumed) -{ - if (s->running) { - if (s->remaining <= size_consumed) { - s->running = false; - s->expired = true; - } else { - s->remaining -= size_consumed; - } - } -} -static inline void dts_reset(struct DataTransferStatsDirection *s, - unsigned long starting_size) -{ - s->expired = false; - s->remaining = starting_size; - /* - * The semantics of setting CONF_ssh_rekey_data to zero are to - * disable data-volume based rekeying completely. So if the - * starting size is actually zero, we don't set 'running' to true - * in the first place, which means we won't ever set the expired - * flag. - */ - s->running = (starting_size != 0); -} - -BinaryPacketProtocol *ssh2_bpp_new( - LogContext *logctx, struct DataTransferStats *stats, bool is_server); -void ssh2_bpp_new_outgoing_crypto( - BinaryPacketProtocol *bpp, - const ssh_cipheralg *cipher, const void *ckey, const void *iv, - const ssh2_macalg *mac, bool etm_mode, const void *mac_key, - const ssh_compression_alg *compression, bool delayed_compression, - bool reset_sequence_number); -void ssh2_bpp_new_incoming_crypto( - BinaryPacketProtocol *bpp, - const ssh_cipheralg *cipher, const void *ckey, const void *iv, - const ssh2_macalg *mac, bool etm_mode, const void *mac_key, - const ssh_compression_alg *compression, bool delayed_compression, - bool reset_sequence_number); - -/* - * A query method specific to the interface between ssh2transport and - * ssh2bpp. If true, it indicates that we're potentially in the - * race-condition-prone part of delayed compression setup and so - * asynchronous outgoing transport-layer packets are currently not - * being sent, which means in particular that it would be a bad idea - * to start a rekey because then we'd stop responding to anything - * _other_ than transport-layer packets and deadlock the protocol. - */ -bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp); - -BinaryPacketProtocol *ssh2_bare_bpp_new(LogContext *logctx); - -/* - * The initial code to handle the SSH version exchange is also - * structured as an implementation of BinaryPacketProtocol, because - * that makes it easy to switch from that to the next BPP once it - * tells us which one we're using. - */ -struct ssh_version_receiver { - void (*got_ssh_version)(struct ssh_version_receiver *rcv, - int major_version); -}; -BinaryPacketProtocol *ssh_verstring_new( - Conf *conf, LogContext *logctx, bool bare_connection_mode, - const char *protoversion, struct ssh_version_receiver *rcv, - bool server_mode, const char *impl_name); -const char *ssh_verstring_get_remote(BinaryPacketProtocol *); -const char *ssh_verstring_get_local(BinaryPacketProtocol *); -int ssh_verstring_get_bugs(BinaryPacketProtocol *); - -#endif /* PUTTY_SSHBPP_H */ diff --git a/ssh/bpp1.c b/ssh/bpp1.c deleted file mode 100644 index b82932f77..000000000 --- a/ssh/bpp1.c +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Binary packet protocol for SSH-1. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "sshcr.h" - -struct ssh1_bpp_state { - int crState; - long len, pad, biglen, length, maxlen; - unsigned char *data; - uint32_t realcrc, gotcrc; - int chunk; - PktIn *pktin; - - ssh_cipher *cipher_in, *cipher_out; - - struct crcda_ctx *crcda_ctx; - uint8_t iv[8]; /* for crcda */ - - bool pending_compression_request; - ssh_compressor *compctx; - ssh_decompressor *decompctx; - - BinaryPacketProtocol bpp; -}; - -static void ssh1_bpp_free(BinaryPacketProtocol *bpp); -static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp); -static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp); -static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category); -static PktOut *ssh1_bpp_new_pktout(int type); - -static const BinaryPacketProtocolVtable ssh1_bpp_vtable = { - .free = ssh1_bpp_free, - .handle_input = ssh1_bpp_handle_input, - .handle_output = ssh1_bpp_handle_output, - .new_pktout = ssh1_bpp_new_pktout, - .queue_disconnect = ssh1_bpp_queue_disconnect, - .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */ -}; - -BinaryPacketProtocol *ssh1_bpp_new(LogContext *logctx) -{ - struct ssh1_bpp_state *s = snew(struct ssh1_bpp_state); - memset(s, 0, sizeof(*s)); - s->bpp.vt = &ssh1_bpp_vtable; - s->bpp.logctx = logctx; - ssh_bpp_common_setup(&s->bpp); - return &s->bpp; -} - -static void ssh1_bpp_free(BinaryPacketProtocol *bpp) -{ - struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp); - if (s->cipher_in) - ssh_cipher_free(s->cipher_in); - if (s->cipher_out) - ssh_cipher_free(s->cipher_out); - if (s->compctx) - ssh_compressor_free(s->compctx); - if (s->decompctx) - ssh_decompressor_free(s->decompctx); - if (s->crcda_ctx) - crcda_free_context(s->crcda_ctx); - sfree(s->pktin); - sfree(s); -} - -void ssh1_bpp_new_cipher(BinaryPacketProtocol *bpp, - const ssh_cipheralg *cipher, - const void *session_key) -{ - struct ssh1_bpp_state *s; - assert(bpp->vt == &ssh1_bpp_vtable); - s = container_of(bpp, struct ssh1_bpp_state, bpp); - - assert(!s->cipher_in); - assert(!s->cipher_out); - - if (cipher) { - s->cipher_in = ssh_cipher_new(cipher); - s->cipher_out = ssh_cipher_new(cipher); - ssh_cipher_setkey(s->cipher_in, session_key); - ssh_cipher_setkey(s->cipher_out, session_key); - - assert(!s->crcda_ctx); - s->crcda_ctx = crcda_make_context(); - - bpp_logevent("Initialised %s encryption", cipher->text_name); - - memset(s->iv, 0, sizeof(s->iv)); - - assert(cipher->blksize <= sizeof(s->iv)); - ssh_cipher_setiv(s->cipher_in, s->iv); - ssh_cipher_setiv(s->cipher_out, s->iv); - } -} - -void ssh1_bpp_start_compression(BinaryPacketProtocol *bpp) -{ - struct ssh1_bpp_state *s; - assert(bpp->vt == &ssh1_bpp_vtable); - s = container_of(bpp, struct ssh1_bpp_state, bpp); - - assert(!s->compctx); - assert(!s->decompctx); - - s->compctx = ssh_compressor_new(&ssh_zlib); - s->decompctx = ssh_decompressor_new(&ssh_zlib); - - bpp_logevent("Started zlib (RFC1950) compression"); -} - -#define BPP_READ(ptr, len) do \ - { \ - bool success; \ - crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \ - s->bpp.in_raw, ptr, len)) || \ - s->bpp.input_eof); \ - if (!success) \ - goto eof; \ - ssh_check_frozen(s->bpp.ssh); \ - } while (0) - -static void ssh1_bpp_handle_input(BinaryPacketProtocol *bpp) -{ - struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp); - - crBegin(s->crState); - - while (1) { - s->maxlen = 0; - s->length = 0; - - { - unsigned char lenbuf[4]; - BPP_READ(lenbuf, 4); - s->len = toint(GET_32BIT_MSB_FIRST(lenbuf)); - } - - if (s->len < 5 || s->len > 262144) { /* SSH1.5-mandated max size */ - ssh_sw_abort(s->bpp.ssh, - "Out-of-range packet length from remote suggests" - " data stream corruption"); - crStopV; - } - - s->pad = 8 - (s->len % 8); - s->biglen = s->len + s->pad; - s->length = s->len - 5; - - /* - * Allocate the packet to return, now we know its length. - */ - s->pktin = snew_plus(PktIn, s->biglen); - s->pktin->qnode.prev = s->pktin->qnode.next = NULL; - s->pktin->qnode.on_free_queue = false; - s->pktin->type = 0; - - s->maxlen = s->biglen; - s->data = snew_plus_get_aux(s->pktin); - - BPP_READ(s->data, s->biglen); - - if (s->cipher_in && detect_attack(s->crcda_ctx, - s->data, s->biglen, s->iv)) { - ssh_sw_abort(s->bpp.ssh, - "Network attack (CRC compensation) detected!"); - crStopV; - } - /* Save the last cipher block, to be passed to the next call - * to detect_attack */ - assert(s->biglen >= 8); - memcpy(s->iv, s->data + s->biglen - 8, sizeof(s->iv)); - - if (s->cipher_in) - ssh_cipher_decrypt(s->cipher_in, s->data, s->biglen); - - s->realcrc = crc32_ssh1(make_ptrlen(s->data, s->biglen - 4)); - s->gotcrc = GET_32BIT_MSB_FIRST(s->data + s->biglen - 4); - if (s->gotcrc != s->realcrc) { - ssh_sw_abort(s->bpp.ssh, "Incorrect CRC received on packet"); - crStopV; - } - - if (s->decompctx) { - unsigned char *decompblk; - int decomplen; - if (!ssh_decompressor_decompress( - s->decompctx, s->data + s->pad, s->length + 1, - &decompblk, &decomplen)) { - ssh_sw_abort(s->bpp.ssh, - "Zlib decompression encountered invalid data"); - crStopV; - } - - if (s->maxlen < s->pad + decomplen) { - PktIn *old_pktin = s->pktin; - - s->maxlen = s->pad + decomplen; - s->pktin = snew_plus(PktIn, s->maxlen); - *s->pktin = *old_pktin; /* structure copy */ - s->data = snew_plus_get_aux(s->pktin); - - smemclr(old_pktin, s->biglen); - sfree(old_pktin); - } - - memcpy(s->data + s->pad, decompblk, decomplen); - sfree(decompblk); - s->length = decomplen - 1; - } - - /* - * Now we can find the bounds of the semantic content of the - * packet, and the initial type byte. - */ - s->data += s->pad; - s->pktin->type = *s->data++; - BinarySource_INIT(s->pktin, s->data, s->length); - - if (s->bpp.logctx) { - logblank_t blanks[MAX_BLANKS]; - int nblanks = ssh1_censor_packet( - s->bpp.pls, s->pktin->type, false, - make_ptrlen(s->data, s->length), blanks); - log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, - ssh1_pkt_type(s->pktin->type), - get_ptr(s->pktin), get_avail(s->pktin), nblanks, blanks, - NULL, 0, NULL); - } - - s->pktin->qnode.formal_size = get_avail(s->pktin); - pq_push(&s->bpp.in_pq, s->pktin); - - { - int type = s->pktin->type; - s->pktin = NULL; - - switch (type) { - case SSH1_SMSG_SUCCESS: - case SSH1_SMSG_FAILURE: - if (s->pending_compression_request) { - /* - * This is the response to - * SSH1_CMSG_REQUEST_COMPRESSION. - */ - if (type == SSH1_SMSG_SUCCESS) { - /* - * If the response was positive, start - * compression. - */ - ssh1_bpp_start_compression(&s->bpp); - } - - /* - * Either way, cancel the pending flag, and - * schedule a run of our output side in case we - * had any packets queued up in the meantime. - */ - s->pending_compression_request = false; - queue_idempotent_callback(&s->bpp.ic_out_pq); - } - break; - } - } - } - - eof: - if (!s->bpp.expect_close) { - ssh_remote_error(s->bpp.ssh, - "Remote side unexpectedly closed network connection"); - } else { - ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection"); - } - return; /* avoid touching s now it's been freed */ - - crFinishV; -} - -static PktOut *ssh1_bpp_new_pktout(int pkt_type) -{ - PktOut *pkt = ssh_new_packet(); - pkt->length = 4 + 8; /* space for length + max padding */ - put_byte(pkt, pkt_type); - pkt->prefix = pkt->length; - pkt->type = pkt_type; - pkt->downstream_id = 0; - pkt->additional_log_text = NULL; - return pkt; -} - -static void ssh1_bpp_format_packet(struct ssh1_bpp_state *s, PktOut *pkt) -{ - int pad, biglen, pktoffs; - uint32_t crc; - int len; - - if (s->bpp.logctx) { - ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix, - pkt->length - pkt->prefix); - logblank_t blanks[MAX_BLANKS]; - int nblanks = ssh1_censor_packet( - s->bpp.pls, pkt->type, true, pktdata, blanks); - log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, - ssh1_pkt_type(pkt->type), - pktdata.ptr, pktdata.len, nblanks, blanks, - NULL, 0, NULL); - } - - if (s->compctx) { - unsigned char *compblk; - int complen; - ssh_compressor_compress(s->compctx, pkt->data + 12, pkt->length - 12, - &compblk, &complen, 0); - /* Replace the uncompressed packet data with the compressed - * version. */ - pkt->length = 12; - put_data(pkt, compblk, complen); - sfree(compblk); - } - - put_uint32(pkt, 0); /* space for CRC */ - len = pkt->length - 4 - 8; /* len(type+data+CRC) */ - pad = 8 - (len % 8); - pktoffs = 8 - pad; - biglen = len + pad; /* len(padding+type+data+CRC) */ - - random_read(pkt->data + pktoffs, 4+8 - pktoffs); - crc = crc32_ssh1( - make_ptrlen(pkt->data + pktoffs + 4, biglen - 4)); /* all ex len */ - PUT_32BIT_MSB_FIRST(pkt->data + pktoffs + 4 + biglen - 4, crc); - PUT_32BIT_MSB_FIRST(pkt->data + pktoffs, len); - - if (s->cipher_out) - ssh_cipher_encrypt(s->cipher_out, pkt->data + pktoffs + 4, biglen); - - bufchain_add(s->bpp.out_raw, pkt->data + pktoffs, - biglen + 4); /* len(length+padding+type+data+CRC) */ -} - -static void ssh1_bpp_handle_output(BinaryPacketProtocol *bpp) -{ - struct ssh1_bpp_state *s = container_of(bpp, struct ssh1_bpp_state, bpp); - PktOut *pkt; - - if (s->pending_compression_request) { - /* - * Don't send any output packets while we're awaiting a - * response to SSH1_CMSG_REQUEST_COMPRESSION, because if they - * cross over in transit with the responding SSH1_CMSG_SUCCESS - * then the other end could decode them with the wrong - * compression settings. - */ - return; - } - - while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) { - int type = pkt->type; - ssh1_bpp_format_packet(s, pkt); - ssh_free_pktout(pkt); - - if (type == SSH1_CMSG_REQUEST_COMPRESSION) { - /* - * When we see the actual compression request go past, set - * the pending flag, and stop processing packets this - * time. - */ - s->pending_compression_request = true; - break; - } - } - - ssh_sendbuffer_changed(bpp->ssh); -} - -static void ssh1_bpp_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category) -{ - PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH1_MSG_DISCONNECT); - put_stringz(pkt, msg); - pq_push(&bpp->out_pq, pkt); -} diff --git a/ssh/bpp2.c b/ssh/bpp2.c deleted file mode 100644 index 88003e827..000000000 --- a/ssh/bpp2.c +++ /dev/null @@ -1,998 +0,0 @@ -/* - * Binary packet protocol for SSH-2. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "sshcr.h" - -struct ssh2_bpp_direction { - unsigned long sequence; - ssh_cipher *cipher; - ssh2_mac *mac; - bool etm_mode; - const ssh_compression_alg *pending_compression; -}; - -struct ssh2_bpp_state { - int crState; - long len, pad, payload, packetlen, maclen, length, maxlen; - unsigned char *buf; - size_t bufsize; - unsigned char *data; - unsigned cipherblk; - PktIn *pktin; - struct DataTransferStats *stats; - bool cbc_ignore_workaround; - - struct ssh2_bpp_direction in, out; - /* comp and decomp logically belong in the per-direction - * substructure, except that they have different types */ - ssh_decompressor *in_decomp; - ssh_compressor *out_comp; - - bool is_server; - bool pending_newkeys; - bool pending_compression, seen_userauth_success; - bool enforce_next_packet_is_userauth_success; - unsigned nnewkeys; - int prev_type; - - BinaryPacketProtocol bpp; -}; - -static void ssh2_bpp_free(BinaryPacketProtocol *bpp); -static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp); -static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp); -static PktOut *ssh2_bpp_new_pktout(int type); - -static const BinaryPacketProtocolVtable ssh2_bpp_vtable = { - .free = ssh2_bpp_free, - .handle_input = ssh2_bpp_handle_input, - .handle_output = ssh2_bpp_handle_output, - .new_pktout = ssh2_bpp_new_pktout, - .queue_disconnect = ssh2_bpp_queue_disconnect, /* in common.c */ - .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */ -}; - -BinaryPacketProtocol *ssh2_bpp_new( - LogContext *logctx, struct DataTransferStats *stats, bool is_server) -{ - struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state); - memset(s, 0, sizeof(*s)); - s->bpp.vt = &ssh2_bpp_vtable; - s->bpp.logctx = logctx; - s->stats = stats; - s->is_server = is_server; - ssh_bpp_common_setup(&s->bpp); - return &s->bpp; -} - -static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s) -{ - if (s->out.mac) - ssh2_mac_free(s->out.mac); - if (s->out.cipher) - ssh_cipher_free(s->out.cipher); - if (s->out_comp) - ssh_compressor_free(s->out_comp); -} - -static void ssh2_bpp_free_incoming_crypto(struct ssh2_bpp_state *s) -{ - /* As above, take care to free in.mac before in.cipher */ - if (s->in.mac) - ssh2_mac_free(s->in.mac); - if (s->in.cipher) - ssh_cipher_free(s->in.cipher); - if (s->in_decomp) - ssh_decompressor_free(s->in_decomp); -} - -static void ssh2_bpp_free(BinaryPacketProtocol *bpp) -{ - struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); - sfree(s->buf); - ssh2_bpp_free_outgoing_crypto(s); - ssh2_bpp_free_incoming_crypto(s); - sfree(s->pktin); - sfree(s); -} - -void ssh2_bpp_new_outgoing_crypto( - BinaryPacketProtocol *bpp, - const ssh_cipheralg *cipher, const void *ckey, const void *iv, - const ssh2_macalg *mac, bool etm_mode, const void *mac_key, - const ssh_compression_alg *compression, bool delayed_compression, - bool reset_sequence_number) -{ - struct ssh2_bpp_state *s; - assert(bpp->vt == &ssh2_bpp_vtable); - s = container_of(bpp, struct ssh2_bpp_state, bpp); - - ssh2_bpp_free_outgoing_crypto(s); - - if (cipher) { - s->out.cipher = ssh_cipher_new(cipher); - ssh_cipher_setkey(s->out.cipher, ckey); - ssh_cipher_setiv(s->out.cipher, iv); - - s->cbc_ignore_workaround = ( - (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_IS_CBC) && - !(s->bpp.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)); - - bpp_logevent("Initialised %s outbound encryption", - ssh_cipher_alg(s->out.cipher)->text_name); - } else { - s->out.cipher = NULL; - s->cbc_ignore_workaround = false; - } - s->out.etm_mode = etm_mode; - if (mac) { - s->out.mac = ssh2_mac_new(mac, s->out.cipher); - /* - * Important that mac_setkey comes after cipher_setkey, - * because in the case where the MAC makes use of the cipher - * (e.g. AES-GCM), it will need the cipher to be keyed - * already. - */ - ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen)); - - bpp_logevent("Initialised %s outbound MAC algorithm%s%s", - ssh2_mac_text_name(s->out.mac), - etm_mode ? " (in ETM mode)" : "", - (s->out.cipher && - ssh_cipher_alg(s->out.cipher)->required_mac ? - " (required by cipher)" : "")); - } else { - s->out.mac = NULL; - } - - if (reset_sequence_number) - s->out.sequence = 0; - - if (delayed_compression && !s->seen_userauth_success) { - s->out.pending_compression = compression; - s->out_comp = NULL; - - bpp_logevent("Will enable %s compression after user authentication", - s->out.pending_compression->text_name); - } else { - s->out.pending_compression = NULL; - - /* 'compression' is always non-NULL, because no compression is - * indicated by ssh_comp_none. But this setup call may return a - * null out_comp. */ - s->out_comp = ssh_compressor_new(compression); - - if (s->out_comp) - bpp_logevent("Initialised %s compression", - ssh_compressor_alg(s->out_comp)->text_name); - } -} - -void ssh2_bpp_new_incoming_crypto( - BinaryPacketProtocol *bpp, - const ssh_cipheralg *cipher, const void *ckey, const void *iv, - const ssh2_macalg *mac, bool etm_mode, const void *mac_key, - const ssh_compression_alg *compression, bool delayed_compression, - bool reset_sequence_number) -{ - struct ssh2_bpp_state *s; - assert(bpp->vt == &ssh2_bpp_vtable); - s = container_of(bpp, struct ssh2_bpp_state, bpp); - - ssh2_bpp_free_incoming_crypto(s); - - if (cipher) { - s->in.cipher = ssh_cipher_new(cipher); - ssh_cipher_setkey(s->in.cipher, ckey); - ssh_cipher_setiv(s->in.cipher, iv); - - bpp_logevent("Initialised %s inbound encryption", - ssh_cipher_alg(s->in.cipher)->text_name); - } else { - s->in.cipher = NULL; - } - s->in.etm_mode = etm_mode; - if (mac) { - s->in.mac = ssh2_mac_new(mac, s->in.cipher); - /* MAC setkey has to follow cipher, just as in outgoing_crypto above */ - ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen)); - - bpp_logevent("Initialised %s inbound MAC algorithm%s%s", - ssh2_mac_text_name(s->in.mac), - etm_mode ? " (in ETM mode)" : "", - (s->in.cipher && - ssh_cipher_alg(s->in.cipher)->required_mac ? - " (required by cipher)" : "")); - } else { - s->in.mac = NULL; - } - - if (delayed_compression && !s->seen_userauth_success) { - s->in.pending_compression = compression; - s->in_decomp = NULL; - - bpp_logevent("Will enable %s decompression after user authentication", - s->in.pending_compression->text_name); - } else { - s->in.pending_compression = NULL; - - /* 'compression' is always non-NULL, because no compression is - * indicated by ssh_comp_none. But this setup call may return a - * null in_decomp. */ - s->in_decomp = ssh_decompressor_new(compression); - - if (s->in_decomp) - bpp_logevent("Initialised %s decompression", - ssh_decompressor_alg(s->in_decomp)->text_name); - } - - /* Clear the pending_newkeys flag, so that handle_input below will - * start consuming the input data again. */ - s->pending_newkeys = false; - - if (reset_sequence_number) - s->in.sequence = 0; - - /* And schedule a run of handle_input, in case there's already - * input data in the queue. */ - queue_idempotent_callback(&s->bpp.ic_in_raw); -} - -bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp) -{ - struct ssh2_bpp_state *s; - assert(bpp->vt == &ssh2_bpp_vtable); - s = container_of(bpp, struct ssh2_bpp_state, bpp); - - return s->pending_compression; -} - -static void ssh2_bpp_enable_pending_compression(struct ssh2_bpp_state *s) -{ - BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ - - if (s->in.pending_compression) { - s->in_decomp = ssh_decompressor_new(s->in.pending_compression); - bpp_logevent("Initialised delayed %s decompression", - ssh_decompressor_alg(s->in_decomp)->text_name); - s->in.pending_compression = NULL; - } - if (s->out.pending_compression) { - s->out_comp = ssh_compressor_new(s->out.pending_compression); - bpp_logevent("Initialised delayed %s compression", - ssh_compressor_alg(s->out_comp)->text_name); - s->out.pending_compression = NULL; - } -} - -#define BPP_READ(ptr, len) do \ - { \ - bool success; \ - crMaybeWaitUntilV((success = bufchain_try_fetch_consume( \ - s->bpp.in_raw, ptr, len)) || \ - s->bpp.input_eof); \ - if (!success) \ - goto eof; \ - ssh_check_frozen(s->bpp.ssh); \ - } while (0) - -#define userauth_range(pkttype) ((unsigned)((pkttype) - 50) < 20) - -static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp) -{ - struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); - - crBegin(s->crState); - - while (1) { - s->maxlen = 0; - s->length = 0; - if (s->in.cipher) - s->cipherblk = ssh_cipher_alg(s->in.cipher)->blksize; - else - s->cipherblk = 8; - if (s->cipherblk < 8) - s->cipherblk = 8; - s->maclen = s->in.mac ? ssh2_mac_alg(s->in.mac)->len : 0; - - if (s->in.cipher && - (ssh_cipher_alg(s->in.cipher)->flags & SSH_CIPHER_IS_CBC) && - s->in.mac && !s->in.etm_mode) { - /* - * 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. - * - * This defence is unnecessary in OpenSSH ETM mode, because - * the whole point of ETM mode is that the attacker can't - * tweak the ciphertext stream at all without the MAC - * detecting it before we decrypt anything. - */ - - /* - * Make sure we have buffer space for a maximum-size packet. - */ - unsigned buflimit = OUR_V2_PACKETLIMIT + s->maclen; - if (s->bufsize < buflimit) { - s->bufsize = buflimit; - s->buf = sresize(s->buf, s->bufsize, unsigned char); - } - - /* Read an amount corresponding to the MAC. */ - BPP_READ(s->buf, s->maclen); - - s->packetlen = 0; - ssh2_mac_start(s->in.mac); - put_uint32(s->in.mac, s->in.sequence); - - for (;;) { /* Once around this loop per cipher block. */ - /* Read another cipher-block's worth, and tack it on to - * the end. */ - BPP_READ(s->buf + (s->packetlen + s->maclen), s->cipherblk); - /* Decrypt one more block (a little further back in - * the stream). */ - ssh_cipher_decrypt(s->in.cipher, - s->buf + s->packetlen, s->cipherblk); - - /* Feed that block to the MAC. */ - put_data(s->in.mac, - s->buf + s->packetlen, s->cipherblk); - s->packetlen += s->cipherblk; - - /* See if that gives us a valid packet. */ - if (ssh2_mac_verresult(s->in.mac, s->buf + s->packetlen) && - ((s->len = toint(GET_32BIT_MSB_FIRST(s->buf))) == - s->packetlen-4)) - break; - if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) { - ssh_sw_abort(s->bpp.ssh, - "No valid incoming packet found"); - crStopV; - } - } - s->maxlen = s->packetlen + s->maclen; - - /* - * Now transfer the data into an output packet. - */ - s->pktin = snew_plus(PktIn, s->maxlen); - s->pktin->qnode.prev = s->pktin->qnode.next = NULL; - s->pktin->type = 0; - s->pktin->qnode.on_free_queue = false; - s->data = snew_plus_get_aux(s->pktin); - memcpy(s->data, s->buf, s->maxlen); - } else if (s->in.mac && s->in.etm_mode) { - if (s->bufsize < 4) { - s->bufsize = 4; - s->buf = sresize(s->buf, s->bufsize, unsigned char); - } - - /* - * OpenSSH encrypt-then-MAC mode: the packet length is - * unencrypted, unless the cipher supports length encryption. - */ - BPP_READ(s->buf, 4); - - /* Cipher supports length decryption, so do it */ - if (s->in.cipher && (ssh_cipher_alg(s->in.cipher)->flags & - SSH_CIPHER_SEPARATE_LENGTH)) { - /* Keep the packet the same though, so the MAC passes */ - unsigned char len[4]; - memcpy(len, s->buf, 4); - ssh_cipher_decrypt_length( - s->in.cipher, len, 4, s->in.sequence); - s->len = toint(GET_32BIT_MSB_FIRST(len)); - } else { - s->len = toint(GET_32BIT_MSB_FIRST(s->buf)); - } - - /* - * _Completely_ silly lengths should be stomped on before they - * do us any more damage. - */ - if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT || - s->len % s->cipherblk != 0) { - ssh_sw_abort(s->bpp.ssh, - "Incoming packet length field was garbled"); - crStopV; - } - - /* - * So now we can work out the total packet length. - */ - s->packetlen = s->len + 4; - - /* - * Allocate the packet to return, now we know its length. - */ - s->pktin = snew_plus(PktIn, OUR_V2_PACKETLIMIT + s->maclen); - s->pktin->qnode.prev = s->pktin->qnode.next = NULL; - s->pktin->type = 0; - s->pktin->qnode.on_free_queue = false; - s->data = snew_plus_get_aux(s->pktin); - memcpy(s->data, s->buf, 4); - - /* - * Read the remainder of the packet. - */ - BPP_READ(s->data + 4, s->packetlen + s->maclen - 4); - - /* - * Check the MAC. - */ - if (s->in.mac && !ssh2_mac_verify( - s->in.mac, s->data, s->len + 4, s->in.sequence)) { - ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet"); - crStopV; - } - - /* Decrypt everything between the length field and the MAC. */ - if (s->in.cipher) - ssh_cipher_decrypt( - s->in.cipher, s->data + 4, s->packetlen - 4); - } else { - if (s->bufsize < s->cipherblk) { - s->bufsize = s->cipherblk; - s->buf = sresize(s->buf, s->bufsize, unsigned char); - } - - /* - * Acquire and decrypt the first block of the packet. This will - * contain the length and padding details. - */ - BPP_READ(s->buf, s->cipherblk); - - if (s->in.cipher) - ssh_cipher_decrypt(s->in.cipher, s->buf, s->cipherblk); - - /* - * Now get the length figure. - */ - s->len = toint(GET_32BIT_MSB_FIRST(s->buf)); - - /* - * _Completely_ silly lengths should be stomped on before they - * do us any more damage. - */ - if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT || - (s->len + 4) % s->cipherblk != 0) { - ssh_sw_abort(s->bpp.ssh, - "Incoming packet was garbled on decryption"); - crStopV; - } - - /* - * So now we can work out the total packet length. - */ - s->packetlen = s->len + 4; - - /* - * Allocate the packet to return, now we know its length. - */ - s->maxlen = s->packetlen + s->maclen; - s->pktin = snew_plus(PktIn, s->maxlen); - s->pktin->qnode.prev = s->pktin->qnode.next = NULL; - s->pktin->type = 0; - s->pktin->qnode.on_free_queue = false; - s->data = snew_plus_get_aux(s->pktin); - memcpy(s->data, s->buf, s->cipherblk); - - /* - * Read and decrypt the remainder of the packet. - */ - BPP_READ(s->data + s->cipherblk, - s->packetlen + s->maclen - s->cipherblk); - - /* Decrypt everything _except_ the MAC. */ - if (s->in.cipher) - ssh_cipher_decrypt( - s->in.cipher, - s->data + s->cipherblk, s->packetlen - s->cipherblk); - - /* - * Check the MAC. - */ - if (s->in.mac && !ssh2_mac_verify( - s->in.mac, s->data, s->len + 4, s->in.sequence)) { - ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet"); - crStopV; - } - } - /* Get and sanity-check the amount of random padding. */ - s->pad = s->data[4]; - if (s->pad < 4 || s->len - s->pad < 1) { - ssh_sw_abort(s->bpp.ssh, - "Invalid padding length on received packet"); - crStopV; - } - /* - * This enables us to deduce the payload length. - */ - s->payload = s->len - s->pad - 1; - - s->length = s->payload + 5; - - dts_consume(&s->stats->in, s->packetlen); - - s->pktin->sequence = s->in.sequence++; - if (s->in.cipher) - ssh_cipher_next_message(s->in.cipher); - if (s->in.mac) - ssh2_mac_next_message(s->in.mac); - - s->length = s->packetlen - s->pad; - assert(s->length >= 0); - - /* - * Decompress packet payload. - */ - { - unsigned char *newpayload; - int newlen; - if (s->in_decomp && ssh_decompressor_decompress( - s->in_decomp, s->data + 5, s->length - 5, - &newpayload, &newlen)) { - if (s->maxlen < newlen + 5) { - PktIn *old_pktin = s->pktin; - - s->maxlen = newlen + 5; - s->pktin = snew_plus(PktIn, s->maxlen); - *s->pktin = *old_pktin; /* structure copy */ - s->data = snew_plus_get_aux(s->pktin); - - smemclr(old_pktin, s->packetlen + s->maclen); - sfree(old_pktin); - } - s->length = 5 + newlen; - memcpy(s->data + 5, newpayload, newlen); - sfree(newpayload); - } - } - - /* - * Now we can identify the semantic content of the packet, - * and also the initial type byte. - */ - if (s->length <= 5) { /* == 5 we hope, but robustness */ - /* - * RFC 4253 doesn't explicitly say that completely empty - * packets with no type byte are forbidden. We handle them - * here by giving them a type code larger than 0xFF, which - * will be picked up at the next layer and trigger - * SSH_MSG_UNIMPLEMENTED. - */ - s->pktin->type = SSH_MSG_NO_TYPE_CODE; - s->data += 5; - s->length = 0; - } else { - s->pktin->type = s->data[5]; - s->data += 6; - s->length -= 6; - } - BinarySource_INIT(s->pktin, s->data, s->length); - - if (s->bpp.logctx) { - logblank_t blanks[MAX_BLANKS]; - int nblanks = ssh2_censor_packet( - s->bpp.pls, s->pktin->type, false, - make_ptrlen(s->data, s->length), blanks); - log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type, - ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, - s->pktin->type), - s->data, s->length, nblanks, blanks, - &s->pktin->sequence, 0, NULL); - } - - if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) { - sfree(s->pktin); - s->pktin = NULL; - continue; - } - - s->pktin->qnode.formal_size = get_avail(s->pktin); - pq_push(&s->bpp.in_pq, s->pktin); - - { - int type = s->pktin->type; - int prev_type = s->prev_type; - s->prev_type = type; - s->pktin = NULL; - - if (s->enforce_next_packet_is_userauth_success) { - /* See EXT_INFO handler below */ - if (type != SSH2_MSG_USERAUTH_SUCCESS) { - ssh_proto_error(s->bpp.ssh, - "Remote side sent SSH2_MSG_EXT_INFO " - "not either preceded by NEWKEYS or " - "followed by USERAUTH_SUCCESS"); - return; - } - s->enforce_next_packet_is_userauth_success = false; - } - - if (type == SSH2_MSG_NEWKEYS) { - if (s->nnewkeys < 2) - s->nnewkeys++; - /* - * Mild layer violation: in this situation we must - * suspend processing of the input byte stream until - * the transport layer has initialised the new keys by - * calling ssh2_bpp_new_incoming_crypto above. - */ - s->pending_newkeys = true; - crWaitUntilV(!s->pending_newkeys); - continue; - } - - if (type == SSH2_MSG_USERAUTH_SUCCESS && !s->is_server) { - /* - * Another one: if we were configured with OpenSSH's - * deferred compression which is triggered on receipt - * of USERAUTH_SUCCESS, then this is the moment to - * turn on compression. - */ - ssh2_bpp_enable_pending_compression(s); - - /* - * Whether or not we were doing delayed compression in - * _this_ set of crypto parameters, we should set a - * flag indicating that we're now authenticated, so - * that a delayed compression method enabled in any - * future rekey will be treated as un-delayed. - */ - s->seen_userauth_success = true; - } - - if (type == SSH2_MSG_EXT_INFO) { - /* - * And another: enforce that an incoming EXT_INFO is - * either the message immediately after the initial - * NEWKEYS, or (if we're the client) the one - * immediately before USERAUTH_SUCCESS. - */ - if (prev_type == SSH2_MSG_NEWKEYS && s->nnewkeys == 1) { - /* OK - this is right after the first NEWKEYS. */ - } else if (s->is_server) { - /* We're the server, so they're the client. - * Clients may not send EXT_INFO at _any_ other - * time. */ - ssh_proto_error(s->bpp.ssh, - "Remote side sent SSH2_MSG_EXT_INFO " - "that was not immediately after the " - "initial NEWKEYS"); - return; - } else if (s->nnewkeys > 0 && s->seen_userauth_success) { - /* We're the client, so they're the server. In - * that case they may also send EXT_INFO - * immediately before USERAUTH_SUCCESS. Error out - * immediately if this can't _possibly_ be that - * moment (because we haven't even seen NEWKEYS - * yet, or because we've already seen - * USERAUTH_SUCCESS). */ - ssh_proto_error(s->bpp.ssh, - "Remote side sent SSH2_MSG_EXT_INFO " - "after USERAUTH_SUCCESS"); - return; - } else { - /* This _could_ be OK, provided the next packet is - * USERAUTH_SUCCESS. Set a flag to remember to - * fault it if not. */ - s->enforce_next_packet_is_userauth_success = true; - } - } - - if (s->pending_compression && userauth_range(type)) { - /* - * Receiving any userauth message at all indicates - * that we're not about to turn on delayed compression - * - either because we just _have_ done, or because - * this message is a USERAUTH_FAILURE or some kind of - * intermediate 'please send more data' continuation - * message. Either way, we turn off the outgoing - * packet blockage for now, and release any queued - * output packets, so that we can make another attempt - * to authenticate. The next userauth packet we send - * will re-block the output direction. - */ - s->pending_compression = false; - queue_idempotent_callback(&s->bpp.ic_out_pq); - } - } - } - - eof: - /* - * We've seen EOF. But we might have pushed stuff on the outgoing - * packet queue first, and that stuff _might_ include a DISCONNECT - * message, in which case we'd like to use that as the diagnostic. - * So first wait for the queue to have been processed. - */ - crMaybeWaitUntilV(!pq_peek(&s->bpp.in_pq)); - if (!s->bpp.expect_close) { - ssh_remote_error(s->bpp.ssh, - "Remote side unexpectedly closed network connection"); - } else { - ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection"); - } - return; /* avoid touching s now it's been freed */ - - crFinishV; -} - -static PktOut *ssh2_bpp_new_pktout(int pkt_type) -{ - PktOut *pkt = ssh_new_packet(); - pkt->length = 5; /* space for packet length + padding length */ - pkt->minlen = 0; - pkt->type = pkt_type; - put_byte(pkt, pkt_type); - pkt->prefix = pkt->length; - return pkt; -} - -static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt) -{ - int origlen, cipherblk, maclen, padding, unencrypted_prefix, i; - - if (s->bpp.logctx) { - ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix, - pkt->length - pkt->prefix); - logblank_t blanks[MAX_BLANKS]; - int nblanks = ssh2_censor_packet( - s->bpp.pls, pkt->type, true, pktdata, blanks); - log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type, - ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx, - pkt->type), - pktdata.ptr, pktdata.len, nblanks, blanks, &s->out.sequence, - pkt->downstream_id, pkt->additional_log_text); - } - - cipherblk = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 8; - cipherblk = cipherblk < 8 ? 8 : cipherblk; /* or 8 if blksize < 8 */ - - if (s->out_comp) { - unsigned char *newpayload; - int minlen, newlen; - - /* - * Compress packet payload. - */ - minlen = pkt->minlen; - if (minlen) { - /* - * Work out how much compressed data we need (at least) to - * make the overall packet length come to pkt->minlen. - */ - if (s->out.mac) - minlen -= ssh2_mac_alg(s->out.mac)->len; - minlen -= 8; /* length field + min padding */ - } - - ssh_compressor_compress(s->out_comp, pkt->data + 5, pkt->length - 5, - &newpayload, &newlen, minlen); - pkt->length = 5; - put_data(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. - */ - padding = 4; - unencrypted_prefix = (s->out.mac && s->out.etm_mode) ? 4 : 0; - padding += - (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk) - % cipherblk; - assert(padding <= 255); - maclen = s->out.mac ? ssh2_mac_alg(s->out.mac)->len : 0; - origlen = pkt->length; - for (i = 0; i < padding; i++) - put_byte(pkt, 0); /* make space for random padding */ - random_read(pkt->data + origlen, padding); - pkt->data[4] = padding; - PUT_32BIT_MSB_FIRST(pkt->data, origlen + padding - 4); - - /* Encrypt length if the scheme requires it */ - if (s->out.cipher && - (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_SEPARATE_LENGTH)) { - ssh_cipher_encrypt_length(s->out.cipher, pkt->data, 4, - s->out.sequence); - } - - put_padding(pkt, maclen, 0); - - if (s->out.mac && s->out.etm_mode) { - /* - * OpenSSH-defined encrypt-then-MAC protocol. - */ - if (s->out.cipher) - ssh_cipher_encrypt(s->out.cipher, - pkt->data + 4, origlen + padding - 4); - ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding, - s->out.sequence); - } else { - /* - * SSH-2 standard protocol. - */ - if (s->out.mac) - ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding, - s->out.sequence); - if (s->out.cipher) - ssh_cipher_encrypt(s->out.cipher, pkt->data, origlen + padding); - } - - s->out.sequence++; /* whether or not we MACed */ - if (s->out.cipher) - ssh_cipher_next_message(s->out.cipher); - if (s->out.mac) - ssh2_mac_next_message(s->out.mac); - - dts_consume(&s->stats->out, origlen + padding); -} - -static void ssh2_bpp_format_packet(struct ssh2_bpp_state *s, PktOut *pkt) -{ - if (pkt->minlen > 0 && !s->out_comp) { - /* - * If we've been told to pad the packet out to a given minimum - * length, but we're not compressing (and hence can't get the - * compression to do the padding by pointlessly opening and - * closing zlib blocks), then our other strategy is to precede - * this message with an SSH_MSG_IGNORE that makes it up to the - * right length. - * - * A third option in principle, and the most obviously - * sensible, would be to set the explicit padding field in the - * packet to more than its minimum value. Sadly, that turns - * out to break some servers (our institutional memory thinks - * Cisco in particular) and so we abandoned that idea shortly - * after trying it. - */ - - /* - * Calculate the length we expect the real packet to have. - */ - int block, length; - PktOut *ignore_pkt; - - block = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 0; - if (block < 8) - block = 8; - length = pkt->length; - length += 4; /* minimum 4 byte padding */ - length += block-1; - length -= (length % block); - if (s->out.mac) - length += ssh2_mac_alg(s->out.mac)->len; - - if (length < pkt->minlen) { - /* - * We need an ignore message. Calculate its length. - */ - length = pkt->minlen - length; - - /* - * And work backwards from that to the length of the - * contained string. - */ - if (s->out.mac) - length -= ssh2_mac_alg(s->out.mac)->len; - length -= 8; /* length field + min padding */ - length -= 5; /* type code + string length prefix */ - - if (length < 0) - length = 0; - - ignore_pkt = ssh2_bpp_new_pktout(SSH2_MSG_IGNORE); - put_uint32(ignore_pkt, length); - size_t origlen = ignore_pkt->length; - for (size_t i = 0; i < length; i++) - put_byte(ignore_pkt, 0); /* make space for random padding */ - random_read(ignore_pkt->data + origlen, length); - ssh2_bpp_format_packet_inner(s, ignore_pkt); - bufchain_add(s->bpp.out_raw, ignore_pkt->data, ignore_pkt->length); - ssh_free_pktout(ignore_pkt); - } - } - - ssh2_bpp_format_packet_inner(s, pkt); - bufchain_add(s->bpp.out_raw, pkt->data, pkt->length); -} - -static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp) -{ - struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp); - PktOut *pkt; - int n_userauth; - - /* - * Count the userauth packets in the queue. - */ - n_userauth = 0; - for (pkt = pq_first(&s->bpp.out_pq); pkt != NULL; - pkt = pq_next(&s->bpp.out_pq, pkt)) - if (userauth_range(pkt->type)) - n_userauth++; - - if (s->pending_compression && !n_userauth) { - /* - * We're currently blocked from sending any outgoing packets - * until the other end tells us whether we're going to have to - * enable compression or not. - * - * If our end has pushed a userauth packet on the queue, that - * must mean it knows that a USERAUTH_SUCCESS is not - * immediately forthcoming, so we unblock ourselves and send - * up to and including that packet. But in this if statement, - * there aren't any, so we're still blocked. - */ - return; - } - - if (s->cbc_ignore_workaround) { - /* - * When using a CBC-mode cipher in SSH-2, 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 inserting an - * SSH_MSG_IGNORE if the last cipher block of the previous - * packet has already been sent to the network (which we - * approximate conservatively by checking if it's vanished - * from out_raw). - */ - if (bufchain_size(s->bpp.out_raw) < - (ssh_cipher_alg(s->out.cipher)->blksize + - ssh2_mac_alg(s->out.mac)->len)) { - /* - * There's less data in out_raw than the MAC size plus the - * cipher block size, which means at least one byte of - * that cipher block must already have left. Add an - * IGNORE. - */ - pkt = ssh_bpp_new_pktout(&s->bpp, SSH2_MSG_IGNORE); - put_stringz(pkt, ""); - ssh2_bpp_format_packet(s, pkt); - } - } - - while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) { - int type = pkt->type; - - if (userauth_range(type)) - n_userauth--; - - ssh2_bpp_format_packet(s, pkt); - ssh_free_pktout(pkt); - - if (n_userauth == 0 && s->out.pending_compression && !s->is_server) { - /* - * This is the last userauth packet in the queue, so - * unless our side decides to send another one in future, - * we have to assume will potentially provoke - * USERAUTH_SUCCESS. Block (non-userauth) outgoing packets - * until we see the reply. - */ - s->pending_compression = true; - return; - } else if (type == SSH2_MSG_USERAUTH_SUCCESS && s->is_server) { - ssh2_bpp_enable_pending_compression(s); - } - } - - ssh_sendbuffer_changed(bpp->ssh); -} diff --git a/ssh/ca-config.c b/ssh/ca-config.c deleted file mode 100644 index 8c180b364..000000000 --- a/ssh/ca-config.c +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Define and handle the configuration dialog box for SSH host CAs, - * using the same portable dialog specification API as config.c. - */ - -#include "putty.h" -#include "dialog.h" -#include "storage.h" -#include "tree234.h" -#include "ssh.h" - -const bool has_ca_config_box = true; - -#define NRSATYPES 3 - -struct ca_state { - dlgcontrol *ca_name_edit; - dlgcontrol *ca_reclist; - dlgcontrol *ca_pubkey_edit; - dlgcontrol *ca_pubkey_info; - dlgcontrol *ca_validity_edit; - dlgcontrol *rsa_type_checkboxes[NRSATYPES]; - char *name, *pubkey, *validity; - tree234 *ca_names; /* stores plain 'char *' */ - ca_options opts; - strbuf *ca_pubkey_blob; -}; - -static int ca_name_compare(void *av, void *bv) -{ - return strcmp((const char *)av, (const char *)bv); -} - -static inline void clear_string_tree(tree234 *t) -{ - char *p; - while ((p = delpos234(t, 0)) != NULL) - sfree(p); -} - -static void ca_state_free(void *vctx) -{ - struct ca_state *st = (struct ca_state *)vctx; - clear_string_tree(st->ca_names); - freetree234(st->ca_names); - sfree(st->name); - sfree(st->validity); - sfree(st); -} - -static void ca_refresh_name_list(struct ca_state *st) -{ - clear_string_tree(st->ca_names); - - host_ca_enum *hce = enum_host_ca_start(); - if (hce) { - strbuf *namebuf = strbuf_new(); - - while (strbuf_clear(namebuf), enum_host_ca_next(hce, namebuf)) { - char *name = dupstr(namebuf->s); - char *added = add234(st->ca_names, name); - /* Just imaginable that concurrent filesystem access might - * cause a repetition; avoid leaking memory if so */ - if (added != name) - sfree(name); - } - - strbuf_free(namebuf); - enum_host_ca_finish(hce); - } -} - -static void set_from_hca(struct ca_state *st, host_ca *hca) -{ - sfree(st->name); - st->name = dupstr(hca->name ? hca->name : ""); - - sfree(st->pubkey); - if (hca->ca_public_key) - st->pubkey = strbuf_to_str( - base64_encode_sb(ptrlen_from_strbuf(hca->ca_public_key), 0)); - else - st->pubkey = dupstr(""); - - st->validity = dupstr(hca->validity_expression ? - hca->validity_expression : ""); - - st->opts = hca->opts; /* structure copy */ -} - -static void ca_refresh_pubkey_info(struct ca_state *st, dlgparam *dp) -{ - char *text = NULL; - ssh_key *key = NULL; - strbuf *blob = strbuf_new(); - - ptrlen data = ptrlen_from_asciz(st->pubkey); - - if (st->ca_pubkey_blob) - strbuf_free(st->ca_pubkey_blob); - st->ca_pubkey_blob = NULL; - - if (!data.len) { - text = dupstr(" "); - goto out; - } - - /* - * See if we have a plain base64-encoded public key blob. - */ - if (base64_valid(data)) { - base64_decode_bs(BinarySink_UPCAST(blob), data); - } else { - /* - * Otherwise, try to decode as if it was a public key _file_. - */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, data); - const char *error; - if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(blob), NULL, &error)) { - text = dupprintf("Cannot decode key: %s", error); - goto out; - } - } - - ptrlen alg_name = pubkey_blob_to_alg_name(ptrlen_from_strbuf(blob)); - if (!alg_name.len) { - text = dupstr("Invalid key (no key type)"); - goto out; - } - - const ssh_keyalg *alg = find_pubkey_alg_len(alg_name); - if (!alg) { - text = dupprintf("Unrecognised key type '%.*s'", - PTRLEN_PRINTF(alg_name)); - goto out; - } - if (alg->is_certificate) { - text = dupprintf("CA key may not be a certificate (type is '%.*s')", - PTRLEN_PRINTF(alg_name)); - goto out; - } - - key = ssh_key_new_pub(alg, ptrlen_from_strbuf(blob)); - if (!key) { - text = dupprintf("Invalid '%.*s' key data", PTRLEN_PRINTF(alg_name)); - goto out; - } - - text = ssh2_fingerprint(key, SSH_FPTYPE_DEFAULT); - st->ca_pubkey_blob = blob; - blob = NULL; /* prevent free */ - - out: - dlg_text_set(st->ca_pubkey_info, dp, text); - if (key) - ssh_key_free(key); - sfree(text); - if (blob) - strbuf_free(blob); -} - -static void ca_load_selected_record(struct ca_state *st, dlgparam *dp) -{ - int i = dlg_listbox_index(st->ca_reclist, dp); - if (i < 0) { - dlg_beep(dp); - return; - } - const char *name = index234(st->ca_names, i); - if (!name) { /* in case the list box and the tree got out of sync */ - dlg_beep(dp); - return; - } - host_ca *hca = host_ca_load(name); - if (!hca) { - char *msg = dupprintf("Unable to load host CA record '%s'", name); - dlg_error_msg(dp, msg); - sfree(msg); - return; - } - - set_from_hca(st, hca); - host_ca_free(hca); - - dlg_refresh(st->ca_name_edit, dp); - dlg_refresh(st->ca_pubkey_edit, dp); - dlg_refresh(st->ca_validity_edit, dp); - for (size_t i = 0; i < NRSATYPES; i++) - dlg_refresh(st->rsa_type_checkboxes[i], dp); - ca_refresh_pubkey_info(st, dp); -} - -static void ca_ok_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - if (event == EVENT_ACTION) - dlg_end(dp, 0); -} - -static void ca_name_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_REFRESH) { - dlg_editbox_set(ctrl, dp, st->name); - } else if (event == EVENT_VALCHANGE) { - sfree(st->name); - st->name = dlg_editbox_get(ctrl, dp); - - /* - * Try to auto-select the typed name in the list. - */ - int index; - if (!findrelpos234(st->ca_names, st->name, NULL, REL234_GE, &index)) - index = count234(st->ca_names) - 1; - if (index >= 0) - dlg_listbox_select(st->ca_reclist, dp, index); - } -} - -static void ca_reclist_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_REFRESH) { - dlg_update_start(ctrl, dp); - dlg_listbox_clear(ctrl, dp); - const char *name; - for (int i = 0; (name = index234(st->ca_names, i)) != NULL; i++) - dlg_listbox_add(ctrl, dp, name); - dlg_update_done(ctrl, dp); - } else if (event == EVENT_ACTION) { - /* Double-clicking a session loads it */ - ca_load_selected_record(st, dp); - } -} - -static void ca_load_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_ACTION) { - ca_load_selected_record(st, dp); - } -} - -static void ca_save_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_ACTION) { - if (!*st->validity) { - dlg_error_msg(dp, "No validity expression configured " - "for this key"); - return; - } - - char *error_msg; - ptrlen error_loc; - if (!cert_expr_valid(st->validity, &error_msg, &error_loc)) { - char *error_full = dupprintf("Error in expression: %s", error_msg); - dlg_error_msg(dp, error_full); - dlg_set_focus(st->ca_validity_edit, dp); - dlg_editbox_select_range( - st->ca_validity_edit, dp, - (const char *)error_loc.ptr - st->validity, error_loc.len); - sfree(error_msg); - sfree(error_full); - return; - } - - if (!st->ca_pubkey_blob) { - dlg_error_msg(dp, "No valid CA public key entered"); - return; - } - - host_ca *hca = snew(host_ca); - memset(hca, 0, sizeof(*hca)); - hca->name = dupstr(st->name); - hca->ca_public_key = strbuf_dup(ptrlen_from_strbuf( - st->ca_pubkey_blob)); - hca->validity_expression = dupstr(st->validity); - hca->opts = st->opts; /* structure copy */ - - char *error = host_ca_save(hca); - host_ca_free(hca); - - if (error) { - dlg_error_msg(dp, error); - sfree(error); - } else { - ca_refresh_name_list(st); - dlg_refresh(st->ca_reclist, dp); - } - } -} - -static void ca_delete_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_ACTION) { - int i = dlg_listbox_index(st->ca_reclist, dp); - if (i < 0) { - dlg_beep(dp); - return; - } - const char *name = index234(st->ca_names, i); - if (!name) { /* in case the list box and the tree got out of sync */ - dlg_beep(dp); - return; - } - - char *error = host_ca_delete(name); - if (error) { - dlg_error_msg(dp, error); - sfree(error); - } else { - ca_refresh_name_list(st); - dlg_refresh(st->ca_reclist, dp); - } - } -} - -static void ca_pubkey_edit_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_REFRESH) { - dlg_editbox_set(ctrl, dp, st->pubkey); - } else if (event == EVENT_VALCHANGE) { - sfree(st->pubkey); - st->pubkey = dlg_editbox_get(ctrl, dp); - ca_refresh_pubkey_info(st, dp); - } -} - -static void ca_pubkey_file_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_ACTION) { - Filename *filename = dlg_filesel_get(ctrl, dp); - strbuf *keyblob = strbuf_new(); - const char *load_error; - bool ok = ppk_loadpub_f(filename, NULL, BinarySink_UPCAST(keyblob), - NULL, &load_error); - if (!ok) { - char *message = dupprintf( - "Unable to load public key from '%s': %s", - filename_to_str(filename), load_error); - dlg_error_msg(dp, message); - sfree(message); - } else { - sfree(st->pubkey); - st->pubkey = strbuf_to_str( - base64_encode_sb(ptrlen_from_strbuf(keyblob), 0)); - dlg_refresh(st->ca_pubkey_edit, dp); - } - filename_free(filename); - strbuf_free(keyblob); - } -} - -static void ca_validity_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - if (event == EVENT_REFRESH) { - dlg_editbox_set(ctrl, dp, st->validity); - } else if (event == EVENT_VALCHANGE) { - sfree(st->validity); - st->validity = dlg_editbox_get(ctrl, dp); - } -} - -static void ca_rsa_type_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - struct ca_state *st = (struct ca_state *)ctrl->context.p; - size_t offset = ctrl->context2.i; - bool *option = (bool *)((char *)&st->opts + offset); - - if (event == EVENT_REFRESH) { - dlg_checkbox_set(ctrl, dp, *option); - } else if (event == EVENT_VALCHANGE) { - *option = dlg_checkbox_get(ctrl, dp); - } -} - -void setup_ca_config_box(struct controlbox *b) -{ - struct controlset *s; - dlgcontrol *c; - - /* Internal state for manipulating the host CA system */ - struct ca_state *st = (struct ca_state *)ctrl_alloc_with_free( - b, sizeof(struct ca_state), ca_state_free); - memset(st, 0, sizeof(*st)); - st->ca_names = newtree234(ca_name_compare); - st->validity = dupstr(""); - ca_refresh_name_list(st); - - /* Initialise the settings to a default blank host_ca */ - { - host_ca *hca = host_ca_new(); - set_from_hca(st, hca); - host_ca_free(hca); - } - - /* Action area, with the Done button in it */ - s = ctrl_getset(b, "", "", ""); - ctrl_columns(s, 5, 20, 20, 20, 20, 20); - c = ctrl_pushbutton(s, "Done", 'o', HELPCTX(ssh_kex_cert), - ca_ok_handler, P(st)); - c->button.iscancel = true; - c->column = 4; - - /* Load/save box, as similar as possible to the main saved sessions one */ - s = ctrl_getset(b, "Main", "loadsave", - "Load, save or delete a host CA record"); - ctrl_columns(s, 2, 75, 25); - c = ctrl_editbox(s, "Name for this CA (shown in log messages)", - 'n', 100, HELPCTX(ssh_kex_cert), - ca_name_handler, P(st), P(NULL)); - c->column = 0; - st->ca_name_edit = c; - /* 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); - c = ctrl_listbox(s, NULL, NO_SHORTCUT, HELPCTX(ssh_kex_cert), - ca_reclist_handler, P(st)); - c->column = 0; - c->listbox.height = 6; - st->ca_reclist = c; - c = ctrl_pushbutton(s, "Load", 'l', HELPCTX(ssh_kex_cert), - ca_load_handler, P(st)); - c->column = 1; - c = ctrl_pushbutton(s, "Save", 'v', HELPCTX(ssh_kex_cert), - ca_save_handler, P(st)); - c->column = 1; - c = ctrl_pushbutton(s, "Delete", 'd', HELPCTX(ssh_kex_cert), - ca_delete_handler, P(st)); - c->column = 1; - - s = ctrl_getset(b, "Main", "pubkey", "Public key for this CA record"); - - ctrl_columns(s, 2, 75, 25); - c = ctrl_editbox(s, "Public key of certification authority", 'k', 100, - HELPCTX(ssh_kex_cert), ca_pubkey_edit_handler, - P(st), P(NULL)); - c->column = 0; - st->ca_pubkey_edit = c; - c = ctrl_filesel(s, "Read from file", NO_SHORTCUT, NULL, false, - "Select public key file of certification authority", - HELPCTX(ssh_kex_cert), ca_pubkey_file_handler, P(st)); - c->fileselect.just_button = true; - c->align_next_to = st->ca_pubkey_edit; - c->column = 1; - ctrl_columns(s, 1, 100); - st->ca_pubkey_info = c = ctrl_text(s, " ", HELPCTX(ssh_kex_cert)); - c->text.wrap = false; - - s = ctrl_getset(b, "Main", "options", "What this CA is trusted to do"); - - c = ctrl_editbox(s, "Valid hosts this key is trusted to certify", 'h', 100, - HELPCTX(ssh_cert_valid_expr), ca_validity_handler, - P(st), P(NULL)); - st->ca_validity_edit = c; - - ctrl_columns(s, 4, 44, 18, 18, 18); - c = ctrl_text(s, "Signature types (RSA keys only):", - HELPCTX(ssh_cert_rsa_hash)); - c->column = 0; - dlgcontrol *sigtypelabel = c; - c = ctrl_checkbox(s, "SHA-1", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash), - ca_rsa_type_handler, P(st)); - c->column = 1; - c->align_next_to = sigtypelabel; - c->context2 = I(offsetof(ca_options, permit_rsa_sha1)); - st->rsa_type_checkboxes[0] = c; - c = ctrl_checkbox(s, "SHA-256", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash), - ca_rsa_type_handler, P(st)); - c->column = 2; - c->align_next_to = sigtypelabel; - c->context2 = I(offsetof(ca_options, permit_rsa_sha256)); - st->rsa_type_checkboxes[1] = c; - c = ctrl_checkbox(s, "SHA-512", NO_SHORTCUT, HELPCTX(ssh_cert_rsa_hash), - ca_rsa_type_handler, P(st)); - c->column = 3; - c->align_next_to = sigtypelabel; - c->context2 = I(offsetof(ca_options, permit_rsa_sha512)); - st->rsa_type_checkboxes[2] = c; - ctrl_columns(s, 1, 100); -} diff --git a/ssh/censor1.c b/ssh/censor1.c deleted file mode 100644 index 8dacd3a05..000000000 --- a/ssh/censor1.c +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Packet-censoring code for SSH-1, used to identify sensitive fields - * like passwords so that the logging system can avoid writing them - * into log files. - */ - -#include - -#include "putty.h" -#include "ssh.h" - -int ssh1_censor_packet( - const PacketLogSettings *pls, int type, bool sender_is_client, - ptrlen pkt, logblank_t *blanks) -{ - int nblanks = 0; - ptrlen str; - BinarySource src[1]; - - BinarySource_BARE_INIT_PL(src, pkt); - - if (pls->omit_data && - (type == SSH1_SMSG_STDOUT_DATA || - type == SSH1_SMSG_STDERR_DATA || - type == SSH1_CMSG_STDIN_DATA || - type == SSH1_MSG_CHANNEL_DATA)) { - /* "Session data" packets - omit the data string. */ - if (type == SSH1_MSG_CHANNEL_DATA) - get_uint32(src); /* skip channel id */ - str = get_string(src); - if (!get_err(src)) { - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_OMIT; - blanks[nblanks].len = str.len; - nblanks++; - } - } - - if (sender_is_client && pls->omit_passwords) { - if (type == SSH1_CMSG_AUTH_PASSWORD || - type == SSH1_CMSG_AUTH_TIS_RESPONSE || - type == SSH1_CMSG_AUTH_CCARD_RESPONSE) { - /* If this is a password or similar packet, blank the - * password(s). */ - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = 0; - blanks[nblanks].len = pkt.len; - blanks[nblanks].type = PKTLOG_BLANK; - nblanks++; - } else if (type == SSH1_CMSG_X11_REQUEST_FORWARDING) { - /* - * 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. - */ - get_string(src); /* skip protocol name */ - str = get_string(src); - if (!get_err(src)) { - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_BLANK; - blanks[nblanks].len = str.len; - nblanks++; - } - } - } - - return nblanks; -} diff --git a/ssh/censor2.c b/ssh/censor2.c deleted file mode 100644 index 31ad81495..000000000 --- a/ssh/censor2.c +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Packet-censoring code for SSH-2, used to identify sensitive fields - * like passwords so that the logging system can avoid writing them - * into log files. - */ - -#include - -#include "putty.h" -#include "ssh.h" - -int ssh2_censor_packet( - const PacketLogSettings *pls, int type, bool sender_is_client, - ptrlen pkt, logblank_t *blanks) -{ - int nblanks = 0; - ptrlen str; - BinarySource src[1]; - - BinarySource_BARE_INIT_PL(src, pkt); - - if (pls->omit_data && - (type == SSH2_MSG_CHANNEL_DATA || - type == SSH2_MSG_CHANNEL_EXTENDED_DATA)) { - /* "Session data" packets - omit the data string. */ - get_uint32(src); /* skip channel id */ - if (type == SSH2_MSG_CHANNEL_EXTENDED_DATA) - get_uint32(src); /* skip extended data type */ - str = get_string(src); - if (!get_err(src)) { - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_OMIT; - blanks[nblanks].len = str.len; - nblanks++; - } - } - - if (sender_is_client && pls->omit_passwords) { - if (type == SSH2_MSG_USERAUTH_REQUEST) { - /* If this is a password packet, blank the password(s). */ - get_string(src); /* username */ - get_string(src); /* service name */ - str = get_string(src); /* auth method */ - if (ptrlen_eq_string(str, "password")) { - get_bool(src); - /* Blank the password field. */ - str = get_string(src); - if (!get_err(src)) { - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_BLANK; - blanks[nblanks].len = str.len; - nblanks++; - /* If there's another password field beyond it - * (change of password), blank that too. */ - str = get_string(src); - if (!get_err(src)) - blanks[nblanks-1].len = - src->pos - blanks[nblanks].offset; - } - } - } else if (pls->actx == SSH2_PKTCTX_KBDINTER && - type == SSH2_MSG_USERAUTH_INFO_RESPONSE) { - /* If this is a keyboard-interactive response packet, - * blank the responses. */ - get_uint32(src); - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = src->pos; - blanks[nblanks].type = PKTLOG_BLANK; - do { - str = get_string(src); - } while (!get_err(src)); - blanks[nblanks].len = src->pos - blanks[nblanks].offset; - nblanks++; - } else if (type == SSH2_MSG_CHANNEL_REQUEST) { - /* - * 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. - */ - get_uint32(src); - str = get_string(src); - if (ptrlen_eq_string(str, "x11-req")) { - get_bool(src); - get_bool(src); - get_string(src); - str = get_string(src); - if (!get_err(src)) { - assert(nblanks < MAX_BLANKS); - blanks[nblanks].offset = src->pos - str.len; - blanks[nblanks].type = PKTLOG_BLANK; - blanks[nblanks].len = str.len; - nblanks++; - } - } - } - } - - return nblanks; -} diff --git a/ssh/channel.h b/ssh/channel.h deleted file mode 100644 index d4eb78aae..000000000 --- a/ssh/channel.h +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Abstraction of the various ways to handle the local end of an SSH - * connection-layer channel. - */ - -#ifndef PUTTY_SSHCHAN_H -#define PUTTY_SSHCHAN_H - -typedef struct ChannelVtable ChannelVtable; - -struct ChannelVtable { - void (*free)(Channel *); - - /* Called for channel types that were created at the same time as - * we sent an outgoing CHANNEL_OPEN, when the confirmation comes - * back from the server indicating that the channel has been - * opened, or the failure message indicating that it hasn't, - * respectively. In the latter case, this must _not_ free the - * Channel structure - the client will call the free method - * separately. But it might do logging or other local cleanup. */ - void (*open_confirmation)(Channel *); - void (*open_failed)(Channel *, const char *error_text); - - size_t (*send)(Channel *, bool is_stderr, const void *buf, size_t len); - void (*send_eof)(Channel *); - void (*set_input_wanted)(Channel *, bool wanted); - - char *(*log_close_msg)(Channel *); - - bool (*want_close)(Channel *, bool sent_local_eof, bool rcvd_remote_eof); - - /* A method for every channel request we know of. All of these - * return true for success or false for failure. */ - bool (*rcvd_exit_status)(Channel *, int status); - bool (*rcvd_exit_signal)( - Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg); - bool (*rcvd_exit_signal_numeric)( - Channel *chan, int signum, bool core_dumped, ptrlen msg); - bool (*run_shell)(Channel *chan); - bool (*run_command)(Channel *chan, ptrlen command); - bool (*run_subsystem)(Channel *chan, ptrlen subsys); - bool (*enable_x11_forwarding)( - Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, - unsigned screen_number); - bool (*enable_agent_forwarding)(Channel *chan); - bool (*allocate_pty)( - Channel *chan, ptrlen termtype, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes); - bool (*set_env)(Channel *chan, ptrlen var, ptrlen value); - bool (*send_break)(Channel *chan, unsigned length); - bool (*send_signal)(Channel *chan, ptrlen signame); - bool (*change_window_size)( - Channel *chan, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight); - - /* A method for signalling success/failure responses to channel - * requests initiated from the SshChannel vtable with want_reply - * true. */ - void (*request_response)(Channel *, bool success); -}; - -struct Channel { - const struct ChannelVtable *vt; - unsigned initial_fixed_window_size; -}; - -static inline void chan_free(Channel *ch) -{ ch->vt->free(ch); } -static inline void chan_open_confirmation(Channel *ch) -{ ch->vt->open_confirmation(ch); } -static inline void chan_open_failed(Channel *ch, const char *err) -{ ch->vt->open_failed(ch, err); } -static inline size_t chan_send( - Channel *ch, bool err, const void *buf, size_t len) -{ return ch->vt->send(ch, err, buf, len); } -static inline void chan_send_eof(Channel *ch) -{ ch->vt->send_eof(ch); } -static inline void chan_set_input_wanted(Channel *ch, bool wanted) -{ ch->vt->set_input_wanted(ch, wanted); } -static inline char *chan_log_close_msg(Channel *ch) -{ return ch->vt->log_close_msg(ch); } -static inline bool chan_want_close(Channel *ch, bool leof, bool reof) -{ return ch->vt->want_close(ch, leof, reof); } -static inline bool chan_rcvd_exit_status(Channel *ch, int status) -{ return ch->vt->rcvd_exit_status(ch, status); } -static inline bool chan_rcvd_exit_signal( - Channel *ch, ptrlen sig, bool core, ptrlen msg) -{ return ch->vt->rcvd_exit_signal(ch, sig, core, msg); } -static inline bool chan_rcvd_exit_signal_numeric( - Channel *ch, int sig, bool core, ptrlen msg) -{ return ch->vt->rcvd_exit_signal_numeric(ch, sig, core, msg); } -static inline bool chan_run_shell(Channel *ch) -{ return ch->vt->run_shell(ch); } -static inline bool chan_run_command(Channel *ch, ptrlen cmd) -{ return ch->vt->run_command(ch, cmd); } -static inline bool chan_run_subsystem(Channel *ch, ptrlen subsys) -{ return ch->vt->run_subsystem(ch, subsys); } -static inline bool chan_enable_x11_forwarding( - Channel *ch, bool oneshot, ptrlen ap, ptrlen ad, unsigned scr) -{ return ch->vt->enable_x11_forwarding(ch, oneshot, ap, ad, scr); } -static inline bool chan_enable_agent_forwarding(Channel *ch) -{ return ch->vt->enable_agent_forwarding(ch); } -static inline bool chan_allocate_pty( - Channel *ch, ptrlen termtype, unsigned w, unsigned h, - unsigned pw, unsigned ph, struct ssh_ttymodes modes) -{ return ch->vt->allocate_pty(ch, termtype, w, h, pw, ph, modes); } -static inline bool chan_set_env(Channel *ch, ptrlen var, ptrlen value) -{ return ch->vt->set_env(ch, var, value); } -static inline bool chan_send_break(Channel *ch, unsigned length) -{ return ch->vt->send_break(ch, length); } -static inline bool chan_send_signal(Channel *ch, ptrlen signame) -{ return ch->vt->send_signal(ch, signame); } -static inline bool chan_change_window_size( - Channel *ch, unsigned w, unsigned h, unsigned pw, unsigned ph) -{ return ch->vt->change_window_size(ch, w, h, pw, ph); } -static inline void chan_request_response(Channel *ch, bool success) -{ ch->vt->request_response(ch, success); } - -/* - * Reusable methods you can put in vtables to give default handling of - * some of those functions. - */ - -/* open_confirmation / open_failed for any channel it doesn't apply to */ -void chan_remotely_opened_confirmation(Channel *chan); -void chan_remotely_opened_failure(Channel *chan, const char *errtext); - -/* want_close for any channel that wants the default behaviour of not - * closing until both directions have had an EOF */ -bool chan_default_want_close(Channel *, bool, bool); - -/* default implementations that refuse all the channel requests */ -bool chan_no_exit_status(Channel *, int); -bool chan_no_exit_signal(Channel *, ptrlen, bool, ptrlen); -bool chan_no_exit_signal_numeric(Channel *, int, bool, ptrlen); -bool chan_no_run_shell(Channel *chan); -bool chan_no_run_command(Channel *chan, ptrlen command); -bool chan_no_run_subsystem(Channel *chan, ptrlen subsys); -bool chan_no_enable_x11_forwarding( - Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, - unsigned screen_number); -bool chan_no_enable_agent_forwarding(Channel *chan); -bool chan_no_allocate_pty( - Channel *chan, ptrlen termtype, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes); -bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value); -bool chan_no_send_break(Channel *chan, unsigned length); -bool chan_no_send_signal(Channel *chan, ptrlen signame); -bool chan_no_change_window_size( - Channel *chan, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight); - -/* default implementation that never expects to receive a response */ -void chan_no_request_response(Channel *, bool); - -/* - * Constructor for a trivial do-nothing implementation of - * ChannelVtable. Used for 'zombie' channels, i.e. channels whose - * proper local source of data has been shut down or otherwise stopped - * existing, but the SSH side is still there and needs some kind of a - * Channel implementation to talk to. In particular, the want_close - * method for this channel always returns 'yes, please close this - * channel asap', regardless of whether local and/or remote EOF have - * been sent - indeed, even if _neither_ has. - */ -Channel *zombiechan_new(void); - -/* ---------------------------------------------------------------------- - * This structure is owned by an SSH connection layer, and identifies - * the connection layer's end of the channel, for the Channel - * implementation to talk back to. - */ - -typedef struct SshChannelVtable SshChannelVtable; - -struct SshChannelVtable { - size_t (*write)(SshChannel *c, bool is_stderr, const void *, size_t); - void (*write_eof)(SshChannel *c); - void (*initiate_close)(SshChannel *c, const char *err); - void (*unthrottle)(SshChannel *c, size_t bufsize); - Conf *(*get_conf)(SshChannel *c); - void (*window_override_removed)(SshChannel *c); - void (*x11_sharing_handover)(SshChannel *c, - ssh_sharing_connstate *share_cs, - share_channel *share_chan, - const char *peer_addr, int peer_port, - int endian, int protomajor, int protominor, - const void *initial_data, int initial_len); - - /* - * All the outgoing channel requests we support. Each one has a - * want_reply flag, which will cause a callback to - * chan_request_response when the result is available. - * - * The ones that return 'bool' use it to indicate that the SSH - * protocol in use doesn't support this request at all. - * - * (It's also intentional that not all of them have a want_reply - * flag: the ones that don't are because SSH-1 has no method for - * signalling success or failure of that request, or because we - * wouldn't do anything usefully different with the reply in any - * case.) - */ - void (*send_exit_status)(SshChannel *c, int status); - void (*send_exit_signal)( - SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg); - void (*send_exit_signal_numeric)( - SshChannel *c, int signum, bool core_dumped, ptrlen msg); - void (*request_x11_forwarding)( - SshChannel *c, bool want_reply, const char *authproto, - const char *authdata, int screen_number, bool oneshot); - void (*request_agent_forwarding)( - SshChannel *c, bool want_reply); - void (*request_pty)( - SshChannel *c, bool want_reply, Conf *conf, int w, int h); - bool (*send_env_var)( - SshChannel *c, bool want_reply, const char *var, const char *value); - void (*start_shell)( - SshChannel *c, bool want_reply); - void (*start_command)( - SshChannel *c, bool want_reply, const char *command); - bool (*start_subsystem)( - SshChannel *c, bool want_reply, const char *subsystem); - bool (*send_serial_break)( - SshChannel *c, bool want_reply, int length); /* length=0 for default */ - bool (*send_signal)( - SshChannel *c, bool want_reply, const char *signame); - void (*send_terminal_size_change)( - SshChannel *c, int w, int h); - void (*hint_channel_is_simple)(SshChannel *c); -}; - -struct SshChannel { - const struct SshChannelVtable *vt; - ConnectionLayer *cl; -}; - -static inline size_t sshfwd_write_ext( - SshChannel *c, bool is_stderr, const void *data, size_t len) -{ return c->vt->write(c, is_stderr, data, len); } -static inline size_t sshfwd_write(SshChannel *c, const void *data, size_t len) -{ return sshfwd_write_ext(c, false, data, len); } -static inline void sshfwd_write_eof(SshChannel *c) -{ c->vt->write_eof(c); } -static inline void sshfwd_initiate_close(SshChannel *c, const char *err) -{ c->vt->initiate_close(c, err); } -static inline void sshfwd_unthrottle(SshChannel *c, size_t bufsize) -{ c->vt->unthrottle(c, bufsize); } -static inline Conf *sshfwd_get_conf(SshChannel *c) -{ return c->vt->get_conf(c); } -static inline void sshfwd_window_override_removed(SshChannel *c) -{ c->vt->window_override_removed(c); } -static inline void sshfwd_x11_sharing_handover( - SshChannel *c, ssh_sharing_connstate *cs, share_channel *sch, - const char *addr, int port, int endian, int maj, int min, - const void *idata, int ilen) -{ c->vt->x11_sharing_handover(c, cs, sch, addr, port, endian, - maj, min, idata, ilen); } -static inline void sshfwd_send_exit_status(SshChannel *c, int status) -{ c->vt->send_exit_status(c, status); } -static inline void sshfwd_send_exit_signal( - SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg) -{ c->vt->send_exit_signal(c, signame, core_dumped, msg); } -static inline void sshfwd_send_exit_signal_numeric( - SshChannel *c, int signum, bool core_dumped, ptrlen msg) -{ c->vt->send_exit_signal_numeric(c, signum, core_dumped, msg); } -static inline void sshfwd_request_x11_forwarding( - SshChannel *c, bool want_reply, const char *proto, - const char *data, int scr, bool once) -{ c->vt->request_x11_forwarding(c, want_reply, proto, data, scr, once); } -static inline void sshfwd_request_agent_forwarding( - SshChannel *c, bool want_reply) -{ c->vt->request_agent_forwarding(c, want_reply); } -static inline void sshfwd_request_pty( - SshChannel *c, bool want_reply, Conf *conf, int w, int h) -{ c->vt->request_pty(c, want_reply, conf, w, h); } -static inline bool sshfwd_send_env_var( - SshChannel *c, bool want_reply, const char *var, const char *value) -{ return c->vt->send_env_var(c, want_reply, var, value); } -static inline void sshfwd_start_shell( - SshChannel *c, bool want_reply) -{ c->vt->start_shell(c, want_reply); } -static inline void sshfwd_start_command( - SshChannel *c, bool want_reply, const char *command) -{ c->vt->start_command(c, want_reply, command); } -static inline bool sshfwd_start_subsystem( - SshChannel *c, bool want_reply, const char *subsystem) -{ return c->vt->start_subsystem(c, want_reply, subsystem); } -static inline bool sshfwd_send_serial_break( - SshChannel *c, bool want_reply, int length) -{ return c->vt->send_serial_break(c, want_reply, length); } -static inline bool sshfwd_send_signal( - SshChannel *c, bool want_reply, const char *signame) -{ return c->vt->send_signal(c, want_reply, signame); } -static inline void sshfwd_send_terminal_size_change( - SshChannel *c, int w, int h) -{ c->vt->send_terminal_size_change(c, w, h); } -static inline void sshfwd_hint_channel_is_simple(SshChannel *c) -{ c->vt->hint_channel_is_simple(c); } - -/* ---------------------------------------------------------------------- - * The 'main' or primary channel of the SSH connection is special, - * because it's the one that's connected directly to parts of the - * frontend such as the terminal and the specials menu. So it exposes - * a richer API. - */ - -mainchan *mainchan_new( - PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, - int term_width, int term_height, bool is_simple, SshChannel **sc_out); -void mainchan_get_specials( - mainchan *mc, add_special_fn_t add_special, void *ctx); -void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg); -void mainchan_terminal_size(mainchan *mc, int width, int height); - -#endif /* PUTTY_SSHCHAN_H */ diff --git a/ssh/common.c b/ssh/common.c deleted file mode 100644 index 5142e1023..000000000 --- a/ssh/common.c +++ /dev/null @@ -1,1277 +0,0 @@ -/* - * Supporting routines used in common by all the various components of - * the SSH system. - */ - -#include -#include - -#include "putty.h" -#include "mpint.h" -#include "ssh.h" -#include "storage.h" -#include "bpp.h" -#include "ppl.h" -#include "channel.h" - -/* ---------------------------------------------------------------------- - * Implementation of PacketQueue. - */ - -static void pq_ensure_unlinked(PacketQueueNode *node) -{ - if (node->on_free_queue) { - node->next->prev = node->prev; - node->prev->next = node->next; - } else { - assert(!node->next); - assert(!node->prev); - } -} - -void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node) -{ - pq_ensure_unlinked(node); - node->next = &pqb->end; - node->prev = pqb->end.prev; - node->next->prev = node; - node->prev->next = node; - pqb->total_size += node->formal_size; - - if (pqb->ic) - queue_idempotent_callback(pqb->ic); -} - -void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node) -{ - pq_ensure_unlinked(node); - node->prev = &pqb->end; - node->next = pqb->end.next; - node->next->prev = node; - node->prev->next = node; - pqb->total_size += node->formal_size; - - if (pqb->ic) - queue_idempotent_callback(pqb->ic); -} - -static PacketQueueNode pktin_freeq_head = { - &pktin_freeq_head, &pktin_freeq_head, true -}; - -static void pktin_free_queue_callback(void *vctx) -{ - while (pktin_freeq_head.next != &pktin_freeq_head) { - PacketQueueNode *node = pktin_freeq_head.next; - PktIn *pktin = container_of(node, PktIn, qnode); - pktin_freeq_head.next = node->next; - sfree(pktin); - } - - pktin_freeq_head.prev = &pktin_freeq_head; -} - -static IdempotentCallback ic_pktin_free = { - pktin_free_queue_callback, NULL, false -}; - -static inline void pq_unlink_common(PacketQueueBase *pqb, - PacketQueueNode *node) -{ - node->next->prev = node->prev; - node->prev->next = node->next; - - /* Check total_size doesn't drift out of sync downwards, by - * ensuring it doesn't underflow when we do this subtraction */ - assert(pqb->total_size >= node->formal_size); - pqb->total_size -= node->formal_size; - - /* Check total_size doesn't drift out of sync upwards, by checking - * that it's returned to exactly zero whenever a queue is - * emptied */ - assert(pqb->end.next != &pqb->end || pqb->total_size == 0); -} - -static PktIn *pq_in_after(PacketQueueBase *pqb, - PacketQueueNode *prev, bool pop) -{ - PacketQueueNode *node = prev->next; - if (node == &pqb->end) - return NULL; - - if (pop) { - pq_unlink_common(pqb, node); - - node->prev = pktin_freeq_head.prev; - node->next = &pktin_freeq_head; - node->next->prev = node; - node->prev->next = node; - node->on_free_queue = true; - - queue_idempotent_callback(&ic_pktin_free); - } - - return container_of(node, PktIn, qnode); -} - -static PktOut *pq_out_after(PacketQueueBase *pqb, - PacketQueueNode *prev, bool pop) -{ - PacketQueueNode *node = prev->next; - if (node == &pqb->end) - return NULL; - - if (pop) { - pq_unlink_common(pqb, node); - - node->prev = node->next = NULL; - } - - return container_of(node, PktOut, qnode); -} - -void pq_in_init(PktInQueue *pq) -{ - pq->pqb.ic = NULL; - pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end; - pq->after = pq_in_after; - pq->pqb.total_size = 0; -} - -void pq_out_init(PktOutQueue *pq) -{ - pq->pqb.ic = NULL; - pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end; - pq->after = pq_out_after; - pq->pqb.total_size = 0; -} - -void pq_in_clear(PktInQueue *pq) -{ - PktIn *pkt; - pq->pqb.ic = NULL; - while ((pkt = pq_pop(pq)) != NULL) { - /* No need to actually free these packets: pq_pop on a - * PktInQueue will automatically move them to the free - * queue. */ - } -} - -void pq_out_clear(PktOutQueue *pq) -{ - PktOut *pkt; - pq->pqb.ic = NULL; - while ((pkt = pq_pop(pq)) != NULL) - ssh_free_pktout(pkt); -} - -/* - * Concatenate the contents of the two queues q1 and q2, and leave the - * result in qdest. qdest must be either empty, or one of the input - * queues. - */ -void pq_base_concatenate(PacketQueueBase *qdest, - PacketQueueBase *q1, PacketQueueBase *q2) -{ - struct PacketQueueNode *head1, *tail1, *head2, *tail2; - - size_t total_size = q1->total_size + q2->total_size; - - /* - * Extract the contents from both input queues, and empty them. - */ - - head1 = (q1->end.next == &q1->end ? NULL : q1->end.next); - tail1 = (q1->end.prev == &q1->end ? NULL : q1->end.prev); - head2 = (q2->end.next == &q2->end ? NULL : q2->end.next); - tail2 = (q2->end.prev == &q2->end ? NULL : q2->end.prev); - - q1->end.next = q1->end.prev = &q1->end; - q2->end.next = q2->end.prev = &q2->end; - q1->total_size = q2->total_size = 0; - - /* - * Link the two lists together, handling the case where one or - * both is empty. - */ - - if (tail1) - tail1->next = head2; - else - head1 = head2; - - if (head2) - head2->prev = tail1; - else - tail2 = tail1; - - /* - * Check the destination queue is currently empty. (If it was one - * of the input queues, then it will be, because we emptied both - * of those just a moment ago.) - */ - - assert(qdest->end.next == &qdest->end); - assert(qdest->end.prev == &qdest->end); - - /* - * If our concatenated list has anything in it, then put it in - * dest. - */ - - if (!head1) { - assert(!tail2); - } else { - assert(tail2); - qdest->end.next = head1; - qdest->end.prev = tail2; - head1->prev = &qdest->end; - tail2->next = &qdest->end; - - if (qdest->ic) - queue_idempotent_callback(qdest->ic); - } - - qdest->total_size = total_size; -} - -/* ---------------------------------------------------------------------- - * Low-level functions for the packet structures themselves. - */ - -static void ssh_pkt_BinarySink_write(BinarySink *bs, - const void *data, size_t len); -PktOut *ssh_new_packet(void) -{ - PktOut *pkt = snew(PktOut); - - BinarySink_INIT(pkt, ssh_pkt_BinarySink_write); - pkt->data = NULL; - pkt->length = 0; - pkt->maxlen = 0; - pkt->downstream_id = 0; - pkt->additional_log_text = NULL; - pkt->qnode.next = pkt->qnode.prev = NULL; - pkt->qnode.on_free_queue = false; - - return pkt; -} - -static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len) -{ - sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, len); - memcpy(pkt->data + pkt->length, data, len); - pkt->length += len; - pkt->qnode.formal_size = pkt->length; -} - -static void ssh_pkt_BinarySink_write(BinarySink *bs, - const void *data, size_t len) -{ - PktOut *pkt = BinarySink_DOWNCAST(bs, PktOut); - ssh_pkt_adddata(pkt, data, len); -} - -void ssh_free_pktout(PktOut *pkt) -{ - sfree(pkt->data); - sfree(pkt); -} - -/* ---------------------------------------------------------------------- - * Implement zombiechan_new() and its trivial vtable. - */ - -static void zombiechan_free(Channel *chan); -static size_t zombiechan_send( - Channel *chan, bool is_stderr, const void *, size_t); -static void zombiechan_set_input_wanted(Channel *chan, bool wanted); -static void zombiechan_do_nothing(Channel *chan); -static void zombiechan_open_failure(Channel *chan, const char *); -static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof); -static char *zombiechan_log_close_msg(Channel *chan) { return NULL; } - -static const ChannelVtable zombiechan_channelvt = { - .free = zombiechan_free, - .open_confirmation = zombiechan_do_nothing, - .open_failed = zombiechan_open_failure, - .send = zombiechan_send, - .send_eof = zombiechan_do_nothing, - .set_input_wanted = zombiechan_set_input_wanted, - .log_close_msg = zombiechan_log_close_msg, - .want_close = zombiechan_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = chan_no_request_response, -}; - -Channel *zombiechan_new(void) -{ - Channel *chan = snew(Channel); - chan->vt = &zombiechan_channelvt; - chan->initial_fixed_window_size = 0; - return chan; -} - -static void zombiechan_free(Channel *chan) -{ - assert(chan->vt == &zombiechan_channelvt); - sfree(chan); -} - -static void zombiechan_do_nothing(Channel *chan) -{ - assert(chan->vt == &zombiechan_channelvt); -} - -static void zombiechan_open_failure(Channel *chan, const char *errtext) -{ - assert(chan->vt == &zombiechan_channelvt); -} - -static size_t zombiechan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - assert(chan->vt == &zombiechan_channelvt); - return 0; -} - -static void zombiechan_set_input_wanted(Channel *chan, bool enable) -{ - assert(chan->vt == &zombiechan_channelvt); -} - -static bool zombiechan_want_close(Channel *chan, bool sent_eof, bool rcvd_eof) -{ - return true; -} - -/* ---------------------------------------------------------------------- - * Common routines for handling SSH tty modes. - */ - -static unsigned real_ttymode_opcode(unsigned our_opcode, int ssh_version) -{ - switch (our_opcode) { - case TTYMODE_ISPEED: - return ssh_version == 1 ? TTYMODE_ISPEED_SSH1 : TTYMODE_ISPEED_SSH2; - case TTYMODE_OSPEED: - return ssh_version == 1 ? TTYMODE_OSPEED_SSH1 : TTYMODE_OSPEED_SSH2; - default: - return our_opcode; - } -} - -static unsigned our_ttymode_opcode(unsigned real_opcode, int ssh_version) -{ - if (ssh_version == 1) { - switch (real_opcode) { - case TTYMODE_ISPEED_SSH1: - return TTYMODE_ISPEED; - case TTYMODE_OSPEED_SSH1: - return TTYMODE_OSPEED; - default: - return real_opcode; - } - } else { - switch (real_opcode) { - case TTYMODE_ISPEED_SSH2: - return TTYMODE_ISPEED; - case TTYMODE_OSPEED_SSH2: - return TTYMODE_OSPEED; - default: - return real_opcode; - } - } -} - -struct ssh_ttymodes get_ttymodes_from_conf(Seat *seat, Conf *conf) -{ - struct ssh_ttymodes modes; - size_t i; - - static const struct mode_name_type { - const char *mode; - int opcode; - enum { TYPE_CHAR, TYPE_BOOL } type; - } modes_names_types[] = { - #define TTYMODE_CHAR(name, val, index) { #name, val, TYPE_CHAR }, - #define TTYMODE_FLAG(name, val, field, mask) { #name, val, TYPE_BOOL }, - #include "ttymode-list.h" - #undef TTYMODE_CHAR - #undef TTYMODE_FLAG - }; - - memset(&modes, 0, sizeof(modes)); - - for (i = 0; i < lenof(modes_names_types); i++) { - const struct mode_name_type *mode = &modes_names_types[i]; - const char *sval = conf_get_str_str(conf, CONF_ttymodes, mode->mode); - char *to_free = NULL; - - if (!sval) - sval = "N"; /* just in case */ - - /* - * sval[0] can be - * - 'V', indicating that an explicit value follows it; - * - 'A', indicating that we should pass the value through from - * the local environment via get_ttymode; or - * - 'N', indicating that we should explicitly not send this - * mode. - */ - if (sval[0] == 'A') { - sval = to_free = seat_get_ttymode(seat, mode->mode); - } else if (sval[0] == 'V') { - sval++; /* skip the 'V' */ - } else { - /* else 'N', or something from the future we don't understand */ - continue; - } - - if (sval) { - /* - * Parse the string representation of the tty mode - * into the integer value it will take on the wire. - */ - unsigned ival = 0; - - switch (mode->type) { - case TYPE_CHAR: - if (*sval) { - char *next = NULL; - /* We know ctrlparse won't write to the string, so - * casting away const is ugly but allowable. */ - ival = ctrlparse((char *)sval, &next); - if (!next) - ival = sval[0]; - } else { - ival = 255; /* special value meaning "don't set" */ - } - break; - case TYPE_BOOL: - if (stricmp(sval, "yes") == 0 || - stricmp(sval, "on") == 0 || - stricmp(sval, "true") == 0 || - stricmp(sval, "+") == 0) - ival = 1; /* true */ - else if (stricmp(sval, "no") == 0 || - stricmp(sval, "off") == 0 || - stricmp(sval, "false") == 0 || - stricmp(sval, "-") == 0) - ival = 0; /* false */ - else - ival = (atoi(sval) != 0); - break; - default: - unreachable("Bad mode->type"); - } - - modes.have_mode[mode->opcode] = true; - modes.mode_val[mode->opcode] = ival; - } - - sfree(to_free); - } - - { - unsigned ospeed, ispeed; - - /* Unpick the terminal-speed config string. */ - ospeed = ispeed = 38400; /* last-resort defaults */ - sscanf(conf_get_str(conf, CONF_termspeed), "%u,%u", &ospeed, &ispeed); - /* Currently we unconditionally set these */ - modes.have_mode[TTYMODE_ISPEED] = true; - modes.mode_val[TTYMODE_ISPEED] = ispeed; - modes.have_mode[TTYMODE_OSPEED] = true; - modes.mode_val[TTYMODE_OSPEED] = ospeed; - } - - return modes; -} - -struct ssh_ttymodes read_ttymodes_from_packet( - BinarySource *bs, int ssh_version) -{ - struct ssh_ttymodes modes; - memset(&modes, 0, sizeof(modes)); - - while (1) { - unsigned real_opcode, our_opcode; - - real_opcode = get_byte(bs); - if (real_opcode == TTYMODE_END_OF_LIST) - break; - if (real_opcode >= 160) { - /* - * RFC 4254 (and the SSH 1.5 spec): "Opcodes 160 to 255 - * are not yet defined, and cause parsing to stop (they - * should only be used after any other data)." - * - * My interpretation of this is that if one of these - * opcodes appears, it's not a parse _error_, but it is - * something that we don't know how to parse even well - * enough to step over it to find the next opcode, so we - * stop parsing now and assume that the rest of the string - * is composed entirely of things we don't understand and - * (as usual for unsupported terminal modes) silently - * ignore. - */ - return modes; - } - - our_opcode = our_ttymode_opcode(real_opcode, ssh_version); - assert(our_opcode < TTYMODE_LIMIT); - modes.have_mode[our_opcode] = true; - - if (ssh_version == 1 && real_opcode >= 1 && real_opcode <= 127) - modes.mode_val[our_opcode] = get_byte(bs); - else - modes.mode_val[our_opcode] = get_uint32(bs); - } - - return modes; -} - -void write_ttymodes_to_packet(BinarySink *bs, int ssh_version, - struct ssh_ttymodes modes) -{ - unsigned i; - - for (i = 0; i < TTYMODE_LIMIT; i++) { - if (modes.have_mode[i]) { - unsigned val = modes.mode_val[i]; - unsigned opcode = real_ttymode_opcode(i, ssh_version); - - put_byte(bs, opcode); - if (ssh_version == 1 && opcode >= 1 && opcode <= 127) - put_byte(bs, val); - else - put_uint32(bs, val); - } - } - - put_byte(bs, TTYMODE_END_OF_LIST); -} - -/* ---------------------------------------------------------------------- - * Routine for allocating a new channel ID, given a means of finding - * the index field in a given channel structure. - */ - -unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset) -{ - const unsigned CHANNEL_NUMBER_OFFSET = 256; - search234_state ss; - - /* - * First-fit allocation of channel numbers: we always pick the - * lowest unused one. - * - * Every channel before that, and no channel after it, has an ID - * exactly equal to its tree index plus CHANNEL_NUMBER_OFFSET. So - * we can use the search234 system to identify the length of that - * initial sequence, in a single log-time pass down the channels - * tree. - */ - search234_start(&ss, channels); - while (ss.element) { - unsigned localid = *(unsigned *)((char *)ss.element + localid_offset); - if (localid == ss.index + CHANNEL_NUMBER_OFFSET) - search234_step(&ss, +1); - else - search234_step(&ss, -1); - } - - /* - * Now ss.index gives exactly the number of channels in that - * initial sequence. So adding CHANNEL_NUMBER_OFFSET to it must - * give precisely the lowest unused channel number. - */ - return ss.index + CHANNEL_NUMBER_OFFSET; -} - -/* ---------------------------------------------------------------------- - * Functions for handling the comma-separated strings used to store - * lists of protocol identifiers in SSH-2. - */ - -void add_to_commasep_pl(strbuf *buf, ptrlen data) -{ - if (buf->len > 0) - put_byte(buf, ','); - put_datapl(buf, data); -} - -void add_to_commasep(strbuf *buf, const char *data) -{ - add_to_commasep_pl(buf, ptrlen_from_asciz(data)); -} - -bool get_commasep_word(ptrlen *list, ptrlen *word) -{ - const char *comma; - - /* - * Discard empty list elements, should there be any, because we - * never want to return one as if it was a real string. (This - * introduces a mild tolerance of badly formatted data in lists we - * receive, but I think that's acceptable.) - */ - while (list->len > 0 && *(const char *)list->ptr == ',') { - list->ptr = (const char *)list->ptr + 1; - list->len--; - } - - if (!list->len) - return false; - - comma = memchr(list->ptr, ',', list->len); - if (!comma) { - *word = *list; - list->len = 0; - } else { - size_t wordlen = comma - (const char *)list->ptr; - word->ptr = list->ptr; - word->len = wordlen; - list->ptr = (const char *)list->ptr + wordlen + 1; - list->len -= wordlen + 1; - } - return true; -} - -/* ---------------------------------------------------------------------- - * Functions for translating SSH packet type codes into their symbolic - * string names. - */ - -#define TRANSLATE_UNIVERSAL(y, name, value) \ - if (type == value) return #name; -#define TRANSLATE_KEX(y, name, value, ctx) \ - if (type == value && pkt_kctx == ctx) return #name; -#define TRANSLATE_AUTH(y, name, value, ctx) \ - if (type == value && pkt_actx == ctx) return #name; - -const char *ssh1_pkt_type(int type) -{ - SSH1_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, y); - return "unknown"; -} -const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type) -{ - SSH2_MESSAGE_TYPES(TRANSLATE_UNIVERSAL, TRANSLATE_KEX, TRANSLATE_AUTH, y); - return "unknown"; -} - -#undef TRANSLATE_UNIVERSAL -#undef TRANSLATE_KEX -#undef TRANSLATE_AUTH - -/* ---------------------------------------------------------------------- - * Common helper function for clients and implementations of - * PacketProtocolLayer. - */ - -void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new) -{ - new->bpp = old->bpp; - ssh_ppl_setup_queues(new, old->in_pq, old->out_pq); - new->selfptr = old->selfptr; - new->seat = old->seat; - new->ssh = old->ssh; - - *new->selfptr = new; - ssh_ppl_free(old); - - /* The new layer might need to be the first one that sends a - * packet, so trigger a call to its main coroutine immediately. If - * it doesn't need to go first, the worst that will do is return - * straight away. */ - queue_idempotent_callback(&new->ic_process_queue); -} - -void ssh_ppl_free(PacketProtocolLayer *ppl) -{ - delete_callbacks_for_context(ppl); - ppl->vt->free(ppl); -} - -static void ssh_ppl_ic_process_queue_callback(void *context) -{ - PacketProtocolLayer *ppl = (PacketProtocolLayer *)context; - ssh_ppl_process_queue(ppl); -} - -void ssh_ppl_setup_queues(PacketProtocolLayer *ppl, - PktInQueue *inq, PktOutQueue *outq) -{ - ppl->in_pq = inq; - ppl->out_pq = outq; - ppl->in_pq->pqb.ic = &ppl->ic_process_queue; - ppl->ic_process_queue.fn = ssh_ppl_ic_process_queue_callback; - ppl->ic_process_queue.ctx = ppl; - - /* If there's already something on the input queue, it will want - * handling immediately. */ - if (pq_peek(ppl->in_pq)) - queue_idempotent_callback(&ppl->ic_process_queue); -} - -void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text) -{ - /* Messages sent via this function are from the SSH layer, not - * from the server-side process, so they always have the stderr - * flag set. */ - seat_stderr_pl(ppl->seat, ptrlen_from_asciz(text)); - sfree(text); -} - -size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl) -{ - return ppl->out_pq->pqb.total_size; -} - -void ssh_ppl_default_final_output(PacketProtocolLayer *ppl) -{ -} - -static void ssh_ppl_prompts_callback(void *ctx) -{ - ssh_ppl_process_queue((PacketProtocolLayer *)ctx); -} - -prompts_t *ssh_ppl_new_prompts(PacketProtocolLayer *ppl) -{ - prompts_t *p = new_prompts(); - p->callback = ssh_ppl_prompts_callback; - p->callback_ctx = ppl; - return p; -} - -/* ---------------------------------------------------------------------- - * Common helper functions for clients and implementations of - * BinaryPacketProtocol. - */ - -static void ssh_bpp_input_raw_data_callback(void *context) -{ - BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context; - Ssh *ssh = bpp->ssh; /* in case bpp is about to get freed */ - ssh_bpp_handle_input(bpp); - /* If we've now cleared enough backlog on the input connection, we - * may need to unfreeze it. */ - ssh_conn_processed_data(ssh); -} - -static void ssh_bpp_output_packet_callback(void *context) -{ - BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context; - ssh_bpp_handle_output(bpp); -} - -void ssh_bpp_common_setup(BinaryPacketProtocol *bpp) -{ - pq_in_init(&bpp->in_pq); - pq_out_init(&bpp->out_pq); - bpp->input_eof = false; - bpp->ic_in_raw.fn = ssh_bpp_input_raw_data_callback; - bpp->ic_in_raw.ctx = bpp; - bpp->ic_out_pq.fn = ssh_bpp_output_packet_callback; - bpp->ic_out_pq.ctx = bpp; - bpp->out_pq.pqb.ic = &bpp->ic_out_pq; -} - -void ssh_bpp_free(BinaryPacketProtocol *bpp) -{ - delete_callbacks_for_context(bpp); - bpp->vt->free(bpp); -} - -void ssh2_bpp_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category) -{ - PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_DISCONNECT); - put_uint32(pkt, category); - put_stringz(pkt, msg); - put_stringz(pkt, "en"); /* language tag */ - pq_push(&bpp->out_pq, pkt); -} - -#define BITMAP_UNIVERSAL(y, name, value) \ - | (value >= y && value < y+32 \ - ? 1UL << (value >= y && value < y+32 ? (value-y) : 0) \ - : 0) -#define BITMAP_CONDITIONAL(y, name, value, ctx) \ - BITMAP_UNIVERSAL(y, name, value) -#define SSH2_BITMAP_WORD(y) \ - (0 SSH2_MESSAGE_TYPES(BITMAP_UNIVERSAL, BITMAP_CONDITIONAL, \ - BITMAP_CONDITIONAL, (32*y))) - -bool ssh2_bpp_check_unimplemented(BinaryPacketProtocol *bpp, PktIn *pktin) -{ - static const unsigned valid_bitmap[] = { - SSH2_BITMAP_WORD(0), - SSH2_BITMAP_WORD(1), - SSH2_BITMAP_WORD(2), - SSH2_BITMAP_WORD(3), - SSH2_BITMAP_WORD(4), - SSH2_BITMAP_WORD(5), - SSH2_BITMAP_WORD(6), - SSH2_BITMAP_WORD(7), - }; - - if (pktin->type < 0x100 && - !((valid_bitmap[pktin->type >> 5] >> (pktin->type & 0x1F)) & 1)) { - PktOut *pkt = ssh_bpp_new_pktout(bpp, SSH2_MSG_UNIMPLEMENTED); - put_uint32(pkt, pktin->sequence); - pq_push(&bpp->out_pq, pkt); - return true; - } - - return false; -} - -#undef BITMAP_UNIVERSAL -#undef BITMAP_CONDITIONAL -#undef SSH2_BITMAP_WORD - -/* ---------------------------------------------------------------------- - * Centralised component of SSH host key verification. - * - * verify_ssh_host_key is called from both the SSH-1 and SSH-2 - * transport layers, and does the initial work of checking whether the - * host key is already known. If so, it returns success on its own - * account; otherwise, it calls out to the Seat to give an interactive - * prompt (the nature of which varies depending on the Seat itself). - */ - -SeatPromptResult verify_ssh_host_key( - InteractionReadySeat iseat, Conf *conf, const char *host, int port, - ssh_key *key, const char *keytype, char *keystr, const char *keydisp, - char **fingerprints, int ca_count, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - /* - * First, check if the Conf includes a manual specification of the - * expected host key. If so, that completely supersedes everything - * else, including the normal host key cache _and_ including - * manual overrides: we return success or failure immediately, - * entirely based on whether the key matches the Conf. - */ - if (conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) { - if (fingerprints) { - for (size_t i = 0; i < SSH_N_FPTYPES; i++) { - /* - * Each 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 hash at the end - * of the string. - */ - const char *fingerprint = fingerprints[i]; - if (!fingerprint) - continue; - const char *p = strrchr(fingerprint, ' '); - fingerprint = p ? p+1 : fingerprint; - if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, - fingerprint)) - return SPR_OK; - } - } - - if (key) { - /* - * Construct the base64-encoded public key blob and see if - * that's listed. - */ - strbuf *binblob; - char *base64blob; - int atoms, i; - binblob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(binblob)); - atoms = (binblob->len + 2) / 3; - base64blob = snewn(atoms * 4 + 1, char); - for (i = 0; i < atoms; i++) - base64_encode_atom(binblob->u + 3*i, - binblob->len - 3*i, base64blob + 4*i); - base64blob[atoms * 4] = '\0'; - strbuf_free(binblob); - if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, - base64blob)) { - sfree(base64blob); - return SPR_OK; - } - sfree(base64blob); - } - - return SPR_SW_ABORT("Host key not in manually configured list"); - } - - /* - * Next, check the host key cache. - */ - int storage_status = check_stored_host_key(host, port, keytype, keystr); - if (storage_status == 0) /* matching key was found in the cache */ - return SPR_OK; - - /* - * The key is either missing from the cache, or does not match. - * Either way, fall back to an interactive prompt from the Seat. - */ - SeatDialogText *text = seat_dialog_text_new(); - const SeatDialogPromptDescriptions *pds = - seat_prompt_descriptions(iseat.seat); - - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - - seat_dialog_text_append( - text, SDT_TITLE, "%s Security Alert", appname); - - HelpCtx helpctx; - - if (key && ssh_key_alg(key)->is_certificate) { - seat_dialog_text_append( - text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!"); - seat_dialog_text_append( - text, SDT_PARA, "This server presented a certified host key:"); - seat_dialog_text_append( - text, SDT_DISPLAY, "%s (port %d)", host, port); - if (ca_count) { - seat_dialog_text_append( - text, SDT_PARA, "which was signed by a different " - "certification authority from the %s %s is configured to " - "trust for this server.", ca_count > 1 ? "ones" : "one", - appname); - if (storage_status == 2) { - seat_dialog_text_append( - text, SDT_PARA, "ALSO, that key does not match the key " - "%s had previously cached for this server.", appname); - seat_dialog_text_append( - text, SDT_PARA, "This means that either another " - "certification authority is operating in this realm AND " - "the server administrator has changed the host key, or " - "you have actually connected to another computer " - "pretending to be the server."); - } else { - seat_dialog_text_append( - text, SDT_PARA, "This means that either another " - "certification authority is operating in this realm, or " - "you have actually connected to another computer " - "pretending to be the server."); - } - } else { - assert(storage_status == 2); - seat_dialog_text_append( - text, SDT_PARA, "which does not match the certified key %s " - "had previously cached for this server.", appname); - seat_dialog_text_append( - text, SDT_PARA, "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."); - } - seat_dialog_text_append( - text, SDT_PARA, "The new %s key fingerprint is:", keytype); - seat_dialog_text_append( - text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); - helpctx = HELPCTX(errors_cert_mismatch); - } else if (storage_status == 1) { - seat_dialog_text_append( - text, SDT_PARA, "The host key is not cached for this server:"); - seat_dialog_text_append( - text, SDT_DISPLAY, "%s (port %d)", host, port); - seat_dialog_text_append( - text, SDT_PARA, "You have no guarantee that the server is the " - "computer you think it is."); - seat_dialog_text_append( - text, SDT_PARA, "The server's %s key fingerprint is:", keytype); - seat_dialog_text_append( - text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); - helpctx = HELPCTX(errors_hostkey_absent); - } else { - seat_dialog_text_append( - text, SDT_SCARY_HEADING, "WARNING - POTENTIAL SECURITY BREACH!"); - seat_dialog_text_append( - text, SDT_PARA, "The host key does not match the one %s has " - "cached for this server:", appname); - seat_dialog_text_append( - text, SDT_DISPLAY, "%s (port %d)", host, port); - seat_dialog_text_append( - text, SDT_PARA, "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."); - seat_dialog_text_append( - text, SDT_PARA, "The new %s key fingerprint is:", keytype); - seat_dialog_text_append( - text, SDT_DISPLAY, "%s", fingerprints[fptype_default]); - helpctx = HELPCTX(errors_hostkey_changed); - } - - /* The above text is printed even in batch mode. Here's where we stop if - * we can't present interactive prompts. */ - seat_dialog_text_append( - text, SDT_BATCH_ABORT, "Connection abandoned."); - - if (storage_status == 1) { - seat_dialog_text_append( - text, SDT_PARA, "If you trust this host, %s to add the key to " - "%s's cache and carry on connecting.", - pds->hk_accept_action, appname); - if (key && ssh_key_alg(key)->is_certificate) { - seat_dialog_text_append( - text, SDT_PARA, "(Storing this certified key in the cache " - "will NOT cause its certification authority to be trusted " - "for any other key or host.)"); - } - seat_dialog_text_append( - text, SDT_PARA, "If you want to carry on connecting just once, " - "without adding the key to the cache, %s.", - pds->hk_connect_once_action); - seat_dialog_text_append( - text, SDT_PARA, "If you do not trust this host, %s to abandon the " - "connection.", pds->hk_cancel_action); - seat_dialog_text_append( - text, SDT_PROMPT, "Store key in cache?"); - } else { - seat_dialog_text_append( - text, SDT_PARA, "If you were expecting this change and trust the " - "new key, %s to update %s's cache and carry on connecting.", - pds->hk_accept_action, appname); - if (key && ssh_key_alg(key)->is_certificate) { - seat_dialog_text_append( - text, SDT_PARA, "(Storing this certified key in the cache " - "will NOT cause its certification authority to be trusted " - "for any other key or host.)"); - } - seat_dialog_text_append( - text, SDT_PARA, "If you want to carry on connecting but without " - "updating the cache, %s.", pds->hk_connect_once_action); - seat_dialog_text_append( - text, SDT_PARA, "If you want to abandon the connection " - "completely, %s to cancel. %s is the ONLY guaranteed safe choice.", - pds->hk_cancel_action, pds->hk_cancel_action_Participle); - seat_dialog_text_append( - text, SDT_PROMPT, "Update cached key?"); - } - - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, - "Full text of host's public key"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_BLOB, "%s", keydisp); - - if (fingerprints[SSH_FPTYPE_SHA256]) { - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "SHA256 fingerprint"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", - fingerprints[SSH_FPTYPE_SHA256]); - } - if (fingerprints[SSH_FPTYPE_MD5]) { - seat_dialog_text_append(text, SDT_MORE_INFO_KEY, "MD5 fingerprint"); - seat_dialog_text_append(text, SDT_MORE_INFO_VALUE_SHORT, "%s", - fingerprints[SSH_FPTYPE_MD5]); - } - - SeatPromptResult toret = seat_confirm_ssh_host_key( - iseat, host, port, keytype, keystr, text, helpctx, callback, ctx); - seat_dialog_text_free(text); - return toret; -} - -SeatPromptResult confirm_weak_crypto_primitive( - InteractionReadySeat iseat, const char *algtype, const char *algname, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx, - WeakCryptoReason wcr) -{ - SeatDialogText *text = seat_dialog_text_new(); - const SeatDialogPromptDescriptions *pds = - seat_prompt_descriptions(iseat.seat); - - seat_dialog_text_append(text, SDT_TITLE, "%s Security Alert", appname); - - switch (wcr) { - case WCR_BELOW_THRESHOLD: - seat_dialog_text_append( - text, SDT_PARA, - "The first %s supported by the server is %s, " - "which is below the configured warning threshold.", - algtype, algname); - break; - case WCR_TERRAPIN: - case WCR_TERRAPIN_AVOIDABLE: - seat_dialog_text_append( - text, SDT_PARA, - "The %s selected for this session is %s, " - "which, with this server, is vulnerable to the 'Terrapin' attack " - "CVE-2023-48795, potentially allowing an attacker to modify " - "the encrypted session.", - algtype, algname); - seat_dialog_text_append( - text, SDT_PARA, - "Upgrading, patching, or reconfiguring this SSH server is the " - "best way to avoid this vulnerability, if possible."); - if (wcr == WCR_TERRAPIN_AVOIDABLE) { - seat_dialog_text_append( - text, SDT_PARA, - "You can also avoid this vulnerability by abandoning " - "this connection, moving ChaCha20 to below the " - "'warn below here' line in PuTTY's SSH cipher " - "configuration (so that an algorithm without the " - "vulnerability will be selected), and starting a new " - "connection."); - } - break; - default: - unreachable("bad WeakCryptoReason"); - } - - /* In batch mode, we print the above information and then this - * abort message, and stop. */ - seat_dialog_text_append(text, SDT_BATCH_ABORT, "Connection abandoned."); - - seat_dialog_text_append( - text, SDT_PARA, "To accept the risk and continue, %s. " - "To abandon the connection, %s.", - pds->weak_accept_action, pds->weak_cancel_action); - - seat_dialog_text_append(text, SDT_PROMPT, "Continue with connection?"); - - SeatPromptResult toret = seat_confirm_weak_crypto_primitive( - iseat, text, callback, ctx); - seat_dialog_text_free(text); - return toret; -} - -SeatPromptResult confirm_weak_cached_hostkey( - InteractionReadySeat iseat, const char *algname, const char **betteralgs, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - SeatDialogText *text = seat_dialog_text_new(); - const SeatDialogPromptDescriptions *pds = - seat_prompt_descriptions(iseat.seat); - - seat_dialog_text_append(text, SDT_TITLE, "%s Security Alert", appname); - - seat_dialog_text_append( - text, SDT_PARA, - "The first host key type we have stored for this server " - "is %s, which is below the configured warning threshold.", algname); - - seat_dialog_text_append( - text, SDT_PARA, - "The server also provides the following types of host key " - "above the threshold, which we do not have stored:"); - - for (const char **p = betteralgs; *p; p++) - seat_dialog_text_append(text, SDT_DISPLAY, "%s", *p); - - /* In batch mode, we print the above information and then this - * abort message, and stop. */ - seat_dialog_text_append(text, SDT_BATCH_ABORT, "Connection abandoned."); - - seat_dialog_text_append( - text, SDT_PARA, "To accept the risk and continue, %s. " - "To abandon the connection, %s.", - pds->weak_accept_action, pds->weak_cancel_action); - - seat_dialog_text_append(text, SDT_PROMPT, "Continue with connection?"); - - SeatPromptResult toret = seat_confirm_weak_cached_hostkey( - iseat, text, callback, ctx); - seat_dialog_text_free(text); - return toret; -} - -/* ---------------------------------------------------------------------- - * Common functions shared between SSH-1 layers. - */ - -bool ssh1_common_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) -{ - /* - * 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 (!(ppl->remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { - add_special(ctx, "IGNORE message", SS_NOP, 0); - return true; - } - - return false; -} - -bool ssh1_common_filter_queue(PacketProtocolLayer *ppl) -{ - PktIn *pktin; - ptrlen msg; - - while ((pktin = pq_peek(ppl->in_pq)) != NULL) { - switch (pktin->type) { - case SSH1_MSG_DISCONNECT: - msg = get_string(pktin); - ssh_remote_error(ppl->ssh, - "Remote side sent disconnect message:\n\"%.*s\"", - PTRLEN_PRINTF(msg)); - /* don't try to pop the queue, because we've been freed! */ - return true; /* indicate that we've been freed */ - - case SSH1_MSG_DEBUG: - msg = get_string(pktin); - ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg)); - pq_pop(ppl->in_pq); - break; - - case SSH1_MSG_IGNORE: - /* Do nothing, because we're ignoring it! Duhh. */ - pq_pop(ppl->in_pq); - break; - - default: - return false; - } - } - - return false; -} - -void ssh1_compute_session_id( - unsigned char *session_id, const unsigned char *cookie, - RSAKey *hostkey, RSAKey *servkey) -{ - ssh_hash *hash = ssh_hash_new(&ssh_md5); - - for (size_t i = (mp_get_nbits(hostkey->modulus) + 7) / 8; i-- ;) - put_byte(hash, mp_get_byte(hostkey->modulus, i)); - for (size_t i = (mp_get_nbits(servkey->modulus) + 7) / 8; i-- ;) - put_byte(hash, mp_get_byte(servkey->modulus, i)); - put_data(hash, cookie, 8); - ssh_hash_final(hash, session_id); -} - -/* ---------------------------------------------------------------------- - * Wrapper function to handle the abort-connection modes of a - * SeatPromptResult without a lot of verbiage at every call site. - * - * Can become ssh_sw_abort or ssh_user_close, depending on the kind of - * negative SeatPromptResult. - */ -void ssh_spr_close(Ssh *ssh, SeatPromptResult spr, const char *context) -{ - if (spr.kind == SPRK_USER_ABORT) { - ssh_user_close(ssh, "User aborted at %s", context); - } else { - assert(spr.kind == SPRK_SW_ABORT); - char *err = spr_get_error_message(spr); - ssh_sw_abort(ssh, "%s", err); - sfree(err); - } -} diff --git a/ssh/connection1-client.c b/ssh/connection1-client.c deleted file mode 100644 index 41bf97160..000000000 --- a/ssh/connection1-client.c +++ /dev/null @@ -1,552 +0,0 @@ -/* - * Client-specific parts of the SSH-1 connection layer. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "channel.h" -#include "sshcr.h" -#include "connection1.h" - -void ssh1_connection_direction_specific_setup( - struct ssh1_connection_state *s) -{ - if (!s->mainchan) { - /* - * Start up the main session, by telling mainchan.c to do it - * all just as it would in SSH-2, and translating those - * concepts to SSH-1's non-channel-shaped idea of the main - * session. - */ - s->mainchan = mainchan_new( - &s->ppl, &s->cl, s->conf, s->term_width, s->term_height, - false /* is_simple */, NULL); - } -} - -typedef void (*sf_handler_fn_t)(struct ssh1_connection_state *s, - bool success, void *ctx); - -struct outstanding_succfail { - sf_handler_fn_t handler; - void *ctx; - struct outstanding_succfail *next; - - /* - * The 'trivial' flag is set if this handler is in response to a - * request for which the SSH-1 protocol doesn't actually specify a - * response packet. The client of this system (mainchan.c) will - * expect to get an acknowledgment regardless, so we arrange to - * send that ack immediately after the rest of the queue empties. - */ - bool trivial; -}; - -static void ssh1_connection_process_trivial_succfails(void *vs); - -static void ssh1_queue_succfail_handler( - struct ssh1_connection_state *s, sf_handler_fn_t handler, void *ctx, - bool trivial) -{ - struct outstanding_succfail *osf = snew(struct outstanding_succfail); - osf->handler = handler; - osf->ctx = ctx; - osf->trivial = trivial; - osf->next = NULL; - if (s->succfail_tail) - s->succfail_tail->next = osf; - else - s->succfail_head = osf; - s->succfail_tail = osf; - - /* In case this one was trivial and the queue was already empty, - * we should make sure we run the handler promptly, and the - * easiest way is to queue it anyway and then run a trivials pass - * by callback. */ - queue_toplevel_callback(ssh1_connection_process_trivial_succfails, s); -} - -static void ssh1_connection_process_succfail( - struct ssh1_connection_state *s, bool success) -{ - struct outstanding_succfail *prevhead = s->succfail_head; - s->succfail_head = s->succfail_head->next; - if (!s->succfail_head) - s->succfail_tail = NULL; - prevhead->handler(s, success, prevhead->ctx); - sfree(prevhead); -} - -static void ssh1_connection_process_trivial_succfails(void *vs) -{ - struct ssh1_connection_state *s = (struct ssh1_connection_state *)vs; - while (s->succfail_head && s->succfail_head->trivial) - ssh1_connection_process_succfail(s, true); -} - -bool ssh1_handle_direction_specific_packet( - struct ssh1_connection_state *s, PktIn *pktin) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - - PktOut *pktout; - struct ssh1_channel *c; - unsigned remid; - struct ssh_rportfwd pf, *pfp; - ptrlen host, data; - int port; - - switch (pktin->type) { - case SSH1_SMSG_SUCCESS: - case SSH1_SMSG_FAILURE: - if (!s->succfail_head) { - ssh_remote_error(s->ppl.ssh, - "Received %s with no outstanding request", - ssh1_pkt_type(pktin->type)); - return true; - } - - ssh1_connection_process_succfail( - s, pktin->type == SSH1_SMSG_SUCCESS); - queue_toplevel_callback( - ssh1_connection_process_trivial_succfails, s); - - return true; - - case SSH1_SMSG_X11_OPEN: - remid = get_uint32(pktin); - - /* Refuse if X11 forwarding is disabled. */ - if (!s->X11_fwd_enabled) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, remid); - pq_push(s->ppl.out_pq, pktout); - ppl_logevent("Rejected X11 connect request"); - } else { - c = snew(struct ssh1_channel); - c->connlayer = s; - ssh1_channel_init(c); - c->remoteid = remid; - c->chan = x11_new_channel(s->x11authtree, &c->sc, - NULL, -1, false); - c->remoteid = remid; - c->halfopen = false; - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, c->localid); - pq_push(s->ppl.out_pq, pktout); - ppl_logevent("Opened X11 forward channel"); - } - - return true; - - case SSH1_SMSG_AGENT_OPEN: - remid = get_uint32(pktin); - - /* Refuse if agent forwarding is disabled. */ - if (!ssh_agent_forwarding_permitted(&s->cl)) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, remid); - pq_push(s->ppl.out_pq, pktout); - } else { - c = snew(struct ssh1_channel); - c->connlayer = s; - ssh1_channel_init(c); - c->remoteid = remid; - c->halfopen = false; - - /* - * If possible, make a stream-oriented connection to the - * agent and set up an ordinary port-forwarding type - * channel over it. - */ - Plug *plug; - Channel *ch = portfwd_raw_new(&s->cl, &plug, true); - Socket *skt = agent_connect(plug); - if (!sk_socket_error(skt)) { - portfwd_raw_setup(ch, skt, &c->sc); - c->chan = ch; - } else { - portfwd_raw_free(ch); - - /* - * Otherwise, fall back to the old-fashioned system of - * parsing the forwarded data stream ourselves for - * message boundaries, and passing each individual - * message to the one-off agent_query(). - */ - c->chan = agentf_new(&c->sc); - } - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, c->localid); - pq_push(s->ppl.out_pq, pktout); - } - - return true; - - case SSH1_MSG_PORT_OPEN: - remid = get_uint32(pktin); - host = get_string(pktin); - port = toint(get_uint32(pktin)); - - pf.dhost = mkstr(host); - pf.dport = port; - pfp = find234(s->rportfwds, &pf, NULL); - - if (!pfp) { - ppl_logevent("Rejected remote port open request for %s:%d", - pf.dhost, port); - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, remid); - pq_push(s->ppl.out_pq, pktout); - } else { - char *err; - - c = snew(struct ssh1_channel); - c->connlayer = s; - ppl_logevent("Received remote port open request for %s:%d", - pf.dhost, port); - err = portfwdmgr_connect( - s->portfwdmgr, &c->chan, pf.dhost, port, - &c->sc, pfp->addressfamily); - - if (err) { - ppl_logevent("Port open failed: %s", err); - sfree(err); - ssh1_channel_free(c); - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, remid); - pq_push(s->ppl.out_pq, pktout); - } else { - ssh1_channel_init(c); - c->remoteid = remid; - c->halfopen = false; - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, c->localid); - pq_push(s->ppl.out_pq, pktout); - ppl_logevent("Forwarded port opened successfully"); - } - } - - sfree(pf.dhost); - - return true; - - case SSH1_SMSG_STDOUT_DATA: - case SSH1_SMSG_STDERR_DATA: - data = get_string(pktin); - if (!get_err(pktin)) { - int bufsize = seat_output( - s->ppl.seat, pktin->type == SSH1_SMSG_STDERR_DATA, - data.ptr, data.len); - if (!s->stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) { - s->stdout_throttling = true; - ssh_throttle_conn(s->ppl.ssh, +1); - } - } - - return true; - - case SSH1_SMSG_EXIT_STATUS: { - int exitcode = get_uint32(pktin); - ppl_logevent("Server sent command exit status %d", exitcode); - ssh_got_exitcode(s->ppl.ssh, exitcode); - - s->session_terminated = true; - return true; - } - - default: - return false; - } -} - -static void ssh1mainchan_succfail_wantreply(struct ssh1_connection_state *s, - bool success, void *ctx) -{ - chan_request_response(s->mainchan_chan, success); -} - -static void ssh1mainchan_succfail_nowantreply(struct ssh1_connection_state *s, - bool success, void *ctx) -{ -} - -static void ssh1mainchan_queue_response(struct ssh1_connection_state *s, - bool want_reply, bool trivial) -{ - sf_handler_fn_t handler = (want_reply ? ssh1mainchan_succfail_wantreply : - ssh1mainchan_succfail_nowantreply); - ssh1_queue_succfail_handler(s, handler, NULL, trivial); -} - -static void ssh1mainchan_request_x11_forwarding( - SshChannel *sc, bool want_reply, const char *authproto, - const char *authdata, int screen_number, bool oneshot) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_X11_REQUEST_FORWARDING); - put_stringz(pktout, authproto); - put_stringz(pktout, authdata); - if (s->local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) - put_uint32(pktout, screen_number); - pq_push(s->ppl.out_pq, pktout); - - ssh1mainchan_queue_response(s, want_reply, false); -} - -static void ssh1mainchan_request_agent_forwarding( - SshChannel *sc, bool want_reply) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_AGENT_REQUEST_FORWARDING); - pq_push(s->ppl.out_pq, pktout); - - ssh1mainchan_queue_response(s, want_reply, false); -} - -static void ssh1mainchan_request_pty( - SshChannel *sc, bool want_reply, Conf *conf, int w, int h) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_PTY); - put_stringz(pktout, conf_get_str(s->conf, CONF_termtype)); - put_uint32(pktout, h); - put_uint32(pktout, w); - put_uint32(pktout, 0); /* width in pixels */ - put_uint32(pktout, 0); /* height in pixels */ - write_ttymodes_to_packet( - BinarySink_UPCAST(pktout), 1, - get_ttymodes_from_conf(s->ppl.seat, conf)); - pq_push(s->ppl.out_pq, pktout); - - ssh1mainchan_queue_response(s, want_reply, false); -} - -static bool ssh1mainchan_send_env_var( - SshChannel *sc, bool want_reply, const char *var, const char *value) -{ - return false; /* SSH-1 doesn't support this at all */ -} - -static void ssh1mainchan_start_shell(SshChannel *sc, bool want_reply) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_SHELL); - pq_push(s->ppl.out_pq, pktout); - - ssh1mainchan_queue_response(s, want_reply, true); -} - -static void ssh1mainchan_start_command( - SshChannel *sc, bool want_reply, const char *command) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EXEC_CMD); - put_stringz(pktout, command); - pq_push(s->ppl.out_pq, pktout); - - ssh1mainchan_queue_response(s, want_reply, true); -} - -static bool ssh1mainchan_start_subsystem( - SshChannel *sc, bool want_reply, const char *subsystem) -{ - return false; /* SSH-1 doesn't support this at all */ -} - -static bool ssh1mainchan_send_serial_break( - SshChannel *sc, bool want_reply, int length) -{ - return false; /* SSH-1 doesn't support this at all */ -} - -static bool ssh1mainchan_send_signal( - SshChannel *sc, bool want_reply, const char *signame) -{ - return false; /* SSH-1 doesn't support this at all */ -} - -static void ssh1mainchan_send_terminal_size_change( - SshChannel *sc, int w, int h) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_WINDOW_SIZE); - put_uint32(pktout, h); - put_uint32(pktout, w); - put_uint32(pktout, 0); /* width in pixels */ - put_uint32(pktout, 0); /* height in pixels */ - pq_push(s->ppl.out_pq, pktout); -} - -static void ssh1mainchan_hint_channel_is_simple(SshChannel *sc) -{ -} - -static size_t ssh1mainchan_write( - SshChannel *sc, bool is_stderr, const void *data, size_t len) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_STDIN_DATA); - put_string(pktout, data, len); - pq_push(s->ppl.out_pq, pktout); - - return 0; -} - -static void ssh1mainchan_write_eof(SshChannel *sc) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_EOF); - pq_push(s->ppl.out_pq, pktout); -} - -static const SshChannelVtable ssh1mainchan_vtable = { - .write = ssh1mainchan_write, - .write_eof = ssh1mainchan_write_eof, - .request_x11_forwarding = ssh1mainchan_request_x11_forwarding, - .request_agent_forwarding = ssh1mainchan_request_agent_forwarding, - .request_pty = ssh1mainchan_request_pty, - .send_env_var = ssh1mainchan_send_env_var, - .start_shell = ssh1mainchan_start_shell, - .start_command = ssh1mainchan_start_command, - .start_subsystem = ssh1mainchan_start_subsystem, - .send_serial_break = ssh1mainchan_send_serial_break, - .send_signal = ssh1mainchan_send_signal, - .send_terminal_size_change = ssh1mainchan_send_terminal_size_change, - .hint_channel_is_simple = ssh1mainchan_hint_channel_is_simple, - /* other methods are NULL */ -}; - -static void ssh1_session_confirm_callback(void *vctx) -{ - struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx; - chan_open_confirmation(s->mainchan_chan); -} - -SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - s->mainchan_sc.vt = &ssh1mainchan_vtable; - s->mainchan_sc.cl = &s->cl; - s->mainchan_chan = chan; - queue_toplevel_callback(ssh1_session_confirm_callback, s); - return &s->mainchan_sc; -} - -static void ssh1_rportfwd_response(struct ssh1_connection_state *s, - bool success, void *ctx) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx; - - if (success) { - ppl_logevent("Remote port forwarding from %s enabled", - rpf->log_description); - } else { - ppl_logevent("Remote port forwarding from %s refused", - rpf->log_description); - - struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); - assert(realpf == rpf); - portfwdmgr_close(s->portfwdmgr, rpf->pfr); - free_rportfwd(rpf); - } -} - -struct ssh_rportfwd *ssh1_rportfwd_alloc( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd); - - rpf->shost = dupstr(shost); - rpf->sport = sport; - rpf->dhost = dupstr(dhost); - rpf->dport = dport; - rpf->addressfamily = addressfamily; - rpf->log_description = dupstr(log_description); - rpf->pfr = pfr; - - if (add234(s->rportfwds, rpf) != rpf) { - free_rportfwd(rpf); - return NULL; - } - - PktOut *pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_PORT_FORWARD_REQUEST); - put_uint32(pktout, rpf->sport); - put_stringz(pktout, rpf->dhost); - put_uint32(pktout, rpf->dport); - pq_push(s->ppl.out_pq, pktout); - - ssh1_queue_succfail_handler(s, ssh1_rportfwd_response, rpf, false); - - return rpf; -} - -SshChannel *ssh1_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) -{ - unreachable("Should never be called in the client"); -} - -SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan) -{ - unreachable("Should never be called in the client"); -} - -bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s) -{ - seat_set_trust_status(s->ppl.seat, false); - if (!seat_has_mixed_input_stream(s->ppl.seat)) - return false; - if (seat_can_set_trust_status(s->ppl.seat)) - return false; - return true; -} diff --git a/ssh/connection1-server.c b/ssh/connection1-server.c deleted file mode 100644 index cc69bdb39..000000000 --- a/ssh/connection1-server.c +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Server-specific parts of the SSH-1 connection layer. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "channel.h" -#include "sshcr.h" -#include "connection1.h" -#include "server.h" - -static size_t ssh1sesschan_write(SshChannel *c, bool is_stderr, - const void *, size_t); -static void ssh1sesschan_write_eof(SshChannel *c); -static void ssh1sesschan_initiate_close(SshChannel *c, const char *err); -static void ssh1sesschan_send_exit_status(SshChannel *c, int status); -static void ssh1sesschan_send_exit_signal( - SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg); - -static const SshChannelVtable ssh1sesschan_vtable = { - .write = ssh1sesschan_write, - .write_eof = ssh1sesschan_write_eof, - .initiate_close = ssh1sesschan_initiate_close, - .send_exit_status = ssh1sesschan_send_exit_status, - .send_exit_signal = ssh1sesschan_send_exit_signal, - /* everything else is NULL */ -}; - -void ssh1connection_server_configure( - PacketProtocolLayer *ppl, const SshServerConfig *ssc) -{ - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - s->ssc = ssc; -} - -void ssh1_connection_direction_specific_setup( - struct ssh1_connection_state *s) -{ - if (!s->mainchan_chan) { - s->mainchan_sc.vt = &ssh1sesschan_vtable; - s->mainchan_sc.cl = &s->cl; - s->mainchan_chan = sesschan_new( - &s->mainchan_sc, s->ppl.logctx, NULL, s->ssc); - } -} - -bool ssh1_handle_direction_specific_packet( - struct ssh1_connection_state *s, PktIn *pktin) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - PktOut *pktout; - struct ssh1_channel *c; - unsigned remid; - ptrlen host, cmd, data; - char *host_str, *err; - int port, listenport; - bool success; - - switch (pktin->type) { - case SSH1_CMSG_EXEC_SHELL: - if (s->finished_setup) - goto unexpected_setup_packet; - - ppl_logevent("Client requested a shell"); - chan_run_shell(s->mainchan_chan); - s->finished_setup = true; - return true; - - case SSH1_CMSG_EXEC_CMD: - if (s->finished_setup) - goto unexpected_setup_packet; - - cmd = get_string(pktin); - ppl_logevent("Client sent command '%.*s'", PTRLEN_PRINTF(cmd)); - chan_run_command(s->mainchan_chan, cmd); - s->finished_setup = true; - return true; - - case SSH1_CMSG_REQUEST_COMPRESSION: - if (s->compressing || !s->ssc->ssh1_allow_compression) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE); - pq_push(s->ppl.out_pq, pktout); - } else { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS); - pq_push(s->ppl.out_pq, pktout); - /* Synchronous run of output formatting, to ensure that - * success packet is converted into wire format before we - * start compressing */ - ssh_bpp_handle_output(s->ppl.bpp); - /* And now ensure that the _next_ packet will be the first - * compressed one. */ - ssh1_bpp_start_compression(s->ppl.bpp); - s->compressing = true; - } - - return true; - - case SSH1_CMSG_REQUEST_PTY: { - if (s->finished_setup) - goto unexpected_setup_packet; - - ptrlen termtype = get_string(pktin); - unsigned height = get_uint32(pktin); - unsigned width = get_uint32(pktin); - unsigned pixwidth = get_uint32(pktin); - unsigned pixheight = get_uint32(pktin); - struct ssh_ttymodes modes = read_ttymodes_from_packet( - BinarySource_UPCAST(pktin), 1); - - if (get_err(pktin)) { - ppl_logevent("Unable to decode pty request packet"); - success = false; - } else if (!chan_allocate_pty( - s->mainchan_chan, termtype, width, height, - pixwidth, pixheight, modes)) { - ppl_logevent("Unable to allocate a pty"); - success = false; - } else { - success = true; - } - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); - pq_push(s->ppl.out_pq, pktout); - return true; - } - - case SSH1_CMSG_PORT_FORWARD_REQUEST: - if (s->finished_setup) - goto unexpected_setup_packet; - - listenport = toint(get_uint32(pktin)); - host = get_string(pktin); - port = toint(get_uint32(pktin)); - - ppl_logevent("Client requested port %d forward to %.*s:%d", - listenport, PTRLEN_PRINTF(host), port); - - host_str = mkstr(host); - success = portfwdmgr_listen( - s->portfwdmgr, NULL, listenport, host_str, port, s->conf); - sfree(host_str); - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); - pq_push(s->ppl.out_pq, pktout); - return true; - - case SSH1_CMSG_X11_REQUEST_FORWARDING: { - if (s->finished_setup) - goto unexpected_setup_packet; - - ptrlen authproto = get_string(pktin); - ptrlen authdata = get_string(pktin); - unsigned screen_number = 0; - if (s->remote_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) - screen_number = get_uint32(pktin); - - success = chan_enable_x11_forwarding( - s->mainchan_chan, false, authproto, authdata, screen_number); - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); - pq_push(s->ppl.out_pq, pktout); - return true; - } - - case SSH1_CMSG_AGENT_REQUEST_FORWARDING: - if (s->finished_setup) - goto unexpected_setup_packet; - - success = chan_enable_agent_forwarding(s->mainchan_chan); - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, (success ? SSH1_SMSG_SUCCESS : SSH1_SMSG_FAILURE)); - pq_push(s->ppl.out_pq, pktout); - return true; - - case SSH1_CMSG_STDIN_DATA: - data = get_string(pktin); - chan_send(s->mainchan_chan, false, data.ptr, data.len); - return true; - - case SSH1_CMSG_EOF: - chan_send_eof(s->mainchan_chan); - return true; - - case SSH1_CMSG_WINDOW_SIZE: - return true; - - case SSH1_MSG_PORT_OPEN: - remid = get_uint32(pktin); - host = get_string(pktin); - port = toint(get_uint32(pktin)); - - host_str = mkstr(host); - - ppl_logevent("Received request to connect to port %s:%d", - host_str, port); - c = snew(struct ssh1_channel); - c->connlayer = s; - err = portfwdmgr_connect( - s->portfwdmgr, &c->chan, host_str, port, - &c->sc, ADDRTYPE_UNSPEC); - - sfree(host_str); - - if (err) { - ppl_logevent("Port open failed: %s", err); - sfree(err); - ssh1_channel_free(c); - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, remid); - pq_push(s->ppl.out_pq, pktout); - } else { - ssh1_channel_init(c); - c->remoteid = remid; - c->halfopen = false; - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, c->localid); - pq_push(s->ppl.out_pq, pktout); - ppl_logevent("Forwarded port opened successfully"); - } - - return true; - - case SSH1_CMSG_EXIT_CONFIRMATION: - if (!s->sent_exit_status) { - ssh_proto_error(s->ppl.ssh, "Received SSH1_CMSG_EXIT_CONFIRMATION" - " without having sent SSH1_SMSG_EXIT_STATUS"); - return true; - } - ppl_logevent("Client sent exit confirmation"); - return true; - - default: - return false; - } - - unexpected_setup_packet: - ssh_proto_error(s->ppl.ssh, "Received unexpected setup packet after the " - "setup phase, type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - /* FIXME: ensure caller copes with us just having freed the whole layer */ - return true; -} - -SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan) -{ - unreachable("Should never be called in the server"); -} - -struct ssh_rportfwd *ssh1_rportfwd_alloc( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx) -{ - unreachable("Should never be called in the server"); -} - -static size_t ssh1sesschan_write(SshChannel *sc, bool is_stderr, - const void *data, size_t len) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, - (is_stderr ? SSH1_SMSG_STDERR_DATA : SSH1_SMSG_STDOUT_DATA)); - put_string(pktout, data, len); - pq_push(s->ppl.out_pq, pktout); - - return 0; -} - -static void ssh1sesschan_write_eof(SshChannel *sc) -{ - /* SSH-1 can't represent server-side EOF */ - /* FIXME: some kind of check-termination system, whereby once this has been called _and_ we've had an exit status _and_ we've got no other channels open, we send the actual EXIT_STATUS message */ -} - -static void ssh1sesschan_initiate_close(SshChannel *sc, const char *err) -{ - /* SSH-1 relies on the client to close the connection in the end */ -} - -static void ssh1sesschan_send_exit_status(SshChannel *sc, int status) -{ - struct ssh1_connection_state *s = - container_of(sc, struct ssh1_connection_state, mainchan_sc); - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_EXIT_STATUS); - put_uint32(pktout, status); - pq_push(s->ppl.out_pq, pktout); - - s->sent_exit_status = true; -} - -static void ssh1sesschan_send_exit_signal( - SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg) -{ - /* SSH-1 has no separate representation for signals */ - ssh1sesschan_send_exit_status(sc, 128); -} - -SshChannel *ssh1_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh1_channel *c = snew(struct ssh1_channel); - PktOut *pktout; - - c->connlayer = s; - ssh1_channel_init(c); - c->halfopen = true; - c->chan = chan; - - ppl_logevent("Forwarding X11 connection to client"); - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_X11_OPEN); - put_uint32(pktout, c->localid); - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh1_channel *c = snew(struct ssh1_channel); - PktOut *pktout; - - c->connlayer = s; - ssh1_channel_init(c); - c->halfopen = true; - c->chan = chan; - - ppl_logevent("Forwarding agent connection to client"); - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_AGENT_OPEN); - put_uint32(pktout, c->localid); - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s) -{ - return false; -} diff --git a/ssh/connection1.c b/ssh/connection1.c deleted file mode 100644 index 7204450a1..000000000 --- a/ssh/connection1.c +++ /dev/null @@ -1,812 +0,0 @@ -/* - * Packet protocol layer for the SSH-1 'connection protocol', i.e. - * everything after authentication finishes. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "channel.h" -#include "sshcr.h" -#include "connection1.h" - -static int ssh1_rportfwd_cmp(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 void ssh1_connection_free(PacketProtocolLayer *); -static void ssh1_connection_process_queue(PacketProtocolLayer *); -static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg); -static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf); - -static const PacketProtocolLayerVtable ssh1_connection_vtable = { - .free = ssh1_connection_free, - .process_queue = ssh1_connection_process_queue, - .get_specials = ssh1_common_get_specials, - .special_cmd = ssh1_connection_special_cmd, - .reconfigure = ssh1_connection_reconfigure, - .queued_data_size = ssh_ppl_default_queued_data_size, - .final_output = ssh_ppl_default_final_output, - .name = NULL, /* no layer names in SSH-1 */ -}; - -static void ssh1_rportfwd_remove( - ConnectionLayer *cl, struct ssh_rportfwd *rpf); -static SshChannel *ssh1_lportfwd_open( - ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan); -static struct X11FakeAuth *ssh1_add_x11_display( - ConnectionLayer *cl, int authtype, struct X11Display *disp); -static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl); -static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height); -static void ssh1_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize); -static size_t ssh1_stdin_backlog(ConnectionLayer *cl); -static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled); -static bool ssh1_ldisc_option(ConnectionLayer *cl, int option); -static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value); -static void ssh1_enable_x_fwd(ConnectionLayer *cl); -static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted); -static bool ssh1_get_wants_user_input(ConnectionLayer *cl); -static void ssh1_got_user_input(ConnectionLayer *cl); - -static const ConnectionLayerVtable ssh1_connlayer_vtable = { - .rportfwd_alloc = ssh1_rportfwd_alloc, - .rportfwd_remove = ssh1_rportfwd_remove, - .lportfwd_open = ssh1_lportfwd_open, - .session_open = ssh1_session_open, - .serverside_x11_open = ssh1_serverside_x11_open, - .serverside_agent_open = ssh1_serverside_agent_open, - .add_x11_display = ssh1_add_x11_display, - .agent_forwarding_permitted = ssh1_agent_forwarding_permitted, - .terminal_size = ssh1_terminal_size, - .stdout_unthrottle = ssh1_stdout_unthrottle, - .stdin_backlog = ssh1_stdin_backlog, - .throttle_all_channels = ssh1_throttle_all_channels, - .ldisc_option = ssh1_ldisc_option, - .set_ldisc_option = ssh1_set_ldisc_option, - .enable_x_fwd = ssh1_enable_x_fwd, - .set_wants_user_input = ssh1_set_wants_user_input, - .get_wants_user_input = ssh1_get_wants_user_input, - .got_user_input = ssh1_got_user_input, - /* other methods are NULL */ -}; - -static size_t ssh1channel_write( - SshChannel *c, bool is_stderr, const void *buf, size_t len); -static void ssh1channel_write_eof(SshChannel *c); -static void ssh1channel_initiate_close(SshChannel *c, const char *err); -static void ssh1channel_unthrottle(SshChannel *c, size_t bufsize); -static Conf *ssh1channel_get_conf(SshChannel *c); -static void ssh1channel_window_override_removed(SshChannel *c) { /* ignore */ } - -static const SshChannelVtable ssh1channel_vtable = { - .write = ssh1channel_write, - .write_eof = ssh1channel_write_eof, - .initiate_close = ssh1channel_initiate_close, - .unthrottle = ssh1channel_unthrottle, - .get_conf = ssh1channel_get_conf, - .window_override_removed = ssh1channel_window_override_removed, - /* everything else is NULL */ -}; - -static void ssh1_channel_try_eof(struct ssh1_channel *c); -static void ssh1_channel_close_local(struct ssh1_channel *c, - const char *reason); -static void ssh1_channel_destroy(struct ssh1_channel *c); -static void ssh1_channel_check_close(struct ssh1_channel *c); - -static int ssh1_channelcmp(void *av, void *bv) -{ - const struct ssh1_channel *a = (const struct ssh1_channel *) av; - const struct ssh1_channel *b = (const struct ssh1_channel *) bv; - if (a->localid < b->localid) - return -1; - if (a->localid > b->localid) - return +1; - return 0; -} - -static int ssh1_channelfind(void *av, void *bv) -{ - const unsigned *a = (const unsigned *) av; - const struct ssh1_channel *b = (const struct ssh1_channel *) bv; - if (*a < b->localid) - return -1; - if (*a > b->localid) - return +1; - return 0; -} - -void ssh1_channel_free(struct ssh1_channel *c) -{ - if (c->chan) - chan_free(c->chan); - sfree(c); -} - -PacketProtocolLayer *ssh1_connection_new( - Ssh *ssh, Conf *conf, bufchain *user_input, ConnectionLayer **cl_out) -{ - struct ssh1_connection_state *s = snew(struct ssh1_connection_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh1_connection_vtable; - - s->conf = conf_copy(conf); - - s->channels = newtree234(ssh1_channelcmp); - - s->x11authtree = newtree234(x11_authcmp); - - s->user_input = user_input; - - /* Need to get the log context for s->cl now, because we won't be - * helpfully notified when a copy is written into s->ppl by our - * owner. */ - s->cl.vt = &ssh1_connlayer_vtable; - s->cl.logctx = ssh_get_logctx(ssh); - - s->portfwdmgr = portfwdmgr_new(&s->cl); - s->rportfwds = newtree234(ssh1_rportfwd_cmp); - - *cl_out = &s->cl; - return &s->ppl; -} - -static void ssh1_connection_free(PacketProtocolLayer *ppl) -{ - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - struct X11FakeAuth *auth; - struct ssh1_channel *c; - struct ssh_rportfwd *rpf; - - conf_free(s->conf); - - while ((c = delpos234(s->channels, 0)) != NULL) - ssh1_channel_free(c); - freetree234(s->channels); - if (s->mainchan_chan) - chan_free(s->mainchan_chan); - - if (s->x11disp) - x11_free_display(s->x11disp); - while ((auth = delpos234(s->x11authtree, 0)) != NULL) - x11_free_fake_auth(auth); - freetree234(s->x11authtree); - - while ((rpf = delpos234(s->rportfwds, 0)) != NULL) - free_rportfwd(rpf); - freetree234(s->rportfwds); - portfwdmgr_free(s->portfwdmgr); - - if (s->antispoof_prompt) - free_prompts(s->antispoof_prompt); - - delete_callbacks_for_context(s); - - sfree(s); -} - -void ssh1_connection_set_protoflags(PacketProtocolLayer *ppl, - int local, int remote) -{ - assert(ppl->vt == &ssh1_connection_vtable); - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - s->local_protoflags = local; - s->remote_protoflags = remote; -} - -static bool ssh1_connection_filter_queue(struct ssh1_connection_state *s) -{ - PktIn *pktin; - ptrlen data; - struct ssh1_channel *c; - unsigned localid; - bool expect_halfopen; - - while (1) { - if (ssh1_common_filter_queue(&s->ppl)) - return true; - if ((pktin = pq_peek(s->ppl.in_pq)) == NULL) - return false; - - switch (pktin->type) { - case SSH1_MSG_CHANNEL_DATA: - case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION: - case SSH1_MSG_CHANNEL_OPEN_FAILURE: - case SSH1_MSG_CHANNEL_CLOSE: - case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION: - /* - * Common preliminary code for all the messages from the - * server that cite one of our channel ids: look up that - * channel id, check it exists, and if it's for a sharing - * downstream, pass it on. - */ - localid = get_uint32(pktin); - c = find234(s->channels, &localid, ssh1_channelfind); - - expect_halfopen = ( - pktin->type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION || - pktin->type == SSH1_MSG_CHANNEL_OPEN_FAILURE); - - if (!c || c->halfopen != expect_halfopen) { - ssh_remote_error( - s->ppl.ssh, "Received %s for %s channel %u", - ssh1_pkt_type(pktin->type), - !c ? "nonexistent" : c->halfopen ? "half-open" : "open", - localid); - return true; - } - - switch (pktin->type) { - case SSH1_MSG_CHANNEL_OPEN_CONFIRMATION: - assert(c->halfopen); - c->remoteid = get_uint32(pktin); - c->halfopen = false; - c->throttling_conn = false; - - chan_open_confirmation(c->chan); - - /* - * Now that the channel is fully open, it's possible - * in principle to immediately close it. Check whether - * it wants us to! - * - * This can occur if a local socket error occurred - * between us sending out CHANNEL_OPEN and receiving - * OPEN_CONFIRMATION. If that happens, all we can do - * is immediately initiate close proceedings now that - * we know the server's id to put in the close - * message. We'll have handled that in this code by - * having already turned c->chan into a zombie, so its - * want_close method (which ssh1_channel_check_close - * will consult) will already be returning true. - */ - ssh1_channel_check_close(c); - - if (c->pending_eof) - ssh1_channel_try_eof(c); /* in case we had a pending EOF */ - break; - - case SSH1_MSG_CHANNEL_OPEN_FAILURE: - assert(c->halfopen); - - chan_open_failed(c->chan, NULL); - chan_free(c->chan); - - del234(s->channels, c); - ssh1_channel_free(c); - break; - - case SSH1_MSG_CHANNEL_DATA: - data = get_string(pktin); - if (!get_err(pktin)) { - int bufsize = chan_send( - c->chan, false, data.ptr, data.len); - - if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) { - c->throttling_conn = true; - ssh_throttle_conn(s->ppl.ssh, +1); - } - } - break; - - case SSH1_MSG_CHANNEL_CLOSE: - if (!(c->closes & CLOSES_RCVD_CLOSE)) { - c->closes |= CLOSES_RCVD_CLOSE; - chan_send_eof(c->chan); - ssh1_channel_check_close(c); - } - break; - - case SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION: - if (!(c->closes & CLOSES_RCVD_CLOSECONF)) { - if (!(c->closes & CLOSES_SENT_CLOSE)) { - ssh_remote_error( - s->ppl.ssh, - "Received CHANNEL_CLOSE_CONFIRMATION for channel" - " %u for which we never sent CHANNEL_CLOSE\n", - c->localid); - return true; - } - - c->closes |= CLOSES_RCVD_CLOSECONF; - ssh1_channel_check_close(c); - } - break; - } - - pq_pop(s->ppl.in_pq); - break; - - default: - if (ssh1_handle_direction_specific_packet(s, pktin)) { - pq_pop(s->ppl.in_pq); - if (ssh1_check_termination(s)) - return true; - } else { - return false; - } - } - } -} - -static PktIn *ssh1_connection_pop(struct ssh1_connection_state *s) -{ - ssh1_connection_filter_queue(s); - return pq_pop(s->ppl.in_pq); -} - -static void ssh1_connection_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - PktIn *pktin; - - if (ssh1_connection_filter_queue(s)) /* no matter why we were called */ - return; - - crBegin(s->crState); - - /* - * Signal the seat that authentication is done, so that it can - * deploy spoofing defences. If it doesn't have any, deploy our - * own fallback one. - * - * We do this here rather than at the end of userauth, because we - * might not have gone through userauth at all (if we're a - * connection-sharing downstream). - */ - if (ssh1_connection_need_antispoof_prompt(s)) { - s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl); - s->antispoof_prompt->to_server = true; - s->antispoof_prompt->from_server = false; - s->antispoof_prompt->name = dupstr("Authentication successful"); - add_prompt( - s->antispoof_prompt, - dupstr("Access granted. Press Return to begin session. "), false); - s->antispoof_ret = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->antispoof_prompt); - while (s->antispoof_ret.kind == SPRK_INCOMPLETE) { - crReturnV; - s->antispoof_ret = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->antispoof_prompt); - } - free_prompts(s->antispoof_prompt); - s->antispoof_prompt = NULL; - } - - portfwdmgr_config(s->portfwdmgr, s->conf); - s->portfwdmgr_configured = true; - - while (!s->finished_setup) { - ssh1_connection_direction_specific_setup(s); - crReturnV; - } - - while (1) { - - /* - * By this point, most incoming packets are already being - * handled by filter_queue, and we need only pay attention to - * the unusual ones. - */ - - if ((pktin = ssh1_connection_pop(s)) != NULL) { - ssh_proto_error(s->ppl.ssh, "Unexpected packet received, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - crReturnV; - } - - crFinishV; -} - -static void ssh1_channel_check_close(struct ssh1_channel *c) -{ - struct ssh1_connection_state *s = c->connlayer; - PktOut *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_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes) || - chan_want_close(c->chan, (c->closes & CLOSES_SENT_CLOSE), - (c->closes & CLOSES_RCVD_CLOSE))) && - !(c->closes & CLOSES_SENT_CLOSECONF)) { - /* - * We have both sent and received CLOSE (or the channel type - * doesn't need us to), which means the channel is in final - * wind-up. Send CLOSE and/or CLOSE_CONFIRMATION, whichever we - * haven't sent yet. - */ - if (!(c->closes & CLOSES_SENT_CLOSE)) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE); - put_uint32(pktout, c->remoteid); - pq_push(s->ppl.out_pq, pktout); - c->closes |= CLOSES_SENT_CLOSE; - } - if (c->closes & CLOSES_RCVD_CLOSE) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION); - put_uint32(pktout, c->remoteid); - pq_push(s->ppl.out_pq, pktout); - c->closes |= CLOSES_SENT_CLOSECONF; - } - } - - if (!((CLOSES_SENT_CLOSECONF | CLOSES_RCVD_CLOSECONF) & ~c->closes)) { - /* - * We have both sent and received CLOSE_CONFIRMATION, which - * means we're completely done with the channel. - */ - ssh1_channel_destroy(c); - } -} - -static void ssh1_channel_try_eof(struct ssh1_channel *c) -{ - struct ssh1_connection_state *s = c->connlayer; - PktOut *pktout; - assert(c->pending_eof); /* precondition for calling us */ - if (c->halfopen) - return; /* can't close: not even opened yet */ - - c->pending_eof = false; /* we're about to send it */ - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_CLOSE); - put_uint32(pktout, c->remoteid); - pq_push(s->ppl.out_pq, pktout); - c->closes |= CLOSES_SENT_CLOSE; - - ssh1_channel_check_close(c); -} - -/* - * Close any local socket and free any local resources associated with - * a channel. This converts the channel into a zombie. - */ -static void ssh1_channel_close_local(struct ssh1_channel *c, - const char *reason) -{ - struct ssh1_connection_state *s = c->connlayer; - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - char *msg = chan_log_close_msg(c->chan); - - if (msg != NULL) { - ppl_logevent("%s%s%s", msg, reason ? " " : "", reason ? reason : ""); - sfree(msg); - } - - chan_free(c->chan); - c->chan = zombiechan_new(); -} - -static void ssh1_check_termination_callback(void *vctx) -{ - struct ssh1_connection_state *s = (struct ssh1_connection_state *)vctx; - ssh1_check_termination(s); -} - -static void ssh1_channel_destroy(struct ssh1_channel *c) -{ - struct ssh1_connection_state *s = c->connlayer; - - ssh1_channel_close_local(c, NULL); - del234(s->channels, c); - ssh1_channel_free(c); - - /* - * If that was the last channel left open, we might need to - * terminate. But we'll be a bit cautious, by doing that in a - * toplevel callback, just in case anything on the current call - * stack objects to this entire PPL being freed. - */ - queue_toplevel_callback(ssh1_check_termination_callback, s); -} - -bool ssh1_check_termination(struct ssh1_connection_state *s) -{ - /* - * Decide whether we should terminate the SSH connection now. - * Called after a channel goes away, or when the main session - * returns SSH1_SMSG_EXIT_STATUS; we terminate when none of either - * is left. - */ - if (s->session_terminated && count234(s->channels) == 0) { - PktOut *pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_EXIT_CONFIRMATION); - pq_push(s->ppl.out_pq, pktout); - - ssh_user_close(s->ppl.ssh, "Session finished"); - return true; - } - - return false; -} - -/* - * Set up most of a new ssh1_channel. Leaves chan untouched (since it - * will sometimes have been filled in before calling this). - */ -void ssh1_channel_init(struct ssh1_channel *c) -{ - struct ssh1_connection_state *s = c->connlayer; - c->closes = 0; - c->pending_eof = false; - c->throttling_conn = false; - c->sc.vt = &ssh1channel_vtable; - c->sc.cl = &s->cl; - c->localid = alloc_channel_id(s->channels, struct ssh1_channel); - add234(s->channels, c); -} - -static Conf *ssh1channel_get_conf(SshChannel *sc) -{ - struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); - struct ssh1_connection_state *s = c->connlayer; - return s->conf; -} - -static void ssh1channel_write_eof(SshChannel *sc) -{ - struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); - - if (c->closes & CLOSES_SENT_CLOSE) - return; - - c->pending_eof = true; - ssh1_channel_try_eof(c); -} - -static void ssh1channel_initiate_close(SshChannel *sc, const char *err) -{ - struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); - char *reason; - - reason = err ? dupprintf("due to local error: %s", err) : NULL; - ssh1_channel_close_local(c, reason); - sfree(reason); - c->pending_eof = false; /* this will confuse a zombie channel */ - - ssh1_channel_check_close(c); -} - -static void ssh1channel_unthrottle(SshChannel *sc, size_t bufsize) -{ - struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); - struct ssh1_connection_state *s = c->connlayer; - - if (c->throttling_conn && bufsize <= SSH1_BUFFER_LIMIT) { - c->throttling_conn = false; - ssh_throttle_conn(s->ppl.ssh, -1); - } -} - -static size_t ssh1channel_write( - SshChannel *sc, bool is_stderr, const void *buf, size_t len) -{ - struct ssh1_channel *c = container_of(sc, struct ssh1_channel, sc); - struct ssh1_connection_state *s = c->connlayer; - - assert(!(c->closes & CLOSES_SENT_CLOSE)); - - PktOut *pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_CHANNEL_DATA); - put_uint32(pktout, c->remoteid); - put_string(pktout, buf, len); - pq_push(s->ppl.out_pq, pktout); - - /* - * In SSH-1 we can return 0 here - implying that channels 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; -} - -static struct X11FakeAuth *ssh1_add_x11_display( - ConnectionLayer *cl, int authtype, struct X11Display *disp) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - struct X11FakeAuth *auth = x11_invent_fake_auth(s->x11authtree, authtype); - auth->disp = disp; - return auth; -} - -static SshChannel *ssh1_lportfwd_open( - ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh1_channel *c = snew(struct ssh1_channel); - PktOut *pktout; - - c->connlayer = s; - ssh1_channel_init(c); - c->halfopen = true; - c->chan = chan; - - ppl_logevent("Opening connection to %s:%d for %s", - hostname, port, description); - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_PORT_OPEN); - put_uint32(pktout, c->localid); - put_stringz(pktout, hostname); - put_uint32(pktout, port); - /* originator string would go here, but we didn't specify - * SSH_PROTOFLAG_HOST_IN_FWD_OPEN */ - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -static void ssh1_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) -{ - /* - * We cannot cancel listening ports on the server side in SSH-1! - * There's no message to support it. - */ -} - -static bool ssh1_agent_forwarding_permitted(ConnectionLayer *cl) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - return conf_get_bool(s->conf, CONF_agentfwd) && agent_exists(); -} - -static void ssh1_connection_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) -{ - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - PktOut *pktout; - - if (code == SS_PING || code == SS_NOP) { - if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); - put_stringz(pktout, ""); - pq_push(s->ppl.out_pq, pktout); - } - } else if (s->mainchan) { - mainchan_special_cmd(s->mainchan, code, arg); - } -} - -static void ssh1_terminal_size(ConnectionLayer *cl, int width, int height) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - s->term_width = width; - s->term_height = height; - if (s->mainchan) - mainchan_terminal_size(s->mainchan, width, height); -} - -static void ssh1_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - if (s->stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) { - s->stdout_throttling = false; - ssh_throttle_conn(s->ppl.ssh, -1); - } -} - -static size_t ssh1_stdin_backlog(ConnectionLayer *cl) -{ - return 0; -} - -static void ssh1_throttle_all_channels(ConnectionLayer *cl, bool throttled) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - struct ssh1_channel *c; - int i; - - for (i = 0; NULL != (c = index234(s->channels, i)); i++) - chan_set_input_wanted(c->chan, !throttled); -} - -static bool ssh1_ldisc_option(ConnectionLayer *cl, int option) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - return s->ldisc_opts[option]; -} - -static void ssh1_set_ldisc_option(ConnectionLayer *cl, int option, bool value) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - s->ldisc_opts[option] = value; -} - -static void ssh1_enable_x_fwd(ConnectionLayer *cl) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - s->X11_fwd_enabled = true; -} - -static void ssh1_set_wants_user_input(ConnectionLayer *cl, bool wanted) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - s->want_user_input = wanted; - s->finished_setup = true; - if (wanted) - ssh_check_sendok(s->ppl.ssh); -} - -static bool ssh1_get_wants_user_input(ConnectionLayer *cl) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - return s->want_user_input; -} - -static void ssh1_got_user_input(ConnectionLayer *cl) -{ - struct ssh1_connection_state *s = - container_of(cl, struct ssh1_connection_state, cl); - - while (s->mainchan && bufchain_size(s->user_input) > 0) { - /* - * Add user input to the main channel's buffer. - */ - ptrlen data = bufchain_prefix(s->user_input); - if (data.len > 512) - data.len = 512; - sshfwd_write(&s->mainchan_sc, data.ptr, data.len); - bufchain_consume(s->user_input, data.len); - } -} - -static void ssh1_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ - struct ssh1_connection_state *s = - container_of(ppl, struct ssh1_connection_state, ppl); - - conf_free(s->conf); - s->conf = conf_copy(conf); - - if (s->portfwdmgr_configured) - portfwdmgr_config(s->portfwdmgr, s->conf); -} diff --git a/ssh/connection1.h b/ssh/connection1.h deleted file mode 100644 index ff370131d..000000000 --- a/ssh/connection1.h +++ /dev/null @@ -1,124 +0,0 @@ -struct ssh1_channel; - -struct outstanding_succfail; - -struct ssh1_connection_state { - int crState; - - Conf *conf; - int local_protoflags, remote_protoflags; - - tree234 *channels; /* indexed by local id */ - - /* In SSH-1, the main session doesn't take the form of a 'channel' - * according to the wire protocol. But we want to use the same API - * for it, so we define an SshChannel here - but one that uses a - * separate vtable from the usual one, so it doesn't map to a - * struct ssh1_channel as all the others do. */ - SshChannel mainchan_sc; - Channel *mainchan_chan; /* the other end of mainchan_sc */ - mainchan *mainchan; /* and its subtype */ - - bool got_pty; - bool ldisc_opts[LD_N_OPTIONS]; - bool stdout_throttling; - bool want_user_input; - bool session_terminated; - int term_width, term_height, term_width_orig, term_height_orig; - bufchain *user_input; - - bool X11_fwd_enabled; - struct X11Display *x11disp; - struct X11FakeAuth *x11auth; - tree234 *x11authtree; - - tree234 *rportfwds; - PortFwdManager *portfwdmgr; - bool portfwdmgr_configured; - - bool finished_setup; - - /* - * These store the list of requests that we're waiting for - * SSH_SMSG_{SUCCESS,FAILURE} replies to. (Those messages don't - * come with any indication of what they're in response to, so we - * have to keep track of the queue ourselves.) - */ - struct outstanding_succfail *succfail_head, *succfail_tail; - - bool compressing; /* used in server mode only */ - bool sent_exit_status; /* also for server mode */ - - prompts_t *antispoof_prompt; - SeatPromptResult antispoof_ret; - - const SshServerConfig *ssc; - - ConnectionLayer cl; - PacketProtocolLayer ppl; -}; - -struct ssh1_channel { - struct ssh1_connection_state *connlayer; - - unsigned remoteid, localid; - int type; - /* True if we opened this channel but server hasn't confirmed. */ - bool halfopen; - - /* Bitmap of whether we've sent/received CHANNEL_CLOSE and - * CHANNEL_CLOSE_CONFIRMATION. */ -#define CLOSES_SENT_CLOSE 1 -#define CLOSES_SENT_CLOSECONF 2 -#define CLOSES_RCVD_CLOSE 4 -#define CLOSES_RCVD_CLOSECONF 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. - */ - bool pending_eof; - - /* - * True if this channel is causing the underlying connection to be - * throttled. - */ - bool throttling_conn; - - /* - * True if we currently have backed-up data on the direction of - * this channel pointing out of the SSH connection, and therefore - * would prefer the 'Channel' implementation not to read further - * local input if possible. - */ - bool throttled_by_backlog; - - Channel *chan; /* handle the client side of this channel, if not */ - SshChannel sc; /* entry point for chan to talk back to */ -}; - -SshChannel *ssh1_session_open(ConnectionLayer *cl, Channel *chan); -void ssh1_channel_init(struct ssh1_channel *c); -void ssh1_channel_free(struct ssh1_channel *c); -struct ssh_rportfwd *ssh1_rportfwd_alloc( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx); -SshChannel *ssh1_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi); -SshChannel *ssh1_serverside_agent_open(ConnectionLayer *cl, Channel *chan); - -void ssh1_connection_direction_specific_setup( - struct ssh1_connection_state *s); -bool ssh1_handle_direction_specific_packet( - struct ssh1_connection_state *s, PktIn *pktin); - -bool ssh1_check_termination(struct ssh1_connection_state *s); - -bool ssh1_connection_need_antispoof_prompt(struct ssh1_connection_state *s); diff --git a/ssh/connection2-client.c b/ssh/connection2-client.c deleted file mode 100644 index f17d1e21c..000000000 --- a/ssh/connection2-client.c +++ /dev/null @@ -1,511 +0,0 @@ -/* - * Client-specific parts of the SSH-2 connection layer. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "channel.h" -#include "sshcr.h" -#include "connection2.h" - -static ChanopenResult chan_open_x11( - struct ssh2_connection_state *s, SshChannel *sc, - ptrlen peeraddr, int peerport) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - char *peeraddr_str; - Channel *ch; - - ppl_logevent("Received X11 connect request from %.*s:%d", - PTRLEN_PRINTF(peeraddr), peerport); - - if (!s->X11_fwd_enabled && !s->connshare) { - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, - ("X11 forwarding is not enabled")); - } - - peeraddr_str = peeraddr.ptr ? mkstr(peeraddr) : NULL; - ch = x11_new_channel( - s->x11authtree, sc, peeraddr_str, peerport, s->connshare != NULL); - sfree(peeraddr_str); - ppl_logevent("Opened X11 forward channel"); - CHANOPEN_RETURN_SUCCESS(ch); -} - -static ChanopenResult chan_open_forwarded_tcpip( - struct ssh2_connection_state *s, SshChannel *sc, - ptrlen fwdaddr, int fwdport, ptrlen peeraddr, int peerport) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh_rportfwd pf, *realpf; - Channel *ch; - char *err; - - ppl_logevent("Received remote port %.*s:%d open request from %.*s:%d", - PTRLEN_PRINTF(fwdaddr), fwdport, - PTRLEN_PRINTF(peeraddr), peerport); - - pf.shost = mkstr(fwdaddr); - pf.sport = fwdport; - realpf = find234(s->rportfwds, &pf, NULL); - sfree(pf.shost); - - if (realpf == NULL) { - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, - ("Remote port is not recognised")); - } - - if (realpf->share_ctx) { - /* - * This port forwarding is on behalf of a connection-sharing - * downstream. - */ - CHANOPEN_RETURN_DOWNSTREAM(realpf->share_ctx); - } - - err = portfwdmgr_connect( - s->portfwdmgr, &ch, realpf->dhost, realpf->dport, - sc, realpf->addressfamily); - ppl_logevent("Attempting to forward remote port to %s:%d", - realpf->dhost, realpf->dport); - if (err != NULL) { - ppl_logevent("Port open failed: %s", err); - sfree(err); - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_CONNECT_FAILED, - ("Port open failed")); - } - - ppl_logevent("Forwarded port opened successfully"); - CHANOPEN_RETURN_SUCCESS(ch); -} - -static ChanopenResult chan_open_auth_agent( - struct ssh2_connection_state *s, SshChannel *sc) -{ - if (!ssh_agent_forwarding_permitted(&s->cl)) { - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, - ("Agent forwarding is not enabled")); - } - - /* - * If possible, make a stream-oriented connection to the agent and - * set up an ordinary port-forwarding type channel over it. - */ - Plug *plug; - Channel *ch = portfwd_raw_new(&s->cl, &plug, true); - Socket *skt = agent_connect(plug); - - if (!sk_socket_error(skt)) { - portfwd_raw_setup(ch, skt, sc); - CHANOPEN_RETURN_SUCCESS(ch); - } else { - portfwd_raw_free(ch); - /* - * Otherwise, fall back to the old-fashioned system of parsing the - * forwarded data stream ourselves for message boundaries, and - * passing each individual message to the one-off agent_query(). - */ - CHANOPEN_RETURN_SUCCESS(agentf_new(sc)); - } -} - -ChanopenResult ssh2_connection_parse_channel_open( - struct ssh2_connection_state *s, ptrlen type, - PktIn *pktin, SshChannel *sc) -{ - if (ptrlen_eq_string(type, "x11")) { - ptrlen peeraddr = get_string(pktin); - int peerport = get_uint32(pktin); - - return chan_open_x11(s, sc, peeraddr, peerport); - } else if (ptrlen_eq_string(type, "forwarded-tcpip")) { - ptrlen fwdaddr = get_string(pktin); - int fwdport = toint(get_uint32(pktin)); - ptrlen peeraddr = get_string(pktin); - int peerport = toint(get_uint32(pktin)); - - return chan_open_forwarded_tcpip( - s, sc, fwdaddr, fwdport, peeraddr, peerport); - } else if (ptrlen_eq_string(type, "auth-agent@openssh.com")) { - return chan_open_auth_agent(s, sc); - } else { - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_UNKNOWN_CHANNEL_TYPE, - ("Unsupported channel type requested")); - } -} - -bool ssh2_connection_parse_global_request( - struct ssh2_connection_state *s, ptrlen type, PktIn *pktin) -{ - /* - * We don't know of any global requests that an SSH client needs - * to honour. - */ - return false; -} - -PktOut *ssh2_portfwd_chanopen( - struct ssh2_connection_state *s, struct ssh2_channel *c, - const char *hostname, int port, - const char *description, const SocketPeerInfo *peerinfo) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - PktOut *pktout; - - /* - * In client mode, this function is called by portfwdmgr in - * response to PortListeners that were set up in - * portfwdmgr_config, which means that the hostname and port - * parameters will indicate the host we want to tell the server to - * connect _to_. - */ - - ppl_logevent("Opening connection to %s:%d for %s", - hostname, port, description); - - pktout = ssh2_chanopen_init(c, "direct-tcpip"); - { - char *trimmed_host = host_strduptrim(hostname); - put_stringz(pktout, trimmed_host); - sfree(trimmed_host); - } - put_uint32(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. - */ - put_stringz(pktout, "0.0.0.0"); - put_uint32(pktout, 0); - - return pktout; -} - -static int ssh2_rportfwd_cmp(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; -} - -static void ssh2_rportfwd_globreq_response(struct ssh2_connection_state *s, - PktIn *pktin, void *ctx) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh_rportfwd *rpf = (struct ssh_rportfwd *)ctx; - - if (pktin->type == SSH2_MSG_REQUEST_SUCCESS) { - ppl_logevent("Remote port forwarding from %s enabled", - rpf->log_description); - } else { - ppl_logevent("Remote port forwarding from %s refused", - rpf->log_description); - - struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); - assert(realpf == rpf); - portfwdmgr_close(s->portfwdmgr, rpf->pfr); - free_rportfwd(rpf); - } -} - -struct ssh_rportfwd *ssh2_rportfwd_alloc( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct ssh_rportfwd *rpf = snew(struct ssh_rportfwd); - - if (!s->rportfwds) - s->rportfwds = newtree234(ssh2_rportfwd_cmp); - - rpf->shost = dupstr(shost); - rpf->sport = sport; - rpf->dhost = dupstr(dhost); - rpf->dport = dport; - rpf->addressfamily = addressfamily; - rpf->log_description = dupstr(log_description); - rpf->pfr = pfr; - rpf->share_ctx = share_ctx; - - if (add234(s->rportfwds, rpf) != rpf) { - free_rportfwd(rpf); - return NULL; - } - - if (!rpf->share_ctx) { - PktOut *pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST); - put_stringz(pktout, "tcpip-forward"); - put_bool(pktout, true); /* want reply */ - put_stringz(pktout, rpf->shost); - put_uint32(pktout, rpf->sport); - pq_push(s->ppl.out_pq, pktout); - - ssh2_queue_global_request_handler( - s, ssh2_rportfwd_globreq_response, rpf); - } - - return rpf; -} - -void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - if (rpf->share_ctx) { - /* - * We don't manufacture a cancel-tcpip-forward message for - * remote port forwardings being removed on behalf of a - * downstream; we just pass through the one the downstream - * sent to us. - */ - } else { - PktOut *pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_GLOBAL_REQUEST); - put_stringz(pktout, "cancel-tcpip-forward"); - put_bool(pktout, false); /* _don't_ want reply */ - put_stringz(pktout, rpf->shost); - put_uint32(pktout, rpf->sport); - pq_push(s->ppl.out_pq, pktout); - } - - assert(s->rportfwds); - struct ssh_rportfwd *realpf = del234(s->rportfwds, rpf); - assert(realpf == rpf); - free_rportfwd(rpf); -} - -SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh2_channel *c = snew(struct ssh2_channel); - PktOut *pktout; - - c->connlayer = s; - ssh2_channel_init(c); - c->halfopen = true; - c->chan = chan; - - ppl_logevent("Opening main session channel"); - - pktout = ssh2_chanopen_init(c, "session"); - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -SshChannel *ssh2_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) -{ - unreachable("Should never be called in the client"); -} - -SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan) -{ - unreachable("Should never be called in the client"); -} - -static void ssh2_channel_response( - struct ssh2_channel *c, PktIn *pkt, void *ctx) -{ - /* If pkt==NULL (because this handler has been called in response - * to CHANNEL_CLOSE arriving while the request was still - * outstanding), we treat that the same as CHANNEL_FAILURE. */ - chan_request_response(c->chan, - pkt && pkt->type == SSH2_MSG_CHANNEL_SUCCESS); -} - -void ssh2channel_start_shell(SshChannel *sc, bool want_reply) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "shell", want_reply ? ssh2_channel_response : NULL, NULL); - pq_push(s->ppl.out_pq, pktout); -} - -void ssh2channel_start_command( - SshChannel *sc, bool want_reply, const char *command) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "exec", want_reply ? ssh2_channel_response : NULL, NULL); - put_stringz(pktout, command); - pq_push(s->ppl.out_pq, pktout); -} - -bool ssh2channel_start_subsystem( - SshChannel *sc, bool want_reply, const char *subsystem) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "subsystem", want_reply ? ssh2_channel_response : NULL, NULL); - put_stringz(pktout, subsystem); - pq_push(s->ppl.out_pq, pktout); - - return true; -} - -void ssh2channel_send_exit_status(SshChannel *sc, int status) -{ - unreachable("Should never be called in the client"); -} - -void ssh2channel_send_exit_signal( - SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg) -{ - unreachable("Should never be called in the client"); -} - -void ssh2channel_send_exit_signal_numeric( - SshChannel *sc, int signum, bool core_dumped, ptrlen msg) -{ - unreachable("Should never be called in the client"); -} - -void ssh2channel_request_x11_forwarding( - SshChannel *sc, bool want_reply, const char *authproto, - const char *authdata, int screen_number, bool oneshot) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "x11-req", want_reply ? ssh2_channel_response : NULL, NULL); - put_bool(pktout, oneshot); - put_stringz(pktout, authproto); - put_stringz(pktout, authdata); - put_uint32(pktout, screen_number); - pq_push(s->ppl.out_pq, pktout); -} - -void ssh2channel_request_agent_forwarding(SshChannel *sc, bool want_reply) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "auth-agent-req@openssh.com", - want_reply ? ssh2_channel_response : NULL, NULL); - pq_push(s->ppl.out_pq, pktout); -} - -void ssh2channel_request_pty( - SshChannel *sc, bool want_reply, Conf *conf, int w, int h) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - strbuf *modebuf; - - PktOut *pktout = ssh2_chanreq_init( - c, "pty-req", want_reply ? ssh2_channel_response : NULL, NULL); - put_stringz(pktout, conf_get_str(conf, CONF_termtype)); - put_uint32(pktout, w); - put_uint32(pktout, h); - put_uint32(pktout, 0); /* pixel width */ - put_uint32(pktout, 0); /* pixel height */ - modebuf = strbuf_new(); - write_ttymodes_to_packet( - BinarySink_UPCAST(modebuf), 2, - get_ttymodes_from_conf(s->ppl.seat, conf)); - put_stringsb(pktout, modebuf); - pq_push(s->ppl.out_pq, pktout); -} - -bool ssh2channel_send_env_var( - SshChannel *sc, bool want_reply, const char *var, const char *value) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "env", want_reply ? ssh2_channel_response : NULL, NULL); - put_stringz(pktout, var); - put_stringz(pktout, value); - pq_push(s->ppl.out_pq, pktout); - - return true; -} - -bool ssh2channel_send_serial_break(SshChannel *sc, bool want_reply, int length) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "break", want_reply ? ssh2_channel_response : NULL, NULL); - put_uint32(pktout, length); - pq_push(s->ppl.out_pq, pktout); - - return true; -} - -bool ssh2channel_send_signal( - SshChannel *sc, bool want_reply, const char *signame) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "signal", want_reply ? ssh2_channel_response : NULL, NULL); - put_stringz(pktout, signame); - pq_push(s->ppl.out_pq, pktout); - - return true; -} - -void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init(c, "window-change", NULL, NULL); - put_uint32(pktout, w); - put_uint32(pktout, h); - put_uint32(pktout, 0); /* pixel width */ - put_uint32(pktout, 0); /* pixel height */ - pq_push(s->ppl.out_pq, pktout); -} - -bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s) -{ - seat_set_trust_status(s->ppl.seat, false); - if (!seat_has_mixed_input_stream(s->ppl.seat)) - return false; - if (seat_can_set_trust_status(s->ppl.seat)) - return false; - if (ssh_is_bare(s->ppl.ssh)) - return false; - return true; -} diff --git a/ssh/connection2-server.c b/ssh/connection2-server.c deleted file mode 100644 index c871b4b3d..000000000 --- a/ssh/connection2-server.c +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Server-specific parts of the SSH-2 connection layer. - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "channel.h" -#include "sshcr.h" -#include "connection2.h" -#include "server.h" - -void ssh2connection_server_configure( - PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt, - const SshServerConfig *ssc) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - s->sftpserver_vt = sftpserver_vt; - s->ssc = ssc; -} - -static ChanopenResult chan_open_session( - struct ssh2_connection_state *s, SshChannel *sc) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - - ppl_logevent("Opened session channel"); - CHANOPEN_RETURN_SUCCESS(sesschan_new(sc, s->ppl.logctx, - s->sftpserver_vt, s->ssc)); -} - -static ChanopenResult chan_open_direct_tcpip( - struct ssh2_connection_state *s, SshChannel *sc, - ptrlen dstaddr, int dstport, ptrlen peeraddr, int peerport) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - Channel *ch; - char *dstaddr_str, *err; - - dstaddr_str = mkstr(dstaddr); - - ppl_logevent("Received request to connect to port %s:%d (from %.*s:%d)", - dstaddr_str, dstport, PTRLEN_PRINTF(peeraddr), peerport); - err = portfwdmgr_connect( - s->portfwdmgr, &ch, dstaddr_str, dstport, sc, ADDRTYPE_UNSPEC); - - sfree(dstaddr_str); - - if (err != NULL) { - ppl_logevent("Port open failed: %s", err); - sfree(err); - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_CONNECT_FAILED, ("Connection failed")); - } - - ppl_logevent("Port opened successfully"); - CHANOPEN_RETURN_SUCCESS(ch); -} - -ChanopenResult ssh2_connection_parse_channel_open( - struct ssh2_connection_state *s, ptrlen type, - PktIn *pktin, SshChannel *sc) -{ - if (ptrlen_eq_string(type, "session")) { - return chan_open_session(s, sc); - } else if (ptrlen_eq_string(type, "direct-tcpip")) { - ptrlen dstaddr = get_string(pktin); - int dstport = toint(get_uint32(pktin)); - ptrlen peeraddr = get_string(pktin); - int peerport = toint(get_uint32(pktin)); - return chan_open_direct_tcpip( - s, sc, dstaddr, dstport, peeraddr, peerport); - } else { - CHANOPEN_RETURN_FAILURE( - SSH2_OPEN_UNKNOWN_CHANNEL_TYPE, - ("Unsupported channel type requested")); - } -} - -bool ssh2_connection_parse_global_request( - struct ssh2_connection_state *s, ptrlen type, PktIn *pktin) -{ - if (ptrlen_eq_string(type, "tcpip-forward")) { - char *host = mkstr(get_string(pktin)); - unsigned port = get_uint32(pktin); - /* In SSH-2, the host/port we listen on are the same host/port - * we want reported back to us when a connection comes in, - * because that's what we tell the client */ - bool toret = portfwdmgr_listen( - s->portfwdmgr, host, port, host, port, s->conf); - sfree(host); - return toret; - } else if (ptrlen_eq_string(type, "cancel-tcpip-forward")) { - char *host = mkstr(get_string(pktin)); - unsigned port = get_uint32(pktin); - bool toret = portfwdmgr_unlisten(s->portfwdmgr, host, port); - sfree(host); - return toret; - } else { - /* Unrecognised request. */ - return false; - } -} - -PktOut *ssh2_portfwd_chanopen( - struct ssh2_connection_state *s, struct ssh2_channel *c, - const char *hostname, int port, - const char *description, const SocketPeerInfo *pi) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - PktOut *pktout; - - /* - * In server mode, this function is called by portfwdmgr in - * response to PortListeners that were set up by calling - * portfwdmgr_listen, which means that the hostname and port - * parameters will identify the listening socket on which a - * connection just came in. - */ - - if (pi && pi->log_text) - ppl_logevent("Forwarding connection to listening port %s:%d from %s", - hostname, port, pi->log_text); - else - ppl_logevent("Forwarding connection to listening port %s:%d", - hostname, port); - - pktout = ssh2_chanopen_init(c, "forwarded-tcpip"); - put_stringz(pktout, hostname); - put_uint32(pktout, port); - put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0")); - put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0)); - - return pktout; -} - -struct ssh_rportfwd *ssh2_rportfwd_alloc( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx) -{ - unreachable("Should never be called in the server"); -} - -void ssh2_rportfwd_remove(ConnectionLayer *cl, struct ssh_rportfwd *rpf) -{ - unreachable("Should never be called in the server"); -} - -SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan) -{ - unreachable("Should never be called in the server"); -} - -SshChannel *ssh2_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh2_channel *c = snew(struct ssh2_channel); - PktOut *pktout; - - c->connlayer = s; - ssh2_channel_init(c); - c->halfopen = true; - c->chan = chan; - - ppl_logevent("Forwarding X11 channel to client"); - - pktout = ssh2_chanopen_init(c, "x11"); - put_stringz(pktout, (pi && pi->addr_text ? pi->addr_text : "0.0.0.0")); - put_uint32(pktout, (pi && pi->port >= 0 ? pi->port : 0)); - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - struct ssh2_channel *c = snew(struct ssh2_channel); - PktOut *pktout; - - c->connlayer = s; - ssh2_channel_init(c); - c->halfopen = true; - c->chan = chan; - - ppl_logevent("Forwarding SSH agent to client"); - - pktout = ssh2_chanopen_init(c, "auth-agent@openssh.com"); - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -void ssh2channel_start_shell(SshChannel *sc, bool want_reply) -{ - unreachable("Should never be called in the server"); -} - -void ssh2channel_start_command( - SshChannel *sc, bool want_reply, const char *command) -{ - unreachable("Should never be called in the server"); -} - -bool ssh2channel_start_subsystem( - SshChannel *sc, bool want_reply, const char *subsystem) -{ - unreachable("Should never be called in the server"); -} - -void ssh2channel_send_exit_status(SshChannel *sc, int status) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init(c, "exit-status", NULL, NULL); - put_uint32(pktout, status); - - pq_push(s->ppl.out_pq, pktout); -} - -void ssh2channel_send_exit_signal( - SshChannel *sc, ptrlen signame, bool core_dumped, ptrlen msg) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL); - put_stringpl(pktout, signame); - put_bool(pktout, core_dumped); - put_stringpl(pktout, msg); - put_stringz(pktout, ""); /* language tag */ - - pq_push(s->ppl.out_pq, pktout); -} - -void ssh2channel_send_exit_signal_numeric( - SshChannel *sc, int signum, bool core_dumped, ptrlen msg) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init(c, "exit-signal", NULL, NULL); - put_uint32(pktout, signum); - put_bool(pktout, core_dumped); - put_stringpl(pktout, msg); - put_stringz(pktout, ""); /* language tag */ - - pq_push(s->ppl.out_pq, pktout); -} - -void ssh2channel_request_x11_forwarding( - SshChannel *sc, bool want_reply, const char *authproto, - const char *authdata, int screen_number, bool oneshot) -{ - unreachable("Should never be called in the server"); -} - -void ssh2channel_request_agent_forwarding(SshChannel *sc, bool want_reply) -{ - unreachable("Should never be called in the server"); -} - -void ssh2channel_request_pty( - SshChannel *sc, bool want_reply, Conf *conf, int w, int h) -{ - unreachable("Should never be called in the server"); -} - -bool ssh2channel_send_env_var( - SshChannel *sc, bool want_reply, const char *var, const char *value) -{ - unreachable("Should never be called in the server"); -} - -bool ssh2channel_send_serial_break(SshChannel *sc, bool want_reply, int length) -{ - unreachable("Should never be called in the server"); -} - -bool ssh2channel_send_signal( - SshChannel *sc, bool want_reply, const char *signame) -{ - unreachable("Should never be called in the server"); -} - -void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h) -{ - unreachable("Should never be called in the server"); -} - -bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s) -{ - return false; -} diff --git a/ssh/connection2.c b/ssh/connection2.c deleted file mode 100644 index b08dab5f7..000000000 --- a/ssh/connection2.c +++ /dev/null @@ -1,1747 +0,0 @@ -/* - * Packet protocol layer for the SSH-2 connection protocol (RFC 4254). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "channel.h" -#include "sshcr.h" -#include "connection2.h" - -static void ssh2_connection_free(PacketProtocolLayer *); -static void ssh2_connection_process_queue(PacketProtocolLayer *); -static bool ssh2_connection_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); -static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg); -static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf); - -static const PacketProtocolLayerVtable ssh2_connection_vtable = { - .free = ssh2_connection_free, - .process_queue = ssh2_connection_process_queue, - .get_specials = ssh2_connection_get_specials, - .special_cmd = ssh2_connection_special_cmd, - .reconfigure = ssh2_connection_reconfigure, - .queued_data_size = ssh_ppl_default_queued_data_size, - .final_output = ssh_ppl_default_final_output, - .name = "ssh-connection", -}; - -static SshChannel *ssh2_lportfwd_open( - ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan); -static struct X11FakeAuth *ssh2_add_x11_display( - ConnectionLayer *cl, int authtype, struct X11Display *x11disp); -static struct X11FakeAuth *ssh2_add_sharing_x11_display( - ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, - share_channel *share_chan); -static void ssh2_remove_sharing_x11_display(ConnectionLayer *cl, - struct X11FakeAuth *auth); -static void ssh2_send_packet_from_downstream( - ConnectionLayer *cl, unsigned id, int type, - const void *pkt, int pktlen, const char *additional_log_text); -static unsigned ssh2_alloc_sharing_channel( - ConnectionLayer *cl, ssh_sharing_connstate *connstate); -static void ssh2_delete_sharing_channel( - ConnectionLayer *cl, unsigned localid); -static void ssh2_sharing_queue_global_request( - ConnectionLayer *cl, ssh_sharing_connstate *share_ctx); -static void ssh2_sharing_no_more_downstreams(ConnectionLayer *cl); -static bool ssh2_agent_forwarding_permitted(ConnectionLayer *cl); -static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height); -static void ssh2_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize); -static size_t ssh2_stdin_backlog(ConnectionLayer *cl); -static void ssh2_throttle_all_channels(ConnectionLayer *cl, bool throttled); -static bool ssh2_ldisc_option(ConnectionLayer *cl, int option); -static void ssh2_set_ldisc_option(ConnectionLayer *cl, int option, bool value); -static void ssh2_enable_x_fwd(ConnectionLayer *cl); -static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted); -static bool ssh2_get_wants_user_input(ConnectionLayer *cl); -static void ssh2_got_user_input(ConnectionLayer *cl); - -static const ConnectionLayerVtable ssh2_connlayer_vtable = { - .rportfwd_alloc = ssh2_rportfwd_alloc, - .rportfwd_remove = ssh2_rportfwd_remove, - .lportfwd_open = ssh2_lportfwd_open, - .session_open = ssh2_session_open, - .serverside_x11_open = ssh2_serverside_x11_open, - .serverside_agent_open = ssh2_serverside_agent_open, - .add_x11_display = ssh2_add_x11_display, - .add_sharing_x11_display = ssh2_add_sharing_x11_display, - .remove_sharing_x11_display = ssh2_remove_sharing_x11_display, - .send_packet_from_downstream = ssh2_send_packet_from_downstream, - .alloc_sharing_channel = ssh2_alloc_sharing_channel, - .delete_sharing_channel = ssh2_delete_sharing_channel, - .sharing_queue_global_request = ssh2_sharing_queue_global_request, - .sharing_no_more_downstreams = ssh2_sharing_no_more_downstreams, - .agent_forwarding_permitted = ssh2_agent_forwarding_permitted, - .terminal_size = ssh2_terminal_size, - .stdout_unthrottle = ssh2_stdout_unthrottle, - .stdin_backlog = ssh2_stdin_backlog, - .throttle_all_channels = ssh2_throttle_all_channels, - .ldisc_option = ssh2_ldisc_option, - .set_ldisc_option = ssh2_set_ldisc_option, - .enable_x_fwd = ssh2_enable_x_fwd, - .set_wants_user_input = ssh2_set_wants_user_input, - .get_wants_user_input = ssh2_get_wants_user_input, - .got_user_input = ssh2_got_user_input, -}; - -static char *ssh2_channel_open_failure_error_text(PktIn *pktin) -{ - static const char *const reasons[] = { - NULL, - "Administratively prohibited", - "Connect failed", - "Unknown channel type", - "Resource shortage", - }; - unsigned reason_code; - const char *reason_code_string; - char reason_code_buf[256]; - ptrlen reason; - - reason_code = get_uint32(pktin); - if (reason_code < lenof(reasons) && reasons[reason_code]) { - reason_code_string = reasons[reason_code]; - } else { - reason_code_string = reason_code_buf; - sprintf(reason_code_buf, "unknown reason code %#x", reason_code); - } - - reason = get_string(pktin); - - return dupprintf("%s [%.*s]", reason_code_string, PTRLEN_PRINTF(reason)); -} - -static size_t ssh2channel_write( - SshChannel *c, bool is_stderr, const void *buf, size_t len); -static void ssh2channel_write_eof(SshChannel *c); -static void ssh2channel_initiate_close(SshChannel *c, const char *err); -static void ssh2channel_unthrottle(SshChannel *c, size_t bufsize); -static Conf *ssh2channel_get_conf(SshChannel *c); -static void ssh2channel_window_override_removed(SshChannel *c); -static void ssh2channel_x11_sharing_handover( - SshChannel *c, ssh_sharing_connstate *share_cs, share_channel *share_chan, - const char *peer_addr, int peer_port, int endian, - int protomajor, int protominor, const void *initial_data, int initial_len); -static void ssh2channel_hint_channel_is_simple(SshChannel *c); - -static const SshChannelVtable ssh2channel_vtable = { - .write = ssh2channel_write, - .write_eof = ssh2channel_write_eof, - .initiate_close = ssh2channel_initiate_close, - .unthrottle = ssh2channel_unthrottle, - .get_conf = ssh2channel_get_conf, - .window_override_removed = ssh2channel_window_override_removed, - .x11_sharing_handover = ssh2channel_x11_sharing_handover, - .send_exit_status = ssh2channel_send_exit_status, - .send_exit_signal = ssh2channel_send_exit_signal, - .send_exit_signal_numeric = ssh2channel_send_exit_signal_numeric, - .request_x11_forwarding = ssh2channel_request_x11_forwarding, - .request_agent_forwarding = ssh2channel_request_agent_forwarding, - .request_pty = ssh2channel_request_pty, - .send_env_var = ssh2channel_send_env_var, - .start_shell = ssh2channel_start_shell, - .start_command = ssh2channel_start_command, - .start_subsystem = ssh2channel_start_subsystem, - .send_serial_break = ssh2channel_send_serial_break, - .send_signal = ssh2channel_send_signal, - .send_terminal_size_change = ssh2channel_send_terminal_size_change, - .hint_channel_is_simple = ssh2channel_hint_channel_is_simple, -}; - -static void ssh2_channel_check_close(struct ssh2_channel *c); -static void ssh2_channel_try_eof(struct ssh2_channel *c); -static void ssh2_set_window(struct ssh2_channel *c, int newwin); -static size_t ssh2_try_send(struct ssh2_channel *c); -static void ssh2_try_send_and_unthrottle(struct ssh2_channel *c); -static void ssh2_channel_check_throttle(struct ssh2_channel *c); -static void ssh2_channel_close_local(struct ssh2_channel *c, - const char *reason); -static void ssh2_channel_destroy(struct ssh2_channel *c); - -static void ssh2_check_termination(struct ssh2_connection_state *s); - -struct outstanding_global_request { - gr_handler_fn_t handler; - void *ctx; - struct outstanding_global_request *next; -}; -void ssh2_queue_global_request_handler( - struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx) -{ - struct outstanding_global_request *ogr = - snew(struct outstanding_global_request); - ogr->handler = handler; - ogr->ctx = ctx; - ogr->next = NULL; - if (s->globreq_tail) - s->globreq_tail->next = ogr; - else - s->globreq_head = ogr; - s->globreq_tail = ogr; -} - -static int ssh2_channelcmp(void *av, void *bv) -{ - const struct ssh2_channel *a = (const struct ssh2_channel *) av; - const struct ssh2_channel *b = (const struct ssh2_channel *) bv; - if (a->localid < b->localid) - return -1; - if (a->localid > b->localid) - return +1; - return 0; -} - -static int ssh2_channelfind(void *av, void *bv) -{ - const unsigned *a = (const unsigned *) av; - const struct ssh2_channel *b = (const struct ssh2_channel *) bv; - if (*a < b->localid) - return -1; - if (*a > b->localid) - return +1; - return 0; -} - -/* - * Each channel has a queue of outstanding CHANNEL_REQUESTS and their - * handlers. - */ -struct outstanding_channel_request { - cr_handler_fn_t handler; - void *ctx; - struct outstanding_channel_request *next; -}; - -static void ssh2_channel_free(struct ssh2_channel *c) -{ - bufchain_clear(&c->outbuffer); - bufchain_clear(&c->errbuffer); - while (c->chanreq_head) { - struct outstanding_channel_request *chanreq = c->chanreq_head; - c->chanreq_head = c->chanreq_head->next; - sfree(chanreq); - } - if (c->chan) { - struct ssh2_connection_state *s = c->connlayer; - if (s->mainchan_sc == &c->sc) { - s->mainchan = NULL; - s->mainchan_sc = NULL; - } - chan_free(c->chan); - } - sfree(c); -} - -PacketProtocolLayer *ssh2_connection_new( - Ssh *ssh, ssh_sharing_state *connshare, bool is_simple, - Conf *conf, const char *peer_verstring, bufchain *user_input, - ConnectionLayer **cl_out) -{ - struct ssh2_connection_state *s = snew(struct ssh2_connection_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh2_connection_vtable; - - s->conf = conf_copy(conf); - - s->ssh_is_simple = is_simple; - - /* - * If the ssh_no_shell option is enabled, we disable the usual - * termination check, so that we persist even in the absence of - * any at all channels (because our purpose is probably to be a - * background port forwarder). - */ - s->persistent = conf_get_bool(s->conf, CONF_ssh_no_shell); - - s->connshare = connshare; - s->peer_verstring = dupstr(peer_verstring); - - s->channels = newtree234(ssh2_channelcmp); - - s->x11authtree = newtree234(x11_authcmp); - - s->user_input = user_input; - - /* Need to get the log context for s->cl now, because we won't be - * helpfully notified when a copy is written into s->ppl by our - * owner. */ - s->cl.vt = &ssh2_connlayer_vtable; - s->cl.logctx = ssh_get_logctx(ssh); - - s->portfwdmgr = portfwdmgr_new(&s->cl); - - *cl_out = &s->cl; - if (s->connshare) - ssh_connshare_provide_connlayer(s->connshare, &s->cl); - - return &s->ppl; -} - -static void ssh2_connection_free(PacketProtocolLayer *ppl) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - struct X11FakeAuth *auth; - struct ssh2_channel *c; - struct ssh_rportfwd *rpf; - - sfree(s->peer_verstring); - - conf_free(s->conf); - - while ((c = delpos234(s->channels, 0)) != NULL) - ssh2_channel_free(c); - freetree234(s->channels); - - while ((auth = delpos234(s->x11authtree, 0)) != NULL) { - if (auth->disp) - x11_free_display(auth->disp); - x11_free_fake_auth(auth); - } - freetree234(s->x11authtree); - - if (s->rportfwds) { - while ((rpf = delpos234(s->rportfwds, 0)) != NULL) - free_rportfwd(rpf); - freetree234(s->rportfwds); - } - portfwdmgr_free(s->portfwdmgr); - - if (s->antispoof_prompt) - free_prompts(s->antispoof_prompt); - - delete_callbacks_for_context(s); - - sfree(s); -} - -static bool ssh2_connection_filter_queue(struct ssh2_connection_state *s) -{ - PktIn *pktin; - PktOut *pktout; - ptrlen type, data; - struct ssh2_channel *c; - struct outstanding_channel_request *ocr; - unsigned localid, remid, winsize, pktsize, ext_type; - bool want_reply, reply_success, expect_halfopen; - ChanopenResult chanopen_result; - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - - while (1) { - if (ssh2_common_filter_queue(&s->ppl)) - return true; - if ((pktin = pq_peek(s->ppl.in_pq)) == NULL) - return false; - - switch (pktin->type) { - case SSH2_MSG_GLOBAL_REQUEST: - type = get_string(pktin); - want_reply = get_bool(pktin); - - reply_success = ssh2_connection_parse_global_request( - s, type, pktin); - - if (want_reply) { - int type = (reply_success ? SSH2_MSG_REQUEST_SUCCESS : - SSH2_MSG_REQUEST_FAILURE); - pktout = ssh_bpp_new_pktout(s->ppl.bpp, type); - pq_push(s->ppl.out_pq, pktout); - } - pq_pop(s->ppl.in_pq); - break; - - case SSH2_MSG_REQUEST_SUCCESS: - case SSH2_MSG_REQUEST_FAILURE: - if (!s->globreq_head) { - ssh_proto_error( - s->ppl.ssh, - "Received %s with no outstanding global request", - ssh2_pkt_type(s->ppl.bpp->pls->kctx, s->ppl.bpp->pls->actx, - pktin->type)); - return true; - } - - s->globreq_head->handler(s, pktin, s->globreq_head->ctx); - { - struct outstanding_global_request *tmp = s->globreq_head; - s->globreq_head = s->globreq_head->next; - sfree(tmp); - } - if (!s->globreq_head) - s->globreq_tail = NULL; - - pq_pop(s->ppl.in_pq); - break; - - case SSH2_MSG_CHANNEL_OPEN: - type = get_string(pktin); - c = snew(struct ssh2_channel); - c->connlayer = s; - c->chan = NULL; - - remid = get_uint32(pktin); - winsize = get_uint32(pktin); - pktsize = get_uint32(pktin); - - chanopen_result = ssh2_connection_parse_channel_open( - s, type, pktin, &c->sc); - - if (chanopen_result.outcome == CHANOPEN_RESULT_DOWNSTREAM) { - /* - * This channel-open request needs to go to a - * connection-sharing downstream, so abandon our own - * channel-open procedure and just pass the message on - * to sharing.c. - */ - share_got_pkt_from_server( - chanopen_result.u.downstream.share_ctx, pktin->type, - BinarySource_UPCAST(pktin)->data, - BinarySource_UPCAST(pktin)->len); - sfree(c); - break; - } - - c->remoteid = remid; - c->halfopen = false; - if (chanopen_result.outcome == CHANOPEN_RESULT_FAILURE) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_FAILURE); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, chanopen_result.u.failure.reason_code); - put_stringz(pktout, chanopen_result.u.failure.wire_message); - put_stringz(pktout, "en"); /* language tag */ - pq_push(s->ppl.out_pq, pktout); - ppl_logevent("Rejected channel open: %s", - chanopen_result.u.failure.wire_message); - sfree(chanopen_result.u.failure.wire_message); - sfree(c); - } else { - c->chan = chanopen_result.u.success.channel; - ssh2_channel_init(c); - c->remwindow = winsize; - c->remmaxpkt = pktsize; - if (c->remmaxpkt > s->ppl.bpp->vt->packet_size_limit) - c->remmaxpkt = s->ppl.bpp->vt->packet_size_limit; - if (c->chan->initial_fixed_window_size) { - c->locwindow = c->locmaxwin = c->remlocwin = - c->chan->initial_fixed_window_size; - } - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, c->localid); - put_uint32(pktout, c->locwindow); - put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ - pq_push(s->ppl.out_pq, pktout); - } - - pq_pop(s->ppl.in_pq); - break; - - case SSH2_MSG_CHANNEL_DATA: - case SSH2_MSG_CHANNEL_EXTENDED_DATA: - case SSH2_MSG_CHANNEL_WINDOW_ADJUST: - case SSH2_MSG_CHANNEL_REQUEST: - case SSH2_MSG_CHANNEL_EOF: - case SSH2_MSG_CHANNEL_CLOSE: - case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: - case SSH2_MSG_CHANNEL_OPEN_FAILURE: - case SSH2_MSG_CHANNEL_SUCCESS: - case SSH2_MSG_CHANNEL_FAILURE: - /* - * Common preliminary code for all the messages from the - * server that cite one of our channel ids: look up that - * channel id, check it exists, and if it's for a sharing - * downstream, pass it on. - */ - localid = get_uint32(pktin); - c = find234(s->channels, &localid, ssh2_channelfind); - - if (c && c->sharectx) { - share_got_pkt_from_server(c->sharectx, pktin->type, - BinarySource_UPCAST(pktin)->data, - BinarySource_UPCAST(pktin)->len); - pq_pop(s->ppl.in_pq); - break; - } - - expect_halfopen = ( - pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION || - pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE); - - if (!c || c->halfopen != expect_halfopen) { - ssh_proto_error(s->ppl.ssh, - "Received %s for %s channel %u", - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type), - (!c ? "nonexistent" : - c->halfopen ? "half-open" : "open"), - localid); - return true; - } - - switch (pktin->type) { - case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: - assert(c->halfopen); - c->remoteid = get_uint32(pktin); - c->halfopen = false; - c->remwindow = get_uint32(pktin); - c->remmaxpkt = get_uint32(pktin); - if (c->remmaxpkt > s->ppl.bpp->vt->packet_size_limit) - c->remmaxpkt = s->ppl.bpp->vt->packet_size_limit; - - chan_open_confirmation(c->chan); - - /* - * Now that the channel is fully open, it's possible - * in principle to immediately close it. Check whether - * it wants us to! - * - * This can occur if a local socket error occurred - * between us sending out CHANNEL_OPEN and receiving - * OPEN_CONFIRMATION. If that happens, all we can do - * is immediately initiate close proceedings now that - * we know the server's id to put in the close - * message. We'll have handled that in this code by - * having already turned c->chan into a zombie, so its - * want_close method (which ssh2_channel_check_close - * will consult) will already be returning true. - */ - ssh2_channel_check_close(c); - - if (c->pending_eof) - ssh2_channel_try_eof(c); /* in case we had a pending EOF */ - break; - - case SSH2_MSG_CHANNEL_OPEN_FAILURE: { - assert(c->halfopen); - - char *err = ssh2_channel_open_failure_error_text(pktin); - chan_open_failed(c->chan, err); - sfree(err); - - del234(s->channels, c); - ssh2_channel_free(c); - - break; - } - - case SSH2_MSG_CHANNEL_DATA: - case SSH2_MSG_CHANNEL_EXTENDED_DATA: - ext_type = (pktin->type == SSH2_MSG_CHANNEL_DATA ? 0 : - get_uint32(pktin)); - data = get_string(pktin); - if (!get_err(pktin)) { - int bufsize; - c->locwindow -= data.len; - c->remlocwin -= data.len; - if (ext_type != 0 && ext_type != SSH2_EXTENDED_DATA_STDERR) - data.len = 0; /* ignore unknown extended data */ - bufsize = chan_send( - c->chan, ext_type == SSH2_EXTENDED_DATA_STDERR, - data.ptr, data.len); - - /* - * The channel may have turned into a connection- - * shared one as a result of that chan_send, e.g. - * if the data we just provided completed the X11 - * auth phase and caused a callback to - * x11_sharing_handover. If so, do nothing - * further. - */ - if (c->sharectx) - 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->remlocwin <= 0 && - c->throttle_state == UNTHROTTLED && - c->locmaxwin < 0x40000000) - c->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. - */ - if (bufsize < c->locmaxwin) - ssh2_set_window(c, c->locmaxwin - bufsize); - - /* - * 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->locmaxwin || - (s->ssh_is_simple && bufsize>0)) && - !c->throttling_conn) { - c->throttling_conn = true; - ssh_throttle_conn(s->ppl.ssh, +1); - } - } - break; - - case SSH2_MSG_CHANNEL_WINDOW_ADJUST: - if (!(c->closes & CLOSES_SENT_EOF)) { - c->remwindow += get_uint32(pktin); - ssh2_try_send_and_unthrottle(c); - } - break; - - case SSH2_MSG_CHANNEL_REQUEST: - type = get_string(pktin); - want_reply = get_bool(pktin); - - reply_success = false; - - 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; - } - - /* - * Try every channel request name we recognise, no - * matter what the channel, and see if the Channel - * instance will accept it. - */ - if (ptrlen_eq_string(type, "exit-status")) { - int exitcode = toint(get_uint32(pktin)); - reply_success = chan_rcvd_exit_status(c->chan, exitcode); - } else if (ptrlen_eq_string(type, "exit-signal")) { - ptrlen signame; - int signum; - bool core = false; - ptrlen errmsg; - int format; - - /* - * 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. - */ - - size_t startpos = BinarySource_UPCAST(pktin)->pos; - - for (format = 0; format < 2; format++) { - BinarySource_UPCAST(pktin)->pos = startpos; - BinarySource_UPCAST(pktin)->err = BSE_NO_ERROR; - - /* placate compiler warnings about unin */ - signame = make_ptrlen(NULL, 0); - signum = 0; - - if (format == 0) /* standard string-based format */ - signame = get_string(pktin); - else /* nonstandard integer format */ - signum = toint(get_uint32(pktin)); - - core = get_bool(pktin); - errmsg = get_string(pktin); /* error message */ - get_string(pktin); /* language tag */ - - if (!get_err(pktin) && get_avail(pktin) == 0) - break; /* successful parse */ - } - - switch (format) { - case 0: - reply_success = chan_rcvd_exit_signal( - c->chan, signame, core, errmsg); - break; - case 1: - reply_success = chan_rcvd_exit_signal_numeric( - c->chan, signum, core, errmsg); - break; - default: - /* Couldn't parse this message in either format */ - reply_success = false; - break; - } - } else if (ptrlen_eq_string(type, "shell")) { - reply_success = chan_run_shell(c->chan); - } else if (ptrlen_eq_string(type, "exec")) { - ptrlen command = get_string(pktin); - reply_success = chan_run_command(c->chan, command); - } else if (ptrlen_eq_string(type, "subsystem")) { - ptrlen subsys = get_string(pktin); - reply_success = chan_run_subsystem(c->chan, subsys); - } else if (ptrlen_eq_string(type, "x11-req")) { - bool oneshot = get_bool(pktin); - ptrlen authproto = get_string(pktin); - ptrlen authdata = get_string(pktin); - unsigned screen_number = get_uint32(pktin); - reply_success = chan_enable_x11_forwarding( - c->chan, oneshot, authproto, authdata, screen_number); - } else if (ptrlen_eq_string(type, - "auth-agent-req@openssh.com")) { - reply_success = chan_enable_agent_forwarding(c->chan); - } else if (ptrlen_eq_string(type, "pty-req")) { - ptrlen termtype = get_string(pktin); - unsigned width = get_uint32(pktin); - unsigned height = get_uint32(pktin); - unsigned pixwidth = get_uint32(pktin); - unsigned pixheight = get_uint32(pktin); - ptrlen encoded_modes = get_string(pktin); - BinarySource bs_modes[1]; - struct ssh_ttymodes modes; - - BinarySource_BARE_INIT_PL(bs_modes, encoded_modes); - modes = read_ttymodes_from_packet(bs_modes, 2); - if (get_err(bs_modes) || get_avail(bs_modes) > 0) { - ppl_logevent("Unable to decode terminal mode string"); - reply_success = false; - } else { - reply_success = chan_allocate_pty( - c->chan, termtype, width, height, - pixwidth, pixheight, modes); - } - } else if (ptrlen_eq_string(type, "env")) { - ptrlen var = get_string(pktin); - ptrlen value = get_string(pktin); - - reply_success = chan_set_env(c->chan, var, value); - } else if (ptrlen_eq_string(type, "break")) { - unsigned length = get_uint32(pktin); - - reply_success = chan_send_break(c->chan, length); - } else if (ptrlen_eq_string(type, "signal")) { - ptrlen signame = get_string(pktin); - - reply_success = chan_send_signal(c->chan, signame); - } else if (ptrlen_eq_string(type, "window-change")) { - unsigned width = get_uint32(pktin); - unsigned height = get_uint32(pktin); - unsigned pixwidth = get_uint32(pktin); - unsigned pixheight = get_uint32(pktin); - reply_success = chan_change_window_size( - c->chan, width, height, pixwidth, pixheight); - } - if (want_reply) { - int type = (reply_success ? SSH2_MSG_CHANNEL_SUCCESS : - SSH2_MSG_CHANNEL_FAILURE); - pktout = ssh_bpp_new_pktout(s->ppl.bpp, type); - put_uint32(pktout, c->remoteid); - pq_push(s->ppl.out_pq, pktout); - } - break; - - case SSH2_MSG_CHANNEL_SUCCESS: - case SSH2_MSG_CHANNEL_FAILURE: - ocr = c->chanreq_head; - if (!ocr) { - ssh_proto_error( - s->ppl.ssh, - "Received %s for channel %d with no outstanding " - "channel request", - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, pktin->type), - c->localid); - return true; - } - ocr->handler(c, pktin, ocr->ctx); - c->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); - break; - - case SSH2_MSG_CHANNEL_EOF: - if (!(c->closes & CLOSES_RCVD_EOF)) { - c->closes |= CLOSES_RCVD_EOF; - chan_send_eof(c->chan); - ssh2_channel_check_close(c); - } - break; - - case SSH2_MSG_CHANNEL_CLOSE: - /* - * When we receive CLOSE on a channel, we assume it - * comes with an implied EOF if we haven't seen EOF - * yet. - */ - if (!(c->closes & CLOSES_RCVD_EOF)) { - c->closes |= CLOSES_RCVD_EOF; - chan_send_eof(c->chan); - } - - if (!(s->ppl.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->chanreq_head) { - struct outstanding_channel_request *ocr = - c->chanreq_head; - ocr->handler(c, NULL, ocr->ctx); - c->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)) { - /* - * 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->outbuffer); - bufchain_clear(&c->errbuffer); - - /* - * Send outgoing EOF. - */ - sshfwd_write_eof(&c->sc); - - /* - * Make sure we don't read any more from whatever - * our local data source is for this channel. - * (This will pick up on the changes made by - * sshfwd_write_eof.) - */ - ssh2_channel_check_throttle(c); - } - - /* - * Now process the actual close. - */ - if (!(c->closes & CLOSES_RCVD_CLOSE)) { - c->closes |= CLOSES_RCVD_CLOSE; - ssh2_channel_check_close(c); - } - - break; - } - - pq_pop(s->ppl.in_pq); - break; - - default: - return false; - } - } -} - -static void ssh2_handle_winadj_response(struct ssh2_channel *c, - PktIn *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->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->throttle_state == UNTHROTTLING) - c->throttle_state = UNTHROTTLED; -} - -static void ssh2_set_window(struct ssh2_channel *c, int newwin) -{ - struct ssh2_connection_state *s = c->connlayer; - - /* - * 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; - - /* - * If the client-side Channel is in an initial setup phase with a - * fixed window size, e.g. for an X11 channel when we're still - * waiting to see its initial auth and may yet hand it off to a - * downstream, don't send any WINDOW_ADJUST either. - */ - if (c->chan->initial_fixed_window_size) - 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 ((s->ppl.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->locwindow) { - PktOut *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->locmaxwin && - !(s->ppl.remote_bugs & BUG_CHOKES_ON_WINADJ)) { - up = snew(unsigned); - *up = newwin - c->locwindow; - pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org", - ssh2_handle_winadj_response, up); - pq_push(s->ppl.out_pq, pktout); - - if (c->throttle_state != UNTHROTTLED) - c->throttle_state = UNTHROTTLING; - } else { - /* Pretend the WINDOW_ADJUST was acked immediately. */ - c->remlocwin = newwin; - c->throttle_state = THROTTLED; - } - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_WINDOW_ADJUST); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, newwin - c->locwindow); - pq_push(s->ppl.out_pq, pktout); - c->locwindow = newwin; - } -} - -static PktIn *ssh2_connection_pop(struct ssh2_connection_state *s) -{ - ssh2_connection_filter_queue(s); - return pq_pop(s->ppl.in_pq); -} - -static void ssh2_connection_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - PktIn *pktin; - - if (ssh2_connection_filter_queue(s)) /* no matter why we were called */ - return; - - crBegin(s->crState); - - if (s->connshare) - share_activate(s->connshare, s->peer_verstring); - - /* - * Signal the seat that authentication is done, so that it can - * deploy spoofing defences. If it doesn't have any, deploy our - * own fallback one. - * - * We do this here rather than at the end of userauth, because we - * might not have gone through userauth at all (if we're a - * connection-sharing downstream). - */ - if (ssh2_connection_need_antispoof_prompt(s)) { - s->antispoof_prompt = ssh_ppl_new_prompts(&s->ppl); - s->antispoof_prompt->to_server = false; - s->antispoof_prompt->from_server = false; - s->antispoof_prompt->name = dupstr("Authentication successful"); - add_prompt( - s->antispoof_prompt, - dupstr("Access granted. Press Return to begin session. "), false); - s->antispoof_ret = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->antispoof_prompt); - while (s->antispoof_ret.kind == SPRK_INCOMPLETE) { - crReturnV; - s->antispoof_ret = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->antispoof_prompt); - } - free_prompts(s->antispoof_prompt); - s->antispoof_prompt = NULL; - } - - /* - * Enable port forwardings. - */ - portfwdmgr_config(s->portfwdmgr, s->conf); - s->portfwdmgr_configured = true; - - /* - * Create the main session channel, if any. - */ - s->mainchan = mainchan_new( - &s->ppl, &s->cl, s->conf, s->term_width, s->term_height, - s->ssh_is_simple, &s->mainchan_sc); - s->started = true; - - /* - * Transfer data! - */ - - while (1) { - if ((pktin = ssh2_connection_pop(s)) != NULL) { - - /* - * _All_ the connection-layer packets we expect to - * receive are now handled by the dispatch table. - * Anything that reaches here must be bogus. - */ - - ssh_proto_error(s->ppl.ssh, "Received unexpected connection-layer " - "packet, type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return; - } - crReturnV; - } - - crFinishV; -} - -static void ssh2_channel_check_close(struct ssh2_channel *c) -{ - struct ssh2_connection_state *s = c->connlayer; - PktOut *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 (chan_want_close(c->chan, (c->closes & CLOSES_SENT_EOF), - (c->closes & CLOSES_RCVD_EOF)) && - !c->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 = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_CLOSE); - put_uint32(pktout, c->remoteid); - pq_push(s->ppl.out_pq, pktout); - c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE; - } - - if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) { - assert(c->chanreq_head == NULL); - /* - * We have both sent and received CLOSE, which means we're - * completely done with the channel. - */ - ssh2_channel_destroy(c); - } -} - -static void ssh2_channel_try_eof(struct ssh2_channel *c) -{ - struct ssh2_connection_state *s = c->connlayer; - PktOut *pktout; - assert(c->pending_eof); /* precondition for calling us */ - if (c->halfopen) - return; /* can't close: not even opened yet */ - if (bufchain_size(&c->outbuffer) > 0 || bufchain_size(&c->errbuffer) > 0) - return; /* can't send EOF: pending outgoing data */ - - c->pending_eof = false; /* we're about to send it */ - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_EOF); - put_uint32(pktout, c->remoteid); - pq_push(s->ppl.out_pq, pktout); - c->closes |= CLOSES_SENT_EOF; - ssh2_channel_check_close(c); -} - -/* - * Attempt to send data on an SSH-2 channel. - */ -static size_t ssh2_try_send(struct ssh2_channel *c) -{ - struct ssh2_connection_state *s = c->connlayer; - PktOut *pktout; - size_t bufsize; - - if (!c->halfopen) { - while (c->remwindow > 0 && - (bufchain_size(&c->outbuffer) > 0 || - bufchain_size(&c->errbuffer) > 0)) { - bufchain *buf = (bufchain_size(&c->errbuffer) > 0 ? - &c->errbuffer : &c->outbuffer); - - ptrlen data = bufchain_prefix(buf); - if (data.len > c->remwindow) - data.len = c->remwindow; - if (data.len > c->remmaxpkt) - data.len = c->remmaxpkt; - if (buf == &c->errbuffer) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_CHANNEL_EXTENDED_DATA); - put_uint32(pktout, c->remoteid); - put_uint32(pktout, SSH2_EXTENDED_DATA_STDERR); - } else { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_DATA); - put_uint32(pktout, c->remoteid); - } - put_stringpl(pktout, data); - pq_push(s->ppl.out_pq, pktout); - bufchain_consume(buf, data.len); - c->remwindow -= data.len; - } - } - - /* - * After having sent as much data as we can, return the amount - * still buffered. - */ - bufsize = bufchain_size(&c->outbuffer) + bufchain_size(&c->errbuffer); - - /* - * And if there's no data pending but we need to send an EOF, send - * it. - */ - if (!bufsize && c->pending_eof) - ssh2_channel_try_eof(c); - - ssh_sendbuffer_changed(s->ppl.ssh); - return bufsize; -} - -static void ssh2_try_send_and_unthrottle(struct ssh2_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) { - c->throttled_by_backlog = false; - ssh2_channel_check_throttle(c); - } -} - -static void ssh2_channel_check_throttle(struct ssh2_channel *c) -{ - /* - * We don't want this channel to read further input if this - * particular channel has a backed-up SSH window, or if the - * outgoing side of the whole SSH connection is currently - * throttled, or if this channel already has an outgoing EOF - * either sent or pending. - */ - chan_set_input_wanted(c->chan, - !c->throttled_by_backlog && - !c->connlayer->all_channels_throttled && - !c->pending_eof && - !(c->closes & CLOSES_SENT_EOF)); -} - -/* - * Close any local socket and free any local resources associated with - * a channel. This converts the channel into a zombie. - */ -static void ssh2_channel_close_local(struct ssh2_channel *c, - const char *reason) -{ - struct ssh2_connection_state *s = c->connlayer; - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - char *msg = NULL; - - if (c->sharectx) - return; - - msg = chan_log_close_msg(c->chan); - - if (msg) - ppl_logevent("%s%s%s", msg, reason ? " " : "", reason ? reason : ""); - - sfree(msg); - - chan_free(c->chan); - c->chan = zombiechan_new(); -} - -static void ssh2_check_termination_callback(void *vctx) -{ - struct ssh2_connection_state *s = (struct ssh2_connection_state *)vctx; - ssh2_check_termination(s); -} - -static void ssh2_channel_destroy(struct ssh2_channel *c) -{ - struct ssh2_connection_state *s = c->connlayer; - - assert(c->chanreq_head == NULL); - - ssh2_channel_close_local(c, NULL); - del234(s->channels, c); - ssh2_channel_free(c); - - /* - * If that was the last channel left open, we might need to - * terminate. But we'll be a bit cautious, by doing that in a - * toplevel callback, just in case anything on the current call - * stack objects to this entire PPL being freed. - */ - queue_toplevel_callback(ssh2_check_termination_callback, s); -} - -static void ssh2_check_termination(struct ssh2_connection_state *s) -{ - /* - * Decide whether we should terminate the SSH connection now. - * Called after a channel or a downstream goes away. The general - * policy is that we terminate when none of either is left. - */ - - if (s->persistent) - return; /* persistent mode: never proactively terminate */ - - if (!s->started) { - /* At startup, we don't have any channels open because we - * haven't got round to opening the main one yet. In that - * situation, we don't want to terminate, even if a sharing - * connection opens and closes and causes a call to this - * function. */ - return; - } - - if (count234(s->channels) == 0 && - !(s->connshare && share_ndownstreams(s->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_user_close(s->ppl.ssh, "All channels closed"); - return; - } -} - -/* - * Set up most of a new ssh2_channel. Nulls out sharectx, but leaves - * chan untouched (since it will sometimes have been filled in before - * calling this). - */ -void ssh2_channel_init(struct ssh2_channel *c) -{ - struct ssh2_connection_state *s = c->connlayer; - c->closes = 0; - c->pending_eof = false; - c->throttling_conn = false; - c->throttled_by_backlog = false; - c->sharectx = NULL; - c->locwindow = c->locmaxwin = c->remlocwin = - s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE; - c->chanreq_head = NULL; - c->throttle_state = UNTHROTTLED; - bufchain_init(&c->outbuffer); - bufchain_init(&c->errbuffer); - c->sc.vt = &ssh2channel_vtable; - c->sc.cl = &s->cl; - c->localid = alloc_channel_id(s->channels, struct ssh2_channel); - add234(s->channels, c); -} - -/* - * Construct the common parts of a CHANNEL_OPEN. - */ -PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type) -{ - struct ssh2_connection_state *s = c->connlayer; - PktOut *pktout; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_OPEN); - put_stringz(pktout, type); - put_uint32(pktout, c->localid); - put_uint32(pktout, c->locwindow); /* our window size */ - put_uint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */ - return pktout; -} - -/* - * 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. - */ -PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type, - cr_handler_fn_t handler, void *ctx) -{ - struct ssh2_connection_state *s = c->connlayer; - PktOut *pktout; - - assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE))); - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_REQUEST); - put_uint32(pktout, c->remoteid); - put_stringz(pktout, type); - put_bool(pktout, handler != NULL); - if (handler != NULL) { - struct outstanding_channel_request *ocr = - snew(struct outstanding_channel_request); - - ocr->handler = handler; - ocr->ctx = ctx; - ocr->next = NULL; - if (!c->chanreq_head) - c->chanreq_head = ocr; - else - c->chanreq_tail->next = ocr; - c->chanreq_tail = ocr; - } - return pktout; -} - -static Conf *ssh2channel_get_conf(SshChannel *sc) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - return s->conf; -} - -static void ssh2channel_write_eof(SshChannel *sc) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - - if (c->closes & CLOSES_SENT_EOF) - return; - - c->pending_eof = true; - ssh2_channel_try_eof(c); -} - -static void ssh2channel_initiate_close(SshChannel *sc, const char *err) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - char *reason; - - reason = err ? dupprintf("due to local error: %s", err) : NULL; - ssh2_channel_close_local(c, reason); - sfree(reason); - c->pending_eof = false; /* this will confuse a zombie channel */ - - ssh2_channel_check_close(c); -} - -static void ssh2channel_unthrottle(SshChannel *sc, size_t bufsize) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - size_t buflimit; - - buflimit = s->ssh_is_simple ? 0 : c->locmaxwin; - if (bufsize < buflimit) - ssh2_set_window(c, buflimit - bufsize); - - if (c->throttling_conn && bufsize <= buflimit) { - c->throttling_conn = false; - ssh_throttle_conn(s->ppl.ssh, -1); - } -} - -static size_t ssh2channel_write( - SshChannel *sc, bool is_stderr, const void *buf, size_t len) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - assert(!(c->closes & CLOSES_SENT_EOF)); - bufchain_add(is_stderr ? &c->errbuffer : &c->outbuffer, buf, len); - return ssh2_try_send(c); -} - -static void ssh2channel_x11_sharing_handover( - SshChannel *sc, ssh_sharing_connstate *share_cs, share_channel *share_chan, - const char *peer_addr, int peer_port, int endian, - int protomajor, int protominor, const void *initial_data, int initial_len) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - /* - * 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 sharing one, meaning - * that we completely stop tracking windows and buffering data and - * just pass more or less unmodified SSH messages back and forth. - */ - c->sharectx = share_cs; - share_setup_x11_channel(share_cs, share_chan, - c->localid, c->remoteid, c->remwindow, - c->remmaxpkt, c->locwindow, - peer_addr, peer_port, endian, - protomajor, protominor, - initial_data, initial_len); - chan_free(c->chan); - c->chan = NULL; -} - -static void ssh2channel_window_override_removed(SshChannel *sc) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - /* - * This function is called when a client-side Channel has just - * stopped requiring an initial fixed-size window. - */ - assert(!c->chan->initial_fixed_window_size); - ssh2_set_window(c, s->ssh_is_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE); -} - -static void ssh2channel_hint_channel_is_simple(SshChannel *sc) -{ - struct ssh2_channel *c = container_of(sc, struct ssh2_channel, sc); - struct ssh2_connection_state *s = c->connlayer; - - PktOut *pktout = ssh2_chanreq_init( - c, "simple@putty.projects.tartarus.org", NULL, NULL); - pq_push(s->ppl.out_pq, pktout); -} - -static SshChannel *ssh2_lportfwd_open( - ConnectionLayer *cl, const char *hostname, int port, - const char *description, const SocketPeerInfo *pi, Channel *chan) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct ssh2_channel *c = snew(struct ssh2_channel); - PktOut *pktout; - - c->connlayer = s; - ssh2_channel_init(c); - c->halfopen = true; - c->chan = chan; - - pktout = ssh2_portfwd_chanopen(s, c, hostname, port, description, pi); - pq_push(s->ppl.out_pq, pktout); - - return &c->sc; -} - -static void ssh2_sharing_globreq_response( - struct ssh2_connection_state *s, PktIn *pktin, void *ctx) -{ - ssh_sharing_connstate *cs = (ssh_sharing_connstate *)ctx; - share_got_pkt_from_server(cs, pktin->type, - BinarySource_UPCAST(pktin)->data, - BinarySource_UPCAST(pktin)->len); -} - -static void ssh2_sharing_queue_global_request( - ConnectionLayer *cl, ssh_sharing_connstate *cs) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - ssh2_queue_global_request_handler(s, ssh2_sharing_globreq_response, cs); -} - -static void ssh2_sharing_no_more_downstreams(ConnectionLayer *cl) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - queue_toplevel_callback(ssh2_check_termination_callback, s); -} - -static struct X11FakeAuth *ssh2_add_x11_display( - ConnectionLayer *cl, int authtype, struct X11Display *disp) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct X11FakeAuth *auth = x11_invent_fake_auth(s->x11authtree, authtype); - auth->disp = disp; - return auth; -} - -static struct X11FakeAuth *ssh2_add_sharing_x11_display( - ConnectionLayer *cl, int authtype, ssh_sharing_connstate *share_cs, - share_channel *share_chan) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - 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(s->x11authtree, authtype); - auth->share_cs = share_cs; - auth->share_chan = share_chan; - - return auth; -} - -static void ssh2_remove_sharing_x11_display( - ConnectionLayer *cl, struct X11FakeAuth *auth) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - del234(s->x11authtree, auth); - x11_free_fake_auth(auth); -} - -static unsigned ssh2_alloc_sharing_channel( - ConnectionLayer *cl, ssh_sharing_connstate *connstate) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct ssh2_channel *c = snew(struct ssh2_channel); - - c->connlayer = s; - ssh2_channel_init(c); - c->chan = NULL; - c->sharectx = connstate; - return c->localid; -} - -static void ssh2_delete_sharing_channel(ConnectionLayer *cl, unsigned localid) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct ssh2_channel *c = find234(s->channels, &localid, ssh2_channelfind); - if (c) - ssh2_channel_destroy(c); -} - -static void ssh2_send_packet_from_downstream( - ConnectionLayer *cl, unsigned id, int type, - const void *data, int datalen, const char *additional_log_text) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - PktOut *pkt = ssh_bpp_new_pktout(s->ppl.bpp, type); - pkt->downstream_id = id; - pkt->additional_log_text = additional_log_text; - put_data(pkt, data, datalen); - pq_push(s->ppl.out_pq, pkt); -} - -static bool ssh2_agent_forwarding_permitted(ConnectionLayer *cl) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - return conf_get_bool(s->conf, CONF_agentfwd) && agent_exists(); -} - -static bool ssh2_connection_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - bool toret = false; - - if (s->mainchan) { - mainchan_get_specials(s->mainchan, add_special, ctx); - toret = true; - } - - /* - * 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 (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { - if (toret) - add_special(ctx, NULL, SS_SEP, 0); - - add_special(ctx, "IGNORE message", SS_NOP, 0); - toret = true; - } - - return toret; -} - -static void ssh2_connection_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - PktOut *pktout; - - if (code == SS_PING || code == SS_NOP) { - if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE)) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_IGNORE); - put_stringz(pktout, ""); - pq_push(s->ppl.out_pq, pktout); - } - } else if (s->mainchan) { - mainchan_special_cmd(s->mainchan, code, arg); - } -} - -static void ssh2_terminal_size(ConnectionLayer *cl, int width, int height) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - s->term_width = width; - s->term_height = height; - if (s->mainchan) - mainchan_terminal_size(s->mainchan, width, height); -} - -static void ssh2_stdout_unthrottle(ConnectionLayer *cl, size_t bufsize) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - if (s->mainchan) - sshfwd_unthrottle(s->mainchan_sc, bufsize); -} - -static size_t ssh2_stdin_backlog(ConnectionLayer *cl) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct ssh2_channel *c; - - if (!s->mainchan) - return 0; - c = container_of(s->mainchan_sc, struct ssh2_channel, sc); - return s->mainchan ? - bufchain_size(&c->outbuffer) + bufchain_size(&c->errbuffer) : 0; -} - -static void ssh2_throttle_all_channels(ConnectionLayer *cl, bool throttled) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - struct ssh2_channel *c; - int i; - - s->all_channels_throttled = throttled; - - for (i = 0; NULL != (c = index234(s->channels, i)); i++) - if (!c->sharectx) - ssh2_channel_check_throttle(c); -} - -static bool ssh2_ldisc_option(ConnectionLayer *cl, int option) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - return s->ldisc_opts[option]; -} - -static void ssh2_set_ldisc_option(ConnectionLayer *cl, int option, bool value) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - s->ldisc_opts[option] = value; -} - -static void ssh2_enable_x_fwd(ConnectionLayer *cl) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - s->X11_fwd_enabled = true; -} - -static void ssh2_set_wants_user_input(ConnectionLayer *cl, bool wanted) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - s->want_user_input = wanted; - if (wanted) - ssh_check_sendok(s->ppl.ssh); -} - -static bool ssh2_get_wants_user_input(ConnectionLayer *cl) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - return s->want_user_input; -} - -static void ssh2_got_user_input(ConnectionLayer *cl) -{ - struct ssh2_connection_state *s = - container_of(cl, struct ssh2_connection_state, cl); - - while (s->mainchan && bufchain_size(s->user_input) > 0) { - /* - * Add user input to the main channel's buffer. - */ - ptrlen data = bufchain_prefix(s->user_input); - sshfwd_write(s->mainchan_sc, data.ptr, data.len); - bufchain_consume(s->user_input, data.len); - } -} - -static void ssh2_connection_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ - struct ssh2_connection_state *s = - container_of(ppl, struct ssh2_connection_state, ppl); - - conf_free(s->conf); - s->conf = conf_copy(conf); - - if (s->portfwdmgr_configured) - portfwdmgr_config(s->portfwdmgr, s->conf); -} diff --git a/ssh/connection2.h b/ssh/connection2.h deleted file mode 100644 index 54c3ebf98..000000000 --- a/ssh/connection2.h +++ /dev/null @@ -1,236 +0,0 @@ -#ifndef PUTTY_SSH2CONNECTION_H -#define PUTTY_SSH2CONNECTION_H - -struct outstanding_channel_request; -struct outstanding_global_request; - -struct ssh2_connection_state { - int crState; - - ssh_sharing_state *connshare; - char *peer_verstring; - - mainchan *mainchan; - SshChannel *mainchan_sc; - bool ldisc_opts[LD_N_OPTIONS]; - int session_attempt, session_status; - int term_width, term_height; - bool want_user_input; - bufchain *user_input; - - bool ssh_is_simple; - bool persistent; - bool started; - - Conf *conf; - - tree234 *channels; /* indexed by local id */ - bool all_channels_throttled; - - bool X11_fwd_enabled; - tree234 *x11authtree; - - bool got_pty; - - tree234 *rportfwds; - PortFwdManager *portfwdmgr; - bool portfwdmgr_configured; - - prompts_t *antispoof_prompt; - SeatPromptResult antispoof_ret; - - const SftpServerVtable *sftpserver_vt; - const SshServerConfig *ssc; - - /* - * These store the list of global requests that we're waiting for - * replies to. (REQUEST_FAILURE doesn't come with any indication - * of what message caused it, so we have to keep track of the - * queue ourselves.) - */ - struct outstanding_global_request *globreq_head, *globreq_tail; - - ConnectionLayer cl; - PacketProtocolLayer ppl; -}; - -typedef void (*gr_handler_fn_t)(struct ssh2_connection_state *s, - PktIn *pktin, void *ctx); -void ssh2_queue_global_request_handler( - struct ssh2_connection_state *s, gr_handler_fn_t handler, void *ctx); - -struct ssh2_channel { - struct ssh2_connection_state *connlayer; - - unsigned remoteid, localid; - int type; - /* True if we opened this channel but server hasn't confirmed. */ - bool halfopen; - - /* Bitmap of whether we've sent/received CHANNEL_EOF and - * CHANNEL_CLOSE. */ -#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. - */ - bool pending_eof; - - /* - * True if this channel is causing the underlying connection to be - * throttled. - */ - bool throttling_conn; - - /* - * True if we currently have backed-up data on the direction of - * this channel pointing out of the SSH connection, and therefore - * would prefer the 'Channel' implementation not to read further - * local input if possible. - */ - bool throttled_by_backlog; - - bufchain outbuffer, errbuffer; - 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 we're waiting for - * replies to. (CHANNEL_FAILURE doesn't come with any indication - * of what message caused it, so we have to keep track of the - * queue ourselves.) - */ - struct outstanding_channel_request *chanreq_head, *chanreq_tail; - - enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state; - - ssh_sharing_connstate *sharectx; /* sharing context, if this is a - * downstream channel */ - Channel *chan; /* handle the client side of this channel, if not */ - SshChannel sc; /* entry point for chan to talk back to */ -}; - -typedef void (*cr_handler_fn_t)(struct ssh2_channel *, PktIn *, void *); - -void ssh2_channel_init(struct ssh2_channel *c); -PktOut *ssh2_chanreq_init(struct ssh2_channel *c, const char *type, - cr_handler_fn_t handler, void *ctx); - -typedef enum ChanopenOutcome { - CHANOPEN_RESULT_FAILURE, - CHANOPEN_RESULT_SUCCESS, - CHANOPEN_RESULT_DOWNSTREAM, -} ChanopenOutcome; - -typedef struct ChanopenResult { - ChanopenOutcome outcome; - union { - struct { - char *wire_message; /* must be freed by recipient */ - unsigned reason_code; - } failure; - struct { - Channel *channel; - } success; - struct { - ssh_sharing_connstate *share_ctx; - } downstream; - } u; -} ChanopenResult; - -PktOut *ssh2_chanopen_init(struct ssh2_channel *c, const char *type); - -PktOut *ssh2_portfwd_chanopen( - struct ssh2_connection_state *s, struct ssh2_channel *c, - const char *hostname, int port, - const char *description, const SocketPeerInfo *peerinfo); - -struct ssh_rportfwd *ssh2_rportfwd_alloc( - ConnectionLayer *cl, - const char *shost, int sport, const char *dhost, int dport, - int addressfamily, const char *log_description, PortFwdRecord *pfr, - ssh_sharing_connstate *share_ctx); -void ssh2_rportfwd_remove( - ConnectionLayer *cl, struct ssh_rportfwd *rpf); -SshChannel *ssh2_session_open(ConnectionLayer *cl, Channel *chan); -SshChannel *ssh2_serverside_x11_open( - ConnectionLayer *cl, Channel *chan, const SocketPeerInfo *pi); -SshChannel *ssh2_serverside_agent_open(ConnectionLayer *cl, Channel *chan); - -void ssh2channel_send_exit_status(SshChannel *c, int status); -void ssh2channel_send_exit_signal( - SshChannel *c, ptrlen signame, bool core_dumped, ptrlen msg); -void ssh2channel_send_exit_signal_numeric( - SshChannel *c, int signum, bool core_dumped, ptrlen msg); -void ssh2channel_request_x11_forwarding( - SshChannel *c, bool want_reply, const char *authproto, - const char *authdata, int screen_number, bool oneshot); -void ssh2channel_request_agent_forwarding(SshChannel *c, bool want_reply); -void ssh2channel_request_pty( - SshChannel *c, bool want_reply, Conf *conf, int w, int h); -bool ssh2channel_send_env_var( - SshChannel *c, bool want_reply, const char *var, const char *value); -void ssh2channel_start_shell(SshChannel *c, bool want_reply); -void ssh2channel_start_command( - SshChannel *c, bool want_reply, const char *command); -bool ssh2channel_start_subsystem( - SshChannel *c, bool want_reply, const char *subsystem); -bool ssh2channel_send_env_var( - SshChannel *c, bool want_reply, const char *var, const char *value); -bool ssh2channel_send_serial_break( - SshChannel *c, bool want_reply, int length); -bool ssh2channel_send_signal( - SshChannel *c, bool want_reply, const char *signame); -void ssh2channel_send_terminal_size_change(SshChannel *c, int w, int h); - -#define CHANOPEN_RETURN_FAILURE(code, msgparams) do \ - { \ - ChanopenResult toret; \ - toret.outcome = CHANOPEN_RESULT_FAILURE; \ - toret.u.failure.reason_code = code; \ - toret.u.failure.wire_message = dupprintf msgparams; \ - return toret; \ - } while (0) - -#define CHANOPEN_RETURN_SUCCESS(chan) do \ - { \ - ChanopenResult toret; \ - toret.outcome = CHANOPEN_RESULT_SUCCESS; \ - toret.u.success.channel = chan; \ - return toret; \ - } while (0) - -#define CHANOPEN_RETURN_DOWNSTREAM(shctx) do \ - { \ - ChanopenResult toret; \ - toret.outcome = CHANOPEN_RESULT_DOWNSTREAM; \ - toret.u.downstream.share_ctx = shctx; \ - return toret; \ - } while (0) - -ChanopenResult ssh2_connection_parse_channel_open( - struct ssh2_connection_state *s, ptrlen type, - PktIn *pktin, SshChannel *sc); - -bool ssh2_connection_parse_global_request( - struct ssh2_connection_state *s, ptrlen type, PktIn *pktin); - -bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s); - -#endif /* PUTTY_SSH2CONNECTION_H */ diff --git a/ssh/crc-attack-detector.c b/ssh/crc-attack-detector.c deleted file mode 100644 index 58e4e99b4..000000000 --- a/ssh/crc-attack-detector.c +++ /dev/null @@ -1,171 +0,0 @@ -/* $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" - -/* SSH Constants */ -#define SSH_MAXBLOCKS (32 * 1024) -#define SSH_BLOCKSIZE (8) - -/* Hashing constants */ -#define HASH_MINSIZE (8 * 1024) -#define HASH_ENTRYSIZE (sizeof(uint16_t)) -#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)) - -static const uint8_t ONE[4] = { 1, 0, 0, 0 }; -static const uint8_t ZERO[4] = { 0, 0, 0, 0 }; - -struct crcda_ctx { - uint16_t *h; - uint32_t n; -}; - -struct crcda_ctx *crcda_make_context(void) -{ - struct crcda_ctx *ctx = snew(struct crcda_ctx); - ctx->h = NULL; - ctx->n = HASH_MINSIZE / HASH_ENTRYSIZE; - return ctx; -} - -void crcda_free_context(struct crcda_ctx *ctx) -{ - if (ctx) { - sfree(ctx->h); - ctx->h = NULL; - sfree(ctx); - } -} - -static void crc_update(uint32_t *a, const void *b) -{ - *a = crc32_update(*a, make_ptrlen(b, 4)); -} - -/* detect if a block is used in a particular pattern */ -static bool check_crc(const uint8_t *S, const uint8_t *buf, - uint32_t len, const uint8_t *IV) -{ - uint32_t crc; - const uint8_t *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 */ -bool detect_attack(struct crcda_ctx *ctx, - const unsigned char *buf, uint32_t len, - const unsigned char *IV) -{ - register uint32_t i, j; - uint32_t l; - register const uint8_t *c; - const uint8_t *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_t); - } else { - if (l > ctx->n) { - ctx->n = l; - ctx->h = sresize(ctx->h, ctx->n, uint16_t); - } - } - - 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 true; /* attack detected */ - else - break; - } - for (d = buf; d < c; d += SSH_BLOCKSIZE) { - if (!CMP(c, d)) { - if ((check_crc(c, buf, len, IV))) - return true; /* attack detected */ - else - break; - } - } - } - return false; /* 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) { - assert(IV); /* or we wouldn't have stored HASH_IV above */ - if (!CMP(c, IV)) { - if (check_crc(c, buf, len, IV)) - return true; /* attack detected */ - else - break; - } - } else if (!CMP(c, buf + ctx->h[i] * SSH_BLOCKSIZE)) { - if (check_crc(c, buf, len, IV)) - return true; /* attack detected */ - else - break; - } - } - ctx->h[i] = j; - } - return false; /* ok */ -} diff --git a/ssh/gss.h b/ssh/gss.h deleted file mode 100644 index c819d48b0..000000000 --- a/ssh/gss.h +++ /dev/null @@ -1,217 +0,0 @@ -#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_BAD_MIC, - SSH_GSS_NO_CREDS, - 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; - -#define GSS_NO_EXPIRATION ((time_t)-1) - -#define GSS_DEF_REKEY_MINS 2 /* Default minutes between GSS cache checks */ - -/* Functions, provided by either {windows,unix}/gss.c or gssc.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, time_t *expiry, - unsigned long *lifetime); - -/* - * 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 *, - time_t *expiry); - -/* - * 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); - -/* - * Validates an input MIC for some input data. - */ -typedef Ssh_gss_stat (*t_ssh_gss_verify_mic)(struct ssh_gss_library *lib, - Ssh_gss_ctx ctx, - Ssh_gss_buf *in_data, - Ssh_gss_buf *in_mic); - -/* - * 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_verify_mic verify_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; -}; - -/* - * State that has to be shared between all GSSAPI-using parts of the - * same SSH connection, in particular between GSS key exchange and the - * subsequent trivial userauth method that reuses its output. - */ -struct ssh_connection_shared_gss_state { - struct ssh_gss_liblist *libs; - struct ssh_gss_library *lib; - Ssh_gss_name srv_name; - Ssh_gss_ctx ctx; -}; - -#endif /* NO_GSSAPI */ - -#endif /*PUTTY_SSHGSS_H*/ diff --git a/ssh/gssc.c b/ssh/gssc.c deleted file mode 100644 index d10caf8b8..000000000 --- a/ssh/gssc.c +++ /dev/null @@ -1,288 +0,0 @@ -#include "putty.h" - -#include -#include -#include "gssc.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); - - 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, - time_t *expiry) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - gss_OID_set_desc k5only = { 1, GSS_MECH_KRB5 }; - gss_cred_id_t cred; - OM_uint32 dummy; - OM_uint32 time_rec; - gssapi_ssh_gss_ctx *gssctx = snew(gssapi_ssh_gss_ctx); - - gssctx->ctx = GSS_C_NO_CONTEXT; - gssctx->expiry = 0; - - gssctx->maj_stat = - gss->acquire_cred(&gssctx->min_stat, GSS_C_NO_NAME, GSS_C_INDEFINITE, - &k5only, GSS_C_INITIATE, &cred, - (gss_OID_set *)0, &time_rec); - - if (gssctx->maj_stat != GSS_S_COMPLETE) { - sfree(gssctx); - return SSH_GSS_FAILURE; - } - - /* - * When the credential lifetime is not yet available due to deferred - * processing, gss_acquire_cred should return a 0 lifetime which is - * distinct from GSS_C_INDEFINITE which signals a crential that never - * expires. However, not all implementations get this right, and with - * Kerberos, initiator credentials always expire at some point. So when - * lifetime is 0 or GSS_C_INDEFINITE we call gss_inquire_cred_by_mech() to - * complete deferred processing. - */ - if (time_rec == GSS_C_INDEFINITE || time_rec == 0) { - gssctx->maj_stat = - gss->inquire_cred_by_mech(&gssctx->min_stat, cred, - (gss_OID) GSS_MECH_KRB5, - NULL, - &time_rec, - NULL, - NULL); - } - (void) gss->release_cred(&dummy, &cred); - - if (gssctx->maj_stat != GSS_S_COMPLETE) { - sfree(gssctx); - return SSH_GSS_FAILURE; - } - - if (time_rec != GSS_C_INDEFINITE) - gssctx->expiry = time(NULL) + time_rec; - else - gssctx->expiry = GSS_NO_EXPIRATION; - - if (expiry) { - *expiry = gssctx->expiry; - } - - *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, - time_t *expiry, - unsigned long *lifetime) -{ - struct gssapi_functions *gss = &lib->u.gssapi; - gssapi_ssh_gss_ctx *gssctx = (gssapi_ssh_gss_ctx*) *ctx; - OM_uint32 ret_flags; - OM_uint32 lifetime_rec; - - 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, - &lifetime_rec); - - if (lifetime) { - if (lifetime_rec == GSS_C_INDEFINITE) - *lifetime = ULONG_MAX; - else - *lifetime = lifetime_rec; - } - if (expiry) { - if (lifetime_rec == GSS_C_INDEFINITE) - *expiry = GSS_NO_EXPIRATION; - else - *expiry = time(NULL) + lifetime_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); - *ctx = NULL; - - 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_verify_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->verify_mic(&(gssctx->min_stat), gssctx->ctx, buf, hash, NULL); -} - -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->verify_mic = ssh_gssapi_verify_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/ssh/gssc.h b/ssh/gssc.h deleted file mode 100644 index d1d99eb1d..000000000 --- a/ssh/gssc.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef PUTTY_SSHGSSC_H -#define PUTTY_SSHGSSC_H -#include "putty.h" -#ifndef NO_GSSAPI - -#include "pgssapi.h" -#include "gss.h" - -typedef struct gssapi_ssh_gss_ctx { - OM_uint32 maj_stat; - OM_uint32 min_stat; - gss_ctx_id_t ctx; - time_t expiry; -} 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/ssh/kex2-client.c b/ssh/kex2-client.c deleted file mode 100644 index d5425237c..000000000 --- a/ssh/kex2-client.c +++ /dev/null @@ -1,1062 +0,0 @@ -/* - * Client side of key exchange for the SSH-2 transport protocol (RFC 4253). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "sshcr.h" -#include "storage.h" -#include "transport2.h" -#include "mpint.h" - -/* - * Another copy of the symbol defined in mpunsafe.c. See the comment - * there. - */ -const int deliberate_symbol_clash = 12345; - -void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - PktIn *pktin; - PktOut *pktout; - - crBegin(s->crStateKex); - - if (s->kex_alg->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->out.cipher ? s->out.cipher->real_keybits : 0; - scbits = s->in.cipher ? s->in.cipher->real_keybits : 0; - 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 > s->kex_alg->hash->hlen * 8) - s->nbits = s->kex_alg->hash->hlen * 8; - - /* - * If we're doing Diffie-Hellman group exchange, start by - * requesting a group. - */ - if (dh_is_gex(s->kex_alg)) { - ppl_logevent("Doing Diffie-Hellman group exchange"); - s->ppl.bpp->pls->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 ((s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST_OLD); - put_uint32(pktout, s->pbits); - } else { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_REQUEST); - put_uint32(pktout, DH_MIN_SIZE); - put_uint32(pktout, s->pbits); - put_uint32(pktout, DH_MAX_SIZE); - } - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting Diffie-Hellman group, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - s->p = get_mp_ssh2(pktin); - s->g = get_mp_ssh2(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, - "Unable to parse Diffie-Hellman group packet"); - *aborted = true; - return; - } - s->dh_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; - - ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit " - "modulus and hash %s with a server-supplied group", - dh_modulus_bit_size(s->dh_ctx), - ssh_hash_alg(s->exhash)->text_name); - } else { - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP; - s->dh_ctx = dh_setup_group(s->kex_alg); - s->kex_init_value = SSH2_MSG_KEXDH_INIT; - s->kex_reply_value = SSH2_MSG_KEXDH_REPLY; - - ppl_logevent("Doing Diffie-Hellman key exchange using %d-bit " - "modulus and hash %s with standard group \"%s\"", - dh_modulus_bit_size(s->dh_ctx), - ssh_hash_alg(s->exhash)->text_name, - s->kex_alg->groupname); - } - - /* - * Now generate and send e for Diffie-Hellman. - */ - seat_set_busy_status(s->ppl.seat, BUSY_CPU); - s->e = dh_create_e(s->dh_ctx); - pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_init_value); - put_mp_ssh2(pktout, s->e); - pq_push(s->ppl.out_pq, pktout); - - seat_set_busy_status(s->ppl.seat, BUSY_WAITING); - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != s->kex_reply_value) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting Diffie-Hellman reply, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - seat_set_busy_status(s->ppl.seat, BUSY_CPU); - s->hostkeydata = get_string(pktin); - s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata); - s->f = get_mp_ssh2(pktin); - s->sigdata = get_string(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, - "Unable to parse Diffie-Hellman reply packet"); - *aborted = true; - return; - } - - { - const char *err = dh_validate_f(s->dh_ctx, s->f); - if (err) { - ssh_proto_error(s->ppl.ssh, "Diffie-Hellman reply failed " - "validation: %s", err); - *aborted = true; - return; - } - } - mp_int *K = dh_find_K(s->dh_ctx, s->f); - put_mp_ssh2(s->kex_shared_secret, K); - mp_free(K); - - /* We assume everything from now on will be quick, and it might - * involve user interaction. */ - seat_set_busy_status(s->ppl.seat, BUSY_NOT); - - put_stringpl(s->exhash, s->hostkeydata); - if (dh_is_gex(s->kex_alg)) { - if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) - put_uint32(s->exhash, DH_MIN_SIZE); - put_uint32(s->exhash, s->pbits); - if (!(s->ppl.remote_bugs & BUG_SSH2_OLDGEX)) - put_uint32(s->exhash, DH_MAX_SIZE); - put_mp_ssh2(s->exhash, s->p); - put_mp_ssh2(s->exhash, s->g); - } - put_mp_ssh2(s->exhash, s->e); - put_mp_ssh2(s->exhash, s->f); - - dh_cleanup(s->dh_ctx); - s->dh_ctx = NULL; - mp_free(s->f); s->f = NULL; - if (dh_is_gex(s->kex_alg)) { - mp_free(s->g); s->g = NULL; - mp_free(s->p); s->p = NULL; - } - } else if (s->kex_alg->main_type == KEXTYPE_ECDH) { - char *desc = ecdh_keyalg_description(s->kex_alg); - ppl_logevent("Doing %s, using hash %s", desc, - ssh_hash_alg(s->exhash)->text_name); - sfree(desc); - - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; - - s->ecdh_key = ecdh_key_new(s->kex_alg, false); - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT); - { - strbuf *pubpoint = strbuf_new(); - ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); - put_stringsb(pktout, pubpoint); - } - - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting ECDH reply, type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - - s->hostkeydata = get_string(pktin); - put_stringpl(s->exhash, s->hostkeydata); - s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata); - - { - strbuf *pubpoint = strbuf_new(); - ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); - put_string(s->exhash, pubpoint->u, pubpoint->len); - strbuf_free(pubpoint); - } - - { - ptrlen keydata = get_string(pktin); - put_stringpl(s->exhash, keydata); - bool ok = ecdh_key_getkey(s->ecdh_key, keydata, - BinarySink_UPCAST(s->kex_shared_secret)); - if (!get_err(pktin) && !ok) { - ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " - "point in ECDH reply"); - *aborted = true; - return; - } - } - - s->sigdata = get_string(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "Unable to parse ECDH reply packet"); - *aborted = true; - return; - } - - ecdh_key_free(s->ecdh_key); - s->ecdh_key = NULL; -#ifndef NO_GSSAPI - } else if (kex_is_gss(s->kex_alg)) { - ptrlen data; - - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_GSSKEX; - s->init_token_sent = false; - s->complete_rcvd = false; - s->hkey = NULL; - s->keystr = NULL; - - /* - * 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... - * - * This is rote from the KEXTYPE_DH section above. - */ - { - int csbits, scbits; - - csbits = s->out.cipher->real_keybits; - scbits = s->in.cipher->real_keybits; - 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 > s->kex_alg->hash->hlen * 8) - s->nbits = s->kex_alg->hash->hlen * 8; - - assert(!s->ecdh_key); - assert(!s->dh_ctx); - - if (s->kex_alg->main_type == KEXTYPE_GSS_ECDH) { - s->ecdh_key = ecdh_key_new(s->kex_alg, false); - - char *desc = ecdh_keyalg_description(s->kex_alg); - ppl_logevent("Doing GSSAPI (with Kerberos V5) %s with hash %s", - desc, ssh_hash_alg(s->exhash)->text_name); - sfree(desc); - } else if (dh_is_gex(s->kex_alg)) { - /* - * Work out how big a DH group we will need to allow that - * much data. - */ - s->pbits = 512 << ((s->nbits - 1) / 64); - ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman " - "group exchange, with minimum %d bits, and hash %s", - s->pbits, ssh_hash_alg(s->exhash)->text_name); - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXGSS_GROUPREQ); - put_uint32(pktout, s->pbits); /* min */ - put_uint32(pktout, s->pbits); /* preferred */ - put_uint32(pktout, s->pbits * 2); /* max */ - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV( - (pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEXGSS_GROUP) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting Diffie-Hellman group, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - s->p = get_mp_ssh2(pktin); - s->g = get_mp_ssh2(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, - "Unable to parse Diffie-Hellman group packet"); - *aborted = true; - return; - } - s->dh_ctx = dh_setup_gex(s->p, s->g); - } else { - s->dh_ctx = dh_setup_group(s->kex_alg); - ppl_logevent("Using GSSAPI (with Kerberos V5) Diffie-Hellman with" - " standard group \"%s\" and hash %s", - s->kex_alg->groupname, - ssh_hash_alg(s->exhash)->text_name); - } - - /* Now generate e for Diffie-Hellman. */ - seat_set_busy_status(s->ppl.seat, BUSY_CPU); - if (s->ecdh_key) { - s->ebuf = strbuf_new_nm(); - ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(s->ebuf)); - } else { - s->e = dh_create_e(s->dh_ctx); - } - - if (s->shgss->lib->gsslogmsg) - ppl_logevent("%s", s->shgss->lib->gsslogmsg); - - /* initial tokens are empty */ - SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); - SSH_GSS_CLEAR_BUF(&s->gss_sndtok); - SSH_GSS_CLEAR_BUF(&s->mic); - s->gss_stat = s->shgss->lib->acquire_cred( - s->shgss->lib, &s->shgss->ctx, &s->gss_cred_expiry); - if (s->gss_stat != SSH_GSS_OK) { - ssh_sw_abort(s->ppl.ssh, - "GSSAPI key exchange failed to initialise"); - *aborted = true; - return; - } - - /* now enter the loop */ - assert(s->shgss->srv_name); - do { - /* - * When acquire_cred yields no useful expiration, go with the - * service ticket expiration. - */ - s->gss_stat = s->shgss->lib->init_sec_context( - s->shgss->lib, &s->shgss->ctx, s->shgss->srv_name, - s->gss_delegate, &s->gss_rcvtok, &s->gss_sndtok, - (s->gss_cred_expiry == GSS_NO_EXPIRATION ? - &s->gss_cred_expiry : NULL), NULL); - SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); - - if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd) - break; /* MIC is verified after the loop */ - - if (s->gss_stat != SSH_GSS_S_COMPLETE && - s->gss_stat != SSH_GSS_S_CONTINUE_NEEDED) { - if (s->shgss->lib->display_status( - s->shgss->lib, s->shgss->ctx, - &s->gss_buf) == SSH_GSS_OK) { - char *err = s->gss_buf.value; - ssh_sw_abort(s->ppl.ssh, - "GSSAPI key exchange failed to initialise " - "context: %s", err); - sfree(err); - *aborted = true; - return; - } - } - assert(s->gss_stat == SSH_GSS_S_COMPLETE || - s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED); - - if (!s->init_token_sent) { - s->init_token_sent = true; - pktout = ssh_bpp_new_pktout(s->ppl.bpp, - SSH2_MSG_KEXGSS_INIT); - if (s->gss_sndtok.length == 0) { - ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange failed: " - "no initial context token"); - *aborted = true; - return; - } - put_string(pktout, - s->gss_sndtok.value, s->gss_sndtok.length); - if (s->ecdh_key) { - put_stringpl(pktout, ptrlen_from_strbuf(s->ebuf)); - } else { - put_mp_ssh2(pktout, s->e); - } - pq_push(s->ppl.out_pq, pktout); - s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); - ppl_logevent("GSSAPI key exchange initialised"); - } else if (s->gss_sndtok.length != 0) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_KEXGSS_CONTINUE); - put_string(pktout, - s->gss_sndtok.value, s->gss_sndtok.length); - pq_push(s->ppl.out_pq, pktout); - s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); - } - - if (s->gss_stat == SSH_GSS_S_COMPLETE && s->complete_rcvd) - break; - - wait_for_gss_token: - crMaybeWaitUntilV( - (pktin = ssh2_transport_pop(s)) != NULL); - switch (pktin->type) { - case SSH2_MSG_KEXGSS_CONTINUE: - data = get_string(pktin); - s->gss_rcvtok.value = (char *)data.ptr; - s->gss_rcvtok.length = data.len; - continue; - case SSH2_MSG_KEXGSS_COMPLETE: - s->complete_rcvd = true; - if (s->ecdh_key) { - s->fbuf = strbuf_dup_nm(get_string(pktin)); - } else { - s->f = get_mp_ssh2(pktin); - } - data = get_string(pktin); - s->mic.value = (char *)data.ptr; - s->mic.length = data.len; - /* If there's a final token we loop to consume it */ - if (get_bool(pktin)) { - data = get_string(pktin); - s->gss_rcvtok.value = (char *)data.ptr; - s->gss_rcvtok.length = data.len; - continue; - } - break; - case SSH2_MSG_KEXGSS_HOSTKEY: - s->hostkeydata = get_string(pktin); - if (s->hostkey_alg) { - s->hkey = ssh_key_new_pub(s->hostkey_alg, - s->hostkeydata); - put_stringpl(s->exhash, s->hostkeydata); - } - /* - * Can't loop as we have no token to pass to - * init_sec_context. - */ - goto wait_for_gss_token; - case SSH2_MSG_KEXGSS_ERROR: - /* - * We have no use for the server's major and minor - * status. The minor status is really only - * meaningful to the server, and with luck the major - * status means something to us (but not really all - * that much). The string is more meaningful, and - * hopefully the server sends any error tokens, as - * that will produce the most useful information for - * us. - */ - get_uint32(pktin); /* server's major status */ - get_uint32(pktin); /* server's minor status */ - data = get_string(pktin); - ppl_logevent("GSSAPI key exchange failed; " - "server's message: %.*s", PTRLEN_PRINTF(data)); - /* Language tag, but we have no use for it */ - get_string(pktin); - /* - * Wait for an error token, if there is one, or the - * server's disconnect. The error token, if there - * is one, must follow the SSH2_MSG_KEXGSS_ERROR - * message, per the RFC. - */ - goto wait_for_gss_token; - default: - ssh_proto_error(s->ppl.ssh, "Received unexpected packet " - "during GSSAPI key exchange, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - } while (s->gss_rcvtok.length || - s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED || - !s->complete_rcvd); - - if (s->ecdh_key) { - bool ok = ecdh_key_getkey(s->ecdh_key, ptrlen_from_strbuf(s->fbuf), - BinarySink_UPCAST(s->kex_shared_secret)); - if (!ok) { - ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " - "point in GSSAPI ECDH reply"); - *aborted = true; - return; - } - } else { - const char *err = dh_validate_f(s->dh_ctx, s->f); - if (err) { - ssh_proto_error(s->ppl.ssh, "GSSAPI reply failed " - "validation: %s", err); - *aborted = true; - return; - } - mp_int *K = dh_find_K(s->dh_ctx, s->f); - put_mp_ssh2(s->kex_shared_secret, K); - mp_free(K); - } - - /* We assume everything from now on will be quick, and it might - * involve user interaction. */ - seat_set_busy_status(s->ppl.seat, BUSY_NOT); - - if (!s->hkey) - put_stringz(s->exhash, ""); - - if (s->ecdh_key) { - put_stringpl(s->exhash, ptrlen_from_strbuf(s->ebuf)); - put_stringpl(s->exhash, ptrlen_from_strbuf(s->fbuf)); - } else { - if (dh_is_gex(s->kex_alg)) { - /* min, preferred, max */ - put_uint32(s->exhash, s->pbits); - put_uint32(s->exhash, s->pbits); - put_uint32(s->exhash, s->pbits * 2); - - put_mp_ssh2(s->exhash, s->p); - put_mp_ssh2(s->exhash, s->g); - } - put_mp_ssh2(s->exhash, s->e); - put_mp_ssh2(s->exhash, s->f); - } - - /* - * MIC verification is done below, after we compute the hash - * used as the MIC input. - */ - - if (s->ecdh_key) { - ecdh_key_free(s->ecdh_key); - s->ecdh_key = NULL; - strbuf_free(s->ebuf); s->ebuf = NULL; - strbuf_free(s->fbuf); s->fbuf = NULL; - } else { - dh_cleanup(s->dh_ctx); - s->dh_ctx = NULL; - mp_free(s->f); s->f = NULL; - if (dh_is_gex(s->kex_alg)) { - mp_free(s->g); s->g = NULL; - mp_free(s->p); s->p = NULL; - } - } -#endif - } else { - ptrlen rsakeydata; - - assert(s->kex_alg->main_type == KEXTYPE_RSA); - ppl_logevent("Doing RSA key exchange with hash %s", - ssh_hash_alg(s->exhash)->text_name); - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX; - /* - * RSA key exchange. First expect a KEXRSA_PUBKEY packet - * from the server. - */ - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting RSA public key, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - - s->hostkeydata = get_string(pktin); - put_stringpl(s->exhash, s->hostkeydata); - s->hkey = ssh_key_new_pub(s->hostkey_alg, s->hostkeydata); - - rsakeydata = get_string(pktin); - - s->rsa_kex_key = ssh_rsakex_newkey(rsakeydata); - if (!s->rsa_kex_key) { - ssh_proto_error(s->ppl.ssh, - "Unable to parse RSA public key packet"); - *aborted = true; - return; - } - s->rsa_kex_key_needs_freeing = true; - - put_stringpl(s->exhash, rsakeydata); - - /* - * 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->rsa_kex_key); - - const struct ssh_rsa_kex_extra *extra = - (const struct ssh_rsa_kex_extra *)s->kex_alg->extra; - if (klen < extra->minklen) { - ssh_proto_error(s->ppl.ssh, "Server sent %d-bit RSA key, " - "less than the minimum size %d for %s " - "key exchange", klen, extra->minklen, - s->kex_alg->name); - *aborted = true; - return; - } - - int nbits = klen - (2*s->kex_alg->hash->hlen*8 + 49); - assert(nbits > 0); - - strbuf *buf, *outstr; - - mp_int *tmp = mp_random_bits(nbits - 1); - mp_int *K = mp_power_2(nbits - 1); - mp_add_into(K, K, tmp); - mp_free(tmp); - - /* - * Encode this as an mpint. - */ - buf = strbuf_new_nm(); - put_mp_ssh2(buf, K); - - /* - * Store a copy as the output shared secret from the kex. - */ - put_mp_ssh2(s->kex_shared_secret, K); - mp_free(K); - - /* - * Encrypt it with the given RSA key. - */ - outstr = ssh_rsakex_encrypt(s->rsa_kex_key, s->kex_alg->hash, - ptrlen_from_strbuf(buf)); - - /* - * And send it off in a return packet. - */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_SECRET); - put_stringpl(pktout, ptrlen_from_strbuf(outstr)); - pq_push(s->ppl.out_pq, pktout); - - put_stringsb(s->exhash, outstr); /* frees outstr */ - - strbuf_free(buf); - } - - ssh_rsakex_freekey(s->rsa_kex_key); - s->rsa_kex_key = NULL; - s->rsa_kex_key_needs_freeing = false; - - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEXRSA_DONE) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting RSA kex signature, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - - s->sigdata = get_string(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "Unable to parse RSA kex signature"); - *aborted = true; - return; - } - } - - ssh2transport_finalise_exhash(s); - -#ifndef NO_GSSAPI - if (kex_is_gss(s->kex_alg)) { - Ssh_gss_buf gss_buf; - SSH_GSS_CLEAR_BUF(&s->gss_buf); - - gss_buf.value = s->exchange_hash; - gss_buf.length = s->kex_alg->hash->hlen; - s->gss_stat = s->shgss->lib->verify_mic( - s->shgss->lib, s->shgss->ctx, &gss_buf, &s->mic); - if (s->gss_stat != SSH_GSS_OK) { - if (s->shgss->lib->display_status( - s->shgss->lib, s->shgss->ctx, &s->gss_buf) == SSH_GSS_OK) { - char *err = s->gss_buf.value; - ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was " - "not valid: %s", err); - sfree(err); - } else { - ssh_sw_abort(s->ppl.ssh, "GSSAPI key exchange MIC was " - "not valid"); - } - *aborted = true; - return; - } - - s->gss_kex_used = true; - - /*- - * If this the first KEX, save the GSS context for "gssapi-keyex" - * authentication. - * - * https://www.rfc-editor.org/rfc/rfc4462#section-4 - * - * This method may be used only if the initial key exchange was - * performed using a GSS-API-based key exchange method defined in - * accordance with Section 2. The GSS-API context used with this - * method is always that established during an initial GSS-API-based - * key exchange. Any context established during key exchange for the - * purpose of rekeying MUST NOT be used with this method. - */ - if (s->got_session_id) { - s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); - } - ppl_logevent("GSSAPI Key Exchange complete!"); - } -#endif - - s->dh_ctx = NULL; - - /* In GSS keyex there's no hostkey signature to verify */ - if (!kex_is_gss(s->kex_alg)) { - if (!s->hkey) { - ssh_proto_error(s->ppl.ssh, "Server's host key is invalid"); - *aborted = true; - return; - } - - if (!ssh_key_verify( - s->hkey, s->sigdata, - make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen))) { -#ifndef FUZZING - ssh_proto_error(s->ppl.ssh, "Signature from server's host key " - "is invalid"); - *aborted = true; - return; -#endif - } - } - - s->keystr = s->hkey ? ssh_key_cache_str(s->hkey) : NULL; -#ifndef NO_GSSAPI - if (s->gss_kex_used) { - /* - * In a GSS-based session, check the host key (if any) against - * the transient host key cache. - */ - if (kex_is_gss(s->kex_alg)) { - - /* - * We've just done a GSS key exchange. If it gave us a - * host key, store it. - */ - if (s->hkey) { - char *fingerprint = ssh2_double_fingerprint( - s->hkey, SSH_FPTYPE_DEFAULT); - ppl_logevent("GSS kex provided fallback host key:"); - ppl_logevent("%s", fingerprint); - sfree(fingerprint); - - ssh_transient_hostkey_cache_add(s->thc, s->hkey); - } else if (!ssh_transient_hostkey_cache_non_empty(s->thc)) { - /* - * But if it didn't, then we currently have no - * fallback host key to use in subsequent non-GSS - * rekeys. So we should immediately trigger a non-GSS - * rekey of our own, to set one up, before the session - * keys have been used for anything else. - * - * This is similar to the cross-certification done at - * user request in the permanent host key cache, but - * here we do it automatically, once, at session - * startup, and only add the key to the transient - * cache. - */ - if (s->hostkey_alg) { - s->need_gss_transient_hostkey = true; - } else { - /* - * If we negotiated the "null" host key algorithm - * in the key exchange, that's an indication that - * no host key at all is available from the server - * (both because we listed "null" last, and - * because RFC 4462 section 5 says that a server - * MUST NOT offer "null" as a host key algorithm - * unless that is the only algorithm it provides - * at all). - * - * In that case we actually _can't_ perform a - * non-GSSAPI key exchange, so it's pointless to - * attempt one proactively. This is also likely to - * cause trouble later if a rekey is required at a - * moment whne GSS credentials are not available, - * but someone setting up a server in this - * configuration presumably accepts that as a - * consequence. - */ - if (!s->warned_about_no_gss_transient_hostkey) { - ppl_logevent("No fallback host key available"); - s->warned_about_no_gss_transient_hostkey = true; - } - } - } - } else { - /* - * We've just done a fallback key exchange, so make - * sure the host key it used is in the cache of keys - * we previously received in GSS kexes. - * - * An exception is if this was the non-GSS key exchange we - * triggered on purpose to populate the transient cache. - */ - assert(s->hkey); /* only KEXTYPE_GSS* lets this be null */ - char *fingerprint = ssh2_double_fingerprint( - s->hkey, SSH_FPTYPE_DEFAULT); - - if (s->need_gss_transient_hostkey) { - ppl_logevent("Post-GSS rekey provided fallback host key:"); - ppl_logevent("%s", fingerprint); - ssh_transient_hostkey_cache_add(s->thc, s->hkey); - s->need_gss_transient_hostkey = false; - } else if (!ssh_transient_hostkey_cache_verify(s->thc, s->hkey)) { - ppl_logevent("Non-GSS rekey after initial GSS kex " - "used host key:"); - ppl_logevent("%s", fingerprint); - sfree(fingerprint); - ssh_sw_abort(s->ppl.ssh, "Server's host key did not match any " - "used in previous GSS kex"); - *aborted = true; - return; - } - - sfree(fingerprint); - } - } else -#endif /* NO_GSSAPI */ - if (!s->got_session_id) { - /* - * Make a note of any other host key formats that are available. - */ - { - int i, j, nkeys = 0; - char *list = NULL; - for (i = 0; i < lenof(ssh2_hostkey_algs); i++) { - if (ssh2_hostkey_algs[i].alg == s->hostkey_alg) - continue; - - for (j = 0; j < s->n_uncert_hostkeys; j++) - if (s->uncert_hostkeys[j] == i) - break; - - if (j < s->n_uncert_hostkeys) { - char *newlist; - if (list) - newlist = dupprintf( - "%s/%s", list, - ssh2_hostkey_algs[i].alg->ssh_id); - else - newlist = dupprintf( - "%s", ssh2_hostkey_algs[i].alg->ssh_id); - sfree(list); - list = newlist; - nkeys++; - } - } - if (list) { - ppl_logevent("Server also has %s host key%s, but we " - "don't know %s", list, - nkeys > 1 ? "s" : "", - nkeys > 1 ? "any of them" : "it"); - sfree(list); - } - } - - ssh2_userkey uk = { .key = s->hkey, .comment = NULL }; - char **fingerprints = ssh2_all_fingerprints(s->hkey); - - FingerprintType fptype_default = - ssh2_pick_default_fingerprint(fingerprints); - ppl_logevent("Host key fingerprint is:"); - ppl_logevent("%s", fingerprints[fptype_default]); - - /* - * Authenticate remote host: verify host key, either by - * certification or by the local host key cache. - * - * (We've already checked the signature of the exchange - * hash.) - */ - if (ssh_key_alg(s->hkey)->is_certificate) { - char *base_fp = ssh2_fingerprint( - s->hkey, ssh_fptype_to_cert(fptype_default)); - ppl_logevent("Host key is a certificate. " - "Hash including certificate:"); - ppl_logevent("%s", base_fp); - sfree(base_fp); - - strbuf *id_string = strbuf_new(); - StripCtrlChars *id_string_scc = stripctrl_new( - BinarySink_UPCAST(id_string), false, L'\0'); - ssh_key_cert_id_string( - s->hkey, BinarySink_UPCAST(id_string_scc)); - stripctrl_free(id_string_scc); - ppl_logevent("Certificate ID string is \"%s\"", id_string->s); - strbuf_free(id_string); - - strbuf *ca_pub = strbuf_new(); - ssh_key_ca_public_blob(s->hkey, BinarySink_UPCAST(ca_pub)); - host_ca hca_search = { .ca_public_key = ca_pub }; - host_ca *hca_found = find234(s->host_cas, &hca_search, NULL); - - char *ca_fp = ssh2_fingerprint_blob(ptrlen_from_strbuf(ca_pub), - fptype_default); - ppl_logevent("Fingerprint of certification authority:"); - ppl_logevent("%s", ca_fp); - sfree(ca_fp); - - strbuf_free(ca_pub); - - strbuf *error = strbuf_new(); - bool cert_ok = false; - - if (!hca_found) { - put_fmt(error, "Certification authority is not trusted"); - } else { - ppl_logevent("Certification authority matches '%s'", - hca_found->name); - cert_ok = ssh_key_check_cert( - s->hkey, - true, /* host certificate */ - ptrlen_from_asciz(s->savedhost), - time(NULL), - &hca_found->opts, - BinarySink_UPCAST(error)); - } - if (cert_ok) { - strbuf_free(error); - ssh2_free_all_fingerprints(fingerprints); - ppl_logevent("Accepted certificate"); - goto host_key_ok; - } else { - ppl_logevent("Rejected host key certificate: %s", - error->s); - strbuf_free(error); - /* now fall through into normal host key checking */ - } - } - - { - char *keydisp = ssh2_pubkey_openssh_str(&uk); - - int ca_count = ssh_key_alg(s->hkey)->is_certificate ? - count234(s->host_cas) : 0; - - s->spr = verify_ssh_host_key( - ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, - s->hkey, ssh_key_cache_id(s->hkey), s->keystr, keydisp, - fingerprints, ca_count, ssh2_transport_dialog_callback, s); - - ssh2_free_all_fingerprints(fingerprints); - sfree(keydisp); -#ifdef FUZZING - s->spr = SPR_OK; -#endif - crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); - if (spr_is_abort(s->spr)) { - *aborted = true; - ssh_spr_close(s->ppl.ssh, s->spr, "host key verification"); - return; - } - - if (ssh_key_alg(s->hkey)->is_certificate) { - /* - * Explain what's going on in the Event Log: if we - * got here by way of a certified key whose - * certificate we didn't like, then we should - * explain why we chose to continue with the - * connection anyway! - */ - ppl_logevent("Accepting certified host key anyway based " - "on cache"); - } - } - - host_key_ok: - - /* - * Save this host key, to check against the one presented in - * subsequent rekeys. - */ - strbuf_clear(s->hostkeyblob); - ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob)); - } else if (s->cross_certifying) { - assert(s->hkey); - assert(ssh_key_alg(s->hkey) == s->cross_certifying); - - char *fingerprint = ssh2_double_fingerprint( - s->hkey, SSH_FPTYPE_DEFAULT); - ppl_logevent("Storing additional host key for this host:"); - ppl_logevent("%s", fingerprint); - sfree(fingerprint); - - store_host_key(s->ppl.seat, s->savedhost, s->savedport, - ssh_key_cache_id(s->hkey), s->keystr); - /* - * Don't forget to store the new key as the one we'll be - * re-checking in future normal rekeys. - */ - strbuf_clear(s->hostkeyblob); - ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob)); - } 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. - */ - strbuf *thisblob = strbuf_new(); - ssh_key_public_blob(s->hkey, BinarySink_UPCAST(thisblob)); - bool match = ptrlen_eq_ptrlen(ptrlen_from_strbuf(thisblob), - ptrlen_from_strbuf(s->hostkeyblob)); - strbuf_free(thisblob); - if (!match) { -#ifndef FUZZING - ssh_sw_abort(s->ppl.ssh, - "Host key was different in repeat key exchange"); - *aborted = true; - return; -#endif - } - } - - sfree(s->keystr); - s->keystr = NULL; - if (s->hkey) { - ssh_key_free(s->hkey); - s->hkey = NULL; - } - - crFinishV; -} diff --git a/ssh/kex2-server.c b/ssh/kex2-server.c deleted file mode 100644 index 570d77502..000000000 --- a/ssh/kex2-server.c +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Server side of key exchange for the SSH-2 transport protocol (RFC 4253). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "sshcr.h" -#include "server.h" -#include "sshkeygen.h" -#include "storage.h" -#include "transport2.h" -#include "mpint.h" - -void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ppl, - ssh_key *const *hostkeys, int nhostkeys) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - s->hostkeys = hostkeys; - s->nhostkeys = nhostkeys; -} - -static strbuf *finalise_and_sign_exhash(struct ssh2_transport_state *s) -{ - strbuf *sb; - ssh2transport_finalise_exhash(s); - sb = strbuf_new(); - ssh_key_sign( - s->hkey, make_ptrlen(s->exchange_hash, s->kex_alg->hash->hlen), - s->hkflags, BinarySink_UPCAST(sb)); - return sb; -} - -void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - PktIn *pktin; - PktOut *pktout; - - crBegin(s->crStateKex); - - { - int i; - for (i = 0; i < s->nhostkeys; i++) - if (ssh_key_alg(s->hostkeys[i]) == s->hostkey_alg) { - s->hkey = s->hostkeys[i]; - break; - } - assert(s->hkey); - } - - strbuf_clear(s->hostkeyblob); - ssh_key_public_blob(s->hkey, BinarySink_UPCAST(s->hostkeyblob)); - s->hostkeydata = ptrlen_from_strbuf(s->hostkeyblob); - - put_stringpl(s->exhash, s->hostkeydata); - - if (s->kex_alg->main_type == KEXTYPE_DH) { - /* - * If we're doing Diffie-Hellman group exchange, start by - * waiting for the group request. - */ - if (dh_is_gex(s->kex_alg)) { - ppl_logevent("Doing Diffie-Hellman group exchange"); - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGEX; - - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST && - pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting Diffie-Hellman group exchange " - "request, type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - - if (pktin->type != SSH2_MSG_KEX_DH_GEX_REQUEST_OLD) { - s->dh_got_size_bounds = true; - s->dh_min_size = get_uint32(pktin); - s->pbits = get_uint32(pktin); - s->dh_max_size = get_uint32(pktin); - } else { - s->dh_got_size_bounds = false; - s->pbits = get_uint32(pktin); - } - - /* - * This is a hopeless strategy for making a secure DH - * group! It's good enough for testing a client against, - * but not for serious use. - */ - PrimeGenerationContext *pgc = primegen_new_context( - &primegen_probabilistic); - ProgressReceiver null_progress; - null_progress.vt = &null_progress_vt; - s->p = primegen_generate(pgc, pcs_new(s->pbits), &null_progress); - primegen_free_context(pgc); - - s->g = mp_from_integer(2); - s->dh_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; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_DH_GEX_GROUP); - put_mp_ssh2(pktout, s->p); - put_mp_ssh2(pktout, s->g); - pq_push(s->ppl.out_pq, pktout); - } else { - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_DHGROUP; - s->dh_ctx = dh_setup_group(s->kex_alg); - s->kex_init_value = SSH2_MSG_KEXDH_INIT; - s->kex_reply_value = SSH2_MSG_KEXDH_REPLY; - ppl_logevent("Using Diffie-Hellman with standard group \"%s\"", - s->kex_alg->groupname); - } - - ppl_logevent("Doing Diffie-Hellman key exchange with hash %s", - ssh_hash_alg(s->exhash)->text_name); - - /* - * Generate e for Diffie-Hellman. - */ - s->e = dh_create_e(s->dh_ctx); - - /* - * Wait to receive f. - */ - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != s->kex_init_value) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting Diffie-Hellman initial packet, " - "type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - s->f = get_mp_ssh2(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, - "Unable to parse Diffie-Hellman initial packet"); - *aborted = true; - return; - } - - { - const char *err = dh_validate_f(s->dh_ctx, s->f); - if (err) { - ssh_proto_error(s->ppl.ssh, "Diffie-Hellman initial packet " - "failed validation: %s", err); - *aborted = true; - return; - } - } - mp_int *K = dh_find_K(s->dh_ctx, s->f); - put_mp_ssh2(s->kex_shared_secret, K); - mp_free(K); - - if (dh_is_gex(s->kex_alg)) { - if (s->dh_got_size_bounds) - put_uint32(s->exhash, s->dh_min_size); - put_uint32(s->exhash, s->pbits); - if (s->dh_got_size_bounds) - put_uint32(s->exhash, s->dh_max_size); - put_mp_ssh2(s->exhash, s->p); - put_mp_ssh2(s->exhash, s->g); - } - put_mp_ssh2(s->exhash, s->f); - put_mp_ssh2(s->exhash, s->e); - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, s->kex_reply_value); - put_stringpl(pktout, s->hostkeydata); - put_mp_ssh2(pktout, s->e); - put_stringsb(pktout, finalise_and_sign_exhash(s)); - pq_push(s->ppl.out_pq, pktout); - - dh_cleanup(s->dh_ctx); - s->dh_ctx = NULL; - mp_free(s->f); s->f = NULL; - if (dh_is_gex(s->kex_alg)) { - mp_free(s->g); s->g = NULL; - mp_free(s->p); s->p = NULL; - } - } else if (s->kex_alg->main_type == KEXTYPE_ECDH) { - char *desc = ecdh_keyalg_description(s->kex_alg); - ppl_logevent("Doing %s, using hash %s", desc, - ssh_hash_alg(s->exhash)->text_name); - sfree(desc); - - s->ecdh_key = ecdh_key_new(s->kex_alg, true); - if (!s->ecdh_key) { - ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH"); - *aborted = true; - return; - } - - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEX_ECDH_INIT) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting ECDH initial packet, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - - { - ptrlen keydata = get_string(pktin); - put_stringpl(s->exhash, keydata); - - bool ok = ecdh_key_getkey(s->ecdh_key, keydata, - BinarySink_UPCAST(s->kex_shared_secret)); - if (!get_err(pktin) && !ok) { - ssh_proto_error(s->ppl.ssh, "Received invalid elliptic curve " - "point in ECDH initial packet"); - *aborted = true; - return; - } - } - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_REPLY); - put_stringpl(pktout, s->hostkeydata); - { - strbuf *pubpoint = strbuf_new(); - ecdh_key_getpublic(s->ecdh_key, BinarySink_UPCAST(pubpoint)); - put_string(s->exhash, pubpoint->u, pubpoint->len); - put_stringsb(pktout, pubpoint); - } - put_stringsb(pktout, finalise_and_sign_exhash(s)); - pq_push(s->ppl.out_pq, pktout); - - ecdh_key_free(s->ecdh_key); - s->ecdh_key = NULL; - } else if (s->kex_alg->main_type == KEXTYPE_GSS) { - ssh_sw_abort(s->ppl.ssh, "GSS key exchange not supported in server"); - } else { - assert(s->kex_alg->main_type == KEXTYPE_RSA); - ppl_logevent("Doing RSA key exchange with hash %s", - ssh_hash_alg(s->exhash)->text_name); - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_RSAKEX; - - const struct ssh_rsa_kex_extra *extra = - (const struct ssh_rsa_kex_extra *)s->kex_alg->extra; - - if (s->ssc && s->ssc->rsa_kex_key) { - int klen = ssh_rsakex_klen(s->ssc->rsa_kex_key); - if (klen >= extra->minklen) { - ppl_logevent("Using configured %d-bit RSA key", klen); - s->rsa_kex_key = s->ssc->rsa_kex_key; - } else { - ppl_logevent("Configured %d-bit RSA key is too short (min %d)", - klen, extra->minklen); - } - } - - if (!s->rsa_kex_key) { - ppl_logevent("Generating a %d-bit RSA key", extra->minklen); - - s->rsa_kex_key = snew(RSAKey); - - PrimeGenerationContext *pgc = primegen_new_context( - &primegen_probabilistic); - ProgressReceiver null_progress; - null_progress.vt = &null_progress_vt; - rsa_generate(s->rsa_kex_key, extra->minklen, false, - pgc, &null_progress); - primegen_free_context(pgc); - - s->rsa_kex_key->comment = NULL; - s->rsa_kex_key_needs_freeing = true; - } - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_PUBKEY); - put_stringpl(pktout, s->hostkeydata); - { - strbuf *pubblob = strbuf_new(); - ssh_key_public_blob(&s->rsa_kex_key->sshk, - BinarySink_UPCAST(pubblob)); - put_string(s->exhash, pubblob->u, pubblob->len); - put_stringsb(pktout, pubblob); - } - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEXRSA_SECRET) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting RSA kex secret, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - *aborted = true; - return; - } - - mp_int *K; - { - ptrlen encrypted_secret = get_string(pktin); - put_stringpl(s->exhash, encrypted_secret); - K = ssh_rsakex_decrypt( - s->rsa_kex_key, s->kex_alg->hash, encrypted_secret); - } - - if (!K) { - ssh_proto_error(s->ppl.ssh, "Unable to decrypt RSA kex secret"); - *aborted = true; - return; - } - - put_mp_ssh2(s->kex_shared_secret, K); - mp_free(K); - - if (s->rsa_kex_key_needs_freeing) { - ssh_rsakex_freekey(s->rsa_kex_key); - sfree(s->rsa_kex_key); - } - s->rsa_kex_key = NULL; - s->rsa_kex_key_needs_freeing = false; - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXRSA_DONE); - put_stringsb(pktout, finalise_and_sign_exhash(s)); - pq_push(s->ppl.out_pq, pktout); - } - - crFinishV; -} diff --git a/ssh/login1-server.c b/ssh/login1-server.c deleted file mode 100644 index 661a4a174..000000000 --- a/ssh/login1-server.c +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Packet protocol layer for the SSH-1 login phase, from the server side. - */ - -#include - -#include "putty.h" -#include "mpint.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "sshcr.h" -#include "server.h" -#include "sshkeygen.h" - -struct ssh1_login_server_state { - int crState; - - PacketProtocolLayer *successor_layer; - - const SshServerConfig *ssc; - - int remote_protoflags; - int local_protoflags; - unsigned long supported_ciphers_mask, supported_auths_mask; - unsigned cipher_type; - - unsigned char cookie[8]; - unsigned char session_key[32]; - unsigned char session_id[16]; - char *username_str; - ptrlen username; - - RSAKey *servkey, *hostkey; - bool servkey_generated_here; - mp_int *sesskey; - - AuthPolicy *authpolicy; - unsigned ap_methods, current_method; - unsigned char auth_rsa_expected_response[16]; - RSAKey *authkey; - - PacketProtocolLayer ppl; -}; - -static void ssh1_login_server_free(PacketProtocolLayer *); -static void ssh1_login_server_process_queue(PacketProtocolLayer *); - -static bool ssh1_login_server_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, - void *ctx) { return false; } -static void ssh1_login_server_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) {} -static void ssh1_login_server_reconfigure( - PacketProtocolLayer *ppl, Conf *conf) {} - -static const PacketProtocolLayerVtable ssh1_login_server_vtable = { - .free = ssh1_login_server_free, - .process_queue = ssh1_login_server_process_queue, - .get_specials = ssh1_login_server_get_specials, - .special_cmd = ssh1_login_server_special_cmd, - .reconfigure = ssh1_login_server_reconfigure, - .queued_data_size = ssh_ppl_default_queued_data_size, - .final_output = ssh_ppl_default_final_output, - .name = NULL, /* no layer names in SSH-1 */ -}; - -PacketProtocolLayer *ssh1_login_server_new( - PacketProtocolLayer *successor_layer, RSAKey *hostkey, - AuthPolicy *authpolicy, const SshServerConfig *ssc) -{ - struct ssh1_login_server_state *s = snew(struct ssh1_login_server_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh1_login_server_vtable; - - s->ssc = ssc; - s->hostkey = hostkey; - s->authpolicy = authpolicy; - - s->successor_layer = successor_layer; - return &s->ppl; -} - -static void ssh1_login_server_free(PacketProtocolLayer *ppl) -{ - struct ssh1_login_server_state *s = - container_of(ppl, struct ssh1_login_server_state, ppl); - - if (s->successor_layer) - ssh_ppl_free(s->successor_layer); - - if (s->servkey_generated_here && s->servkey) { - freersakey(s->servkey); - sfree(s->servkey); - } - - smemclr(s->session_key, sizeof(s->session_key)); - sfree(s->username_str); - - sfree(s); -} - -static bool ssh1_login_server_filter_queue(struct ssh1_login_server_state *s) -{ - return ssh1_common_filter_queue(&s->ppl); -} - -static PktIn *ssh1_login_server_pop(struct ssh1_login_server_state *s) -{ - if (ssh1_login_server_filter_queue(s)) - return NULL; - return pq_pop(s->ppl.in_pq); -} - -static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh1_login_server_state *s = - container_of(ppl, struct ssh1_login_server_state, ppl); - PktIn *pktin; - PktOut *pktout; - int i; - - /* Filter centrally handled messages off the front of the queue on - * every entry to this coroutine, no matter where we're resuming - * from, even if we're _not_ looping on pq_pop. That way we can - * still proactively handle those messages even if we're waiting - * for a user response. */ - if (ssh1_login_server_filter_queue(s)) - return; - - crBegin(s->crState); - - if (!s->servkey) { - int server_key_bits = s->hostkey->bytes - 256; - if (server_key_bits < 512) - server_key_bits = s->hostkey->bytes + 256; - s->servkey = snew(RSAKey); - - PrimeGenerationContext *pgc = primegen_new_context( - &primegen_probabilistic); - ProgressReceiver null_progress; - null_progress.vt = &null_progress_vt; - rsa_generate(s->servkey, server_key_bits, false, pgc, &null_progress); - primegen_free_context(pgc); - - s->servkey->comment = NULL; - s->servkey_generated_here = true; - } - - s->local_protoflags = SSH1_PROTOFLAGS_SUPPORTED; - s->supported_ciphers_mask = s->ssc->ssh1_cipher_mask; - s->supported_auths_mask = 0; - s->ap_methods = auth_methods(s->authpolicy); - if (s->ap_methods & AUTHMETHOD_PASSWORD) - s->supported_auths_mask |= (1U << SSH1_AUTH_PASSWORD); - if (s->ap_methods & AUTHMETHOD_PUBLICKEY) - s->supported_auths_mask |= (1U << SSH1_AUTH_RSA); - if (s->ap_methods & AUTHMETHOD_TIS) - s->supported_auths_mask |= (1U << SSH1_AUTH_TIS); - if (s->ap_methods & AUTHMETHOD_CRYPTOCARD) - s->supported_auths_mask |= (1U << SSH1_AUTH_CCARD); - - random_read(s->cookie, 8); - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_PUBLIC_KEY); - put_data(pktout, s->cookie, 8); - rsa_ssh1_public_blob(BinarySink_UPCAST(pktout), - s->servkey, RSA_SSH1_EXPONENT_FIRST); - rsa_ssh1_public_blob(BinarySink_UPCAST(pktout), - s->hostkey, RSA_SSH1_EXPONENT_FIRST); - put_uint32(pktout, s->local_protoflags); - put_uint32(pktout, s->supported_ciphers_mask); - put_uint32(pktout, s->supported_auths_mask); - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); - if (pktin->type != SSH1_CMSG_SESSION_KEY) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet in response" - " to initial public key packet, type %d (%s)", - pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - - { - ptrlen client_cookie; - s->cipher_type = get_byte(pktin); - client_cookie = get_data(pktin, 8); - s->sesskey = get_mp_ssh1(pktin); - s->remote_protoflags = get_uint32(pktin); - - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "Unable to parse session key packet"); - return; - } - if (!ptrlen_eq_ptrlen(client_cookie, make_ptrlen(s->cookie, 8))) { - ssh_proto_error(s->ppl.ssh, - "Client sent incorrect anti-spoofing cookie"); - return; - } - } - if (s->cipher_type >= 32 || - !((s->supported_ciphers_mask >> s->cipher_type) & 1)) { - ssh_proto_error(s->ppl.ssh, "Client selected an unsupported cipher"); - return; - } - - { - RSAKey *smaller, *larger; - strbuf *data = strbuf_new_nm(); - - if (mp_get_nbits(s->hostkey->modulus) > - mp_get_nbits(s->servkey->modulus)) { - larger = s->hostkey; - smaller = s->servkey; - } else { - smaller = s->hostkey; - larger = s->servkey; - } - - if (rsa_ssh1_decrypt_pkcs1(s->sesskey, larger, data)) { - mp_free(s->sesskey); - s->sesskey = mp_from_bytes_be(ptrlen_from_strbuf(data)); - strbuf_clear(data); - if (rsa_ssh1_decrypt_pkcs1(s->sesskey, smaller, data) && - data->len == sizeof(s->session_key)) { - memcpy(s->session_key, data->u, sizeof(s->session_key)); - mp_free(s->sesskey); - s->sesskey = NULL; /* indicates success */ - } - } - - strbuf_free(data); - } - if (s->sesskey) { - ssh_proto_error(s->ppl.ssh, "Failed to decrypt session key"); - return; - } - - ssh1_compute_session_id(s->session_id, s->cookie, s->hostkey, s->servkey); - - for (i = 0; i < 16; i++) - s->session_key[i] ^= s->session_id[i]; - - { - const ssh_cipheralg *cipher = - (s->cipher_type == SSH1_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : - s->cipher_type == SSH1_CIPHER_DES ? &ssh_des : &ssh_3des_ssh1); - ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key); - } - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS); - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); - if (pktin->type != SSH1_CMSG_USER) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet while " - "expecting username, type %d (%s)", - pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - s->username = get_string(pktin); - s->username.ptr = s->username_str = mkstr(s->username); - ppl_logevent("Received username '%.*s'", PTRLEN_PRINTF(s->username)); - - if (auth_none(s->authpolicy, s->username)) - goto auth_success; - - while (1) { - /* Signal failed authentication */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_FAILURE); - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); - if (pktin->type == SSH1_CMSG_AUTH_PASSWORD) { - s->current_method = AUTHMETHOD_PASSWORD; - if (!(s->ap_methods & s->current_method)) - continue; - - ptrlen password = get_string(pktin); - - /* Tolerate historic traffic-analysis defence of NUL + - * garbage on the end of the binary password string */ - char *nul = memchr(password.ptr, '\0', password.len); - if (nul) - password.len = (const char *)nul - (const char *)password.ptr; - - if (auth_password(s->authpolicy, s->username, password, NULL)) - goto auth_success; - } else if (pktin->type == SSH1_CMSG_AUTH_RSA) { - s->current_method = AUTHMETHOD_PUBLICKEY; - if (!(s->ap_methods & s->current_method)) - continue; - - { - mp_int *modulus = get_mp_ssh1(pktin); - s->authkey = auth_publickey_ssh1( - s->authpolicy, s->username, modulus); - - if (!s->authkey && - s->ssc->stunt_pretend_to_accept_any_pubkey) { - mp_int *zero = mp_from_integer(0); - mp_int *fake_challenge = mp_random_in_range(zero, modulus); - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE); - put_mp_ssh1(pktout, fake_challenge); - pq_push(s->ppl.out_pq, pktout); - - mp_free(zero); - mp_free(fake_challenge); - } - - mp_free(modulus); - } - - if (!s->authkey && - !s->ssc->stunt_pretend_to_accept_any_pubkey) - continue; - - if (s->authkey && s->authkey->bytes < 32) { - ppl_logevent("Auth key far too small"); - continue; - } - - if (s->authkey) { - unsigned char *rsabuf = - snewn(s->authkey->bytes, unsigned char); - - random_read(rsabuf, 32); - - { - ssh_hash *h = ssh_hash_new(&ssh_md5); - put_data(h, rsabuf, 32); - put_data(h, s->session_id, 16); - ssh_hash_final(h, s->auth_rsa_expected_response); - } - - if (!rsa_ssh1_encrypt(rsabuf, 32, s->authkey)) { - sfree(rsabuf); - ppl_logevent("Failed to encrypt auth challenge"); - continue; - } - - mp_int *bn = mp_from_bytes_be( - make_ptrlen(rsabuf, s->authkey->bytes)); - smemclr(rsabuf, s->authkey->bytes); - sfree(rsabuf); - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE); - put_mp_ssh1(pktout, bn); - pq_push(s->ppl.out_pq, pktout); - - mp_free(bn); - } - - crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); - if (pktin->type != SSH1_CMSG_AUTH_RSA_RESPONSE) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet in " - "response to RSA auth challenge, type %d (%s)", - pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - - if (!s->authkey) - continue; - - { - ptrlen response = get_data(pktin, 16); - ptrlen expected = make_ptrlen( - s->auth_rsa_expected_response, 16); - if (!ptrlen_eq_ptrlen(response, expected)) { - ppl_logevent("Wrong response to auth challenge"); - continue; - } - } - - goto auth_success; - } else if (pktin->type == SSH1_CMSG_AUTH_TIS || - pktin->type == SSH1_CMSG_AUTH_CCARD) { - char *challenge; - unsigned response_type; - ptrlen response; - - s->current_method = (pktin->type == SSH1_CMSG_AUTH_TIS ? - AUTHMETHOD_TIS : AUTHMETHOD_CRYPTOCARD); - if (!(s->ap_methods & s->current_method)) - continue; - - challenge = auth_ssh1int_challenge( - s->authpolicy, s->current_method, s->username); - if (!challenge) - continue; - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, - (s->current_method == AUTHMETHOD_TIS ? - SSH1_SMSG_AUTH_TIS_CHALLENGE : - SSH1_SMSG_AUTH_CCARD_CHALLENGE)); - put_stringz(pktout, challenge); - pq_push(s->ppl.out_pq, pktout); - sfree(challenge); - - crMaybeWaitUntilV((pktin = ssh1_login_server_pop(s)) != NULL); - response_type = (s->current_method == AUTHMETHOD_TIS ? - SSH1_CMSG_AUTH_TIS_RESPONSE : - SSH1_CMSG_AUTH_CCARD_RESPONSE); - if (pktin->type != response_type) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet in " - "response to %s challenge, type %d (%s)", - (s->current_method == AUTHMETHOD_TIS ? - "TIS" : "CryptoCard"), - pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - - response = get_string(pktin); - - if (auth_ssh1int_response(s->authpolicy, response)) - goto auth_success; - } - } - - auth_success: - if (!auth_successful(s->authpolicy, s->username, s->current_method)) { - ssh_sw_abort(s->ppl.ssh, "Multiple authentications required but SSH-1" - " cannot perform them"); - return; - } - - /* Signal successful authentication */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_SMSG_SUCCESS); - pq_push(s->ppl.out_pq, pktout); - - ssh1_connection_set_protoflags( - s->successor_layer, s->local_protoflags, s->remote_protoflags); - { - PacketProtocolLayer *successor = s->successor_layer; - s->successor_layer = NULL; /* avoid freeing it ourself */ - ssh_ppl_replace(&s->ppl, successor); - return; /* we've just freed s, so avoid even touching s->crState */ - } - - crFinishV; -} diff --git a/ssh/login1.c b/ssh/login1.c deleted file mode 100644 index 808630ed5..000000000 --- a/ssh/login1.c +++ /dev/null @@ -1,1194 +0,0 @@ -/* - * Packet protocol layer for the SSH-1 login phase (combining what - * SSH-2 would think of as key exchange and user authentication). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "mpint.h" -#include "bpp.h" -#include "ppl.h" -#include "sshcr.h" - -typedef struct agent_key { - RSAKey key; - strbuf *comment; - ptrlen blob; /* only used during initial parsing of agent response */ -} agent_key; - -struct ssh1_login_state { - int crState; - - PacketProtocolLayer *successor_layer; - - Conf *conf; - - char *savedhost; - int savedport; - bool try_agent_auth, is_trivial_auth; - - int remote_protoflags; - int local_protoflags; - unsigned char session_key[32]; - char *username; - agent_pending_query *auth_agent_query; - - int len; - unsigned char *rsabuf; - unsigned long supported_ciphers_mask, supported_auths_mask; - bool tried_publickey, tried_agent; - bool tis_auth_refused, ccard_auth_refused; - unsigned char cookie[8]; - unsigned char session_id[16]; - int cipher_type; - strbuf *publickey_blob; - char *publickey_comment; - bool privatekey_available, privatekey_encrypted; - prompts_t *cur_prompt; - SeatPromptResult spr; - char c; - int pwpkt_type; - void *agent_response_to_free; - ptrlen agent_response; - BinarySource asrc[1]; /* response from SSH agent */ - size_t agent_keys_len; - agent_key *agent_keys; - size_t agent_key_index, agent_key_limit; - bool authed; - RSAKey key; - Filename *keyfile; - RSAKey servkey, hostkey; - - StripCtrlChars *tis_scc; - bool tis_scc_initialised; - - PacketProtocolLayer ppl; -}; - -static void ssh1_login_free(PacketProtocolLayer *); -static void ssh1_login_process_queue(PacketProtocolLayer *); -static void ssh1_login_dialog_callback(void *, SeatPromptResult); -static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg); -static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf); - -static const PacketProtocolLayerVtable ssh1_login_vtable = { - .free = ssh1_login_free, - .process_queue = ssh1_login_process_queue, - .get_specials = ssh1_common_get_specials, - .special_cmd = ssh1_login_special_cmd, - .reconfigure = ssh1_login_reconfigure, - .queued_data_size = ssh_ppl_default_queued_data_size, - .final_output = ssh_ppl_default_final_output, - .name = NULL, /* no layer names in SSH-1 */ -}; - -static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req); -static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen); - -PacketProtocolLayer *ssh1_login_new( - Conf *conf, const char *host, int port, - PacketProtocolLayer *successor_layer) -{ - struct ssh1_login_state *s = snew(struct ssh1_login_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh1_login_vtable; - - s->conf = conf_copy(conf); - s->savedhost = dupstr(host); - s->savedport = port; - s->successor_layer = successor_layer; - s->is_trivial_auth = true; - - return &s->ppl; -} - -static void ssh1_login_free(PacketProtocolLayer *ppl) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - - if (s->successor_layer) - ssh_ppl_free(s->successor_layer); - - conf_free(s->conf); - sfree(s->savedhost); - sfree(s->rsabuf); - sfree(s->username); - if (s->publickey_blob) - strbuf_free(s->publickey_blob); - sfree(s->publickey_comment); - if (s->cur_prompt) - free_prompts(s->cur_prompt); - if (s->agent_keys) { - for (size_t i = 0; i < s->agent_keys_len; i++) { - freersakey(&s->agent_keys[i].key); - strbuf_free(s->agent_keys[i].comment); - } - sfree(s->agent_keys); - } - sfree(s->agent_response_to_free); - if (s->auth_agent_query) - agent_cancel_query(s->auth_agent_query); - sfree(s); -} - -static bool ssh1_login_filter_queue(struct ssh1_login_state *s) -{ - return ssh1_common_filter_queue(&s->ppl); -} - -static PktIn *ssh1_login_pop(struct ssh1_login_state *s) -{ - if (ssh1_login_filter_queue(s)) - return NULL; - return pq_pop(s->ppl.in_pq); -} - -static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s); - -static void ssh1_login_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - PktIn *pktin; - PktOut *pkt; - int i; - - /* Filter centrally handled messages off the front of the queue on - * every entry to this coroutine, no matter where we're resuming - * from, even if we're _not_ looping on pq_pop. That way we can - * still proactively handle those messages even if we're waiting - * for a user response. */ - if (ssh1_login_filter_queue(s)) - return; - - crBegin(s->crState); - - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - - if (pktin->type != SSH1_SMSG_PUBLIC_KEY) { - ssh_proto_error(s->ppl.ssh, "Public key packet not received"); - return; - } - - ppl_logevent("Received public keys"); - - { - ptrlen pl = get_data(pktin, 8); - memcpy(s->cookie, pl.ptr, pl.len); - } - - get_rsa_ssh1_pub(pktin, &s->servkey, RSA_SSH1_EXPONENT_FIRST); - get_rsa_ssh1_pub(pktin, &s->hostkey, RSA_SSH1_EXPONENT_FIRST); - - s->hostkey.comment = NULL; /* avoid confusing rsa_ssh1_fingerprint */ - - /* - * Log the host key fingerprint. - */ - if (!get_err(pktin)) { - char *fingerprint = rsa_ssh1_fingerprint(&s->hostkey); - ppl_logevent("Host key fingerprint is:"); - ppl_logevent(" %s", fingerprint); - sfree(fingerprint); - } - - s->remote_protoflags = get_uint32(pktin); - s->supported_ciphers_mask = get_uint32(pktin); - s->supported_auths_mask = get_uint32(pktin); - - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "Bad SSH-1 public key packet"); - return; - } - - if ((s->ppl.remote_bugs & BUG_CHOKES_ON_RSA)) - s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA); - - s->local_protoflags = - s->remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED; - s->local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER; - - ssh1_compute_session_id(s->session_id, s->cookie, - &s->hostkey, &s->servkey); - - random_read(s->session_key, 32); - - /* - * Verify that the `bits' and `bytes' parameters match. - */ - if (s->hostkey.bits > s->hostkey.bytes * 8 || - s->servkey.bits > s->servkey.bytes * 8) { - ssh_proto_error(s->ppl.ssh, "SSH-1 public keys were badly formatted"); - return; - } - - s->len = 32; - if (s->len < s->hostkey.bytes) - s->len = s->hostkey.bytes; - if (s->len < s->servkey.bytes) - s->len = s->servkey.bytes; - - s->rsabuf = snewn(s->len, unsigned char); - - /* - * Verify the host key. - */ - { - char *keystr = rsastr_fmt(&s->hostkey); - char *keydisp = ssh1_pubkey_str(&s->hostkey); - char **fingerprints = rsa_ssh1_fake_all_fingerprints(&s->hostkey); - - s->spr = verify_ssh_host_key( - ppl_get_iseat(&s->ppl), s->conf, s->savedhost, s->savedport, NULL, - "rsa", keystr, keydisp, fingerprints, 0, - ssh1_login_dialog_callback, s); - - ssh2_free_all_fingerprints(fingerprints); - sfree(keydisp); - sfree(keystr); - } - -#ifdef FUZZING - s->spr = SPR_OK; -#endif - crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); - - if (spr_is_abort(s->spr)) { - ssh_spr_close(s->ppl.ssh, s->spr, "host key verification"); - return; - } - - for (i = 0; i < 32; i++) { - s->rsabuf[i] = s->session_key[i]; - if (i < 16) - s->rsabuf[i] ^= s->session_id[i]; - } - - { - RSAKey *smaller = (s->hostkey.bytes > s->servkey.bytes ? - &s->servkey : &s->hostkey); - RSAKey *larger = (s->hostkey.bytes > s->servkey.bytes ? - &s->hostkey : &s->servkey); - - if (!rsa_ssh1_encrypt(s->rsabuf, 32, smaller) || - !rsa_ssh1_encrypt(s->rsabuf, smaller->bytes, larger)) { - ssh_proto_error(s->ppl.ssh, "SSH-1 public key encryptions failed " - "due to bad formatting"); - return; - } - } - - ppl_logevent("Encrypted session key"); - - { - bool cipher_chosen = false, warn = false; - const char *cipher_string = NULL; - int i; - for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) { - int next_cipher = conf_get_int_int( - s->conf, CONF_ssh_cipherlist, i); - if (next_cipher == CIPHER_WARN) { - /* If/when we choose a cipher, warn about it */ - warn = true; - } else if (next_cipher == CIPHER_AES) { - /* XXX Probably don't need to mention this. */ - ppl_logevent("AES not supported in SSH-1, skipping"); - } else { - switch (next_cipher) { - case CIPHER_3DES: s->cipher_type = SSH1_CIPHER_3DES; - cipher_string = "3DES"; break; - case CIPHER_BLOWFISH: s->cipher_type = SSH1_CIPHER_BLOWFISH; - cipher_string = "Blowfish"; break; - case CIPHER_DES: s->cipher_type = SSH1_CIPHER_DES; - cipher_string = "single-DES"; break; - } - if (s->supported_ciphers_mask & (1 << s->cipher_type)) - cipher_chosen = true; - } - } - if (!cipher_chosen) { - if ((s->supported_ciphers_mask & (1 << SSH1_CIPHER_3DES)) == 0) { - ssh_proto_error(s->ppl.ssh, "Server violates SSH-1 protocol " - "by not supporting 3DES encryption"); - } else { - /* shouldn't happen */ - ssh_sw_abort(s->ppl.ssh, "No supported ciphers found"); - } - return; - } - - /* Warn about chosen cipher if necessary. */ - if (warn) { - s->spr = confirm_weak_crypto_primitive( - ppl_get_iseat(&s->ppl), "cipher", cipher_string, - ssh1_login_dialog_callback, s, WCR_BELOW_THRESHOLD); - crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); - if (spr_is_abort(s->spr)) { - ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning"); - return; - } - } - } - - switch (s->cipher_type) { - case SSH1_CIPHER_3DES: - ppl_logevent("Using 3DES encryption"); - break; - case SSH1_CIPHER_DES: - ppl_logevent("Using single-DES encryption"); - break; - case SSH1_CIPHER_BLOWFISH: - ppl_logevent("Using Blowfish encryption"); - break; - } - - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_SESSION_KEY); - put_byte(pkt, s->cipher_type); - put_data(pkt, s->cookie, 8); - put_uint16(pkt, s->len * 8); - put_data(pkt, s->rsabuf, s->len); - put_uint32(pkt, s->local_protoflags); - pq_push(s->ppl.out_pq, pkt); - - ppl_logevent("Trying to enable encryption..."); - - sfree(s->rsabuf); - s->rsabuf = NULL; - - /* - * Force the BPP to synchronously marshal all packets up to and - * including the SESSION_KEY into wire format, before we turn on - * crypto. - */ - ssh_bpp_handle_output(s->ppl.bpp); - - { - const ssh_cipheralg *cipher = - (s->cipher_type == SSH1_CIPHER_BLOWFISH ? &ssh_blowfish_ssh1 : - s->cipher_type == SSH1_CIPHER_DES ? &ssh_des : &ssh_3des_ssh1); - ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key); - } - - freersakey(&s->servkey); - freersakey(&s->hostkey); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - - if (pktin->type != SSH1_SMSG_SUCCESS) { - ssh_proto_error(s->ppl.ssh, "Encryption not successfully enabled"); - return; - } - - ppl_logevent("Successfully started encryption"); - - if ((s->username = get_remote_username(s->conf)) == NULL) { - s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH login name"); - add_prompt(s->cur_prompt, dupstr("login as: "), true); - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->spr.kind == SPRK_INCOMPLETE) { - crReturnV; - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - } - if (spr_is_abort(s->spr)) { - /* - * Failed to get a username. Terminate. - */ - ssh_spr_close(s->ppl.ssh, s->spr, "username prompt"); - return; - } - s->username = prompt_get_result(s->cur_prompt->prompts[0]); - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - } - - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_USER); - put_stringz(pkt, s->username); - pq_push(s->ppl.out_pq, pkt); - - ppl_logevent("Sent username \"%s\"", s->username); - if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat)) - ppl_printf("Sent username \"%s\"\r\n", s->username); - - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - - if (!(s->supported_auths_mask & (1 << SSH1_AUTH_RSA))) { - /* We must not attempt PK auth. Pretend we've already tried it. */ - s->tried_publickey = s->tried_agent = true; - } else { - s->tried_publickey = s->tried_agent = false; - } - s->tis_auth_refused = s->ccard_auth_refused = false; - - /* - * Load the public half of any configured keyfile for later use. - */ - s->keyfile = conf_get_filename(s->conf, CONF_keyfile); - if (!filename_is_null(s->keyfile)) { - int keytype; - ppl_logevent("Reading key file \"%s\"", filename_to_str(s->keyfile)); - keytype = key_type(s->keyfile); - if (keytype == SSH_KEYTYPE_SSH1 || - keytype == SSH_KEYTYPE_SSH1_PUBLIC) { - const char *error; - s->publickey_blob = strbuf_new(); - if (rsa1_loadpub_f(s->keyfile, - BinarySink_UPCAST(s->publickey_blob), - &s->publickey_comment, &error)) { - s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1); - if (!s->privatekey_available) - ppl_logevent("Key file contains public key only"); - s->privatekey_encrypted = rsa1_encrypted_f(s->keyfile, NULL); - } else { - ppl_logevent("Unable to load key (%s)", error); - ppl_printf("Unable to load key file \"%s\" (%s)\r\n", - filename_to_str(s->keyfile), error); - - strbuf_free(s->publickey_blob); - s->publickey_blob = NULL; - } - } else { - ppl_logevent("Unable to use this key file (%s)", - key_type_to_str(keytype)); - ppl_printf("Unable to use key file \"%s\" (%s)\r\n", - filename_to_str(s->keyfile), - key_type_to_str(keytype)); - } - } - - /* Check whether we're configured to try Pageant, and also whether - * it's available. */ - s->try_agent_auth = (conf_get_bool(s->conf, CONF_tryagent) && - agent_exists()); - - while (pktin->type == SSH1_SMSG_FAILURE) { - s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD; - - if (s->try_agent_auth && !s->tried_agent) { - /* - * Attempt RSA authentication using Pageant. - */ - s->authed = false; - s->tried_agent = true; - ppl_logevent("Pageant is running. Requesting keys."); - - /* Request the keys held by the agent. */ - { - strbuf *request = strbuf_new_for_agent_query(); - put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES); - ssh1_login_agent_query(s, request); - strbuf_free(request); - crMaybeWaitUntilV(!s->auth_agent_query); - } - BinarySource_BARE_INIT_PL(s->asrc, s->agent_response); - - get_uint32(s->asrc); /* skip length field */ - if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { - size_t nkeys = get_uint32(s->asrc); - size_t origpos = s->asrc->pos; - - /* - * Check that the agent response is well formed. - */ - for (size_t i = 0; i < nkeys; i++) { - get_rsa_ssh1_pub(s->asrc, NULL, RSA_SSH1_EXPONENT_FIRST); - get_string(s->asrc); /* comment */ - if (get_err(s->asrc)) { - ppl_logevent("Pageant's response was truncated"); - goto parsed_agent_query; - } - } - - /* - * Copy the list of public-key blobs out of the Pageant - * response. - */ - BinarySource_REWIND_TO(s->asrc, origpos); - s->agent_keys_len = nkeys; - s->agent_keys = snewn(s->agent_keys_len, agent_key); - for (size_t i = 0; i < nkeys; i++) { - memset(&s->agent_keys[i].key, 0, - sizeof(s->agent_keys[i].key)); - - const char *blobstart = get_ptr(s->asrc); - get_rsa_ssh1_pub(s->asrc, &s->agent_keys[i].key, - RSA_SSH1_EXPONENT_FIRST); - const char *blobend = get_ptr(s->asrc); - - s->agent_keys[i].comment = strbuf_dup(get_string(s->asrc)); - - s->agent_keys[i].blob = make_ptrlen( - blobstart, blobend - blobstart); - } - - ppl_logevent("Pageant has %"SIZEu" SSH-1 keys", nkeys); - - if (s->publickey_blob) { - /* - * If we've been given a specific public key blob, - * filter the list of keys to try from the agent - * down to only that one, or none if it's not - * there. - */ - ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob); - size_t i; - - for (i = 0; i < nkeys; i++) { - if (ptrlen_eq_ptrlen(our_blob, s->agent_keys[i].blob)) - break; - } - - if (i < nkeys) { - ppl_logevent("Pageant key #%"SIZEu" matches " - "configured key file", i); - s->agent_key_index = i; - s->agent_key_limit = i+1; - } else { - ppl_logevent("Configured key file not in Pageant"); - s->agent_key_index = 0; - s->agent_key_limit = 0; - } - } else { - /* - * Otherwise, try them all. - */ - s->agent_key_index = 0; - s->agent_key_limit = nkeys; - } - } else { - ppl_logevent("Failed to get reply from Pageant"); - } - parsed_agent_query:; - - for (; s->agent_key_index < s->agent_key_limit; - s->agent_key_index++) { - ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); - put_mp_ssh1(pkt, - s->agent_keys[s->agent_key_index].key.modulus); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - ppl_logevent("Key refused"); - continue; - } - ppl_logevent("Received RSA challenge"); - - { - mp_int *challenge = get_mp_ssh1(pktin); - if (get_err(pktin)) { - mp_free(challenge); - ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " - "was badly formatted"); - return; - } - - strbuf *agentreq = strbuf_new_for_agent_query(); - put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); - - rsa_ssh1_public_blob( - BinarySink_UPCAST(agentreq), - &s->agent_keys[s->agent_key_index].key, - RSA_SSH1_EXPONENT_FIRST); - - put_mp_ssh1(agentreq, challenge); - mp_free(challenge); - - put_data(agentreq, s->session_id, 16); - put_uint32(agentreq, 1); /* response format */ - ssh1_login_agent_query(s, agentreq); - strbuf_free(agentreq); - crMaybeWaitUntilV(!s->auth_agent_query); - } - - { - const unsigned char *ret = s->agent_response.ptr; - if (ret) { - if (s->agent_response.len >= 5+16 && - ret[4] == SSH1_AGENT_RSA_RESPONSE) { - ppl_logevent("Sending Pageant's response"); - pkt = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); - put_data(pkt, ret + 5, 16); - pq_push(s->ppl.out_pq, pkt); - s->is_trivial_auth = false; - crMaybeWaitUntilV( - (pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type == SSH1_SMSG_SUCCESS) { - ppl_logevent("Pageant's response " - "accepted"); - if (seat_verbose(s->ppl.seat)) { - ptrlen comment = ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index]. - comment); - ppl_printf("Authenticated using RSA " - "key \"%.*s\" from " - "agent\r\n", - PTRLEN_PRINTF(comment)); - } - s->authed = true; - } else - ppl_logevent("Pageant's response not " - "accepted"); - } else { - ppl_logevent("Pageant failed to answer " - "challenge"); - sfree((char *)ret); - } - } else { - ppl_logevent("No reply received from Pageant"); - } - } - if (s->authed) - break; - } - if (s->authed) - break; - } - if (s->publickey_blob && s->privatekey_available && - !s->tried_publickey) { - /* - * Try public key authentication with the specified - * key file. - */ - bool got_passphrase; /* need not be kept over crReturn */ - if (seat_verbose(s->ppl.seat)) - ppl_printf("Trying public key authentication.\r\n"); - ppl_logevent("Trying public key \"%s\"", - filename_to_str(s->keyfile)); - s->tried_publickey = true; - got_passphrase = false; - while (!got_passphrase) { - /* - * Get a passphrase, if necessary. - */ - int retd; - char *passphrase = NULL; /* only written after crReturn */ - const char *error; - if (!s->privatekey_encrypted) { - if (seat_verbose(s->ppl.seat)) - ppl_printf("No passphrase required.\r\n"); - passphrase = NULL; - } else { - s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); - s->cur_prompt->to_server = false; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH key passphrase"); - add_prompt(s->cur_prompt, - dupprintf("Passphrase for key \"%s\": ", - s->publickey_comment), false); - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->spr.kind == SPRK_INCOMPLETE) { - crReturnV; - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - } - if (spr_is_abort(s->spr)) { - /* Failed to get a passphrase. Terminate. */ - ssh_spr_close(s->ppl.ssh, s->spr, "passphrase prompt"); - return; - } - passphrase = prompt_get_result(s->cur_prompt->prompts[0]); - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - } - /* - * Try decrypting key with passphrase. - */ - retd = rsa1_load_f(s->keyfile, &s->key, passphrase, &error); - if (passphrase) { - smemclr(passphrase, strlen(passphrase)); - sfree(passphrase); - } - if (retd == 1) { - /* Correct passphrase. */ - got_passphrase = true; - } else if (retd == 0) { - ppl_printf("Couldn't load private key from %s (%s).\r\n", - filename_to_str(s->keyfile), error); - got_passphrase = false; - break; /* go and try something else */ - } else if (retd == -1) { - ppl_printf("Wrong passphrase.\r\n"); - got_passphrase = false; - /* and try again */ - } else { - unreachable("unexpected return from rsa1_load_f()"); - } - } - - if (got_passphrase) { - - /* - * Send a public key attempt. - */ - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); - put_mp_ssh1(pkt, s->key.modulus); - pq_push(s->ppl.out_pq, pkt); - - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - ppl_printf("Server refused our public key.\r\n"); - continue; /* go and try something else */ - } - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to offer of public key, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - - { - int i; - unsigned char buffer[32]; - mp_int *challenge, *response; - - challenge = get_mp_ssh1(pktin); - if (get_err(pktin)) { - mp_free(challenge); - ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " - "was badly formatted"); - return; - } - response = rsa_ssh1_decrypt(challenge, &s->key); - freersapriv(&s->key); /* burn the evidence */ - - for (i = 0; i < 32; i++) { - buffer[i] = mp_get_byte(response, 31 - i); - } - - { - ssh_hash *h = ssh_hash_new(&ssh_md5); - put_data(h, buffer, 32); - put_data(h, s->session_id, 16); - ssh_hash_final(h, buffer); - } - - pkt = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); - put_data(pkt, buffer, 16); - pq_push(s->ppl.out_pq, pkt); - s->is_trivial_auth = false; - - mp_free(challenge); - mp_free(response); - } - - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - if (seat_verbose(s->ppl.seat)) - ppl_printf("Failed to authenticate with" - " our public key.\r\n"); - continue; /* go and try something else */ - } else if (pktin->type != SSH1_SMSG_SUCCESS) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to RSA authentication, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - - break; /* we're through! */ - } - - } - - /* - * Otherwise, try various forms of password-like authentication. - */ - s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); - - if (conf_get_bool(s->conf, CONF_try_tis_auth) && - (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) && - !s->tis_auth_refused) { - ssh1_login_setup_tis_scc(s); - s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE; - ppl_logevent("Requested TIS authentication"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_TIS); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - ppl_logevent("TIS authentication declined"); - if (seat_interactive(s->ppl.seat)) - ppl_printf("TIS authentication refused.\r\n"); - s->tis_auth_refused = true; - continue; - } else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) { - ptrlen challenge = get_string(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "TIS challenge packet was " - "badly formed"); - return; - } - ppl_logevent("Received TIS challenge"); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = true; - s->cur_prompt->name = dupstr("SSH TIS authentication"); - - strbuf *sb = strbuf_new(); - put_datapl(sb, PTRLEN_LITERAL("\ --- TIS authentication challenge from server: ---------------------------------\ -\r\n")); - if (s->tis_scc) { - stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb)); - put_datapl(s->tis_scc, challenge); - stripctrl_retarget(s->tis_scc, NULL); - } else { - put_datapl(sb, challenge); - } - if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL)) - put_datapl(sb, PTRLEN_LITERAL("\r\n")); - put_datapl(sb, PTRLEN_LITERAL("\ --- End of TIS authentication challenge from server: --------------------------\ -\r\n")); - - s->cur_prompt->instruction = strbuf_to_str(sb); - s->cur_prompt->instr_reqd = true; - add_prompt(s->cur_prompt, dupstr( - "TIS authentication response: "), false); - } else { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to TIS authentication, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - } else if (conf_get_bool(s->conf, CONF_try_tis_auth) && - (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) && - !s->ccard_auth_refused) { - ssh1_login_setup_tis_scc(s); - s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE; - ppl_logevent("Requested CryptoCard authentication"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_CCARD); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - ppl_logevent("CryptoCard authentication declined"); - ppl_printf("CryptoCard authentication refused.\r\n"); - s->ccard_auth_refused = true; - continue; - } else if (pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) { - ptrlen challenge = get_string(pktin); - if (get_err(pktin)) { - ssh_proto_error(s->ppl.ssh, "CryptoCard challenge packet " - "was badly formed"); - return; - } - ppl_logevent("Received CryptoCard challenge"); - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = true; - s->cur_prompt->name = dupstr("SSH CryptoCard authentication"); - - strbuf *sb = strbuf_new(); - put_datapl(sb, PTRLEN_LITERAL("\ --- CryptoCard authentication challenge from server: --------------------------\ -\r\n")); - if (s->tis_scc) { - stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb)); - put_datapl(s->tis_scc, challenge); - stripctrl_retarget(s->tis_scc, NULL); - } else { - put_datapl(sb, challenge); - } - if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL)) - put_datapl(sb, PTRLEN_LITERAL("\r\n")); - put_datapl(sb, PTRLEN_LITERAL("\ --- End of CryptoCard authentication challenge from server: -------------------\ -\r\n")); - - s->cur_prompt->instruction = strbuf_to_str(sb); - s->cur_prompt->instr_reqd = true; - add_prompt(s->cur_prompt, dupstr( - "CryptoCard authentication response: "), false); - } else { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to TIS authentication, " - "type %d (%s)", pktin->type, - ssh1_pkt_type(pktin->type)); - return; - } - } - if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) { - if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) { - ssh_sw_abort(s->ppl.ssh, "No supported authentication methods " - "available"); - return; - } - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH password"); - add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", - s->username, s->savedhost), - false); - } - - /* - * Show password prompt, having first obtained it via a TIS - * or CryptoCard exchange if we're doing TIS or CryptoCard - * authentication. - */ - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->spr.kind == SPRK_INCOMPLETE) { - crReturnV; - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - } - if (spr_is_abort(s->spr)) { - /* - * Failed to get a password (for example - * because one was supplied on the command line - * which has already failed to work). Terminate. - */ - ssh_spr_close(s->ppl.ssh, s->spr, "password prompt"); - return; - } - - 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 (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) && - !(s->ppl.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; - const char *pw = prompt_get_result_ref( - s->cur_prompt->prompts[0]); - - pwlen = strlen(pw); - if (pwlen < 16) { - bottom = 0; /* zero length passwords are OK! :-) */ - top = 15; - } else { - bottom = pwlen & ~7; - top = bottom + 7; - } - - assert(pwlen >= bottom && pwlen <= top); - - for (i = bottom; i <= top; i++) { - if (i == pwlen) { - pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); - put_stringz(pkt, pw); - pq_push(s->ppl.out_pq, pkt); - } else { - strbuf *random_data = strbuf_new_nm(); - random_read(strbuf_append(random_data, i), i); - - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); - put_stringsb(pkt, random_data); - pq_push(s->ppl.out_pq, pkt); - } - } - ppl_logevent("Sending password with camouflage packets"); - } - else if (!(s->ppl.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. - */ - strbuf *padded_pw = strbuf_new_nm(); - - ppl_logevent("Sending length-padded password"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); - put_asciz(padded_pw, prompt_get_result_ref( - s->cur_prompt->prompts[0])); - size_t pad = 63 & -padded_pw->len; - random_read(strbuf_append(padded_pw, pad), pad); - put_stringsb(pkt, padded_pw); - pq_push(s->ppl.out_pq, pkt); - } else { - /* - * The server is believed unable to cope with - * any of our password camouflage methods. - */ - ppl_logevent("Sending unpadded password"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); - put_stringz(pkt, prompt_get_result_ref( - s->cur_prompt->prompts[0])); - pq_push(s->ppl.out_pq, pkt); - } - } else { - pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type); - put_stringz(pkt, prompt_get_result_ref(s->cur_prompt->prompts[0])); - pq_push(s->ppl.out_pq, pkt); - } - s->is_trivial_auth = false; - ppl_logevent("Sent password"); - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - if (pktin->type == SSH1_SMSG_FAILURE) { - if (seat_verbose(s->ppl.seat)) - ppl_printf("Access denied\r\n"); - ppl_logevent("Authentication refused"); - } else if (pktin->type != SSH1_SMSG_SUCCESS) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to password authentication, type %d " - "(%s)", pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - } - - if (conf_get_bool(s->conf, CONF_ssh_no_trivial_userauth) && - s->is_trivial_auth) { - ssh_proto_error(s->ppl.ssh, "Authentication was trivial! " - "Abandoning session as specified in configuration."); - return; - } - - ppl_logevent("Authentication successful"); - - if (conf_get_bool(s->conf, CONF_compression)) { - ppl_logevent("Requesting compression"); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_COMPRESSION); - put_uint32(pkt, 6); /* gzip compression level */ - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL); - if (pktin->type == SSH1_SMSG_SUCCESS) { - /* - * We don't have to actually do anything here: the SSH-1 - * BPP will take care of automatically starting the - * compression, by recognising our outgoing request packet - * and the success response. (Horrible, but it's the - * easiest way to avoid race conditions if other packets - * cross in transit.) - */ - } else if (pktin->type == SSH1_SMSG_FAILURE) { - ppl_logevent("Server refused to enable compression"); - ppl_printf("Server refused to compress\r\n"); - } else { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet" - " in response to compression request, type %d " - "(%s)", pktin->type, ssh1_pkt_type(pktin->type)); - return; - } - } - - ssh1_connection_set_protoflags( - s->successor_layer, s->local_protoflags, s->remote_protoflags); - { - PacketProtocolLayer *successor = s->successor_layer; - s->successor_layer = NULL; /* avoid freeing it ourself */ - ssh_ppl_replace(&s->ppl, successor); - return; /* we've just freed s, so avoid even touching s->crState */ - } - - crFinishV; -} - -static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s) -{ - if (s->tis_scc_initialised) - return; - s->tis_scc = seat_stripctrl_new(s->ppl.seat, NULL, SIC_KI_PROMPTS); - if (s->tis_scc) - stripctrl_enable_line_limiting(s->tis_scc); - s->tis_scc_initialised = true; -} - -static void ssh1_login_dialog_callback(void *loginv, SeatPromptResult spr) -{ - struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; - s->spr = spr; - ssh_ppl_process_queue(&s->ppl); -} - -static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req) -{ - void *response; - int response_len; - - sfree(s->agent_response_to_free); - s->agent_response_to_free = NULL; - - s->auth_agent_query = agent_query(req, &response, &response_len, - ssh1_login_agent_callback, s); - if (!s->auth_agent_query) - ssh1_login_agent_callback(s, response, response_len); -} - -static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen) -{ - struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; - - s->auth_agent_query = NULL; - s->agent_response_to_free = reply; - s->agent_response = make_ptrlen(reply, replylen); - - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - PktOut *pktout; - - if (code == SS_PING || code == SS_NOP) { - if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE); - put_stringz(pktout, ""); - pq_push(s->ppl.out_pq, pktout); - } - } -} - -static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ - struct ssh1_login_state *s = - container_of(ppl, struct ssh1_login_state, ppl); - ssh_ppl_reconfigure(s->successor_layer, conf); -} diff --git a/ssh/mainchan.c b/ssh/mainchan.c deleted file mode 100644 index 52492ff64..000000000 --- a/ssh/mainchan.c +++ /dev/null @@ -1,539 +0,0 @@ -/* - * SSH main session channel handling. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "ppl.h" -#include "channel.h" - -static void mainchan_free(Channel *chan); -static void mainchan_open_confirmation(Channel *chan); -static void mainchan_open_failure(Channel *chan, const char *errtext); -static size_t mainchan_send( - Channel *chan, bool is_stderr, const void *, size_t); -static void mainchan_send_eof(Channel *chan); -static void mainchan_set_input_wanted(Channel *chan, bool wanted); -static char *mainchan_log_close_msg(Channel *chan); -static bool mainchan_rcvd_exit_status(Channel *chan, int status); -static bool mainchan_rcvd_exit_signal( - Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg); -static bool mainchan_rcvd_exit_signal_numeric( - Channel *chan, int signum, bool core_dumped, ptrlen msg); -static void mainchan_request_response(Channel *chan, bool success); - -static const ChannelVtable mainchan_channelvt = { - .free = mainchan_free, - .open_confirmation = mainchan_open_confirmation, - .open_failed = mainchan_open_failure, - .send = mainchan_send, - .send_eof = mainchan_send_eof, - .set_input_wanted = mainchan_set_input_wanted, - .log_close_msg = mainchan_log_close_msg, - .want_close = chan_default_want_close, - .rcvd_exit_status = mainchan_rcvd_exit_status, - .rcvd_exit_signal = mainchan_rcvd_exit_signal, - .rcvd_exit_signal_numeric = mainchan_rcvd_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = mainchan_request_response, -}; - -typedef enum MainChanType { - MAINCHAN_SESSION, MAINCHAN_DIRECT_TCPIP -} MainChanType; - -struct mainchan { - SshChannel *sc; - Conf *conf; - PacketProtocolLayer *ppl; - ConnectionLayer *cl; - - MainChanType type; - bool is_simple; - - bool req_x11, req_agent, req_pty, req_cmd_primary, req_cmd_fallback; - int n_req_env, n_env_replies, n_env_fails; - bool eof_pending, eof_sent, got_pty, ready; - - int term_width, term_height; - - Channel chan; -}; - -mainchan *mainchan_new( - PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, - int term_width, int term_height, bool is_simple, SshChannel **sc_out) -{ - mainchan *mc; - - if (conf_get_bool(conf, CONF_ssh_no_shell)) - return NULL; /* no main channel at all */ - - mc = snew(mainchan); - memset(mc, 0, sizeof(mainchan)); - mc->ppl = ppl; - mc->cl = cl; - mc->conf = conf_copy(conf); - mc->term_width = term_width; - mc->term_height = term_height; - mc->is_simple = is_simple; - - mc->sc = NULL; - mc->chan.vt = &mainchan_channelvt; - mc->chan.initial_fixed_window_size = 0; - - if (*conf_get_str(mc->conf, CONF_ssh_nc_host)) { - const char *host = conf_get_str(mc->conf, CONF_ssh_nc_host); - int port = conf_get_int(mc->conf, CONF_ssh_nc_port); - - mc->sc = ssh_lportfwd_open(cl, host, port, "main channel", - NULL, &mc->chan); - mc->type = MAINCHAN_DIRECT_TCPIP; - } else { - mc->sc = ssh_session_open(cl, &mc->chan); - mc->type = MAINCHAN_SESSION; - } - - if (sc_out) *sc_out = mc->sc; - return mc; -} - -static void mainchan_free(Channel *chan) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - conf_free(mc->conf); - sfree(mc); -} - -static void mainchan_try_fallback_command(mainchan *mc); -static void mainchan_ready(mainchan *mc); - -static void mainchan_open_confirmation(Channel *chan) -{ - mainchan *mc = container_of(chan, mainchan, chan); - PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ - - seat_update_specials_menu(mc->ppl->seat); - ppl_logevent("Opened main channel"); - seat_notify_session_started(mc->ppl->seat); - - if (mc->is_simple) - sshfwd_hint_channel_is_simple(mc->sc); - - if (mc->type == MAINCHAN_SESSION) { - /* - * Send the CHANNEL_REQUESTS for the main session channel. - */ - char *key, *val, *cmd; - struct X11Display *x11disp; - struct X11FakeAuth *x11auth; - bool retry_cmd_now = false; - - if (conf_get_bool(mc->conf, CONF_x11_forward)) { - char *x11_setup_err; - if ((x11disp = x11_setup_display( - conf_get_str(mc->conf, CONF_x11_display), - mc->conf, &x11_setup_err)) == NULL) { - ppl_logevent("X11 forwarding not enabled: unable to" - " initialise X display: %s", x11_setup_err); - sfree(x11_setup_err); - } else { - x11auth = ssh_add_x11_display( - mc->cl, conf_get_int(mc->conf, CONF_x11_auth), x11disp); - - sshfwd_request_x11_forwarding( - mc->sc, true, x11auth->protoname, x11auth->datastring, - x11disp->screennum, false); - mc->req_x11 = true; - } - } - - if (ssh_agent_forwarding_permitted(mc->cl)) { - sshfwd_request_agent_forwarding(mc->sc, true); - mc->req_agent = true; - } - - if (!conf_get_bool(mc->conf, CONF_nopty)) { - sshfwd_request_pty( - mc->sc, true, mc->conf, mc->term_width, mc->term_height); - mc->req_pty = true; - } - - for (val = conf_get_str_strs(mc->conf, CONF_environmt, NULL, &key); - val != NULL; - val = conf_get_str_strs(mc->conf, CONF_environmt, key, &key)) { - sshfwd_send_env_var(mc->sc, true, key, val); - mc->n_req_env++; - } - if (mc->n_req_env) - ppl_logevent("Sent %d environment variables", mc->n_req_env); - - cmd = conf_get_str(mc->conf, CONF_remote_cmd); - if (conf_get_bool(mc->conf, CONF_ssh_subsys)) { - retry_cmd_now = !sshfwd_start_subsystem(mc->sc, true, cmd); - } else if (*cmd) { - sshfwd_start_command(mc->sc, true, cmd); - } else { - sshfwd_start_shell(mc->sc, true); - } - - if (retry_cmd_now) - mainchan_try_fallback_command(mc); - else - mc->req_cmd_primary = true; - - } else { - ssh_set_ldisc_option(mc->cl, LD_ECHO, true); - ssh_set_ldisc_option(mc->cl, LD_EDIT, true); - mainchan_ready(mc); - } -} - -static void mainchan_try_fallback_command(mainchan *mc) -{ - const char *cmd = conf_get_str(mc->conf, CONF_remote_cmd2); - if (conf_get_bool(mc->conf, CONF_ssh_subsys2)) { - sshfwd_start_subsystem(mc->sc, true, cmd); - } else { - sshfwd_start_command(mc->sc, true, cmd); - } - mc->req_cmd_fallback = true; -} - -static void mainchan_request_response(Channel *chan, bool success) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ - - if (mc->req_x11) { - mc->req_x11 = false; - - if (success) { - ppl_logevent("X11 forwarding enabled"); - ssh_enable_x_fwd(mc->cl); - } else { - ppl_logevent("X11 forwarding refused"); - } - return; - } - - if (mc->req_agent) { - mc->req_agent = false; - - if (success) { - ppl_logevent("Agent forwarding enabled"); - } else { - ppl_logevent("Agent forwarding refused"); - } - return; - } - - if (mc->req_pty) { - mc->req_pty = false; - - if (success) { - ppl_logevent("Allocated pty"); - mc->got_pty = true; - } else { - ppl_logevent("Server refused to allocate pty"); - ppl_printf("Server refused to allocate pty\r\n"); - ssh_set_ldisc_option(mc->cl, LD_ECHO, true); - ssh_set_ldisc_option(mc->cl, LD_EDIT, true); - } - return; - } - - if (mc->n_env_replies < mc->n_req_env) { - int j = mc->n_env_replies++; - if (!success) { - ppl_logevent("Server refused to set environment variable %s", - conf_get_str_nthstrkey(mc->conf, - CONF_environmt, j)); - mc->n_env_fails++; - } - - if (mc->n_env_replies == mc->n_req_env) { - if (mc->n_env_fails == 0) { - ppl_logevent("All environment variables successfully set"); - } else if (mc->n_env_fails == mc->n_req_env) { - ppl_logevent("All environment variables refused"); - ppl_printf("Server refused to set environment " - "variables\r\n"); - } else { - ppl_printf("Server refused to set all environment " - "variables\r\n"); - } - } - return; - } - - if (mc->req_cmd_primary) { - mc->req_cmd_primary = false; - - if (success) { - ppl_logevent("Started a shell/command"); - mainchan_ready(mc); - } else if (*conf_get_str(mc->conf, CONF_remote_cmd2)) { - ppl_logevent("Primary command failed; attempting fallback"); - mainchan_try_fallback_command(mc); - } else { - /* - * If there's no remote_cmd2 configured, then we have no - * fallback command, so we've run out of options. - */ - ssh_sw_abort_deferred(mc->ppl->ssh, - "Server refused to start a shell/command"); - } - return; - } - - if (mc->req_cmd_fallback) { - mc->req_cmd_fallback = false; - - if (success) { - ppl_logevent("Started a shell/command"); - ssh_got_fallback_cmd(mc->ppl->ssh); - mainchan_ready(mc); - } else { - ssh_sw_abort_deferred(mc->ppl->ssh, - "Server refused to start a shell/command"); - } - return; - } -} - -static void mainchan_ready(mainchan *mc) -{ - mc->ready = true; - - ssh_set_wants_user_input(mc->cl, true); - ssh_got_user_input(mc->cl); /* in case any is already queued */ - - /* If an EOF arrived before we were ready, handle it now. */ - if (mc->eof_pending) { - mc->eof_pending = false; - mainchan_special_cmd(mc, SS_EOF, 0); - } - - ssh_ldisc_update(mc->ppl->ssh); - queue_idempotent_callback(&mc->ppl->ic_process_queue); -} - -static void mainchan_open_failure(Channel *chan, const char *errtext) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - - ssh_sw_abort_deferred(mc->ppl->ssh, - "Server refused to open main channel: %s", errtext); -} - -static size_t mainchan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - return seat_output(mc->ppl->seat, is_stderr, data, length); -} - -static void mainchan_send_eof(Channel *chan) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ - - if (!mc->eof_sent && (seat_eof(mc->ppl->seat) || mc->got_pty)) { - /* - * Either seat_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(mc->sc); - ppl_logevent("Sent EOF message"); - mc->eof_sent = true; - ssh_set_wants_user_input(mc->cl, false); /* stop reading from stdin */ - } -} - -static void mainchan_set_input_wanted(Channel *chan, bool wanted) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - - /* - * This is the main channel of the SSH session, i.e. the one tied - * to the standard input (or GUI) of the primary SSH client user - * interface. So ssh->send_ok is how we control whether we're - * reading from that input. - */ - ssh_set_wants_user_input(mc->cl, wanted); -} - -static char *mainchan_log_close_msg(Channel *chan) -{ - return dupstr("Main session channel closed"); -} - -static bool mainchan_rcvd_exit_status(Channel *chan, int status) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ - - ssh_got_exitcode(mc->ppl->ssh, status); - ppl_logevent("Session sent command exit status %d", status); - return true; -} - -static void mainchan_log_exit_signal_common( - mainchan *mc, const char *sigdesc, - bool core_dumped, ptrlen msg) -{ - PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ - - const char *core_msg = core_dumped ? " (core dumped)" : ""; - const char *msg_pre = (msg.len ? " (" : ""); - const char *msg_post = (msg.len ? ")" : ""); - ppl_logevent("Session exited on %s%s%s%.*s%s", - sigdesc, core_msg, msg_pre, PTRLEN_PRINTF(msg), msg_post); -} - -static bool mainchan_rcvd_exit_signal( - Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - int exitcode; - char *signame_str; - - /* - * Translate the signal description back into a locally meaningful - * number, or 128 if the string didn't match any we recognise. - */ - exitcode = 128; - - #define SIGNAL_SUB(s) \ - if (ptrlen_eq_string(signame, #s)) \ - exitcode = 128 + SIG ## s; - #define SIGNAL_MAIN(s, text) SIGNAL_SUB(s) - #define SIGNALS_LOCAL_ONLY - #include "signal-list.h" - #undef SIGNAL_SUB - #undef SIGNAL_MAIN - #undef SIGNALS_LOCAL_ONLY - - ssh_got_exitcode(mc->ppl->ssh, exitcode); - if (exitcode == 128) - signame_str = dupprintf("unrecognised signal \"%.*s\"", - PTRLEN_PRINTF(signame)); - else - signame_str = dupprintf("signal SIG%.*s", PTRLEN_PRINTF(signame)); - mainchan_log_exit_signal_common(mc, signame_str, core_dumped, msg); - sfree(signame_str); - return true; -} - -static bool mainchan_rcvd_exit_signal_numeric( - Channel *chan, int signum, bool core_dumped, ptrlen msg) -{ - assert(chan->vt == &mainchan_channelvt); - mainchan *mc = container_of(chan, mainchan, chan); - char *signum_str; - - ssh_got_exitcode(mc->ppl->ssh, 128 + signum); - signum_str = dupprintf("signal %d", signum); - mainchan_log_exit_signal_common(mc, signum_str, core_dumped, msg); - sfree(signum_str); - return true; -} - -void mainchan_get_specials( - mainchan *mc, add_special_fn_t add_special, void *ctx) -{ - /* FIXME: this _does_ depend on whether these services are supported */ - - add_special(ctx, "Break", SS_BRK, 0); - - #define SIGNAL_MAIN(name, desc) \ - add_special(ctx, "SIG" #name " (" desc ")", SS_SIG ## name, 0); - #define SIGNAL_SUB(name) - #include "signal-list.h" - #undef SIGNAL_MAIN - #undef SIGNAL_SUB - - add_special(ctx, "More signals", SS_SUBMENU, 0); - - #define SIGNAL_MAIN(name, desc) - #define SIGNAL_SUB(name) \ - add_special(ctx, "SIG" #name, SS_SIG ## name, 0); - #include "signal-list.h" - #undef SIGNAL_MAIN - #undef SIGNAL_SUB - - add_special(ctx, NULL, SS_EXITMENU, 0); -} - -static const char *ssh_signal_lookup(SessionSpecialCode code) -{ - #define SIGNAL_SUB(name) \ - if (code == SS_SIG ## name) return #name; - #define SIGNAL_MAIN(name, desc) SIGNAL_SUB(name) - #include "signal-list.h" - #undef SIGNAL_MAIN - #undef SIGNAL_SUB - - /* If none of those clauses matched, fail lookup. */ - return NULL; -} - -void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg) -{ - PacketProtocolLayer *ppl = mc->ppl; /* for ppl_logevent */ - const char *signame; - - if (code == SS_EOF) { - if (!mc->ready) { - /* - * Buffer the EOF to send as soon as the main channel is - * fully set up. - */ - mc->eof_pending = true; - } else if (!mc->eof_sent) { - sshfwd_write_eof(mc->sc); - mc->eof_sent = true; - } - } else if (code == SS_BRK) { - sshfwd_send_serial_break( - mc->sc, false, 0 /* default break length */); - } else if ((signame = ssh_signal_lookup(code)) != NULL) { - /* It's a signal. */ - sshfwd_send_signal(mc->sc, false, signame); - ppl_logevent("Sent signal SIG%s", signame); - } -} - -void mainchan_terminal_size(mainchan *mc, int width, int height) -{ - mc->term_width = width; - mc->term_height = height; - - if (mc->req_pty || mc->got_pty) - sshfwd_send_terminal_size_change(mc->sc, width, height); -} diff --git a/ssh/nogss.c b/ssh/nogss.c deleted file mode 100644 index fa4ac65f4..000000000 --- a/ssh/nogss.c +++ /dev/null @@ -1,19 +0,0 @@ -#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/ssh/nosharing.c b/ssh/nosharing.c deleted file mode 100644 index c45634c59..000000000 --- a/ssh/nosharing.c +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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, - bool can_upstream, bool can_downstream) -{ - return SHARE_NONE; -} - -void platform_ssh_share_cleanup(const char *name) -{ -} diff --git a/ssh/pgssapi.c b/ssh/pgssapi.c deleted file mode 100644 index 1730444df..000000000 --- a/ssh/pgssapi.c +++ /dev/null @@ -1,135 +0,0 @@ -/* 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. - * Constants of the form GSS_C_NT_* are specified by rfc 2744. - * Comments are quotes from RFC 2744 itself. - * - * These may be #defined to complex expressions by the local header - * file, if we're including one in static-GSSAPI mode. (For example, - * Heimdal defines them to things like - * (&__gss_c_nt_user_name_oid_desc).) So we only define them if - * needed. */ - -#ifndef GSS_C_NT_USER_NAME -static gss_OID_desc oid_GSS_C_NT_USER_NAME = { - /* The implementation must reserve static storage for a - * gss_OID_desc object containing the value */ - 10, "\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. */ -}; -const_gss_OID GSS_C_NT_USER_NAME = &oid_GSS_C_NT_USER_NAME; -#endif - -#ifndef GSS_C_NT_MACHINE_UID_NAME -static gss_OID_desc oid_GSS_C_NT_MACHINE_UID_NAME = { - /* The implementation must reserve static storage for a - * gss_OID_desc object containing the value */ - 10, "\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. */ -}; -const_gss_OID GSS_C_NT_MACHINE_UID_NAME = &oid_GSS_C_NT_MACHINE_UID_NAME; -#endif - -#ifndef GSS_C_NT_STRING_UID_NAME -static gss_OID_desc oid_GSS_C_NT_STRING_UID_NAME = { - /* The implementation must reserve static storage for a - * gss_OID_desc object containing the value */ - 10, "\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. */ -}; -const_gss_OID GSS_C_NT_STRING_UID_NAME = &oid_GSS_C_NT_STRING_UID_NAME; -#endif - -#ifndef GSS_C_NT_HOSTBASED_SERVICE_X -static gss_OID_desc oid_GSS_C_NT_HOSTBASED_SERVICE_X = { - /* The implementation must reserve static storage for a - * gss_OID_desc object containing the value */ - 6, "\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 */ -}; -const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = &oid_GSS_C_NT_HOSTBASED_SERVICE_X; -#endif - -#ifndef GSS_C_NT_HOSTBASED_SERVICE -static gss_OID_desc oid_GSS_C_NT_HOSTBASED_SERVICE = { - /* The implementation must reserve static storage for a - * gss_OID_desc object containing the value */ - 10, "\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. */ -}; -const_gss_OID GSS_C_NT_HOSTBASED_SERVICE = &oid_GSS_C_NT_HOSTBASED_SERVICE; -#endif - -#ifndef GSS_C_NT_ANONYMOUS -static gss_OID_desc oid_GSS_C_NT_ANONYMOUS = { - /* The implementation must reserve static storage for a - * gss_OID_desc object containing the value */ - 6, "\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. */ -}; -const_gss_OID GSS_C_NT_ANONYMOUS = &oid_GSS_C_NT_ANONYMOUS; -#endif - -#ifndef GSS_C_NT_EXPORT_NAME -static gss_OID_desc oid_GSS_C_NT_EXPORT_NAME = { - /* The implementation must reserve static storage for a - * gss_OID_desc object containing the value */ - 6, "\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. - */ -}; -const_gss_OID GSS_C_NT_EXPORT_NAME = &oid_GSS_C_NT_EXPORT_NAME; -#endif - -#endif /* NO_LIBDL */ - -static gss_OID_desc gss_mech_krb5_desc = -{ 9, "\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/ssh/pgssapi.h b/ssh/pgssapi.h deleted file mode 100644 index 3f21e0ff5..000000000 --- a/ssh/pgssapi.h +++ /dev/null @@ -1,333 +0,0 @@ -#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_t 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; -typedef int gss_cred_usage_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 - -/*- - * RFC 2744 Page 86 - * Expiration time of 2^32-1 seconds means infinite lifetime for a - * credential or security context - */ -#define GSS_C_INDEFINITE 0xfffffffful - -/* 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_verify_mic) - (OM_uint32 * /*minor_status*/, - const gss_ctx_id_t /*context_handle*/, - const gss_buffer_t /*message_buffer*/, - const gss_buffer_t /*msg_token*/, - gss_qop_t * /*qop_state*/); - -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*/); - -typedef OM_uint32 (GSS_CC *t_gss_acquire_cred) - (OM_uint32 * /*minor_status*/, - const gss_name_t /*desired_name*/, - OM_uint32 /*time_req*/, - const gss_OID_set /*desired_mechs*/, - gss_cred_usage_t /*cred_usage*/, - gss_cred_id_t * /*output_cred_handle*/, - gss_OID_set * /*actual_mechs*/, - OM_uint32 * /*time_rec*/); - -typedef OM_uint32 (GSS_CC *t_gss_inquire_cred_by_mech) - (OM_uint32 * /*minor_status*/, - const gss_cred_id_t /*cred_handle*/, - const gss_OID /*mech_type*/, - gss_name_t * /*name*/, - OM_uint32 * /*initiator_lifetime*/, - OM_uint32 * /*acceptor_lifetime*/, - gss_cred_usage_t * /*cred_usage*/); - -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_verify_mic verify_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; - t_gss_acquire_cred acquire_cred; - t_gss_inquire_cred_by_mech inquire_cred_by_mech; -}; - -#endif /* NO_GSSAPI */ - -#endif /* PUTTY_PGSSAPI_H */ diff --git a/ssh/portfwd.c b/ssh/portfwd.c deleted file mode 100644 index b4eea3c96..000000000 --- a/ssh/portfwd.c +++ /dev/null @@ -1,1179 +0,0 @@ -/* - * SSH port forwarding. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "channel.h" -#include "proxy/socks.h" - -/* - * Enumeration of values that live in the 'socks_state' field of - * struct PortForwarding. - */ -typedef enum { - SOCKS_NONE, /* direct connection (no SOCKS, or SOCKS already done) */ - SOCKS_INITIAL, /* don't know if we're SOCKS 4 or 5 yet */ - SOCKS_4, /* expect a SOCKS 4 (or 4A) connection message */ - SOCKS_5_INITIAL, /* expect a SOCKS 5 preliminary message */ - SOCKS_5_CONNECT /* expect a SOCKS 5 connection message */ -} SocksState; - -typedef struct PortForwarding { - SshChannel *c; /* channel structure held by SSH connection layer */ - ConnectionLayer *cl; /* the connection layer itself */ - /* Note that ssh need not be filled in if c is non-NULL */ - Socket *s; - bool input_wanted; - bool ready; - SocksState socks_state; - /* - * `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 the initial SOCKS - * segment of the incoming data, plus anything after that that we - * receive before we're ready to send data to the SSH server. - */ - strbuf *socksbuf; - size_t socksbuf_consumed; - - Plug plug; - Channel chan; -} PortForwarding; - -struct PortListener { - ConnectionLayer *cl; - Socket *s; - bool is_dynamic; - /* - * `hostname' and `port' are the real hostname and port, for - * ordinary forwardings. - */ - char *hostname; - int port; - - Plug plug; -}; - -static struct PortForwarding *new_portfwd_state(void) -{ - struct PortForwarding *pf = snew(struct PortForwarding); - pf->hostname = NULL; - pf->socksbuf = NULL; - return pf; -} - -static void free_portfwd_state(struct PortForwarding *pf) -{ - if (!pf) - return; - sfree(pf->hostname); - if (pf->socksbuf) - strbuf_free(pf->socksbuf); - 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, PlugLogType 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, PlugLogType 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 pfd_close(struct PortForwarding *pf); - -static void pfd_closing(Plug *plug, PlugCloseType type, const char *error_msg) -{ - struct PortForwarding *pf = - container_of(plug, struct PortForwarding, plug); - - if (type != PLUGCLOSE_NORMAL) { - /* - * Socket error. Slam the connection instantly shut. - */ - if (pf->c) { - sshfwd_initiate_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_initiate_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); - } -} - -static void pfl_terminate(struct PortListener *pl); - -static void pfl_closing(Plug *plug, PlugCloseType type, const char *error_msg) -{ - struct PortListener *pl = (struct PortListener *) plug; - pfl_terminate(pl); -} - -static SshChannel *wrap_lportfwd_open( - ConnectionLayer *cl, const char *hostname, int port, - Socket *s, Channel *chan) -{ - SocketPeerInfo *pi; - char *description; - SshChannel *toret; - - pi = sk_peer_info(s); - if (pi && pi->log_text) { - description = dupprintf("forwarding from %s", pi->log_text); - } else { - description = dupstr("forwarding"); - } - toret = ssh_lportfwd_open(cl, hostname, port, description, pi, chan); - sk_free_peer_info(pi); - - sfree(description); - return toret; -} - -static char *ipv4_to_string(unsigned ipv4) -{ - return dupprintf("%u.%u.%u.%u", - (ipv4 >> 24) & 0xFF, (ipv4 >> 16) & 0xFF, - (ipv4 >> 8) & 0xFF, (ipv4 ) & 0xFF); -} - -static char *ipv6_to_string(ptrlen ipv6) -{ - const unsigned char *addr = ipv6.ptr; - assert(ipv6.len == 16); - return dupprintf("%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x", - (unsigned)GET_16BIT_MSB_FIRST(addr + 0), - (unsigned)GET_16BIT_MSB_FIRST(addr + 2), - (unsigned)GET_16BIT_MSB_FIRST(addr + 4), - (unsigned)GET_16BIT_MSB_FIRST(addr + 6), - (unsigned)GET_16BIT_MSB_FIRST(addr + 8), - (unsigned)GET_16BIT_MSB_FIRST(addr + 10), - (unsigned)GET_16BIT_MSB_FIRST(addr + 12), - (unsigned)GET_16BIT_MSB_FIRST(addr + 14)); -} - -static void pfd_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - struct PortForwarding *pf = - container_of(plug, struct PortForwarding, plug); - - if (len == 0) - return; - - if (pf->socks_state != SOCKS_NONE) { - BinarySource src[1]; - - /* - * Store all the data we've got in socksbuf. - */ - put_data(pf->socksbuf, data, len); - - /* - * Check the start of socksbuf to see if it's a valid and - * complete message in the SOCKS exchange. - */ - - if (pf->socks_state == SOCKS_INITIAL) { - /* Preliminary: check the first byte of the data (which we - * _must_ have by now) to find out which SOCKS major - * version we're speaking. */ - switch (pf->socksbuf->u[0]) { - case SOCKS4_REQUEST_VERSION: - pf->socks_state = SOCKS_4; - break; - case SOCKS5_REQUEST_VERSION: - pf->socks_state = SOCKS_5_INITIAL; - break; - default: - pfd_close(pf); /* unrecognised version */ - return; - } - } - - BinarySource_BARE_INIT(src, pf->socksbuf->u, pf->socksbuf->len); - get_data(src, pf->socksbuf_consumed); - - while (pf->socks_state != SOCKS_NONE) { - unsigned socks_version, message_type, reserved_byte; - unsigned reply_code, port, ipv4, method; - ptrlen methods; - const char *socks4_hostname; - strbuf *output; - - switch (pf->socks_state) { - case SOCKS_INITIAL: - case SOCKS_NONE: - unreachable("These case values cannot appear"); - - case SOCKS_4: - /* SOCKS 4/4A connect message */ - socks_version = get_byte(src); - message_type = get_byte(src); - - if (get_err(src) == BSE_OUT_OF_DATA) - return; - if (socks_version == SOCKS4_REQUEST_VERSION && - message_type == SOCKS_CMD_CONNECT) { - /* CONNECT message */ - bool name_based = false; - - port = get_uint16(src); - ipv4 = get_uint32(src); - if (ipv4 >= SOCKS4A_NAME_FOLLOWS_BASE && - ipv4 < SOCKS4A_NAME_FOLLOWS_LIMIT) { - /* - * Addresses in this range indicate the SOCKS 4A - * extension to specify a hostname, which comes - * after the username. - */ - name_based = true; - } - get_asciz(src); /* skip username */ - socks4_hostname = name_based ? get_asciz(src) : NULL; - - if (get_err(src) == BSE_OUT_OF_DATA) - return; - if (get_err(src)) - goto socks4_reject; - - pf->port = port; - if (name_based) { - pf->hostname = dupstr(socks4_hostname); - } else { - pf->hostname = ipv4_to_string(ipv4); - } - - output = strbuf_new(); - put_byte(output, SOCKS4_REPLY_VERSION); - put_byte(output, SOCKS4_RESP_SUCCESS); - put_uint16(output, 0); /* null port field */ - put_uint32(output, 0); /* null address field */ - sk_write(pf->s, output->u, output->len); - strbuf_free(output); - - pf->socks_state = SOCKS_NONE; - pf->socksbuf_consumed = src->pos; - break; - } - - socks4_reject: - output = strbuf_new(); - put_byte(output, SOCKS4_REPLY_VERSION); - put_byte(output, SOCKS4_RESP_FAILURE); - put_uint16(output, 0); /* null port field */ - put_uint32(output, 0); /* null address field */ - sk_write(pf->s, output->u, output->len); - strbuf_free(output); - pfd_close(pf); - return; - - case SOCKS_5_INITIAL: - /* SOCKS 5 initial method list */ - socks_version = get_byte(src); - methods = get_pstring(src); - - method = SOCKS5_AUTH_REJECTED; - - /* Search the method list for AUTH_NONE, which is the - * only one this client code can speak */ - for (size_t i = 0; i < methods.len; i++) { - unsigned char this_method = - ((const unsigned char *)methods.ptr)[i]; - if (this_method == SOCKS5_AUTH_NONE) { - method = this_method; - break; - } - } - - if (get_err(src) == BSE_OUT_OF_DATA) - return; - if (get_err(src)) - method = SOCKS5_AUTH_REJECTED; - - output = strbuf_new(); - put_byte(output, SOCKS5_REPLY_VERSION); - put_byte(output, method); - sk_write(pf->s, output->u, output->len); - strbuf_free(output); - - if (method == SOCKS5_AUTH_REJECTED) { - pfd_close(pf); - return; - } - - pf->socks_state = SOCKS_5_CONNECT; - pf->socksbuf_consumed = src->pos; - break; - - case SOCKS_5_CONNECT: - /* SOCKS 5 connect message */ - socks_version = get_byte(src); - message_type = get_byte(src); - reserved_byte = get_byte(src); - - if (socks_version == SOCKS5_REQUEST_VERSION && - message_type == SOCKS_CMD_CONNECT && - reserved_byte == 0) { - - reply_code = SOCKS5_RESP_SUCCESS; - - switch (get_byte(src)) { - case SOCKS5_ADDR_IPV4: - pf->hostname = ipv4_to_string(get_uint32(src)); - break; - case SOCKS5_ADDR_IPV6: - pf->hostname = ipv6_to_string(get_data(src, 16)); - break; - case SOCKS5_ADDR_HOSTNAME: - pf->hostname = mkstr(get_pstring(src)); - break; - default: - pf->hostname = NULL; - reply_code = SOCKS5_RESP_ADDRTYPE_NOT_SUPPORTED; - break; - } - - pf->port = get_uint16(src); - } else { - reply_code = SOCKS5_RESP_COMMAND_NOT_SUPPORTED; - } - - if (get_err(src) == BSE_OUT_OF_DATA) - return; - if (get_err(src)) - reply_code = SOCKS5_RESP_FAILURE; - - output = strbuf_new(); - put_byte(output, SOCKS5_REPLY_VERSION); - put_byte(output, reply_code); - put_byte(output, 0); /* reserved */ - put_byte(output, SOCKS5_ADDR_IPV4); /* IPv4 address follows */ - put_uint32(output, 0); /* bound IPv4 address (unused) */ - put_uint16(output, 0); /* bound port number (unused) */ - sk_write(pf->s, output->u, output->len); - strbuf_free(output); - - if (reply_code != SOCKS5_RESP_SUCCESS) { - pfd_close(pf); - return; - } - - pf->socks_state = SOCKS_NONE; - pf->socksbuf_consumed = src->pos; - break; - } - } - - /* - * We come here when we're ready to make an actual - * connection. - */ - - /* - * Freeze the socket until the SSH server confirms the - * connection. - */ - sk_set_frozen(pf->s, true); - - pf->c = wrap_lportfwd_open(pf->cl, pf->hostname, pf->port, pf->s, - &pf->chan); - } - if (pf->ready) - sshfwd_write(pf->c, data, len); -} - -static void pfd_sent(Plug *plug, size_t bufsize) -{ - struct PortForwarding *pf = - container_of(plug, struct PortForwarding, plug); - - if (pf->c) - sshfwd_unthrottle(pf->c, bufsize); -} - -static const PlugVtable PortForwarding_plugvt = { - .log = pfd_log, - .closing = pfd_closing, - .receive = pfd_receive, - .sent = pfd_sent, -}; - -static void pfd_chan_free(Channel *chan); -static void pfd_open_confirmation(Channel *chan); -static void pfd_open_failure(Channel *chan, const char *errtext); -static size_t pfd_send( - Channel *chan, bool is_stderr, const void *data, size_t len); -static void pfd_send_eof(Channel *chan); -static void pfd_set_input_wanted(Channel *chan, bool wanted); -static char *pfd_log_close_msg(Channel *chan); - -static const ChannelVtable PortForwarding_channelvt = { - .free = pfd_chan_free, - .open_confirmation = pfd_open_confirmation, - .open_failed = pfd_open_failure, - .send = pfd_send, - .send_eof = pfd_send_eof, - .set_input_wanted = pfd_set_input_wanted, - .log_close_msg = pfd_log_close_msg, - .want_close = chan_default_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = chan_no_request_response, -}; - -Channel *portfwd_raw_new(ConnectionLayer *cl, Plug **plug, bool start_ready) -{ - struct PortForwarding *pf; - - pf = new_portfwd_state(); - pf->plug.vt = &PortForwarding_plugvt; - pf->chan.initial_fixed_window_size = 0; - pf->chan.vt = &PortForwarding_channelvt; - pf->input_wanted = true; - - pf->c = NULL; - - pf->cl = cl; - pf->input_wanted = true; - pf->ready = start_ready; - - pf->socks_state = SOCKS_NONE; - pf->hostname = NULL; - pf->port = 0; - - *plug = &pf->plug; - return &pf->chan; -} - -void portfwd_raw_free(Channel *pfchan) -{ - struct PortForwarding *pf; - assert(pfchan->vt == &PortForwarding_channelvt); - pf = container_of(pfchan, struct PortForwarding, chan); - free_portfwd_state(pf); -} - -void portfwd_raw_setup(Channel *pfchan, Socket *s, SshChannel *sc) -{ - struct PortForwarding *pf; - assert(pfchan->vt == &PortForwarding_channelvt); - pf = container_of(pfchan, struct PortForwarding, chan); - - pf->s = s; - pf->c = sc; -} - -/* - * called when someone connects to the local port - */ - -static int pfl_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) -{ - struct PortListener *pl = container_of(p, struct PortListener, plug); - struct PortForwarding *pf; - Channel *chan; - Plug *plug; - Socket *s; - const char *err; - - chan = portfwd_raw_new(pl->cl, &plug, false); - s = constructor(ctx, plug); - if ((err = sk_socket_error(s)) != NULL) { - portfwd_raw_free(chan); - return 1; - } - - pf = container_of(chan, struct PortForwarding, chan); - - if (pl->is_dynamic) { - pf->s = s; - pf->socks_state = SOCKS_INITIAL; - pf->socksbuf = strbuf_new(); - pf->socksbuf_consumed = 0; - pf->port = 0; /* "hostname" buffer is so far empty */ - sk_set_frozen(s, false); /* we want to receive SOCKS _now_! */ - } else { - pf->hostname = dupstr(pl->hostname); - pf->port = pl->port; - portfwd_raw_setup( - chan, s, - wrap_lportfwd_open(pl->cl, pf->hostname, pf->port, s, &pf->chan)); - } - - return 0; -} - -static const PlugVtable PortListener_plugvt = { - .log = pfl_log, - .closing = pfl_closing, - .accepting = pfl_accepting, -}; - -/* - * Add a new port-forwarding listener from srcaddr:port -> desthost:destport. - * - * desthost == NULL indicates dynamic SOCKS port forwarding. - * - * On success, returns NULL and fills in *pl_ret. On error, returns a - * dynamically allocated error message string. - */ -static char *pfl_listen(const char *desthost, int destport, - const char *srcaddr, int port, - ConnectionLayer *cl, Conf *conf, - struct PortListener **pl_ret, int address_family) -{ - const char *err; - struct PortListener *pl; - - /* - * Open socket. - */ - pl = *pl_ret = new_portlistener_state(); - pl->plug.vt = &PortListener_plugvt; - if (desthost) { - pl->hostname = dupstr(desthost); - pl->port = destport; - pl->is_dynamic = false; - } else - pl->is_dynamic = true; - pl->cl = cl; - - pl->s = new_listener(srcaddr, port, &pl->plug, - !conf_get_bool(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; -} - -static char *pfd_log_close_msg(Channel *chan) -{ - return dupstr("Forwarded port closed"); -} - -static void pfd_close(struct PortForwarding *pf) -{ - if (!pf) - return; - - sk_close(pf->s); - free_portfwd_state(pf); -} - -/* - * Terminate a listener. - */ -static void pfl_terminate(struct PortListener *pl) -{ - if (!pl) - return; - - sk_close(pl->s); - free_portlistener_state(pl); -} - -static void pfd_set_input_wanted(Channel *chan, bool wanted) -{ - assert(chan->vt == &PortForwarding_channelvt); - PortForwarding *pf = container_of(chan, PortForwarding, chan); - pf->input_wanted = wanted; - sk_set_frozen(pf->s, !pf->input_wanted); -} - -static void pfd_chan_free(Channel *chan) -{ - assert(chan->vt == &PortForwarding_channelvt); - PortForwarding *pf = container_of(chan, PortForwarding, chan); - pfd_close(pf); -} - -/* - * Called to send data down the raw connection. - */ -static size_t pfd_send( - Channel *chan, bool is_stderr, const void *data, size_t len) -{ - assert(chan->vt == &PortForwarding_channelvt); - PortForwarding *pf = container_of(chan, PortForwarding, chan); - return sk_write(pf->s, data, len); -} - -static void pfd_send_eof(Channel *chan) -{ - assert(chan->vt == &PortForwarding_channelvt); - PortForwarding *pf = container_of(chan, PortForwarding, chan); - sk_write_eof(pf->s); -} - -static void pfd_open_confirmation(Channel *chan) -{ - assert(chan->vt == &PortForwarding_channelvt); - PortForwarding *pf = container_of(chan, PortForwarding, chan); - - pf->ready = true; - sk_set_frozen(pf->s, false); - sk_write(pf->s, NULL, 0); - if (pf->socksbuf) { - sshfwd_write(pf->c, pf->socksbuf->u + pf->socksbuf_consumed, - pf->socksbuf->len - pf->socksbuf_consumed); - strbuf_free(pf->socksbuf); - pf->socksbuf = NULL; - } -} - -static void pfd_open_failure(Channel *chan, const char *errtext) -{ - assert(chan->vt == &PortForwarding_channelvt); - PortForwarding *pf = container_of(chan, PortForwarding, chan); - - logeventf(pf->cl->logctx, - "Forwarded connection refused by remote%s%s", - errtext ? ": " : "", errtext ? errtext : ""); -} - -/* ---------------------------------------------------------------------- - * Code to manage the complete set of currently active port - * forwardings, and update it from Conf. - */ - -struct PortFwdRecord { - 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; -}; - -static int pfr_cmp(void *av, void *bv) -{ - PortFwdRecord *a = (PortFwdRecord *) av; - PortFwdRecord *b = (PortFwdRecord *) 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 void pfr_free(PortFwdRecord *pfr) -{ - /* Dispose of any listening socket. */ - if (pfr->local) - pfl_terminate(pfr->local); - - sfree(pfr->saddr); - sfree(pfr->daddr); - sfree(pfr->sserv); - sfree(pfr->dserv); - sfree(pfr); -} - -struct PortFwdManager { - ConnectionLayer *cl; - Conf *conf; - tree234 *forwardings; -}; - -PortFwdManager *portfwdmgr_new(ConnectionLayer *cl) -{ - PortFwdManager *mgr = snew(PortFwdManager); - - mgr->cl = cl; - mgr->conf = NULL; - mgr->forwardings = newtree234(pfr_cmp); - - return mgr; -} - -void portfwdmgr_close(PortFwdManager *mgr, PortFwdRecord *pfr) -{ - PortFwdRecord *realpfr = del234(mgr->forwardings, pfr); - if (realpfr == pfr) - pfr_free(pfr); -} - -void portfwdmgr_close_all(PortFwdManager *mgr) -{ - PortFwdRecord *pfr; - - while ((pfr = delpos234(mgr->forwardings, 0)) != NULL) - pfr_free(pfr); -} - -void portfwdmgr_free(PortFwdManager *mgr) -{ - portfwdmgr_close_all(mgr); - freetree234(mgr->forwardings); - if (mgr->conf) - conf_free(mgr->conf); - sfree(mgr); -} - -void portfwdmgr_config(PortFwdManager *mgr, Conf *conf) -{ - PortFwdRecord *pfr; - int i; - char *key, *val; - - if (mgr->conf) - conf_free(mgr->conf); - mgr->conf = conf_copy(conf); - - /* - * 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. - */ - for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) - pfr->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(mgr->cl->logctx, "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(mgr->cl->logctx, - "Service lookup failed for destination" - " port \"%s\"", dports); - } - } - } - - if (sport && dport) { - /* Set up a description of the source port. */ - pfr = snew(PortFwdRecord); - pfr->type = type; - pfr->saddr = saddr; - pfr->sserv = sserv ? dupstr(sports) : NULL; - pfr->sport = sport; - pfr->daddr = host; - pfr->dserv = dserv ? dupstr(dports) : NULL; - pfr->dport = dport; - pfr->local = NULL; - pfr->remote = NULL; - pfr->addressfamily = (address_family == '4' ? ADDRTYPE_IPV4 : - address_family == '6' ? ADDRTYPE_IPV6 : - ADDRTYPE_UNSPEC); - - PortFwdRecord *existing = add234(mgr->forwardings, pfr); - if (existing != pfr) { - if (existing->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. - */ - existing->status = KEEP; - } - /* - * Anything else indicates that there was a duplicate - * in our input, which we'll silently ignore. - */ - pfr_free(pfr); - } else { - pfr->status = CREATE; - } - } else { - sfree(saddr); - sfree(host); - } - } - - /* - * Now go through and destroy any port forwardings which were - * not re-enabled. - */ - for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) { - if (pfr->status == DESTROY) { - char *message; - - message = dupprintf("%s port forwarding from %s%s%d", - pfr->type == 'L' ? "local" : - pfr->type == 'R' ? "remote" : "dynamic", - pfr->saddr ? pfr->saddr : "", - pfr->saddr ? ":" : "", - pfr->sport); - - if (pfr->type != 'D') { - char *msg2 = dupprintf("%s to %s:%d", message, - pfr->daddr, pfr->dport); - sfree(message); - message = msg2; - } - - logeventf(mgr->cl->logctx, "Cancelling %s", message); - sfree(message); - - /* pfr->remote or pfr->local may be NULL if setting up a - * forwarding failed. */ - if (pfr->remote) { - /* - * Cancel the port forwarding at the server - * end. - * - * Actually closing the listening port on the server - * side may fail - because in SSH-1 there's no message - * in the protocol to request it! - * - * Instead, we simply remove the record of the - * forwarding from our local end, so that any - * connections the server tries to make on it are - * rejected. - */ - ssh_rportfwd_remove(mgr->cl, pfr->remote); - pfr->remote = NULL; - } else if (pfr->local) { - pfl_terminate(pfr->local); - pfr->local = NULL; - } - - delpos234(mgr->forwardings, i); - pfr_free(pfr); - i--; /* so we don't skip one in the list */ - } - } - - /* - * And finally, set up any new port forwardings (status==CREATE). - */ - for (i = 0; (pfr = index234(mgr->forwardings, i)) != NULL; i++) { - if (pfr->status == CREATE) { - char *sportdesc, *dportdesc; - sportdesc = dupprintf("%s%s%s%s%d%s", - pfr->saddr ? pfr->saddr : "", - pfr->saddr ? ":" : "", - pfr->sserv ? pfr->sserv : "", - pfr->sserv ? "(" : "", - pfr->sport, - pfr->sserv ? ")" : ""); - if (pfr->type == 'D') { - dportdesc = NULL; - } else { - dportdesc = dupprintf("%s:%s%s%d%s", - pfr->daddr, - pfr->dserv ? pfr->dserv : "", - pfr->dserv ? "(" : "", - pfr->dport, - pfr->dserv ? ")" : ""); - } - - if (pfr->type == 'L') { - char *err = pfl_listen(pfr->daddr, pfr->dport, - pfr->saddr, pfr->sport, - mgr->cl, conf, &pfr->local, - pfr->addressfamily); - - logeventf(mgr->cl->logctx, - "Local %sport %s forwarding to %s%s%s", - pfr->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : - pfr->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", - sportdesc, dportdesc, - err ? " failed: " : "", err ? err : ""); - if (err) - sfree(err); - } else if (pfr->type == 'D') { - char *err = pfl_listen(NULL, -1, pfr->saddr, pfr->sport, - mgr->cl, conf, &pfr->local, - pfr->addressfamily); - - logeventf(mgr->cl->logctx, - "Local %sport %s SOCKS dynamic forwarding%s%s", - pfr->addressfamily == ADDRTYPE_IPV4 ? "IPv4 " : - pfr->addressfamily == ADDRTYPE_IPV6 ? "IPv6 " : "", - sportdesc, - err ? " failed: " : "", err ? err : ""); - - if (err) - sfree(err); - } else { - const char *shost; - - if (pfr->saddr) { - shost = pfr->saddr; - } else if (conf_get_bool(conf, CONF_rport_acceptall)) { - shost = ""; - } else { - shost = "localhost"; - } - - pfr->remote = ssh_rportfwd_alloc( - mgr->cl, shost, pfr->sport, pfr->daddr, pfr->dport, - pfr->addressfamily, sportdesc, pfr, NULL); - - if (!pfr->remote) { - logeventf(mgr->cl->logctx, - "Duplicate remote port forwarding to %s:%d", - pfr->daddr, pfr->dport); - pfr_free(pfr); - } else { - logeventf(mgr->cl->logctx, "Requesting remote port %s" - " forward to %s", sportdesc, dportdesc); - } - } - sfree(sportdesc); - sfree(dportdesc); - } - } -} - -bool portfwdmgr_listen(PortFwdManager *mgr, const char *host, int port, - const char *keyhost, int keyport, Conf *conf) -{ - PortFwdRecord *pfr; - - pfr = snew(PortFwdRecord); - pfr->type = 'L'; - pfr->saddr = host ? dupstr(host) : NULL; - pfr->daddr = keyhost ? dupstr(keyhost) : NULL; - pfr->sserv = pfr->dserv = NULL; - pfr->sport = port; - pfr->dport = keyport; - pfr->local = NULL; - pfr->remote = NULL; - pfr->addressfamily = ADDRTYPE_UNSPEC; - - PortFwdRecord *existing = add234(mgr->forwardings, pfr); - if (existing != pfr) { - /* - * We had this record already. Return failure. - */ - pfr_free(pfr); - return false; - } - - char *err = pfl_listen(keyhost, keyport, host, port, - mgr->cl, conf, &pfr->local, pfr->addressfamily); - logeventf(mgr->cl->logctx, - "%s on port %s:%d to forward to client%s%s", - err ? "Failed to listen" : "Listening", host, port, - err ? ": " : "", err ? err : ""); - if (err) { - sfree(err); - del234(mgr->forwardings, pfr); - pfr_free(pfr); - return false; - } - - return true; -} - -bool portfwdmgr_unlisten(PortFwdManager *mgr, const char *host, int port) -{ - PortFwdRecord pfr_key; - - pfr_key.type = 'L'; - /* Safe to cast the const away here, because it will only be used - * by pfr_cmp, which won't write to the string */ - pfr_key.saddr = pfr_key.daddr = (char *)host; - pfr_key.sserv = pfr_key.dserv = NULL; - pfr_key.sport = pfr_key.dport = port; - pfr_key.local = NULL; - pfr_key.remote = NULL; - pfr_key.addressfamily = ADDRTYPE_UNSPEC; - - PortFwdRecord *pfr = del234(mgr->forwardings, &pfr_key); - - if (!pfr) - return false; - - logeventf(mgr->cl->logctx, "Closing listening port %s:%d", host, port); - - pfr_free(pfr); - return true; -} - -/* - * 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 *portfwdmgr_connect(PortFwdManager *mgr, Channel **chan_ret, - char *hostname, int port, SshChannel *c, - int addressfamily) -{ - SockAddr *addr; - const char *err; - char *dummy_realhost = NULL; - struct PortForwarding *pf; - - /* - * Try to find host. - */ - addr = name_lookup(hostname, port, &dummy_realhost, mgr->conf, - addressfamily, NULL, NULL); - 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 = new_portfwd_state(); - *chan_ret = &pf->chan; - pf->plug.vt = &PortForwarding_plugvt; - pf->chan.initial_fixed_window_size = 0; - pf->chan.vt = &PortForwarding_channelvt; - pf->input_wanted = true; - pf->ready = true; - pf->c = c; - pf->cl = mgr->cl; - pf->socks_state = SOCKS_NONE; - - pf->s = new_connection(addr, dummy_realhost, port, - false, true, false, false, &pf->plug, mgr->conf, - NULL); - sfree(dummy_realhost); - if ((err = sk_socket_error(pf->s)) != NULL) { - char *err_ret = dupstr(err); - sk_close(pf->s); - free_portfwd_state(pf); - *chan_ret = NULL; - return err_ret; - } - - return NULL; -} diff --git a/ssh/ppl.h b/ssh/ppl.h deleted file mode 100644 index cb9e044d5..000000000 --- a/ssh/ppl.h +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Abstraction of the various layers of SSH packet-level protocol, - * general enough to take in all three of the main SSH-2 layers and - * both of the SSH-1 phases. - */ - -#ifndef PUTTY_SSHPPL_H -#define PUTTY_SSHPPL_H - -typedef void (*packet_handler_fn_t)(PacketProtocolLayer *ppl, PktIn *pktin); -typedef struct PacketProtocolLayerVtable PacketProtocolLayerVtable; - -struct PacketProtocolLayerVtable { - void (*free)(PacketProtocolLayer *); - void (*process_queue)(PacketProtocolLayer *ppl); - bool (*get_specials)( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); - void (*special_cmd)( - PacketProtocolLayer *ppl, SessionSpecialCode code, int arg); - void (*reconfigure)(PacketProtocolLayer *ppl, Conf *conf); - size_t (*queued_data_size)(PacketProtocolLayer *ppl); - void (*final_output)(PacketProtocolLayer *ppl); - - /* Protocol-level name of this layer. */ - const char *name; -}; - -struct PacketProtocolLayer { - const struct PacketProtocolLayerVtable *vt; - - /* Link to the underlying SSH BPP. */ - BinaryPacketProtocol *bpp; - - /* Queue from which the layer receives its input packets, and one - * to put its output packets on. */ - PktInQueue *in_pq; - PktOutQueue *out_pq; - - /* Idempotent callback that in_pq will be linked to, causing a - * call to the process_queue method. in_pq points to this, so it - * will be automatically triggered by pushing things on the - * layer's input queue, but it can also be triggered on purpose. */ - IdempotentCallback ic_process_queue; - - /* Owner's pointer to this layer. Permits a layer to unilaterally - * abdicate in favour of a replacement, by overwriting this - * pointer and then freeing itself. */ - PacketProtocolLayer **selfptr; - - /* Logging and error-reporting facilities. */ - LogContext *logctx; - Seat *seat; /* for dialog boxes, session output etc */ - Interactor *interactor; /* for ppl_get_iseat */ - Ssh *ssh; /* for session termination + assorted connection-layer ops */ - - /* Known bugs in the remote implementation. */ - unsigned remote_bugs; -}; - -static inline void ssh_ppl_process_queue(PacketProtocolLayer *ppl) -{ ppl->vt->process_queue(ppl); } -static inline bool ssh_ppl_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) -{ return ppl->vt->get_specials(ppl, add_special, ctx); } -static inline void ssh_ppl_special_cmd( - PacketProtocolLayer *ppl, SessionSpecialCode code, int arg) -{ ppl->vt->special_cmd(ppl, code, arg); } -static inline void ssh_ppl_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ ppl->vt->reconfigure(ppl, conf); } -static inline size_t ssh_ppl_queued_data_size(PacketProtocolLayer *ppl) -{ return ppl->vt->queued_data_size(ppl); } -static inline void ssh_ppl_final_output(PacketProtocolLayer *ppl) -{ ppl->vt->final_output(ppl); } - -static inline InteractionReadySeat ppl_get_iseat(PacketProtocolLayer *ppl) -{ return interactor_announce(ppl->interactor); } - -/* ssh_ppl_free is more than just a macro wrapper on the vtable; it - * does centralised parts of the freeing too. */ -void ssh_ppl_free(PacketProtocolLayer *ppl); - -/* Helper routine to point a PPL at its input and output queues. Also - * sets up the IdempotentCallback on the input queue to trigger a call - * to process_queue whenever packets are added to it. */ -void ssh_ppl_setup_queues(PacketProtocolLayer *ppl, - PktInQueue *inq, PktOutQueue *outq); - -/* Routine a PPL can call to abdicate in favour of a replacement, by - * overwriting ppl->selfptr. Has the side effect of freeing 'old', so - * if 'old' actually called this (which is likely) then it should - * avoid dereferencing itself on return from this function! */ -void ssh_ppl_replace(PacketProtocolLayer *old, PacketProtocolLayer *new); - -/* Default implementation of queued_data_size, which just adds up the - * sizes of all the packets in pq_out. A layer can override this if it - * has other things to take into account as well. */ -size_t ssh_ppl_default_queued_data_size(PacketProtocolLayer *ppl); - -/* Default implementation of final_output which outputs nothing. */ -void ssh_ppl_default_final_output(PacketProtocolLayer *ppl); - -PacketProtocolLayer *ssh1_login_new( - Conf *conf, const char *host, int port, - PacketProtocolLayer *successor_layer); -PacketProtocolLayer *ssh1_connection_new( - Ssh *ssh, Conf *conf, bufchain *user_input, ConnectionLayer **cl_out); - -struct DataTransferStats; -struct ssh_connection_shared_gss_state; -PacketProtocolLayer *ssh2_transport_new( - Conf *conf, const char *host, int port, const char *fullhostname, - const char *client_greeting, const char *server_greeting, - struct ssh_connection_shared_gss_state *shgss, - struct DataTransferStats *stats, PacketProtocolLayer *higher_layer, - const SshServerConfig *ssc); -PacketProtocolLayer *ssh2_userauth_new( - PacketProtocolLayer *successor_layer, - const char *hostname, int port, const char *fullhostname, - Filename *keyfile, Filename *detached_cert, - bool show_banner, bool tryagent, bool notrivialauth, - const char *default_username, bool change_username, - bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, - bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss, - const char *auth_plugin); -PacketProtocolLayer *ssh2_connection_new( - Ssh *ssh, ssh_sharing_state *connshare, bool is_simple, - Conf *conf, const char *peer_verstring, bufchain *user_input, - ConnectionLayer **cl_out); - -/* Can't put this in the userauth constructor without having a - * dependency loop at setup time (transport and userauth can't _both_ - * be constructed second and given a pointer to the other). */ -void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth, - PacketProtocolLayer *transport); - -/* Convenience macro for protocol layers to send formatted strings to - * the Event Log. Assumes a function parameter called 'ppl' is in - * scope. */ -#define ppl_logevent(...) ( \ - logevent_and_free((ppl)->logctx, dupprintf(__VA_ARGS__))) - -/* Convenience macro for protocol layers to send formatted strings to - * the terminal. Also expects 'ppl' to be in scope. */ -#define ppl_printf(...) \ - ssh_ppl_user_output_string_and_free(ppl, dupprintf(__VA_ARGS__)) -void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text); - -/* Methods for userauth to communicate back to the transport layer */ -ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ssh2_transport_ptr); -void ssh2_transport_notify_auth_done(PacketProtocolLayer *ssh2_transport_ptr); - -/* Shared method between ssh2 layers (defined in transport2.c) to - * handle the common packets between login and connection: DISCONNECT, - * DEBUG and IGNORE. Those messages are handled by the ssh2transport - * layer if we have one, but in bare ssh2-connection mode they have to - * be handled by ssh2connection. */ -bool ssh2_common_filter_queue(PacketProtocolLayer *ppl); - -/* Method for making a prompts_t in such a way that it will install a - * callback that causes this PPL's process_queue method to be called - * when asynchronous prompt input completes. */ -prompts_t *ssh_ppl_new_prompts(PacketProtocolLayer *ppl); - -/* Methods for ssh1login to pass protocol flags to ssh1connection */ -void ssh1_connection_set_protoflags( - PacketProtocolLayer *ppl, int local, int remote); - -/* Shared get_specials method between the two ssh1 layers */ -bool ssh1_common_get_specials(PacketProtocolLayer *, add_special_fn_t, void *); - -/* Other shared functions between ssh1 layers */ -bool ssh1_common_filter_queue(PacketProtocolLayer *ppl); -void ssh1_compute_session_id( - unsigned char *session_id, const unsigned char *cookie, - RSAKey *hostkey, RSAKey *servkey); - -/* Method used by the SSH server */ -void ssh2_transport_provide_hostkeys(PacketProtocolLayer *ssh2_transport_ptr, - ssh_key *const *hostkeys, int nhostkeys); - -#endif /* PUTTY_SSHPPL_H */ diff --git a/ssh/scpserver.c b/ssh/scpserver.c deleted file mode 100644 index 0995c9986..000000000 --- a/ssh/scpserver.c +++ /dev/null @@ -1,1396 +0,0 @@ -/* - * Server side of the old-school SCP protocol. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sshcr.h" -#include "channel.h" -#include "sftp.h" - -/* - * I think it's worth actually documenting my understanding of what - * this protocol _is_, since I don't know of any other documentation - * of it anywhere. - * - * Format of data stream - * --------------------- - * - * The sending side of an SCP connection - the client, if you're - * uploading files, or the server if you're downloading - sends a data - * stream consisting of a sequence of 'commands', or header records, - * or whatever you want to call them, interleaved with file data. - * - * Each command starts with a letter indicating what type it is, and - * ends with a \n. - * - * The 'C' command introduces an actual file. It is followed by an - * octal file-permissions mask, then a space, then a decimal file - * size, then a space, then the file name up to the termating newline. - * For example, "C0644 12345 filename.txt\n" would be a plausible C - * command. - * - * After the 'C' command, the sending side will transmit exactly as - * many bytes of file data as specified by the size field in the - * header line, followed by a single zero byte. - * - * The 'D' command introduces a subdirectory. Its format is identical - * to 'C', including the size field, but the size field is sent as - * zero. - * - * After the 'D' command, all subsequent C and D commands are taken to - * indicate files that should be placed inside that subdirectory, - * until a terminating 'E' command. - * - * The 'E' command indicates the end of a subdirectory. It has no - * arguments at all (its format is always just "E\n"). After the E - * command, the receiver should revert to placing further downloaded - * files in whatever directory it was placing them before the - * subdirectory opened by the just-closed D. - * - * D and E commands match like parentheses: if you send, say, - * - * C0644 123 foo.txt ( followed by data ) - * D0755 0 subdir - * C0644 123 bar.txt ( followed by data ) - * D0755 0 subsubdir - * C0644 123 baz.txt ( followed by data ) - * E - * C0644 123 quux.txt ( followed by data ) - * E - * C0644 123 wibble.txt ( followed by data ) - * - * then foo.txt, subdir and wibble.txt go in the top-level destination - * directory; bar.txt, subsubdir and quux.txt go in 'subdir'; and - * baz.txt goes in 'subdir/subsubdir'. - * - * The sender terminates the data stream with EOF when it has no more - * files to send. I believe it is not _required_ for all D to be - * closed by an E before this happens - you can elide a trailing - * sequence of E commands without provoking an error message from the - * receiver. - * - * Finally, the 'T' command is sent immediately before a C or D. It is - * followed by four space-separated decimal integers giving an mtime - * and atime to be applied to the file or directory created by the - * following C or D command. The first two integers give the mtime, - * encoded as seconds and microseconds (respectively) since the Unix - * epoch; the next two give the atime, encoded similarly. So - * "T1540373455 0 1540373457 0\n" is an example of a valid T command. - * - * Acknowledgments - * --------------- - * - * The sending side waits for an ack from the receiving side before - * sending each command; before beginning to send the file data - * following a C command; and before sending the final EOF. - * - * (In particular, the receiving side is expected to send an initial - * ack before _anything_ is sent.) - * - * Normally an ack consists of a single zero byte. It's also allowable - * to send a byte with value 1 or 2 followed by a \n-terminated error - * message (where 1 means a non-fatal error and 2 means a fatal one). - * I have to suppose that sending an error message from client to - * server is of limited use, but apparently it's allowed. - * - * Initiation - * ---------- - * - * The protocol is begun by the client sending a command string to the - * server via the SSH-2 "exec" request (or the analogous - * SSH1_CMSG_EXEC_CMD), which indicates that this is an scp session - * rather than any other thing; specifies the direction of transfer; - * says what file(s) are to be sent by the server, or where the server - * should put files that the client is about to send; and a couple of - * other options. - * - * The command string takes the following form: - * - * Start with prefix "scp ", indicating that this is an SCP command at - * all. Otherwise it's a request to run some completely different - * command in the SSH session. - * - * Next the command can contain zero or more of the following options, - * each followed by a space: - * - * "-v" turns on verbose server diagnostics. Of course a server is not - * required to actually produce any, but this is an invitation for it - * to send any it might have available. Diagnostics are free-form, and - * sent as SSH standard-error extended data, so that they are separate - * from the actual data stream as described above. - * - * (Servers can send standard-error output anyway if they like, and in - * case of an actual error, they probably will with or without -v.) - * - * "-r" indicates recursive file transfer, i.e. potentially including - * subdirectories. For a download, this indicates that the client is - * willing to receive subdirectories (a D/E command pair bracketing - * further files and subdirs); without it, the server should only send - * C commands for individual files, followed by EOF. - * - * This flag must also be specified for a recursive upload, because I - * believe at least one server will reject D/E pairs sent by the - * client if the command didn't have -r in it. (Probably a consequence - * of sharing code between download and upload.) - * - * "-p" means preserve file times. In a download, this requests the - * server to send a T command before each C or D. I don't know whether - * any server will insist on having seen this option from the client - * before accepting T commands in an upload, but it is probably - * sensible to send it anyway. - * - * "-d", in an upload, means that the destination pathname (see below) - * is expected to be a directory, and that uploaded files (and - * subdirs) should be put inside it. Without -d, the semantics are - * that _if_ the destination exists and is a directory, then files - * will be put in it, whereas if it is not, then just a single file - * (or subdir) upload is expected, which will be placed at that exact - * pathname. - * - * In a download, I observe that clients tend to send -d if they are - * requesting multiple files or a wildcard, but as far as I know, - * servers ignore it. - * - * After all those optional options, there is a single mandatory - * option indicating the direction of transfer, which is either "-f" - * or "-t". "-f" indicates a download; "-t" indicates an upload. - * - * After that mandatory option, there is a single space, followed by - * the name(s) of files to transfer. - * - * This file name field is transmitted with NO QUOTING, in spite of - * the fact that a server will typically interpret it as a shell - * command. You'd think this couldn't possibly work, in the face of - * almost any filename with an interesting character in it - and you'd - * be right. Or rather (you might argue), it works 'as designed', but - * it's designed in a weird way, in that it's the user's - * responsibility to apply quoting on the client command line to get - * the filename through the shell that will decode things on the - * server side. - * - * But one effect of this is that if you issue a download command - * including a wildcard, say "scp -f somedir/foo*.txt", then the shell - * will expand the wildcard, and actually run the server-side scp - * program with multiple arguments, say "somedir/foo.txt - * somedir/quux.txt", leading to the download sending multiple C - * commands. This clearly _is_ intended: it's how a command such as - * 'scp server:somedir/foo*.txt destdir' can work at all. - * - * (You would think, given that, that it might also be legal to send - * multiple space-separated filenames in order to trigger a download - * of exactly those files. Given how scp is invoked in practice on a - * typical server, this would surely actually work, but my observation - * is that scp clients don't in fact try this - if you run OpenSSH's - * scp by saying 'scp server:foo server:bar destdir' then it will make - * two separate connections to the server for the two files, rather - * than sending a single space-separated remote command. PSCP won't - * even do that, and will make you do it in two separate runs.) - * - * So, some examples: - * - * - "scp -f filename.txt" - * - * Server should send a single C command (plus data) for that file. - * Client ought to ignore the filename in the C command, in favour - * of saving the file under the name implied by the user's command - * line. - * - * - "scp -f file*.txt" - * - * Server sends zero or more C commands, then EOF. Client will have - * been given a target directory to put them all in, and will name - * each one according to the name in the C command. - * - * (You'd like the client to validate the filenames against the - * wildcard it sent, to ensure a malicious server didn't try to - * overwrite some path like ".bashrc" when you thought you were - * downloading only normal text files. But wildcard semantics are - * chosen by the server, so this is essentially hopeless to do - * rigorously.) - * - * - "scp -f -r somedir" - * - * Assuming somedir is actually a directory, server sends a D/E - * pair, in between which are the contents of the directory - * (perhaps including further nested D/E pairs). Client probably - * ignores the name field of the outermost D - * - * - "scp -f -r some*wild*card*" - * - * Server sends multiple C or D-stuff-E, one for each top-level - * thing matching the wildcard, whether it's a file or a directory. - * - * - "scp -t -d some_dir" - * - * Client sends stuff, and server deposits each file at - * some_dir/. - * - * - "scp -t some_path_name" - * - * Client sends one C command, and server deposits it at - * some_path_name itself, or in some_path_name/, depending whether some_path_name was already a - * directory or not. - */ - -/* - * Here's a useful debugging aid: run over a binary file containing - * the complete contents of the sender's data stream (e.g. extracted - * by contrib/logparse.pl -d), it removes the file contents, leaving - * only the list of commands, so you can see what the server sent. - * - * perl -pe 'read ARGV,$x,1+$1 if/^C\S+ (\d+)/' - */ - -/* ---------------------------------------------------------------------- - * Shared system for receiving replies from the SftpServer, and - * putting them into a set of ordinary variables rather than - * marshalling them into actual SFTP reply packets that we'd only have - * to unmarshal again. - */ - -typedef struct ScpReplyReceiver ScpReplyReceiver; -struct ScpReplyReceiver { - bool err; - unsigned code; - char *errmsg; - struct fxp_attrs attrs; - ptrlen name, handle, data; - - SftpReplyBuilder srb; -}; - -static void scp_reply_ok(SftpReplyBuilder *srb) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = false; -} - -static void scp_reply_error( - SftpReplyBuilder *srb, unsigned code, const char *msg) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = true; - reply->code = code; - sfree(reply->errmsg); - reply->errmsg = dupstr(msg); -} - -static void scp_reply_name_count(SftpReplyBuilder *srb, unsigned count) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = false; -} - -static void scp_reply_full_name( - SftpReplyBuilder *srb, ptrlen name, - ptrlen longname, struct fxp_attrs attrs) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - char *p; - reply->err = false; - sfree((void *)reply->name.ptr); - reply->name.ptr = p = mkstr(name); - reply->name.len = name.len; - reply->attrs = attrs; -} - -static void scp_reply_simple_name(SftpReplyBuilder *srb, ptrlen name) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = false; -} - -static void scp_reply_handle(SftpReplyBuilder *srb, ptrlen handle) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - char *p; - reply->err = false; - sfree((void *)reply->handle.ptr); - reply->handle.ptr = p = mkstr(handle); - reply->handle.len = handle.len; -} - -static void scp_reply_data(SftpReplyBuilder *srb, ptrlen data) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - char *p; - reply->err = false; - sfree((void *)reply->data.ptr); - reply->data.ptr = p = mkstr(data); - reply->data.len = data.len; -} - -static void scp_reply_attrs( - SftpReplyBuilder *srb, struct fxp_attrs attrs) -{ - ScpReplyReceiver *reply = container_of(srb, ScpReplyReceiver, srb); - reply->err = false; - reply->attrs = attrs; -} - -static const SftpReplyBuilderVtable ScpReplyReceiver_vt = { - .reply_ok = scp_reply_ok, - .reply_error = scp_reply_error, - .reply_simple_name = scp_reply_simple_name, - .reply_name_count = scp_reply_name_count, - .reply_full_name = scp_reply_full_name, - .reply_handle = scp_reply_handle, - .reply_data = scp_reply_data, - .reply_attrs = scp_reply_attrs, -}; - -static void scp_reply_setup(ScpReplyReceiver *reply) -{ - memset(reply, 0, sizeof(*reply)); - reply->srb.vt = &ScpReplyReceiver_vt; -} - -static void scp_reply_cleanup(ScpReplyReceiver *reply) -{ - sfree(reply->errmsg); - sfree((void *)reply->name.ptr); - sfree((void *)reply->handle.ptr); - sfree((void *)reply->data.ptr); -} - -/* ---------------------------------------------------------------------- - * Source end of the SCP protocol. - */ - -#define SCP_MAX_BACKLOG 65536 - -typedef struct ScpSource ScpSource; -typedef struct ScpSourceStackEntry ScpSourceStackEntry; - -struct ScpSource { - SftpServer *sf; - - int acks; - bool expect_newline, eof, throttled, finished; - - SshChannel *sc; - ScpSourceStackEntry *head; - bool recursive; - bool send_file_times; - - strbuf *pending_commands[3]; - int n_pending_commands; - - uint64_t file_offset, file_size; - - ScpReplyReceiver reply; - - ScpServer scpserver; -}; - -typedef enum ScpSourceNodeType ScpSourceNodeType; -enum ScpSourceNodeType { SCP_ROOTPATH, SCP_NAME, SCP_READDIR, SCP_READFILE }; - -struct ScpSourceStackEntry { - ScpSourceStackEntry *next; - ScpSourceNodeType type; - ptrlen pathname, handle; - const char *wildcard; - struct fxp_attrs attrs; -}; - -static void scp_source_push(ScpSource *scp, ScpSourceNodeType type, - ptrlen pathname, ptrlen handle, - const struct fxp_attrs *attrs, const char *wc) -{ - size_t wc_len = wc ? strlen(wc)+1 : 0; - ScpSourceStackEntry *node = snew_plus( - ScpSourceStackEntry, pathname.len + handle.len + wc_len); - char *namebuf = snew_plus_get_aux(node); - memcpy(namebuf, pathname.ptr, pathname.len); - node->pathname = make_ptrlen(namebuf, pathname.len); - memcpy(namebuf + pathname.len, handle.ptr, handle.len); - node->handle = make_ptrlen(namebuf + pathname.len, handle.len); - if (wc) { - strcpy(namebuf + pathname.len + handle.len, wc); - node->wildcard = namebuf + pathname.len + handle.len; - } else { - node->wildcard = NULL; - } - node->attrs = attrs ? *attrs : no_attrs; - node->type = type; - node->next = scp->head; - scp->head = node; -} - -static char *scp_source_err_base(ScpSource *scp, const char *fmt, va_list ap) -{ - char *msg = dupvprintf(fmt, ap); - sshfwd_write_ext(scp->sc, true, msg, strlen(msg)); - sshfwd_write_ext(scp->sc, true, "\012", 1); - return msg; -} -static PRINTF_LIKE(2, 3) void scp_source_err( - ScpSource *scp, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - sfree(scp_source_err_base(scp, fmt, ap)); - va_end(ap); -} -static PRINTF_LIKE(2, 3) void scp_source_abort( - ScpSource *scp, const char *fmt, ...) -{ - va_list ap; - char *msg; - - va_start(ap, fmt); - msg = scp_source_err_base(scp, fmt, ap); - va_end(ap); - - sshfwd_send_exit_status(scp->sc, 1); - sshfwd_write_eof(scp->sc); - sshfwd_initiate_close(scp->sc, msg); - - scp->finished = true; -} - -static void scp_source_push_name( - ScpSource *scp, ptrlen pathname, struct fxp_attrs attrs, const char *wc) -{ - if (!(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) { - scp_source_err(scp, "unable to read file permissions for %.*s", - PTRLEN_PRINTF(pathname)); - return; - } - if (attrs.permissions & PERMS_DIRECTORY) { - if (!scp->recursive && !wc) { - scp_source_err(scp, "%.*s: is a directory", - PTRLEN_PRINTF(pathname)); - return; - } - } else { - if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) { - scp_source_err(scp, "unable to read file size for %.*s", - PTRLEN_PRINTF(pathname)); - return; - } - } - - scp_source_push(scp, SCP_NAME, pathname, PTRLEN_LITERAL(""), &attrs, wc); -} - -static void scp_source_free(ScpServer *s); -static size_t scp_source_send(ScpServer *s, const void *data, size_t length); -static void scp_source_eof(ScpServer *s); -static void scp_source_throttle(ScpServer *s, bool throttled); - -static const ScpServerVtable ScpSource_ScpServer_vt = { - .free = scp_source_free, - .send = scp_source_send, - .throttle = scp_source_throttle, - .eof = scp_source_eof, -}; - -static ScpSource *scp_source_new( - SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen pathname) -{ - ScpSource *scp = snew(ScpSource); - memset(scp, 0, sizeof(*scp)); - - scp->scpserver.vt = &ScpSource_ScpServer_vt; - scp_reply_setup(&scp->reply); - scp->sc = sc; - scp->sf = sftpsrv_new(sftpserver_vt); - scp->n_pending_commands = 0; - - scp_source_push(scp, SCP_ROOTPATH, pathname, PTRLEN_LITERAL(""), - NULL, NULL); - - return scp; -} - -static void scp_source_free(ScpServer *s) -{ - ScpSource *scp = container_of(s, ScpSource, scpserver); - scp_reply_cleanup(&scp->reply); - while (scp->n_pending_commands > 0) - strbuf_free(scp->pending_commands[--scp->n_pending_commands]); - while (scp->head) { - ScpSourceStackEntry *node = scp->head; - scp->head = node->next; - sfree(node); - } - - delete_callbacks_for_context(scp); - - sfree(scp); -} - -static void scp_source_send_E(ScpSource *scp) -{ - strbuf *cmd; - - assert(scp->n_pending_commands == 0); - - scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - put_fmt(cmd, "E\012"); -} - -static void scp_source_send_CD( - ScpSource *scp, char cmdchar, - struct fxp_attrs attrs, uint64_t size, ptrlen name) -{ - strbuf *cmd; - - assert(scp->n_pending_commands == 0); - - if (scp->send_file_times && (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) { - scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - /* Our SFTP-based filesystem API doesn't support microsecond times */ - put_fmt(cmd, "T%lu 0 %lu 0\012", attrs.mtime, attrs.atime); - } - - const char *slash; - while ((slash = memchr(name.ptr, '/', name.len)) != NULL) - name = make_ptrlen( - slash+1, name.len - (slash+1 - (const char *)name.ptr)); - - scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - put_fmt(cmd, "%c%04o %"PRIu64" %.*s\012", cmdchar, - (unsigned)(attrs.permissions & 07777), - size, PTRLEN_PRINTF(name)); - - if (cmdchar == 'C') { - /* We'll also wait for an ack before sending the file data, - * which we record by saving a zero-length 'command' to be - * sent after the C. */ - scp->pending_commands[scp->n_pending_commands++] = cmd = strbuf_new(); - } -} - -static void scp_source_process_stack(ScpSource *scp); -static void scp_source_process_stack_cb(void *vscp) -{ - ScpSource *scp = (ScpSource *)vscp; - if (scp->finished) - return; /* this callback is out of date */ - scp_source_process_stack(scp); -} -static void scp_requeue(ScpSource *scp) -{ - queue_toplevel_callback(scp_source_process_stack_cb, scp); -} - -static void scp_source_process_stack(ScpSource *scp) -{ - if (scp->throttled) - return; - - while (scp->n_pending_commands > 0) { - /* Expect an ack, and consume it */ - if (scp->eof) { - scp_source_abort( - scp, "scp: received client EOF, abandoning transfer"); - return; - } - if (scp->acks == 0) - return; - scp->acks--; - - /* - * Now send the actual command (unless it was the phony - * zero-length one that indicates our need for an ack before - * beginning to send file data). - */ - - if (scp->pending_commands[0]->len) - sshfwd_write(scp->sc, scp->pending_commands[0]->s, - scp->pending_commands[0]->len); - - strbuf_free(scp->pending_commands[0]); - scp->n_pending_commands--; - if (scp->n_pending_commands > 0) { - /* - * We still have at least one pending command to send, so - * move up the queue. - * - * (We do that with a bodgy memmove, because there are at - * most a bounded number of commands ever pending at once, - * so no need to worry about quadratic time.) - */ - memmove(scp->pending_commands, scp->pending_commands+1, - scp->n_pending_commands * sizeof(*scp->pending_commands)); - } - } - - /* - * Mostly, we start by waiting for an ack byte from the receiver. - */ - if (scp->head && scp->head->type == SCP_READFILE && scp->file_offset) { - /* - * Exception: if we're already in the middle of transferring a - * file, we'll be called back here because the channel backlog - * has cleared; we don't need to wait for an ack. - */ - } else if (scp->head && scp->head->type == SCP_ROOTPATH) { - /* - * Another exception: the initial action node that makes us - * stat the root path. We'll translate it into an SCP_NAME, - * and _that_ will require an ack. - */ - ScpSourceStackEntry *node = scp->head; - scp->head = node->next; - - /* - * Start by checking if there's a wildcard involved in the - * root path. - */ - char *rootpath_str = mkstr(node->pathname); - char *rootpath_unesc = snewn(1+node->pathname.len, char); - ptrlen pathname; - const char *wildcard; - - if (wc_unescape(rootpath_unesc, rootpath_str)) { - /* - * We successfully removed instances of the escape - * character used in our wildcard syntax, without - * encountering any actual wildcard chars - i.e. this is - * not a wildcard, just a single file. The simple case. - */ - pathname = ptrlen_from_asciz(rootpath_str); - wildcard = NULL; - } else { - /* - * This is a wildcard. Separate it into a directory name - * (which we enforce mustn't contain wc characters, for - * simplicity) and a wildcard to match leaf names. - */ - char *last_slash = strrchr(rootpath_str, '/'); - - if (last_slash) { - wildcard = last_slash + 1; - *last_slash = '\0'; - if (!wc_unescape(rootpath_unesc, rootpath_str)) { - scp_source_abort(scp, "scp: wildcards in path components " - "before the file name not supported"); - sfree(rootpath_str); - sfree(rootpath_unesc); - return; - } - - pathname = ptrlen_from_asciz(rootpath_unesc); - } else { - pathname = PTRLEN_LITERAL("."); - wildcard = rootpath_str; - } - } - - /* - * Now we know what directory we're scanning, and what - * wildcard (if any) we're using to match the filenames we get - * back. - */ - sftpsrv_stat(scp->sf, &scp->reply.srb, pathname, true); - if (scp->reply.err) { - scp_source_abort( - scp, "%.*s: unable to access: %s", - PTRLEN_PRINTF(pathname), scp->reply.errmsg); - sfree(rootpath_str); - sfree(rootpath_unesc); - sfree(node); - return; - } - - scp_source_push_name(scp, pathname, scp->reply.attrs, wildcard); - - sfree(rootpath_str); - sfree(rootpath_unesc); - sfree(node); - scp_requeue(scp); - return; - } else { - } - - if (scp->head && scp->head->type == SCP_READFILE) { - /* - * Transfer file data if our backlog hasn't filled up. - */ - int backlog; - uint64_t limit = scp->file_size - scp->file_offset; - if (limit > 4096) - limit = 4096; - if (limit > 0) { - sftpsrv_read(scp->sf, &scp->reply.srb, scp->head->handle, - scp->file_offset, limit); - if (scp->reply.err) { - scp_source_abort( - scp, "%.*s: unable to read: %s", - PTRLEN_PRINTF(scp->head->pathname), scp->reply.errmsg); - return; - } - - backlog = sshfwd_write( - scp->sc, scp->reply.data.ptr, scp->reply.data.len); - scp->file_offset += scp->reply.data.len; - - if (backlog < SCP_MAX_BACKLOG) - scp_requeue(scp); - return; - } - - /* - * If we're done, send a terminating zero byte, close our file - * handle, and pop the stack. - */ - sshfwd_write(scp->sc, "\0", 1); - sftpsrv_close(scp->sf, &scp->reply.srb, scp->head->handle); - ScpSourceStackEntry *node = scp->head; - scp->head = node->next; - sfree(node); - scp_requeue(scp); - return; - } - - /* - * If our queue is actually empty, send outgoing EOF. - */ - if (!scp->head) { - sshfwd_send_exit_status(scp->sc, 0); - sshfwd_write_eof(scp->sc); - sshfwd_initiate_close(scp->sc, NULL); - scp->finished = true; - return; - } - - /* - * Otherwise, handle a command. - */ - ScpSourceStackEntry *node = scp->head; - scp->head = node->next; - - if (node->type == SCP_READDIR) { - sftpsrv_readdir(scp->sf, &scp->reply.srb, node->handle, 1, true); - if (scp->reply.err) { - if (scp->reply.code != SSH_FX_EOF) - scp_source_err(scp, "%.*s: unable to list directory: %s", - PTRLEN_PRINTF(node->pathname), - scp->reply.errmsg); - sftpsrv_close(scp->sf, &scp->reply.srb, node->handle); - - if (!node->wildcard) { - /* - * Send 'pop stack' or 'end of directory' command, - * unless this was the topmost READDIR in a - * wildcard-based retrieval (in which case we didn't - * send a D command to start, so an E now would have - * no stack entry to pop). - */ - scp_source_send_E(scp); - } - } else if (ptrlen_eq_string(scp->reply.name, ".") || - ptrlen_eq_string(scp->reply.name, "..") || - (node->wildcard && - !wc_match_pl(node->wildcard, scp->reply.name))) { - /* Skip special directory names . and .., and anything - * that doesn't match our wildcard (if we have one). */ - scp->head = node; /* put back the unfinished READDIR */ - node = NULL; /* and prevent it being freed */ - } else { - strbuf *subpath = strbuf_new(); - put_datapl(subpath, node->pathname); - put_byte(subpath, '/'); - put_datapl(subpath, scp->reply.name); - - scp->head = node; /* put back the unfinished READDIR */ - node = NULL; /* and prevent it being freed */ - scp_source_push_name(scp, ptrlen_from_strbuf(subpath), - scp->reply.attrs, NULL); - - strbuf_free(subpath); - } - } else if (node->attrs.permissions & PERMS_DIRECTORY) { - assert(scp->recursive || node->wildcard); - - if (!node->wildcard) - scp_source_send_CD(scp, 'D', node->attrs, 0, node->pathname); - sftpsrv_opendir(scp->sf, &scp->reply.srb, node->pathname); - if (scp->reply.err) { - scp_source_err( - scp, "%.*s: unable to access: %s", - PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); - - if (!node->wildcard) { - /* Send 'pop stack' or 'end of directory' command. */ - scp_source_send_E(scp); - } - } else { - scp_source_push( - scp, SCP_READDIR, node->pathname, - scp->reply.handle, NULL, node->wildcard); - } - } else { - sftpsrv_open(scp->sf, &scp->reply.srb, - node->pathname, SSH_FXF_READ, no_attrs); - if (scp->reply.err) { - scp_source_err( - scp, "%.*s: unable to open: %s", - PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); - scp_requeue(scp); - return; - } - sftpsrv_fstat(scp->sf, &scp->reply.srb, scp->reply.handle); - if (scp->reply.err) { - scp_source_err( - scp, "%.*s: unable to stat: %s", - PTRLEN_PRINTF(node->pathname), scp->reply.errmsg); - sftpsrv_close(scp->sf, &scp->reply.srb, scp->reply.handle); - scp_requeue(scp); - return; - } - scp->file_offset = 0; - scp->file_size = scp->reply.attrs.size; - scp_source_send_CD(scp, 'C', node->attrs, - scp->file_size, node->pathname); - scp_source_push( - scp, SCP_READFILE, node->pathname, scp->reply.handle, NULL, NULL); - } - sfree(node); - scp_requeue(scp); -} - -static size_t scp_source_send(ScpServer *s, const void *vdata, size_t length) -{ - ScpSource *scp = container_of(s, ScpSource, scpserver); - const char *data = (const char *)vdata; - size_t i; - - if (scp->finished) - return 0; - - for (i = 0; i < length; i++) { - if (scp->expect_newline) { - if (data[i] == '\012') { - /* End of an error message following a 1 byte */ - scp->expect_newline = false; - scp->acks++; - } - } else { - switch (data[i]) { - case 0: /* ordinary ack */ - scp->acks++; - break; - case 1: /* non-fatal error; consume it */ - scp->expect_newline = true; - break; - case 2: - scp_source_abort( - scp, "terminating on fatal error from client"); - return 0; - default: - scp_source_abort( - scp, "unrecognised response code from client"); - return 0; - } - } - } - - scp_source_process_stack(scp); - - return 0; -} - -static void scp_source_throttle(ScpServer *s, bool throttled) -{ - ScpSource *scp = container_of(s, ScpSource, scpserver); - - if (scp->finished) - return; - - scp->throttled = throttled; - if (!throttled) - scp_source_process_stack(scp); -} - -static void scp_source_eof(ScpServer *s) -{ - ScpSource *scp = container_of(s, ScpSource, scpserver); - - if (scp->finished) - return; - - scp->eof = true; - scp_source_process_stack(scp); -} - -/* ---------------------------------------------------------------------- - * Sink end of the SCP protocol. - */ - -typedef struct ScpSink ScpSink; -typedef struct ScpSinkStackEntry ScpSinkStackEntry; - -struct ScpSink { - SftpServer *sf; - - SshChannel *sc; - ScpSinkStackEntry *head; - - uint64_t file_offset, file_size; - unsigned long atime, mtime; - bool got_file_times; - - bufchain data; - bool input_eof; - strbuf *command; - char command_chr; - - strbuf *filename_sb; - ptrlen filename; - struct fxp_attrs attrs; - - char *errmsg; - - int crState; - - ScpReplyReceiver reply; - - ScpServer scpserver; -}; - -struct ScpSinkStackEntry { - ScpSinkStackEntry *next; - ptrlen destpath; - - /* - * If isdir is true, then destpath identifies a directory that the - * files we receive should be created inside. If it's false, then - * it identifies the exact pathname the next file we receive - * should be created _as_ - regardless of the filename in the 'C' - * command. - */ - bool isdir; -}; - -static void scp_sink_push(ScpSink *scp, ptrlen pathname, bool isdir) -{ - ScpSinkStackEntry *node = snew_plus(ScpSinkStackEntry, pathname.len); - char *p = snew_plus_get_aux(node); - - node->destpath.ptr = p; - node->destpath.len = pathname.len; - memcpy(p, pathname.ptr, pathname.len); - node->isdir = isdir; - - node->next = scp->head; - scp->head = node; -} - -static void scp_sink_pop(ScpSink *scp) -{ - ScpSinkStackEntry *node = scp->head; - scp->head = node->next; - sfree(node); -} - -static void scp_sink_free(ScpServer *s); -static size_t scp_sink_send(ScpServer *s, const void *data, size_t length); -static void scp_sink_eof(ScpServer *s); -static void scp_sink_throttle(ScpServer *s, bool throttled) {} - -static const ScpServerVtable ScpSink_ScpServer_vt = { - .free = scp_sink_free, - .send = scp_sink_send, - .throttle = scp_sink_throttle, - .eof = scp_sink_eof, -}; - -static void scp_sink_coroutine(ScpSink *scp); -static void scp_sink_start_callback(void *vscp) -{ - scp_sink_coroutine((ScpSink *)vscp); -} - -static ScpSink *scp_sink_new( - SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen pathname, - bool pathname_is_definitely_dir) -{ - ScpSink *scp = snew(ScpSink); - memset(scp, 0, sizeof(*scp)); - - scp->scpserver.vt = &ScpSink_ScpServer_vt; - scp_reply_setup(&scp->reply); - scp->sc = sc; - scp->sf = sftpsrv_new(sftpserver_vt); - bufchain_init(&scp->data); - scp->command = strbuf_new(); - scp->filename_sb = strbuf_new(); - - if (!pathname_is_definitely_dir) { - /* - * If our root pathname is not already expected to be a - * directory because of the -d option in the command line, - * test it ourself to see whether it is or not. - */ - sftpsrv_stat(scp->sf, &scp->reply.srb, pathname, true); - if (!scp->reply.err && - (scp->reply.attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) && - (scp->reply.attrs.permissions & PERMS_DIRECTORY)) - pathname_is_definitely_dir = true; - } - scp_sink_push(scp, pathname, pathname_is_definitely_dir); - - queue_toplevel_callback(scp_sink_start_callback, scp); - - return scp; -} - -static void scp_sink_free(ScpServer *s) -{ - ScpSink *scp = container_of(s, ScpSink, scpserver); - - scp_reply_cleanup(&scp->reply); - bufchain_clear(&scp->data); - strbuf_free(scp->command); - strbuf_free(scp->filename_sb); - while (scp->head) - scp_sink_pop(scp); - sfree(scp->errmsg); - - delete_callbacks_for_context(scp); - - sfree(scp); -} - -static void scp_sink_coroutine(ScpSink *scp) -{ - crBegin(scp->crState); - - while (1) { - /* - * Send an ack, and read a command. - */ - sshfwd_write(scp->sc, "\0", 1); - strbuf_clear(scp->command); - while (1) { - crMaybeWaitUntilV(scp->input_eof || bufchain_size(&scp->data) > 0); - if (scp->input_eof) - goto done; - - ptrlen data = bufchain_prefix(&scp->data); - const char *cdata = data.ptr; - const char *newline = memchr(cdata, '\012', data.len); - if (newline) - data.len = (int)(newline+1 - cdata); - put_data(scp->command, cdata, data.len); - bufchain_consume(&scp->data, data.len); - - if (newline) - break; - } - - /* - * Parse the command. - */ - strbuf_chomp(scp->command, '\n'); - scp->command_chr = scp->command->len > 0 ? scp->command->s[0] : '\0'; - if (scp->command_chr == 'T') { - unsigned long dummy1, dummy2; - if (sscanf(scp->command->s, "T%lu %lu %lu %lu", - &scp->mtime, &dummy1, &scp->atime, &dummy2) != 4) - goto parse_error; - scp->got_file_times = true; - } else if (scp->command_chr == 'C' || scp->command_chr == 'D') { - /* - * Common handling of the start of this case, because the - * messages are parsed similarly. We diverge later. - */ - const char *q, *p = scp->command->s + 1; /* skip the 'C' */ - - scp->attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; - scp->attrs.permissions = 0; - while (*p >= '0' && *p <= '7') { - scp->attrs.permissions = - scp->attrs.permissions * 8 + (*p - '0'); - p++; - } - if (*p != ' ') - goto parse_error; - p++; - - q = p; - while (*p >= '0' && *p <= '9') - p++; - if (*p != ' ') - goto parse_error; - p++; - scp->file_size = strtoull(q, NULL, 10); - - ptrlen leafname = make_ptrlen( - p, scp->command->len - (p - scp->command->s)); - strbuf_clear(scp->filename_sb); - put_datapl(scp->filename_sb, scp->head->destpath); - if (scp->head->isdir) { - if (scp->filename_sb->len > 0 && - scp->filename_sb->s[scp->filename_sb->len-1] - != '/') - put_byte(scp->filename_sb, '/'); - put_datapl(scp->filename_sb, leafname); - } - scp->filename = ptrlen_from_strbuf(scp->filename_sb); - - if (scp->got_file_times) { - scp->attrs.mtime = scp->mtime; - scp->attrs.atime = scp->atime; - scp->attrs.flags |= SSH_FILEXFER_ATTR_ACMODTIME; - } - scp->got_file_times = false; - - if (scp->command_chr == 'D') { - sftpsrv_mkdir(scp->sf, &scp->reply.srb, - scp->filename, scp->attrs); - - if (scp->reply.err) { - scp->errmsg = dupprintf( - "'%.*s': unable to create directory: %s", - PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); - goto done; - } - - scp_sink_push(scp, scp->filename, true); - } else { - sftpsrv_open(scp->sf, &scp->reply.srb, scp->filename, - SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC, - scp->attrs); - if (scp->reply.err) { - scp->errmsg = dupprintf( - "'%.*s': unable to open file: %s", - PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); - goto done; - } - - /* - * Now send an ack, and read the file data. - */ - sshfwd_write(scp->sc, "\0", 1); - scp->file_offset = 0; - while (scp->file_offset < scp->file_size) { - ptrlen data; - uint64_t this_len, remaining; - - crMaybeWaitUntilV( - scp->input_eof || bufchain_size(&scp->data) > 0); - if (scp->input_eof) { - sftpsrv_close(scp->sf, &scp->reply.srb, - scp->reply.handle); - goto done; - } - - data = bufchain_prefix(&scp->data); - this_len = data.len; - remaining = scp->file_size - scp->file_offset; - if (this_len > remaining) - this_len = remaining; - sftpsrv_write(scp->sf, &scp->reply.srb, - scp->reply.handle, scp->file_offset, - make_ptrlen(data.ptr, this_len)); - if (scp->reply.err) { - scp->errmsg = dupprintf( - "'%.*s': unable to write to file: %s", - PTRLEN_PRINTF(scp->filename), scp->reply.errmsg); - goto done; - } - bufchain_consume(&scp->data, this_len); - scp->file_offset += this_len; - } - - /* - * Wait for the trailing NUL byte. - */ - crMaybeWaitUntilV( - scp->input_eof || bufchain_size(&scp->data) > 0); - if (scp->input_eof) { - sftpsrv_close(scp->sf, &scp->reply.srb, - scp->reply.handle); - goto done; - } - bufchain_consume(&scp->data, 1); - } - } else if (scp->command_chr == 'E') { - if (!scp->head) { - scp->errmsg = dupstr("received E command without matching D"); - goto done; - } - scp_sink_pop(scp); - scp->got_file_times = false; - } else { - ptrlen cmd_pl; - - /* - * Also come here if any of the above cases run into - * parsing difficulties. - */ - parse_error: - cmd_pl = ptrlen_from_strbuf(scp->command); - scp->errmsg = dupprintf("unrecognised scp command '%.*s'", - PTRLEN_PRINTF(cmd_pl)); - goto done; - } - } - - done: - if (scp->errmsg) { - sshfwd_write_ext(scp->sc, true, scp->errmsg, strlen(scp->errmsg)); - sshfwd_write_ext(scp->sc, true, "\012", 1); - sshfwd_send_exit_status(scp->sc, 1); - } else { - sshfwd_send_exit_status(scp->sc, 0); - } - sshfwd_write_eof(scp->sc); - sshfwd_initiate_close(scp->sc, scp->errmsg); - while (1) crReturnV; - - crFinishV; -} - -static size_t scp_sink_send(ScpServer *s, const void *data, size_t length) -{ - ScpSink *scp = container_of(s, ScpSink, scpserver); - - if (!scp->input_eof) { - bufchain_add(&scp->data, data, length); - scp_sink_coroutine(scp); - } - return 0; -} - -static void scp_sink_eof(ScpServer *s) -{ - ScpSink *scp = container_of(s, ScpSink, scpserver); - - scp->input_eof = true; - scp_sink_coroutine(scp); -} - -/* ---------------------------------------------------------------------- - * Top-level error handler, instantiated in the case where the user - * sent a command starting with "scp " that we couldn't make sense of. - */ - -typedef struct ScpError ScpError; - -struct ScpError { - SshChannel *sc; - char *message; - ScpServer scpserver; -}; - -static void scp_error_free(ScpServer *s); - -static size_t scp_error_send(ScpServer *s, const void *data, size_t length) -{ return 0; } -static void scp_error_eof(ScpServer *s) {} -static void scp_error_throttle(ScpServer *s, bool throttled) {} - -static const ScpServerVtable ScpError_ScpServer_vt = { - .free = scp_error_free, - .send = scp_error_send, - .throttle = scp_error_throttle, - .eof = scp_error_eof, -}; - -static void scp_error_send_message_cb(void *vscp) -{ - ScpError *scp = (ScpError *)vscp; - sshfwd_write_ext(scp->sc, true, scp->message, strlen(scp->message)); - sshfwd_write_ext(scp->sc, true, "\n", 1); - sshfwd_send_exit_status(scp->sc, 1); - sshfwd_write_eof(scp->sc); - sshfwd_initiate_close(scp->sc, scp->message); -} - -static PRINTF_LIKE(2, 3) ScpError *scp_error_new( - SshChannel *sc, const char *fmt, ...) -{ - va_list ap; - ScpError *scp = snew(ScpError); - - memset(scp, 0, sizeof(*scp)); - - scp->scpserver.vt = &ScpError_ScpServer_vt; - scp->sc = sc; - - va_start(ap, fmt); - scp->message = dupvprintf(fmt, ap); - va_end(ap); - - queue_toplevel_callback(scp_error_send_message_cb, scp); - - return scp; -} - -static void scp_error_free(ScpServer *s) -{ - ScpError *scp = container_of(s, ScpError, scpserver); - - sfree(scp->message); - - delete_callbacks_for_context(scp); - - sfree(scp); -} - -/* ---------------------------------------------------------------------- - * Top-level entry point, which parses a command sent from the SSH - * client, and if it recognises it as an scp command, instantiates an - * appropriate ScpServer implementation and returns it. - */ - -ScpServer *scp_recognise_exec( - SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen command) -{ - bool recursive = false, preserve = false; - bool targetshouldbedirectory = false; - ptrlen command_orig = command; - - if (!ptrlen_startswith(command, PTRLEN_LITERAL("scp "), &command)) - return NULL; - - while (1) { - if (ptrlen_startswith(command, PTRLEN_LITERAL("-v "), &command)) { - /* Enable verbose mode in the server, which we ignore */ - continue; - } - if (ptrlen_startswith(command, PTRLEN_LITERAL("-r "), &command)) { - recursive = true; - continue; - } - if (ptrlen_startswith(command, PTRLEN_LITERAL("-p "), &command)) { - preserve = true; - continue; - } - if (ptrlen_startswith(command, PTRLEN_LITERAL("-d "), &command)) { - targetshouldbedirectory = true; - continue; - } - break; - } - - if (ptrlen_startswith(command, PTRLEN_LITERAL("-t "), &command)) { - ScpSink *scp = scp_sink_new(sc, sftpserver_vt, command, - targetshouldbedirectory); - return &scp->scpserver; - } else if (ptrlen_startswith(command, PTRLEN_LITERAL("-f "), &command)) { - ScpSource *scp = scp_source_new(sc, sftpserver_vt, command); - scp->recursive = recursive; - scp->send_file_times = preserve; - return &scp->scpserver; - } else { - ScpError *scp = scp_error_new( - sc, "Unable to parse scp command: '%.*s'", - PTRLEN_PRINTF(command_orig)); - return &scp->scpserver; - } -} diff --git a/ssh/server.c b/ssh/server.c deleted file mode 100644 index b477392ec..000000000 --- a/ssh/server.c +++ /dev/null @@ -1,611 +0,0 @@ -/* - * Top-level code for SSH server implementation. - */ - -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "channel.h" -#include "server.h" -#ifndef NO_GSSAPI -#include "gssc.h" -#include "gss.h" -#endif - -struct Ssh { int dummy; }; - -typedef struct server server; -struct server { - bufchain in_raw, out_raw; - IdempotentCallback ic_out_raw; - bool pending_close; - - bufchain dummy_user_input; /* we never put anything on this */ - - PacketLogSettings pls; - LogContext *logctx; - struct DataTransferStats stats; - - int remote_bugs; - - Socket *socket; - Plug plug; - int conn_throttle_count; - bool frozen; - - Conf *conf; - const SshServerConfig *ssc; - ssh_key *const *hostkeys; - int nhostkeys; - RSAKey *hostkey1; - AuthPolicy *authpolicy; - LogPolicy *logpolicy; - const SftpServerVtable *sftpserver_vt; - - agentfwd *stunt_agentfwd; - - Seat seat; - Ssh ssh; - struct ssh_version_receiver version_receiver; - - BinaryPacketProtocol *bpp; - PacketProtocolLayer *base_layer; - ConnectionLayer *cl; - -#ifndef NO_GSSAPI - struct ssh_connection_shared_gss_state gss_state; -#endif -}; - -static void ssh_server_free_callback(void *vsrv); -static void server_got_ssh_version(struct ssh_version_receiver *rcv, - int major_version); -static void server_connect_bpp(server *srv); -static void server_bpp_output_raw_data_callback(void *vctx); - -void share_activate(ssh_sharing_state *sharestate, - const char *server_verstring) {} -void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, - ConnectionLayer *cl) {} -int share_ndownstreams(ssh_sharing_state *sharestate) { return 0; } -void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, - const void *vpkt, int pktlen) {} -void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan, - 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) {} -Channel *agentf_new(SshChannel *c) { return NULL; } -bool agent_exists(void) { return false; } -void ssh_got_exitcode(Ssh *ssh, int exitcode) {} -void ssh_check_frozen(Ssh *ssh) {} - -mainchan *mainchan_new( - PacketProtocolLayer *ppl, ConnectionLayer *cl, Conf *conf, - int term_width, int term_height, bool is_simple, SshChannel **sc_out) -{ return NULL; } -void mainchan_get_specials( - mainchan *mc, add_special_fn_t add_special, void *ctx) {} -void mainchan_special_cmd(mainchan *mc, SessionSpecialCode code, int arg) {} -void mainchan_terminal_size(mainchan *mc, int width, int height) {} - -/* Seat functions to ensure we don't get choosy about crypto - as the - * server, it's not up to us to give user warnings */ -static SeatPromptResult server_confirm_weak_crypto_primitive( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ return SPR_OK; } -static SeatPromptResult server_confirm_weak_cached_hostkey( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ return SPR_OK; } - -static const SeatVtable server_seat_vt = { - .output = nullseat_output, - .eof = nullseat_eof, - .sent = nullseat_sent, - .banner = nullseat_banner, - .get_userpass_input = nullseat_get_userpass_input, - .notify_session_started = nullseat_notify_session_started, - .notify_remote_exit = nullseat_notify_remote_exit, - .notify_remote_disconnect = nullseat_notify_remote_disconnect, - .connection_fatal = nullseat_connection_fatal, - .nonfatal = nullseat_nonfatal, - .update_specials_menu = nullseat_update_specials_menu, - .get_ttymode = nullseat_get_ttymode, - .set_busy_status = nullseat_set_busy_status, - .confirm_ssh_host_key = nullseat_confirm_ssh_host_key, - .confirm_weak_crypto_primitive = server_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = server_confirm_weak_cached_hostkey, - .prompt_descriptions = nullseat_prompt_descriptions, - .is_utf8 = nullseat_is_never_utf8, - .echoedit_update = nullseat_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = nullseat_get_window_pixel_size, - .stripctrl_new = nullseat_stripctrl_new, - .set_trust_status = nullseat_set_trust_status, - .can_set_trust_status = nullseat_can_set_trust_status_no, - .has_mixed_input_stream = nullseat_has_mixed_input_stream_no, - .verbose = nullseat_verbose_no, - .interactive = nullseat_interactive_no, - .get_cursor_position = nullseat_get_cursor_position, -}; - -static void server_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code) -{ - /* server *srv = container_of(plug, server, plug); */ - /* FIXME */ -} - -static void server_closing(Plug *plug, PlugCloseType type, - const char *error_msg) -{ - server *srv = container_of(plug, server, plug); - if (type != PLUGCLOSE_NORMAL) { - ssh_remote_error(&srv->ssh, "%s", error_msg); - } else if (srv->bpp) { - srv->bpp->input_eof = true; - queue_idempotent_callback(&srv->bpp->ic_in_raw); - } -} - -static void server_receive( - Plug *plug, int urgent, const char *data, size_t len) -{ - server *srv = container_of(plug, server, plug); - - /* Log raw data, if we're in that mode. */ - if (srv->logctx) - log_packet(srv->logctx, PKT_INCOMING, -1, NULL, data, len, - 0, NULL, NULL, 0, NULL); - - bufchain_add(&srv->in_raw, data, len); - if (!srv->frozen && srv->bpp) - queue_idempotent_callback(&srv->bpp->ic_in_raw); -} - -static void server_sent(Plug *plug, size_t bufsize) -{ -#ifdef FIXME - server *srv = container_of(plug, server, plug); - - /* - * If the send backlog on the SSH socket itself clears, we should - * unthrottle the whole world if it was throttled. Also trigger an - * extra call to the consumer of the BPP's output, to try to send - * some more data off its bufchain. - */ - if (bufsize < SSH_MAX_BACKLOG) { - srv_throttle_all(srv, 0, bufsize); - queue_idempotent_callback(&srv->ic_out_raw); - } -#endif -} - -LogContext *ssh_get_logctx(Ssh *ssh) -{ - server *srv = container_of(ssh, server, ssh); - return srv->logctx; -} - -void ssh_sendbuffer_changed(Ssh *ssh) -{ -} - -void ssh_throttle_conn(Ssh *ssh, int adjust) -{ - server *srv = container_of(ssh, server, ssh); - int old_count = srv->conn_throttle_count; - bool frozen; - - srv->conn_throttle_count += adjust; - assert(srv->conn_throttle_count >= 0); - - if (srv->conn_throttle_count && !old_count) { - frozen = true; - } else if (!srv->conn_throttle_count && old_count) { - frozen = false; - } else { - return; /* don't change current frozen state */ - } - - srv->frozen = frozen; - - if (srv->socket) { - sk_set_frozen(srv->socket, frozen); - - /* - * Now process any SSH connection data that was stashed in our - * queue while we were frozen. - */ - queue_idempotent_callback(&srv->bpp->ic_in_raw); - } -} - -void ssh_conn_processed_data(Ssh *ssh) -{ - /* FIXME: we could add the same check_frozen_state system as we - * have in ssh.c, but because that was originally added to work - * around a peculiarity of the GUI event loop, I haven't yet. */ -} - -Conf *make_ssh_server_conf(void) -{ - Conf *conf = conf_new(); - load_open_settings(NULL, conf); - /* In Uppity, we support even the legacy des-cbc cipher by - * default, so that it will be available if the user forces it by - * overriding the KEXINIT strings. If the user wants it _not_ - * supported, of course, they can override KEXINIT in the other - * direction. */ - conf_set_bool(conf, CONF_ssh2_des_cbc, true); - return conf; -} - -void ssh_check_sendok(Ssh *ssh) {} - -static const PlugVtable ssh_server_plugvt = { - .log = server_socket_log, - .closing = server_closing, - .receive = server_receive, - .sent = server_sent, -}; - -Plug *ssh_server_plug( - Conf *conf, const SshServerConfig *ssc, - ssh_key *const *hostkeys, int nhostkeys, - RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy, - const SftpServerVtable *sftpserver_vt) -{ - server *srv = snew(server); - - memset(srv, 0, sizeof(server)); - - srv->plug.vt = &ssh_server_plugvt; - srv->conf = conf_copy(conf); - srv->ssc = ssc; - srv->logctx = log_init(logpolicy, conf); - conf_set_bool(srv->conf, CONF_ssh_no_shell, true); - srv->nhostkeys = nhostkeys; - srv->hostkeys = hostkeys; - srv->hostkey1 = hostkey1; - srv->authpolicy = authpolicy; - srv->logpolicy = logpolicy; - srv->sftpserver_vt = sftpserver_vt; - - srv->seat.vt = &server_seat_vt; - - bufchain_init(&srv->in_raw); - bufchain_init(&srv->out_raw); - bufchain_init(&srv->dummy_user_input); - -#ifndef NO_GSSAPI - /* FIXME: replace with sensible */ - srv->gss_state.libs = snew(struct ssh_gss_liblist); - srv->gss_state.libs->nlibraries = 0; -#endif - - return &srv->plug; -} - -void ssh_server_start(Plug *plug, Socket *socket) -{ - server *srv = container_of(plug, server, plug); - const char *our_protoversion; - - if (srv->ssc->bare_connection) { - our_protoversion = "2.0"; /* SSH-2 only */ - } else if (srv->hostkey1 && srv->nhostkeys) { - our_protoversion = "1.99"; /* offer both SSH-1 and SSH-2 */ - } else if (srv->hostkey1) { - our_protoversion = "1.5"; /* SSH-1 only */ - } else { - assert(srv->nhostkeys); - our_protoversion = "2.0"; /* SSH-2 only */ - } - - srv->socket = socket; - - srv->ic_out_raw.fn = server_bpp_output_raw_data_callback; - srv->ic_out_raw.ctx = srv; - srv->version_receiver.got_ssh_version = server_got_ssh_version; - srv->bpp = ssh_verstring_new( - srv->conf, srv->logctx, srv->ssc->bare_connection, - our_protoversion, &srv->version_receiver, - true, srv->ssc->application_name); - server_connect_bpp(srv); - queue_idempotent_callback(&srv->bpp->ic_in_raw); -} - -static void ssh_server_free_callback(void *vsrv) -{ - server *srv = (server *)vsrv; - - logeventf(srv->logctx, "freeing server instance"); - - bufchain_clear(&srv->in_raw); - bufchain_clear(&srv->out_raw); - bufchain_clear(&srv->dummy_user_input); - - if (srv->socket) - sk_close(srv->socket); - - if (srv->stunt_agentfwd) - agentfwd_free(srv->stunt_agentfwd); - - if (srv->base_layer) - ssh_ppl_free(srv->base_layer); - if (srv->bpp) - ssh_bpp_free(srv->bpp); - - delete_callbacks_for_context(srv); - - conf_free(srv->conf); - log_free(srv->logctx); - -#ifndef NO_GSSAPI - sfree(srv->gss_state.libs); /* FIXME: replace with sensible */ -#endif - - LogPolicy *lp = srv->logpolicy; - sfree(srv); - - server_instance_terminated(lp); -} - -static void server_connect_bpp(server *srv) -{ - srv->bpp->ssh = &srv->ssh; - srv->bpp->in_raw = &srv->in_raw; - srv->bpp->out_raw = &srv->out_raw; - bufchain_set_callback(srv->bpp->out_raw, &srv->ic_out_raw); - srv->bpp->pls = &srv->pls; - srv->bpp->logctx = srv->logctx; - srv->bpp->remote_bugs = srv->remote_bugs; - /* Servers don't really have a notion of 'unexpected' connection - * closure. The client is free to close if it likes. */ - srv->bpp->expect_close = true; -} - -static void server_connect_ppl(server *srv, PacketProtocolLayer *ppl) -{ - ppl->bpp = srv->bpp; - ppl->logctx = srv->logctx; - ppl->ssh = &srv->ssh; - ppl->seat = &srv->seat; - ppl->interactor = NULL; - ppl->remote_bugs = srv->remote_bugs; -} - -static void server_bpp_output_raw_data_callback(void *vctx) -{ - server *srv = (server *)vctx; - - if (!srv->socket) - return; - - while (bufchain_size(&srv->out_raw) > 0) { - size_t backlog; - - ptrlen data = bufchain_prefix(&srv->out_raw); - - if (srv->logctx) - log_packet(srv->logctx, PKT_OUTGOING, -1, NULL, data.ptr, data.len, - 0, NULL, NULL, 0, NULL); - backlog = sk_write(srv->socket, data.ptr, data.len); - - bufchain_consume(&srv->out_raw, data.len); - - if (backlog > SSH_MAX_BACKLOG) { -#ifdef FIXME - ssh_throttle_all(ssh, 1, backlog); -#endif - return; - } - } - - if (srv->pending_close) { - sk_close(srv->socket); - srv->socket = NULL; - queue_toplevel_callback(ssh_server_free_callback, srv); - } -} - -static void server_shutdown_internal(server *srv) -{ - /* - * We only need to free the base PPL, which will free the others - * (if any) transitively. - */ - if (srv->base_layer) { - ssh_ppl_free(srv->base_layer); - srv->base_layer = NULL; - } - - srv->cl = NULL; -} - -static void server_initiate_connection_close(server *srv) -{ - /* Wind up everything above the BPP. */ - server_shutdown_internal(srv); - - /* Force any remaining queued SSH packets through the BPP, and - * schedule closing the network socket after they go out. */ - ssh_bpp_handle_output(srv->bpp); - srv->pending_close = true; - queue_idempotent_callback(&srv->ic_out_raw); - - /* Now we expect the other end to close the connection too in - * response, so arrange that we'll receive notification of that - * via ssh_remote_eof. */ - srv->bpp->expect_close = true; -} - -#define GET_FORMATTED_MSG(fmt) \ - char *msg; \ - va_list ap; \ - va_start(ap, fmt); \ - msg = dupvprintf(fmt, ap); \ - va_end(ap); - -#define LOG_FORMATTED_MSG(logctx, fmt) do \ - { \ - va_list ap; \ - va_start(ap, fmt); \ - logeventvf(logctx, fmt, ap); \ - va_end(ap); \ - } while (0) - -void ssh_remote_error(Ssh *ssh, const char *fmt, ...) -{ - server *srv = container_of(ssh, server, ssh); - LOG_FORMATTED_MSG(srv->logctx, fmt); - queue_toplevel_callback(ssh_server_free_callback, srv); -} - -void ssh_remote_eof(Ssh *ssh, const char *fmt, ...) -{ - server *srv = container_of(ssh, server, ssh); - LOG_FORMATTED_MSG(srv->logctx, fmt); - queue_toplevel_callback(ssh_server_free_callback, srv); -} - -void ssh_proto_error(Ssh *ssh, const char *fmt, ...) -{ - server *srv = container_of(ssh, server, ssh); - if (srv->base_layer) { - GET_FORMATTED_MSG(fmt); - ssh_bpp_queue_disconnect(srv->bpp, msg, - SSH2_DISCONNECT_PROTOCOL_ERROR); - server_initiate_connection_close(srv); - logeventf(srv->logctx, "Protocol error: %s", msg); - sfree(msg); - } -} - -void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) -{ - server *srv = container_of(ssh, server, ssh); - LOG_FORMATTED_MSG(srv->logctx, fmt); - queue_toplevel_callback(ssh_server_free_callback, srv); -} - -void ssh_user_close(Ssh *ssh, const char *fmt, ...) -{ - server *srv = container_of(ssh, server, ssh); - ssh_bpp_handle_output(srv->bpp); - LOG_FORMATTED_MSG(srv->logctx, fmt); - queue_toplevel_callback(ssh_server_free_callback, srv); -} - -static void server_got_ssh_version(struct ssh_version_receiver *rcv, - int major_version) -{ - server *srv = container_of(rcv, server, version_receiver); - BinaryPacketProtocol *old_bpp; - PacketProtocolLayer *connection_layer; - - old_bpp = srv->bpp; - srv->remote_bugs = ssh_verstring_get_bugs(old_bpp); - - if (srv->ssc->bare_connection) { - srv->bpp = ssh2_bare_bpp_new(srv->logctx); - server_connect_bpp(srv); - - connection_layer = ssh2_connection_new( - &srv->ssh, NULL, false, srv->conf, - ssh_verstring_get_local(old_bpp), &srv->dummy_user_input, - &srv->cl); - ssh2connection_server_configure(connection_layer, - srv->sftpserver_vt, srv->ssc); - server_connect_ppl(srv, connection_layer); - - srv->base_layer = connection_layer; - } else if (major_version == 2) { - PacketProtocolLayer *userauth_layer, *transport_child_layer; - - srv->bpp = ssh2_bpp_new(srv->logctx, &srv->stats, true); - server_connect_bpp(srv); - - connection_layer = ssh2_connection_new( - &srv->ssh, NULL, false, srv->conf, - ssh_verstring_get_local(old_bpp), &srv->dummy_user_input, - &srv->cl); - ssh2connection_server_configure(connection_layer, - srv->sftpserver_vt, srv->ssc); - server_connect_ppl(srv, connection_layer); - - if (conf_get_bool(srv->conf, CONF_ssh_no_userauth)) { - userauth_layer = NULL; - transport_child_layer = connection_layer; - } else { - userauth_layer = ssh2_userauth_server_new( - connection_layer, srv->authpolicy, srv->ssc); - server_connect_ppl(srv, userauth_layer); - transport_child_layer = userauth_layer; - } - - srv->base_layer = ssh2_transport_new( - srv->conf, NULL, 0, NULL, - ssh_verstring_get_remote(old_bpp), - ssh_verstring_get_local(old_bpp), -#ifndef NO_GSSAPI - &srv->gss_state, -#else - NULL, -#endif - &srv->stats, transport_child_layer, srv->ssc); - ssh2_transport_provide_hostkeys( - srv->base_layer, srv->hostkeys, srv->nhostkeys); - if (userauth_layer) - ssh2_userauth_server_set_transport_layer( - userauth_layer, srv->base_layer); - server_connect_ppl(srv, srv->base_layer); - - } else { - srv->bpp = ssh1_bpp_new(srv->logctx); - server_connect_bpp(srv); - - connection_layer = ssh1_connection_new( - &srv->ssh, srv->conf, &srv->dummy_user_input, &srv->cl); - ssh1connection_server_configure(connection_layer, srv->ssc); - server_connect_ppl(srv, connection_layer); - - srv->base_layer = ssh1_login_server_new( - connection_layer, srv->hostkey1, srv->authpolicy, srv->ssc); - server_connect_ppl(srv, srv->base_layer); - } - - /* Connect the base layer - whichever it is - to the BPP, and set - * up its selfptr. */ - srv->base_layer->selfptr = &srv->base_layer; - ssh_ppl_setup_queues(srv->base_layer, &srv->bpp->in_pq, &srv->bpp->out_pq); - -#ifdef FIXME // we probably will want one of these, in the end - srv->pinger = pinger_new(srv->conf, &srv->backend); -#endif - - if (srv->ssc->stunt_open_unconditional_agent_socket) { - char *socketname; - srv->stunt_agentfwd = agentfwd_new(srv->cl, &socketname); - if (srv->stunt_agentfwd) { - logeventf(srv->logctx, "opened unconditional agent socket at %s\n", - socketname); - sfree(socketname); - } - } - - queue_idempotent_callback(&srv->bpp->ic_in_raw); - ssh_ppl_process_queue(srv->base_layer); - - ssh_bpp_free(old_bpp); -} diff --git a/ssh/server.h b/ssh/server.h deleted file mode 100644 index dc73747cc..000000000 --- a/ssh/server.h +++ /dev/null @@ -1,146 +0,0 @@ -typedef struct AuthPolicy AuthPolicy; - -struct SshServerConfig { - const char *application_name; - const char *session_starting_dir; - - RSAKey *rsa_kex_key; - - /* - * In all of these ptrlens, setting the 'ptr' member to NULL means - * that we're not overriding the default configuration. - */ - ptrlen banner; /* default here is 'no banner' */ - ptrlen kex_override[NKEXLIST]; - - bool exit_signal_numeric; /* mimic an old server bug */ - - unsigned long ssh1_cipher_mask; - bool ssh1_allow_compression; - bool bare_connection; - - bool stunt_pretend_to_accept_any_pubkey; - bool stunt_open_unconditional_agent_socket; - bool stunt_allow_trivial_ki_auth; - bool stunt_return_success_to_pubkey_offer; - bool stunt_close_after_banner; -}; - -Plug *ssh_server_plug( - Conf *conf, const SshServerConfig *ssc, - ssh_key *const *hostkeys, int nhostkeys, - RSAKey *hostkey1, AuthPolicy *authpolicy, LogPolicy *logpolicy, - const SftpServerVtable *sftpserver_vt); -void ssh_server_start(Plug *plug, Socket *socket); - -void server_instance_terminated(LogPolicy *logpolicy); -void platform_logevent(const char *msg); - -#define AUTHMETHODS(X) \ - X(NONE) \ - X(PASSWORD) \ - X(PUBLICKEY) \ - X(KBDINT) \ - X(TIS) \ - X(CRYPTOCARD) \ - /* end of list */ - -#define AUTHMETHOD_BIT_INDEX(name) AUTHMETHOD_BIT_INDEX_##name, -enum { AUTHMETHODS(AUTHMETHOD_BIT_INDEX) AUTHMETHOD_BIT_INDEX_dummy }; -#define AUTHMETHOD_BIT_VALUE(name) \ - AUTHMETHOD_##name = 1 << AUTHMETHOD_BIT_INDEX_##name, -enum { AUTHMETHODS(AUTHMETHOD_BIT_VALUE) AUTHMETHOD_BIT_VALUE_dummy }; - -typedef struct AuthKbdInt AuthKbdInt; -typedef struct AuthKbdIntPrompt AuthKbdIntPrompt; -struct AuthKbdInt { - char *title, *instruction; /* both need freeing */ - int nprompts; - AuthKbdIntPrompt *prompts; /* the array itself needs freeing */ -}; -struct AuthKbdIntPrompt { - char *prompt; /* needs freeing */ - bool echo; -}; - -unsigned auth_methods(AuthPolicy *); -bool auth_none(AuthPolicy *, ptrlen username); - -int auth_password(AuthPolicy *, ptrlen username, ptrlen password, - ptrlen *opt_new_password); -/* auth_password returns 1 for 'accepted', 0 for 'rejected', and 2 for - * 'ok but now you need to change your password' */ - -bool auth_publickey(AuthPolicy *, ptrlen username, ptrlen public_blob); -/* auth_publickey_ssh1 must return the whole public key given the modulus, - * because the SSH-1 client never transmits the exponent over the wire. - * The key remains owned by the AuthPolicy. */ - -AuthKbdInt *auth_kbdint_prompts(AuthPolicy *, ptrlen username); -/* auth_kbdint_prompts returns NULL to trigger auth failure */ -int auth_kbdint_responses(AuthPolicy *, const ptrlen *responses); -/* auth_kbdint_responses returns >0 for success, <0 for failure, and 0 - * to indicate that we haven't decided yet and further prompts are - * coming */ - -/* The very similar SSH-1 TIS and CryptoCard methods are combined into - * a single API for AuthPolicy, which takes a method argument */ -char *auth_ssh1int_challenge(AuthPolicy *, unsigned method, ptrlen username); -bool auth_ssh1int_response(AuthPolicy *, ptrlen response); - -RSAKey *auth_publickey_ssh1( - AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus); -/* auth_successful returns false if further authentication is needed */ -bool auth_successful(AuthPolicy *, ptrlen username, unsigned method); - -PacketProtocolLayer *ssh2_userauth_server_new( - PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy, - const SshServerConfig *ssc); -void ssh2_userauth_server_set_transport_layer( - PacketProtocolLayer *userauth, PacketProtocolLayer *transport); - -void ssh2connection_server_configure( - PacketProtocolLayer *ppl, const SftpServerVtable *sftpserver_vt, - const SshServerConfig *ssc); -void ssh1connection_server_configure( - PacketProtocolLayer *ppl, const SshServerConfig *ssc); - -PacketProtocolLayer *ssh1_login_server_new( - PacketProtocolLayer *successor_layer, RSAKey *hostkey, - AuthPolicy *authpolicy, const SshServerConfig *ssc); - -Channel *sesschan_new(SshChannel *c, LogContext *logctx, - const SftpServerVtable *sftpserver_vt, - const SshServerConfig *ssc); - -Backend *pty_backend_create( - Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd, - struct ssh_ttymodes ttymodes, bool pipes_instead_of_pty, const char *dir, - const char *const *env_vars_to_unset); -int pty_backend_exit_signum(Backend *be); -ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg); - -/* - * Establish a listening X server. Return value is the _number_ of - * Sockets that it established pointing at the given Plug. (0 - * indicates complete failure.) The socket pointers themselves are - * written into sockets[], up to a possible total of MAX_X11_SOCKETS. - * - * The supplied Conf has necessary environment variables written into - * it. (And is also used to open the port listeners, though that - * shouldn't affect anything.) - */ -#define MAX_X11_SOCKETS 2 -int platform_make_x11_server(Plug *plug, const char *progname, int mindisp, - const char *screen_number_suffix, - ptrlen authproto, ptrlen authdata, - Socket **sockets, Conf *conf); - -Conf *make_ssh_server_conf(void); - -/* Provided by Unix front end programs to unix/sftpserver.c */ -void make_unix_sftp_filehandle_key(void *data, size_t size); - -typedef struct agentfwd agentfwd; -agentfwd *agentfwd_new(ConnectionLayer *cl, char **socketname_out); -void agentfwd_free(agentfwd *agent); diff --git a/ssh/sesschan.c b/ssh/sesschan.c deleted file mode 100644 index 55d868e02..000000000 --- a/ssh/sesschan.c +++ /dev/null @@ -1,800 +0,0 @@ -/* - * Implement the "session" channel type for the SSH server. - */ - -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "channel.h" -#include "server.h" -#include "sftp.h" - -struct agentfwd { - ConnectionLayer *cl; - Socket *socket; - Plug plug; -}; - -typedef struct sesschan { - SshChannel *c; - - LogContext *parent_logctx, *child_logctx; - Conf *conf; - const SftpServerVtable *sftpserver_vt; - - LogPolicy logpolicy; - Seat seat; - - bool want_pty; - struct ssh_ttymodes ttymodes; - int wc, hc, wp, hp; - strbuf *termtype; - - bool ignoring_input; - bool seen_eof, seen_exit; - - Plug xfwd_plug; - int n_x11_sockets; - Socket *x11_sockets[MAX_X11_SOCKETS]; - - agentfwd *agent; - - Backend *backend; - - bufchain subsys_input; - SftpServer *sftpsrv; - ScpServer *scpsrv; - const SshServerConfig *ssc; - - Channel chan; -} sesschan; - -static void sesschan_free(Channel *chan); -static size_t sesschan_send( - Channel *chan, bool is_stderr, const void *, size_t); -static void sesschan_send_eof(Channel *chan); -static char *sesschan_log_close_msg(Channel *chan); -static bool sesschan_want_close(Channel *, bool, bool); -static void sesschan_set_input_wanted(Channel *chan, bool wanted); -static bool sesschan_run_shell(Channel *chan); -static bool sesschan_run_command(Channel *chan, ptrlen command); -static bool sesschan_run_subsystem(Channel *chan, ptrlen subsys); -static bool sesschan_enable_x11_forwarding( - Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, - unsigned screen_number); -static bool sesschan_enable_agent_forwarding(Channel *chan); -static bool sesschan_allocate_pty( - Channel *chan, ptrlen termtype, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes); -static bool sesschan_set_env(Channel *chan, ptrlen var, ptrlen value); -static bool sesschan_send_break(Channel *chan, unsigned length); -static bool sesschan_send_signal(Channel *chan, ptrlen signame); -static bool sesschan_change_window_size( - Channel *chan, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight); - -static const ChannelVtable sesschan_channelvt = { - .free = sesschan_free, - .open_confirmation = chan_remotely_opened_confirmation, - .open_failed = chan_remotely_opened_failure, - .send = sesschan_send, - .send_eof = sesschan_send_eof, - .set_input_wanted = sesschan_set_input_wanted, - .log_close_msg = sesschan_log_close_msg, - .want_close = sesschan_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = sesschan_run_shell, - .run_command = sesschan_run_command, - .run_subsystem = sesschan_run_subsystem, - .enable_x11_forwarding = sesschan_enable_x11_forwarding, - .enable_agent_forwarding = sesschan_enable_agent_forwarding, - .allocate_pty = sesschan_allocate_pty, - .set_env = sesschan_set_env, - .send_break = sesschan_send_break, - .send_signal = sesschan_send_signal, - .change_window_size = sesschan_change_window_size, - .request_response = chan_no_request_response, -}; - -static size_t sftp_chan_send( - Channel *chan, bool is_stderr, const void *, size_t); -static void sftp_chan_send_eof(Channel *chan); -static char *sftp_log_close_msg(Channel *chan); - -static const ChannelVtable sftp_channelvt = { - .free = sesschan_free, - .open_confirmation = chan_remotely_opened_confirmation, - .open_failed = chan_remotely_opened_failure, - .send = sftp_chan_send, - .send_eof = sftp_chan_send_eof, - .set_input_wanted = sesschan_set_input_wanted, - .log_close_msg = sftp_log_close_msg, - .want_close = chan_default_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = chan_no_request_response, -}; - -static size_t scp_chan_send( - Channel *chan, bool is_stderr, const void *, size_t); -static void scp_chan_send_eof(Channel *chan); -static void scp_set_input_wanted(Channel *chan, bool wanted); -static char *scp_log_close_msg(Channel *chan); - -static const ChannelVtable scp_channelvt = { - .free = sesschan_free, - .open_confirmation = chan_remotely_opened_confirmation, - .open_failed = chan_remotely_opened_failure, - .send = scp_chan_send, - .send_eof = scp_chan_send_eof, - .set_input_wanted = scp_set_input_wanted, - .log_close_msg = scp_log_close_msg, - .want_close = chan_default_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = chan_no_request_response, -}; - -static void sesschan_eventlog(LogPolicy *lp, const char *event) {} -static void sesschan_logging_error(LogPolicy *lp, const char *event) {} -static int sesschan_askappend( - LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) { return 2; } - -static const LogPolicyVtable sesschan_logpolicy_vt = { - .eventlog = sesschan_eventlog, - .askappend = sesschan_askappend, - .logging_error = sesschan_logging_error, - .verbose = null_lp_verbose_no, -}; - -static size_t sesschan_seat_output( - Seat *, SeatOutputType type, const void *, size_t); -static bool sesschan_seat_eof(Seat *); -static void sesschan_notify_remote_exit(Seat *seat); -static void sesschan_connection_fatal(Seat *seat, const char *message); -static bool sesschan_get_window_pixel_size(Seat *seat, int *w, int *h); - -static const SeatVtable sesschan_seat_vt = { - .output = sesschan_seat_output, - .eof = sesschan_seat_eof, - .sent = nullseat_sent, - .banner = nullseat_banner, - .get_userpass_input = nullseat_get_userpass_input, - .notify_session_started = nullseat_notify_session_started, - .notify_remote_exit = sesschan_notify_remote_exit, - .notify_remote_disconnect = nullseat_notify_remote_disconnect, - .connection_fatal = sesschan_connection_fatal, - .nonfatal = nullseat_nonfatal, - .update_specials_menu = nullseat_update_specials_menu, - .get_ttymode = nullseat_get_ttymode, - .set_busy_status = nullseat_set_busy_status, - .confirm_ssh_host_key = nullseat_confirm_ssh_host_key, - .confirm_weak_crypto_primitive = nullseat_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = nullseat_confirm_weak_cached_hostkey, - .prompt_descriptions = nullseat_prompt_descriptions, - .is_utf8 = nullseat_is_never_utf8, - .echoedit_update = nullseat_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = sesschan_get_window_pixel_size, - .stripctrl_new = nullseat_stripctrl_new, - .set_trust_status = nullseat_set_trust_status, - .can_set_trust_status = nullseat_can_set_trust_status_no, - .has_mixed_input_stream = nullseat_has_mixed_input_stream_no, - .verbose = nullseat_verbose_no, - .interactive = nullseat_interactive_no, - .get_cursor_position = nullseat_get_cursor_position, -}; - -Channel *sesschan_new(SshChannel *c, LogContext *logctx, - const SftpServerVtable *sftpserver_vt, - const SshServerConfig *ssc) -{ - sesschan *sess = snew(sesschan); - memset(sess, 0, sizeof(sesschan)); - - sess->c = c; - sess->chan.vt = &sesschan_channelvt; - sess->chan.initial_fixed_window_size = 0; - sess->parent_logctx = logctx; - sess->ssc = ssc; - - /* Start with a completely default Conf */ - sess->conf = conf_new(); - load_open_settings(NULL, sess->conf); - - /* Set close-on-exit = true to suppress pty.c's "[pterm: process - * terminated with status x]" message */ - conf_set_int(sess->conf, CONF_close_on_exit, FORCE_ON); - - sess->seat.vt = &sesschan_seat_vt; - sess->logpolicy.vt = &sesschan_logpolicy_vt; - sess->child_logctx = log_init(&sess->logpolicy, sess->conf); - - sess->sftpserver_vt = sftpserver_vt; - - bufchain_init(&sess->subsys_input); - - return &sess->chan; -} - -static void sesschan_free(Channel *chan) -{ - sesschan *sess = container_of(chan, sesschan, chan); - int i; - - delete_callbacks_for_context(sess); - conf_free(sess->conf); - if (sess->backend) - backend_free(sess->backend); - bufchain_clear(&sess->subsys_input); - if (sess->sftpsrv) - sftpsrv_free(sess->sftpsrv); - for (i = 0; i < sess->n_x11_sockets; i++) - sk_close(sess->x11_sockets[i]); - if (sess->agent) - agentfwd_free(sess->agent); - - sfree(sess); -} - -static size_t sesschan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - if (!sess->backend || sess->ignoring_input) - return 0; - - backend_send(sess->backend, data, length); - return backend_sendbuffer(sess->backend); -} - -static void sesschan_send_eof(Channel *chan) -{ - sesschan *sess = container_of(chan, sesschan, chan); - if (sess->backend) - backend_special(sess->backend, SS_EOF, 0); -} - -static char *sesschan_log_close_msg(Channel *chan) -{ - return dupstr("Session channel closed"); -} - -static void sesschan_set_input_wanted(Channel *chan, bool wanted) -{ - sesschan *sess = container_of(chan, sesschan, chan); - /* Request the back end to resume sending input, if it had become - * throttled by the channel window shortening */ - if (wanted && sess->backend) - backend_unthrottle(sess->backend, 0); -} - -static void sesschan_start_backend(sesschan *sess, const char *cmd) -{ - /* - * List of environment variables that we should not pass through - * from the login session Uppity was run in (which, it being a - * test server, there will usually be one of). These variables - * will be set as part of X or agent forwarding, and shouldn't be - * confusingly set in the absence of that. - * - * (DISPLAY must also be cleared, but pty.c will do that anyway - * when our get_x_display method returns NULL.) - */ - static const char *const env_to_unset[] = { - "XAUTHORITY", "SSH_AUTH_SOCK", "SSH_AGENT_PID", - NULL /* terminator */ - }; - - sess->backend = pty_backend_create( - &sess->seat, sess->child_logctx, sess->conf, NULL, cmd, - sess->ttymodes, !sess->want_pty, sess->ssc->session_starting_dir, - env_to_unset); - backend_size(sess->backend, sess->wc, sess->hc); -} - -bool sesschan_run_shell(Channel *chan) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - if (sess->backend) - return false; - - sesschan_start_backend(sess, NULL); - return true; -} - -bool sesschan_run_command(Channel *chan, ptrlen command) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - if (sess->backend) - return false; - - /* FIXME: make this possible to configure off */ - if ((sess->scpsrv = scp_recognise_exec(sess->c, sess->sftpserver_vt, - command)) != NULL) { - sess->chan.vt = &scp_channelvt; - logevent(sess->parent_logctx, "Starting built-in SCP server"); - return true; - } - - char *command_str = mkstr(command); - sesschan_start_backend(sess, command_str); - sfree(command_str); - - return true; -} - -bool sesschan_run_subsystem(Channel *chan, ptrlen subsys) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - if (ptrlen_eq_string(subsys, "sftp") && sess->sftpserver_vt) { - sess->sftpsrv = sftpsrv_new(sess->sftpserver_vt); - sess->chan.vt = &sftp_channelvt; - logevent(sess->parent_logctx, "Starting built-in SFTP subsystem"); - return true; - } - - return false; -} - -static void fwd_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ /* don't expect any weirdnesses from a listening socket */ } -static void fwd_closing(Plug *plug, PlugCloseType type, const char *error_msg) -{ /* not here, either */ } - -static int xfwd_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) -{ - sesschan *sess = container_of(p, sesschan, xfwd_plug); - Plug *plug; - Channel *chan; - Socket *s; - SocketPeerInfo *pi; - const char *err; - - chan = portfwd_raw_new(sess->c->cl, &plug, false); - s = constructor(ctx, plug); - if ((err = sk_socket_error(s)) != NULL) { - portfwd_raw_free(chan); - return 1; - } - pi = sk_peer_info(s); - portfwd_raw_setup(chan, s, ssh_serverside_x11_open(sess->c->cl, chan, pi)); - sk_free_peer_info(pi); - - return 0; -} - -static const PlugVtable xfwd_plugvt = { - .log = fwd_log, - .closing = fwd_closing, - .accepting = xfwd_accepting, -}; - -bool sesschan_enable_x11_forwarding( - Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata_hex, - unsigned screen_number) -{ - sesschan *sess = container_of(chan, sesschan, chan); - strbuf *authdata_bin; - size_t i; - - if (oneshot) - return false; /* not supported */ - - /* - * Decode the authorisation data from ASCII hex into binary. - */ - if (authdata_hex.len % 2) - return false; /* expected an even number of digits */ - authdata_bin = strbuf_new_nm(); - for (i = 0; i < authdata_hex.len; i += 2) { - const unsigned char *hex = authdata_hex.ptr; - char hexbuf[3]; - - if (!isxdigit((unsigned char)hex[i]) || - !isxdigit((unsigned char)hex[i+1])) { - strbuf_free(authdata_bin); - return false; /* not hex */ - } - - hexbuf[0] = hex[i]; - hexbuf[1] = hex[i+1]; - hexbuf[2] = '\0'; - put_byte(authdata_bin, strtoul(hexbuf, NULL, 16)); - } - - sess->xfwd_plug.vt = &xfwd_plugvt; - - char *screensuffix = dupprintf(".%u", screen_number); - - sess->n_x11_sockets = platform_make_x11_server( - &sess->xfwd_plug, appname, 10, screensuffix, - authproto, ptrlen_from_strbuf(authdata_bin), - sess->x11_sockets, sess->conf); - - sfree(screensuffix); - strbuf_free(authdata_bin); - return sess->n_x11_sockets != 0; -} - -static int agentfwd_accepting( - Plug *p, accept_fn_t constructor, accept_ctx_t ctx) -{ - agentfwd *agent = container_of(p, agentfwd, plug); - Plug *plug; - Channel *chan; - Socket *s; - const char *err; - - chan = portfwd_raw_new(agent->cl, &plug, false); - s = constructor(ctx, plug); - if ((err = sk_socket_error(s)) != NULL) { - portfwd_raw_free(chan); - return 1; - } - portfwd_raw_setup(chan, s, ssh_serverside_agent_open(agent->cl, chan)); - - return 0; -} - -static const PlugVtable agentfwd_plugvt = { - .log = fwd_log, - .closing = fwd_closing, - .accepting = agentfwd_accepting, -}; - -agentfwd *agentfwd_new(ConnectionLayer *cl, char **socketname_out) -{ - agentfwd *agent = snew(agentfwd); - agent->cl = cl; - agent->plug.vt = &agentfwd_plugvt; - - char *dir_prefix = dupprintf("/tmp/%s-agentfwd", appname); - char *error = NULL, *socketname = NULL; - agent->socket = platform_make_agent_socket( - &agent->plug, dir_prefix, &error, &socketname); - sfree(dir_prefix); - sfree(error); - - if (!agent->socket) { - sfree(agent); - sfree(socketname); - return NULL; - } - - *socketname_out = socketname; - return agent; -} - -void agentfwd_free(agentfwd *agent) -{ - if (agent->socket) - sk_close(agent->socket); - sfree(agent); -} - -bool sesschan_enable_agent_forwarding(Channel *chan) -{ - sesschan *sess = container_of(chan, sesschan, chan); - char *socketname; - - assert(!sess->agent); - - sess->agent = agentfwd_new(sess->c->cl, &socketname); - - if (!sess->agent) - return false; - - conf_set_str_str(sess->conf, CONF_environmt, "SSH_AUTH_SOCK", socketname); - sfree(socketname); - return true; -} - -bool sesschan_allocate_pty( - Channel *chan, ptrlen termtype, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes) -{ - sesschan *sess = container_of(chan, sesschan, chan); - char *s; - - if (sess->want_pty) - return false; - - s = mkstr(termtype); - conf_set_str(sess->conf, CONF_termtype, s); - sfree(s); - - sess->want_pty = true; - sess->ttymodes = modes; - sess->wc = width; - sess->hc = height; - sess->wp = pixwidth; - sess->hp = pixheight; - - return true; -} - -bool sesschan_set_env(Channel *chan, ptrlen var, ptrlen value) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - char *svar = mkstr(var), *svalue = mkstr(value); - conf_set_str_str(sess->conf, CONF_environmt, svar, svalue); - sfree(svar); - sfree(svalue); - - return true; -} - -bool sesschan_send_break(Channel *chan, unsigned length) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - if (sess->backend) { - /* We ignore the break length. We could pass it through as the - * 'arg' parameter, and have pty.c collect it and pass it on - * to tcsendbreak, but since tcsendbreak in turn assigns - * implementation-defined semantics to _its_ duration - * parameter, this all just sounds too difficult. */ - backend_special(sess->backend, SS_BRK, 0); - return true; - } - return false; -} - -bool sesschan_send_signal(Channel *chan, ptrlen signame) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - /* Start with a code that definitely isn't a signal (or indeed a - * special command at all), to indicate 'nothing matched'. */ - SessionSpecialCode code = SS_EXITMENU; - - #define SIGNAL_SUB(name) \ - if (ptrlen_eq_string(signame, #name)) code = SS_SIG ## name; - #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name) - #include "signal-list.h" - #undef SIGNAL_MAIN - #undef SIGNAL_SUB - - if (code == SS_EXITMENU) - return false; - - backend_special(sess->backend, code, 0); - return true; -} - -bool sesschan_change_window_size( - Channel *chan, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - if (!sess->want_pty) - return false; - - sess->wc = width; - sess->hc = height; - sess->wp = pixwidth; - sess->hp = pixheight; - - if (sess->backend) - backend_size(sess->backend, sess->wc, sess->hc); - - return true; -} - -static size_t sesschan_seat_output( - Seat *seat, SeatOutputType type, const void *data, size_t len) -{ - sesschan *sess = container_of(seat, sesschan, seat); - return sshfwd_write_ext(sess->c, type == SEAT_OUTPUT_STDERR, data, len); -} - -static void sesschan_check_close_callback(void *vctx) -{ - sesschan *sess = (sesschan *)vctx; - - /* - * Once we've seen incoming EOF from the backend (aka EIO from the - * pty master) and also passed on the process's exit status, we - * should proactively initiate closure of the session channel. - */ - if (sess->seen_eof && sess->seen_exit) - sshfwd_initiate_close(sess->c, NULL); -} - -static bool sesschan_want_close(Channel *chan, bool seen_eof, bool rcvd_eof) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - /* - * Similarly to above, we don't want to initiate channel closure - * until we've sent the process's exit status, _even_ if EOF of - * the actual data stream has happened in both directions. - */ - return (sess->seen_eof && sess->seen_exit); -} - -static bool sesschan_seat_eof(Seat *seat) -{ - sesschan *sess = container_of(seat, sesschan, seat); - - sshfwd_write_eof(sess->c); - sess->seen_eof = true; - - queue_toplevel_callback(sesschan_check_close_callback, sess); - return true; -} - -static void sesschan_notify_remote_exit(Seat *seat) -{ - sesschan *sess = container_of(seat, sesschan, seat); - - if (!sess->backend) - return; - - bool got_signal = false; - if (!sess->ssc->exit_signal_numeric) { - char *sigmsg; - ptrlen signame = pty_backend_exit_signame(sess->backend, &sigmsg); - - if (signame.len) { - if (!sigmsg) - sigmsg = dupstr(""); - - sshfwd_send_exit_signal( - sess->c, signame, false, ptrlen_from_asciz(sigmsg)); - - got_signal = true; - } - - sfree(sigmsg); - } else { - int signum = pty_backend_exit_signum(sess->backend); - - if (signum >= 0) { - sshfwd_send_exit_signal_numeric(sess->c, signum, false, - PTRLEN_LITERAL("")); - got_signal = true; - } - } - - if (!got_signal) - sshfwd_send_exit_status(sess->c, backend_exitcode(sess->backend)); - - sess->seen_exit = true; - queue_toplevel_callback(sesschan_check_close_callback, sess); -} - -static void sesschan_connection_fatal(Seat *seat, const char *message) -{ - sesschan *sess = container_of(seat, sesschan, seat); - - /* Closest translation I can think of */ - sshfwd_send_exit_signal( - sess->c, PTRLEN_LITERAL("HUP"), false, ptrlen_from_asciz(message)); - - sess->ignoring_input = true; -} - -static bool sesschan_get_window_pixel_size(Seat *seat, int *width, int *height) -{ - sesschan *sess = container_of(seat, sesschan, seat); - - *width = sess->wp; - *height = sess->hp; - - return true; -} - -/* ---------------------------------------------------------------------- - * Built-in SFTP subsystem. - */ - -static size_t sftp_chan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - sesschan *sess = container_of(chan, sesschan, chan); - - bufchain_add(&sess->subsys_input, data, length); - - while (bufchain_size(&sess->subsys_input) >= 4) { - char lenbuf[4]; - unsigned pktlen; - struct sftp_packet *pkt, *reply; - - bufchain_fetch(&sess->subsys_input, lenbuf, 4); - pktlen = GET_32BIT_MSB_FIRST(lenbuf); - - if (bufchain_size(&sess->subsys_input) - 4 < pktlen) - break; /* wait for more data */ - - bufchain_consume(&sess->subsys_input, 4); - pkt = sftp_recv_prepare(pktlen); - bufchain_fetch_consume(&sess->subsys_input, pkt->data, pktlen); - sftp_recv_finish(pkt); - reply = sftp_handle_request(sess->sftpsrv, pkt); - sftp_pkt_free(pkt); - - sftp_send_prepare(reply); - sshfwd_write(sess->c, reply->data, reply->length); - sftp_pkt_free(reply); - } - - return 0; -} - -static void sftp_chan_send_eof(Channel *chan) -{ - sesschan *sess = container_of(chan, sesschan, chan); - sshfwd_write_eof(sess->c); -} - -static char *sftp_log_close_msg(Channel *chan) -{ - return dupstr("Session channel (SFTP) closed"); -} - -/* ---------------------------------------------------------------------- - * Built-in SCP subsystem. - */ - -static size_t scp_chan_send(Channel *chan, bool is_stderr, - const void *data, size_t length) -{ - sesschan *sess = container_of(chan, sesschan, chan); - return scp_send(sess->scpsrv, data, length); -} - -static void scp_chan_send_eof(Channel *chan) -{ - sesschan *sess = container_of(chan, sesschan, chan); - scp_eof(sess->scpsrv); -} - -static char *scp_log_close_msg(Channel *chan) -{ - return dupstr("Session channel (SCP) closed"); -} - -static void scp_set_input_wanted(Channel *chan, bool wanted) -{ - sesschan *sess = container_of(chan, sesschan, chan); - scp_throttle(sess->scpsrv, !wanted); -} diff --git a/ssh/sftp.c b/ssh/sftp.c deleted file mode 100644 index 09d173bb7..000000000 --- a/ssh/sftp.c +++ /dev/null @@ -1,1204 +0,0 @@ -/* - * sftp.c: SFTP generic client code. - */ - -#include -#include -#include -#include -#include - -#include "misc.h" -#include "tree234.h" -#include "sftp.h" - -static const char *fxp_error_message; -static int fxp_errtype; - -static void fxp_internal_error(const char *msg); - -/* ---------------------------------------------------------------------- - * Client-specific parts of the send- and receive-packet system. - */ - -static bool sftp_send(struct sftp_packet *pkt) -{ - bool ret; - sftp_send_prepare(pkt); - 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]; - - if (!sftp_recvdata(x, 4)) - return NULL; - - /* Impose _some_ upper bound on packet size. We never expect to - * receive more than 32K of data in response to an FXP_READ, - * because we decide how much data to ask for. FXP_READDIR and - * pathname-returning things like FXP_REALPATH don't have an - * explicit bound, so I suppose we just have to trust the server - * to be sensible. */ - unsigned pktlen = GET_32BIT_MSB_FIRST(x); - if (pktlen > (1<<20)) - return NULL; - - pkt = sftp_recv_prepare(pktlen); - - if (!sftp_recvdata(pkt->data, pkt->length)) { - sftp_pkt_free(pkt); - return NULL; - } - - if (!sftp_recv_finish(pkt)) { - sftp_pkt_free(pkt); - return NULL; - } - - return pkt; -} - -/* ---------------------------------------------------------------------- - * Request ID allocation and temporary dispatch routines. - */ - -#define REQUEST_ID_OFFSET 256 - -struct sftp_request { - unsigned id; - bool 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 = false; - 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 = true; -} - -struct sftp_request *sftp_find_request(struct sftp_packet *pktin) -{ - unsigned id; - struct sftp_request *req; - - if (!pktin) { - fxp_internal_error("did not receive a valid SFTP packet\n"); - return NULL; - } - - id = get_uint32(pktin); - if (get_err(pktin)) { - 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; -} - -/* ---------------------------------------------------------------------- - * 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 { - fxp_errtype = get_uint32(pktin); - if (get_err(pktin)) { - fxp_error_message = "malformed FXP_STATUS packet"; - fxp_errtype = -1; - } else { - if (fxp_errtype < 0 || fxp_errtype >= lenof(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(const 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. - */ -bool fxp_init(void) -{ - struct sftp_packet *pktout, *pktin; - unsigned long remotever; - - pktout = sftp_pkt_init(SSH_FXP_INIT); - put_uint32(pktout, SFTP_PROTO_VERSION); - sftp_send(pktout); - - pktin = sftp_recv(); - if (!pktin) { - fxp_internal_error("could not connect"); - return false; - } - if (pktin->type != SSH_FXP_VERSION) { - fxp_internal_error("did not receive FXP_VERSION"); - sftp_pkt_free(pktin); - return false; - } - remotever = get_uint32(pktin); - if (get_err(pktin)) { - fxp_internal_error("malformed FXP_VERSION packet"); - sftp_pkt_free(pktin); - return false; - } - if (remotever > SFTP_PROTO_VERSION) { - fxp_internal_error("remote protocol is more advanced than we support"); - sftp_pkt_free(pktin); - return false; - } - /* - * 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 true; -} - -/* - * Canonify a pathname. - */ -struct sftp_request *fxp_realpath_send(const char *path) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_REALPATH); - put_uint32(pktout, req->id); - put_stringz(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; - ptrlen name; - - count = get_uint32(pktin); - if (get_err(pktin) || count != 1) { - fxp_internal_error("REALPATH did not return name count of 1\n"); - sftp_pkt_free(pktin); - return NULL; - } - name = get_string(pktin); - if (get_err(pktin)) { - fxp_internal_error("REALPATH returned malformed FXP_NAME\n"); - sftp_pkt_free(pktin); - return NULL; - } - path = mkstr(name); - 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(const char *path, int type, - const struct fxp_attrs *attrs) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_OPEN); - put_uint32(pktout, req->id); - put_stringz(pktout, path); - put_uint32(pktout, type); - put_fxp_attrs(pktout, attrs ? *attrs : no_attrs); - sftp_send(pktout); - - return req; -} - -static struct fxp_handle *fxp_got_handle(struct sftp_packet *pktin) -{ - ptrlen id; - struct fxp_handle *handle; - - id = get_string(pktin); - if (get_err(pktin)) { - fxp_internal_error("received malformed FXP_HANDLE"); - sftp_pkt_free(pktin); - return NULL; - } - handle = snew(struct fxp_handle); - handle->hstring = mkstr(id); - handle->hlen = id.len; - sftp_pkt_free(pktin); - return handle; -} - -struct fxp_handle *fxp_open_recv(struct sftp_packet *pktin, - struct sftp_request *req) -{ - sfree(req); - - if (pktin->type == SSH_FXP_HANDLE) { - return fxp_got_handle(pktin); - } else { - fxp_got_status(pktin); - sftp_pkt_free(pktin); - return NULL; - } -} - -/* - * Open a directory. - */ -struct sftp_request *fxp_opendir_send(const char *path) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_OPENDIR); - put_uint32(pktout, req->id); - put_stringz(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) { - return fxp_got_handle(pktin); - } 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); - put_uint32(pktout, req->id); - put_string(pktout, handle->hstring, handle->hlen); - sftp_send(pktout); - - sfree(handle->hstring); - sfree(handle); - - return req; -} - -bool fxp_close_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; -} - -struct sftp_request *fxp_mkdir_send(const char *path, - const struct fxp_attrs *attrs) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_MKDIR); - put_uint32(pktout, req->id); - put_stringz(pktout, path); - put_fxp_attrs(pktout, attrs ? *attrs : no_attrs); - sftp_send(pktout); - - return req; -} - -bool fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req) -{ - int id; - sfree(req); - id = fxp_got_status(pktin); - sftp_pkt_free(pktin); - return id == 1; -} - -struct sftp_request *fxp_rmdir_send(const char *path) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_RMDIR); - put_uint32(pktout, req->id); - put_stringz(pktout, path); - sftp_send(pktout); - - return req; -} - -bool fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req) -{ - int id; - sfree(req); - id = fxp_got_status(pktin); - sftp_pkt_free(pktin); - return id == 1; -} - -struct sftp_request *fxp_remove_send(const char *fname) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_REMOVE); - put_uint32(pktout, req->id); - put_stringz(pktout, fname); - sftp_send(pktout); - - return req; -} - -bool fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req) -{ - int id; - sfree(req); - id = fxp_got_status(pktin); - sftp_pkt_free(pktin); - return id == 1; -} - -struct sftp_request *fxp_rename_send(const char *srcfname, - const char *dstfname) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_RENAME); - put_uint32(pktout, req->id); - put_stringz(pktout, srcfname); - put_stringz(pktout, dstfname); - sftp_send(pktout); - - return req; -} - -bool fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req) -{ - int id; - sfree(req); - id = fxp_got_status(pktin); - sftp_pkt_free(pktin); - return id == 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(const char *fname) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_STAT); - put_uint32(pktout, req->id); - put_stringz(pktout, fname); - sftp_send(pktout); - - return req; -} - -static bool fxp_got_attrs(struct sftp_packet *pktin, struct fxp_attrs *attrs) -{ - get_fxp_attrs(pktin, attrs); - if (get_err(pktin)) { - fxp_internal_error("malformed SSH_FXP_ATTRS packet"); - sftp_pkt_free(pktin); - return false; - } - sftp_pkt_free(pktin); - return true; -} - -bool fxp_stat_recv(struct sftp_packet *pktin, struct sftp_request *req, - struct fxp_attrs *attrs) -{ - sfree(req); - if (pktin->type == SSH_FXP_ATTRS) { - return fxp_got_attrs(pktin, attrs); - } else { - fxp_got_status(pktin); - sftp_pkt_free(pktin); - return false; - } -} - -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); - put_uint32(pktout, req->id); - put_string(pktout, handle->hstring, handle->hlen); - sftp_send(pktout); - - return req; -} - -bool fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, - struct fxp_attrs *attrs) -{ - sfree(req); - if (pktin->type == SSH_FXP_ATTRS) { - return fxp_got_attrs(pktin, attrs); - } else { - fxp_got_status(pktin); - sftp_pkt_free(pktin); - return false; - } -} - -/* - * Set the attributes of a file. - */ -struct sftp_request *fxp_setstat_send(const char *fname, - struct fxp_attrs attrs) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_SETSTAT); - put_uint32(pktout, req->id); - put_stringz(pktout, fname); - put_fxp_attrs(pktout, attrs); - sftp_send(pktout); - - return req; -} - -bool fxp_setstat_recv(struct sftp_packet *pktin, struct sftp_request *req) -{ - int id; - sfree(req); - id = fxp_got_status(pktin); - sftp_pkt_free(pktin); - return id == 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); - put_uint32(pktout, req->id); - put_string(pktout, handle->hstring, handle->hlen); - put_fxp_attrs(pktout, attrs); - sftp_send(pktout); - - return req; -} - -bool fxp_fsetstat_recv(struct sftp_packet *pktin, struct sftp_request *req) -{ - int id; - sfree(req); - id = fxp_got_status(pktin); - sftp_pkt_free(pktin); - return id == 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_t offset, int len) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_READ); - put_uint32(pktout, req->id); - put_string(pktout, handle->hstring, handle->hlen); - put_uint64(pktout, offset); - put_uint32(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) { - ptrlen data; - - data = get_string(pktin); - if (get_err(pktin)) { - fxp_internal_error("READ returned malformed SSH_FXP_DATA packet"); - sftp_pkt_free(pktin); - return -1; - } - - if (data.len > len) { - fxp_internal_error("READ returned more bytes than requested"); - sftp_pkt_free(pktin); - return -1; - } - - memcpy(buffer, data.ptr, data.len); - sftp_pkt_free(pktin); - return data.len; - } 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); - put_uint32(pktout, req->id); - put_string(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 *names; - unsigned long i; - - i = get_uint32(pktin); - - /* - * 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 (get_err(pktin) || i > get_avail(pktin) / 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; - } - - names = snew(struct fxp_names); - names->nnames = i; - names->names = snewn(names->nnames, struct fxp_name); - for (i = 0; i < (unsigned long)names->nnames; i++) { - names->names[i].filename = mkstr(get_string(pktin)); - names->names[i].longname = mkstr(get_string(pktin)); - get_fxp_attrs(pktin, &names->names[i].attrs); - } - - if (get_err(pktin)) { - fxp_internal_error("malformed FXP_NAME packet"); - for (i = 0; i < (unsigned long)names->nnames; i++) { - sfree(names->names[i].filename); - sfree(names->names[i].longname); - } - sfree(names->names); - sfree(names); - sfree(pktin); - return NULL; - } - sftp_pkt_free(pktin); - return names; - } 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, - void *buffer, uint64_t offset, int len) -{ - struct sftp_request *req = sftp_alloc_request(); - struct sftp_packet *pktout; - - pktout = sftp_pkt_init(SSH_FXP_WRITE); - put_uint32(pktout, req->id); - put_string(pktout, handle->hstring, handle->hlen); - put_uint64(pktout, offset); - put_string(pktout, buffer, len); - sftp_send(pktout); - - return req; -} - -bool 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 *orig) -{ - struct fxp_name *copy; - copy = snew(struct fxp_name); - copy->filename = dupstr(orig->filename); - copy->longname = dupstr(orig->longname); - copy->attrs = orig->attrs; /* structure copy */ - return copy; -} - -/* - * 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_t offset; - struct req *next, *prev; -}; - -struct fxp_xfer { - uint64_t offset, furthestdata, filesize; - int req_totalsize, req_maxsize; - bool eof, err; - struct fxp_handle *fh; - struct req *head, *tail; -}; - -static struct fxp_xfer *xfer_init(struct fxp_handle *fh, uint64_t 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 = false; - xfer->filesize = UINT64_MAX; - xfer->furthestdata = 0; - - return xfer; -} - -bool 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 += rr->len; - xfer->req_totalsize += rr->len; - -#ifdef DEBUG_DOWNLOAD - printf("queueing read request %p at %"PRIu64"\n", rr, rr->offset); -#endif - } -} - -struct fxp_xfer *xfer_download_init(struct fxp_handle *fh, uint64_t 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->retlen = 0; - 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 && xfer->furthestdata < rr->offset) { - xfer->furthestdata = rr->offset; -#ifdef DEBUG_DOWNLOAD - printf("setting furthestdata = %"PRIu64"\n", xfer->furthestdata); -#endif - } - - if (rr->retlen < rr->len) { - uint64_t filesize = rr->offset + (rr->retlen < 0 ? 0 : rr->retlen); -#ifdef DEBUG_DOWNLOAD - printf("short block! trying filesize = %"PRIu64"\n", filesize); -#endif - if (xfer->filesize > filesize) { - xfer->filesize = filesize; -#ifdef DEBUG_DOWNLOAD - printf("actually changing filesize\n"); -#endif - } - } - - if (xfer->furthestdata > xfer->filesize) { - 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 = true; -} - -bool 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 true; - } else - return false; -} - -struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64_t 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 = true; - - return xfer; -} - -bool xfer_upload_ready(struct fxp_xfer *xfer) -{ - return sftp_sendbuffer() == 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 += rr->len; - xfer->req_totalsize += rr->len; - -#ifdef DEBUG_UPLOAD - printf("queueing write request %p at %"PRIu64" [len %d]\n", - rr, rr->offset, 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; - bool 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 ? 1 : 0); -#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/ssh/sftp.h b/ssh/sftp.h deleted file mode 100644 index fb7d3267f..000000000 --- a/ssh/sftp.h +++ /dev/null @@ -1,544 +0,0 @@ -/* - * sftp.h: definitions for SFTP and the sftp.c routines. - */ - -#include "defs.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 - -#define PERMS_DIRECTORY 040000 - -/* - * 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 and then returns true, or it returns false - * for failure. - * - * sftp_senddata returns true on success, false on failure. - * - * sftp_sendbuffer returns the size of the backlog of data in the - * transmit queue. - */ -bool sftp_senddata(const char *data, size_t len); -size_t sftp_sendbuffer(void); -bool sftp_recvdata(char *data, size_t len); - -/* - * Free sftp_requests - */ -void sftp_cleanup_request(void); - -struct fxp_attrs { - unsigned long flags; - uint64_t size; - unsigned long uid; - unsigned long gid; - unsigned long permissions; - unsigned long atime; - unsigned long mtime; -}; -extern const struct fxp_attrs no_attrs; - -/* - * 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, defaultperms) \ - ((attrs).flags & SSH_FILEXFER_ATTR_PERMISSIONS ? \ - (attrs).permissions : defaultperms) - -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; - -/* - * Packet-manipulation functions. - */ - -struct sftp_packet { - char *data; - size_t length, maxlen, savedpos; - int type; - BinarySink_IMPLEMENTATION; - BinarySource_IMPLEMENTATION; -}; - -/* When sending a packet, create it with sftp_pkt_init, then add - * things to it by treating it as a BinarySink. When it's done, call - * sftp_send_prepare, and then pkt->data and pkt->length describe its - * wire format. */ -struct sftp_packet *sftp_pkt_init(int pkt_type); -void sftp_send_prepare(struct sftp_packet *pkt); - -/* When receiving a packet, create it with sftp_recv_prepare once you - * decode its length from the first 4 bytes of wire data. Then write - * that many bytes into pkt->data, and call sftp_recv_finish to set up - * the type code and BinarySource. */ -struct sftp_packet *sftp_recv_prepare(unsigned length); -bool sftp_recv_finish(struct sftp_packet *pkt); - -/* Either kind of packet can be freed afterwards with sftp_pkt_free. */ -void sftp_pkt_free(struct sftp_packet *pkt); - -void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs); -bool BinarySource_get_fxp_attrs(BinarySource *src, struct fxp_attrs *attrs); -#define put_fxp_attrs(bs, attrs) \ - BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs) -#define get_fxp_attrs(bs, attrs) \ - BinarySource_get_fxp_attrs(BinarySource_UPCAST(bs), attrs) - -/* - * Error handling. - */ - -const char *fxp_error(void); -int fxp_error_type(void); - -/* - * Perform exchange of init/version packets. Return false on failure. - */ -bool 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(const 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(const char *path, int type, - const 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(const char *path); -struct fxp_handle *fxp_opendir_recv(struct sftp_packet *pktin, - struct sftp_request *req); - -/* - * Close a file/dir. Returns true on success, false on error. - */ -struct sftp_request *fxp_close_send(struct fxp_handle *handle); -bool fxp_close_recv(struct sftp_packet *pktin, struct sftp_request *req); - -/* - * Make a directory. - */ -struct sftp_request *fxp_mkdir_send(const char *path, - const struct fxp_attrs *attrs); -bool fxp_mkdir_recv(struct sftp_packet *pktin, struct sftp_request *req); - -/* - * Remove a directory. - */ -struct sftp_request *fxp_rmdir_send(const char *path); -bool fxp_rmdir_recv(struct sftp_packet *pktin, struct sftp_request *req); - -/* - * Remove a file. - */ -struct sftp_request *fxp_remove_send(const char *fname); -bool fxp_remove_recv(struct sftp_packet *pktin, struct sftp_request *req); - -/* - * Rename a file. - */ -struct sftp_request *fxp_rename_send(const char *srcfname, - const char *dstfname); -bool fxp_rename_recv(struct sftp_packet *pktin, struct sftp_request *req); - -/* - * Return file attributes. - */ -struct sftp_request *fxp_stat_send(const char *fname); -bool 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); -bool fxp_fstat_recv(struct sftp_packet *pktin, struct sftp_request *req, - struct fxp_attrs *attrs); - -/* - * Set file attributes. - */ -struct sftp_request *fxp_setstat_send(const char *fname, - struct fxp_attrs attrs); -bool 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); -bool 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_t offset, int len); -int fxp_read_recv(struct sftp_packet *pktin, struct sftp_request *req, - char *buffer, int len); - -/* - * Write to a file. - */ -struct sftp_request *fxp_write_send(struct fxp_handle *handle, - void *buffer, uint64_t offset, int len); -bool 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_t offset); -void xfer_download_queue(struct fxp_xfer *xfer); -int xfer_download_gotpkt(struct fxp_xfer *xfer, struct sftp_packet *pktin); -bool xfer_download_data(struct fxp_xfer *xfer, void **buf, int *len); - -struct fxp_xfer *xfer_upload_init(struct fxp_handle *fh, uint64_t offset); -bool 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); - -bool xfer_done(struct fxp_xfer *xfer); -void xfer_set_error(struct fxp_xfer *xfer); -void xfer_cleanup(struct fxp_xfer *xfer); - -/* - * Vtable for the platform-specific filesystem implementation that - * answers requests in an SFTP server. - */ -typedef struct SftpReplyBuilder SftpReplyBuilder; -struct SftpServer { - const SftpServerVtable *vt; -}; -struct SftpServerVtable { - SftpServer *(*new)(const SftpServerVtable *vt); - void (*free)(SftpServer *srv); - - /* - * Handle actual filesystem requests. - * - * Each of these functions replies by calling an appropriate - * sftp_reply_foo() function on the given reply packet. - */ - - /* Should call fxp_reply_error or fxp_reply_simple_name */ - void (*realpath)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path); - - /* Should call fxp_reply_error or fxp_reply_handle */ - void (*open)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, unsigned flags, struct fxp_attrs attrs); - - /* Should call fxp_reply_error or fxp_reply_handle */ - void (*opendir)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*close)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*mkdir)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, struct fxp_attrs attrs); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*rmdir)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*remove)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*rename)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen srcpath, ptrlen dstpath); - - /* Should call fxp_reply_error or fxp_reply_attrs */ - void (*stat)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path, - bool follow_symlinks); - - /* Should call fxp_reply_error or fxp_reply_attrs */ - void (*fstat)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*setstat)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, struct fxp_attrs attrs); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*fsetstat)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, struct fxp_attrs attrs); - - /* Should call fxp_reply_error or fxp_reply_data */ - void (*read)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, unsigned length); - - /* Should call fxp_reply_error or fxp_reply_ok */ - void (*write)(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, ptrlen data); - - /* Should call fxp_reply_error, or fxp_reply_name_count once and - * then fxp_reply_full_name that many times */ - void (*readdir)(SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle, - int max_entries, bool omit_longname); -}; - -static inline SftpServer *sftpsrv_new(const SftpServerVtable *vt) -{ return vt->new(vt); } -static inline void sftpsrv_free(SftpServer *srv) -{ srv->vt->free(srv); } -static inline void sftpsrv_realpath(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path) -{ srv->vt->realpath(srv, reply, path); } -static inline void sftpsrv_open( - SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, unsigned flags, struct fxp_attrs attrs) -{ srv->vt->open(srv, reply, path, flags, attrs); } -static inline void sftpsrv_opendir( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen path) -{ srv->vt->opendir(srv, reply, path); } -static inline void sftpsrv_close( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle) -{ srv->vt->close(srv, reply, handle); } -static inline void sftpsrv_mkdir(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, struct fxp_attrs attrs) -{ srv->vt->mkdir(srv, reply, path, attrs); } -static inline void sftpsrv_rmdir( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen path) -{ srv->vt->rmdir(srv, reply, path); } -static inline void sftpsrv_remove( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen path) -{ srv->vt->remove(srv, reply, path); } -static inline void sftpsrv_rename(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen srcpath, ptrlen dstpath) -{ srv->vt->rename(srv, reply, srcpath, dstpath); } -static inline void sftpsrv_stat( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen path, bool follow) -{ srv->vt->stat(srv, reply, path, follow); } -static inline void sftpsrv_fstat( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle) -{ srv->vt->fstat(srv, reply, handle); } -static inline void sftpsrv_setstat(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, struct fxp_attrs attrs) -{ srv->vt->setstat(srv, reply, path, attrs); } -static inline void sftpsrv_fsetstat(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, struct fxp_attrs attrs) -{ srv->vt->fsetstat(srv, reply, handle, attrs); } -static inline void sftpsrv_read( - SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, unsigned length) -{ srv->vt->read(srv, reply, handle, offset, length); } -static inline void sftpsrv_write(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, ptrlen data) -{ srv->vt->write(srv, reply, handle, offset, data); } -static inline void sftpsrv_readdir( - SftpServer *srv, SftpReplyBuilder *reply, ptrlen handle, - int max_entries, bool omit_longname) -{ srv->vt->readdir(srv, reply, handle, max_entries, omit_longname); } - -typedef struct SftpReplyBuilderVtable SftpReplyBuilderVtable; -struct SftpReplyBuilder { - const SftpReplyBuilderVtable *vt; -}; -struct SftpReplyBuilderVtable { - void (*reply_ok)(SftpReplyBuilder *reply); - void (*reply_error)(SftpReplyBuilder *reply, unsigned code, - const char *msg); - void (*reply_simple_name)(SftpReplyBuilder *reply, ptrlen name); - void (*reply_name_count)(SftpReplyBuilder *reply, unsigned count); - void (*reply_full_name)(SftpReplyBuilder *reply, ptrlen name, - ptrlen longname, struct fxp_attrs attrs); - void (*reply_handle)(SftpReplyBuilder *reply, ptrlen handle); - void (*reply_data)(SftpReplyBuilder *reply, ptrlen data); - void (*reply_attrs)(SftpReplyBuilder *reply, struct fxp_attrs attrs); -}; - -static inline void fxp_reply_ok(SftpReplyBuilder *reply) -{ reply->vt->reply_ok(reply); } -static inline void fxp_reply_error(SftpReplyBuilder *reply, unsigned code, - const char *msg) -{ reply->vt->reply_error(reply, code, msg); } -static inline void fxp_reply_simple_name(SftpReplyBuilder *reply, ptrlen name) -{ reply->vt->reply_simple_name(reply, name); } -static inline void fxp_reply_name_count( - SftpReplyBuilder *reply, unsigned count) -{ reply->vt->reply_name_count(reply, count); } -static inline void fxp_reply_full_name(SftpReplyBuilder *reply, ptrlen name, - ptrlen longname, struct fxp_attrs attrs) -{ reply->vt->reply_full_name(reply, name, longname, attrs); } -static inline void fxp_reply_handle(SftpReplyBuilder *reply, ptrlen handle) -{ reply->vt->reply_handle(reply, handle); } -static inline void fxp_reply_data(SftpReplyBuilder *reply, ptrlen data) -{ reply->vt->reply_data(reply, data); } -static inline void fxp_reply_attrs( - SftpReplyBuilder *reply, struct fxp_attrs attrs) -{ reply->vt->reply_attrs(reply, attrs); } - -/* - * The usual implementation of an SftpReplyBuilder, containing a - * 'struct sftp_packet' which is assumed to be already initialised - * before one of the above request methods is called. - */ -extern const struct SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt; -typedef struct DefaultSftpReplyBuilder DefaultSftpReplyBuilder; -struct DefaultSftpReplyBuilder { - SftpReplyBuilder rb; - struct sftp_packet *pkt; -}; - -/* - * The top-level function that handles an SFTP request, given an - * implementation of the above SftpServer abstraction to do the actual - * filesystem work. It handles all the marshalling and unmarshalling - * of packets, and the copying of request ids into the responses. - */ -struct sftp_packet *sftp_handle_request( - SftpServer *srv, struct sftp_packet *request); - -/* ---------------------------------------------------------------------- - * Not exactly SFTP-related, but here's a system that implements an - * old-fashioned SCP server module, given an SftpServer vtable to use - * as its underlying filesystem access. - */ - -typedef struct ScpServer ScpServer; -typedef struct ScpServerVtable ScpServerVtable; -struct ScpServer { - const struct ScpServerVtable *vt; -}; -struct ScpServerVtable { - void (*free)(ScpServer *s); - - size_t (*send)(ScpServer *s, const void *data, size_t length); - void (*throttle)(ScpServer *s, bool throttled); - void (*eof)(ScpServer *s); -}; - -static inline void scp_free(ScpServer *s) -{ s->vt->free(s); } -static inline size_t scp_send(ScpServer *s, const void *data, size_t length) -{ return s->vt->send(s, data, length); } -static inline void scp_throttle(ScpServer *s, bool throttled) -{ s->vt->throttle(s, throttled); } -static inline void scp_eof(ScpServer *s) -{ s->vt->eof(s); } - -/* - * Create an ScpServer by calling this function, giving it the command - * you received from the SSH client to execute. If that command is - * recognised as an scp command, it will construct an ScpServer object - * and return it; otherwise, it will return NULL, and you should - * execute the command in whatever way you normally would. - * - * The ScpServer will generate output for the client by writing it to - * the provided SshChannel using sshfwd_write; you pass it input using - * the send method in its own vtable. - */ -ScpServer *scp_recognise_exec( - SshChannel *sc, const SftpServerVtable *sftpserver_vt, ptrlen command); diff --git a/ssh/sftpcommon.c b/ssh/sftpcommon.c deleted file mode 100644 index f2f9a3bc0..000000000 --- a/ssh/sftpcommon.c +++ /dev/null @@ -1,139 +0,0 @@ -/* - * sftpcommon.c: SFTP code shared between client and server. - */ - -#include -#include -#include -#include -#include - -#include "misc.h" -#include "sftp.h" - -static void sftp_pkt_BinarySink_write( - BinarySink *bs, const void *data, size_t length) -{ - struct sftp_packet *pkt = BinarySink_DOWNCAST(bs, struct sftp_packet); - - assert(length <= 0xFFFFFFFFU - pkt->length); - - sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, length); - memcpy(pkt->data + pkt->length, data, length); - pkt->length += length; -} - -struct sftp_packet *sftp_pkt_init(int type) -{ - struct sftp_packet *pkt; - pkt = snew(struct sftp_packet); - pkt->data = NULL; - pkt->savedpos = -1; - pkt->length = 0; - pkt->maxlen = 0; - pkt->type = type; - BinarySink_INIT(pkt, sftp_pkt_BinarySink_write); - put_uint32(pkt, 0); /* length field will be filled in later */ - put_byte(pkt, 0); /* so will the type field */ - return pkt; -} - -void BinarySink_put_fxp_attrs(BinarySink *bs, struct fxp_attrs attrs) -{ - put_uint32(bs, attrs.flags); - if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) - put_uint64(bs, attrs.size); - if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) { - put_uint32(bs, attrs.uid); - put_uint32(bs, attrs.gid); - } - if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - put_uint32(bs, attrs.permissions); - } - if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { - put_uint32(bs, attrs.atime); - put_uint32(bs, attrs.mtime); - } - if (attrs.flags & SSH_FILEXFER_ATTR_EXTENDED) { - /* - * We currently don't support sending any extended - * attributes. - */ - } -} - -const struct fxp_attrs no_attrs = { 0 }; - -#define put_fxp_attrs(bs, attrs) \ - BinarySink_put_fxp_attrs(BinarySink_UPCAST(bs), attrs) - -bool BinarySource_get_fxp_attrs(BinarySource *src, struct fxp_attrs *attrs) -{ - attrs->flags = get_uint32(src); - if (attrs->flags & SSH_FILEXFER_ATTR_SIZE) - attrs->size = get_uint64(src); - if (attrs->flags & SSH_FILEXFER_ATTR_UIDGID) { - attrs->uid = get_uint32(src); - attrs->gid = get_uint32(src); - } - if (attrs->flags & SSH_FILEXFER_ATTR_PERMISSIONS) - attrs->permissions = get_uint32(src); - if (attrs->flags & SSH_FILEXFER_ATTR_ACMODTIME) { - attrs->atime = get_uint32(src); - attrs->mtime = get_uint32(src); - } - if (attrs->flags & SSH_FILEXFER_ATTR_EXTENDED) { - unsigned long count = get_uint32(src); - while (count--) { - if (get_err(src)) { - /* Truncated packet. Don't waste time looking for - * attributes that aren't there. Caller should spot - * the truncation. */ - break; - } - /* - * We should try to analyse these, if we ever find one - * we recognise. - */ - get_string(src); - get_string(src); - } - } - return true; -} - -void sftp_pkt_free(struct sftp_packet *pkt) -{ - if (pkt->data) - sfree(pkt->data); - sfree(pkt); -} - -void sftp_send_prepare(struct sftp_packet *pkt) -{ - PUT_32BIT_MSB_FIRST(pkt->data, pkt->length - 4); - if (pkt->length >= 5) { - /* Rewrite the type code, in case the caller changed its mind - * about pkt->type since calling sftp_pkt_init */ - pkt->data[4] = pkt->type; - } -} - -struct sftp_packet *sftp_recv_prepare(unsigned length) -{ - struct sftp_packet *pkt; - - pkt = snew(struct sftp_packet); - pkt->savedpos = 0; - pkt->length = pkt->maxlen = length; - pkt->data = snewn(pkt->length, char); - - return pkt; -} - -bool sftp_recv_finish(struct sftp_packet *pkt) -{ - BinarySource_INIT(pkt, pkt->data, pkt->length); - pkt->type = get_byte(pkt); - return !get_err(pkt); -} diff --git a/ssh/sftpserver.c b/ssh/sftpserver.c deleted file mode 100644 index ba2168722..000000000 --- a/ssh/sftpserver.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Implement the centralised parts of the server side of SFTP. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "sftp.h" - -struct sftp_packet *sftp_handle_request( - SftpServer *srv, struct sftp_packet *req) -{ - struct sftp_packet *reply; - unsigned id; - uint32_t flags; - ptrlen path, dstpath, handle, data; - uint64_t offset; - unsigned length; - struct fxp_attrs attrs; - DefaultSftpReplyBuilder dsrb; - SftpReplyBuilder *rb; - - if (req->type == SSH_FXP_INIT) { - /* - * Special case which doesn't have a request id at the start. - */ - reply = sftp_pkt_init(SSH_FXP_VERSION); - /* - * Since we support only the lowest protocol version, we don't - * need to take the min of this and the client's version, or - * even to bother reading the client version number out of the - * input packet. - */ - put_uint32(reply, SFTP_PROTO_VERSION); - return reply; - } - - /* - * Centralise the request id handling. We'll overwrite the type - * code of the output packet later. - */ - id = get_uint32(req); - reply = sftp_pkt_init(0); - put_uint32(reply, id); - - dsrb.rb.vt = &DefaultSftpReplyBuilder_vt; - dsrb.pkt = reply; - rb = &dsrb.rb; - - switch (req->type) { - case SSH_FXP_REALPATH: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_realpath(srv, rb, path); - break; - - case SSH_FXP_OPEN: - path = get_string(req); - flags = get_uint32(req); - get_fxp_attrs(req, &attrs); - if (get_err(req)) - goto decode_error; - if ((flags & (SSH_FXF_READ|SSH_FXF_WRITE)) == 0) { - fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, - "open without READ or WRITE flag"); - } else if ((flags & (SSH_FXF_CREAT|SSH_FXF_TRUNC)) == SSH_FXF_TRUNC) { - fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, - "open with TRUNC but not CREAT"); - } else if ((flags & (SSH_FXF_CREAT|SSH_FXF_EXCL)) == SSH_FXF_EXCL) { - fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, - "open with EXCL but not CREAT"); - } else { - sftpsrv_open(srv, rb, path, flags, attrs); - } - break; - - case SSH_FXP_OPENDIR: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_opendir(srv, rb, path); - break; - - case SSH_FXP_CLOSE: - handle = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_close(srv, rb, handle); - break; - - case SSH_FXP_MKDIR: - path = get_string(req); - get_fxp_attrs(req, &attrs); - if (get_err(req)) - goto decode_error; - sftpsrv_mkdir(srv, rb, path, attrs); - break; - - case SSH_FXP_RMDIR: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_rmdir(srv, rb, path); - break; - - case SSH_FXP_REMOVE: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_remove(srv, rb, path); - break; - - case SSH_FXP_RENAME: - path = get_string(req); - dstpath = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_rename(srv, rb, path, dstpath); - break; - - case SSH_FXP_STAT: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_stat(srv, rb, path, true); - break; - - case SSH_FXP_LSTAT: - path = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_stat(srv, rb, path, false); - break; - - case SSH_FXP_FSTAT: - handle = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_fstat(srv, rb, handle); - break; - - case SSH_FXP_SETSTAT: - path = get_string(req); - get_fxp_attrs(req, &attrs); - if (get_err(req)) - goto decode_error; - sftpsrv_setstat(srv, rb, path, attrs); - break; - - case SSH_FXP_FSETSTAT: - handle = get_string(req); - get_fxp_attrs(req, &attrs); - if (get_err(req)) - goto decode_error; - sftpsrv_fsetstat(srv, rb, handle, attrs); - break; - - case SSH_FXP_READ: - handle = get_string(req); - offset = get_uint64(req); - length = get_uint32(req); - if (get_err(req)) - goto decode_error; - sftpsrv_read(srv, rb, handle, offset, length); - break; - - case SSH_FXP_READDIR: - handle = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_readdir(srv, rb, handle, INT_MAX, false); - break; - - case SSH_FXP_WRITE: - handle = get_string(req); - offset = get_uint64(req); - data = get_string(req); - if (get_err(req)) - goto decode_error; - sftpsrv_write(srv, rb, handle, offset, data); - break; - - default: - if (get_err(req)) - goto decode_error; - fxp_reply_error(rb, SSH_FX_OP_UNSUPPORTED, - "Unrecognised request type"); - break; - - decode_error: - fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, "Unable to decode request"); - } - - return reply; -} - -static void default_reply_ok(SftpReplyBuilder *reply) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_STATUS; - put_uint32(d->pkt, SSH_FX_OK); - put_stringz(d->pkt, ""); -} - -static void default_reply_error( - SftpReplyBuilder *reply, unsigned code, const char *msg) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_STATUS; - put_uint32(d->pkt, code); - put_stringz(d->pkt, msg); -} - -static void default_reply_name_count(SftpReplyBuilder *reply, unsigned count) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_NAME; - put_uint32(d->pkt, count); -} - -static void default_reply_full_name(SftpReplyBuilder *reply, ptrlen name, - ptrlen longname, struct fxp_attrs attrs) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_NAME; - put_stringpl(d->pkt, name); - put_stringpl(d->pkt, longname); - put_fxp_attrs(d->pkt, attrs); -} - -static void default_reply_simple_name(SftpReplyBuilder *reply, ptrlen name) -{ - fxp_reply_name_count(reply, 1); - fxp_reply_full_name(reply, name, PTRLEN_LITERAL(""), no_attrs); -} - -static void default_reply_handle(SftpReplyBuilder *reply, ptrlen handle) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_HANDLE; - put_stringpl(d->pkt, handle); -} - -static void default_reply_data(SftpReplyBuilder *reply, ptrlen data) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_DATA; - put_stringpl(d->pkt, data); -} - -static void default_reply_attrs( - SftpReplyBuilder *reply, struct fxp_attrs attrs) -{ - DefaultSftpReplyBuilder *d = - container_of(reply, DefaultSftpReplyBuilder, rb); - d->pkt->type = SSH_FXP_ATTRS; - put_fxp_attrs(d->pkt, attrs); -} - -const SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt = { - .reply_ok = default_reply_ok, - .reply_error = default_reply_error, - .reply_simple_name = default_reply_simple_name, - .reply_name_count = default_reply_name_count, - .reply_full_name = default_reply_full_name, - .reply_handle = default_reply_handle, - .reply_data = default_reply_data, - .reply_attrs = default_reply_attrs, -}; diff --git a/ssh/sharing.c b/ssh/sharing.c deleted file mode 100644 index af1f4c094..000000000 --- a/ssh/sharing.c +++ /dev/null @@ -1,2176 +0,0 @@ -/* - * 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 - -#include "putty.h" -#include "tree234.h" -#include "ssh.h" -#include "sshcr.h" - -struct ssh_sharing_state { - 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 */ - ConnectionLayer *cl; /* instance of the ssh connection layer */ - char *server_verstring; /* server version string after "SSH-" */ - - Plug plug; -}; - -struct share_globreq; - -struct ssh_sharing_connstate { - 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 */ - - bool sent_verstring, got_verstring; - int curr_packetlen; - - unsigned char recvbuf[0x4010]; - size_t 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; - - Plug plug; -}; - -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; - bool x11_one_shot; -}; - -struct share_forwarding { - char *host; - int port; - bool active; /* has the server sent REQUEST_SUCCESS? */ - struct ssh_rportfwd *rpf; -}; - -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. - */ - bool 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; - bool 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(ssh_sharing_state *sharestate) -{ - 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 = false; - 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_remove_sharing_x11_display(cs->parent->cl, - 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 PRINTF_LIKE(2, 3) void log_downstream(struct ssh_sharing_connstate *cs, - const char *logfmt, ...) -{ - va_list ap; - char *buf; - - va_start(ap, logfmt); - buf = dupvprintf(logfmt, ap); - va_end(ap); - logeventf(cs->parent->cl->logctx, - "Connection sharing downstream #%u: %s", cs->id, buf); - sfree(buf); -} - -static PRINTF_LIKE(2, 3) void log_general(struct ssh_sharing_state *sharestate, - const char *logfmt, ...) -{ - va_list ap; - char *buf; - - va_start(ap, logfmt); - buf = dupvprintf(logfmt, ap); - va_end(ap); - logeventf(sharestate->cl->logctx, "Connection sharing: %s", buf); - sfree(buf); -} - -static void send_packet_to_downstream(struct ssh_sharing_connstate *cs, - int type, const void *pkt, int pktlen, - struct share_channel *chan) -{ - strbuf *packet; - - 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. - */ - BinarySource src[1]; - unsigned channel; - ptrlen data; - - BinarySource_BARE_INIT(src, pkt, pktlen); - channel = get_uint32(src); - data = get_string(src); - - do { - int this_len = (data.len > chan->downstream_maxpkt ? - chan->downstream_maxpkt : data.len); - - packet = strbuf_new_nm(); - put_uint32(packet, 0); /* placeholder for length field */ - put_byte(packet, type); - put_uint32(packet, channel); - put_uint32(packet, this_len); - put_data(packet, data.ptr, this_len); - data.ptr = (const char *)data.ptr + this_len; - data.len -= this_len; - PUT_32BIT_MSB_FIRST(packet->s, packet->len-4); - sk_write(cs->sock, packet->s, packet->len); - strbuf_free(packet); - } while (data.len > 0); - } else { - /* - * Just do the obvious thing. - */ - packet = strbuf_new_nm(); - put_uint32(packet, 0); /* placeholder for length field */ - put_byte(packet, type); - put_data(packet, pkt, pktlen); - PUT_32BIT_MSB_FIRST(packet->s, packet->len-4); - sk_write(cs->sock, packet->s, packet->len); - strbuf_free(packet); - } -} - -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"; - strbuf *packet; - - packet = strbuf_new(); - put_uint32(packet, hc->server_id); - put_uint32(packet, SSH2_OPEN_CONNECT_FAILED); - put_stringz(packet, reason); - put_stringz(packet, lang); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_OPEN_FAILURE, - packet->s, packet->len, - "cleanup after downstream went away"); - strbuf_free(packet); - - 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++) { - strbuf *packet; - - if (chan->state != SENT_CLOSE && chan->state != UNACKNOWLEDGED) { - packet = strbuf_new(); - put_uint32(packet, chan->server_id); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE, - packet->s, packet->len, - "cleanup after downstream went away"); - strbuf_free(packet); - - 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->cl, 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) { - strbuf *packet = strbuf_new(); - put_stringz(packet, "cancel-tcpip-forward"); - put_bool(packet, false); /* !want_reply */ - put_stringz(packet, fwd->host); - put_uint32(packet, fwd->port); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_GLOBAL_REQUEST, - packet->s, packet->len, - "cleanup after downstream went away"); - strbuf_free(packet); - - ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); - 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) { - struct ssh_sharing_state *sharestate = cs->parent; - - /* - * Now we're _really_ done, so we can get rid of cs completely. - */ - del234(sharestate->connections, cs); - log_downstream(cs, "disconnected"); - share_connstate_free(cs); - - /* - * And if this was the last downstream, notify the connection - * layer, because it might now be time to wind up the whole - * SSH connection. - */ - if (count234(sharestate->connections) == 0 && sharestate->cl) - ssh_sharing_no_more_downstreams(sharestate->cl); - } -} - -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) -{ - strbuf *packet = strbuf_new(); - put_uint32(packet, SSH2_DISCONNECT_PROTOCOL_ERROR); - put_stringz(packet, message); - put_stringz(packet, "en"); /* language */ - send_packet_to_downstream(cs, SSH2_MSG_DISCONNECT, - packet->s, packet->len, NULL); - strbuf_free(packet); - - share_begin_cleanup(cs); -} - -static void share_closing(Plug *plug, PlugCloseType type, - const char *error_msg) -{ - struct ssh_sharing_connstate *cs = container_of( - plug, struct ssh_sharing_connstate, plug); - - /* - * Most of the time, we log what went wrong when a downstream - * disappears with a socket error. One exception, though, is - * receiving EPIPE when we haven't received a protocol version - * string from the downstream, because that can happen as a result - * of plink -shareexists (opening the connection and instantly - * closing it again without bothering to read our version string). - * So that one case is not treated as a log-worthy error. - */ - if (type == PLUGCLOSE_BROKEN_PIPE && !cs->got_verstring) { - /* do nothing */; - } else if (type != PLUGCLOSE_NORMAL) { - log_downstream(cs, "Socket error: %s", error_msg); - } - share_begin_cleanup(cs); -} - -/* - * Append a message to the end of an xchannel's queue. - */ -static void share_xchannel_add_message( - struct share_xchannel *xc, int type, const void *data, int len) -{ - struct share_xchannel_message *msg; - - /* - * Allocate the 'struct share_xchannel_message' and the actual - * data in one unit. - */ - msg = snew_plus(struct share_xchannel_message, len); - msg->data = snew_plus_get_aux(msg); - msg->datalen = len; - msg->type = type; - memcpy(msg->data, data, len); - - /* - * Queue it in the xchannel. - */ - if (xc->msgtail) - xc->msgtail->next = msg; - else - xc->msghead = msg; - msg->next = NULL; - xc->msgtail = msg; -} - -static 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). - */ - bool 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. - */ - BinarySource src[1]; - BinarySource_BARE_INIT(src, msg->data, msg->datalen); - get_uint32(src); /* skip channel id */ - get_string(src); /* skip request type */ - if (get_bool(src)) { - strbuf *packet = strbuf_new(); - put_uint32(packet, xc->server_id); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, - "downstream refused X channel open"); - strbuf_free(packet); - } - } 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->cl, xc->upstream_id); - share_remove_xchannel(cs, xc); - } -} - -static void share_xchannel_confirmation( - struct ssh_sharing_connstate *cs, struct share_xchannel *xc, - struct share_channel *chan, unsigned downstream_window) -{ - strbuf *packet; - - /* - * 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_MSB_FIRST(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. - */ - packet = strbuf_new(); - put_uint32(packet, xc->server_id); - put_uint32(packet, downstream_window - xc->window); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_WINDOW_ADJUST, - packet->s, packet->len, - "window adjustment after downstream accepted X channel"); - strbuf_free(packet); -} - -static 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. - */ - strbuf *packet = strbuf_new(); - put_uint32(packet, xc->server_id); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_CLOSE, - packet->s, packet->len, - "downstream refused X channel open"); - strbuf_free(packet); - - /* - * 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(ssh_sharing_connstate *cs, share_channel *chan, - 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 share_xchannel *xc; - void *greeting; - int greeting_len; - strbuf *packet; - - /* - * 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); - packet = strbuf_new_nm(); - put_uint32(packet, 0); /* leave the channel id field unfilled - we - * don't know the downstream id yet */ - put_uint32(packet, greeting_len + initial_len); - put_data(packet, greeting, greeting_len); - put_data(packet, initial_data, initial_len); - sfree(greeting); - share_xchannel_add_message(xc, SSH2_MSG_CHANNEL_DATA, - packet->s, packet->len); - strbuf_free(packet); - - xc->window = client_adjusted_window + greeting_len; - - /* - * Send on a CHANNEL_OPEN to downstream. - */ - packet = strbuf_new(); - put_stringz(packet, "x11"); - put_uint32(packet, server_id); - put_uint32(packet, server_currwin); - put_uint32(packet, server_maxpkt); - put_stringz(packet, peer_addr); - put_uint32(packet, peer_port); - send_packet_to_downstream(cs, SSH2_MSG_CHANNEL_OPEN, - packet->s, packet->len, NULL); - strbuf_free(packet); - - /* - * If this was a once-only X forwarding, clean it up now. - */ - if (chan->x11_one_shot) { - ssh_remove_sharing_x11_display(cs->parent->cl, - 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 = false; - } -} - -void share_got_pkt_from_server(ssh_sharing_connstate *cs, int type, - const void *vpkt, int pktlen) -{ - const unsigned char *pkt = (const unsigned char *)vpkt; - struct share_globreq *globreq; - size_t id_pos; - unsigned upstream_id, server_id; - struct share_channel *chan; - struct share_xchannel *xc; - BinarySource src[1]; - - BinarySource_BARE_INIT(src, pkt, pktlen); - - switch (type) { - case SSH2_MSG_REQUEST_SUCCESS: - case SSH2_MSG_REQUEST_FAILURE: - globreq = cs->globreq_head; - assert(globreq); /* should match the queue in connection2.c */ - 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: - get_string(src); - server_id = get_uint32(src); - assert(!get_err(src)); - 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. - */ - id_pos = src->pos; - upstream_id = get_uint32(src); - if ((chan = share_find_channel_by_upstream(cs, upstream_id)) != NULL) { - /* - * The normal case: this id refers to an open channel. - */ - unsigned char *rewritten = snewn(pktlen, unsigned char); - memcpy(rewritten, pkt, pktlen); - PUT_32BIT_MSB_FIRST(rewritten + id_pos, chan->downstream_id); - send_packet_to_downstream(cs, type, rewritten, pktlen, chan); - sfree(rewritten); - - /* - * 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_MSB_FIRST(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->cl, 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->cl, - 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. - */ - share_xchannel_add_message(xc, type, 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: - unreachable("This packet type should never have come from " - "connection2.c"); - } -} - -static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs, - int type, - unsigned char *pkt, int pktlen) -{ - ptrlen request_name; - struct share_forwarding *fwd; - size_t id_pos; - unsigned maxpkt; - unsigned old_id, new_id, server_id; - struct share_globreq *globreq; - struct share_channel *chan; - struct share_halfchannel *hc; - struct share_xchannel *xc; - strbuf *packet; - char *err = NULL; - BinarySource src[1]; - size_t wantreplypos; - bool orig_wantreply; - - BinarySource_BARE_INIT(src, pkt, pktlen); - - 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 = get_string(src); - wantreplypos = src->pos; - orig_wantreply = get_bool(src); - - if (ptrlen_eq_string(request_name, "tcpip-forward")) { - ptrlen hostpl; - char *host; - int port; - struct ssh_rportfwd *rpf; - - /* - * Pick the packet apart to find the want_reply field and - * the host/port we're going to ask to listen on. - */ - hostpl = get_string(src); - port = toint(get_uint32(src)); - if (get_err(src)) { - err = dupprintf("Truncated GLOBAL_REQUEST packet"); - goto confused; - } - host = mkstr(hostpl); - - /* - * See if we can allocate space in the connection layer'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. - */ - rpf = ssh_rportfwd_alloc( - cs->parent->cl, host, port, NULL, 0, 0, NULL, NULL, cs); - if (!rpf) { - 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. - */ - pkt[wantreplypos] = 1; - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, type, pkt, pktlen, - orig_wantreply ? NULL : "upstream added want_reply flag"); - fwd = share_add_forwarding(cs, host, port); - ssh_sharing_queue_global_request(cs->parent->cl, 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; - - fwd->rpf = rpf; - } - } - - sfree(host); - } else if (ptrlen_eq_string(request_name, "cancel-tcpip-forward")) { - ptrlen hostpl; - char *host; - int port; - struct share_forwarding *fwd; - - /* - * Pick the packet apart to find the want_reply field and - * the host/port we're going to ask to listen on. - */ - hostpl = get_string(src); - port = toint(get_uint32(src)); - if (get_err(src)) { - err = dupprintf("Truncated GLOBAL_REQUEST packet"); - goto confused; - } - host = mkstr(hostpl); - - /* - * 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 { - /* - * Tell the connection layer to stop sending us - * channel-opens for this forwarding. - */ - ssh_rportfwd_remove(cs->parent->cl, fwd->rpf); - - /* - * 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. - */ - pkt[wantreplypos] = 1; - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, type, pkt, pktlen, - orig_wantreply ? NULL : "upstream added want_reply flag"); - ssh_sharing_queue_global_request(cs->parent->cl, cs); - - /* - * And queue a globreq so that when the reply comes - * back we know to cancel it. - */ - 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_CANCEL_TCPIP_FORWARD; - } - - sfree(host); - } else { - /* - * Request we don't understand. Manufacture a failure - * message if an answer was required. - */ - if (orig_wantreply) - 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 */ - get_string(src); - id_pos = src->pos; - old_id = get_uint32(src); - new_id = ssh_alloc_sharing_channel(cs->parent->cl, cs); - get_uint32(src); /* skip initial window size */ - maxpkt = get_uint32(src); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_OPEN packet"); - goto confused; - } - share_add_channel(cs, old_id, new_id, 0, UNACKNOWLEDGED, maxpkt); - PUT_32BIT_MSB_FIRST(pkt + id_pos, new_id); - ssh_send_packet_from_downstream(cs->parent->cl, 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; - } - - server_id = get_uint32(src); - id_pos = src->pos; - old_id = get_uint32(src); - get_uint32(src); /* skip initial window size */ - maxpkt = get_uint32(src); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_OPEN_CONFIRMATION packet"); - goto confused; - } - - /* 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->cl, 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_MSB_FIRST(pkt + id_pos, new_id); - - chan = share_add_channel(cs, old_id, new_id, server_id, OPEN, maxpkt); - - if (hc) { - ssh_send_packet_from_downstream(cs->parent->cl, cs->id, - type, pkt, pktlen, NULL); - share_remove_halfchannel(cs, hc); - } else if (xc) { - unsigned downstream_window = GET_32BIT_MSB_FIRST(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: - server_id = get_uint32(src); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_OPEN_FAILURE packet"); - goto confused; - } - - /* 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->cl, 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: - server_id = get_uint32(src); - - if (type == SSH2_MSG_CHANNEL_REQUEST) { - request_name = get_string(src); - - /* - * 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 (ptrlen_eq_string(request_name, "auth-agent-req@openssh.com") && - !ssh_agent_forwarding_permitted(cs->parent->cl)) { - - chan = share_find_channel_by_server(cs, server_id); - if (chan) { - packet = strbuf_new(); - put_uint32(packet, chan->downstream_id); - send_packet_to_downstream( - cs, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, NULL); - strbuf_free(packet); - } 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 (ptrlen_eq_string(request_name, "x11-req")) { - bool want_reply, single_connection; - int screen; - ptrlen auth_data; - int auth_proto; - - 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. - */ - want_reply = get_bool(src); - single_connection = get_bool(src); - auth_proto = x11_identify_auth_proto(get_string(src)); - auth_data = get_string(src); - screen = toint(get_uint32(src)); - if (get_err(src)) { - err = dupprintf("Truncated CHANNEL_REQUEST(\"x11-req\")" - " packet"); - goto confused; - } - - if (auth_proto < 0) { - /* Reject due to not understanding downstream's - * requested authorisation method. */ - packet = strbuf_new(); - put_uint32(packet, chan->downstream_id); - send_packet_to_downstream( - cs, SSH2_MSG_CHANNEL_FAILURE, - packet->s, packet->len, NULL); - strbuf_free(packet); - break; - } - - chan->x11_auth_proto = auth_proto; - chan->x11_auth_data = x11_dehexify(auth_data, - &chan->x11_auth_datalen); - chan->x11_auth_upstream = - ssh_add_sharing_x11_display(cs->parent->cl, 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. - */ - packet = strbuf_new_nm(); - put_uint32(packet, server_id); - put_stringz(packet, "x11-req"); - put_bool(packet, want_reply); - put_bool(packet, single_connection); - put_stringz(packet, chan->x11_auth_upstream->protoname); - put_stringz(packet, chan->x11_auth_upstream->datastring); - put_uint32(packet, screen); - ssh_send_packet_from_downstream( - cs->parent->cl, cs->id, SSH2_MSG_CHANNEL_REQUEST, - packet->s, packet->len, NULL); - strbuf_free(packet); - - break; - } - } - - ssh_send_packet_from_downstream(cs->parent->cl, cs->id, - type, pkt, pktlen, NULL); - if (type == SSH2_MSG_CHANNEL_CLOSE && pktlen >= 4) { - chan = share_find_channel_by_server(cs, server_id); - if (chan) { - if (chan->state == RCVD_CLOSE) { - ssh_delete_sharing_channel(cs->parent->cl, - 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; - } -} - -/* - * An extra coroutine macro, specific to this code which is consuming - * 'const char *data'. - */ -#define crGetChar(c) do \ - { \ - while (len == 0) { \ - *crLine = __LINE__; return; case __LINE__:; \ - } \ - len--; \ - (c) = (unsigned char)*data++; \ - } while (0) - -static void share_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - ssh_sharing_connstate *cs = container_of( - plug, 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); - return; - } - 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); - return; - } - if (cs->recvlen > 0 && cs->recvbuf[cs->recvlen-1] == '\015') - cs->recvlen--; /* trim off \r before \n */ - ptrlen verstring = make_ptrlen(cs->recvbuf, cs->recvlen); - log_downstream(cs, "Downstream version string: %.*s", - PTRLEN_PRINTF(verstring)); - cs->got_verstring = true; - - /* - * 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_MSB_FIRST(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); - return; - } - 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); - } - - crFinishV; -} - -static void share_sent(Plug *plug, size_t bufsize) -{ - /* ssh_sharing_connstate *cs = container_of( - plug, 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 void share_listen_closing(Plug *plug, PlugCloseType type, - const char *error_msg) -{ - ssh_sharing_state *sharestate = - container_of(plug, ssh_sharing_state, plug); - if (type != PLUGCLOSE_NORMAL) - log_general(sharestate, "listening socket: %s", error_msg); - sk_close(sharestate->listensock); - sharestate->listensock = NULL; -} - -static void share_send_verstring(ssh_sharing_connstate *cs) -{ - char *fullstring = dupcat("SSHCONNECTION@putty.projects.tartarus.org-2.0-", - cs->parent->server_verstring, "\015\012"); - sk_write(cs->sock, fullstring, strlen(fullstring)); - sfree(fullstring); - - cs->sent_verstring = true; -} - -int share_ndownstreams(ssh_sharing_state *sharestate) -{ - return count234(sharestate->connections); -} - -void share_activate(ssh_sharing_state *sharestate, - const char *server_verstring) -{ - /* - * Indication from connection layer that we are now ready to begin - * serving any downstreams that have already connected to us. - */ - 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 const PlugVtable ssh_sharing_conn_plugvt = { - .closing = share_closing, - .receive = share_receive, - .sent = share_sent, - .log = nullplug_log, -}; - -static int share_listen_accepting(Plug *plug, - accept_fn_t constructor, accept_ctx_t ctx) -{ - struct ssh_sharing_state *sharestate = container_of( - plug, struct ssh_sharing_state, plug); - struct ssh_sharing_connstate *cs; - const char *err; - SocketPeerInfo *peerinfo; - - /* - * A new downstream has connected to us. - */ - cs = snew(struct ssh_sharing_connstate); - cs->plug.vt = &ssh_sharing_conn_plugvt; - 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, &cs->plug); - if ((err = sk_socket_error(cs->sock)) != NULL) { - sfree(cs); - return err != NULL; - } - - sk_set_frozen(cs->sock, false); - - 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); - log_downstream(cs, "connected%s%s", - (peerinfo && peerinfo->log_text ? " from " : ""), - (peerinfo && peerinfo->log_text ? peerinfo->log_text : "")); - sk_free_peer_info(peerinfo); - - return 0; -} - -/* - * 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. - * - * The per-platform code will quite likely hash or obfuscate this name - * in turn, for privacy from other users; failing that, it might - * transform it to avoid dangerous filename characters and so on. But - * that doesn't matter to us: for us, the point is that two session - * configurations which return the same string from this function will - * be treated as potentially shareable with each other. - */ -static char *ssh_share_sockname(const char *host, int port, Conf *conf) -{ - char *username = NULL; - char *sockname; - - /* Include the username we're logging in as in the hash, unless - * we're using a protocol for which it's completely irrelevant. */ - if (conf_get_int(conf, CONF_protocol) != PROT_SSHCONN) - 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); - return sockname; -} - -bool ssh_share_test_for_upstream(const char *host, int port, Conf *conf) -{ - char *sockname, *logtext, *ds_err, *us_err; - int result; - Socket *sock; - - sockname = ssh_share_sockname(host, port, conf); - - sock = NULL; - logtext = ds_err = us_err = NULL; - result = platform_ssh_share(sockname, conf, nullplug, (Plug *)NULL, &sock, - &logtext, &ds_err, &us_err, false, true); - - sfree(logtext); - sfree(ds_err); - sfree(us_err); - sfree(sockname); - - if (result == SHARE_NONE) { - assert(sock == NULL); - return false; - } else { - assert(result == SHARE_DOWNSTREAM); - sk_close(sock); - return true; - } -} - -static const PlugVtable ssh_sharing_listen_plugvt = { - .closing = share_listen_closing, - .accepting = share_listen_accepting, - .log = nullplug_log, -}; - -void ssh_connshare_provide_connlayer(ssh_sharing_state *sharestate, - ConnectionLayer *cl) -{ - sharestate->cl = cl; -} - -/* - * 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, LogContext *logctx, - Plug *sshplug, ssh_sharing_state **state) -{ - int result; - bool can_upstream, can_downstream; - char *logtext, *ds_err, *us_err; - char *sockname; - Socket *sock, *toret = NULL; - struct ssh_sharing_state *sharestate; - - if (!conf_get_bool(conf, CONF_ssh_connection_sharing)) - return NULL; /* do not share anything */ - can_upstream = share_can_be_upstream && - conf_get_bool(conf, CONF_ssh_connection_sharing_upstream); - can_downstream = share_can_be_downstream && - conf_get_bool(conf, CONF_ssh_connection_sharing_downstream); - if (!can_upstream && !can_downstream) - return NULL; - - sockname = ssh_share_sockname(host, port, conf); - - /* - * Create a data structure for the listening plug if we turn out - * to be an upstream. - */ - sharestate = snew(struct ssh_sharing_state); - sharestate->plug.vt = &ssh_sharing_listen_plugvt; - sharestate->listensock = NULL; - sharestate->cl = 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, sshplug, &sharestate->plug, &sock, &logtext, - &ds_err, &us_err, can_upstream, can_downstream); - 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. - */ - - if (logtext) { - /* For this result, if 'logtext' is not NULL then it is an - * error message indicating a reason why connection sharing - * couldn't be set up _at all_ */ - logeventf(logctx, - "Could not set up connection sharing: %s", logtext); - } else { - /* Failing that, ds_err and us_err indicate why we - * couldn't be a downstream and an upstream respectively */ - if (ds_err) - logeventf(logctx, "Could not set up connection sharing" - " as downstream: %s", ds_err); - if (us_err) - logeventf(logctx, "Could not set up connection sharing" - " as upstream: %s", us_err); - } - - assert(sock == NULL); - *state = NULL; - sfree(sharestate); - sfree(sockname); - break; - - 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. - */ - - /* 'logtext' is a local endpoint address */ - logeventf(logctx, "Using existing shared connection at %s", logtext); - - *state = NULL; - sfree(sharestate); - sfree(sockname); - toret = sock; - break; - - 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. - */ - - /* 'logtext' is a local endpoint address */ - logeventf(logctx, "Sharing this connection at %s", logtext); - - *state = sharestate; - sharestate->listensock = sock; - sharestate->connections = newtree234(share_connstate_cmp); - sharestate->server_verstring = NULL; - sharestate->sockname = sockname; - sharestate->nextid = 1; - break; - } - - sfree(logtext); - sfree(ds_err); - sfree(us_err); - return toret; -} diff --git a/ssh/signal-list.h b/ssh/signal-list.h deleted file mode 100644 index b213c34fc..000000000 --- a/ssh/signal-list.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * List of signal names known to SSH, indicating whether PuTTY's UI - * for special session commands likes to put them in the main specials - * menu or in a submenu (and if the former, what title they have). - * - * This is a separate header file rather than my usual style of a - * parametric list macro, because in this case I need to be able to - * #ifdef out each mode in case it's not defined on a particular - * target system. - * - * If you want only the locally defined signals, #define - * SIGNALS_LOCAL_ONLY before including this header. - */ - -#if !defined SIGNALS_LOCAL_ONLY || defined SIGINT -SIGNAL_MAIN(INT, "Interrupt") -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGTERM -SIGNAL_MAIN(TERM, "Terminate") -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGKILL -SIGNAL_MAIN(KILL, "Kill") -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGQUIT -SIGNAL_MAIN(QUIT, "Quit") -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGHUP -SIGNAL_MAIN(HUP, "Hangup") -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGABRT -SIGNAL_SUB(ABRT) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGALRM -SIGNAL_SUB(ALRM) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGFPE -SIGNAL_SUB(FPE) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGILL -SIGNAL_SUB(ILL) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGPIPE -SIGNAL_SUB(PIPE) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGSEGV -SIGNAL_SUB(SEGV) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGUSR1 -SIGNAL_SUB(USR1) -#endif -#if !defined SIGNALS_LOCAL_ONLY || defined SIGUSR2 -SIGNAL_SUB(USR2) -#endif diff --git a/ssh/ssh.c b/ssh/ssh.c deleted file mode 100644 index 836661d66..000000000 --- a/ssh/ssh.c +++ /dev/null @@ -1,1330 +0,0 @@ -/* - * SSH backend. - */ - -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "pageant.h" /* for AGENT_MAX_MSGLEN */ -#include "tree234.h" -#include "storage.h" -#include "marshal.h" -#include "ssh.h" -#include "sshcr.h" -#include "bpp.h" -#include "ppl.h" -#include "channel.h" -#ifndef NO_GSSAPI -#include "gssc.h" -#include "gss.h" -#define MIN_CTXT_LIFETIME 5 /* Avoid rekey with short lifetime (seconds) */ -#define GSS_KEX_CAPABLE (1<<0) /* Can do GSS KEX */ -#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */ -#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */ -#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */ -#endif - -struct Ssh { - Socket *s; - Seat *seat; - Conf *conf; - - struct ssh_version_receiver version_receiver; - int remote_bugs; - - Plug plug; - Backend backend; - Interactor interactor; - - Ldisc *ldisc; - LogContext *logctx; - - /* The last list returned from get_specials. */ - SessionSpecial *specials; - - bool bare_connection; - ssh_sharing_state *connshare; - bool attempting_connshare; - -#ifndef NO_GSSAPI - struct ssh_connection_shared_gss_state gss_state; -#endif - - char *savedhost; - int savedport; - char *fullhostname; - char *description; - - bool fallback_cmd; - int exitcode; - - int version; - int conn_throttle_count; - size_t overall_bufsize; - bool throttled_all; - - /* - * logically_frozen is true if we're not currently _processing_ - * data from the SSH socket (e.g. because a higher layer has asked - * us not to due to ssh_throttle_conn). socket_frozen is true if - * we're not even _reading_ data from the socket (i.e. it should - * always match the value we last passed to sk_set_frozen). - * - * The two differ in that socket_frozen can also become - * temporarily true because of a large backlog in the in_raw - * bufchain, to force no further plug_receive events until the BPP - * input function has had a chance to run. (Some front ends, like - * GTK, can persistently call the network and never get round to - * the toplevel callbacks.) If we've stopped reading from the - * socket for that reason, we absolutely _do_ want to carry on - * processing our input bufchain, because that's the only way - * it'll ever get cleared! - * - * ssh_check_frozen() resets socket_frozen, and should be called - * whenever either of logically_frozen and the bufchain size - * changes. - */ - bool logically_frozen, socket_frozen; - - /* in case we find these out before we have a ConnectionLayer to tell */ - int term_width, term_height; - - bufchain in_raw, out_raw, user_input; - bool pending_close; - IdempotentCallback ic_out_raw; - - PacketLogSettings pls; - struct DataTransferStats stats; - - BinaryPacketProtocol *bpp; - - /* - * base_layer identifies the bottommost packet protocol layer, the - * one connected directly to the BPP's packet queues. Any - * operation that needs to talk to all layers (e.g. free, or - * get_specials) will do it by talking to this, which will - * recursively propagate it if necessary. - */ - PacketProtocolLayer *base_layer; - - /* - * The ConnectionLayer vtable from our connection layer. - */ - ConnectionLayer *cl; - - /* - * A dummy ConnectionLayer that can be used for logging sharing - * downstreams that connect before the real one is ready. - */ - ConnectionLayer cl_dummy; - - /* - * session_started is false until we initialise the main protocol - * layers. So it distinguishes between base_layer==NULL meaning - * that the SSH protocol hasn't been set up _yet_, and - * base_layer==NULL meaning the SSH protocol has run and finished. - * It's also used to mark the point where we stop counting proxy - * command diagnostics as pre-session-startup. - */ - bool session_started; - - Pinger *pinger; - - char *deferred_abort_message; - - bool need_random_unref; -}; - - -#define ssh_logevent(params) ( \ - logevent_and_free((ssh)->logctx, dupprintf params)) - -static void ssh_shutdown(Ssh *ssh); -static void ssh_throttle_all(Ssh *ssh, bool enable, size_t bufsize); -static void ssh_bpp_output_raw_data_callback(void *vctx); - -LogContext *ssh_get_logctx(Ssh *ssh) -{ - return ssh->logctx; -} - -static void ssh_connect_bpp(Ssh *ssh) -{ - ssh->bpp->ssh = ssh; - ssh->bpp->in_raw = &ssh->in_raw; - ssh->bpp->out_raw = &ssh->out_raw; - bufchain_set_callback(ssh->bpp->out_raw, &ssh->ic_out_raw); - ssh->bpp->pls = &ssh->pls; - ssh->bpp->logctx = ssh->logctx; - ssh->bpp->remote_bugs = ssh->remote_bugs; -} - -static void ssh_connect_ppl(Ssh *ssh, PacketProtocolLayer *ppl) -{ - ppl->bpp = ssh->bpp; - ppl->seat = ssh->seat; - ppl->interactor = &ssh->interactor; - ppl->ssh = ssh; - ppl->logctx = ssh->logctx; - ppl->remote_bugs = ssh->remote_bugs; -} - -static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, - int major_version) -{ - Ssh *ssh = container_of(rcv, Ssh, version_receiver); - BinaryPacketProtocol *old_bpp; - PacketProtocolLayer *connection_layer; - - ssh->session_started = true; - - /* - * We don't support choosing a major protocol version dynamically, - * so this should always be the same value we set up in - * connect_to_host(). - */ - assert(ssh->version == major_version); - - old_bpp = ssh->bpp; - ssh->remote_bugs = ssh_verstring_get_bugs(old_bpp); - - if (!ssh->bare_connection) { - if (ssh->version == 2) { - PacketProtocolLayer *userauth_layer, *transport_child_layer; - - /* - * 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. - */ - bool is_simple = - (conf_get_bool(ssh->conf, CONF_ssh_simple) && !ssh->connshare); - - ssh->bpp = ssh2_bpp_new(ssh->logctx, &ssh->stats, false); - ssh_connect_bpp(ssh); - -#ifndef NO_GSSAPI - /* Load and pick the highest GSS library on the preference - * list. */ - if (!ssh->gss_state.libs) - ssh->gss_state.libs = ssh_gss_setup(ssh->conf); - ssh->gss_state.lib = NULL; - if (ssh->gss_state.libs->nlibraries > 0) { - int i, j; - for (i = 0; i < ngsslibs; i++) { - int want_id = conf_get_int_int(ssh->conf, - CONF_ssh_gsslist, i); - for (j = 0; j < ssh->gss_state.libs->nlibraries; j++) - if (ssh->gss_state.libs->libraries[j].id == want_id) { - ssh->gss_state.lib = - &ssh->gss_state.libs->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(ssh->gss_state.lib); - } -#endif - - connection_layer = ssh2_connection_new( - ssh, ssh->connshare, is_simple, ssh->conf, - ssh_verstring_get_remote(old_bpp), &ssh->user_input, &ssh->cl); - ssh_connect_ppl(ssh, connection_layer); - - if (conf_get_bool(ssh->conf, CONF_ssh_no_userauth)) { - userauth_layer = NULL; - transport_child_layer = connection_layer; - } else { - char *username = get_remote_username(ssh->conf); - - userauth_layer = ssh2_userauth_new( - connection_layer, ssh->savedhost, ssh->savedport, - ssh->fullhostname, - conf_get_filename(ssh->conf, CONF_keyfile), - conf_get_filename(ssh->conf, CONF_detached_cert), - conf_get_bool(ssh->conf, CONF_ssh_show_banner), - conf_get_bool(ssh->conf, CONF_tryagent), - conf_get_bool(ssh->conf, CONF_ssh_no_trivial_userauth), - username, - conf_get_bool(ssh->conf, CONF_change_username), - conf_get_bool(ssh->conf, CONF_try_ki_auth), -#ifndef NO_GSSAPI - conf_get_bool(ssh->conf, CONF_try_gssapi_auth), - conf_get_bool(ssh->conf, CONF_try_gssapi_kex), - conf_get_bool(ssh->conf, CONF_gssapifwd), - &ssh->gss_state, -#else - false, - false, - false, - NULL, -#endif - conf_get_str(ssh->conf, CONF_auth_plugin)); - ssh_connect_ppl(ssh, userauth_layer); - transport_child_layer = userauth_layer; - - sfree(username); - } - - ssh->base_layer = ssh2_transport_new( - ssh->conf, ssh->savedhost, ssh->savedport, - ssh->fullhostname, - ssh_verstring_get_local(old_bpp), - ssh_verstring_get_remote(old_bpp), -#ifndef NO_GSSAPI - &ssh->gss_state, -#else - NULL, -#endif - &ssh->stats, transport_child_layer, NULL); - ssh_connect_ppl(ssh, ssh->base_layer); - - if (userauth_layer) - ssh2_userauth_set_transport_layer(userauth_layer, - ssh->base_layer); - - } else { - - ssh->bpp = ssh1_bpp_new(ssh->logctx); - ssh_connect_bpp(ssh); - - connection_layer = ssh1_connection_new( - ssh, ssh->conf, &ssh->user_input, &ssh->cl); - ssh_connect_ppl(ssh, connection_layer); - - ssh->base_layer = ssh1_login_new( - ssh->conf, ssh->savedhost, ssh->savedport, connection_layer); - ssh_connect_ppl(ssh, ssh->base_layer); - - } - - } else { - ssh->bpp = ssh2_bare_bpp_new(ssh->logctx); - ssh_connect_bpp(ssh); - - connection_layer = ssh2_connection_new( - ssh, ssh->connshare, false, ssh->conf, - ssh_verstring_get_remote(old_bpp), &ssh->user_input, &ssh->cl); - ssh_connect_ppl(ssh, connection_layer); - ssh->base_layer = connection_layer; - } - - /* Connect the base layer - whichever it is - to the BPP, and set - * up its selfptr. */ - ssh->base_layer->selfptr = &ssh->base_layer; - ssh_ppl_setup_queues(ssh->base_layer, &ssh->bpp->in_pq, &ssh->bpp->out_pq); - - seat_update_specials_menu(ssh->seat); - ssh->pinger = pinger_new(ssh->conf, &ssh->backend); - - queue_idempotent_callback(&ssh->bpp->ic_in_raw); - ssh_ppl_process_queue(ssh->base_layer); - - /* Pass in the initial terminal size, if we knew it already. */ - ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height); - - ssh_bpp_free(old_bpp); -} - -void ssh_check_frozen(Ssh *ssh) -{ - if (!ssh->s) - return; - - bool prev_frozen = ssh->socket_frozen; - ssh->socket_frozen = (ssh->logically_frozen || - bufchain_size(&ssh->in_raw) > SSH_MAX_BACKLOG); - sk_set_frozen(ssh->s, ssh->socket_frozen); - if (prev_frozen && !ssh->socket_frozen && ssh->bpp) { - /* - * If we've just unfrozen, process any SSH connection data - * that was stashed in our queue while we were frozen. - */ - queue_idempotent_callback(&ssh->bpp->ic_in_raw); - } -} - -void ssh_conn_processed_data(Ssh *ssh) -{ - ssh_check_frozen(ssh); -} - -static void ssh_bpp_output_raw_data_callback(void *vctx) -{ - Ssh *ssh = (Ssh *)vctx; - - if (!ssh->s) - return; - - while (bufchain_size(&ssh->out_raw) > 0) { - size_t backlog; - - ptrlen data = bufchain_prefix(&ssh->out_raw); - - if (ssh->logctx) - log_packet(ssh->logctx, PKT_OUTGOING, -1, NULL, data.ptr, data.len, - 0, NULL, NULL, 0, NULL); - backlog = sk_write(ssh->s, data.ptr, data.len); - - bufchain_consume(&ssh->out_raw, data.len); - - if (backlog > SSH_MAX_BACKLOG) { - ssh_throttle_all(ssh, true, backlog); - return; - } - } - - ssh_check_frozen(ssh); - - if (ssh->pending_close) { - sk_close(ssh->s); - ssh->s = NULL; - seat_notify_remote_disconnect(ssh->seat); - } -} - -static void ssh_shutdown_internal(Ssh *ssh) -{ - expire_timer_context(ssh); - - if (ssh->connshare) { - sharestate_free(ssh->connshare); - ssh->connshare = NULL; - } - - if (ssh->pinger) { - pinger_free(ssh->pinger); - ssh->pinger = NULL; - } - - /* - * We only need to free the base PPL, which will free the others - * (if any) transitively. - */ - if (ssh->base_layer) { - ssh_ppl_free(ssh->base_layer); - ssh->base_layer = NULL; - } - - ssh->cl = NULL; -} - -static void ssh_shutdown(Ssh *ssh) -{ - ssh_shutdown_internal(ssh); - - if (ssh->bpp) { - ssh_bpp_free(ssh->bpp); - ssh->bpp = NULL; - } - - if (ssh->s) { - sk_close(ssh->s); - ssh->s = NULL; - seat_notify_remote_disconnect(ssh->seat); - } - - bufchain_clear(&ssh->in_raw); - bufchain_clear(&ssh->out_raw); - bufchain_clear(&ssh->user_input); -} - -static void ssh_initiate_connection_close(Ssh *ssh) -{ - /* Wind up everything above the BPP. */ - ssh_shutdown_internal(ssh); - - /* Force any remaining queued SSH packets through the BPP, and - * schedule closing the network socket after they go out. */ - ssh_bpp_handle_output(ssh->bpp); - ssh->pending_close = true; - queue_idempotent_callback(&ssh->ic_out_raw); - - /* Now we expect the other end to close the connection too in - * response, so arrange that we'll receive notification of that - * via ssh_remote_eof. */ - ssh->bpp->expect_close = true; -} - -#define GET_FORMATTED_MSG \ - char *msg; \ - va_list ap; \ - va_start(ap, fmt); \ - msg = dupvprintf(fmt, ap); \ - va_end(ap); \ - ((void)0) /* eat trailing semicolon */ - -void ssh_remote_error(Ssh *ssh, const char *fmt, ...) -{ - if (ssh->base_layer || !ssh->session_started) { - GET_FORMATTED_MSG; - - if (ssh->base_layer) - ssh_ppl_final_output(ssh->base_layer); - - /* Error messages sent by the remote don't count as clean exits */ - ssh->exitcode = 128; - - /* Close the socket immediately, since the server has already - * closed its end (or is about to). */ - ssh_shutdown(ssh); - - logevent(ssh->logctx, msg); - seat_connection_fatal(ssh->seat, "%s", msg); - sfree(msg); - } -} - -void ssh_remote_eof(Ssh *ssh, const char *fmt, ...) -{ - if (ssh->base_layer || !ssh->session_started) { - GET_FORMATTED_MSG; - - if (ssh->base_layer) - ssh_ppl_final_output(ssh->base_layer); - - /* EOF from the remote, if we were expecting it, does count as - * a clean exit */ - ssh->exitcode = 0; - - /* Close the socket immediately, since the server has already - * closed its end. */ - ssh_shutdown(ssh); - - logevent(ssh->logctx, msg); - sfree(msg); - seat_notify_remote_exit(ssh->seat); - } else { - /* This is responding to EOF after we've already seen some - * other reason for terminating the session. */ - ssh_shutdown(ssh); - } -} - -void ssh_proto_error(Ssh *ssh, const char *fmt, ...) -{ - if (ssh->base_layer || !ssh->session_started) { - GET_FORMATTED_MSG; - - if (ssh->base_layer) - ssh_ppl_final_output(ssh->base_layer); - - ssh->exitcode = 128; - - ssh_bpp_queue_disconnect(ssh->bpp, msg, - SSH2_DISCONNECT_PROTOCOL_ERROR); - ssh_initiate_connection_close(ssh); - - logevent(ssh->logctx, msg); - seat_connection_fatal(ssh->seat, "%s", msg); - sfree(msg); - } -} - -void ssh_sw_abort(Ssh *ssh, const char *fmt, ...) -{ - if (ssh->base_layer || !ssh->session_started) { - GET_FORMATTED_MSG; - - if (ssh->base_layer) - ssh_ppl_final_output(ssh->base_layer); - - ssh->exitcode = 128; - - ssh_initiate_connection_close(ssh); - - logevent(ssh->logctx, msg); - seat_connection_fatal(ssh->seat, "%s", msg); - sfree(msg); - - seat_notify_remote_exit(ssh->seat); - } -} - -void ssh_user_close(Ssh *ssh, const char *fmt, ...) -{ - if (ssh->base_layer || !ssh->session_started) { - GET_FORMATTED_MSG; - - if (ssh->base_layer) - ssh_ppl_final_output(ssh->base_layer); - - /* Closing the connection due to user action, even if the - * action is the user aborting during authentication prompts, - * does count as a clean exit - except that this is also how - * we signal ordinary session termination, in which case we - * should use the exit status already sent from the main - * session (if any). */ - if (ssh->exitcode < 0) - ssh->exitcode = 0; - - ssh_initiate_connection_close(ssh); - - logevent(ssh->logctx, msg); - sfree(msg); - - seat_notify_remote_exit(ssh->seat); - } -} - -static void ssh_deferred_abort_callback(void *vctx) -{ - Ssh *ssh = (Ssh *)vctx; - char *msg = ssh->deferred_abort_message; - ssh->deferred_abort_message = NULL; - ssh_sw_abort(ssh, "%s", msg); - sfree(msg); -} - -void ssh_sw_abort_deferred(Ssh *ssh, const char *fmt, ...) -{ - if (!ssh->deferred_abort_message) { - GET_FORMATTED_MSG; - ssh->deferred_abort_message = msg; - queue_toplevel_callback(ssh_deferred_abort_callback, ssh); - } -} - -static void ssh_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *error_msg, int error_code) -{ - Ssh *ssh = container_of(plug, Ssh, plug); - - /* - * 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. - */ - - if (!ssh->attempting_connshare) - backend_socket_log(ssh->seat, ssh->logctx, type, addr, port, - error_msg, error_code, ssh->conf, - ssh->session_started); -} - -static void ssh_closing(Plug *plug, PlugCloseType type, const char *error_msg) -{ - Ssh *ssh = container_of(plug, Ssh, plug); - if (type == PLUGCLOSE_USER_ABORT) { - ssh_user_close(ssh, "%s", error_msg); - } else if (type != PLUGCLOSE_NORMAL) { - ssh_remote_error(ssh, "%s", error_msg); - } else if (ssh->bpp) { - ssh->bpp->input_eof = true; - queue_idempotent_callback(&ssh->bpp->ic_in_raw); - } -} - -static void ssh_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - Ssh *ssh = container_of(plug, Ssh, plug); - - /* Log raw data, if we're in that mode. */ - if (ssh->logctx) - log_packet(ssh->logctx, PKT_INCOMING, -1, NULL, data, len, - 0, NULL, NULL, 0, NULL); - - bufchain_add(&ssh->in_raw, data, len); - if (!ssh->logically_frozen && ssh->bpp) - queue_idempotent_callback(&ssh->bpp->ic_in_raw); - - ssh_check_frozen(ssh); -} - -static void ssh_sent(Plug *plug, size_t bufsize) -{ - Ssh *ssh = container_of(plug, Ssh, plug); - /* - * If the send backlog on the SSH socket itself clears, we should - * unthrottle the whole world if it was throttled. Also trigger an - * extra call to the consumer of the BPP's output, to try to send - * some more data off its bufchain. - */ - if (bufsize < SSH_MAX_BACKLOG) { - ssh_throttle_all(ssh, false, bufsize); - queue_idempotent_callback(&ssh->ic_out_raw); - ssh_sendbuffer_changed(ssh); - } -} - -static void ssh_hostport_setup(const char *host, int port, Conf *conf, - char **savedhost, int *savedport, - char **loghost_ret) -{ - char *loghost = conf_get_str(conf, CONF_loghost); - if (loghost_ret) - *loghost_ret = loghost; - - if (*loghost) { - char *tmphost; - char *colon; - - tmphost = dupstr(loghost); - *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) - *savedport = atoi(colon); - } - - *savedhost = host_strduptrim(tmphost); - sfree(tmphost); - } else { - *savedhost = host_strduptrim(host); - if (port < 0) - port = 22; /* default ssh port */ - *savedport = port; - } -} - -static bool ssh_test_for_upstream(const char *host, int port, Conf *conf) -{ - char *savedhost; - int savedport; - bool ret; - - random_ref(); /* platform may need this to determine share socket name */ - ssh_hostport_setup(host, port, conf, &savedhost, &savedport, NULL); - ret = ssh_share_test_for_upstream(savedhost, savedport, conf); - sfree(savedhost); - random_unref(); - - return ret; -} - -static char *ssh_close_warn_text(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - if (!ssh->connshare) - return NULL; - int ndowns = share_ndownstreams(ssh->connshare); - if (ndowns == 0) - return NULL; - char *msg = dupprintf("This will also close %d downstream connection%s.", - ndowns, ndowns==1 ? "" : "s"); - return msg; -} - -static const PlugVtable Ssh_plugvt = { - .log = ssh_socket_log, - .closing = ssh_closing, - .receive = ssh_receive, - .sent = ssh_sent, -}; - -static char *ssh_description(Interactor *itr) -{ - Ssh *ssh = container_of(itr, Ssh, interactor); - return dupstr(ssh->description); -} - -static LogPolicy *ssh_logpolicy(Interactor *itr) -{ - Ssh *ssh = container_of(itr, Ssh, interactor); - return log_get_policy(ssh->logctx); -} - -static Seat *ssh_get_seat(Interactor *itr) -{ - Ssh *ssh = container_of(itr, Ssh, interactor); - return ssh->seat; -} - -static void ssh_set_seat(Interactor *itr, Seat *seat) -{ - Ssh *ssh = container_of(itr, Ssh, interactor); - ssh->seat = seat; -} - -static const InteractorVtable Ssh_interactorvt = { - .description = ssh_description, - .logpolicy = ssh_logpolicy, - .get_seat = ssh_get_seat, - .set_seat = ssh_set_seat, -}; - -/* - * 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 char *connect_to_host( - Ssh *ssh, const char *host, int port, char *loghost, char **realhost, - bool nodelay, bool keepalive) -{ - SockAddr *addr; - const char *err; - int addressfamily, sshprot; - - ssh->plug.vt = &Ssh_plugvt; - - /* - * 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->logctx, - &ssh->plug, &ssh->connshare); - if (ssh->connshare) - ssh_connshare_provide_connlayer(ssh->connshare, &ssh->cl_dummy); - ssh->attempting_connshare = false; - if (ssh->s != NULL) { - /* - * We are a downstream. - */ - ssh->bare_connection = true; - ssh->fullhostname = NULL; - *realhost = dupstr(host); /* best we can do */ - - if (seat_verbose(ssh->seat) || seat_interactive(ssh->seat)) { - /* In an interactive session, or in verbose mode, announce - * in the console window that we're a sharing downstream, - * to avoid confusing users as to why this session doesn't - * behave in quite the usual way. */ - const char *msg = - "Reusing a shared connection to this server.\r\n"; - seat_stderr_pl(ssh->seat, ptrlen_from_asciz(msg)); - } - } else { - /* - * We're not a downstream, so open a normal socket. - */ - - /* - * Try to find host. - */ - addressfamily = conf_get_int(ssh->conf, CONF_addressfamily); - addr = name_lookup(host, port, realhost, ssh->conf, addressfamily, - ssh->logctx, "SSH connection"); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return dupstr(err); - } - ssh->fullhostname = dupstr(*realhost); /* save in case of GSSAPI */ - - ssh->s = new_connection(addr, *realhost, port, - false, true, nodelay, keepalive, - &ssh->plug, ssh->conf, &ssh->interactor); - if ((err = sk_socket_error(ssh->s)) != NULL) { - ssh->s = NULL; - seat_notify_remote_exit(ssh->seat); - seat_notify_remote_disconnect(ssh->seat); - return dupstr(err); - } - } - - /* - * The SSH version number is always fixed (since we no longer support - * fallback between versions), so set it now. - */ - sshprot = conf_get_int(ssh->conf, CONF_sshprot); - assert(sshprot == 0 || sshprot == 3); - if (sshprot == 0) - /* SSH-1 only */ - ssh->version = 1; - if (sshprot == 3 || ssh->bare_connection) { - /* SSH-2 only */ - ssh->version = 2; - } - - /* - * Set up the initial BPP that will do the version string - * exchange, and get it started so that it can send the outgoing - * version string early if it wants to. - */ - ssh->version_receiver.got_ssh_version = ssh_got_ssh_version; - ssh->bpp = ssh_verstring_new( - ssh->conf, ssh->logctx, ssh->bare_connection, - ssh->version == 1 ? "1.5" : "2.0", &ssh->version_receiver, - false, "PuTTY"); - ssh_connect_bpp(ssh); - queue_idempotent_callback(&ssh->bpp->ic_in_raw); - - /* - * loghost, if configured, overrides realhost. - */ - if (*loghost) { - sfree(*realhost); - *realhost = dupstr(loghost); - } - - return NULL; -} - -/* - * Throttle or unthrottle the SSH connection. - */ -void ssh_throttle_conn(Ssh *ssh, int adjust) -{ - int old_count = ssh->conn_throttle_count; - bool frozen; - - ssh->conn_throttle_count += adjust; - assert(ssh->conn_throttle_count >= 0); - - if (ssh->conn_throttle_count && !old_count) { - frozen = true; - } else if (!ssh->conn_throttle_count && old_count) { - frozen = false; - } else { - return; /* don't change current frozen state */ - } - - ssh->logically_frozen = frozen; - ssh_check_frozen(ssh); -} - -/* - * Throttle or unthrottle _all_ local data streams (for when sends - * on the SSH connection itself back up). - */ -static void ssh_throttle_all(Ssh *ssh, bool enable, size_t bufsize) -{ - if (enable == ssh->throttled_all) - return; - ssh->throttled_all = enable; - ssh->overall_bufsize = bufsize; - - ssh_throttle_all_channels(ssh->cl, enable); -} - -static void ssh_cache_conf_values(Ssh *ssh) -{ - ssh->pls.omit_passwords = conf_get_bool(ssh->conf, CONF_logomitpass); - ssh->pls.omit_data = conf_get_bool(ssh->conf, CONF_logomitdata); -} - -bool ssh_is_bare(Ssh *ssh) -{ - return ssh->backend.vt->protocol == PROT_SSHCONN; -} - -/* Dummy connlayer must provide ssh_sharing_no_more_downstreams, - * because it might be called early due to plink -shareexists */ -static void dummy_sharing_no_more_downstreams(ConnectionLayer *cl) {} -static const ConnectionLayerVtable dummy_connlayer_vtable = { - .sharing_no_more_downstreams = dummy_sharing_no_more_downstreams, -}; - -/* - * Called to set up the connection. - * - * Returns an error message, or NULL on success. - */ -static char *ssh_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - Ssh *ssh; - - ssh = snew(Ssh); - memset(ssh, 0, sizeof(Ssh)); - - ssh->conf = conf_copy(conf); - ssh_cache_conf_values(ssh); - ssh->exitcode = -1; - ssh->pls.kctx = SSH2_PKTCTX_NOKEX; - ssh->pls.actx = SSH2_PKTCTX_NOAUTH; - bufchain_init(&ssh->in_raw); - bufchain_init(&ssh->out_raw); - bufchain_init(&ssh->user_input); - ssh->ic_out_raw.fn = ssh_bpp_output_raw_data_callback; - ssh->ic_out_raw.ctx = ssh; - - ssh->term_width = conf_get_int(ssh->conf, CONF_width); - ssh->term_height = conf_get_int(ssh->conf, CONF_height); - - ssh->backend.vt = vt; - ssh->interactor.vt = &Ssh_interactorvt; - ssh->backend.interactor = &ssh->interactor; - *backend_handle = &ssh->backend; - - ssh->bare_connection = (vt->protocol == PROT_SSHCONN); - - ssh->seat = seat; - ssh->cl_dummy.vt = &dummy_connlayer_vtable; - ssh->cl_dummy.logctx = ssh->logctx = logctx; - - char *loghost; - - ssh_hostport_setup(host, port, ssh->conf, - &ssh->savedhost, &ssh->savedport, &loghost); - ssh->description = default_description(vt, ssh->savedhost, ssh->savedport); - - random_ref(); /* do this now - may be needed by sharing setup code */ - ssh->need_random_unref = true; - - char *conn_err = connect_to_host( - ssh, host, port, loghost, realhost, nodelay, keepalive); - if (conn_err) { - /* Call random_unref now instead of waiting until the caller - * frees this useless Ssh object, in case the caller is - * impatient and just exits without bothering, in which case - * the random seed won't be re-saved. */ - ssh->need_random_unref = false; - random_unref(); - return conn_err; - } - - return NULL; -} - -static void ssh_free(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - bool need_random_unref; - - ssh_shutdown(ssh); - - if (is_tempseat(ssh->seat)) - tempseat_free(ssh->seat); - - conf_free(ssh->conf); - if (ssh->connshare) - sharestate_free(ssh->connshare); - sfree(ssh->savedhost); - sfree(ssh->fullhostname); - sfree(ssh->specials); - -#ifndef NO_GSSAPI - if (ssh->gss_state.srv_name) - ssh->gss_state.lib->release_name( - ssh->gss_state.lib, &ssh->gss_state.srv_name); - if (ssh->gss_state.ctx != NULL) - ssh->gss_state.lib->release_cred( - ssh->gss_state.lib, &ssh->gss_state.ctx); - if (ssh->gss_state.libs) - ssh_gss_cleanup(ssh->gss_state.libs); -#endif - - sfree(ssh->deferred_abort_message); - sfree(ssh->description); - - delete_callbacks_for_context(ssh); /* likely to catch ic_out_raw */ - - need_random_unref = ssh->need_random_unref; - sfree(ssh); - - if (need_random_unref) - random_unref(); -} - -/* - * Reconfigure the SSH backend. - */ -static void ssh_reconfig(Backend *be, Conf *conf) -{ - Ssh *ssh = container_of(be, Ssh, backend); - - if (ssh->pinger) - pinger_reconfig(ssh->pinger, ssh->conf, conf); - - if (ssh->base_layer) - ssh_ppl_reconfigure(ssh->base_layer, conf); - - conf_free(ssh->conf); - ssh->conf = conf_copy(conf); - ssh_cache_conf_values(ssh); -} - -/* - * Called to send data down the SSH connection. - */ -static void ssh_send(Backend *be, const char *buf, size_t len) -{ - Ssh *ssh = container_of(be, Ssh, backend); - - if (ssh == NULL || ssh->s == NULL) - return; - - bufchain_add(&ssh->user_input, buf, len); - if (ssh->cl) - ssh_got_user_input(ssh->cl); -} - -/* - * Called to query the current amount of buffered stdin data. - */ -static size_t ssh_sendbuffer(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - size_t backlog; - - if (!ssh || !ssh->s || !ssh->cl) - return 0; - - backlog = ssh_stdin_backlog(ssh->cl); - - if (ssh->base_layer) - backlog += ssh_ppl_queued_data_size(ssh->base_layer); - - /* - * If the SSH socket itself has backed up, add the total backup - * size on that to any individual buffer on the stdin channel. - */ - if (ssh->throttled_all) - backlog += ssh->overall_bufsize; - - return backlog; -} - -void ssh_sendbuffer_changed(Ssh *ssh) -{ - seat_sent(ssh->seat, ssh_sendbuffer(&ssh->backend)); -} - -/* - * Called to set the size of the window from SSH's POV. - */ -static void ssh_size(Backend *be, int width, int height) -{ - Ssh *ssh = container_of(be, Ssh, backend); - - ssh->term_width = width; - ssh->term_height = height; - if (ssh->cl) - ssh_terminal_size(ssh->cl, ssh->term_width, ssh->term_height); -} - -struct ssh_add_special_ctx { - SessionSpecial *specials; - size_t nspecials, specials_size; -}; - -static void ssh_add_special(void *vctx, const char *text, - SessionSpecialCode code, int arg) -{ - struct ssh_add_special_ctx *ctx = (struct ssh_add_special_ctx *)vctx; - SessionSpecial *spec; - - sgrowarray(ctx->specials, ctx->specials_size, ctx->nspecials); - spec = &ctx->specials[ctx->nspecials++]; - spec->name = text; - spec->code = code; - spec->arg = arg; -} - -/* - * Return a list of the special codes that make sense in this - * protocol. - */ -static const SessionSpecial *ssh_get_specials(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - - /* - * Ask all our active protocol layers what specials they've got, - * and amalgamate the list into one combined one. - */ - - struct ssh_add_special_ctx ctx[1]; - - ctx->specials = NULL; - ctx->nspecials = ctx->specials_size = 0; - - if (ssh->base_layer) - ssh_ppl_get_specials(ssh->base_layer, ssh_add_special, ctx); - - if (ctx->specials) { - /* If the list is non-empty, terminate it with a SS_EXITMENU. */ - ssh_add_special(ctx, NULL, SS_EXITMENU, 0); - } - - sfree(ssh->specials); - ssh->specials = ctx->specials; - return ssh->specials; -} - -/* - * Send special codes. - */ -static void ssh_special(Backend *be, SessionSpecialCode code, int arg) -{ - Ssh *ssh = container_of(be, Ssh, backend); - - if (ssh->base_layer) - ssh_ppl_special_cmd(ssh->base_layer, code, arg); -} - -/* - * This is called when the seat's output channel manages to clear some - * backlog. - */ -static void ssh_unthrottle(Backend *be, size_t bufsize) -{ - Ssh *ssh = container_of(be, Ssh, backend); - - if (ssh->cl) - ssh_stdout_unthrottle(ssh->cl, bufsize); -} - -static bool ssh_connected(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - return ssh->s != NULL; -} - -static bool ssh_sendok(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - return ssh->cl && ssh_get_wants_user_input(ssh->cl); -} - -void ssh_check_sendok(Ssh *ssh) -{ - /* Called when the connection layer might have caused ssh_sendok - * to start returning true */ - if (ssh->ldisc) - ldisc_check_sendok(ssh->ldisc); -} - -void ssh_ldisc_update(Ssh *ssh) -{ - /* Called when the connection layer wants to propagate an update - * to the line discipline options */ - if (ssh->ldisc) - ldisc_echoedit_update(ssh->ldisc); -} - -static bool ssh_ldisc(Backend *be, int option) -{ - Ssh *ssh = container_of(be, Ssh, backend); - return ssh->cl ? ssh_ldisc_option(ssh->cl, option) : false; -} - -static void ssh_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - Ssh *ssh = container_of(be, Ssh, backend); - ssh->ldisc = ldisc; -} - -void ssh_got_exitcode(Ssh *ssh, int exitcode) -{ - ssh->exitcode = exitcode; -} - -static int ssh_return_exitcode(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - if (ssh->s && (!ssh->session_started || ssh->base_layer)) - 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(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - 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 pscp.c can reach - * into the SSH code and find out which one it got. - */ -extern bool ssh_fallback_cmd(Backend *be) -{ - Ssh *ssh = container_of(be, Ssh, backend); - return ssh->fallback_cmd; -} - -void ssh_got_fallback_cmd(Ssh *ssh) -{ - ssh->fallback_cmd = true; -} - -const BackendVtable ssh_backend = { - .init = ssh_init, - .free = ssh_free, - .reconfig = ssh_reconfig, - .send = ssh_send, - .sendbuffer = ssh_sendbuffer, - .size = ssh_size, - .special = ssh_special, - .get_specials = ssh_get_specials, - .connected = ssh_connected, - .exitcode = ssh_return_exitcode, - .sendok = ssh_sendok, - .ldisc_option_state = ssh_ldisc, - .provide_ldisc = ssh_provide_ldisc, - .unthrottle = ssh_unthrottle, - .cfg_info = ssh_cfg_info, - .test_for_upstream = ssh_test_for_upstream, - .close_warn_text = ssh_close_warn_text, - .id = "ssh", - .displayname_tc = "SSH", - .displayname_lc = "SSH", /* proper name, so capitalise it anyway */ - .protocol = PROT_SSH, - .flags = BACKEND_SUPPORTS_NC_HOST | BACKEND_NOTIFIES_SESSION_START, - .default_port = 22, -}; - -const BackendVtable sshconn_backend = { - .init = ssh_init, - .free = ssh_free, - .reconfig = ssh_reconfig, - .send = ssh_send, - .sendbuffer = ssh_sendbuffer, - .size = ssh_size, - .special = ssh_special, - .get_specials = ssh_get_specials, - .connected = ssh_connected, - .exitcode = ssh_return_exitcode, - .sendok = ssh_sendok, - .ldisc_option_state = ssh_ldisc, - .provide_ldisc = ssh_provide_ldisc, - .unthrottle = ssh_unthrottle, - .cfg_info = ssh_cfg_info, - .test_for_upstream = ssh_test_for_upstream, - .close_warn_text = ssh_close_warn_text, - .id = "ssh-connection", - .displayname_tc = "Bare ssh-connection", - .displayname_lc = "bare ssh-connection", - .protocol = PROT_SSHCONN, - .flags = BACKEND_SUPPORTS_NC_HOST | BACKEND_NOTIFIES_SESSION_START, -}; diff --git a/ssh/transient-hostkey-cache.c b/ssh/transient-hostkey-cache.c deleted file mode 100644 index 2e77fdf9c..000000000 --- a/ssh/transient-hostkey-cache.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Data structure managing host keys in sessions based on GSSAPI KEX. - * - * In a session we started with a GSSAPI key exchange, the concept of - * 'host key' has completely different lifetime and security semantics - * from the usual ones. Per RFC 4462 section 2.1, we assume that any - * host key delivered to us in the course of a GSSAPI key exchange is - * _solely_ there to use as a transient fallback within the same - * session, if at the time of a subsequent rekey the GSS credentials - * are temporarily invalid and so a non-GSS KEX method has to be used. - * - * In particular, in a GSS-based SSH deployment, host keys may not - * even _be_ persistent identities for the server; it would be - * legitimate for a server to generate a fresh one routinely if it - * wanted to, like SSH-1 server keys. - * - * So, in this mode, we never touch the persistent host key cache at - * all, either to check keys against it _or_ to store keys in it. - * Instead, we maintain an in-memory cache of host keys that have been - * mentioned in GSS key exchanges within this particular session, and - * we permit precisely those host keys in non-GSS rekeys. - */ - -#include - -#include "putty.h" -#include "ssh.h" - -struct ssh_transient_hostkey_cache { - tree234 *cache; -}; - -struct ssh_transient_hostkey_cache_entry { - const ssh_keyalg *alg; - strbuf *pub_blob; -}; - -static int ssh_transient_hostkey_cache_cmp(void *av, void *bv) -{ - const struct ssh_transient_hostkey_cache_entry - *a = (const struct ssh_transient_hostkey_cache_entry *)av, - *b = (const struct ssh_transient_hostkey_cache_entry *)bv; - return strcmp(a->alg->ssh_id, b->alg->ssh_id); -} - -static int ssh_transient_hostkey_cache_find(void *av, void *bv) -{ - const ssh_keyalg *aalg = (const ssh_keyalg *)av; - const struct ssh_transient_hostkey_cache_entry - *b = (const struct ssh_transient_hostkey_cache_entry *)bv; - return strcmp(aalg->ssh_id, b->alg->ssh_id); -} - -ssh_transient_hostkey_cache *ssh_transient_hostkey_cache_new(void) -{ - ssh_transient_hostkey_cache *thc = snew(ssh_transient_hostkey_cache); - thc->cache = newtree234(ssh_transient_hostkey_cache_cmp); - return thc; -} - -void ssh_transient_hostkey_cache_free(ssh_transient_hostkey_cache *thc) -{ - struct ssh_transient_hostkey_cache_entry *ent; - while ((ent = delpos234(thc->cache, 0)) != NULL) { - strbuf_free(ent->pub_blob); - sfree(ent); - } - freetree234(thc->cache); - sfree(thc); -} - -void ssh_transient_hostkey_cache_add( - ssh_transient_hostkey_cache *thc, ssh_key *key) -{ - struct ssh_transient_hostkey_cache_entry *ent, *retd; - - if ((ent = find234(thc->cache, (void *)ssh_key_alg(key), - ssh_transient_hostkey_cache_find)) != NULL) { - del234(thc->cache, ent); - strbuf_free(ent->pub_blob); - sfree(ent); - } - - ent = snew(struct ssh_transient_hostkey_cache_entry); - ent->alg = ssh_key_alg(key); - ent->pub_blob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(ent->pub_blob)); - retd = add234(thc->cache, ent); - assert(retd == ent); -} - -bool ssh_transient_hostkey_cache_verify( - ssh_transient_hostkey_cache *thc, ssh_key *key) -{ - struct ssh_transient_hostkey_cache_entry *ent; - bool toret = false; - - if ((ent = find234(thc->cache, (void *)ssh_key_alg(key), - ssh_transient_hostkey_cache_find)) != NULL) { - strbuf *this_blob = strbuf_new(); - ssh_key_public_blob(key, BinarySink_UPCAST(this_blob)); - - if (this_blob->len == ent->pub_blob->len && - !memcmp(this_blob->s, ent->pub_blob->s, - this_blob->len)) - toret = true; - - strbuf_free(this_blob); - } - - return toret; -} - -bool ssh_transient_hostkey_cache_has( - ssh_transient_hostkey_cache *thc, const ssh_keyalg *alg) -{ - struct ssh_transient_hostkey_cache_entry *ent = - find234(thc->cache, (void *)alg, - ssh_transient_hostkey_cache_find); - return ent != NULL; -} - -bool ssh_transient_hostkey_cache_non_empty(ssh_transient_hostkey_cache *thc) -{ - return count234(thc->cache) > 0; -} diff --git a/ssh/transport2.c b/ssh/transport2.c deleted file mode 100644 index 5dd73cfef..000000000 --- a/ssh/transport2.c +++ /dev/null @@ -1,2753 +0,0 @@ -/* - * Packet protocol layer for the SSH-2 transport protocol (RFC 4253). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "sshcr.h" -#include "server.h" -#include "storage.h" -#include "transport2.h" -#include "mpint.h" - -const struct ssh_signkey_with_user_pref_id ssh2_hostkey_algs[] = { - #define ARRAYENT_HOSTKEY_ALGORITHM(type, alg) { &alg, type }, - HOSTKEY_ALGORITHMS(ARRAYENT_HOSTKEY_ALGORITHM) -}; - -const static ssh2_macalg *const macs[] = { - &ssh_hmac_sha256, &ssh_hmac_sha512, - &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5 -}; -const static ssh2_macalg *const buggymacs[] = { - &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5 -}; - -const static ptrlen ext_info_c = PTRLEN_DECL_LITERAL("ext-info-c"); -const static ptrlen ext_info_s = PTRLEN_DECL_LITERAL("ext-info-s"); -const static ptrlen kex_strict_c = - PTRLEN_DECL_LITERAL("kex-strict-c-v00@openssh.com"); -const static ptrlen kex_strict_s = - PTRLEN_DECL_LITERAL("kex-strict-s-v00@openssh.com"); - -/* Pointer value to store in s->weak_algorithms_consented_to to - * indicate that the user has accepted the risk of the Terrapin - * attack */ -static const char terrapin_weakness[1]; - -static ssh_compressor *ssh_comp_none_init(void) -{ - return NULL; -} -static void ssh_comp_none_cleanup(ssh_compressor *handle) -{ -} -static ssh_decompressor *ssh_decomp_none_init(void) -{ - return NULL; -} -static void ssh_decomp_none_cleanup(ssh_decompressor *handle) -{ -} -static void ssh_comp_none_block(ssh_compressor *handle, - const unsigned char *block, int len, - unsigned char **outblock, int *outlen, - int minlen) -{ -} -static bool ssh_decomp_none_block(ssh_decompressor *handle, - const unsigned char *block, int len, - unsigned char **outblock, int *outlen) -{ - return false; -} -static const ssh_compression_alg ssh_comp_none = { - .name = "none", - .delayed_name = NULL, - .compress_new = ssh_comp_none_init, - .compress_free = ssh_comp_none_cleanup, - .compress = ssh_comp_none_block, - .decompress_new = ssh_decomp_none_init, - .decompress_free = ssh_decomp_none_cleanup, - .decompress = ssh_decomp_none_block, - .text_name = NULL, -}; -const static ssh_compression_alg *const compressions[] = { - &ssh_zlib, &ssh_comp_none -}; - -static void ssh2_transport_free(PacketProtocolLayer *); -static void ssh2_transport_process_queue(PacketProtocolLayer *); -static bool ssh2_transport_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); -static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg); -static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf); -static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl); - -static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s); -static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def); -static void ssh2_transport_higher_layer_packet_callback(void *context); -static void ssh2_transport_final_output(PacketProtocolLayer *ppl); -static const char *terrapin_vulnerable( - bool strict_kex, const transport_direction *d); -static bool try_to_avoid_terrapin(const struct ssh2_transport_state *s); - -static const PacketProtocolLayerVtable ssh2_transport_vtable = { - .free = ssh2_transport_free, - .process_queue = ssh2_transport_process_queue, - .get_specials = ssh2_transport_get_specials, - .special_cmd = ssh2_transport_special_cmd, - .reconfigure = ssh2_transport_reconfigure, - .queued_data_size = ssh2_transport_queued_data_size, - .final_output = ssh2_transport_final_output, - .name = NULL, /* no protocol name for this layer */ -}; - -#ifndef NO_GSSAPI -static void ssh2_transport_gss_update(struct ssh2_transport_state *s, - bool definitely_rekeying); -#endif - -static bool ssh2_transport_timer_update(struct ssh2_transport_state *s, - unsigned long rekey_time); -static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive( - struct ssh2_transport_state *s, const char *type, const char *name, - const void *alg, WeakCryptoReason wcr); - -static const char *const kexlist_descr[NKEXLIST] = { - "key exchange algorithm", - "host key algorithm", - "client-to-server cipher", - "server-to-client cipher", - "client-to-server MAC", - "server-to-client MAC", - "client-to-server compression method", - "server-to-client compression method" -}; - -static int weak_algorithm_compare(void *av, void *bv); -static int ca_blob_compare(void *av, void *bv); - -PacketProtocolLayer *ssh2_transport_new( - Conf *conf, const char *host, int port, const char *fullhostname, - const char *client_greeting, const char *server_greeting, - struct ssh_connection_shared_gss_state *shgss, - struct DataTransferStats *stats, PacketProtocolLayer *higher_layer, - const SshServerConfig *ssc) -{ - struct ssh2_transport_state *s = snew(struct ssh2_transport_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh2_transport_vtable; - - s->conf = conf_copy(conf); - s->savedhost = dupstr(host); - s->savedport = port; - s->fullhostname = dupstr(fullhostname); - s->shgss = shgss; - s->client_greeting = dupstr(client_greeting); - s->server_greeting = dupstr(server_greeting); - s->stats = stats; - s->hostkeyblob = strbuf_new(); - s->host_cas = newtree234(ca_blob_compare); - - pq_in_init(&s->pq_in_higher); - pq_out_init(&s->pq_out_higher); - s->pq_out_higher.pqb.ic = &s->ic_pq_out_higher; - s->ic_pq_out_higher.fn = ssh2_transport_higher_layer_packet_callback; - s->ic_pq_out_higher.ctx = &s->ppl; - - s->higher_layer = higher_layer; - s->higher_layer->selfptr = &s->higher_layer; - ssh_ppl_setup_queues(s->higher_layer, &s->pq_in_higher, &s->pq_out_higher); - -#ifndef NO_GSSAPI - s->gss_cred_expiry = GSS_NO_EXPIRATION; - s->shgss->srv_name = GSS_C_NO_NAME; - s->shgss->ctx = NULL; -#endif - s->thc = ssh_transient_hostkey_cache_new(); - s->gss_kex_used = false; - - s->outgoing_kexinit = strbuf_new(); - s->incoming_kexinit = strbuf_new(); - if (ssc) { - s->ssc = ssc; - s->client_kexinit = s->incoming_kexinit; - s->server_kexinit = s->outgoing_kexinit; - s->cstrans = &s->in; - s->sctrans = &s->out; - s->out.mkkey_adjust = 1; - } else { - s->client_kexinit = s->outgoing_kexinit; - s->server_kexinit = s->incoming_kexinit; - s->cstrans = &s->out; - s->sctrans = &s->in; - s->in.mkkey_adjust = 1; - } - - s->weak_algorithms_consented_to = newtree234(weak_algorithm_compare); - - ssh2_transport_set_max_data_size(s); - - return &s->ppl; -} - -static void ssh2_transport_free(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - /* - * As our last act before being freed, move any outgoing packets - * off our higher layer's output queue on to our own output queue. - * We might be being freed while the SSH connection is still alive - * (because we're initiating shutdown from our end), in which case - * we don't want those last few packets to get lost. - * - * (If our owner were to have already destroyed our output pq - * before wanting to free us, then it would have to reset our - * publicly visible out_pq field to NULL to inhibit this attempt. - * But that's not how I expect the shutdown sequence to go in - * practice.) - */ - if (s->ppl.out_pq) - pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); - - conf_free(s->conf); - - ssh_ppl_free(s->higher_layer); - - pq_in_clear(&s->pq_in_higher); - pq_out_clear(&s->pq_out_higher); - - sfree(s->savedhost); - sfree(s->fullhostname); - sfree(s->client_greeting); - sfree(s->server_greeting); - sfree(s->keystr); - strbuf_free(s->hostkeyblob); - { - host_ca *hca; - while ( (hca = delpos234(s->host_cas, 0)) ) - host_ca_free(hca); - freetree234(s->host_cas); - } - if (s->hkey && !s->hostkeys) { - ssh_key_free(s->hkey); - s->hkey = NULL; - } - for (size_t i = 0; i < NKEXLIST; i++) - sfree(s->kexlists[i].algs); - if (s->f) mp_free(s->f); - if (s->p) mp_free(s->p); - if (s->g) mp_free(s->g); - if (s->ebuf) strbuf_free(s->ebuf); - if (s->fbuf) strbuf_free(s->fbuf); - if (s->kex_shared_secret) strbuf_free(s->kex_shared_secret); - if (s->dh_ctx) - dh_cleanup(s->dh_ctx); - if (s->rsa_kex_key_needs_freeing) { - ssh_rsakex_freekey(s->rsa_kex_key); - sfree(s->rsa_kex_key); - } - if (s->ecdh_key) - ecdh_key_free(s->ecdh_key); - if (s->exhash) - ssh_hash_free(s->exhash); - strbuf_free(s->outgoing_kexinit); - strbuf_free(s->incoming_kexinit); - ssh_transient_hostkey_cache_free(s->thc); - - freetree234(s->weak_algorithms_consented_to); - - expire_timer_context(s); - sfree(s); -} - -/* - * SSH-2 key derivation (RFC 4253 section 7.2). - */ -static void ssh2_mkkey( - struct ssh2_transport_state *s, strbuf *out, - strbuf *kex_shared_secret, unsigned char *H, char chr, int keylen) -{ - int hlen = s->kex_alg->hash->hlen; - int keylen_padded; - unsigned char *key; - ssh_hash *h; - - if (keylen == 0) - return; - - /* - * Round the requested amount of key material up to a multiple of - * the length of the hash we're using to make it. This makes life - * simpler because then we can just write each hash output block - * straight into the output buffer without fiddling about - * truncating the last one. Since it's going into a strbuf, and - * strbufs are always smemclr()ed on free, there's no need to - * worry about leaving extra potentially-sensitive data in memory - * that the caller didn't ask for. - */ - keylen_padded = ((keylen + hlen - 1) / hlen) * hlen; - - strbuf_clear(out); - key = strbuf_append(out, keylen_padded); - - /* First hlen bytes. */ - h = ssh_hash_new(s->kex_alg->hash); - if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY)) - put_datapl(h, ptrlen_from_strbuf(kex_shared_secret)); - put_data(h, H, hlen); - put_byte(h, chr); - put_data(h, s->session_id, s->session_id_len); - ssh_hash_digest(h, key); - - /* Subsequent blocks of hlen bytes. */ - if (keylen_padded > hlen) { - int offset; - - ssh_hash_reset(h); - if (!(s->ppl.remote_bugs & BUG_SSH2_DERIVEKEY)) - put_datapl(h, ptrlen_from_strbuf(kex_shared_secret)); - put_data(h, H, hlen); - - for (offset = hlen; offset < keylen_padded; offset += hlen) { - put_data(h, key + offset - hlen, hlen); - ssh_hash_digest_nondestructive(h, key + offset); - } - - } - - ssh_hash_free(h); -} - -/* - * Find a slot in a KEXINIT algorithm list to use for a new algorithm. - * If the algorithm is already in the list, return a pointer to its - * entry, otherwise return an entry from the end of the list. - * - * 'name' is expected to be a ptrlen which it's safe to keep a copy - * of. - */ -static struct kexinit_algorithm *ssh2_kexinit_addalg_pl( - struct kexinit_algorithm_list *list, ptrlen name) -{ - for (size_t i = 0; i < list->nalgs; i++) - if (ptrlen_eq_ptrlen(list->algs[i].name, name)) - return &list->algs[i]; - - sgrowarray(list->algs, list->algsize, list->nalgs); - struct kexinit_algorithm *entry = &list->algs[list->nalgs++]; - entry->name = name; - return entry; -} - -static struct kexinit_algorithm *ssh2_kexinit_addalg( - struct kexinit_algorithm_list *list, const char *name) -{ - return ssh2_kexinit_addalg_pl(list, ptrlen_from_asciz(name)); -} - -bool ssh2_common_filter_queue(PacketProtocolLayer *ppl) -{ - 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", - }; - - PktIn *pktin; - ptrlen msg; - int reason; - - while ((pktin = pq_peek(ppl->in_pq)) != NULL) { - switch (pktin->type) { - case SSH2_MSG_DISCONNECT: - reason = get_uint32(pktin); - msg = get_string(pktin); - - ssh_remote_error( - ppl->ssh, "Remote side sent disconnect message\n" - "type %d (%s):\n\"%.*s\"", reason, - ((reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? - ssh2_disconnect_reasons[reason] : "unknown"), - PTRLEN_PRINTF(msg)); - /* don't try to pop the queue, because we've been freed! */ - return true; /* indicate that we've been freed */ - - case SSH2_MSG_DEBUG: - /* XXX maybe we should actually take notice of the return value */ - get_bool(pktin); - msg = get_string(pktin); - ppl_logevent("Remote debug message: %.*s", PTRLEN_PRINTF(msg)); - pq_pop(ppl->in_pq); - break; - - case SSH2_MSG_IGNORE: - /* Do nothing, because we're ignoring it! Duhh. */ - pq_pop(ppl->in_pq); - break; - - case SSH2_MSG_EXT_INFO: { - /* - * The BPP enforces that these turn up only at legal - * points in the protocol. In particular, it will not pass - * an EXT_INFO on to us if it arrives before encryption is - * enabled (which is when a MITM could inject one - * maliciously). - * - * However, one of the criteria for legality is that a - * server is permitted to send this message immediately - * _before_ USERAUTH_SUCCESS. So we may receive this - * message not yet knowing whether it's legal to have sent - * it - we won't know until the BPP processes the next - * packet. - * - * But that should be OK, because firstly, an - * out-of-sequence EXT_INFO that's still within the - * encrypted session is only a _protocol_ violation, not - * an attack; secondly, any data we set in response to - * such an illegal EXT_INFO won't have a chance to affect - * the session before the BPP aborts it anyway. - */ - uint32_t nexts = get_uint32(pktin); - for (uint32_t i = 0; i < nexts && !get_err(pktin); i++) { - ptrlen extname = get_string(pktin); - ptrlen extvalue = get_string(pktin); - if (ptrlen_eq_string(extname, "server-sig-algs")) { - /* - * Server has sent a list of signature algorithms - * it will potentially accept for user - * authentication keys. Check in particular - * whether the RFC 8332 improved versions of - * ssh-rsa are in the list, and set flags in the - * BPP if so. - * - * TODO: another thing we _could_ do here is to - * record a full list of the algorithm identifiers - * we've seen, whether we understand them - * ourselves or not. Then we could use that as a - * pre-filter during userauth, to skip keys in the - * SSH agent if we already know the server can't - * possibly accept them. (Even if the key - * algorithm is one that the agent and the server - * both understand but we do not.) - */ - ptrlen algname; - while (get_commasep_word(&extvalue, &algname)) { - if (ptrlen_eq_string(algname, "rsa-sha2-256")) - ppl->bpp->ext_info_rsa_sha256_ok = true; - if (ptrlen_eq_string(algname, "rsa-sha2-512")) - ppl->bpp->ext_info_rsa_sha512_ok = true; - } - } - } - pq_pop(ppl->in_pq); - break; - } - - default: - return false; - } - } - - return false; -} - -static bool ssh2_transport_filter_queue(struct ssh2_transport_state *s) -{ - PktIn *pktin; - - if (!s->enabled_incoming_crypto) { - /* - * Record the fact that we've seen any non-KEXINIT packet at - * the head of our queue. - * - * This enables us to check later that the initial incoming - * KEXINIT was the very first packet, if scanning the KEXINITs - * turns out to enable strict-kex mode. - */ - PktIn *pktin = pq_peek(s->ppl.in_pq); - if (pktin && pktin->type != SSH2_MSG_KEXINIT) - s->seen_non_kexinit = true; - - if (s->strict_kex) { - /* - * Also, if we're already in strict-KEX mode and haven't - * turned on crypto yet, don't do any actual filtering. - * This ensures that extraneous packets _after_ the - * KEXINIT will go to the main coroutine, which will - * complain about them. - */ - return false; - } - } - - while (1) { - if (ssh2_common_filter_queue(&s->ppl)) - return true; - if ((pktin = pq_peek(s->ppl.in_pq)) == NULL) - return false; - - /* Pass on packets to the next layer if they're outside - * the range reserved for the transport protocol. */ - if (pktin->type >= 50) { - /* ... except that we shouldn't tolerate higher-layer - * packets coming from the server before we've seen - * the first NEWKEYS. */ - if (!s->higher_layer_ok) { - ssh_proto_error(s->ppl.ssh, "Received premature higher-" - "layer packet, type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return true; - } - - pq_pop(s->ppl.in_pq); - pq_push(&s->pq_in_higher, pktin); - } else { - /* Anything else is a transport-layer packet that the main - * process_queue coroutine should handle. */ - return false; - } - } -} - -PktIn *ssh2_transport_pop(struct ssh2_transport_state *s) -{ - if (ssh2_transport_filter_queue(s)) - return NULL; /* we've been freed */ - return pq_pop(s->ppl.in_pq); -} - -static void ssh2_write_kexinit_lists( - BinarySink *pktout, - struct kexinit_algorithm_list kexlists[NKEXLIST], - Conf *conf, const SshServerConfig *ssc, int remote_bugs, - const char *hk_host, int hk_port, const ssh_keyalg *hk_prev, - ssh_transient_hostkey_cache *thc, tree234 *host_cas, - ssh_key *const *our_hostkeys, int our_nhostkeys, - bool first_time, bool can_gssapi_keyex, bool transient_hostkey_mode) -{ - int i, j, k; - bool warn; - - int n_preferred_kex; - const ssh_kexes *preferred_kex[KEX_MAX + 3]; /* +3 for GSSAPI */ - int n_preferred_hk; - int preferred_hk[HK_MAX]; - int n_preferred_ciphers; - const ssh2_ciphers *preferred_ciphers[CIPHER_MAX]; - const ssh_compression_alg *preferred_comp; - const ssh2_macalg *const *maclist; - int nmacs; - - struct kexinit_algorithm *alg; - - /* - * Set up the preferred key exchange. (NULL => warn below here) - */ - n_preferred_kex = 0; - if (can_gssapi_keyex) { - preferred_kex[n_preferred_kex++] = &ssh_gssk5_ecdh_kex; - preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha2_kex; - preferred_kex[n_preferred_kex++] = &ssh_gssk5_sha1_kex; - } - for (i = 0; i < KEX_MAX; i++) { - switch (conf_get_int_int(conf, CONF_ssh_kexlist, i)) { - case KEX_DHGEX: - preferred_kex[n_preferred_kex++] = - &ssh_diffiehellman_gex; - break; - case KEX_DHGROUP18: - preferred_kex[n_preferred_kex++] = - &ssh_diffiehellman_group18; - break; - case KEX_DHGROUP17: - preferred_kex[n_preferred_kex++] = - &ssh_diffiehellman_group17; - break; - case KEX_DHGROUP16: - preferred_kex[n_preferred_kex++] = - &ssh_diffiehellman_group16; - break; - case KEX_DHGROUP15: - preferred_kex[n_preferred_kex++] = - &ssh_diffiehellman_group15; - break; - case KEX_DHGROUP14: - preferred_kex[n_preferred_kex++] = - &ssh_diffiehellman_group14; - break; - case KEX_DHGROUP1: - preferred_kex[n_preferred_kex++] = - &ssh_diffiehellman_group1; - break; - case KEX_RSA: - preferred_kex[n_preferred_kex++] = - &ssh_rsa_kex; - break; - case KEX_ECDH: - preferred_kex[n_preferred_kex++] = - &ssh_ecdh_kex; - break; - case KEX_NTRU_HYBRID: - preferred_kex[n_preferred_kex++] = - &ssh_ntru_hybrid_kex; - break; - case KEX_WARN: - /* Flag for later. Don't bother if it's the last in - * the list. */ - if (i < KEX_MAX - 1) { - preferred_kex[n_preferred_kex++] = NULL; - } - break; - } - } - - /* - * Set up the preferred host key types. These are just the ids - * in the enum in putty.h, so 'warn below here' is indicated - * by HK_WARN. - */ - n_preferred_hk = 0; - for (i = 0; i < HK_MAX; i++) { - int id = conf_get_int_int(conf, CONF_ssh_hklist, i); - /* As above, don't bother with HK_WARN if it's last in the - * list */ - if (id != HK_WARN || i < HK_MAX - 1) - preferred_hk[n_preferred_hk++] = id; - } - - /* - * Set up the preferred ciphers. (NULL => warn below here) - */ - n_preferred_ciphers = 0; - for (i = 0; i < CIPHER_MAX; i++) { - switch (conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { - case CIPHER_BLOWFISH: - preferred_ciphers[n_preferred_ciphers++] = &ssh2_blowfish; - break; - case CIPHER_DES: - if (conf_get_bool(conf, CONF_ssh2_des_cbc)) - preferred_ciphers[n_preferred_ciphers++] = &ssh2_des; - break; - case CIPHER_3DES: - preferred_ciphers[n_preferred_ciphers++] = &ssh2_3des; - break; - case CIPHER_AES: - preferred_ciphers[n_preferred_ciphers++] = &ssh2_aes; - break; - case CIPHER_ARCFOUR: - preferred_ciphers[n_preferred_ciphers++] = &ssh2_arcfour; - break; - case CIPHER_CHACHA20: - preferred_ciphers[n_preferred_ciphers++] = &ssh2_ccp; - break; - case CIPHER_AESGCM: - preferred_ciphers[n_preferred_ciphers++] = &ssh2_aesgcm; - break; - case CIPHER_WARN: - /* Flag for later. Don't bother if it's the last in - * the list. */ - if (i < CIPHER_MAX - 1) { - preferred_ciphers[n_preferred_ciphers++] = NULL; - } - break; - } - } - - /* - * Set up preferred compression. - */ - if (conf_get_bool(conf, CONF_compression)) - preferred_comp = &ssh_zlib; - else - preferred_comp = &ssh_comp_none; - - for (i = 0; i < NKEXLIST; i++) - kexlists[i].nalgs = 0; - /* List key exchange algorithms. */ - warn = false; - for (i = 0; i < n_preferred_kex; i++) { - const ssh_kexes *k = preferred_kex[i]; - if (!k) warn = true; - else for (j = 0; j < k->nkexes; j++) { - alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_KEX], - k->list[j]->name); - alg->u.kex.kex = k->list[j]; - alg->u.kex.warn = warn; - } - } - /* List server host key algorithms. */ - if (our_hostkeys) { - /* - * In server mode, we just list the algorithms that match the - * host keys we actually have. - */ - for (i = 0; i < our_nhostkeys; i++) { - const ssh_keyalg *keyalg = ssh_key_alg(our_hostkeys[i]); - - alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], - keyalg->ssh_id); - alg->u.hk.hostkey = keyalg; - alg->u.hk.hkflags = 0; - alg->u.hk.warn = false; - - uint32_t supported_flags = ssh_keyalg_supported_flags(keyalg); - static const uint32_t try_flags[] = { - SSH_AGENT_RSA_SHA2_256, - SSH_AGENT_RSA_SHA2_512, - }; - for (size_t i = 0; i < lenof(try_flags); i++) { - if (try_flags[i] & ~supported_flags) - continue; /* these flags not supported */ - - alg = ssh2_kexinit_addalg( - &kexlists[KEXLIST_HOSTKEY], - ssh_keyalg_alternate_ssh_id(keyalg, try_flags[i])); - - alg->u.hk.hostkey = keyalg; - alg->u.hk.hkflags = try_flags[i]; - alg->u.hk.warn = false; - } - } - } else if (first_time) { - /* - * In the first key exchange, we list all the algorithms we're - * prepared to cope with, but (if configured to) we prefer - * those algorithms for which we have a host key for this - * host. - * - * If the host key algorithm is below the warning - * threshold, we warn even if we did already have a key - * for it, on the basis that if the user has just - * reconfigured that host key type to be warned about, - * they surely _do_ want to be alerted that a server - * they're actually connecting to is using it. - */ - - bool accept_certs = false; - { - host_ca_enum *handle = enum_host_ca_start(); - if (handle) { - strbuf *name = strbuf_new(); - while (strbuf_clear(name), enum_host_ca_next(handle, name)) { - host_ca *hca = host_ca_load(name->s); - if (!hca) - continue; - - if (hca->ca_public_key && - cert_expr_match_str(hca->validity_expression, - hk_host, hk_port)) { - accept_certs = true; - add234(host_cas, hca); - } else { - host_ca_free(hca); - } - } - enum_host_ca_finish(handle); - strbuf_free(name); - } - } - - if (accept_certs) { - /* Add all the certificate algorithms first, in preference order */ - warn = false; - for (i = 0; i < n_preferred_hk; i++) { - if (preferred_hk[i] == HK_WARN) - warn = true; - for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { - const struct ssh_signkey_with_user_pref_id *a = - &ssh2_hostkey_algs[j]; - if (!a->alg->is_certificate) - continue; - if (a->id != preferred_hk[i]) - continue; - alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], - a->alg->ssh_id); - alg->u.hk.hostkey = a->alg; - alg->u.hk.warn = warn; - } - } - } - - /* Next, add algorithms we already know a key for (unless - * configured not to do that) */ - warn = false; - for (i = 0; i < n_preferred_hk; i++) { - if (preferred_hk[i] == HK_WARN) - warn = true; - for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { - const struct ssh_signkey_with_user_pref_id *a = - &ssh2_hostkey_algs[j]; - if (a->alg->is_certificate && accept_certs) - continue; /* already added this one */ - if (a->id != preferred_hk[i]) - continue; - if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) && - have_ssh_host_key(hk_host, hk_port, - a->alg->cache_id)) { - alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], - a->alg->ssh_id); - alg->u.hk.hostkey = a->alg; - alg->u.hk.warn = warn; - } - } - } - - /* And finally, everything else */ - warn = false; - for (i = 0; i < n_preferred_hk; i++) { - if (preferred_hk[i] == HK_WARN) - warn = true; - for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { - const struct ssh_signkey_with_user_pref_id *a = - &ssh2_hostkey_algs[j]; - if (a->alg->is_certificate) - continue; - if (a->id != preferred_hk[i]) - continue; - alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], - a->alg->ssh_id); - alg->u.hk.hostkey = a->alg; - alg->u.hk.warn = warn; - } - } -#ifndef NO_GSSAPI - } else if (transient_hostkey_mode) { - /* - * If we've previously done a GSSAPI KEX, then we list - * precisely the algorithms for which a previous GSS key - * exchange has delivered us a host key, because we expect - * one of exactly those keys to be used in any subsequent - * non-GSS-based rekey. - * - * An exception is if this is the key exchange we - * triggered for the purposes of populating that cache - - * in which case the cache will currently be empty, which - * isn't helpful! - */ - warn = false; - for (i = 0; i < n_preferred_hk; i++) { - if (preferred_hk[i] == HK_WARN) - warn = true; - for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { - if (ssh2_hostkey_algs[j].id != preferred_hk[i]) - continue; - if (ssh_transient_hostkey_cache_has( - thc, ssh2_hostkey_algs[j].alg)) { - alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], - ssh2_hostkey_algs[j].alg->ssh_id); - alg->u.hk.hostkey = ssh2_hostkey_algs[j].alg; - alg->u.hk.warn = warn; - } - } - } -#endif - } else { - /* - * In subsequent key exchanges, we list only the host key - * 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(hk_prev); - alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], hk_prev->ssh_id); - alg->u.hk.hostkey = hk_prev; - alg->u.hk.warn = false; - } - if (can_gssapi_keyex) { - alg = ssh2_kexinit_addalg(&kexlists[KEXLIST_HOSTKEY], "null"); - alg->u.hk.hostkey = NULL; - } - /* List encryption algorithms (client->server then server->client). */ - for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) { - warn = false; -#ifdef FUZZING - alg = ssh2_kexinit_addalg(&kexlists[K], "none"); - alg->u.cipher.cipher = NULL; - alg->u.cipher.warn = warn; -#endif /* FUZZING */ - for (i = 0; i < n_preferred_ciphers; i++) { - const ssh2_ciphers *c = preferred_ciphers[i]; - if (!c) warn = true; - else for (j = 0; j < c->nciphers; j++) { - alg = ssh2_kexinit_addalg(&kexlists[k], - c->list[j]->ssh2_id); - alg->u.cipher.cipher = c->list[j]; - alg->u.cipher.warn = warn; - } - } - } - - /* - * Be prepared to work around the buggy MAC problem. - */ - if (remote_bugs & BUG_SSH2_HMAC) { - maclist = buggymacs; - nmacs = lenof(buggymacs); - } else { - maclist = macs; - nmacs = lenof(macs); - } - - /* List MAC algorithms (client->server then server->client). */ - for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) { -#ifdef FUZZING - alg = ssh2_kexinit_addalg(kexlists[j], "none"); - alg->u.mac.mac = NULL; - alg->u.mac.etm = false; -#endif /* FUZZING */ - for (i = 0; i < nmacs; i++) { - alg = ssh2_kexinit_addalg(&kexlists[j], maclist[i]->name); - alg->u.mac.mac = maclist[i]; - alg->u.mac.etm = false; - } - for (i = 0; i < nmacs; i++) { - /* For each MAC, there may also be an ETM version, - * which we list second. */ - if (maclist[i]->etm_name) { - alg = ssh2_kexinit_addalg(&kexlists[j], maclist[i]->etm_name); - alg->u.mac.mac = maclist[i]; - alg->u.mac.etm = true; - } - } - } - - /* List client->server compression algorithms, - * then server->client compression algorithms. (We use the - * same set twice.) */ - for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) { - assert(lenof(compressions) > 1); - /* Prefer non-delayed versions */ - alg = ssh2_kexinit_addalg(&kexlists[j], preferred_comp->name); - alg->u.comp.comp = preferred_comp; - alg->u.comp.delayed = false; - if (preferred_comp->delayed_name) { - alg = ssh2_kexinit_addalg(&kexlists[j], - preferred_comp->delayed_name); - alg->u.comp.comp = preferred_comp; - alg->u.comp.delayed = true; - } - for (i = 0; i < lenof(compressions); i++) { - const ssh_compression_alg *c = compressions[i]; - alg = ssh2_kexinit_addalg(&kexlists[j], c->name); - alg->u.comp.comp = c; - alg->u.comp.delayed = false; - if (c->delayed_name) { - alg = ssh2_kexinit_addalg(&kexlists[j], c->delayed_name); - alg->u.comp.comp = c; - alg->u.comp.delayed = true; - } - } - } - - /* - * Finally, format the lists into text and write them into the - * outgoing KEXINIT packet. - */ - for (i = 0; i < NKEXLIST; i++) { - strbuf *list = strbuf_new(); - if (ssc && ssc->kex_override[i].ptr) { - put_datapl(list, ssc->kex_override[i]); - } else { - for (j = 0; j < kexlists[i].nalgs; j++) - add_to_commasep_pl(list, kexlists[i].algs[j].name); - } - if (i == KEXLIST_KEX && first_time) { - if (our_hostkeys) { /* we're the server */ - add_to_commasep_pl(list, ext_info_s); - add_to_commasep_pl(list, kex_strict_s); - } else { /* we're the client */ - add_to_commasep_pl(list, ext_info_c); - add_to_commasep_pl(list, kex_strict_c); - } - } - put_stringsb(pktout, list); - } - /* List client->server languages. Empty list. */ - put_stringz(pktout, ""); - /* List server->client languages. Empty list. */ - put_stringz(pktout, ""); -} - -struct server_hostkeys { - int *indices; - size_t n, size; -}; - -static bool kexinit_keyword_found(ptrlen list, ptrlen keyword) -{ - for (ptrlen word; get_commasep_word(&list, &word) ;) - if (ptrlen_eq_ptrlen(word, keyword)) - return true; - return false; -} - -typedef struct ScanKexinitsResult { - bool success; - - /* only if success is false */ - enum { - SKR_INCOMPLETE, - SKR_UNKNOWN_ID, - SKR_NO_AGREEMENT, - } error; - - const char *kind; /* what kind of thing did we fail to sort out? */ - ptrlen desc; /* and what was it? or what was the available list? */ -} ScanKexinitsResult; - -static ScanKexinitsResult ssh2_scan_kexinits( - ptrlen client_kexinit, ptrlen server_kexinit, bool we_are_server, - struct kexinit_algorithm_list kexlists[NKEXLIST], - const ssh_kex **kex_alg, const ssh_keyalg **hostkey_alg, - transport_direction *cs, transport_direction *sc, - bool *warn_kex, bool *warn_hk, bool *warn_cscipher, bool *warn_sccipher, - bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet, - struct server_hostkeys *server_hostkeys, unsigned *hkflags, - bool *can_send_ext_info, bool first_time, bool *strict_kex) -{ - BinarySource client[1], server[1]; - int i; - bool guess_correct; - ptrlen clists[NKEXLIST], slists[NKEXLIST]; - const struct kexinit_algorithm *selected[NKEXLIST]; - - BinarySource_BARE_INIT_PL(client, client_kexinit); - BinarySource_BARE_INIT_PL(server, server_kexinit); - - /* Skip packet type bytes and random cookies. */ - get_data(client, 1 + 16); - get_data(server, 1 + 16); - - guess_correct = true; - - /* Find the matching string in each list, and map it to its - * kexinit_algorithm structure. */ - for (i = 0; i < NKEXLIST; i++) { - ptrlen clist, slist, cword, sword, found; - bool cfirst, sfirst; - int j; - - clists[i] = get_string(client); - slists[i] = get_string(server); - if (get_err(client) || get_err(server)) { - ScanKexinitsResult skr = { - .success = false, .error = SKR_INCOMPLETE, - }; - return skr; - } - - for (cfirst = true, clist = clists[i]; - get_commasep_word(&clist, &cword); cfirst = false) - for (sfirst = true, slist = slists[i]; - get_commasep_word(&slist, &sword); sfirst = false) - if (ptrlen_eq_ptrlen(cword, sword)) { - found = cword; - goto found_match; - } - - /* No matching string found in the two lists. Delay reporting - * a fatal error until below, because sometimes it turns out - * not to be fatal. */ - selected[i] = NULL; - - /* - * However, even if a failure to agree on any algorithm at all - * is not completely fatal (e.g. because it's the MAC - * negotiation for a cipher that comes with a built-in MAC), - * it still invalidates the guessed key exchange packet. (RFC - * 4253 section 7, not contradicted by OpenSSH's - * PROTOCOL.chacha20poly1305 or as far as I can see by their - * code.) - */ - guess_correct = false; - - continue; - - found_match: - - selected[i] = NULL; - for (j = 0; j < kexlists[i].nalgs; j++) { - if (ptrlen_eq_ptrlen(found, kexlists[i].algs[j].name)) { - selected[i] = &kexlists[i].algs[j]; - break; - } - } - if (!selected[i]) { - /* - * In the client, this should never happen! But in the - * server, where we allow manual override on the command - * line of the exact KEXINIT strings, it can happen - * because the command line contained a typo. So we - * produce a reasonably useful message instead of an - * assertion failure. - */ - ScanKexinitsResult skr = { - .success = false, .error = SKR_UNKNOWN_ID, - .kind = kexlist_descr[i], .desc = found, - }; - return skr; - } - - /* - * If the kex or host key algorithm is not the first one in - * both sides' lists, that means the guessed key exchange - * packet (if any) is officially wrong. - */ - if ((i == KEXLIST_KEX || i == KEXLIST_HOSTKEY) && !(cfirst || sfirst)) - guess_correct = false; - } - - /* - * Skip language strings in both KEXINITs, and read the flags - * saying whether a guessed KEX packet follows. - */ - get_string(client); - get_string(client); - get_string(server); - get_string(server); - if (ignore_guess_cs_packet) - *ignore_guess_cs_packet = get_bool(client) && !guess_correct; - if (ignore_guess_sc_packet) - *ignore_guess_sc_packet = get_bool(server) && !guess_correct; - - /* - * Now transcribe the selected algorithm set into the output data. - */ - for (i = 0; i < NKEXLIST; i++) { - const struct kexinit_algorithm *alg; - - /* - * If we've already selected a cipher which requires a - * particular MAC, then just select that. This is the case in - * which it's not a fatal error if the actual MAC string lists - * didn't include any matching error. - */ - if (i == KEXLIST_CSMAC && cs->cipher && - cs->cipher->required_mac) { - cs->mac = cs->cipher->required_mac; - cs->etm_mode = !!(cs->mac->etm_name); - continue; - } - if (i == KEXLIST_SCMAC && sc->cipher && - sc->cipher->required_mac) { - sc->mac = sc->cipher->required_mac; - sc->etm_mode = !!(sc->mac->etm_name); - continue; - } - - alg = selected[i]; - if (!alg) { - /* - * Otherwise, any match failure _is_ a fatal error. - */ - ScanKexinitsResult skr = { - .success = false, .error = SKR_UNKNOWN_ID, - .kind = kexlist_descr[i], .desc = slists[i], - }; - return skr; - } - - switch (i) { - case KEXLIST_KEX: - *kex_alg = alg->u.kex.kex; - *warn_kex = alg->u.kex.warn; - break; - - case KEXLIST_HOSTKEY: - /* - * Ignore an unexpected/inappropriate offer of "null", - * we offer "null" when we're willing to use GSS KEX, - * but it is only acceptable when GSSKEX is actually - * selected. - */ - if (alg->u.hk.hostkey == NULL && - (*kex_alg)->main_type != KEXTYPE_GSS) - continue; - - *hostkey_alg = alg->u.hk.hostkey; - *hkflags = alg->u.hk.hkflags; - *warn_hk = alg->u.hk.warn; - break; - - case KEXLIST_CSCIPHER: - cs->cipher = alg->u.cipher.cipher; - *warn_cscipher = alg->u.cipher.warn; - break; - - case KEXLIST_SCCIPHER: - sc->cipher = alg->u.cipher.cipher; - *warn_sccipher = alg->u.cipher.warn; - break; - - case KEXLIST_CSMAC: - cs->mac = alg->u.mac.mac; - cs->etm_mode = alg->u.mac.etm; - break; - - case KEXLIST_SCMAC: - sc->mac = alg->u.mac.mac; - sc->etm_mode = alg->u.mac.etm; - break; - - case KEXLIST_CSCOMP: - cs->comp = alg->u.comp.comp; - cs->comp_delayed = alg->u.comp.delayed; - break; - - case KEXLIST_SCCOMP: - sc->comp = alg->u.comp.comp; - sc->comp_delayed = alg->u.comp.delayed; - break; - - default: - unreachable("Bad list index in scan_kexinits"); - } - } - - /* - * Check whether the other side advertised support for EXT_INFO. - */ - if (kexinit_keyword_found( - we_are_server ? clists[KEXLIST_KEX] : slists[KEXLIST_KEX], - we_are_server ? ext_info_c : ext_info_s)) - *can_send_ext_info = true; - - /* - * Check whether the other side advertised support for kex-strict. - */ - if (first_time && kexinit_keyword_found( - we_are_server ? clists[KEXLIST_KEX] : slists[KEXLIST_KEX], - we_are_server ? kex_strict_c : kex_strict_s)) - *strict_kex = true; - - if (server_hostkeys) { - /* - * Finally, make an auxiliary pass over the server's host key - * list to find all the host key algorithms offered by the - * server which we know about at all, whether we selected each - * one or not. We return these as a list of indices into the - * constant ssh2_hostkey_algs[] array. - */ - ptrlen list = slists[KEXLIST_HOSTKEY]; - for (ptrlen word; get_commasep_word(&list, &word) ;) { - for (i = 0; i < lenof(ssh2_hostkey_algs); i++) - if (ptrlen_eq_string(word, ssh2_hostkey_algs[i].alg->ssh_id)) { - sgrowarray(server_hostkeys->indices, server_hostkeys->size, - server_hostkeys->n); - server_hostkeys->indices[server_hostkeys->n++] = i; - break; - } - } - } - - ScanKexinitsResult skr = { .success = true }; - return skr; -} - -static void ssh2_report_scan_kexinits_error(Ssh *ssh, ScanKexinitsResult skr) -{ - assert(!skr.success); - - switch (skr.error) { - case SKR_INCOMPLETE: - /* Report a better error than the spurious "Couldn't - * agree" that we'd generate if we pressed on regardless - * and treated the empty get_string() result as genuine */ - ssh_proto_error(ssh, "KEXINIT packet was incomplete"); - break; - case SKR_UNKNOWN_ID: - ssh_sw_abort(ssh, "Selected %s \"%.*s\" does not correspond to " - "any supported algorithm", - skr.kind, PTRLEN_PRINTF(skr.desc)); - break; - case SKR_NO_AGREEMENT: - ssh_sw_abort(ssh, "Couldn't agree a %s (available: %.*s)", - skr.kind, PTRLEN_PRINTF(skr.desc)); - break; - default: - unreachable("bad ScanKexinitsResult"); - } -} - -static inline bool delay_outgoing_kexinit(struct ssh2_transport_state *s) -{ - if (!(s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT)) - return false; /* bug flag not enabled => no need to delay */ - if (s->incoming_kexinit->len) - return false; /* already got a remote KEXINIT we can filter against */ - return true; -} - -static void filter_outgoing_kexinit(struct ssh2_transport_state *s) -{ - strbuf *pktout = strbuf_new(); - BinarySource osrc[1], isrc[1]; - BinarySource_BARE_INIT( - osrc, s->outgoing_kexinit->u, s->outgoing_kexinit->len); - BinarySource_BARE_INIT( - isrc, s->incoming_kexinit->u, s->incoming_kexinit->len); - - /* Skip the packet type bytes from both packets */ - get_byte(osrc); - get_byte(isrc); - - /* Copy our cookie into the real output packet; skip their cookie */ - put_datapl(pktout, get_data(osrc, 16)); - get_data(isrc, 16); - - /* - * Now we expect NKEXLIST+2 name-lists. We write into the outgoing - * packet a subset of our intended outgoing one, containing only - * names mentioned in the incoming out. - * - * NKEXLIST+2 because for this purpose we treat the 'languages' - * lists the same as the rest. In the rest of this code base we - * ignore those. - */ - strbuf *out = strbuf_new(); - for (size_t i = 0; i < NKEXLIST+2; i++) { - strbuf_clear(out); - ptrlen olist = get_string(osrc), ilist = get_string(isrc); - for (ptrlen oword; get_commasep_word(&olist, &oword) ;) { - ptrlen searchword = oword; - ptrlen ilist_copy = ilist; - - /* - * Special case: the kex_strict keywords are - * asymmetrically named, so if we're contemplating - * including one of them in our filtered KEXINIT, we - * should search the other side's KEXINIT for the _other_ - * one, not the same one. - */ - if (i == KEXLIST_KEX) { - if (ptrlen_eq_ptrlen(oword, kex_strict_c)) - searchword = kex_strict_s; - else if (ptrlen_eq_ptrlen(oword, kex_strict_s)) - searchword = kex_strict_c; - } - - bool add = false; - for (ptrlen iword; get_commasep_word(&ilist_copy, &iword) ;) { - if (ptrlen_eq_ptrlen(searchword, iword)) { - /* Found this word in the incoming list. */ - add = true; - break; - } - } - - if (i == KEXLIST_KEX && ptrlen_eq_string(oword, "ext-info-c")) { - /* Special case: this will _never_ match anything from the - * server, and we need it to enable SHA-2 based RSA. - * - * If this ever turns out to confuse any server all by - * itself then I suppose we'll need an even more - * draconian bug flag to exclude that too. (Obv, such - * a server wouldn't be able to speak SHA-2 RSA - * anyway.) */ - add = true; - } - - if (add) - add_to_commasep_pl(out, oword); - } - put_stringpl(pktout, ptrlen_from_strbuf(out)); - } - strbuf_free(out); - - /* - * Finally, copy the remaining parts of our intended KEXINIT. - */ - put_bool(pktout, get_bool(osrc)); /* first-kex-packet-follows */ - put_uint32(pktout, get_uint32(osrc)); /* reserved word */ - - /* - * Dump this data into s->outgoing_kexinit in place of what we had - * there before. We need to remember the KEXINIT we _really_ sent, - * not the one we'd have liked to send, since the host key - * signature will be validated against the former. - */ - strbuf_shrink_to(s->outgoing_kexinit, 1); /* keep the type byte */ - put_datapl(s->outgoing_kexinit, ptrlen_from_strbuf(pktout)); - strbuf_free(pktout); -} - -void ssh2transport_finalise_exhash(struct ssh2_transport_state *s) -{ - put_datapl(s->exhash, ptrlen_from_strbuf(s->kex_shared_secret)); - assert(ssh_hash_alg(s->exhash)->hlen <= sizeof(s->exchange_hash)); - ssh_hash_final(s->exhash, s->exchange_hash); - s->exhash = NULL; - -#if 0 - debug("Exchange hash is:\n"); - dmemdump(s->exchange_hash, s->kex_alg->hash->hlen); -#endif -} - -static void ssh2_transport_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - PktIn *pktin; - PktOut *pktout; - - /* Filter centrally handled messages off the front of the queue on - * every entry to this coroutine, no matter where we're resuming - * from, even if we're _not_ looping on pq_pop. That way we can - * still proactively handle those messages even if we're waiting - * for a user response. */ - if (ssh2_transport_filter_queue(s)) - return; /* we've been freed */ - - crBegin(s->crState); - - s->in.cipher = s->out.cipher = NULL; - s->in.mac = s->out.mac = NULL; - s->in.comp = s->out.comp = NULL; - - s->got_session_id = false; - s->need_gss_transient_hostkey = false; - s->warned_about_no_gss_transient_hostkey = false; - - begin_key_exchange: - -#ifndef NO_GSSAPI - if (s->need_gss_transient_hostkey) { - /* - * This flag indicates a special case in which we must not do - * GSS key exchange even if we could. (See comments below, - * where the flag was set on the previous key exchange.) - */ - s->can_gssapi_keyex = false; - } else if (conf_get_bool(s->conf, CONF_try_gssapi_kex)) { - /* - * We always check if we have GSS creds before we come up with - * the kex algorithm list, otherwise future rekeys will fail - * when creds expire. To make this so, this code section must - * follow the begin_key_exchange label above, otherwise this - * section would execute just once per-connection. - * - * Update GSS state unless the reason we're here is that a - * timer just checked the GSS state and decided that we should - * rekey to update delegated credentials. In that case, the - * state is "fresh". - */ - if (s->rekey_class != RK_GSS_UPDATE) - ssh2_transport_gss_update(s, true); - - /* Do GSSAPI KEX when capable */ - s->can_gssapi_keyex = s->gss_status & GSS_KEX_CAPABLE; - - /* - * But not when failure is likely. [ GSS implementations may - * attempt (and fail) to use a ticket that is almost expired - * when retrieved from the ccache that actually expires by the - * time the server receives it. ] - * - * Note: The first time always try KEXGSS if we can, failures - * will be very rare, and disabling the initial GSS KEX is - * worse. Some day GSS libraries will ignore cached tickets - * whose lifetime is critically short, and will instead use - * fresh ones. - */ - if (!s->got_session_id && (s->gss_status & GSS_CTXT_MAYFAIL) != 0) - s->can_gssapi_keyex = false; - s->gss_delegate = conf_get_bool(s->conf, CONF_gssapifwd); - } else { - s->can_gssapi_keyex = false; - } -#endif - - s->ppl.bpp->pls->kctx = SSH2_PKTCTX_NOKEX; - - /* - * Construct our KEXINIT packet, in a strbuf so we can refer to it - * later. - */ - strbuf_clear(s->outgoing_kexinit); - put_byte(s->outgoing_kexinit, SSH2_MSG_KEXINIT); - random_read(strbuf_append(s->outgoing_kexinit, 16), 16); - ssh2_write_kexinit_lists( - BinarySink_UPCAST(s->outgoing_kexinit), s->kexlists, - s->conf, s->ssc, s->ppl.remote_bugs, - s->savedhost, s->savedport, s->hostkey_alg, s->thc, s->host_cas, - s->hostkeys, s->nhostkeys, - !s->got_session_id, s->can_gssapi_keyex, - s->gss_kex_used && !s->need_gss_transient_hostkey); - /* First KEX packet does _not_ follow, because we're not that brave. */ - put_bool(s->outgoing_kexinit, false); - put_uint32(s->outgoing_kexinit, 0); /* reserved */ - - /* - * Send our KEXINIT, most of the time. - * - * An exception: in BUG_REQUIRES_FILTERED_KEXINIT mode, we have to - * have seen at least one KEXINIT from the server first, so that - * we can filter our own KEXINIT down to contain only algorithms - * the server mentioned. - * - * But we only need to do this on the _first_ key exchange, when - * we've never seen a KEXINIT from the server before. In rekeys, - * we still have the server's previous KEXINIT lying around, so we - * can filter based on that. - * - * (And a good thing too, since the way you _initiate_ a rekey is - * by sending your KEXINIT, so we'd have no way to prod the server - * into sending its first!) - */ - s->kexinit_delayed = delay_outgoing_kexinit(s); - if (!s->kexinit_delayed) { - if (s->ppl.remote_bugs & BUG_REQUIRES_FILTERED_KEXINIT) { - /* Filter based on the KEXINIT from the previous exchange */ - filter_outgoing_kexinit(s); - } - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); - put_data(pktout, s->outgoing_kexinit->u + 1, - s->outgoing_kexinit->len - 1); /* omit type byte */ - pq_push(s->ppl.out_pq, pktout); - } - - /* - * Flag that KEX is in progress. - */ - s->kex_in_progress = true; - - /* - * Wait for the other side's KEXINIT, and save it. - */ - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_KEXINIT) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting KEXINIT, type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, pktin->type)); - return; - } - strbuf_clear(s->incoming_kexinit); - put_byte(s->incoming_kexinit, SSH2_MSG_KEXINIT); - put_data(s->incoming_kexinit, get_ptr(pktin), get_avail(pktin)); - - /* - * If we've delayed sending our KEXINIT so as to filter it down to - * only things the server won't choke on, send ours now. - */ - if (s->kexinit_delayed) { - filter_outgoing_kexinit(s); - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEXINIT); - put_data(pktout, s->outgoing_kexinit->u + 1, - s->outgoing_kexinit->len - 1); /* omit type byte */ - pq_push(s->ppl.out_pq, pktout); - } - - /* - * Work through the two KEXINIT packets in parallel to find the - * selected algorithm identifiers. - */ - { - struct server_hostkeys hks = { NULL, 0, 0 }; - - ScanKexinitsResult skr = ssh2_scan_kexinits( - ptrlen_from_strbuf(s->client_kexinit), - ptrlen_from_strbuf(s->server_kexinit), s->ssc != NULL, - s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans, - s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher, - &s->warn_sccipher, NULL, &s->ignorepkt, &hks, - &s->hkflags, &s->can_send_ext_info, !s->got_session_id, - &s->strict_kex); - - if (!skr.success) { - sfree(hks.indices); - ssh2_report_scan_kexinits_error(s->ppl.ssh, skr); - return; /* we just called a fatal error function */ - } - - /* - * If we've just turned on strict kex mode, say so, and - * retrospectively fault any pre-KEXINIT extraneous packets. - */ - if (!s->got_session_id && s->strict_kex) { - ppl_logevent("Enabling strict key exchange semantics"); - if (s->seen_non_kexinit) { - ssh_proto_error(s->ppl.ssh, "Received a packet before KEXINIT " - "in strict-kex mode"); - return; - } - } - - /* - * In addition to deciding which host key we're actually going - * to use, we should make a list of the host keys offered by - * the server which we _don't_ have cached. These will be - * offered as cross-certification options by ssh_get_specials. - * - * We also count the key we're currently using for KEX as one - * we've already got, because by the time this menu becomes - * visible, it will be. - */ - s->n_uncert_hostkeys = 0; - - for (int i = 0; i < hks.n; i++) { - int j = hks.indices[i]; - if (ssh2_hostkey_algs[j].alg != s->hostkey_alg && - ssh2_hostkey_algs[j].alg->cache_id && - !have_ssh_host_key(s->savedhost, s->savedport, - ssh2_hostkey_algs[j].alg->cache_id)) { - s->uncert_hostkeys[s->n_uncert_hostkeys++] = j; - } - } - - sfree(hks.indices); - } - - if (s->warn_kex) { - s->spr = ssh2_transport_confirm_weak_crypto_primitive( - s, "key-exchange algorithm", s->kex_alg->name, s->kex_alg, - WCR_BELOW_THRESHOLD); - crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); - if (spr_is_abort(s->spr)) { - ssh_spr_close(s->ppl.ssh, s->spr, "kex warning"); - return; - } - } - - if (s->warn_hk) { - int j, k; - const char **betteralgs = NULL; - size_t nbetter = 0, bettersize = 0; - - /* - * Change warning box wording depending on why we chose a - * warning-level host key algorithm. If it's because - * that's all we have *cached*, list the host keys we - * could usefully cross-certify. Otherwise, use the same - * standard wording as any other weak crypto primitive. - */ - for (j = 0; j < s->n_uncert_hostkeys; j++) { - const struct ssh_signkey_with_user_pref_id *hktype = - &ssh2_hostkey_algs[s->uncert_hostkeys[j]]; - bool better = false; - for (k = 0; k < HK_MAX; k++) { - int id = conf_get_int_int(s->conf, CONF_ssh_hklist, k); - if (id == HK_WARN) { - break; - } else if (id == hktype->id) { - better = true; - break; - } - } - if (better) { - sgrowarray(betteralgs, bettersize, nbetter); - betteralgs[nbetter++] = hktype->alg->ssh_id; - } - } - if (betteralgs) { - /* Use the special warning prompt that lets us provide - * a list of better algorithms */ - sgrowarray(betteralgs, bettersize, nbetter); - betteralgs[nbetter] = NULL; - s->spr = confirm_weak_cached_hostkey( - ppl_get_iseat(&s->ppl), s->hostkey_alg->ssh_id, betteralgs, - ssh2_transport_dialog_callback, s); - sfree(betteralgs); - } else { - /* If none exist, use the more general 'weak crypto' - * warning prompt */ - s->spr = ssh2_transport_confirm_weak_crypto_primitive( - s, "host key type", s->hostkey_alg->ssh_id, - s->hostkey_alg, WCR_BELOW_THRESHOLD); - } - crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); - if (spr_is_abort(s->spr)) { - ssh_spr_close(s->ppl.ssh, s->spr, "host key warning"); - return; - } - } - - if (s->warn_cscipher) { - s->spr = ssh2_transport_confirm_weak_crypto_primitive( - s, "client-to-server cipher", s->out.cipher->ssh2_id, - s->out.cipher, WCR_BELOW_THRESHOLD); - crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); - if (spr_is_abort(s->spr)) { - ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning"); - return; - } - } - - if (s->warn_sccipher) { - s->spr = ssh2_transport_confirm_weak_crypto_primitive( - s, "server-to-client cipher", s->in.cipher->ssh2_id, - s->in.cipher, WCR_BELOW_THRESHOLD); - crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); - if (spr_is_abort(s->spr)) { - ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning"); - return; - } - } - - { - s->terrapin.csvuln = terrapin_vulnerable(s->strict_kex, s->cstrans); - s->terrapin.scvuln = terrapin_vulnerable(s->strict_kex, s->sctrans); - s->terrapin.wcr = WCR_TERRAPIN; - - if (s->terrapin.csvuln || s->terrapin.scvuln) { - ppl_logevent("SSH connection is vulnerable to 'Terrapin' attack " - "(CVE-2023-48795)"); - if (try_to_avoid_terrapin(s)) - s->terrapin.wcr = WCR_TERRAPIN_AVOIDABLE; - } - - if (s->terrapin.csvuln) { - s->spr = ssh2_transport_confirm_weak_crypto_primitive( - s, "client-to-server cipher", s->terrapin.csvuln, - terrapin_weakness, s->terrapin.wcr); - crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); - if (spr_is_abort(s->spr)) { - ssh_spr_close(s->ppl.ssh, s->spr, "vulnerability warning"); - return; - } - } - - if (s->terrapin.scvuln) { - s->spr = ssh2_transport_confirm_weak_crypto_primitive( - s, "server-to-client cipher", s->terrapin.scvuln, - terrapin_weakness, s->terrapin.wcr); - crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE); - if (spr_is_abort(s->spr)) { - ssh_spr_close(s->ppl.ssh, s->spr, "vulnerability warning"); - return; - } - } - - if (s->terrapin.csvuln || s->terrapin.scvuln) { - ppl_logevent("Continuing despite 'Terrapin' vulnerability, " - "at user request"); - } - } - - /* - * If the other side has sent an initial key exchange packet that - * we must treat as a wrong guess, wait for it, and discard it. - */ - if (s->ignorepkt) - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - - /* - * Actually perform the key exchange. - */ - s->exhash = ssh_hash_new(s->kex_alg->hash); - if (s->kex_shared_secret) - strbuf_free(s->kex_shared_secret); - s->kex_shared_secret = strbuf_new_nm(); - put_stringz(s->exhash, s->client_greeting); - put_stringz(s->exhash, s->server_greeting); - put_string(s->exhash, s->client_kexinit->u, s->client_kexinit->len); - put_string(s->exhash, s->server_kexinit->u, s->server_kexinit->len); - s->crStateKex = 0; - while (1) { - bool aborted = false; - ssh2kex_coroutine(s, &aborted); - if (aborted) - return; /* disaster: our entire state has been freed */ - if (!s->crStateKex) - break; /* kex phase has terminated normally */ - crReturnV; - } - - /* - * 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(s->session_id)); - memcpy(s->session_id, s->exchange_hash, sizeof(s->exchange_hash)); - s->session_id_len = s->kex_alg->hash->hlen; - assert(s->session_id_len <= sizeof(s->session_id)); - s->got_session_id = true; - } - - /* - * Send SSH2_MSG_NEWKEYS. - */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_NEWKEYS); - pq_push(s->ppl.out_pq, pktout); - /* Start counting down the outgoing-data limit for these cipher keys. */ - dts_reset(&s->stats->out, s->max_data_size); - - /* - * Force the BPP to synchronously marshal all packets up to and - * including that NEWKEYS into wire format, before we switch over - * to new crypto. - */ - ssh_bpp_handle_output(s->ppl.bpp); - - /* - * We've sent outgoing NEWKEYS, so create and initialise outgoing - * session keys. - */ - { - strbuf *cipher_key = strbuf_new_nm(); - strbuf *cipher_iv = strbuf_new_nm(); - strbuf *mac_key = strbuf_new_nm(); - - if (s->out.cipher) { - ssh2_mkkey(s, cipher_iv, s->kex_shared_secret, s->exchange_hash, - 'A' + s->out.mkkey_adjust, s->out.cipher->blksize); - ssh2_mkkey(s, cipher_key, s->kex_shared_secret, s->exchange_hash, - 'C' + s->out.mkkey_adjust, - s->out.cipher->padded_keybytes); - } - if (s->out.mac) { - ssh2_mkkey(s, mac_key, s->kex_shared_secret, s->exchange_hash, - 'E' + s->out.mkkey_adjust, s->out.mac->keylen); - } - - ssh2_bpp_new_outgoing_crypto( - s->ppl.bpp, - s->out.cipher, cipher_key->u, cipher_iv->u, - s->out.mac, s->out.etm_mode, mac_key->u, - s->out.comp, s->out.comp_delayed, - s->strict_kex); - s->enabled_outgoing_crypto = true; - - strbuf_free(cipher_key); - strbuf_free(cipher_iv); - strbuf_free(mac_key); - } - - /* - * If that was our first key exchange, this is the moment to send - * our EXT_INFO, if we're sending one. - */ - if (!s->post_newkeys_ext_info) { - s->post_newkeys_ext_info = true; /* never do this again */ - if (s->can_send_ext_info) { - strbuf *extinfo = strbuf_new(); - uint32_t n_exts = 0; - - if (s->ssc) { - /* Server->client EXT_INFO lists our supported user - * key algorithms. */ - n_exts++; - put_stringz(extinfo, "server-sig-algs"); - strbuf *list = strbuf_new(); - for (size_t i = 0; i < n_keyalgs; i++) - add_to_commasep(list, all_keyalgs[i]->ssh_id); - put_stringsb(extinfo, list); - } else { - /* Client->server EXT_INFO is currently not sent, but here's - * where we should put things in it if we ever want to. */ - } - - /* Only send EXT_INFO if it's non-empty */ - if (n_exts) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_EXT_INFO); - put_uint32(pktout, n_exts); - put_datapl(pktout, ptrlen_from_strbuf(extinfo)); - pq_push(s->ppl.out_pq, pktout); - } - - strbuf_free(extinfo); - } - } - - /* - * Now our end of the key exchange is complete, we can send all - * our queued higher-layer packets. Transfer the whole of the next - * layer's outgoing queue on to our own. - */ - pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); - ssh_sendbuffer_changed(s->ppl.ssh); - - /* - * Expect SSH2_MSG_NEWKEYS from server. - */ - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_NEWKEYS) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting SSH_MSG_NEWKEYS, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return; - } - /* Start counting down the incoming-data limit for these cipher keys. */ - dts_reset(&s->stats->in, s->max_data_size); - - /* - * We've seen incoming NEWKEYS, so create and initialise - * incoming session keys. - */ - { - strbuf *cipher_key = strbuf_new_nm(); - strbuf *cipher_iv = strbuf_new_nm(); - strbuf *mac_key = strbuf_new_nm(); - - if (s->in.cipher) { - ssh2_mkkey(s, cipher_iv, s->kex_shared_secret, s->exchange_hash, - 'A' + s->in.mkkey_adjust, s->in.cipher->blksize); - ssh2_mkkey(s, cipher_key, s->kex_shared_secret, s->exchange_hash, - 'C' + s->in.mkkey_adjust, - s->in.cipher->padded_keybytes); - } - if (s->in.mac) { - ssh2_mkkey(s, mac_key, s->kex_shared_secret, s->exchange_hash, - 'E' + s->in.mkkey_adjust, s->in.mac->keylen); - } - - ssh2_bpp_new_incoming_crypto( - s->ppl.bpp, - s->in.cipher, cipher_key->u, cipher_iv->u, - s->in.mac, s->in.etm_mode, mac_key->u, - s->in.comp, s->in.comp_delayed, - s->strict_kex); - s->enabled_incoming_crypto = true; - - strbuf_free(cipher_key); - strbuf_free(cipher_iv); - strbuf_free(mac_key); - } - - /* - * Free shared secret. - */ - strbuf_free(s->kex_shared_secret); - s->kex_shared_secret = NULL; - - /* - * Update the specials menu to list the remaining uncertified host - * keys. - */ - seat_update_specials_menu(s->ppl.seat); - - /* - * Key exchange is over. Loop straight back round if we have a - * deferred rekey reason. - */ - if (s->deferred_rekey_reason) { - ppl_logevent("%s", s->deferred_rekey_reason); - pktin = NULL; - s->deferred_rekey_reason = NULL; - goto begin_key_exchange; - } - - /* - * Otherwise, schedule a timer for our next rekey. - */ - s->kex_in_progress = false; - s->last_rekey = GETTICKCOUNT(); - (void) ssh2_transport_timer_update(s, 0); - - /* - * Now we're encrypting. Get the next-layer protocol started if it - * hasn't already, and then sit here waiting for reasons to go - * back to the start and do a repeat key exchange. One of those - * reasons is that we receive KEXINIT from the other end; the - * other is if we find rekey_reason is non-NULL, i.e. we've - * decided to initiate a rekey ourselves for some reason. - */ - if (!s->higher_layer_ok) { - if (!s->hostkeys) { - /* We're the client, so send SERVICE_REQUEST. */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_REQUEST); - put_stringz(pktout, s->higher_layer->vt->name); - pq_push(s->ppl.out_pq, pktout); - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) { - ssh_sw_abort(s->ppl.ssh, "Server refused request to start " - "'%s' protocol", s->higher_layer->vt->name); - return; - } - } else { - ptrlen service_name; - - /* We're the server, so expect SERVICE_REQUEST. */ - crMaybeWaitUntilV((pktin = ssh2_transport_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_SERVICE_REQUEST) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting SERVICE_REQUEST, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return; - } - service_name = get_string(pktin); - if (!ptrlen_eq_string(service_name, s->higher_layer->vt->name)) { - ssh_proto_error(s->ppl.ssh, "Client requested service " - "'%.*s' when we only support '%s'", - PTRLEN_PRINTF(service_name), - s->higher_layer->vt->name); - return; - } - - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_SERVICE_ACCEPT); - put_stringz(pktout, s->higher_layer->vt->name); - pq_push(s->ppl.out_pq, pktout); - } - - s->higher_layer_ok = true; - queue_idempotent_callback(&s->higher_layer->ic_process_queue); - } - - s->rekey_class = RK_NONE; - do { - crReturnV; - - /* Pass through outgoing packets from the higher layer. */ - pq_concatenate(s->ppl.out_pq, s->ppl.out_pq, &s->pq_out_higher); - ssh_sendbuffer_changed(s->ppl.ssh); - - /* Wait for either a KEXINIT, or something setting - * s->rekey_class. This call to ssh2_transport_pop also has - * the side effect of transferring incoming packets _to_ the - * higher layer (via filter_queue). */ - if ((pktin = ssh2_transport_pop(s)) != NULL) { - if (pktin->type != SSH2_MSG_KEXINIT) { - ssh_proto_error(s->ppl.ssh, "Received unexpected transport-" - "layer packet outside a key exchange, " - "type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return; - } - pq_push_front(s->ppl.in_pq, pktin); - ppl_logevent("Remote side initiated key re-exchange"); - s->rekey_class = RK_SERVER; - } - - if (s->rekey_class == RK_POST_USERAUTH) { - /* - * userauth has seen a USERAUTH_SUCCESS. This may be the - * moment to do an immediate rekey with different - * parameters. But it may not; so here we turn that rekey - * class into either RK_NONE or RK_NORMAL. - * - * Currently the only reason for this is if we've done a - * GSS key exchange and don't have anything in our - * transient hostkey cache, in which case we should make - * an attempt to populate the cache now. - */ - if (s->need_gss_transient_hostkey) { - s->rekey_reason = "populating transient host key cache"; - s->rekey_class = RK_NORMAL; - } else { - /* No need to rekey at this time. */ - s->rekey_class = RK_NONE; - } - } - - if (!s->rekey_class) { - /* If we don't yet have any other reason to rekey, check - * if we've hit our data limit in either direction. */ - if (s->stats->in.expired) { - s->rekey_reason = "too much data received"; - s->rekey_class = RK_NORMAL; - } else if (s->stats->out.expired) { - s->rekey_reason = "too much data sent"; - s->rekey_class = RK_NORMAL; - } - } - - if (s->rekey_class != RK_NONE && s->rekey_class != RK_SERVER) { - /* - * 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 ((s->ppl.remote_bugs & BUG_SSH2_REKEY)) { - ppl_logevent("Remote bug prevents key re-exchange (%s)", - s->rekey_reason); - /* Reset the counters, so that at least this message doesn't - * hit the event log _too_ often. */ - dts_reset(&s->stats->in, s->max_data_size); - dts_reset(&s->stats->out, s->max_data_size); - (void) ssh2_transport_timer_update(s, 0); - s->rekey_class = RK_NONE; - } else { - ppl_logevent("Initiating key re-exchange (%s)", - s->rekey_reason); - } - } - } while (s->rekey_class == RK_NONE); - - /* Once we exit the above loop, we really are rekeying. */ - goto begin_key_exchange; - - crFinishV; -} - -static void ssh2_transport_higher_layer_packet_callback(void *context) -{ - PacketProtocolLayer *ppl = (PacketProtocolLayer *)context; - ssh_ppl_process_queue(ppl); -} - -static void ssh2_transport_timer(void *ctx, unsigned long now) -{ - struct ssh2_transport_state *s = (struct ssh2_transport_state *)ctx; - unsigned long mins; - unsigned long ticks; - - if (s->kex_in_progress || now != s->next_rekey) - return; - - mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60); - if (mins == 0) - return; - - /* Rekey if enough time has elapsed */ - ticks = mins * 60 * TICKSPERSEC; - if (now - s->last_rekey > ticks - 30*TICKSPERSEC) { - s->rekey_reason = "timeout"; - s->rekey_class = RK_NORMAL; - queue_idempotent_callback(&s->ppl.ic_process_queue); - return; - } - -#ifndef NO_GSSAPI - /* - * Rekey now if we have a new cred or context expires this cycle, - * but not if this is unsafe. - */ - if (conf_get_int(s->conf, CONF_gssapirekey)) { - ssh2_transport_gss_update(s, false); - if ((s->gss_status & GSS_KEX_CAPABLE) != 0 && - (s->gss_status & GSS_CTXT_MAYFAIL) == 0 && - (s->gss_status & (GSS_CRED_UPDATED|GSS_CTXT_EXPIRES)) != 0) { - s->rekey_reason = "GSS credentials updated"; - s->rekey_class = RK_GSS_UPDATE; - queue_idempotent_callback(&s->ppl.ic_process_queue); - return; - } - } -#endif - - /* Try again later. */ - (void) ssh2_transport_timer_update(s, 0); -} - -/* - * The rekey_time is zero except when re-configuring. - * - * We either schedule the next timer and return false, or return true - * to run the callback now, which will call us again to re-schedule on - * completion. - */ -static bool ssh2_transport_timer_update(struct ssh2_transport_state *s, - unsigned long rekey_time) -{ - unsigned long mins; - unsigned long ticks; - - mins = sanitise_rekey_time(conf_get_int(s->conf, CONF_ssh_rekey_time), 60); - ticks = mins * 60 * TICKSPERSEC; - - /* Handle change from previous setting */ - if (rekey_time != 0 && rekey_time != mins) { - unsigned long next; - unsigned long now = GETTICKCOUNT(); - - mins = rekey_time; - ticks = mins * 60 * TICKSPERSEC; - next = s->last_rekey + ticks; - - /* If overdue, caller will rekey synchronously now */ - if (now - s->last_rekey > ticks) - return true; - ticks = next - now; - } - -#ifndef NO_GSSAPI - if (s->gss_kex_used) { - /* - * If we've used GSSAPI key exchange, then we should - * periodically check whether we need to do another one to - * pass new credentials to the server. - */ - unsigned long gssmins; - - /* Check cascade conditions more frequently if configured */ - gssmins = sanitise_rekey_time( - conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS); - if (gssmins > 0) { - if (gssmins < mins) - ticks = (mins = gssmins) * 60 * TICKSPERSEC; - - if ((s->gss_status & GSS_KEX_CAPABLE) != 0) { - /* - * Run next timer even sooner if it would otherwise be - * too close to the context expiration time - */ - if ((s->gss_status & GSS_CTXT_EXPIRES) == 0 && - s->gss_ctxt_lifetime - mins * 60 < 2 * MIN_CTXT_LIFETIME) - ticks -= 2 * MIN_CTXT_LIFETIME * TICKSPERSEC; - } - } - } -#endif - - /* Schedule the next timer */ - s->next_rekey = schedule_timer(ticks, ssh2_transport_timer, s); - return false; -} - -void ssh2_transport_dialog_callback(void *vctx, SeatPromptResult spr) -{ - struct ssh2_transport_state *s = (struct ssh2_transport_state *)vctx; - s->spr = spr; - ssh_ppl_process_queue(&s->ppl); -} - -#ifndef NO_GSSAPI -/* - * This is called at the beginning of each SSH rekey to determine - * whether we are GSS capable, and if we did GSS key exchange, and are - * delegating credentials, it is also called periodically to determine - * whether we should rekey in order to delegate (more) fresh - * credentials. This is called "credential cascading". - * - * On Windows, with SSPI, we may not get the credential expiration, as - * Windows automatically renews from cached passwords, so the - * credential effectively never expires. Since we still want to - * cascade when the local TGT is updated, we use the expiration of a - * newly obtained context as a proxy for the expiration of the TGT. - */ -static void ssh2_transport_gss_update(struct ssh2_transport_state *s, - bool definitely_rekeying) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - int gss_stat; - time_t gss_cred_expiry; - unsigned long mins; - Ssh_gss_buf gss_sndtok; - Ssh_gss_buf gss_rcvtok; - Ssh_gss_ctx gss_ctx; - - s->gss_status = 0; - - /* - * Nothing to do if no GSSAPI libraries are configured or GSSAPI - * auth is not enabled. - */ - if (s->shgss->libs->nlibraries == 0) - return; - if (!conf_get_bool(s->conf, CONF_try_gssapi_auth) && - !conf_get_bool(s->conf, CONF_try_gssapi_kex)) - return; - - /* Import server name and cache it */ - if (s->shgss->srv_name == GSS_C_NO_NAME) { - gss_stat = s->shgss->lib->import_name( - s->shgss->lib, s->fullhostname, &s->shgss->srv_name); - if (gss_stat != SSH_GSS_OK) { - if (gss_stat == SSH_GSS_BAD_HOST_NAME) - ppl_logevent("GSSAPI import name failed - Bad service name;" - " won't use GSS key exchange"); - else - ppl_logevent("GSSAPI import name failed;" - " won't use GSS key exchange"); - return; - } - } - - /* - * Do we (still) have credentials? Capture the credential - * expiration when available - */ - gss_stat = s->shgss->lib->acquire_cred( - s->shgss->lib, &gss_ctx, &gss_cred_expiry); - if (gss_stat != SSH_GSS_OK) - return; - - SSH_GSS_CLEAR_BUF(&gss_sndtok); - SSH_GSS_CLEAR_BUF(&gss_rcvtok); - - /* - * When acquire_cred yields no useful expiration, get a proxy for - * the cred expiration from the context expiration. - */ - gss_stat = s->shgss->lib->init_sec_context( - s->shgss->lib, &gss_ctx, s->shgss->srv_name, - 0 /* don't delegate */, &gss_rcvtok, &gss_sndtok, - (gss_cred_expiry == GSS_NO_EXPIRATION ? &gss_cred_expiry : NULL), - &s->gss_ctxt_lifetime); - - /* This context was for testing only. */ - if (gss_ctx) - s->shgss->lib->release_cred(s->shgss->lib, &gss_ctx); - - if (gss_stat != SSH_GSS_OK && - gss_stat != SSH_GSS_S_CONTINUE_NEEDED) { - /* - * No point in verbosely interrupting the user to tell them we - * couldn't get GSS credentials, if this was only a check - * between key exchanges to see if fresh ones were available. - * When we do do a rekey, this message (if displayed) will - * appear among the standard rekey blurb, but when we're not, - * it shouldn't pop up all the time regardless. - */ - if (definitely_rekeying) - ppl_logevent("No GSSAPI security context available"); - - return; - } - - if (gss_sndtok.length) - s->shgss->lib->free_tok(s->shgss->lib, &gss_sndtok); - - s->gss_status |= GSS_KEX_CAPABLE; - - /* - * When rekeying to cascade, avoid doing this too close to the - * context expiration time, since the key exchange might fail. - */ - if (s->gss_ctxt_lifetime < MIN_CTXT_LIFETIME) - s->gss_status |= GSS_CTXT_MAYFAIL; - - /* - * If we're not delegating credentials, rekeying is not used to - * refresh them. We must avoid setting GSS_CRED_UPDATED or - * GSS_CTXT_EXPIRES when credential delegation is disabled. - */ - if (!conf_get_bool(s->conf, CONF_gssapifwd)) - return; - - if (s->gss_cred_expiry != GSS_NO_EXPIRATION && - difftime(gss_cred_expiry, s->gss_cred_expiry) > 0) - s->gss_status |= GSS_CRED_UPDATED; - - mins = sanitise_rekey_time( - conf_get_int(s->conf, CONF_gssapirekey), GSS_DEF_REKEY_MINS); - if (mins > 0 && s->gss_ctxt_lifetime <= mins * 60) - s->gss_status |= GSS_CTXT_EXPIRES; -} -#endif /* NO_GSSAPI */ - -ptrlen ssh2_transport_get_session_id(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s; - - assert(ppl->vt == &ssh2_transport_vtable); - s = container_of(ppl, struct ssh2_transport_state, ppl); - - assert(s->got_session_id); - return make_ptrlen(s->session_id, s->session_id_len); -} - -void ssh2_transport_notify_auth_done(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s; - - assert(ppl->vt == &ssh2_transport_vtable); - s = container_of(ppl, struct ssh2_transport_state, ppl); - - s->rekey_reason = NULL; /* will be filled in later */ - s->rekey_class = RK_POST_USERAUTH; - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -static bool ssh2_transport_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - bool need_separator = false; - bool toret = false; - - if (ssh_ppl_get_specials(s->higher_layer, add_special, ctx)) { - need_separator = true; - toret = true; - } - - /* - * Don't bother offering rekey-based specials if we've decided the - * remote won't cope with it, since we wouldn't bother sending it - * if asked anyway. - */ - if (!(s->ppl.remote_bugs & BUG_SSH2_REKEY)) { - if (need_separator) { - add_special(ctx, NULL, SS_SEP, 0); - need_separator = false; - } - - add_special(ctx, "Repeat key exchange", SS_REKEY, 0); - toret = true; - - if (s->n_uncert_hostkeys) { - int i; - - add_special(ctx, NULL, SS_SEP, 0); - add_special(ctx, "Cache new host key type", SS_SUBMENU, 0); - for (i = 0; i < s->n_uncert_hostkeys; i++) { - const ssh_keyalg *alg = - ssh2_hostkey_algs[s->uncert_hostkeys[i]].alg; - - add_special(ctx, alg->ssh_id, SS_XCERT, s->uncert_hostkeys[i]); - } - add_special(ctx, NULL, SS_EXITMENU, 0); - } - } - - return toret; -} - -static void ssh2_transport_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - if (code == SS_REKEY) { - if (!s->kex_in_progress) { - s->rekey_reason = "at user request"; - s->rekey_class = RK_NORMAL; - queue_idempotent_callback(&s->ppl.ic_process_queue); - } - } else if (code == SS_XCERT) { - if (!s->kex_in_progress) { - s->cross_certifying = s->hostkey_alg = ssh2_hostkey_algs[arg].alg; - s->rekey_reason = "cross-certifying new host key"; - s->rekey_class = RK_NORMAL; - queue_idempotent_callback(&s->ppl.ic_process_queue); - } - } else { - /* Send everything else to the next layer up. This includes - * SS_PING/SS_NOP, which we _could_ handle here - but it's - * better to put them in the connection layer, so they'll - * still work in bare connection mode. */ - ssh_ppl_special_cmd(s->higher_layer, code, arg); - } -} - -/* Safely convert rekey_time to unsigned long minutes */ -static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def) -{ - if (rekey_time < 0 || rekey_time > MAX_TICK_MINS) - rekey_time = def; - return (unsigned long)rekey_time; -} - -static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s) -{ - s->max_data_size = parse_blocksize( - conf_get_str(s->conf, CONF_ssh_rekey_data)); -} - -static void ssh2_transport_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ - struct ssh2_transport_state *s; - const char *rekey_reason = NULL; - bool rekey_mandatory = false; - unsigned long old_max_data_size, rekey_time; - int i; - - assert(ppl->vt == &ssh2_transport_vtable); - s = container_of(ppl, struct ssh2_transport_state, ppl); - - rekey_time = sanitise_rekey_time( - conf_get_int(conf, CONF_ssh_rekey_time), 60); - if (ssh2_transport_timer_update(s, rekey_time)) - rekey_reason = "timeout shortened"; - - old_max_data_size = s->max_data_size; - ssh2_transport_set_max_data_size(s); - if (old_max_data_size != s->max_data_size && - s->max_data_size != 0) { - if (s->max_data_size < old_max_data_size) { - unsigned long diff = old_max_data_size - s->max_data_size; - - dts_consume(&s->stats->out, diff); - dts_consume(&s->stats->in, diff); - if (s->stats->out.expired || s->stats->in.expired) - rekey_reason = "data limit lowered"; - } else { - unsigned long diff = s->max_data_size - old_max_data_size; - if (s->stats->out.running) - s->stats->out.remaining += diff; - if (s->stats->in.running) - s->stats->in.remaining += diff; - } - } - - if (conf_get_bool(s->conf, CONF_compression) != - conf_get_bool(conf, CONF_compression)) { - rekey_reason = "compression setting changed"; - rekey_mandatory = true; - } - - for (i = 0; i < CIPHER_MAX; i++) - if (conf_get_int_int(s->conf, CONF_ssh_cipherlist, i) != - conf_get_int_int(conf, CONF_ssh_cipherlist, i)) { - rekey_reason = "cipher settings changed"; - rekey_mandatory = true; - } - if (conf_get_bool(s->conf, CONF_ssh2_des_cbc) != - conf_get_bool(conf, CONF_ssh2_des_cbc)) { - rekey_reason = "cipher settings changed"; - rekey_mandatory = true; - } - - conf_free(s->conf); - s->conf = conf_copy(conf); - - if (rekey_reason) { - if (!s->kex_in_progress && !ssh2_bpp_rekey_inadvisable(s->ppl.bpp)) { - s->rekey_reason = rekey_reason; - s->rekey_class = RK_NORMAL; - queue_idempotent_callback(&s->ppl.ic_process_queue); - } else if (rekey_mandatory) { - s->deferred_rekey_reason = rekey_reason; - } - } - - /* Also pass the configuration along to our higher layer */ - ssh_ppl_reconfigure(s->higher_layer, conf); -} - -static int weak_algorithm_compare(void *av, void *bv) -{ - uintptr_t a = (uintptr_t)av, b = (uintptr_t)bv; - return a < b ? -1 : a > b ? +1 : 0; -} - -static int ca_blob_compare(void *av, void *bv) -{ - host_ca *a = (host_ca *)av, *b = (host_ca *)bv; - strbuf *apk = a->ca_public_key, *bpk = b->ca_public_key; - /* Ordering by public key is arbitrary here, so do whatever is easiest */ - if (apk->len < bpk->len) - return -1; - if (apk->len > bpk->len) - return +1; - return memcmp(apk->u, bpk->u, apk->len); -} - -/* - * Wrapper on confirm_weak_crypto_primitive(), which uses the - * tree234 s->weak_algorithms_consented_to to ensure we ask at most - * once about any given crypto primitive. - */ -static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive( - struct ssh2_transport_state *s, const char *type, const char *name, - const void *alg, WeakCryptoReason wcr) -{ - if (find234(s->weak_algorithms_consented_to, (void *)alg, NULL)) - return SPR_OK; - add234(s->weak_algorithms_consented_to, (void *)alg); - - return confirm_weak_crypto_primitive( - ppl_get_iseat(&s->ppl), type, name, ssh2_transport_dialog_callback, - s, wcr); -} - -static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - return (ssh_ppl_default_queued_data_size(ppl) + - ssh_ppl_queued_data_size(s->higher_layer)); -} - -static void ssh2_transport_final_output(PacketProtocolLayer *ppl) -{ - struct ssh2_transport_state *s = - container_of(ppl, struct ssh2_transport_state, ppl); - - ssh_ppl_final_output(s->higher_layer); -} - -/* Check the settings for a transport direction to see if they're - * vulnerable to the Terrapin attack, aka CVE-2023-48795. If so, - * return a string describing the vulnerable thing. */ -static const char *terrapin_vulnerable( - bool strict_kex, const transport_direction *d) -{ - /* - * Strict kex mode eliminates the vulnerability. (That's what it's - * for.) - */ - if (strict_kex) - return NULL; - - /* - * ChaCha20-Poly1305 is vulnerable and perfectly exploitable. - */ - if (d->cipher == &ssh2_chacha20_poly1305) - return "ChaCha20-Poly1305"; - - /* - * CBC-mode ciphers with OpenSSH's ETM modification are vulnerable - * and probabilistically exploitable. - */ - if (d->etm_mode && (d->cipher->flags & SSH_CIPHER_IS_CBC)) - return "a CBC-mode cipher in OpenSSH ETM mode"; - - return NULL; -} - -/* - * Called when we've detected that at least one transport direction - * has the Terrapin vulnerability. - * - * Before we report it, try to replay what would have happened if the - * user had reconfigured their cipher settings to demote - * ChaCha20+Poly1305 to below the warning threshold. If that would - * have avoided the vulnerability, we should say so in the dialog box. - * - * This is basically the only change in PuTTY's configuration that has - * a chance of avoiding the problem. Terrapin affects the modified - * binary packet protocol used with ChaCha20+Poly1305, and also - * CBC-mode ciphers in ETM mode. But PuTTY unconditionally offers the - * ETM mode of each MAC _after_ the non-ETM mode. So the latter case - * can only come up if the server has been configured to _only_ permit - * the ETM modes of those MACs, which means there's nothing we can do - * anyway. - */ -static bool try_to_avoid_terrapin(const struct ssh2_transport_state *s) -{ - bool avoidable = false; - - strbuf *alt_client_kexinit = strbuf_new(); - Conf *alt_conf = conf_copy(s->conf); - struct kexinit_algorithm_list alt_kexlists[NKEXLIST]; - memset(alt_kexlists, 0, sizeof(alt_kexlists)); - - /* - * We only bother doing this if we're the client, because Uppity - * can't present a dialog box anyway. - */ - if (s->ssc) - goto out; - - /* - * Demote CIPHER_CHACHA20 to just below CIPHER_WARN, if it was - * previously above it. If not, don't do anything - we don't want - * to _promote_ it. - */ - int ccp_pos_now = -1, ccp_pos_wanted = -1; - for (int i = 0; i < CIPHER_MAX; i++) { - switch (conf_get_int_int(alt_conf, CONF_ssh_cipherlist, - i)) { - case CIPHER_CHACHA20: - ccp_pos_now = i; - break; - case CIPHER_WARN: - ccp_pos_wanted = i; - break; - } - } - if (ccp_pos_now < 0 || ccp_pos_wanted < 0) - goto out; /* shouldn't ever happen: didn't find the two entries */ - if (ccp_pos_now >= ccp_pos_wanted) - goto out; /* ChaCha20 is already demoted and it didn't help */ - while (ccp_pos_now < ccp_pos_wanted) { - int cnext = conf_get_int_int(alt_conf, CONF_ssh_cipherlist, - ccp_pos_now + 1); - conf_set_int_int(alt_conf, CONF_ssh_cipherlist, - ccp_pos_now, cnext); - ccp_pos_now++; - } - conf_set_int_int(alt_conf, CONF_ssh_cipherlist, - ccp_pos_now + 1, CIPHER_CHACHA20); - - /* - * Make the outgoing KEXINIT we would have made using this - * configuration. - */ - put_byte(alt_client_kexinit, SSH2_MSG_KEXINIT); - put_padding(alt_client_kexinit, 16, 'x'); /* fake random padding */ - ssh2_write_kexinit_lists( - BinarySink_UPCAST(alt_client_kexinit), alt_kexlists, alt_conf, - s->ssc, s->ppl.remote_bugs, s->savedhost, s->savedport, s->hostkey_alg, - s->thc, s->host_cas, s->hostkeys, s->nhostkeys, !s->got_session_id, - s->can_gssapi_keyex, - s->gss_kex_used && !s->need_gss_transient_hostkey); - put_bool(alt_client_kexinit, false); /* guess packet follows */ - put_uint32(alt_client_kexinit, 0); /* reserved */ - - /* - * Re-analyse the incoming KEXINIT with respect to this one, to - * see what we'd have decided on. - */ - transport_direction cstrans, sctrans; - bool warn_kex, warn_hk, warn_cscipher, warn_sccipher; - bool can_send_ext_info = false, strict_kex = false; - unsigned hkflags; - const ssh_kex *kex_alg; - const ssh_keyalg *hostkey_alg; - - ScanKexinitsResult skr = ssh2_scan_kexinits( - ptrlen_from_strbuf(alt_client_kexinit), - ptrlen_from_strbuf(s->server_kexinit), - s->ssc != NULL, alt_kexlists, &kex_alg, &hostkey_alg, - &cstrans, &sctrans, - &warn_kex, &warn_hk, &warn_cscipher, &warn_sccipher, NULL, NULL, NULL, - &hkflags, &can_send_ext_info, !s->got_session_id, &strict_kex); - if (!skr.success) /* something else would have gone wrong */ - goto out; - - /* - * Reject this as an alternative solution if any of the warn flags - * has got worse, or if there's still anything - * Terrapin-vulnerable. - */ - if (warn_kex > s->warn_kex) - goto out; - if (warn_hk > s->warn_hk) - goto out; - if (warn_cscipher > s->warn_cscipher) - goto out; - if (warn_sccipher > s->warn_sccipher) - goto out; - if (terrapin_vulnerable(strict_kex, &cstrans)) - goto out; - if (terrapin_vulnerable(strict_kex, &sctrans)) - goto out; - - /* - * Success! The vulnerability could have been avoided by this Conf - * tweak, and we should tell the user so. - */ - avoidable = true; - - out: - - for (size_t i = 0; i < NKEXLIST; i++) - sfree(alt_kexlists[i].algs); - strbuf_free(alt_client_kexinit); - conf_free(alt_conf); - - return avoidable; -} diff --git a/ssh/transport2.h b/ssh/transport2.h deleted file mode 100644 index ea739df9a..000000000 --- a/ssh/transport2.h +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Header connecting the pieces of the SSH-2 transport layer. - */ - -#ifndef PUTTY_SSH2TRANSPORT_H -#define PUTTY_SSH2TRANSPORT_H - -#ifndef NO_GSSAPI -#include "gssc.h" -#include "gss.h" -#define MIN_CTXT_LIFETIME 5 /* Avoid rekey with short lifetime (seconds) */ -#define GSS_KEX_CAPABLE (1<<0) /* Can do GSS KEX */ -#define GSS_CRED_UPDATED (1<<1) /* Cred updated since previous delegation */ -#define GSS_CTXT_EXPIRES (1<<2) /* Context expires before next timer */ -#define GSS_CTXT_MAYFAIL (1<<3) /* Context may expire during handshake */ -#endif - -#define DH_MIN_SIZE 1024 -#define DH_MAX_SIZE 8192 - -struct kexinit_algorithm { - ptrlen name; - union { - struct { - const ssh_kex *kex; - bool warn; - } kex; - struct { - const ssh_keyalg *hostkey; - unsigned hkflags; - bool warn; - } hk; - struct { - const ssh_cipheralg *cipher; - bool warn; - } cipher; - struct { - const ssh2_macalg *mac; - bool etm; - } mac; - struct { - const ssh_compression_alg *comp; - bool delayed; - } comp; - } u; -}; -struct kexinit_algorithm_list { - struct kexinit_algorithm *algs; - size_t nalgs, algsize; -}; - -#define HOSTKEY_ALGORITHMS(X) \ - X(HK_ED25519, ssh_ecdsa_ed25519) \ - X(HK_ED448, ssh_ecdsa_ed448) \ - X(HK_ECDSA, ssh_ecdsa_nistp256) \ - X(HK_ECDSA, ssh_ecdsa_nistp384) \ - X(HK_ECDSA, ssh_ecdsa_nistp521) \ - X(HK_DSA, ssh_dsa) \ - X(HK_RSA, ssh_rsa_sha512) \ - X(HK_RSA, ssh_rsa_sha256) \ - X(HK_RSA, ssh_rsa) \ - X(HK_ED25519, opensshcert_ssh_ecdsa_ed25519) \ - /* OpenSSH defines no certified version of Ed448 */ \ - X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp256) \ - X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp384) \ - X(HK_ECDSA, opensshcert_ssh_ecdsa_nistp521) \ - X(HK_DSA, opensshcert_ssh_dsa) \ - X(HK_RSA, opensshcert_ssh_rsa_sha512) \ - X(HK_RSA, opensshcert_ssh_rsa_sha256) \ - X(HK_RSA, opensshcert_ssh_rsa) \ - /* end of list */ -#define COUNT_HOSTKEY_ALGORITHM(type, alg) +1 -#define N_HOSTKEY_ALGORITHMS (0 HOSTKEY_ALGORITHMS(COUNT_HOSTKEY_ALGORITHM)) - -struct ssh_signkey_with_user_pref_id { - const ssh_keyalg *alg; - int id; -}; -extern const struct ssh_signkey_with_user_pref_id - ssh2_hostkey_algs[N_HOSTKEY_ALGORITHMS]; - -/* - * Enumeration of high-level classes of reason why we might need to do - * a repeat key exchange. A full detailed reason in human-readable - * string form for the Event Log is also provided, but this enum type - * is used to discriminate between classes of reason that the code - * needs to treat differently. - * - * RK_NONE == 0 is the value indicating that no rekey is currently - * needed at all. RK_INITIAL indicates that we haven't even done the - * _first_ key exchange yet. RK_SERVER indicates that we're rekeying - * because the server asked for it, not because we decided it - * ourselves. RK_NORMAL is the usual case. RK_GSS_UPDATE indicates - * that we're rekeying because we've just got new GSSAPI credentials - * (hence there's no point in doing a preliminary check for new GSS - * creds, because we already know the answer); RK_POST_USERAUTH - * indicates that _if_ we're going to need a post-userauth immediate - * rekey for any reason, this is the moment to do it. - * - * So RK_POST_USERAUTH only tells the transport layer to _consider_ - * rekeying, not to definitely do it. Also, that one enum value is - * special in that the user-readable reason text is passed in to the - * transport layer as NULL, whereas fills in the reason text after it - * decides whether it needs a rekey at all. In the other cases, - * rekey_reason is passed in to the at the same time as rekey_class. - */ -typedef enum RekeyClass { - RK_NONE = 0, - RK_INITIAL, - RK_SERVER, - RK_NORMAL, - RK_POST_USERAUTH, - RK_GSS_UPDATE -} RekeyClass; - -typedef struct transport_direction { - const ssh_cipheralg *cipher; - const ssh2_macalg *mac; - bool etm_mode; - const ssh_compression_alg *comp; - bool comp_delayed; - int mkkey_adjust; -} transport_direction; - -struct ssh2_transport_state { - int crState, crStateKex; - - PacketProtocolLayer *higher_layer; - PktInQueue pq_in_higher; - PktOutQueue pq_out_higher; - IdempotentCallback ic_pq_out_higher; - - Conf *conf; - char *savedhost; - int savedport; - const char *rekey_reason; - enum RekeyClass rekey_class; - - unsigned long max_data_size; - - const ssh_kex *kex_alg; - const ssh_keyalg *hostkey_alg; - unsigned char session_id[MAX_HASH_LEN]; - int session_id_len; - int dh_min_size, dh_max_size; - bool dh_got_size_bounds; - dh_ctx *dh_ctx; - ssh_hash *exhash; - - struct DataTransferStats *stats; - - const SshServerConfig *ssc; - - char *client_greeting, *server_greeting; - - bool kex_in_progress, kexinit_delayed; - unsigned long next_rekey, last_rekey; - const char *deferred_rekey_reason; - bool higher_layer_ok; - - /* - * Fully qualified host name, which we need if doing GSSAPI. - */ - char *fullhostname; - - /* shgss is outside the ifdef on purpose to keep APIs simple. If - * NO_GSSAPI is not defined, then it's just an opaque structure - * tag and the pointer will be NULL. */ - struct ssh_connection_shared_gss_state *shgss; -#ifndef NO_GSSAPI - int gss_status; - time_t gss_cred_expiry; /* Re-delegate if newer */ - unsigned long gss_ctxt_lifetime; /* Re-delegate when short */ -#endif - ssh_transient_hostkey_cache *thc; - - bool gss_kex_used; - - tree234 *host_cas; - - int nbits, pbits; - bool warn_kex, warn_hk, warn_cscipher, warn_sccipher; - struct { - const char *csvuln, *scvuln; - WeakCryptoReason wcr; - } terrapin; - mp_int *p, *g, *e, *f; - strbuf *ebuf, *fbuf; - strbuf *kex_shared_secret; - strbuf *outgoing_kexinit, *incoming_kexinit; - strbuf *client_kexinit, *server_kexinit; /* aliases to the above */ - int kex_init_value, kex_reply_value; - transport_direction in, out, *cstrans, *sctrans; - ptrlen hostkeydata, sigdata; - strbuf *hostkeyblob; /* used in server to construct host key to - * send to client; in client to check in rekeys */ - char *keystr; - ssh_key *hkey; /* actual host key */ - unsigned hkflags; /* signing flags, used in server */ - RSAKey *rsa_kex_key; /* for RSA kex */ - bool rsa_kex_key_needs_freeing; - ecdh_key *ecdh_key; /* for ECDH kex */ - unsigned char exchange_hash[MAX_HASH_LEN]; - bool can_gssapi_keyex; - bool need_gss_transient_hostkey; - bool warned_about_no_gss_transient_hostkey; - bool got_session_id; - bool can_send_ext_info, post_newkeys_ext_info; - bool strict_kex, enabled_outgoing_crypto, enabled_incoming_crypto; - bool seen_non_kexinit; - SeatPromptResult spr; - bool guessok; - bool ignorepkt; - struct kexinit_algorithm_list kexlists[NKEXLIST]; -#ifndef NO_GSSAPI - Ssh_gss_buf gss_buf; - Ssh_gss_buf gss_rcvtok, gss_sndtok; - Ssh_gss_stat gss_stat; - Ssh_gss_buf mic; - bool init_token_sent; - bool complete_rcvd; - bool gss_delegate; -#endif - - /* List of crypto primitives below the warning threshold that the - * user has already clicked OK to, so that we don't keep asking - * about them again during rekeys. This directly stores pointers - * to the algorithm vtables, compared by pointer value (which is - * not a determinism hazard, because we're only using it as a - * set). */ - tree234 *weak_algorithms_consented_to; - - /* - * List of host key algorithms for which we _don't_ have a stored - * host key. These are indices into the main hostkey_algs[] array - */ - int uncert_hostkeys[N_HOSTKEY_ALGORITHMS]; - int n_uncert_hostkeys; - - /* - * Indicate that the current rekey is intended to finish with a - * newly cross-certified host key. To double-check that we - * certified the right one, we set this to point to the host key - * algorithm we expect it to be. - */ - const ssh_keyalg *cross_certifying; - - ssh_key *const *hostkeys; - int nhostkeys; - - PacketProtocolLayer ppl; -}; - -/* Helpers shared between transport and kex */ -PktIn *ssh2_transport_pop(struct ssh2_transport_state *s); -void ssh2_transport_dialog_callback(void *, SeatPromptResult); - -/* Provided by transport for use in kex */ -void ssh2transport_finalise_exhash(struct ssh2_transport_state *s); - -/* Provided by kex for use in transport. Must set the 'aborted' flag - * if it throws a connection-terminating error, so that the caller - * won't have to check that by looking inside its state parameter - * which might already have been freed. */ -void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted); - -#endif /* PUTTY_SSH2TRANSPORT_H */ diff --git a/ssh/ttymode-list.h b/ssh/ttymode-list.h deleted file mode 100644 index 8fffe1c58..000000000 --- a/ssh/ttymode-list.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - * List of SSH terminal modes, indicating whether SSH types them as - * char or boolean, and if they're boolean, which POSIX flags field of - * a termios structure they appear in, and what bit mask removes them - * (e.g. CS7 and CS8 aren't single bits). - * - * Sources: RFC 4254, SSH-1 RFC-1.2.31, POSIX 2017, and the Linux - * termios manpage for flags not specified by POSIX. - * - * This is a separate header file rather than my usual style of a - * parametric list macro, because in this case I need to be able to - * #ifdef out each mode in case it's not defined on a particular - * target system. - * - * If you want only the locally defined modes, #define - * TTYMODES_LOCAL_ONLY before including this header. - */ -#if !defined TTYMODES_LOCAL_ONLY || defined VINTR -TTYMODE_CHAR(INTR, 1, VINTR) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VQUIT -TTYMODE_CHAR(QUIT, 2, VQUIT) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VERASE -TTYMODE_CHAR(ERASE, 3, VERASE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VKILL -TTYMODE_CHAR(KILL, 4, VKILL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VEOF -TTYMODE_CHAR(EOF, 5, VEOF) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VEOL -TTYMODE_CHAR(EOL, 6, VEOL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VEOL2 -TTYMODE_CHAR(EOL2, 7, VEOL2) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VSTART -TTYMODE_CHAR(START, 8, VSTART) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VSTOP -TTYMODE_CHAR(STOP, 9, VSTOP) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VSUSP -TTYMODE_CHAR(SUSP, 10, VSUSP) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VDSUSP -TTYMODE_CHAR(DSUSP, 11, VDSUSP) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VREPRINT -TTYMODE_CHAR(REPRINT, 12, VREPRINT) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VWERASE -TTYMODE_CHAR(WERASE, 13, VWERASE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VLNEXT -TTYMODE_CHAR(LNEXT, 14, VLNEXT) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VFLUSH -TTYMODE_CHAR(FLUSH, 15, VFLUSH) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VSWTCH -TTYMODE_CHAR(SWTCH, 16, VSWTCH) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VSTATUS -TTYMODE_CHAR(STATUS, 17, VSTATUS) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined VDISCARD -TTYMODE_CHAR(DISCARD, 18, VDISCARD) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IGNPAR -TTYMODE_FLAG(IGNPAR, 30, i, IGNPAR) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined PARMRK -TTYMODE_FLAG(PARMRK, 31, i, PARMRK) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined INPCK -TTYMODE_FLAG(INPCK, 32, i, INPCK) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ISTRIP -TTYMODE_FLAG(ISTRIP, 33, i, ISTRIP) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined INLCR -TTYMODE_FLAG(INLCR, 34, i, INLCR) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IGNCR -TTYMODE_FLAG(IGNCR, 35, i, IGNCR) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ICRNL -TTYMODE_FLAG(ICRNL, 36, i, ICRNL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IUCLC -TTYMODE_FLAG(IUCLC, 37, i, IUCLC) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IXON -TTYMODE_FLAG(IXON, 38, i, IXON) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IXANY -TTYMODE_FLAG(IXANY, 39, i, IXANY) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IXOFF -TTYMODE_FLAG(IXOFF, 40, i, IXOFF) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IMAXBEL -TTYMODE_FLAG(IMAXBEL, 41, i, IMAXBEL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IUTF8 -TTYMODE_FLAG(IUTF8, 42, i, IUTF8) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ISIG -TTYMODE_FLAG(ISIG, 50, l, ISIG) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ICANON -TTYMODE_FLAG(ICANON, 51, l, ICANON) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined XCASE -TTYMODE_FLAG(XCASE, 52, l, XCASE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ECHO -TTYMODE_FLAG(ECHO, 53, l, ECHO) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ECHOE -TTYMODE_FLAG(ECHOE, 54, l, ECHOE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ECHOK -TTYMODE_FLAG(ECHOK, 55, l, ECHOK) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ECHONL -TTYMODE_FLAG(ECHONL, 56, l, ECHONL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined NOFLSH -TTYMODE_FLAG(NOFLSH, 57, l, NOFLSH) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined TOSTOP -TTYMODE_FLAG(TOSTOP, 58, l, TOSTOP) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined IEXTEN -TTYMODE_FLAG(IEXTEN, 59, l, IEXTEN) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ECHOCTL -TTYMODE_FLAG(ECHOCTL, 60, l, ECHOCTL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ECHOKE -TTYMODE_FLAG(ECHOKE, 61, l, ECHOKE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined PENDIN -TTYMODE_FLAG(PENDIN, 62, l, PENDIN) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined OPOST -TTYMODE_FLAG(OPOST, 70, o, OPOST) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined OLCUC -TTYMODE_FLAG(OLCUC, 71, o, OLCUC) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ONLCR -TTYMODE_FLAG(ONLCR, 72, o, ONLCR) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined OCRNL -TTYMODE_FLAG(OCRNL, 73, o, OCRNL) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ONOCR -TTYMODE_FLAG(ONOCR, 74, o, ONOCR) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined ONLRET -TTYMODE_FLAG(ONLRET, 75, o, ONLRET) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined CS7 -TTYMODE_FLAG(CS7, 90, c, CSIZE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined CS8 -TTYMODE_FLAG(CS8, 91, c, CSIZE) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined PARENB -TTYMODE_FLAG(PARENB, 92, c, PARENB) -#endif -#if !defined TTYMODES_LOCAL_ONLY || defined PARODD -TTYMODE_FLAG(PARODD, 93, c, PARODD) -#endif diff --git a/ssh/userauth2-client.c b/ssh/userauth2-client.c deleted file mode 100644 index 85f61ab61..000000000 --- a/ssh/userauth2-client.c +++ /dev/null @@ -1,2620 +0,0 @@ -/* - * Packet protocol layer for the client side of the SSH-2 userauth - * protocol (RFC 4252). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "sshcr.h" - -#ifndef NO_GSSAPI -#include "gssc.h" -#include "gss.h" -#endif - -#define BANNER_LIMIT 131072 - -typedef struct agent_key { - strbuf *blob, *comment; - ptrlen algorithm; -} agent_key; - -struct ssh2_userauth_state { - int crState; - - PacketProtocolLayer *transport_layer, *successor_layer; - Filename *keyfile, *detached_cert_file; - bool show_banner, tryagent, notrivialauth, change_username; - char *hostname, *fullhostname; - int port; - char *default_username; - bool try_ki_auth, try_gssapi_auth, try_gssapi_kex_auth, gssapi_fwd; - - ptrlen session_id; - 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; - bool need_pw, can_pubkey, can_passwd, can_keyb_inter; - SeatPromptResult spr; - bool tried_pubkey_config, done_agent; - struct ssh_connection_shared_gss_state *shgss; -#ifndef NO_GSSAPI - bool can_gssapi; - bool can_gssapi_keyex_auth; - bool tried_gssapi; - bool tried_gssapi_keyex_auth; - time_t gss_cred_expiry; - Ssh_gss_buf gss_buf; - Ssh_gss_buf gss_rcvtok, gss_sndtok; - Ssh_gss_stat gss_stat; -#endif - bool suppress_wait_for_response_packet; - strbuf *last_methods_string; - bool kbd_inter_refused; - prompts_t *cur_prompt; - uint32_t num_prompts; - const char *username; - char *locally_allocated_username; - char *password; - bool got_username; - strbuf *publickey_blob, *detached_cert_blob, *cert_pubkey_diagnosed; - bool privatekey_available, privatekey_encrypted; - char *publickey_algorithm; - char *publickey_comment; - void *agent_response_to_free; - ptrlen agent_response; - BinarySource asrc[1]; /* for reading SSH agent response */ - size_t agent_keys_len; - agent_key *agent_keys; - size_t agent_key_index, agent_key_limit; - ptrlen agent_keyalg; - unsigned signflags; - int len; - PktOut *pktout; - bool is_trivial_auth; - - agent_pending_query *auth_agent_query; - bufchain banner; - bufchain_sink banner_bs; - StripCtrlChars *banner_scc; - bool banner_scc_initialised; - - char *authplugin_cmd; - Socket *authplugin; - uint32_t authplugin_version; - Plug authplugin_plug; - bufchain authplugin_bc; - strbuf *authplugin_incoming_msg; - size_t authplugin_backlog; - bool authplugin_eof; - bool authplugin_ki_active; - - StripCtrlChars *ki_scc; - bool ki_scc_initialised; - bool ki_printed_header; - - PacketProtocolLayer ppl; -}; - -static void ssh2_userauth_free(PacketProtocolLayer *); -static void ssh2_userauth_process_queue(PacketProtocolLayer *); -static bool ssh2_userauth_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx); -static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg); -static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf); - -static void ssh2_userauth_agent_query(struct ssh2_userauth_state *, strbuf *); -static void ssh2_userauth_agent_callback(void *, void *, int); -static void ssh2_userauth_add_sigblob( - struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob); -static void ssh2_userauth_add_alg_and_publickey( - struct ssh2_userauth_state *s, PktOut *pkt, ptrlen alg, ptrlen pkblob); -static void ssh2_userauth_add_session_id( - struct ssh2_userauth_state *s, strbuf *sigdata); -#ifndef NO_GSSAPI -static PktOut *ssh2_userauth_gss_packet( - struct ssh2_userauth_state *s, const char *authtype); -#endif -static bool ssh2_userauth_ki_setup_prompts( - struct ssh2_userauth_state *s, BinarySource *src, bool plugin); -static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s); -static void ssh2_userauth_ki_write_responses( - struct ssh2_userauth_state *s, BinarySink *bs); -static void ssh2_userauth_final_output(PacketProtocolLayer *ppl); - -static void ssh2_userauth_print_banner(struct ssh2_userauth_state *s); -static ptrlen workaround_rsa_sha2_cert_userauth( - struct ssh2_userauth_state *s, ptrlen id); - -static const PacketProtocolLayerVtable ssh2_userauth_vtable = { - .free = ssh2_userauth_free, - .process_queue = ssh2_userauth_process_queue, - .get_specials = ssh2_userauth_get_specials, - .special_cmd = ssh2_userauth_special_cmd, - .reconfigure = ssh2_userauth_reconfigure, - .queued_data_size = ssh_ppl_default_queued_data_size, - .final_output = ssh2_userauth_final_output, - .name = "ssh-userauth", -}; - -PacketProtocolLayer *ssh2_userauth_new( - PacketProtocolLayer *successor_layer, - const char *hostname, int port, const char *fullhostname, - Filename *keyfile, Filename *detached_cert_file, - bool show_banner, bool tryagent, bool notrivialauth, - const char *default_username, bool change_username, - bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth, - bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss, - const char *authplugin_cmd) -{ - struct ssh2_userauth_state *s = snew(struct ssh2_userauth_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh2_userauth_vtable; - - s->successor_layer = successor_layer; - s->hostname = dupstr(hostname); - s->port = port; - s->fullhostname = dupstr(fullhostname); - s->keyfile = filename_copy(keyfile); - s->detached_cert_file = filename_copy(detached_cert_file); - s->show_banner = show_banner; - s->tryagent = tryagent; - s->notrivialauth = notrivialauth; - s->default_username = dupstr(default_username); - s->change_username = change_username; - s->try_ki_auth = try_ki_auth; - s->try_gssapi_auth = try_gssapi_auth; - s->try_gssapi_kex_auth = try_gssapi_kex_auth; - s->gssapi_fwd = gssapi_fwd; - s->shgss = shgss; - s->last_methods_string = strbuf_new(); - s->is_trivial_auth = true; - bufchain_init(&s->banner); - bufchain_sink_init(&s->banner_bs, &s->banner); - s->authplugin_cmd = dupstr(authplugin_cmd); - bufchain_init(&s->authplugin_bc); - - return &s->ppl; -} - -void ssh2_userauth_set_transport_layer(PacketProtocolLayer *userauth, - PacketProtocolLayer *transport) -{ - struct ssh2_userauth_state *s = - container_of(userauth, struct ssh2_userauth_state, ppl); - s->transport_layer = transport; -} - -static void ssh2_userauth_free(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_state *s = - container_of(ppl, struct ssh2_userauth_state, ppl); - bufchain_clear(&s->banner); - - if (s->successor_layer) - ssh_ppl_free(s->successor_layer); - - if (s->agent_keys) { - for (size_t i = 0; i < s->agent_keys_len; i++) { - strbuf_free(s->agent_keys[i].blob); - strbuf_free(s->agent_keys[i].comment); - } - sfree(s->agent_keys); - } - sfree(s->agent_response_to_free); - if (s->auth_agent_query) - agent_cancel_query(s->auth_agent_query); - filename_free(s->keyfile); - filename_free(s->detached_cert_file); - sfree(s->default_username); - sfree(s->locally_allocated_username); - sfree(s->hostname); - sfree(s->fullhostname); - if (s->cur_prompt) - free_prompts(s->cur_prompt); - sfree(s->publickey_comment); - sfree(s->publickey_algorithm); - if (s->publickey_blob) - strbuf_free(s->publickey_blob); - if (s->detached_cert_blob) - strbuf_free(s->detached_cert_blob); - if (s->cert_pubkey_diagnosed) - strbuf_free(s->cert_pubkey_diagnosed); - strbuf_free(s->last_methods_string); - if (s->banner_scc) - stripctrl_free(s->banner_scc); - if (s->ki_scc) - stripctrl_free(s->ki_scc); - sfree(s->authplugin_cmd); - if (s->authplugin) - sk_close(s->authplugin); - bufchain_clear(&s->authplugin_bc); - if (s->authplugin_incoming_msg) - strbuf_free(s->authplugin_incoming_msg); - sfree(s); -} - -static void ssh2_userauth_handle_banner_packet(struct ssh2_userauth_state *s, - PktIn *pktin) -{ - if (!s->show_banner) - return; - - ptrlen string = get_string(pktin); - if (string.len > BANNER_LIMIT - bufchain_size(&s->banner)) - string.len = BANNER_LIMIT - bufchain_size(&s->banner); - - if (!s->banner_scc_initialised) { - s->banner_scc = seat_stripctrl_new( - s->ppl.seat, BinarySink_UPCAST(&s->banner_bs), SIC_BANNER); - if (s->banner_scc) - stripctrl_enable_line_limiting(s->banner_scc); - s->banner_scc_initialised = true; - } - - if (s->banner_scc) - put_datapl(s->banner_scc, string); - else - put_datapl(&s->banner_bs, string); -} - -static void ssh2_userauth_filter_queue(struct ssh2_userauth_state *s) -{ - PktIn *pktin; - - while ((pktin = pq_peek(s->ppl.in_pq)) != NULL) { - switch (pktin->type) { - case SSH2_MSG_USERAUTH_BANNER: - ssh2_userauth_handle_banner_packet(s, pktin); - pq_pop(s->ppl.in_pq); - break; - - default: - return; - } - } -} - -static PktIn *ssh2_userauth_pop(struct ssh2_userauth_state *s) -{ - ssh2_userauth_filter_queue(s); - return pq_pop(s->ppl.in_pq); -} - -static bool ssh2_userauth_signflags(struct ssh2_userauth_state *s, - unsigned *signflags, const char **algname) -{ - *signflags = 0; /* default */ - - const ssh_keyalg *alg = find_pubkey_alg(*algname); - if (!alg) - return false; /* we don't know how to upgrade this */ - - unsigned supported_flags = ssh_keyalg_supported_flags(alg); - - if (s->ppl.bpp->ext_info_rsa_sha512_ok && - (supported_flags & SSH_AGENT_RSA_SHA2_512)) { - *signflags = SSH_AGENT_RSA_SHA2_512; - } else if (s->ppl.bpp->ext_info_rsa_sha256_ok && - (supported_flags & SSH_AGENT_RSA_SHA2_256)) { - *signflags = SSH_AGENT_RSA_SHA2_256; - } else { - return false; - } - - *algname = ssh_keyalg_alternate_ssh_id(alg, *signflags); - return true; -} - -static void authplugin_plug_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *err_msg, int err_code) -{ - struct ssh2_userauth_state *s = container_of( - plug, struct ssh2_userauth_state, authplugin_plug); - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - - if (type == PLUGLOG_PROXY_MSG) - ppl_logevent("%s", err_msg); -} - -static void authplugin_plug_closing( - Plug *plug, PlugCloseType type, const char *error_msg) -{ - struct ssh2_userauth_state *s = container_of( - plug, struct ssh2_userauth_state, authplugin_plug); - s->authplugin_eof = true; - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -static void authplugin_plug_receive( - Plug *plug, int urgent, const char *data, size_t len) -{ - struct ssh2_userauth_state *s = container_of( - plug, struct ssh2_userauth_state, authplugin_plug); - bufchain_add(&s->authplugin_bc, data, len); - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -static void authplugin_plug_sent(Plug *plug, size_t bufsize) -{ - struct ssh2_userauth_state *s = container_of( - plug, struct ssh2_userauth_state, authplugin_plug); - s->authplugin_backlog = bufsize; - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -static const PlugVtable authplugin_plugvt = { - .log = authplugin_plug_log, - .closing = authplugin_plug_closing, - .receive = authplugin_plug_receive, - .sent = authplugin_plug_sent, -}; - -static strbuf *authplugin_newmsg(uint8_t type) -{ - strbuf *amsg = strbuf_new_nm(); - put_uint32(amsg, 0); /* fill in later */ - put_byte(amsg, type); - return amsg; -} - -static void authplugin_send_free(struct ssh2_userauth_state *s, strbuf *amsg) -{ - PUT_32BIT_MSB_FIRST(amsg->u, amsg->len - 4); - assert(s->authplugin); - s->authplugin_backlog = sk_write(s->authplugin, amsg->u, amsg->len); - strbuf_free(amsg); -} - -static bool authplugin_expect_msg(struct ssh2_userauth_state *s, - unsigned *type, BinarySource *src) -{ - if (s->authplugin_eof) { - *type = PLUGIN_EOF; - return true; - } - uint8_t len[4]; - if (!bufchain_try_fetch(&s->authplugin_bc, len, 4)) - return false; - size_t size = GET_32BIT_MSB_FIRST(len); - if (bufchain_size(&s->authplugin_bc) - 4 < size) - return false; - if (s->authplugin_incoming_msg) { - strbuf_clear(s->authplugin_incoming_msg); - } else { - s->authplugin_incoming_msg = strbuf_new_nm(); - } - bufchain_consume(&s->authplugin_bc, 4); /* eat length field */ - bufchain_fetch_consume( - &s->authplugin_bc, strbuf_append(s->authplugin_incoming_msg, size), - size); - BinarySource_BARE_INIT_PL( - src, ptrlen_from_strbuf(s->authplugin_incoming_msg)); - *type = get_byte(src); - if (get_err(src)) - *type = PLUGIN_NOTYPE; - return true; -} - -static void authplugin_bad_packet(struct ssh2_userauth_state *s, - unsigned type, const char *fmt, ...) -{ - strbuf *msg = strbuf_new(); - switch (type) { - case PLUGIN_EOF: - put_dataz(msg, "Unexpected end of file from auth helper plugin"); - break; - case PLUGIN_NOTYPE: - put_dataz(msg, "Received malformed packet from auth helper plugin " - "(too short to have a type code)"); - break; - default: - put_fmt(msg, "Received unknown message type %u " - "from auth helper plugin", type); - break; - - #define CASEDECL(name, value) \ - case name: \ - put_fmt(msg, "Received unexpected %s message from auth helper " \ - "plugin", #name); \ - break; - AUTHPLUGIN_MSG_NAMES(CASEDECL); - #undef CASEDECL - } - if (fmt) { - put_dataz(msg, " ("); - va_list ap; - va_start(ap, fmt); - put_fmt(msg, fmt, ap); - va_end(ap); - put_dataz(msg, ")"); - } - ssh_sw_abort(s->ppl.ssh, "%s", msg->s); - strbuf_free(msg); -} - -static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_state *s = - container_of(ppl, struct ssh2_userauth_state, ppl); - PktIn *pktin; - - ssh2_userauth_filter_queue(s); /* no matter why we were called */ - - crBegin(s->crState); - -#ifndef NO_GSSAPI - s->tried_gssapi = false; - s->tried_gssapi_keyex_auth = false; -#endif - - /* - * Misc one-time setup for authentication. - */ - s->publickey_blob = NULL; - s->session_id = ssh2_transport_get_session_id(s->transport_layer); - - /* - * Load the public half of any configured public key file for - * later use. - */ - if (!filename_is_null(s->keyfile)) { - int keytype; - ppl_logevent("Reading key file \"%s\"", - filename_to_str(s->keyfile)); - keytype = key_type(s->keyfile); - if (keytype == SSH_KEYTYPE_SSH2 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { - const char *error; - s->publickey_blob = strbuf_new(); - if (ppk_loadpub_f(s->keyfile, &s->publickey_algorithm, - BinarySink_UPCAST(s->publickey_blob), - &s->publickey_comment, &error)) { - s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2); - if (!s->privatekey_available) - ppl_logevent("Key file contains public key only"); - s->privatekey_encrypted = ppk_encrypted_f(s->keyfile, NULL); - } else { - ppl_logevent("Unable to load key (%s)", error); - ppl_printf("Unable to load key file \"%s\" (%s)\r\n", - filename_to_str(s->keyfile), error); - strbuf_free(s->publickey_blob); - s->publickey_blob = NULL; - } - } else { - ppl_logevent("Unable to use this key file (%s)", - key_type_to_str(keytype)); - ppl_printf("Unable to use key file \"%s\" (%s)\r\n", - filename_to_str(s->keyfile), - key_type_to_str(keytype)); - s->publickey_blob = NULL; - } - } - - /* - * If the user provided a detached certificate file, load that. - */ - if (!filename_is_null(s->detached_cert_file)) { - char *cert_error = NULL; - strbuf *cert_blob = strbuf_new(); - char *algname = NULL; - char *comment = NULL; - - ppl_logevent("Reading certificate file \"%s\"", - filename_to_str(s->detached_cert_file)); - int keytype = key_type(s->detached_cert_file); - if (!(keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)) { - cert_error = dupstr(key_type_to_str(keytype)); - goto cert_load_done; - } - - const char *error; - bool success = ppk_loadpub_f( - s->detached_cert_file, &algname, - BinarySink_UPCAST(cert_blob), &comment, &error); - - if (!success) { - cert_error = dupstr(error); - goto cert_load_done; - } - - const ssh_keyalg *certalg = find_pubkey_alg(algname); - if (!certalg) { - cert_error = dupprintf( - "unrecognised certificate type '%s'", algname); - goto cert_load_done; - } - - if (!certalg->is_certificate) { - cert_error = dupprintf( - "key type '%s' is not a certificate", certalg->ssh_id); - goto cert_load_done; - } - - /* OK, store the certificate blob to substitute for the - * public blob in all publickey auth packets. */ - if (s->detached_cert_blob) - strbuf_free(s->detached_cert_blob); - s->detached_cert_blob = cert_blob; - cert_blob = NULL; /* prevent free */ - - cert_load_done: - if (cert_error) { - ppl_logevent("Unable to use this certificate file (%s)", - cert_error); - ppl_printf( - "Unable to use certificate file \"%s\" (%s)\r\n", - filename_to_str(s->detached_cert_file), cert_error); - sfree(cert_error); - } - - if (cert_blob) - strbuf_free(cert_blob); - sfree(algname); - sfree(comment); - } - - /* - * Find out about any keys Pageant has (but if there's a public - * key configured, filter out all others). - */ - if (s->tryagent && agent_exists()) { - ppl_logevent("Pageant is running. Requesting keys."); - - /* Request the keys held by the agent. */ - { - strbuf *request = strbuf_new_for_agent_query(); - put_byte(request, SSH2_AGENTC_REQUEST_IDENTITIES); - ssh2_userauth_agent_query(s, request); - strbuf_free(request); - crWaitUntilV(!s->auth_agent_query); - } - BinarySource_BARE_INIT_PL(s->asrc, s->agent_response); - - get_uint32(s->asrc); /* skip length field */ - if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) { - size_t nkeys = get_uint32(s->asrc); - size_t origpos = s->asrc->pos; - - /* - * Check that the agent response is well formed. - */ - for (size_t i = 0; i < nkeys; i++) { - get_string(s->asrc); /* blob */ - get_string(s->asrc); /* comment */ - if (get_err(s->asrc)) { - ppl_logevent("Pageant's response was truncated"); - goto done_agent_query; - } - } - - /* - * Copy the list of public-key blobs out of the Pageant - * response. - */ - BinarySource_REWIND_TO(s->asrc, origpos); - s->agent_keys_len = nkeys; - s->agent_keys = snewn(s->agent_keys_len, agent_key); - for (size_t i = 0; i < nkeys; i++) { - s->agent_keys[i].blob = strbuf_dup(get_string(s->asrc)); - s->agent_keys[i].comment = strbuf_dup(get_string(s->asrc)); - - /* Also, extract the algorithm string from the start - * of the public-key blob. */ - s->agent_keys[i].algorithm = pubkey_blob_to_alg_name( - ptrlen_from_strbuf(s->agent_keys[i].blob)); - } - - ppl_logevent("Pageant has %"SIZEu" SSH-2 keys", nkeys); - - if (s->publickey_blob) { - /* - * If we've been given a specific public key blob, - * filter the list of keys to try from the agent down - * to only that one, or none if it's not there. - */ - ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob); - size_t i; - - for (i = 0; i < nkeys; i++) { - if (ptrlen_eq_ptrlen(our_blob, ptrlen_from_strbuf( - s->agent_keys[i].blob))) - break; - } - - if (i < nkeys) { - ppl_logevent("Pageant key #%"SIZEu" matches " - "configured key file", i); - s->agent_key_index = i; - s->agent_key_limit = i+1; - } else { - ppl_logevent("Configured key file not in Pageant"); - s->agent_key_index = 0; - s->agent_key_limit = 0; - } - } else { - /* - * Otherwise, try them all. - */ - s->agent_key_index = 0; - s->agent_key_limit = nkeys; - } - } else { - ppl_logevent("Failed to get reply from Pageant"); - } - done_agent_query:; - } - - s->got_username = false; - - if (*s->authplugin_cmd) { - s->authplugin_plug.vt = &authplugin_plugvt; - s->authplugin = platform_start_subprocess( - s->authplugin_cmd, &s->authplugin_plug, "plugin"); - ppl_logevent("Started authentication plugin: %s", s->authplugin_cmd); - } - - if (s->authplugin) { - strbuf *amsg = authplugin_newmsg(PLUGIN_INIT); - put_uint32(amsg, PLUGIN_PROTOCOL_MAX_VERSION); - put_stringz(amsg, s->hostname); - put_uint32(amsg, s->port); - put_stringz(amsg, s->username ? s->username : ""); - authplugin_send_free(s, amsg); - - BinarySource src[1]; - unsigned type; - crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src)); - switch (type) { - case PLUGIN_INIT_RESPONSE: { - s->authplugin_version = get_uint32(src); - ptrlen username = get_string(src); - if (get_err(src)) { - ssh_sw_abort(s->ppl.ssh, "Received malformed " - "PLUGIN_INIT_RESPONSE from auth helper plugin"); - return; - } - if (s->authplugin_version > PLUGIN_PROTOCOL_MAX_VERSION) { - ssh_sw_abort(s->ppl.ssh, "Auth helper plugin announced " - "unsupported version number %"PRIu32, - s->authplugin_version); - return; - } - if (username.len) { - sfree(s->default_username); - s->default_username = mkstr(username); - ppl_logevent("Authentication plugin set username '%s'", - s->default_username); - } - break; - } - case PLUGIN_INIT_FAILURE: { - ptrlen message = get_string(src); - if (get_err(src)) { - ssh_sw_abort(s->ppl.ssh, "Received malformed " - "PLUGIN_INIT_FAILURE from auth helper plugin"); - return; - } - /* This is a controlled error, so we need not completely - * abandon the connection. Instead, inform the user, and - * proceed as if the plugin was not present */ - ppl_printf("Authentication plugin failed to initialise:\r\n"); - seat_set_trust_status(s->ppl.seat, false); - ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message)); - seat_set_trust_status(s->ppl.seat, true); - sk_close(s->authplugin); - s->authplugin = NULL; - break; - } - default: - authplugin_bad_packet(s, type, "expected PLUGIN_INIT_RESPONSE or " - "PLUGIN_INIT_FAILURE"); - return; - } - } - - /* - * 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! - */ - while (1) { - /* - * Get a username. - */ - if (s->got_username && !s->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 ((s->username = s->default_username) == NULL) { - s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); - s->cur_prompt->utf8 = true; - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH login name"); - add_prompt(s->cur_prompt, dupstr("login as: "), true); - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->spr.kind == SPRK_INCOMPLETE) { - crReturnV; - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - } - if (spr_is_abort(s->spr)) { - /* - * seat_get_userpass_input() failed to get a username. - * Terminate. - */ - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - ssh_spr_close(s->ppl.ssh, s->spr, "username prompt"); - return; - } - sfree(s->locally_allocated_username); /* for change_username */ - s->username = s->locally_allocated_username = - prompt_get_result(s->cur_prompt->prompts[0]); - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - } else { - if (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat)) - ppl_printf("Using username \"%s\".\r\n", s->username); - } - 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. - */ - s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH; - - s->pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "none"); /* method */ - pq_push(s->ppl.out_pq, s->pktout); - s->type = AUTH_TYPE_NONE; - - s->tried_pubkey_config = false; - s->kbd_inter_refused = false; - s->done_agent = false; - - while (1) { - /* - * Wait for the result of the last authentication request, - * unless the request terminated for some reason on our - * own side. - */ - if (s->suppress_wait_for_response_packet) { - pktin = NULL; - s->suppress_wait_for_response_packet = false; - } else { - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - } - - /* - * 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.) - * - * 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.) - * - * The banner data has been sanitised already by this - * point, but we still need to precede and follow it with - * anti-spoofing header lines. - */ - ssh2_userauth_print_banner(s); - - if (pktin && pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { - ppl_logevent("Access granted"); - goto userauth_success; - } - - if (pktin && pktin->type != SSH2_MSG_USERAUTH_FAILURE && - s->type != AUTH_TYPE_GSSAPI) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet " - "in response to authentication request, " - "type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return; - } - - /* - * 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 && pktin->type == SSH2_MSG_USERAUTH_FAILURE) { - ptrlen methods = get_string(pktin); - bool partial_success = get_bool(pktin); - - if (!partial_success) { - /* - * 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) - ppl_printf("Server refused our key\r\n"); - ppl_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. */ - ppl_printf("Server refused public-key signature" - " despite accepting key!\r\n"); - ppl_logevent("Server refused public-key signature" - " despite accepting key!"); - } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) { - /* quiet, so no ppl_printf */ - ppl_logevent("Server refused keyboard-interactive " - "authentication"); - } else if (s->type==AUTH_TYPE_GSSAPI) { - /* always quiet, so no ppl_printf */ - /* also, the code down in the GSSAPI block has - * already logged this in the Event Log */ - } else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) { - ppl_logevent("Keyboard-interactive authentication " - "failed"); - ppl_printf("Access denied\r\n"); - } else { - assert(s->type == AUTH_TYPE_PASSWORD); - ppl_logevent("Password authentication failed"); - ppl_printf("Access denied\r\n"); - - if (s->change_username) { - /* XXX perhaps we should allow - * keyboard-interactive to do this too? */ - goto try_new_username; - } - } - } else { - ppl_printf("Further authentication required\r\n"); - ppl_logevent("Further authentication required"); - } - - /* - * Save the methods string for use in error messages. - */ - strbuf_clear(s->last_methods_string); - put_datapl(s->last_methods_string, methods); - - /* - * Scan it for method identifiers we know about. - */ - bool srv_pubkey = false, srv_passwd = false; - bool srv_keyb_inter = false; -#ifndef NO_GSSAPI - bool srv_gssapi = false, srv_gssapi_keyex_auth = false; -#endif - - for (ptrlen method; get_commasep_word(&methods, &method) ;) { - if (ptrlen_eq_string(method, "publickey")) - srv_pubkey = true; - else if (ptrlen_eq_string(method, "password")) - srv_passwd = true; - else if (ptrlen_eq_string(method, "keyboard-interactive")) - srv_keyb_inter = true; -#ifndef NO_GSSAPI - else if (ptrlen_eq_string(method, "gssapi-with-mic")) - srv_gssapi = true; - else if (ptrlen_eq_string(method, "gssapi-keyex")) - srv_gssapi_keyex_auth = true; -#endif - } - - /* - * And combine those flags with our own configuration - * and context to set the main can_foo variables. - */ - s->can_pubkey = srv_pubkey; - s->can_passwd = srv_passwd; - s->can_keyb_inter = s->try_ki_auth && srv_keyb_inter; -#ifndef NO_GSSAPI - s->can_gssapi = s->try_gssapi_auth && srv_gssapi && - s->shgss->libs->nlibraries > 0; - s->can_gssapi_keyex_auth = s->try_gssapi_kex_auth && - srv_gssapi_keyex_auth && - s->shgss->libs->nlibraries > 0 && s->shgss->ctx; -#endif - } - - s->ppl.bpp->pls->actx = SSH2_PKTCTX_NOAUTH; - -#ifndef NO_GSSAPI - if (s->can_gssapi_keyex_auth && !s->tried_gssapi_keyex_auth) { - - /* gssapi-keyex authentication */ - - s->type = AUTH_TYPE_GSSAPI; - s->tried_gssapi_keyex_auth = true; - s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI; - - if (s->shgss->lib->gsslogmsg) - ppl_logevent("%s", s->shgss->lib->gsslogmsg); - - ppl_logevent("Trying gssapi-keyex..."); - s->pktout = ssh2_userauth_gss_packet(s, "gssapi-keyex"); - pq_push(s->ppl.out_pq, s->pktout); - s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); - s->shgss->ctx = NULL; - - continue; - } else -#endif /* NO_GSSAPI */ - - if (s->can_pubkey && !s->done_agent && - s->agent_key_index < s->agent_key_limit) { - - /* - * Attempt public-key authentication using a key from Pageant. - */ - s->agent_keyalg = s->agent_keys[s->agent_key_index].algorithm; - char *alg_tmp = mkstr(s->agent_keyalg); - const char *newalg = alg_tmp; - if (ssh2_userauth_signflags(s, &s->signflags, &newalg)) - s->agent_keyalg = ptrlen_from_asciz(newalg); - sfree(alg_tmp); - - s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; - - ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); - - /* See if server will accept it */ - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "publickey"); - /* method */ - put_bool(s->pktout, false); /* no signature included */ - ssh2_userauth_add_alg_and_publickey( - s, s->pktout, s->agent_keyalg, ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].blob)); - pq_push(s->ppl.out_pq, s->pktout); - s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; - - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { - - /* Offer of key refused, presumably via - * USERAUTH_FAILURE. Requeue for the next iteration. */ - pq_push_front(s->ppl.in_pq, pktin); - - } else { - strbuf *agentreq, *sigdata; - ptrlen comment = ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].comment); - - if (seat_verbose(s->ppl.seat)) - ppl_printf("Authenticating with public key " - "\"%.*s\" from agent\r\n", - PTRLEN_PRINTF(comment)); - - /* - * Server is willing to accept the key. - * Construct a SIGN_REQUEST. - */ - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "publickey"); - /* method */ - put_bool(s->pktout, true); /* signature included */ - ssh2_userauth_add_alg_and_publickey( - s, s->pktout, s->agent_keyalg, ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].blob)); - - /* Ask agent for signature. */ - agentreq = strbuf_new_for_agent_query(); - put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); - put_stringpl(agentreq, ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].blob)); - /* Now the data to be signed... */ - sigdata = strbuf_new(); - ssh2_userauth_add_session_id(s, sigdata); - put_data(sigdata, s->pktout->data + 5, - s->pktout->length - 5); - put_stringsb(agentreq, sigdata); - /* And finally the flags word. */ - put_uint32(agentreq, s->signflags); - ssh2_userauth_agent_query(s, agentreq); - strbuf_free(agentreq); - crWaitUntilV(!s->auth_agent_query); - - if (s->agent_response.ptr) { - ptrlen sigblob; - BinarySource src[1]; - BinarySource_BARE_INIT(src, s->agent_response.ptr, - s->agent_response.len); - get_uint32(src); /* skip length field */ - if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE && - (sigblob = get_string(src), !get_err(src))) { - ppl_logevent("Sending Pageant's response"); - ssh2_userauth_add_sigblob( - s, s->pktout, - ptrlen_from_strbuf( - s->agent_keys[s->agent_key_index].blob), - sigblob); - pq_push(s->ppl.out_pq, s->pktout); - s->type = AUTH_TYPE_PUBLICKEY; - s->is_trivial_auth = false; - } else { - ppl_logevent("Pageant refused signing request"); - ppl_printf("Pageant failed to " - "provide a signature\r\n"); - s->suppress_wait_for_response_packet = true; - ssh_free_pktout(s->pktout); - } - } else { - ppl_logevent("Pageant failed to respond to " - "signing request"); - ppl_printf("Pageant failed to " - "respond to signing request\r\n"); - s->suppress_wait_for_response_packet = true; - ssh_free_pktout(s->pktout); - } - } - - /* Do we have any keys left to try? */ - if (++s->agent_key_index >= s->agent_key_limit) - s->done_agent = true; - - } else if (s->can_pubkey && s->publickey_blob && - s->privatekey_available && !s->tried_pubkey_config) { - - ssh2_userkey *key; /* not live over crReturn */ - char *passphrase; /* not live over crReturn */ - - s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; - - s->tried_pubkey_config = true; - - /* - * Try the public key supplied in the configuration. - * - * First, try to upgrade its algorithm. - */ - const char *newalg = s->publickey_algorithm; - if (ssh2_userauth_signflags(s, &s->signflags, &newalg)) { - sfree(s->publickey_algorithm); - s->publickey_algorithm = dupstr(newalg); - } - - /* - * Offer the public blob to see if the server is willing to - * accept it. - */ - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "publickey"); /* method */ - put_bool(s->pktout, false); - /* no signature included */ - ssh2_userauth_add_alg_and_publickey( - s, s->pktout, ptrlen_from_asciz(s->publickey_algorithm), - ptrlen_from_strbuf(s->publickey_blob)); - pq_push(s->ppl.out_pq, s->pktout); - ppl_logevent("Offered public key"); - - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_PK_OK) { - /* Key refused. Give up. */ - pq_push_front(s->ppl.in_pq, pktin); - s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; - continue; /* process this new message */ - } - ppl_logevent("Offer of public key accepted"); - - /* - * Actually attempt a serious authentication using - * the key. - */ - if (seat_verbose(s->ppl.seat)) - ppl_printf("Authenticating with public key \"%s\"\r\n", - s->publickey_comment); - - key = NULL; - while (!key) { - const char *error; /* not live over crReturn */ - if (s->privatekey_encrypted) { - /* - * Get a passphrase from the user. - */ - s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); - s->cur_prompt->to_server = false; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH key passphrase"); - add_prompt(s->cur_prompt, - dupprintf("Passphrase for key \"%s\": ", - s->publickey_comment), - false); - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->spr.kind == SPRK_INCOMPLETE) { - crReturnV; - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - } - if (spr_is_abort(s->spr)) { - /* Failed to get a passphrase. Terminate. */ - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - ssh_bpp_queue_disconnect( - s->ppl.bpp, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_spr_close(s->ppl.ssh, s->spr, - "passphrase prompt"); - return; - } - passphrase = - prompt_get_result(s->cur_prompt->prompts[0]); - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - } else { - passphrase = NULL; /* no passphrase needed */ - } - - /* - * Try decrypting the key. - */ - key = ppk_load_f(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)) { - ppl_printf("Wrong passphrase\r\n"); - key = NULL; - /* and loop again */ - } else { - ppl_printf("Unable to load private key (%s)\r\n", - error); - key = NULL; - s->suppress_wait_for_response_packet = true; - break; /* try something else */ - } - } else { - /* FIXME: if we ever support variable signature - * flags, this is somewhere they'll need to be - * put */ - char *invalid = ssh_key_invalid(key->key, 0); - if (invalid) { - ppl_printf("Cannot use this private key (%s)\r\n", - invalid); - ssh_key_free(key->key); - sfree(key->comment); - sfree(key); - sfree(invalid); - key = NULL; - s->suppress_wait_for_response_packet = true; - break; /* try something else */ - } - } - } - - if (key) { - strbuf *pkblob, *sigdata, *sigblob; - - /* - * 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 = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "publickey"); /* method */ - put_bool(s->pktout, true); /* signature follows */ - pkblob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob)); - ssh2_userauth_add_alg_and_publickey( - s, s->pktout, - ptrlen_from_asciz(s->publickey_algorithm), - ptrlen_from_strbuf(pkblob)); - - /* - * The data to be signed is: - * - * string session-id - * - * followed by everything so far placed in the - * outgoing packet. - */ - sigdata = strbuf_new(); - ssh2_userauth_add_session_id(s, sigdata); - put_data(sigdata, s->pktout->data + 5, - s->pktout->length - 5); - sigblob = strbuf_new(); - ssh_key_sign(key->key, ptrlen_from_strbuf(sigdata), - s->signflags, BinarySink_UPCAST(sigblob)); - strbuf_free(sigdata); - ssh2_userauth_add_sigblob( - s, s->pktout, ptrlen_from_strbuf(pkblob), - ptrlen_from_strbuf(sigblob)); - strbuf_free(pkblob); - strbuf_free(sigblob); - - pq_push(s->ppl.out_pq, s->pktout); - ppl_logevent("Sent public key signature"); - s->type = AUTH_TYPE_PUBLICKEY; - ssh_key_free(key->key); - sfree(key->comment); - sfree(key); - s->is_trivial_auth = false; - } - -#ifndef NO_GSSAPI - } else if (s->can_gssapi && !s->tried_gssapi) { - - /* gssapi-with-mic authentication */ - - ptrlen data; - - s->type = AUTH_TYPE_GSSAPI; - s->tried_gssapi = true; - s->ppl.bpp->pls->actx = SSH2_PKTCTX_GSSAPI; - - if (s->shgss->lib->gsslogmsg) - ppl_logevent("%s", s->shgss->lib->gsslogmsg); - - /* Sending USERAUTH_REQUEST with "gssapi-with-mic" method */ - ppl_logevent("Trying gssapi-with-mic..."); - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "gssapi-with-mic"); - ppl_logevent("Attempting GSSAPI authentication"); - - /* add mechanism info */ - s->shgss->lib->indicate_mech(s->shgss->lib, &s->gss_buf); - - /* number of GSSAPI mechanisms */ - put_uint32(s->pktout, 1); - - /* length of OID + 2 */ - put_uint32(s->pktout, s->gss_buf.length + 2); - put_byte(s->pktout, SSH2_GSS_OIDTYPE); - - /* length of OID */ - put_byte(s->pktout, s->gss_buf.length); - - put_data(s->pktout, s->gss_buf.value, s->gss_buf.length); - pq_push(s->ppl.out_pq, s->pktout); - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) { - ppl_logevent("GSSAPI authentication request refused"); - pq_push_front(s->ppl.in_pq, pktin); - continue; - } - - /* check returned packet ... */ - - data = get_string(pktin); - s->gss_rcvtok.value = (char *)data.ptr; - s->gss_rcvtok.length = data.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) ) { - ppl_logevent("GSSAPI authentication - wrong response " - "from server"); - continue; - } - - /* Import server name if not cached from KEX */ - if (s->shgss->srv_name == GSS_C_NO_NAME) { - s->gss_stat = s->shgss->lib->import_name( - s->shgss->lib, s->fullhostname, &s->shgss->srv_name); - if (s->gss_stat != SSH_GSS_OK) { - if (s->gss_stat == SSH_GSS_BAD_HOST_NAME) - ppl_logevent("GSSAPI import name failed -" - " Bad service name"); - else - ppl_logevent("GSSAPI import name failed"); - continue; - } - } - - /* Allocate our gss_ctx */ - s->gss_stat = s->shgss->lib->acquire_cred( - s->shgss->lib, &s->shgss->ctx, NULL); - if (s->gss_stat != SSH_GSS_OK) { - ppl_logevent("GSSAPI authentication failed to get " - "credentials"); - /* The failure was on our side, so the server - * won't be sending a response packet indicating - * failure. Avoid waiting for it next time round - * the loop. */ - s->suppress_wait_for_response_packet = true; - continue; - } - - /* initial tokens are empty */ - SSH_GSS_CLEAR_BUF(&s->gss_rcvtok); - SSH_GSS_CLEAR_BUF(&s->gss_sndtok); - - /* now enter the loop */ - do { - /* - * When acquire_cred yields no useful expiration, go with - * the service ticket expiration. - */ - s->gss_stat = s->shgss->lib->init_sec_context( - s->shgss->lib, - &s->shgss->ctx, - s->shgss->srv_name, - s->gssapi_fwd, - &s->gss_rcvtok, - &s->gss_sndtok, - NULL, - NULL); - - if (s->gss_stat!=SSH_GSS_S_COMPLETE && - s->gss_stat!=SSH_GSS_S_CONTINUE_NEEDED) { - ppl_logevent("GSSAPI authentication initialisation " - "failed"); - - if (s->shgss->lib->display_status( - s->shgss->lib, s->shgss->ctx, &s->gss_buf) - == SSH_GSS_OK) { - ppl_logevent("%s", (char *)s->gss_buf.value); - sfree(s->gss_buf.value); - } - - pq_push_front(s->ppl.in_pq, pktin); - break; - } - ppl_logevent("GSSAPI authentication initialised"); - - /* - * Client and server now exchange tokens until GSSAPI - * no longer says CONTINUE_NEEDED - */ - if (s->gss_sndtok.length != 0) { - s->is_trivial_auth = false; - s->pktout = - ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_TOKEN); - put_string(s->pktout, - s->gss_sndtok.value, s->gss_sndtok.length); - pq_push(s->ppl.out_pq, s->pktout); - s->shgss->lib->free_tok(s->shgss->lib, &s->gss_sndtok); - } - - if (s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED) { - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - - if (pktin->type == SSH2_MSG_USERAUTH_GSSAPI_ERRTOK) { - /* - * Per RFC 4462 section 3.9, this packet - * type MUST immediately precede an - * ordinary USERAUTH_FAILURE. - * - * We currently don't know how to do - * anything with the GSSAPI error token - * contained in this packet, so we ignore - * it and just wait for the following - * FAILURE. - */ - crMaybeWaitUntilV( - (pktin = ssh2_userauth_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) { - ssh_proto_error( - s->ppl.ssh, "Received unexpected packet " - "after SSH_MSG_USERAUTH_GSSAPI_ERRTOK " - "(expected SSH_MSG_USERAUTH_FAILURE): " - "type %d (%s)", pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, - pktin->type)); - return; - } - } - - if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { - ppl_logevent("GSSAPI authentication failed"); - s->gss_stat = SSH_GSS_FAILURE; - pq_push_front(s->ppl.in_pq, pktin); - break; - } else if (pktin->type != - SSH2_MSG_USERAUTH_GSSAPI_TOKEN) { - ppl_logevent("GSSAPI authentication -" - " bad server response"); - s->gss_stat = SSH_GSS_FAILURE; - break; - } - data = get_string(pktin); - s->gss_rcvtok.value = (char *)data.ptr; - s->gss_rcvtok.length = data.len; - } - } while (s-> gss_stat == SSH_GSS_S_CONTINUE_NEEDED); - - if (s->gss_stat != SSH_GSS_OK) { - s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); - continue; - } - ppl_logevent("GSSAPI authentication loop finished OK"); - - /* Now send the MIC */ - - s->pktout = ssh2_userauth_gss_packet(s, "gssapi-with-mic"); - pq_push(s->ppl.out_pq, s->pktout); - - s->shgss->lib->release_cred(s->shgss->lib, &s->shgss->ctx); - continue; -#endif - } else if (s->can_keyb_inter && !s->kbd_inter_refused) { - - /* - * Keyboard-interactive authentication. - */ - - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; - - s->ppl.bpp->pls->actx = SSH2_PKTCTX_KBDINTER; - - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "keyboard-interactive"); - /* method */ - put_stringz(s->pktout, ""); /* lang */ - put_stringz(s->pktout, ""); /* submethods */ - pq_push(s->ppl.out_pq, s->pktout); - - ppl_logevent("Attempting keyboard-interactive authentication"); - - if (s->authplugin) { - strbuf *amsg = authplugin_newmsg(PLUGIN_PROTOCOL); - put_stringz(amsg, "keyboard-interactive"); - authplugin_send_free(s, amsg); - - BinarySource src[1]; - unsigned type; - crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src)); - switch (type) { - case PLUGIN_PROTOCOL_REJECT: { - ptrlen message = PTRLEN_LITERAL(""); - if (s->authplugin_version >= 2) { - /* draft protocol didn't include a message here */ - message = get_string(src); - } - if (get_err(src)) { - ssh_sw_abort(s->ppl.ssh, "Received malformed " - "PLUGIN_PROTOCOL_REJECT from auth " - "helper plugin"); - return; - } - if (message.len) { - /* If the plugin sent a message about - * _why_ it didn't want to do k-i, pass - * that message on to the user. (It might - * say, for example, what went wrong when - * it tried to open its config file.) */ - ppl_printf("Authentication plugin failed to set " - "up keyboard-interactive " - "authentication:\r\n"); - seat_set_trust_status(s->ppl.seat, false); - ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message)); - seat_set_trust_status(s->ppl.seat, true); - ppl_logevent("Authentication plugin declined to " - "help with keyboard-interactive: " - "%.*s", PTRLEN_PRINTF(message)); - } else { - ppl_logevent("Authentication plugin declined to " - "help with keyboard-interactive"); - } - s->authplugin_ki_active = false; - break; - } - case PLUGIN_PROTOCOL_ACCEPT: - s->authplugin_ki_active = true; - ppl_logevent("Authentication plugin agreed to help " - "with keyboard-interactive"); - break; - default: - authplugin_bad_packet( - s, type, "expected PLUGIN_PROTOCOL_ACCEPT or " - "PLUGIN_PROTOCOL_REJECT"); - return; - } - } else { - s->authplugin_ki_active = false; - } - - if (!s->ki_scc_initialised) { - s->ki_scc = seat_stripctrl_new( - s->ppl.seat, NULL, SIC_KI_PROMPTS); - if (s->ki_scc) - stripctrl_enable_line_limiting(s->ki_scc); - s->ki_scc_initialised = true; - } - - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - 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. */ - pq_push_front(s->ppl.in_pq, pktin); - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET; - s->kbd_inter_refused = true; /* don't try it again */ - continue; - } - - s->ki_printed_header = false; - - /* - * Loop while we still have prompts to send to the user. - */ - if (!s->authplugin_ki_active) { - /* - * The simple case: INFO_REQUESTs are passed on to - * the user, and responses are sent straight back - * to the SSH server. - */ - while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - if (!ssh2_userauth_ki_setup_prompts( - s, BinarySource_UPCAST(pktin), false)) - return; - crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s)); - - if (spr_is_abort(s->spr)) { - /* - * Failed to get responses. Terminate. - */ - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - ssh_bpp_queue_disconnect( - s->ppl.bpp, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_spr_close(s->ppl.ssh, s->spr, "keyboard-" - "interactive authentication prompt"); - return; - } - - /* - * Send the response(s) to the server. - */ - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); - ssh2_userauth_ki_write_responses( - s, BinarySink_UPCAST(s->pktout)); - s->pktout->minlen = 256; - pq_push(s->ppl.out_pq, s->pktout); - - /* - * Get the next packet in case it's another - * INFO_REQUEST. - */ - crMaybeWaitUntilV( - (pktin = ssh2_userauth_pop(s)) != NULL); - } - } else { - /* - * The case where a plugin is involved: - * INFO_REQUEST from the server is sent to the - * plugin, which sends responses that we hand back - * to the server. But in the meantime, the plugin - * might send USER_REQUEST for us to pass to the - * user, and then we send responses to that. - */ - while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - strbuf *amsg = authplugin_newmsg( - PLUGIN_KI_SERVER_REQUEST); - put_datapl(amsg, get_data(pktin, get_avail(pktin))); - authplugin_send_free(s, amsg); - - BinarySource src[1]; - unsigned type; - while (true) { - crMaybeWaitUntilV(authplugin_expect_msg( - s, &type, src)); - if (type != PLUGIN_KI_USER_REQUEST) - break; - - if (!ssh2_userauth_ki_setup_prompts(s, src, true)) - return; - crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s)); - - if (spr_is_abort(s->spr)) { - /* - * Failed to get responses. Terminate. - */ - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - ssh_bpp_queue_disconnect( - s->ppl.bpp, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_spr_close( - s->ppl.ssh, s->spr, "keyboard-" - "interactive authentication prompt"); - return; - } - - /* - * Send the responses on to the plugin. - */ - strbuf *amsg = authplugin_newmsg( - PLUGIN_KI_USER_RESPONSE); - ssh2_userauth_ki_write_responses( - s, BinarySink_UPCAST(amsg)); - authplugin_send_free(s, amsg); - } - - if (type != PLUGIN_KI_SERVER_RESPONSE) { - authplugin_bad_packet( - s, type, "expected PLUGIN_KI_SERVER_RESPONSE " - "or PLUGIN_PROTOCOL_USER_REQUEST"); - return; - } - - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); - put_datapl(s->pktout, get_data(src, get_avail(src))); - s->pktout->minlen = 256; - pq_push(s->ppl.out_pq, s->pktout); - - /* - * Get the next packet in case it's another - * INFO_REQUEST. - */ - crMaybeWaitUntilV( - (pktin = ssh2_userauth_pop(s)) != NULL); - } - } - - /* - * Print our trailer line, if we printed a header. - */ - if (s->ki_printed_header) { - seat_set_trust_status(s->ppl.seat, true); - seat_antispoof_msg( - ppl_get_iseat(&s->ppl), - (s->authplugin_ki_active ? - "End of keyboard-interactive prompts from plugin" : - "End of keyboard-interactive prompts from server")); - } - - /* - * We should have SUCCESS or FAILURE now. - */ - pq_push_front(s->ppl.in_pq, pktin); - - if (s->authplugin_ki_active) { - /* - * As our last communication with the plugin, tell - * it whether the k-i authentication succeeded. - */ - int plugin_msg = -1; - if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) { - plugin_msg = PLUGIN_AUTH_SUCCESS; - } else if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) { - /* - * Peek in the failure packet to see if it's a - * partial success. - */ - BinarySource src[1]; - BinarySource_BARE_INIT( - src, get_ptr(pktin), get_avail(pktin)); - get_string(pktin); /* skip methods */ - bool partial_success = get_bool(pktin); - if (!get_err(src)) { - plugin_msg = partial_success ? - PLUGIN_AUTH_SUCCESS : PLUGIN_AUTH_FAILURE; - } - } - - if (plugin_msg >= 0) { - strbuf *amsg = authplugin_newmsg(plugin_msg); - authplugin_send_free(s, amsg); - - /* Wait until we've actually sent it, in case - * we close the connection to the plugin - * before that outgoing message has left our - * own buffers */ - crMaybeWaitUntilV(s->authplugin_backlog == 0); - } - } - } else if (s->can_passwd) { - s->is_trivial_auth = false; - /* - * Plain old password authentication. - */ - bool changereq_first_time; /* not live over crReturn */ - - s->ppl.bpp->pls->actx = SSH2_PKTCTX_PASSWORD; - - s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); - s->cur_prompt->utf8 = true; - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("SSH password"); - add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ", - s->username, s->hostname), - false); - - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->spr.kind == SPRK_INCOMPLETE) { - crReturnV; - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - } - if (spr_is_abort(s->spr)) { - /* - * Failed to get responses. Terminate. - */ - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - ssh_bpp_queue_disconnect( - s->ppl.bpp, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_spr_close(s->ppl.ssh, s->spr, "password prompt"); - return; - } - /* - * Squirrel away the password. (We may need it later if - * asked to change it.) - */ - s->password = prompt_get_result(s->cur_prompt->prompts[0]); - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - - /* - * 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 = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "password"); - put_bool(s->pktout, false); - put_stringz(s->pktout, s->password); - s->pktout->minlen = 256; - pq_push(s->ppl.out_pq, s->pktout); - ppl_logevent("Sent password"); - s->type = AUTH_TYPE_PASSWORD; - - /* - * Wait for next packet, in case it's a password change - * request. - */ - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - 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. - */ - - bool got_new = false; /* not live over crReturn */ - ptrlen prompt; /* not live over crReturn */ - - { - const char *msg; - if (changereq_first_time) - msg = "Server requested password change"; - else - msg = "Server rejected new password"; - ppl_logevent("%s", msg); - ppl_printf("%s\r\n", msg); - } - - prompt = get_string(pktin); - - s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); - s->cur_prompt->utf8 = true; - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = false; - s->cur_prompt->name = dupstr("New SSH password"); - s->cur_prompt->instruction = mkstr(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) { - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - while (s->spr.kind == SPRK_INCOMPLETE) { - crReturnV; - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - } - if (spr_is_abort(s->spr)) { - /* - * Failed to get responses. Terminate. - */ - /* burn the evidence */ - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - smemclr(s->password, strlen(s->password)); - sfree(s->password); - ssh_bpp_queue_disconnect( - s->ppl.bpp, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); - ssh_spr_close(s->ppl.ssh, s->spr, - "password-change prompt"); - return; - } - - /* - * 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->s[0]) { - smemclr(s->password, strlen(s->password)); - /* burn the evidence */ - sfree(s->password); - s->password = prompt_get_result( - s->cur_prompt->prompts[0]); - } - - /* - * Check the two new passwords match. - */ - got_new = !strcmp( - prompt_get_result_ref(s->cur_prompt->prompts[1]), - prompt_get_result_ref(s->cur_prompt->prompts[2])); - if (!got_new) - /* They don't. Silly user. */ - ppl_printf("Passwords do not match\r\n"); - - } - - /* - * Send the new password (along with the old one). - * (see above for padding rationale) - */ - s->pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(s->pktout, s->username); - put_stringz(s->pktout, s->successor_layer->vt->name); - put_stringz(s->pktout, "password"); - put_bool(s->pktout, true); - put_stringz(s->pktout, s->password); - put_stringz(s->pktout, prompt_get_result_ref( - s->cur_prompt->prompts[1])); - free_prompts(s->cur_prompt); - s->cur_prompt = NULL; - s->pktout->minlen = 256; - pq_push(s->ppl.out_pq, s->pktout); - ppl_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.) - */ - crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL); - 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. - */ - pq_push_front(s->ppl.in_pq, pktin); - - /* - * 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 { - ssh_bpp_queue_disconnect( - s->ppl.bpp, - "No supported authentication methods available", - SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE); - ssh_sw_abort(s->ppl.ssh, "No supported authentication methods " - "available (server sent: %s)", - s->last_methods_string->s); - return; - } - - } - try_new_username:; - } - - userauth_success: - if (s->notrivialauth && s->is_trivial_auth) { - ssh_proto_error(s->ppl.ssh, "Authentication was trivial! " - "Abandoning session as specified in configuration."); - return; - } - - /* - * We've just received USERAUTH_SUCCESS, and we haven't sent - * any packets since. Signal the transport layer to consider - * doing an immediate rekey, if it has any reason to want to. - */ - ssh2_transport_notify_auth_done(s->transport_layer); - - /* - * Finally, hand over to our successor layer, and return - * immediately without reaching the crFinishV: ssh_ppl_replace - * will have freed us, so crFinishV's zeroing-out of crState would - * be a use-after-free bug. - */ - { - PacketProtocolLayer *successor = s->successor_layer; - s->successor_layer = NULL; /* avoid freeing it ourself */ - ssh_ppl_replace(&s->ppl, successor); - return; /* we've just freed s, so avoid even touching s->crState */ - } - - crFinishV; -} - -static void ssh2_userauth_print_banner(struct ssh2_userauth_state *s) -{ - if (bufchain_size(&s->banner) && - (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))) { - if (s->banner_scc) { - seat_antispoof_msg( - ppl_get_iseat(&s->ppl), - "Pre-authentication banner message from server:"); - seat_set_trust_status(s->ppl.seat, false); - } - - bool mid_line = false; - while (bufchain_size(&s->banner) > 0) { - ptrlen data = bufchain_prefix(&s->banner); - seat_banner_pl(ppl_get_iseat(&s->ppl), data); - mid_line = - (((const char *)data.ptr)[data.len-1] != '\n'); - bufchain_consume(&s->banner, data.len); - } - bufchain_clear(&s->banner); - - if (mid_line) - seat_banner_pl(ppl_get_iseat(&s->ppl), - PTRLEN_LITERAL("\r\n")); - - if (s->banner_scc) { - seat_set_trust_status(s->ppl.seat, true); - seat_antispoof_msg(ppl_get_iseat(&s->ppl), - "End of banner message from server"); - } - } -} - -static bool ssh2_userauth_ki_setup_prompts( - struct ssh2_userauth_state *s, BinarySource *src, bool plugin) -{ - ptrlen name, inst; - strbuf *sb; - - /* - * We've got a fresh USERAUTH_INFO_REQUEST. Get the preamble and - * start building a prompt. - */ - name = get_string(src); - inst = get_string(src); - get_string(src); /* skip language tag */ - s->cur_prompt = ssh_ppl_new_prompts(&s->ppl); - s->cur_prompt->utf8 = true; - s->cur_prompt->to_server = true; - s->cur_prompt->from_server = true; - - /* - * Get any prompt(s) from the packet. - */ - s->num_prompts = get_uint32(src); - for (uint32_t i = 0; i < s->num_prompts; i++) { - s->is_trivial_auth = false; - ptrlen prompt = get_string(src); - bool echo = get_bool(src); - - if (get_err(src)) { - ssh_proto_error(s->ppl.ssh, "%s sent truncated %s packet", - plugin ? "Plugin" : "Server", - plugin ? "PLUGIN_KI_USER_REQUEST" : - "SSH_MSG_USERAUTH_INFO_REQUEST"); - return false; - } - - sb = strbuf_new(); - if (!prompt.len) { - put_fmt(sb, "<%s failed to send prompt>: ", - plugin ? "plugin" : "server"); - } else if (s->ki_scc) { - stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); - put_datapl(s->ki_scc, prompt); - stripctrl_retarget(s->ki_scc, NULL); - } else { - put_datapl(sb, prompt); - } - add_prompt(s->cur_prompt, strbuf_to_str(sb), echo); - } - - /* - * Make the header strings. This includes the 'name' (optional - * dialog-box title) and 'instruction' from the server. - * - * First, display our disambiguating header line if this is the - * first time round the loop - _unless_ the server has sent a - * completely empty k-i packet with no prompts _or_ text, which - * apparently some do. In that situation there's no need to alert - * the user that the following text is server- supplied, because, - * well, _what_ text? - * - * We also only do this if we got a stripctrl, because if we - * didn't, that suggests this is all being done via dialog boxes - * anyway. - */ - if (!s->ki_printed_header && s->ki_scc && - (s->num_prompts || name.len || inst.len)) { - seat_antispoof_msg( - ppl_get_iseat(&s->ppl), - (plugin ? - "Keyboard-interactive authentication prompts from plugin:" : - "Keyboard-interactive authentication prompts from server:")); - s->ki_printed_header = true; - seat_set_trust_status(s->ppl.seat, false); - } - - sb = strbuf_new(); - if (name.len) { - if (s->ki_scc) { - stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); - put_datapl(s->ki_scc, name); - stripctrl_retarget(s->ki_scc, NULL); - } else { - put_datapl(sb, name); - } - s->cur_prompt->name_reqd = true; - } else { - if (plugin) - put_datapl(sb, PTRLEN_LITERAL( - "Communication with authentication plugin")); - else - put_datapl(sb, PTRLEN_LITERAL("SSH server authentication")); - s->cur_prompt->name_reqd = false; - } - s->cur_prompt->name = strbuf_to_str(sb); - - sb = strbuf_new(); - if (inst.len) { - if (s->ki_scc) { - stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb)); - put_datapl(s->ki_scc, inst); - stripctrl_retarget(s->ki_scc, NULL); - } else { - put_datapl(sb, inst); - } - s->cur_prompt->instr_reqd = true; - } else { - s->cur_prompt->instr_reqd = false; - } - if (sb->len) - s->cur_prompt->instruction = strbuf_to_str(sb); - else - strbuf_free(sb); - - return true; -} - -static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s) -{ - s->spr = seat_get_userpass_input( - ppl_get_iseat(&s->ppl), s->cur_prompt); - return s->spr.kind != SPRK_INCOMPLETE; -} - -static void ssh2_userauth_ki_write_responses( - struct ssh2_userauth_state *s, BinarySink *bs) -{ - put_uint32(bs, s->num_prompts); - for (uint32_t i = 0; i < s->num_prompts; i++) - put_stringz(bs, prompt_get_result_ref(s->cur_prompt->prompts[i])); - - /* - * 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); - s->cur_prompt = NULL; -} - -static void ssh2_userauth_add_session_id( - struct ssh2_userauth_state *s, strbuf *sigdata) -{ - if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) { - put_datapl(sigdata, s->session_id); - } else { - put_stringpl(sigdata, s->session_id); - } -} - -static void ssh2_userauth_agent_query( - struct ssh2_userauth_state *s, strbuf *req) -{ - void *response; - int response_len; - - sfree(s->agent_response_to_free); - s->agent_response_to_free = NULL; - - s->auth_agent_query = agent_query(req, &response, &response_len, - ssh2_userauth_agent_callback, s); - if (!s->auth_agent_query) - ssh2_userauth_agent_callback(s, response, response_len); -} - -static void ssh2_userauth_agent_callback(void *uav, void *reply, int replylen) -{ - struct ssh2_userauth_state *s = (struct ssh2_userauth_state *)uav; - - s->auth_agent_query = NULL; - s->agent_response_to_free = reply; - s->agent_response = make_ptrlen(reply, replylen); - - queue_idempotent_callback(&s->ppl.ic_process_queue); -} - -/* - * Helper function to add the algorithm and public key strings to a - * "publickey" auth packet. Deals with overriding both strings if the - * user has provided a detached certificate which matches the public - * key in question. - */ -static void ssh2_userauth_add_alg_and_publickey( - struct ssh2_userauth_state *s, PktOut *pkt, ptrlen alg, ptrlen pkblob) -{ - PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */ - - if (s->detached_cert_blob) { - ptrlen detached_cert_pl = ptrlen_from_strbuf(s->detached_cert_blob); - strbuf *certbase = NULL, *pkbase = NULL; - bool done = false; - const ssh_keyalg *pkalg = find_pubkey_alg_len(alg); - ssh_key *certkey = NULL, *pk = NULL; - strbuf *fail_reason = strbuf_new(); - bool verbose = true; - - /* - * Whether or not we send the certificate, we're likely to - * generate a log message about it. But we don't want to log - * once for the offer and once for the real auth attempt, so - * we de-duplicate by remembering the last public key this - * function saw. */ - if (!s->cert_pubkey_diagnosed) - s->cert_pubkey_diagnosed = strbuf_new(); - if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(s->cert_pubkey_diagnosed), - pkblob)) { - verbose = false; - } else { - /* Log this time, but arrange that we don't mention it next time */ - strbuf_clear(s->cert_pubkey_diagnosed); - put_datapl(s->cert_pubkey_diagnosed, pkblob); - } - - /* - * Check that the public key we're replacing is compatible - * with the certificate, in that they should have the same - * base public key. - */ - - const ssh_keyalg *certalg = pubkey_blob_to_alg(detached_cert_pl); - assert(certalg); /* we checked this before setting s->detached_blob */ - assert(certalg->is_certificate); /* and this too */ - - certkey = ssh_key_new_pub(certalg, detached_cert_pl); - if (!certkey) { - put_fmt(fail_reason, "certificate key file is invalid"); - goto no_match; - } - - certbase = strbuf_new(); - ssh_key_public_blob(ssh_key_base_key(certkey), - BinarySink_UPCAST(certbase)); - if (ptrlen_eq_ptrlen(pkblob, ptrlen_from_strbuf(certbase))) - goto match; /* yes, a match! */ - - /* - * If we reach here, the certificate's base key was not - * identical to the key we're given. But it might still be - * identical to the _base_ key of the key we're given, if we - * were using a differently certified version of the same key. - * In that situation, the detached cert should still override. - */ - if (!pkalg) { - put_fmt(fail_reason, "unable to identify algorithm of base key"); - goto no_match; - } - - pk = ssh_key_new_pub(pkalg, pkblob); - if (!pk) { - put_fmt(fail_reason, "base public key is invalid"); - goto no_match; - } - - pkbase = strbuf_new(); - ssh_key_public_blob(ssh_key_base_key(pk), BinarySink_UPCAST(pkbase)); - if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(pkbase), - ptrlen_from_strbuf(certbase))) - goto match; /* yes, a match on 2nd attempt! */ - - /* Give up; we've tried to match these keys up and failed. */ - put_fmt(fail_reason, "base public key does not match certificate"); - goto no_match; - - match: - /* - * The two keys match, so insert the detached certificate into - * the output packet in place of the public key we were given. - * - * However, we need to be a bit careful with the algorithm - * name: we might need to upgrade it to one that matches the - * original algorithm name. (If we were asked to add an - * ssh-rsa key but were given algorithm name "rsa-sha2-512", - * then instead of the certificate's algorithm name - * ssh-rsa-cert-v01@... we need to write the corresponding - * SHA-512 name rsa-sha2-512-cert-v01@... .) - */ - if (verbose) { - ppl_logevent("Sending public key with certificate from \"%s\"", - filename_to_str(s->detached_cert_file)); - } - { - /* Strip off any existing certificate-nature from pkalg, - * for the case where we're replacing a cert embedded in - * the key with the detached one. The second argument of - * ssh_keyalg_related_alg is expected to be one of the - * bare key algorithms, or nothing useful will happen. */ - const ssh_keyalg *pkalg_base = - pkalg->base_alg ? pkalg->base_alg : pkalg; - - /* Construct an algorithm string that includes both the - * signature subtype (e.g. rsa-sha2-512) and the - * certificate-ness. Exception: in earlier versions of - * OpenSSH we don't want to do that, and must send just - * ssh-rsa-cert-... even when we're delivering a non-SHA-1 - * signature. */ - const ssh_keyalg *output_alg = - ssh_keyalg_related_alg(certalg, pkalg_base); - ptrlen output_id = ptrlen_from_asciz(output_alg->ssh_id); - output_id = workaround_rsa_sha2_cert_userauth(s, output_id); - - put_stringpl(pkt, output_id); - } - put_stringpl(pkt, ptrlen_from_strbuf(s->detached_cert_blob)); - done = true; - goto out; - - no_match: - /* Log that we didn't send the certificate, if this public key - * isn't the same one as last call to this function. (Need to - * avoid verbosely logging once for the offer and once for the - * real auth attempt.) */ - if (verbose) { - ppl_logevent("Not substituting certificate \"%s\" for public " - "key: %s", filename_to_str(s->detached_cert_file), - fail_reason->s); - if (s->publickey_blob) { - /* If the user provided a specific key file to use (i.e. - * this wasn't just a key we picked opportunistically out - * of an agent), then they probably _care_ that we didn't - * send the certificate, so we should make a loud error - * message about it as well as just commenting in the - * Event Log. */ - ppl_printf("Unable to use certificate \"%s\" with public " - "key \"%s\": %s\r\n", - filename_to_str(s->detached_cert_file), - filename_to_str(s->keyfile), - fail_reason->s); - } - } - - out: - /* Whether we did that or not, free our stuff. */ - if (certbase) - strbuf_free(certbase); - if (pkbase) - strbuf_free(pkbase); - if (certkey) - ssh_key_free(certkey); - if (pk) - ssh_key_free(pk); - strbuf_free(fail_reason); - - /* And if we did, don't fall through to the alternative below */ - if (done) - return; - } - - /* In all other cases, basically just put in what we were given - - * except for the same bug workaround as above. */ - alg = workaround_rsa_sha2_cert_userauth(s, alg); - put_stringpl(pkt, alg); - put_stringpl(pkt, pkblob); -} - -static ptrlen workaround_rsa_sha2_cert_userauth( - struct ssh2_userauth_state *s, ptrlen id) -{ - if (!(s->ppl.remote_bugs & BUG_RSA_SHA2_CERT_USERAUTH)) - return id; - /* - * No need to try to do this in a general way based on the - * relations between ssh_keyalgs; we know there are a limited - * number of affected versions of OpenSSH, so this doesn't have to - * be futureproof against later additions to the family. - */ - if (ptrlen_eq_string(id, "rsa-sha2-256-cert-v01@openssh.com") || - ptrlen_eq_string(id, "rsa-sha2-512-cert-v01@openssh.com")) - return PTRLEN_LITERAL("ssh-rsa-cert-v01@openssh.com"); - return id; -} - -/* - * 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 just appends the sig blob unmodified as a string, except - * that it optionally breaks it open and fiddle with it to work around - * BUG_SSH2_RSA_PADDING. - */ -static void ssh2_userauth_add_sigblob( - struct ssh2_userauth_state *s, PktOut *pkt, ptrlen pkblob, ptrlen sigblob) -{ - BinarySource pk[1], sig[1]; - BinarySource_BARE_INIT_PL(pk, pkblob); - BinarySource_BARE_INIT_PL(sig, sigblob); - - /* 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 ((s->ppl.remote_bugs & BUG_SSH2_RSA_PADDING) && - ptrlen_eq_string(get_string(pk), "ssh-rsa") && - ptrlen_eq_string(get_string(sig), "ssh-rsa")) { - ptrlen mod_mp, sig_mp; - size_t sig_prefix_len; - - /* - * Find the modulus and signature integers. - */ - get_string(pk); /* skip over exponent */ - mod_mp = get_string(pk); /* remember modulus */ - sig_prefix_len = sig->pos; - sig_mp = get_string(sig); - if (get_err(pk) || get_err(sig)) - goto give_up; - - /* - * Find the byte length of the modulus, not counting leading - * zeroes. - */ - while (mod_mp.len > 0 && *(const char *)mod_mp.ptr == 0) { - mod_mp.len--; - mod_mp.ptr = (const char *)mod_mp.ptr + 1; - } - - /* debug("modulus length is %d\n", len); */ - /* debug("signature length is %d\n", siglen); */ - - if (mod_mp.len > sig_mp.len) { - strbuf *substr = strbuf_new(); - put_data(substr, sigblob.ptr, sig_prefix_len); - put_uint32(substr, mod_mp.len); - put_padding(substr, mod_mp.len - sig_mp.len, 0); - put_datapl(substr, sig_mp); - put_stringsb(pkt, substr); - 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:; - } - - put_stringpl(pkt, sigblob); -} - -#ifndef NO_GSSAPI -static PktOut *ssh2_userauth_gss_packet( - struct ssh2_userauth_state *s, const char *authtype) -{ - strbuf *sb; - PktOut *p; - Ssh_gss_buf buf; - Ssh_gss_buf mic; - - /* - * The mic is computed over the session id + intended - * USERAUTH_REQUEST packet. - */ - sb = strbuf_new(); - put_stringpl(sb, s->session_id); - put_byte(sb, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(sb, s->username); - put_stringz(sb, s->successor_layer->vt->name); - put_stringz(sb, authtype); - - /* Compute the mic */ - buf.value = sb->s; - buf.length = sb->len; - s->shgss->lib->get_mic(s->shgss->lib, s->shgss->ctx, &buf, &mic); - strbuf_free(sb); - - /* Now we can build the real packet */ - if (strcmp(authtype, "gssapi-with-mic") == 0) { - p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_GSSAPI_MIC); - } else { - p = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_REQUEST); - put_stringz(p, s->username); - put_stringz(p, s->successor_layer->vt->name); - put_stringz(p, authtype); - } - put_string(p, mic.value, mic.length); - - return p; -} -#endif - -static bool ssh2_userauth_get_specials( - PacketProtocolLayer *ppl, add_special_fn_t add_special, void *ctx) -{ - /* No specials provided by this layer. */ - return false; -} - -static void ssh2_userauth_special_cmd(PacketProtocolLayer *ppl, - SessionSpecialCode code, int arg) -{ - /* No specials provided by this layer. */ -} - -static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf) -{ - struct ssh2_userauth_state *s = - container_of(ppl, struct ssh2_userauth_state, ppl); - ssh_ppl_reconfigure(s->successor_layer, conf); -} - -static void ssh2_userauth_final_output(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_state *s = - container_of(ppl, struct ssh2_userauth_state, ppl); - - /* - * Check for any unconsumed banner packets that might have landed - * in our queue just before the server closed the connection, and - * add them to our banner buffer. - */ - for (PktIn *pktin = pq_first(s->ppl.in_pq); pktin != NULL; - pktin = pq_next(s->ppl.in_pq, pktin)) { - if (pktin->type == SSH2_MSG_USERAUTH_BANNER) - ssh2_userauth_handle_banner_packet(s, pktin); - } - - /* And now make sure we've shown the banner, before exiting */ - ssh2_userauth_print_banner(s); -} diff --git a/ssh/userauth2-server.c b/ssh/userauth2-server.c deleted file mode 100644 index 9d87148df..000000000 --- a/ssh/userauth2-server.c +++ /dev/null @@ -1,400 +0,0 @@ -/* - * Packet protocol layer for the server side of the SSH-2 userauth - * protocol (RFC 4252). - */ - -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "ppl.h" -#include "sshcr.h" -#include "server.h" - -#ifndef NO_GSSAPI -#include "gssc.h" -#include "gss.h" -#endif - -struct ssh2_userauth_server_state { - int crState; - - PacketProtocolLayer *transport_layer, *successor_layer; - ptrlen session_id; - - AuthPolicy *authpolicy; - const SshServerConfig *ssc; - - ptrlen username, service, method; - unsigned methods, this_method; - bool partial_success; - - AuthKbdInt *aki; - - PacketProtocolLayer ppl; -}; - -static void ssh2_userauth_server_free(PacketProtocolLayer *); -static void ssh2_userauth_server_process_queue(PacketProtocolLayer *); - -static const PacketProtocolLayerVtable ssh2_userauth_server_vtable = { - .free = ssh2_userauth_server_free, - .process_queue = ssh2_userauth_server_process_queue, - .queued_data_size = ssh_ppl_default_queued_data_size, - .final_output = ssh_ppl_default_final_output, - .name = "ssh-userauth", - /* other methods are NULL */ -}; - -static void free_auth_kbdint(AuthKbdInt *aki) -{ - int i; - - if (!aki) - return; - - sfree(aki->title); - sfree(aki->instruction); - for (i = 0; i < aki->nprompts; i++) - sfree(aki->prompts[i].prompt); - sfree(aki->prompts); - sfree(aki); -} - -PacketProtocolLayer *ssh2_userauth_server_new( - PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy, - const SshServerConfig *ssc) -{ - struct ssh2_userauth_server_state *s = - snew(struct ssh2_userauth_server_state); - memset(s, 0, sizeof(*s)); - s->ppl.vt = &ssh2_userauth_server_vtable; - - s->successor_layer = successor_layer; - s->authpolicy = authpolicy; - s->ssc = ssc; - - return &s->ppl; -} - -void ssh2_userauth_server_set_transport_layer(PacketProtocolLayer *userauth, - PacketProtocolLayer *transport) -{ - struct ssh2_userauth_server_state *s = - container_of(userauth, struct ssh2_userauth_server_state, ppl); - s->transport_layer = transport; -} - -static void ssh2_userauth_server_free(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_server_state *s = - container_of(ppl, struct ssh2_userauth_server_state, ppl); - - if (s->successor_layer) - ssh_ppl_free(s->successor_layer); - - free_auth_kbdint(s->aki); - - sfree(s); -} - -static PktIn *ssh2_userauth_server_pop(struct ssh2_userauth_server_state *s) -{ - return pq_pop(s->ppl.in_pq); -} - -static void ssh2_userauth_server_add_session_id( - struct ssh2_userauth_server_state *s, strbuf *sigdata) -{ - if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) { - put_datapl(sigdata, s->session_id); - } else { - put_stringpl(sigdata, s->session_id); - } -} - -static void ssh2_userauth_server_close_after_banner(void *vctx) -{ - struct ssh2_userauth_server_state *s = - (struct ssh2_userauth_server_state *)vctx; - - if (pq_peek(s->ppl.out_pq)) { - /* Don't close the connection until we've passed on our final banner - * packet to the lower layer */ - queue_toplevel_callback(ssh2_userauth_server_close_after_banner, s); - } else { - ssh_user_close(s->ppl.ssh, "Closing connection on request due to " - "--close-after-banner"); - } -} - -static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) -{ - struct ssh2_userauth_server_state *s = - container_of(ppl, struct ssh2_userauth_server_state, ppl); - PktIn *pktin; - PktOut *pktout; - - crBegin(s->crState); - - s->session_id = ssh2_transport_get_session_id(s->transport_layer); - - if (s->ssc->banner.ptr) { - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_BANNER); - put_stringpl(pktout, s->ssc->banner); - put_stringz(pktout, ""); /* language tag */ - pq_push(s->ppl.out_pq, pktout); - } - - if (s->ssc->stunt_close_after_banner) { - queue_toplevel_callback(ssh2_userauth_server_close_after_banner, s); - crReturnV; - } - - while (1) { - crMaybeWaitUntilV((pktin = ssh2_userauth_server_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_REQUEST) { - ssh_proto_error(s->ppl.ssh, "Received unexpected packet when " - "expecting USERAUTH_REQUEST, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, pktin->type)); - return; - } - - s->username = get_string(pktin); - s->service = get_string(pktin); - s->method = get_string(pktin); - - if (!ptrlen_eq_string(s->service, s->successor_layer->vt->name)) { - /* - * Unconditionally reject authentication for any service - * other than the one we're going to hand over to. - */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE); - put_stringz(pktout, ""); - put_bool(pktout, false); - pq_push(s->ppl.out_pq, pktout); - continue; - } - - s->methods = auth_methods(s->authpolicy); - s->partial_success = false; - - if (ptrlen_eq_string(s->method, "none")) { - s->this_method = AUTHMETHOD_NONE; - if (!(s->methods & s->this_method)) - goto failure; - - if (!auth_none(s->authpolicy, s->username)) - goto failure; - } else if (ptrlen_eq_string(s->method, "password")) { - bool changing; - ptrlen password, new_password, *new_password_ptr; - - s->this_method = AUTHMETHOD_PASSWORD; - if (!(s->methods & s->this_method)) - goto failure; - - changing = get_bool(pktin); - password = get_string(pktin); - - if (changing) { - new_password = get_string(pktin); - new_password_ptr = &new_password; - } else { - new_password_ptr = NULL; - } - - int result = auth_password(s->authpolicy, s->username, - password, new_password_ptr); - if (result == 2) { - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ); - put_stringz(pktout, "Please change your password"); - put_stringz(pktout, ""); /* language tag */ - pq_push(s->ppl.out_pq, pktout); - continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ - } else if (result != 1) { - goto failure; - } - } else if (ptrlen_eq_string(s->method, "publickey")) { - bool has_signature, success, send_pk_ok, key_really_ok; - ptrlen algorithm, blob, signature; - const ssh_keyalg *keyalg; - ssh_key *key; - strbuf *sigdata; - - s->this_method = AUTHMETHOD_PUBLICKEY; - if (!(s->methods & s->this_method)) - goto failure; - - has_signature = get_bool(pktin) || - s->ssc->stunt_return_success_to_pubkey_offer; - algorithm = get_string(pktin); - blob = get_string(pktin); - - key_really_ok = auth_publickey(s->authpolicy, s->username, blob); - send_pk_ok = key_really_ok || - s->ssc->stunt_pretend_to_accept_any_pubkey; - - if (!has_signature) { - if (!send_pk_ok) - goto failure; - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK); - put_stringpl(pktout, algorithm); - put_stringpl(pktout, blob); - pq_push(s->ppl.out_pq, pktout); - continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ - } - - if (!key_really_ok) - goto failure; - - keyalg = find_pubkey_alg_len(algorithm); - if (!keyalg) - goto failure; - key = ssh_key_new_pub(keyalg, blob); - if (!key) - goto failure; - - sigdata = strbuf_new(); - ssh2_userauth_server_add_session_id(s, sigdata); - put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST); - put_stringpl(sigdata, s->username); - put_stringpl(sigdata, s->service); - put_stringpl(sigdata, s->method); - put_bool(sigdata, has_signature); - put_stringpl(sigdata, algorithm); - put_stringpl(sigdata, blob); - - signature = get_string(pktin); - success = ssh_key_verify(key, signature, - ptrlen_from_strbuf(sigdata)) || - s->ssc->stunt_return_success_to_pubkey_offer; - ssh_key_free(key); - strbuf_free(sigdata); - - if (!success) - goto failure; - } else if (ptrlen_eq_string(s->method, "keyboard-interactive")) { - int i, ok; - unsigned n; - - s->this_method = AUTHMETHOD_KBDINT; - if (!(s->methods & s->this_method)) - goto failure; - - do { - s->aki = auth_kbdint_prompts(s->authpolicy, s->username); - if (!s->aki) - goto failure; - - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_REQUEST); - put_stringz(pktout, s->aki->title); - put_stringz(pktout, s->aki->instruction); - put_stringz(pktout, ""); /* language tag */ - put_uint32(pktout, s->aki->nprompts); - for (i = 0; i < s->aki->nprompts; i++) { - put_stringz(pktout, s->aki->prompts[i].prompt); - put_bool(pktout, s->aki->prompts[i].echo); - } - pq_push(s->ppl.out_pq, pktout); - - crMaybeWaitUntilV( - (pktin = ssh2_userauth_server_pop(s)) != NULL); - if (pktin->type != SSH2_MSG_USERAUTH_INFO_RESPONSE) { - ssh_proto_error( - s->ppl.ssh, "Received unexpected packet when " - "expecting USERAUTH_INFO_RESPONSE, type %d (%s)", - pktin->type, - ssh2_pkt_type(s->ppl.bpp->pls->kctx, - s->ppl.bpp->pls->actx, pktin->type)); - return; - } - - n = get_uint32(pktin); - if (n != s->aki->nprompts) { - ssh_proto_error( - s->ppl.ssh, "Received %u keyboard-interactive " - "responses after sending %u prompts", - n, s->aki->nprompts); - return; - } - - { - ptrlen *responses = snewn(s->aki->nprompts, ptrlen); - for (i = 0; i < s->aki->nprompts; i++) - responses[i] = get_string(pktin); - ok = auth_kbdint_responses(s->authpolicy, responses); - sfree(responses); - } - - free_auth_kbdint(s->aki); - s->aki = NULL; - } while (ok == 0); - - if (ok <= 0) - goto failure; - } else { - goto failure; - } - - /* - * If we get here, we've successfully completed this - * authentication step. - */ - if (auth_successful(s->authpolicy, s->username, s->this_method)) { - /* - * ... and it was the last one, so we're completely done. - */ - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_SUCCESS); - pq_push(s->ppl.out_pq, pktout); - break; - } else { - /* - * ... but another is required, so fall through to - * generation of USERAUTH_FAILURE, having first refreshed - * the bit mask of available methods. - */ - s->methods = auth_methods(s->authpolicy); - } - s->partial_success = true; - - failure: - pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE); - { - strbuf *list = strbuf_new(); - if (s->methods & AUTHMETHOD_NONE) - add_to_commasep(list, "none"); - if (s->methods & AUTHMETHOD_PASSWORD) - add_to_commasep(list, "password"); - if (s->methods & AUTHMETHOD_PUBLICKEY) - add_to_commasep(list, "publickey"); - if (s->methods & AUTHMETHOD_KBDINT) - add_to_commasep(list, "keyboard-interactive"); - put_stringsb(pktout, list); - } - put_bool(pktout, s->partial_success); - pq_push(s->ppl.out_pq, pktout); - } - - /* - * Finally, hand over to our successor layer, and return - * immediately without reaching the crFinishV: ssh_ppl_replace - * will have freed us, so crFinishV's zeroing-out of crState would - * be a use-after-free bug. - */ - { - PacketProtocolLayer *successor = s->successor_layer; - s->successor_layer = NULL; /* avoid freeing it ourself */ - ssh_ppl_replace(&s->ppl, successor); - return; /* we've just freed s, so avoid even touching s->crState */ - } - - crFinishV; -} diff --git a/ssh/verstring.c b/ssh/verstring.c deleted file mode 100644 index b63810ef1..000000000 --- a/ssh/verstring.c +++ /dev/null @@ -1,664 +0,0 @@ -/* - * Code to handle the initial SSH version string exchange. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "bpp.h" -#include "sshcr.h" - -#define PREFIX_MAXLEN 64 - -struct ssh_verstring_state { - int crState; - - Conf *conf; - ptrlen prefix_wanted; - char *our_protoversion; - struct ssh_version_receiver *receiver; - - bool send_early; - - bool found_prefix; - int major_protoversion; - int remote_bugs; - char prefix[PREFIX_MAXLEN]; - char *impl_name; - strbuf *vstring; - char *protoversion; - const char *softwareversion; - - char *our_vstring; - int i; - - BinaryPacketProtocol bpp; -}; - -static void ssh_verstring_free(BinaryPacketProtocol *bpp); -static void ssh_verstring_handle_input(BinaryPacketProtocol *bpp); -static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp); -static PktOut *ssh_verstring_new_pktout(int type); -static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category); - -static const BinaryPacketProtocolVtable ssh_verstring_vtable = { - .free = ssh_verstring_free, - .handle_input = ssh_verstring_handle_input, - .handle_output = ssh_verstring_handle_output, - .new_pktout = ssh_verstring_new_pktout, - .queue_disconnect = ssh_verstring_queue_disconnect, - .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */ -}; - -static void ssh_detect_bugs(struct ssh_verstring_state *s); -static bool ssh_version_includes_v1(const char *ver); -static bool ssh_version_includes_v2(const char *ver); - -BinaryPacketProtocol *ssh_verstring_new( - Conf *conf, LogContext *logctx, bool bare_connection_mode, - const char *protoversion, struct ssh_version_receiver *rcv, - bool server_mode, const char *impl_name) -{ - struct ssh_verstring_state *s = snew(struct ssh_verstring_state); - - memset(s, 0, sizeof(struct ssh_verstring_state)); - - if (!bare_connection_mode) { - s->prefix_wanted = PTRLEN_LITERAL("SSH-"); - } else { - /* - * Ordinary SSH begins with the banner "SSH-x.y-...". Here, - * we're going to be speaking just the ssh-connection - * subprotocol, extracted and given a trivial binary packet - * protocol, so we need a new banner. - * - * The new banner is like the ordinary SSH banner, but - * replaces the prefix '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. - */ - s->prefix_wanted = PTRLEN_LITERAL( - "SSHCONNECTION@putty.projects.tartarus.org-"); - } - assert(s->prefix_wanted.len <= PREFIX_MAXLEN); - - s->conf = conf_copy(conf); - s->bpp.logctx = logctx; - s->our_protoversion = dupstr(protoversion); - s->receiver = rcv; - s->impl_name = dupstr(impl_name); - s->vstring = strbuf_new(); - - /* - * We send our version string early if we can. But if it includes - * SSH-1, we can't, because we have to take the other end into - * account too (see below). - * - * In server mode, we do send early. - */ - s->send_early = server_mode || !ssh_version_includes_v1(protoversion); - - /* - * Override: we don't send our version string early if the server - * has a bug that will make it discard it. See for example - * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=991958 - */ - if (conf_get_int(s->conf, CONF_sshbug_dropstart) == FORCE_ON) - s->send_early = false; - - s->bpp.vt = &ssh_verstring_vtable; - ssh_bpp_common_setup(&s->bpp); - return &s->bpp; -} - -void ssh_verstring_free(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - conf_free(s->conf); - sfree(s->impl_name); - strbuf_free(s->vstring); - sfree(s->protoversion); - sfree(s->our_vstring); - sfree(s->our_protoversion); - sfree(s); -} - -static int ssh_versioncmp(const char *a, const 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; -} - -static bool ssh_version_includes_v1(const char *ver) -{ - return ssh_versioncmp(ver, "2.0") < 0; -} - -static bool ssh_version_includes_v2(const char *ver) -{ - return ssh_versioncmp(ver, "1.99") >= 0; -} - -static void ssh_verstring_send(struct ssh_verstring_state *s) -{ - BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ - char *p; - int sv_pos; - - /* - * Construct our outgoing version string. - */ - s->our_vstring = dupprintf( - "%.*s%s-%s%s", - (int)s->prefix_wanted.len, (const char *)s->prefix_wanted.ptr, - s->our_protoversion, s->impl_name, sshver); - sv_pos = s->prefix_wanted.len + strlen(s->our_protoversion) + 1; - - /* Convert minus signs and spaces in the software version string - * into underscores. */ - for (p = s->our_vstring + sv_pos; *p; p++) { - if (*p == '-' || *p == ' ') - *p = '_'; - } - -#ifdef FUZZING - /* - * Replace the first character of the string with an "I" if we're - * compiling this code for fuzzing - i.e. the protocol prefix - * becomes "ISH-" instead of "SSH-". - * - * This is irrelevant to any real client software (the only thing - * reading the output of PuTTY built for fuzzing is the fuzzer, - * which can adapt to whatever it sees anyway). But it's a safety - * precaution making it difficult to accidentally run such a - * version of PuTTY (which would be hugely insecure) against a - * live peer implementation. - * - * (So the replacement prefix "ISH" notionally stands for - * 'Insecure Shell', of course.) - */ - s->our_vstring[0] = 'I'; -#endif - - /* - * Now send that version string, plus trailing \r\n or just \n - * (the latter in SSH-1 mode). - */ - bufchain_add(s->bpp.out_raw, s->our_vstring, strlen(s->our_vstring)); - if (ssh_version_includes_v2(s->our_protoversion)) - bufchain_add(s->bpp.out_raw, "\015", 1); - bufchain_add(s->bpp.out_raw, "\012", 1); - - bpp_logevent("We claim version: %s", s->our_vstring); -} - -#define BPP_WAITFOR(minlen) do \ - { \ - bool success; \ - crMaybeWaitUntilV( \ - (success = (bufchain_size(s->bpp.in_raw) >= (minlen))) || \ - s->bpp.input_eof); \ - if (!success) \ - goto eof; \ - } while (0) - -void ssh_verstring_handle_input(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - - crBegin(s->crState); - - /* - * If we're sending our version string up front before seeing the - * other side's, then do it now. - */ - if (s->send_early) - ssh_verstring_send(s); - - /* - * Search for a line beginning with the protocol name prefix in - * the input. - */ - s->i = 0; - while (1) { - /* - * Every time round this loop, we're at the start of a new - * line, so look for the prefix. - */ - BPP_WAITFOR(s->prefix_wanted.len); - bufchain_fetch(s->bpp.in_raw, s->prefix, s->prefix_wanted.len); - if (!memcmp(s->prefix, s->prefix_wanted.ptr, s->prefix_wanted.len)) { - bufchain_consume(s->bpp.in_raw, s->prefix_wanted.len); - ssh_check_frozen(s->bpp.ssh); - break; - } - - /* - * If we didn't find it, consume data until we see a newline. - */ - while (1) { - ptrlen data; - char *nl; - - /* Wait to receive at least 1 byte, but then consume more - * than that if it's there. */ - BPP_WAITFOR(1); - data = bufchain_prefix(s->bpp.in_raw); - if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) { - bufchain_consume(s->bpp.in_raw, nl - (char *)data.ptr + 1); - ssh_check_frozen(s->bpp.ssh); - break; - } else { - bufchain_consume(s->bpp.in_raw, data.len); - ssh_check_frozen(s->bpp.ssh); - } - } - } - - s->found_prefix = true; - - /* - * Copy the greeting line so far into vstring. - */ - put_data(s->vstring, s->prefix_wanted.ptr, s->prefix_wanted.len); - - /* - * Now read the rest of the greeting line. - */ - s->i = 0; - do { - ptrlen data; - char *nl; - - BPP_WAITFOR(1); - data = bufchain_prefix(s->bpp.in_raw); - if ((nl = memchr(data.ptr, '\012', data.len)) != NULL) { - data.len = nl - (char *)data.ptr + 1; - } - - put_datapl(s->vstring, data); - bufchain_consume(s->bpp.in_raw, data.len); - ssh_check_frozen(s->bpp.ssh); - - } while (s->vstring->s[s->vstring->len-1] != '\012'); - - /* - * Trim \r and \n from the version string, and replace them with - * a NUL terminator. - */ - while (s->vstring->len > 0 && - (s->vstring->s[s->vstring->len-1] == '\015' || - s->vstring->s[s->vstring->len-1] == '\012')) - strbuf_shrink_by(s->vstring, 1); - - bpp_logevent("Remote version: %s", s->vstring->s); - - /* - * Pick out the protocol version and software version. The former - * goes in a separately allocated string, so that s->vstring - * remains intact for later use in key exchange; the latter is the - * tail of s->vstring, so it doesn't need to be allocated. - */ - { - const char *pv_start = s->vstring->s + s->prefix_wanted.len; - int pv_len = strcspn(pv_start, "-"); - s->protoversion = dupprintf("%.*s", pv_len, pv_start); - s->softwareversion = pv_start + pv_len; - if (*s->softwareversion) { - assert(*s->softwareversion == '-'); - s->softwareversion++; - } - } - - ssh_detect_bugs(s); - - /* - * Figure out what actual SSH protocol version we're speaking. - */ - if (ssh_version_includes_v2(s->our_protoversion) && - ssh_version_includes_v2(s->protoversion)) { - /* - * We're doing SSH-2. - */ - s->major_protoversion = 2; - } else if (ssh_version_includes_v1(s->our_protoversion) && - ssh_version_includes_v1(s->protoversion)) { - /* - * We're doing SSH-1. - */ - s->major_protoversion = 1; - - /* - * There are multiple minor versions of SSH-1, and the - * protocol does not specify that the minimum of client - * and server versions is used. So we must adjust our - * outgoing protocol version to be no higher than that of - * the other side. - */ - if (!s->send_early && - ssh_versioncmp(s->our_protoversion, s->protoversion) > 0) { - sfree(s->our_protoversion); - s->our_protoversion = dupstr(s->protoversion); - } - } else { - /* - * Unable to agree on a major protocol version at all. - */ - if (!ssh_version_includes_v2(s->our_protoversion)) { - ssh_sw_abort(s->bpp.ssh, - "SSH protocol version 1 required by our " - "configuration but not provided by remote"); - } else { - ssh_sw_abort(s->bpp.ssh, - "SSH protocol version 2 required by our " - "configuration but remote only provides " - "(old, insecure) SSH-1"); - } - crStopV; - } - - bpp_logevent("Using SSH protocol version %d", s->major_protoversion); - - if (!s->send_early) { - /* - * If we didn't send our version string early, construct and - * send it now, because now we know what it is. - */ - ssh_verstring_send(s); - } - - /* - * And we're done. Notify our receiver that we now know our - * protocol version. This will cause it to disconnect us from the - * input stream and ultimately free us, because our job is now - * done. - */ - s->receiver->got_ssh_version(s->receiver, s->major_protoversion); - return; - - eof: - ssh_remote_error(s->bpp.ssh, - "Remote side unexpectedly closed network connection"); - return; /* avoid touching s now it's been freed */ - - crFinishV; -} - -static PktOut *ssh_verstring_new_pktout(int type) -{ - unreachable("Should never try to send packets during SSH version " - "string exchange"); -} - -static void ssh_verstring_handle_output(BinaryPacketProtocol *bpp) -{ - if (pq_peek(&bpp->out_pq)) { - unreachable("Should never try to send packets during SSH version " - "string exchange"); - } -} - -/* - * Examine the remote side's version string, and compare it against a - * list of known buggy implementations. - */ -static void ssh_detect_bugs(struct ssh_verstring_state *s) -{ - BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */ - const char *imp = s->softwareversion; - - s->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(s->conf, CONF_sshbug_ignore1) == FORCE_ON || - (conf_get_int(s->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. - */ - s->remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE; - bpp_logevent("We believe remote version has SSH-1 ignore bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_plainpw1) == FORCE_ON || - (conf_get_int(s->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. - */ - s->remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD; - bpp_logevent("We believe remote version needs a " - "plain SSH-1 password"); - } - - if (conf_get_int(s->conf, CONF_sshbug_rsa1) == FORCE_ON || - (conf_get_int(s->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. - */ - s->remote_bugs |= BUG_CHOKES_ON_RSA; - bpp_logevent("We believe remote version can't handle SSH-1 " - "RSA authentication"); - } - - if (conf_get_int(s->conf, CONF_sshbug_hmac2) == FORCE_ON || - (conf_get_int(s->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. - */ - s->remote_bugs |= BUG_SSH2_HMAC; - bpp_logevent("We believe remote version has SSH-2 HMAC bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_derivekey2) == FORCE_ON || - (conf_get_int(s->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). - */ - s->remote_bugs |= BUG_SSH2_DERIVEKEY; - bpp_logevent("We believe remote version has SSH-2 " - "key-derivation bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_rsapad2) == FORCE_ON || - (conf_get_int(s->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. - */ - s->remote_bugs |= BUG_SSH2_RSA_PADDING; - bpp_logevent("We believe remote version has SSH-2 RSA padding bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_pksessid2) == FORCE_ON || - (conf_get_int(s->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. - */ - s->remote_bugs |= BUG_SSH2_PK_SESSIONID; - bpp_logevent("We believe remote version has SSH-2 " - "public-key-session-ID bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_rekey2) == FORCE_ON || - (conf_get_int(s->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. - */ - s->remote_bugs |= BUG_SSH2_REKEY; - bpp_logevent("We believe remote version has SSH-2 rekey bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_maxpkt2) == FORCE_ON || - (conf_get_int(s->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. - */ - s->remote_bugs |= BUG_SSH2_MAXPKT; - bpp_logevent("We believe remote version ignores SSH-2 " - "maximum packet size"); - } - - if (conf_get_int(s->conf, CONF_sshbug_ignore2) == FORCE_ON) { - /* - * Servers that don't support SSH2_MSG_IGNORE. Currently, - * none detected automatically. - */ - s->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE; - bpp_logevent("We believe remote version has SSH-2 ignore bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_oldgex2) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_oldgex2) == AUTO && - (wc_match("OpenSSH_2.[235]*", imp)))) { - /* - * These versions only support the original (pre-RFC4419) - * SSH-2 GEX request, and disconnect with a protocol error if - * we use the newer version. - */ - s->remote_bugs |= BUG_SSH2_OLDGEX; - bpp_logevent("We believe remote version has outdated SSH-2 GEX"); - } - - if (conf_get_int(s->conf, CONF_sshbug_winadj) == FORCE_ON) { - /* - * Servers that don't support our winadj request for one - * reason or another. Currently, none detected automatically. - */ - s->remote_bugs |= BUG_CHOKES_ON_WINADJ; - bpp_logevent("We believe remote version has winadj bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_chanreq) == FORCE_ON || - (conf_get_int(s->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 - */ - s->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY; - bpp_logevent("We believe remote version has SSH-2 " - "channel request bug"); - } - - if (conf_get_int(s->conf, CONF_sshbug_filter_kexinit) == FORCE_ON) { - s->remote_bugs |= BUG_REQUIRES_FILTERED_KEXINIT; - bpp_logevent("We believe remote version requires us to " - "filter our KEXINIT"); - } - - if (conf_get_int(s->conf, CONF_sshbug_rsa_sha2_cert_userauth) == FORCE_ON || - (conf_get_int(s->conf, CONF_sshbug_rsa_sha2_cert_userauth) == AUTO && - (wc_match("OpenSSH_7.[2-7]*", imp)))) { - /* - * These versions have the bug in which using RSA/SHA-2 - * authentication with a certified key requires the key - * algorithm to be sent as ssh-rsa-cert-... instead of - * rsa-sha2-NNN-cert-... - * - * OpenSSH 7.8 wants rsa-sha2-NNN-cert-...: - * https://github.com/openssh/openssh-portable/commit/4ba0d54794814ec0de1ec87987d0c3b89379b436 - * (also labelled "OpenBSD-Commit-ID: - * c6e9f6d45eed8962ad502d315d7eaef32c419dde") - * - * OpenSSH 7.2 was the first release supporting RSA/SHA-2 - * at all, so this bug is irrelevant to anything before that. - */ - s->remote_bugs |= BUG_RSA_SHA2_CERT_USERAUTH; - bpp_logevent("We believe remote version has SSH-2 " - "RSA/SHA-2/certificate userauth bug"); - } -} - -const char *ssh_verstring_get_remote(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - return s->vstring->s; -} - -const char *ssh_verstring_get_local(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - return s->our_vstring; -} - -int ssh_verstring_get_bugs(BinaryPacketProtocol *bpp) -{ - struct ssh_verstring_state *s = - container_of(bpp, struct ssh_verstring_state, bpp); - return s->remote_bugs; -} - -static void ssh_verstring_queue_disconnect(BinaryPacketProtocol *bpp, - const char *msg, int category) -{ - /* No way to send disconnect messages at this stage of the protocol! */ -} diff --git a/ssh/x11fwd.c b/ssh/x11fwd.c deleted file mode 100644 index c0ae59f18..000000000 --- a/ssh/x11fwd.c +++ /dev/null @@ -1,659 +0,0 @@ -/* - * Platform-independent bits of X11 forwarding. - */ - -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "channel.h" -#include "tree234.h" - -struct XDMSeen { - unsigned int time; - unsigned char clientid[6]; -}; - -typedef struct X11Connection { - 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; - bool verified; - bool input_wanted; - bool no_data_sent_to_x_client; - char *peer_addr; - int peer_port; - SshChannel *c; /* channel structure held by SSH backend */ - Socket *s; - - Plug plug; - Channel chan; -} X11Connection; - -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)); -} - -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) { - random_read(auth->data, auth->datalen); - 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) { - random_read(auth->data, 15); - auth->data[15] = auth->data[8]; - auth->data[8] = 0; - - 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 = NULL; - 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); - } -} - -#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 if (!proto[0]) { - /* - * If the user has attempted to connect to the forwarded X - * display with no authority at all, we can give a better - * error message than the generic "unsupported protocol". We - * at least _recognise_ the null auth protocol, even if we - * don't _accept_ it. - */ - return dupstr("No authorisation provided"); - } else { - return dupprintf("Unsupported authorisation protocol '%s'", proto); - } - - if ((auth = find234(authtree, &match_dummy, 0)) == NULL) - return dupstr("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 dupprintf("XDM-AUTHORIZATION-1 data was wrong length " - "(%d, expected 24)", dlen); - if (peer_port == -1) - return dupstr("cannot do XDM-AUTHORIZATION-1 without remote " - "address data"); - des_decrypt_xdmauth(auth->data+9, data, 24); - - /* Bitwise-OR together any mismatches in the fixed parts of - * the data, to allow checking it all at once */ - uint32_t mismatches = 0; - /* Check non-key half of auth cookie */ - for (i = 0; i < 8; i++) - mismatches |= auth->data[i] ^ data[i]; - /* Check IP address and port */ - mismatches |= GET_32BIT_MSB_FIRST(data+8) ^ peer_ip; - mismatches |= (unsigned short)(GET_16BIT_MSB_FIRST(data+12) ^ - peer_port); - /* Check zero padding */ - for (i = 18; i < 24; i++) - mismatches |= data[i]; - if (mismatches) - return dupstr("XDM-AUTHORIZATION-1 data failed check"); - - t = GET_32BIT_MSB_FIRST(data+14); - tim = time(NULL); - if (((unsigned long)t - (unsigned long)tim - + XDM_MAXSKEW) > 2*XDM_MAXSKEW) - return dupstr("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 dupstr("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; -} - -static void x11_log(Plug *p, PlugLogType 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 void x11_closing(Plug *plug, PlugCloseType type, const char *error_msg) -{ - struct X11Connection *xconn = container_of( - plug, struct X11Connection, plug); - - if (type != PLUGCLOSE_NORMAL) { - /* - * 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_initiate_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); - } -} - -static void x11_receive(Plug *plug, int urgent, const char *data, size_t len) -{ - struct X11Connection *xconn = container_of( - plug, struct X11Connection, plug); - - xconn->no_data_sent_to_x_client = false; - sshfwd_write(xconn->c, data, len); -} - -static void x11_sent(Plug *plug, size_t bufsize) -{ - struct X11Connection *xconn = container_of( - plug, struct X11Connection, plug); - - sshfwd_unthrottle(xconn->c, bufsize); -} - -static const PlugVtable X11Connection_plugvt = { - .log = x11_log, - .closing = x11_closing, - .receive = x11_receive, - .sent = x11_sent, -}; - -static void x11_chan_free(Channel *chan); -static size_t x11_send( - Channel *chan, bool is_stderr, const void *vdata, size_t len); -static void x11_send_eof(Channel *chan); -static void x11_set_input_wanted(Channel *chan, bool wanted); -static char *x11_log_close_msg(Channel *chan); - -static const ChannelVtable X11Connection_channelvt = { - .free = x11_chan_free, - .open_confirmation = chan_remotely_opened_confirmation, - .open_failed = chan_remotely_opened_failure, - .send = x11_send, - .send_eof = x11_send_eof, - .set_input_wanted = x11_set_input_wanted, - .log_close_msg = x11_log_close_msg, - .want_close = chan_default_want_close, - .rcvd_exit_status = chan_no_exit_status, - .rcvd_exit_signal = chan_no_exit_signal, - .rcvd_exit_signal_numeric = chan_no_exit_signal_numeric, - .run_shell = chan_no_run_shell, - .run_command = chan_no_run_command, - .run_subsystem = chan_no_run_subsystem, - .enable_x11_forwarding = chan_no_enable_x11_forwarding, - .enable_agent_forwarding = chan_no_enable_agent_forwarding, - .allocate_pty = chan_no_allocate_pty, - .set_env = chan_no_set_env, - .send_break = chan_no_send_break, - .send_signal = chan_no_send_signal, - .change_window_size = chan_no_change_window_size, - .request_response = chan_no_request_response, -}; - -/* - * Called to set up the X11Connection structure, though this does not - * yet connect to an actual server. - */ -Channel *x11_new_channel(tree234 *authtree, SshChannel *c, - const char *peeraddr, int peerport, - bool connection_sharing_possible) -{ - struct X11Connection *xconn; - - /* - * Open socket. - */ - xconn = snew(struct X11Connection); - xconn->plug.vt = &X11Connection_plugvt; - xconn->chan.vt = &X11Connection_channelvt; - xconn->chan.initial_fixed_window_size = - (connection_sharing_possible ? 128 : 0); - xconn->auth_protocol = NULL; - xconn->authtree = authtree; - xconn->verified = false; - xconn->data_read = 0; - xconn->input_wanted = true; - 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->chan; -} - -static void x11_chan_free(Channel *chan) -{ - assert(chan->vt == &X11Connection_channelvt); - X11Connection *xconn = container_of(chan, X11Connection, chan); - - 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); -} - -static void x11_set_input_wanted(Channel *chan, bool wanted) -{ - assert(chan->vt == &X11Connection_channelvt); - X11Connection *xconn = container_of(chan, X11Connection, chan); - - xconn->input_wanted = wanted; - if (xconn->s) - sk_set_frozen(xconn->s, !xconn->input_wanted); -} - -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_X11(xconn->firstpkt[0], reply + 6, msgsize >> 2);/* data len */ - memset(reply + 8, 0, msgsize); - memcpy(reply + 8, full_message, msglen); - sshfwd_write(xconn->c, reply, 8 + msgsize); - sshfwd_write_eof(xconn->c); - xconn->no_data_sent_to_x_client = false; - sfree(reply); - sfree(full_message); -} - -/* - * Called to send data down the raw connection. - */ -static size_t x11_send( - Channel *chan, bool is_stderr, const void *vdata, size_t len) -{ - assert(chan->vt == &X11Connection_channelvt); - X11Connection *xconn = container_of(chan, X11Connection, chan); - const char *data = (const char *)vdata; - - /* - * 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) { - char endian = xconn->firstpkt[0]; - xconn->auth_plen = GET_16BIT_X11(endian, xconn->firstpkt + 6); - xconn->auth_dlen = GET_16BIT_X11(endian, 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; - char *errmut; - 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; - char endian = xconn->firstpkt[0]; - - protomajor = GET_16BIT_X11(endian, xconn->firstpkt + 2); - protominor = GET_16BIT_X11(endian, 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 */ - - errmut = x11_verify(peer_ip, peer_port, - xconn->authtree, xconn->auth_protocol, - xconn->auth_data, xconn->auth_dlen, &auth_matched); - if (errmut) { - x11_send_init_error(xconn, errmut); - sfree(errmut); - 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. (This will - * have the side effect of freeing xconn.) - */ - 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. - */ - xconn->chan.initial_fixed_window_size = 0; - sshfwd_window_override_removed(xconn->c); - xconn->disp = auth_matched->disp; - xconn->s = new_connection(sk_addr_dup(xconn->disp->addr), - xconn->disp->realhost, xconn->disp->port, - false, true, false, false, &xconn->plug, - sshfwd_get_conf(xconn->c), NULL); - 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. - */ - socketdatalen = 0; /* placate compiler warning */ - 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 = true; - } - - /* - * After initialisation, just copy data simply. - */ - - return sk_write(xconn->s, data, len); -} - -static void x11_send_eof(Channel *chan) -{ - assert(chan->vt == &X11Connection_channelvt); - X11Connection *xconn = container_of(chan, X11Connection, chan); - - 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); - } -} - -static char *x11_log_close_msg(Channel *chan) -{ - return dupstr("Forwarded X11 connection terminated"); -} diff --git a/ssh/zlib.c b/ssh/zlib.c deleted file mode 100644 index 0841faa38..000000000 --- a/ssh/zlib.c +++ /dev/null @@ -1,1252 +0,0 @@ -/* - * 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 - -#include "defs.h" -#include "ssh.h" - -/* ---------------------------------------------------------------------- - * 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, - const unsigned char *data, int len); - -/* - * 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(const 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, - const unsigned char *data, int len) -{ - struct LZ77InternalContext *st = ctx->ictx; - int i, 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) { - - if (len >= HASHCHARS) { - /* - * Hash the next few characters. - */ - int 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; - } - - 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 { - strbuf *outbuf; - unsigned long outbits; - int noutbits; - bool firstblock; -}; - -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) { - put_byte(out->outbuf, 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 (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; - - 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 = lenof(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 = lenof(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); - } -} - -struct ssh_zlib_compressor { - struct LZ77Context ectx; - ssh_compressor sc; -}; - -static ssh_compressor *zlib_compress_init(void) -{ - struct Outbuf *out; - struct ssh_zlib_compressor *comp = snew(struct ssh_zlib_compressor); - - lz77_init(&comp->ectx); - comp->sc.vt = &ssh_zlib; - comp->ectx.literal = zlib_literal; - comp->ectx.match = zlib_match; - - out = snew(struct Outbuf); - out->outbuf = NULL; - out->outbits = out->noutbits = 0; - out->firstblock = true; - comp->ectx.userdata = out; - - return &comp->sc; -} - -static void zlib_compress_cleanup(ssh_compressor *sc) -{ - struct ssh_zlib_compressor *comp = - container_of(sc, struct ssh_zlib_compressor, sc); - struct Outbuf *out = (struct Outbuf *)comp->ectx.userdata; - if (out->outbuf) - strbuf_free(out->outbuf); - sfree(out); - sfree(comp->ectx.ictx); - sfree(comp); -} - -static void zlib_compress_block( - ssh_compressor *sc, const unsigned char *block, int len, - unsigned char **outblock, int *outlen, int minlen) -{ - struct ssh_zlib_compressor *comp = - container_of(sc, struct ssh_zlib_compressor, sc); - struct Outbuf *out = (struct Outbuf *) comp->ectx.userdata; - bool in_block; - - assert(!out->outbuf); - out->outbuf = strbuf_new_nm(); - - /* - * 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 = false; - - in_block = false; - } else - in_block = true; - - 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(&comp->ectx, block, len); - - /* - * 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 */ - - /* - * If we've been asked to pad out the compressed data until it's - * at least a given length, do so by emitting further empty static - * blocks. - */ - while (out->outbuf->len < minlen) { - outbits(out, 0, 7); /* close block */ - outbits(out, 2, 3); /* open new static block */ - } - - *outlen = out->outbuf->len; - *outblock = (unsigned char *)strbuf_to_str(out->outbuf); - out->outbuf = NULL; -} - -/* ---------------------------------------------------------------------- - * 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((size_t)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]; - - /* - * Array that accumulates the code lengths sent in the header of a - * dynamic-Huffman-tree block. - * - * There are 286 actual symbols in the literal/length alphabet - * (256 literals plus 20 length categories), and 30 symbols in the - * distance alphabet. However, the block header transmits the - * number of code lengths for the former alphabet as a 5-bit value - * HLIT to be added to 257, and the latter as a 5-bit value HDIST - * to be added to 1. This means that the number of _code lengths_ - * can go as high as 288 for the symbol alphabet and 32 for the - * distance alphabet - each of those values being 2 more than the - * maximum number of actual symbols. - * - * It's tempting to rule that sending out-of-range HLIT or HDIST - * is therefore just illegal, and to fault it when we initially - * receive that header. But instead I've chosen to permit the - * Huffman-code definition to include code length entries for - * those unused symbols; if a header of that form is transmitted, - * then the effect will be that in the main body of the block, - * some bit sequence(s) will generate an illegal symbol number, - * and _that_ will be faulted as a decoding error. - * - * Rationale: this can already happen! The standard Huffman code - * used in a _static_ block for the literal/length alphabet is - * defined in such a way that it includes codes for symbols 287 - * and 288, which are then never actually sent in the body of the - * block. And I think that if the standard static tree definition - * is willing to include Huffman codes that don't correspond to a - * symbol, then it's an excessive restriction on dynamic tables - * not to permit them to do the same. In particular, it would be - * strange for a dynamic block not to be able to exactly mimic - * either or both of the Huffman codes used by a static block for - * the corresponding alphabet. - * - * So we place no constraint on HLIT or HDIST during code - * construction, and we make this array large enough to include - * the maximum number of code lengths that can possibly arise as a - * result. It's only trying to _use_ the junk Huffman codes after - * table construction is completed that will provoke a decode - * error. - */ - unsigned char lengths[288 + 32]; - - unsigned long bits; - int nbits; - unsigned char window[WINSIZE]; - int winpos; - strbuf *outblk; - - ssh_decompressor dc; -}; - -static ssh_decompressor *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; - dctx->outblk = NULL; - - dctx->dc.vt = &ssh_zlib; - return &dctx->dc; -} - -static void zlib_decompress_cleanup(ssh_decompressor *dc) -{ - struct zlib_decompress_ctx *dctx = - container_of(dc, struct zlib_decompress_ctx, dc); - - 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); - if (dctx->outblk) - strbuf_free(dctx->outblk); - 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); - put_byte(dctx->outblk, c); -} - -#define EATBITS(n) ( dctx->nbits -= (n), dctx->bits >>= (n) ) - -static bool zlib_decompress_block( - ssh_decompressor *dc, const unsigned char *block, int len, - unsigned char **outblock, int *outlen) -{ - struct zlib_decompress_ctx *dctx = - container_of(dc, struct zlib_decompress_ctx, dc); - 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 - }; - - assert(!dctx->outblk); - dctx->outblk = strbuf_new_nm(); - - 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) { - dctx->state = GOTLENSYM; - dctx->sym = code; - } else { - /* literal/length symbols 286 and 287 are invalid */ - goto decode_error; - } - 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: - *outlen = dctx->outblk->len; - *outblock = (unsigned char *)strbuf_to_str(dctx->outblk); - dctx->outblk = NULL; - return true; - - decode_error: - *outblock = NULL; - *outlen = 0; - return false; -} - -const ssh_compression_alg ssh_zlib = { - .name = "zlib", - .delayed_name = "zlib@openssh.com", /* delayed version */ - .compress_new = zlib_compress_init, - .compress_free = zlib_compress_cleanup, - .compress = zlib_compress_block, - .decompress_new = zlib_decompress_init, - .decompress_free = zlib_decompress_cleanup, - .decompress = zlib_decompress_block, - .text_name = "zlib (RFC1950)", -}; diff --git a/sshcr.h b/sshcr.h deleted file mode 100644 index 12bfea6c5..000000000 --- a/sshcr.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Coroutine mechanics used in PuTTY's SSH code. - */ - -#ifndef PUTTY_SSHCR_H -#define PUTTY_SSHCR_H - -/* - * If these macros look impenetrable to you, you might find it helpful - * to read - * - * https://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: - * - * - 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) do { 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); } while (0) -#define crFinishV } *crLine = 0; return; } while (0) -#define crFinishFreed(z) } return (z); } while (0) -#define crFinishFreedV } return; } while (0) -#define crFinishFree(z) } sfree(s); return (z); } while (0) -#define crFinishFreeV } sfree(s); return; } while (0) -#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) - -/* - * The crMaybeWaitUntil macros could have been more easily written in - * terms of the simple crReturn above, by writing things like - * - * while (!condition) { crReturn(whatever); } - * - * (or do-while in the case of crWaitUntil). But it's better to do it - * directly by writing _once_ to crLine before first testing the - * condition, because this way it's robust against the condition check - * potentially freeing the entire coroutine state structure as a side - * effect (as long as it also evaluates false if it does that), - * because we don't write into crLine between the condition evaluating - * to false and the 'return' statement. - */ -#define crMaybeWaitUntil(c) \ - do { \ - *crLine =__LINE__; \ - case __LINE__: if (!(c)) return 0; \ - } while (0) -#define crMaybeWaitUntilV(c) \ - do { \ - *crLine =__LINE__; \ - case __LINE__: if (!(c)) return; \ - } while (0) -#define crWaitUntil(c) \ - do { \ - *crLine =__LINE__; return; \ - case __LINE__: if (!(c)) return 0; \ - } while (0) -#define crWaitUntilV(c) \ - do { \ - *crLine =__LINE__; return; \ - case __LINE__: if (!(c)) return; \ - } while (0) - -#endif /* PUTTY_SSHCR_H */ diff --git a/sshkeygen.h b/sshkeygen.h deleted file mode 100644 index 60b2e836b..000000000 --- a/sshkeygen.h +++ /dev/null @@ -1,300 +0,0 @@ -/* - * sshkeygen.h: routines used internally to key generation. - */ - -/* ---------------------------------------------------------------------- - * A table of all the primes that fit in a 16-bit integer. Call - * init_primes_array to make sure it's been initialised. - */ - -#define NSMALLPRIMES 6542 /* number of primes < 65536 */ -extern const unsigned short *const smallprimes; -void init_smallprimes(void); - -/* ---------------------------------------------------------------------- - * A system for making up random candidate integers during prime - * generation. This unconditionally ensures that the numbers have the - * right number of bits and are not divisible by any prime in the - * smallprimes[] array above. It can also impose further constraints, - * as documented below. - */ -typedef struct PrimeCandidateSource PrimeCandidateSource; - -/* - * pcs_new: you say how many bits you want the prime to have (with the - * usual semantics that an n-bit number is in the range [2^{n-1},2^n)) - * and also optionally specify what you want its topmost 'nfirst' bits - * to be. - * - * (The 'first' system is used for RSA keys, where you need to arrange - * that the product of your two primes is in a more tightly - * constrained range than the factor of 4 you'd get by just generating - * two (n/2)-bit primes and multiplying them.) - */ -PrimeCandidateSource *pcs_new(unsigned bits); -PrimeCandidateSource *pcs_new_with_firstbits(unsigned bits, - unsigned first, unsigned nfirst); - -/* Insist that generated numbers must be congruent to 'res' mod 'mod' */ -void pcs_require_residue(PrimeCandidateSource *s, mp_int *mod, mp_int *res); - -/* Convenience wrapper for the common case where res = 1 */ -void pcs_require_residue_1(PrimeCandidateSource *s, mp_int *mod); - -/* Same as pcs_require_residue_1, but also records that the modulus is - * known to be prime */ -void pcs_require_residue_1_mod_prime(PrimeCandidateSource *s, mp_int *mod); - -/* Insist that generated numbers must _not_ be congruent to 'res' mod - * 'mod'. This is used to avoid being 1 mod the RSA public exponent, - * which is small, so it only needs ordinary integer parameters. */ -void pcs_avoid_residue_small(PrimeCandidateSource *s, - unsigned mod, unsigned res); - -/* Exclude any prime that has no chance of being a Sophie Germain prime. */ -void pcs_try_sophie_germain(PrimeCandidateSource *s); - -/* Mark a PrimeCandidateSource as one-shot, so that the prime generation - * function will return NULL if an attempt fails, rather than looping. */ -void pcs_set_oneshot(PrimeCandidateSource *s); - -/* Prepare a PrimeCandidateSource to actually generate numbers. This - * function does last-minute computation that has to be delayed until - * all constraints have been input. */ -void pcs_ready(PrimeCandidateSource *s); - -/* Actually generate a candidate integer. You must free the result, of - * course. */ -mp_int *pcs_generate(PrimeCandidateSource *s); - -/* Free a PrimeCandidateSource. */ -void pcs_free(PrimeCandidateSource *s); - -/* Return some internal fields of the PCS. Used by testcrypt for - * unit-testing this system. */ -void pcs_inspect(PrimeCandidateSource *pcs, mp_int **limit_out, - mp_int **factor_out, mp_int **addend_out); - -/* Query functions for primegen to use */ -unsigned pcs_get_bits(PrimeCandidateSource *pcs); -unsigned pcs_get_bits_remaining(PrimeCandidateSource *pcs); -mp_int *pcs_get_upper_bound(PrimeCandidateSource *pcs); -mp_int **pcs_get_known_prime_factors(PrimeCandidateSource *pcs, size_t *nout); - -/* ---------------------------------------------------------------------- - * A system for doing Miller-Rabin probabilistic primality tests. - * These benefit from having set up some context beforehand, if you're - * going to do more than one of them on the same candidate prime, so - * we declare an object type here to store that context. - */ - -typedef struct MillerRabin MillerRabin; - -/* Make and free a Miller-Rabin context. */ -MillerRabin *miller_rabin_new(mp_int *p); -void miller_rabin_free(MillerRabin *mr); - -/* Perform a single Miller-Rabin test, using a specified witness value. - * Used in the test suite. */ -struct mr_result { - unsigned passed; - unsigned potential_primitive_root; -}; -struct mr_result miller_rabin_test(MillerRabin *mr, mp_int *w); - -/* Perform a single Miller-Rabin test, using a random witness value. */ -bool miller_rabin_test_random(MillerRabin *mr); - -/* Suggest how many tests are needed to make it sufficiently unlikely - * that a composite number will pass them all */ -unsigned miller_rabin_checks_needed(unsigned bits); - -/* An extension to the M-R test, which iterates until it either finds - * a witness value that is potentially a primitive root, or one - * that proves the number to be composite. */ -mp_int *miller_rabin_find_potential_primitive_root(MillerRabin *mr); - -/* ---------------------------------------------------------------------- - * A system for proving numbers to be prime, using the Pocklington - * test, which requires knowing a partial factorisation of p-1 - * (specifically, factors whose product is at least cbrt(p)) and a - * primitive root. - * - * The API consists of instantiating a 'Pockle' object, which - * internally stores a list of numbers you've already convinced it is - * prime, and can accept further primes if you give a satisfactory - * certificate of their primality based on primes it already knows - * about. - */ - -typedef struct Pockle Pockle; - -/* In real use, you only really need to know whether the Pockle - * successfully accepted your prime. But for testcrypt, it's useful to - * expose many different failure modes so we can try to provoke them - * all in unit tests and check they're working. */ -#define POCKLE_STATUSES(X) \ - X(POCKLE_OK) \ - X(POCKLE_SMALL_PRIME_NOT_SMALL) \ - X(POCKLE_SMALL_PRIME_NOT_PRIME) \ - X(POCKLE_PRIME_SMALLER_THAN_2) \ - X(POCKLE_FACTOR_NOT_KNOWN_PRIME) \ - X(POCKLE_FACTOR_NOT_A_FACTOR) \ - X(POCKLE_PRODUCT_OF_FACTORS_TOO_SMALL) \ - X(POCKLE_FERMAT_TEST_FAILED) \ - X(POCKLE_DISCRIMINANT_IS_SQUARE) \ - X(POCKLE_WITNESS_POWER_IS_1) \ - X(POCKLE_WITNESS_POWER_NOT_COPRIME) \ - /* end of list */ - -#define DEFINE_ENUM(id) id, -typedef enum PockleStatus { POCKLE_STATUSES(DEFINE_ENUM) } PockleStatus; -#undef DEFINE_ENUM - -/* Make a new empty Pockle, containing no primes. */ -Pockle *pockle_new(void); - -/* Insert a prime below 2^32 into the Pockle. No evidence is required: - * Pockle will check it itself. */ -PockleStatus pockle_add_small_prime(Pockle *pockle, mp_int *p); - -/* Insert a general prime into the Pockle. You must provide a list of - * prime factors of p-1, whose product exceeds the cube root of p, and - * also a primitive root mod p. */ -PockleStatus pockle_add_prime(Pockle *pockle, mp_int *p, - mp_int **factors, size_t nfactors, - mp_int *primitive_root); - -/* If you call pockle_mark, and later pass the returned value to - * pockle_release, it will free all the primes that were added to the - * Pockle between those two calls. Useful in recursive algorithms, to - * stop the Pockle growing unboundedly if the recursion keeps having - * to backtrack. */ -size_t pockle_mark(Pockle *pockle); -void pockle_release(Pockle *pockle, size_t mark); - -/* Free a Pockle. */ -void pockle_free(Pockle *pockle); - -/* Generate a certificate of primality for a prime already known to - * the Pockle, in a format acceptable to Math::Prime::Util. */ -strbuf *pockle_mpu(Pockle *pockle, mp_int *p); - -/* ---------------------------------------------------------------------- - * Callback API that allows key generation to report progress to its - * caller. - */ - -typedef struct ProgressReceiverVtable ProgressReceiverVtable; -typedef struct ProgressReceiver ProgressReceiver; -typedef union ProgressPhase ProgressPhase; - -union ProgressPhase { - int n; - void *p; -}; - -struct ProgressReceiver { - const ProgressReceiverVtable *vt; -}; - -struct ProgressReceiverVtable { - ProgressPhase (*add_linear)(ProgressReceiver *prog, double overall_cost); - ProgressPhase (*add_probabilistic)(ProgressReceiver *prog, - double cost_per_attempt, - double attempt_probability); - void (*ready)(ProgressReceiver *prog); - void (*start_phase)(ProgressReceiver *prog, ProgressPhase phase); - void (*report)(ProgressReceiver *prog, double progress); - void (*report_attempt)(ProgressReceiver *prog); - void (*report_phase_complete)(ProgressReceiver *prog); -}; - -static inline ProgressPhase progress_add_linear(ProgressReceiver *prog, - double c) -{ return prog->vt->add_linear(prog, c); } -static inline ProgressPhase progress_add_probabilistic(ProgressReceiver *prog, - double c, double p) -{ return prog->vt->add_probabilistic(prog, c, p); } -static inline void progress_ready(ProgressReceiver *prog) -{ prog->vt->ready(prog); } -static inline void progress_start_phase( - ProgressReceiver *prog, ProgressPhase phase) -{ prog->vt->start_phase(prog, phase); } -static inline void progress_report(ProgressReceiver *prog, double progress) -{ prog->vt->report(prog, progress); } -static inline void progress_report_attempt(ProgressReceiver *prog) -{ prog->vt->report_attempt(prog); } -static inline void progress_report_phase_complete(ProgressReceiver *prog) -{ prog->vt->report_phase_complete(prog); } - -ProgressPhase null_progress_add_linear( - ProgressReceiver *prog, double c); -ProgressPhase null_progress_add_probabilistic( - ProgressReceiver *prog, double c, double p); -void null_progress_ready(ProgressReceiver *prog); -void null_progress_start_phase(ProgressReceiver *prog, ProgressPhase phase); -void null_progress_report(ProgressReceiver *prog, double progress); -void null_progress_report_attempt(ProgressReceiver *prog); -void null_progress_report_phase_complete(ProgressReceiver *prog); -extern const ProgressReceiverVtable null_progress_vt; - -/* A helper function for dreaming up progress cost estimates. */ -double estimate_modexp_cost(unsigned bits); - -/* ---------------------------------------------------------------------- - * The top-level API for generating primes. - */ - -typedef struct PrimeGenerationPolicy PrimeGenerationPolicy; -typedef struct PrimeGenerationContext PrimeGenerationContext; - -struct PrimeGenerationContext { - const PrimeGenerationPolicy *vt; -}; - -struct PrimeGenerationPolicy { - ProgressPhase (*add_progress_phase)(const PrimeGenerationPolicy *policy, - ProgressReceiver *prog, unsigned bits); - PrimeGenerationContext *(*new_context)( - const PrimeGenerationPolicy *policy); - void (*free_context)(PrimeGenerationContext *ctx); - mp_int *(*generate)( - PrimeGenerationContext *ctx, - PrimeCandidateSource *pcs, ProgressReceiver *prog); - strbuf *(*mpu_certificate)(PrimeGenerationContext *ctx, mp_int *p); - - const void *extra; /* additional data a particular impl might need */ -}; - -static inline ProgressPhase primegen_add_progress_phase( - PrimeGenerationContext *ctx, ProgressReceiver *prog, unsigned bits) -{ return ctx->vt->add_progress_phase(ctx->vt, prog, bits); } -static inline PrimeGenerationContext *primegen_new_context( - const PrimeGenerationPolicy *policy) -{ return policy->new_context(policy); } -static inline void primegen_free_context(PrimeGenerationContext *ctx) -{ ctx->vt->free_context(ctx); } -static inline mp_int *primegen_generate( - PrimeGenerationContext *ctx, - PrimeCandidateSource *pcs, ProgressReceiver *prog) -{ return ctx->vt->generate(ctx, pcs, prog); } -static inline strbuf *primegen_mpu_certificate( - PrimeGenerationContext *ctx, mp_int *p) -{ return ctx->vt->mpu_certificate(ctx, p); } - -extern const PrimeGenerationPolicy primegen_probabilistic; -extern const PrimeGenerationPolicy primegen_provable_fast; -extern const PrimeGenerationPolicy primegen_provable_maurer_simple; -extern const PrimeGenerationPolicy primegen_provable_maurer_complex; - -/* ---------------------------------------------------------------------- - * The overall top-level API for generating entire key pairs. - */ - -int rsa_generate(RSAKey *key, int bits, bool strong, - PrimeGenerationContext *pgc, ProgressReceiver *prog); -int dsa_generate(struct dsa_key *key, int bits, PrimeGenerationContext *pgc, - ProgressReceiver *prog); -int ecdsa_generate(struct ecdsa_key *key, int bits); -int eddsa_generate(struct eddsa_key *key, int bits); diff --git a/sshpubk.c b/sshpubk.c deleted file mode 100644 index 0648b4c4e..000000000 --- a/sshpubk.c +++ /dev/null @@ -1,1975 +0,0 @@ -/* - * 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 -#include -#include - -#include "putty.h" -#include "mpint.h" -#include "ssh.h" -#include "misc.h" - -/* - * Fairly arbitrary size limit on any public or private key blob. - * Chosen to match AGENT_MAX_MSGLEN, on the basis that any key too - * large to transfer over the ssh-agent protocol is probably too large - * to be useful in general. - * - * MAX_KEY_BLOB_LINES is the corresponding limit on the Public-Lines - * or Private-Lines header field in a key file. - */ -#define MAX_KEY_BLOB_SIZE 262144 -#define MAX_KEY_BLOB_LINES (MAX_KEY_BLOB_SIZE / 48) - -/* - * Corresponding limit on the size of a key _file_ itself, based on - * base64-encoding the key blob and then adding a few Kb for - * surrounding metadata. - */ -#define MAX_KEY_FILE_SIZE (MAX_KEY_BLOB_SIZE * 4 / 3 + 4096) - -static const ptrlen rsa1_signature = - PTRLEN_DECL_LITERAL("SSH PRIVATE KEY FILE FORMAT 1.1\n\0"); - -#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 ) - -LoadedFile *lf_new(size_t max_size) -{ - LoadedFile *lf = snew_plus(LoadedFile, max_size); - lf->data = snew_plus_get_aux(lf); - lf->len = 0; - lf->max_size = max_size; - return lf; -} - -void lf_free(LoadedFile *lf) -{ - smemclr(lf->data, lf->max_size); - smemclr(lf, sizeof(LoadedFile)); - sfree(lf); -} - -LoadFileStatus lf_load_fp(LoadedFile *lf, FILE *fp) -{ - lf->len = 0; - while (lf->len < lf->max_size) { - size_t retd = fread(lf->data + lf->len, 1, lf->max_size - lf->len, fp); - if (ferror(fp)) - return LF_ERROR; - - if (retd == 0) - break; - - lf->len += retd; - } - - LoadFileStatus status = LF_OK; - - if (lf->len == lf->max_size) { - /* The file might be too long to fit in our fixed-size - * structure. Try reading one more byte, to check. */ - if (fgetc(fp) != EOF) - status = LF_TOO_BIG; - } - - BinarySource_INIT(lf, lf->data, lf->len); - - return status; -} - -LoadFileStatus lf_load(LoadedFile *lf, const Filename *filename) -{ - FILE *fp = f_open(filename, "rb", false); - if (!fp) - return LF_ERROR; - - LoadFileStatus status = lf_load_fp(lf, fp); - fclose(fp); - return status; -} - -static inline bool lf_load_keyfile_helper(LoadFileStatus status, - const char **errptr) -{ - const char *error; - switch (status) { - case LF_OK: - return true; - case LF_TOO_BIG: - error = "file is too large to be a key file"; - break; - case LF_ERROR: - error = strerror(errno); - break; - default: - unreachable("bad status value in lf_load_keyfile_helper"); - } - if (errptr) - *errptr = error; - return false; -} - -LoadedFile *lf_load_keyfile(const Filename *filename, const char **errptr) -{ - LoadedFile *lf = lf_new(MAX_KEY_FILE_SIZE); - if (!lf_load_keyfile_helper(lf_load(lf, filename), errptr)) { - lf_free(lf); - return NULL; - } - return lf; -} - -LoadedFile *lf_load_keyfile_fp(FILE *fp, const char **errptr) -{ - LoadedFile *lf = lf_new(MAX_KEY_FILE_SIZE); - if (!lf_load_keyfile_helper(lf_load_fp(lf, fp), errptr)) { - lf_free(lf); - return NULL; - } - return lf; -} - -static bool expect_signature(BinarySource *src, ptrlen realsig) -{ - ptrlen thissig = get_data(src, realsig.len); - return !get_err(src) && ptrlen_eq_ptrlen(realsig, thissig); -} - -static int rsa1_load_s_internal(BinarySource *src, RSAKey *key, bool pub_only, - char **commentptr, const char *passphrase, - const char **error) -{ - strbuf *buf = NULL; - int ciphertype; - int ret = 0; - ptrlen comment; - - *error = "not an SSH-1 RSA file"; - - if (!expect_signature(src, rsa1_signature)) - goto end; - - *error = "file format error"; - - /* One byte giving encryption type, and one reserved uint32. */ - ciphertype = get_byte(src); - if (ciphertype != 0 && ciphertype != SSH1_CIPHER_3DES) - goto end; - if (get_uint32(src) != 0) - goto end; /* reserved field nonzero, panic! */ - - /* Now the serious stuff. An ordinary SSH-1 public key. */ - get_rsa_ssh1_pub(src, key, RSA_SSH1_MODULUS_FIRST); - - /* Next, the comment field. */ - comment = get_string(src); - if (commentptr) - *commentptr = mkstr(comment); - if (key) - key->comment = mkstr(comment); - - if (pub_only) { - ret = 1; - goto end; - } - - if (!key) { - ret = ciphertype != 0; - *error = NULL; - goto end; - } - - /* - * Decrypt remainder of buffer. - */ - if (ciphertype) { - size_t enclen = get_avail(src); - if (enclen & 7) - goto end; - - buf = strbuf_dup_nm(get_data(src, enclen)); - - unsigned char keybuf[16]; - hash_simple(&ssh_md5, ptrlen_from_asciz(passphrase), keybuf); - des3_decrypt_pubkey(keybuf, buf->u, enclen); - smemclr(keybuf, sizeof(keybuf)); /* burn the evidence */ - - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(buf)); - } - - /* - * We are now in the secret part of the key. The first four - * bytes should be of the form a, b, a, b. - */ - { - int b0a = get_byte(src); - int b1a = get_byte(src); - int b0b = get_byte(src); - int b1b = get_byte(src); - if (b0a != b0b || b1a != b1b) { - *error = "wrong passphrase"; - ret = -1; - goto end; - } - } - - /* - * After that, we have one further bignum which is our - * decryption exponent, and then the three auxiliary values - * (iqmp, q, p). - */ - get_rsa_ssh1_priv(src, key); - key->iqmp = get_mp_ssh1(src); - key->q = get_mp_ssh1(src); - key->p = get_mp_ssh1(src); - - if (!rsa_verify(key)) { - *error = "rsa_verify failed"; - freersakey(key); - ret = 0; - } else { - *error = NULL; - ret = 1; - } - - end: - if (buf) - strbuf_free(buf); - return ret; -} - -int rsa1_load_s(BinarySource *src, RSAKey *key, - const char *passphrase, const char **errstr) -{ - return rsa1_load_s_internal(src, key, false, NULL, passphrase, errstr); -} - -int rsa1_load_f(const Filename *filename, RSAKey *key, - const char *passphrase, const char **errstr) -{ - LoadedFile *lf = lf_load_keyfile(filename, errstr); - if (!lf) - return false; - - int toret = rsa1_load_s(BinarySource_UPCAST(lf), key, passphrase, errstr); - lf_free(lf); - return toret; -} - -/* - * See whether an RSA key is encrypted. Return its comment field as - * well. - */ -bool rsa1_encrypted_s(BinarySource *src, char **comment) -{ - const char *dummy; - return rsa1_load_s_internal(src, NULL, false, comment, NULL, &dummy) == 1; -} - -bool rsa1_encrypted_f(const Filename *filename, char **comment) -{ - LoadedFile *lf = lf_load_keyfile(filename, NULL); - if (!lf) - return false; /* couldn't even open the file */ - - bool toret = rsa1_encrypted_s(BinarySource_UPCAST(lf), comment); - lf_free(lf); - return toret; -} - -/* - * Read the public part of an SSH-1 RSA key from a file (public or - * private), and generate its public blob in exponent-first order. - */ -int rsa1_loadpub_s(BinarySource *src, BinarySink *bs, - char **commentptr, const char **errorstr) -{ - RSAKey key; - int ret; - const char *error = NULL; - - /* Default return if we fail. */ - ret = 0; - - bool is_privkey_file = expect_signature(src, rsa1_signature); - BinarySource_REWIND(src); - - if (is_privkey_file) { - /* - * Load just the public half from an SSH-1 private key file. - */ - memset(&key, 0, sizeof(key)); - if (rsa1_load_s_internal(src, &key, true, commentptr, NULL, &error)) { - rsa_ssh1_public_blob(bs, &key, RSA_SSH1_EXPONENT_FIRST); - freersakey(&key); - ret = 1; - } - } else { - /* - * Try interpreting the file as an SSH-1 public key. - */ - char *line, *p, *bitsp, *expp, *modp, *commentp; - - line = mkstr(get_chomped_line(src)); - p = line; - - bitsp = p; - p += strspn(p, "0123456789"); - if (*p != ' ') - goto not_public_either; - *p++ = '\0'; - - expp = p; - p += strspn(p, "0123456789"); - if (*p != ' ') - goto not_public_either; - *p++ = '\0'; - - modp = p; - p += strspn(p, "0123456789"); - if (*p) { - if (*p != ' ') - goto not_public_either; - *p++ = '\0'; - commentp = p; - } else { - commentp = NULL; - } - - memset(&key, 0, sizeof(key)); - key.exponent = mp_from_decimal(expp); - key.modulus = mp_from_decimal(modp); - if (atoi(bitsp) != mp_get_nbits(key.modulus)) { - mp_free(key.exponent); - mp_free(key.modulus); - sfree(line); - error = "key bit count does not match in SSH-1 public key file"; - goto end; - } - if (commentptr) - *commentptr = commentp ? dupstr(commentp) : NULL; - rsa_ssh1_public_blob(bs, &key, RSA_SSH1_EXPONENT_FIRST); - freersakey(&key); - sfree(line); - return 1; - - not_public_either: - sfree(line); - error = "not an SSH-1 RSA file"; - } - - end: - if ((ret != 1) && errorstr) - *errorstr = error; - return ret; -} - -int rsa1_loadpub_f(const Filename *filename, BinarySink *bs, - char **commentptr, const char **errorstr) -{ - LoadedFile *lf = lf_load_keyfile(filename, errorstr); - if (!lf) - return 0; - - int toret = rsa1_loadpub_s(BinarySource_UPCAST(lf), bs, - commentptr, errorstr); - lf_free(lf); - return toret; -} - -strbuf *rsa1_save_sb(RSAKey *key, const char *passphrase) -{ - strbuf *buf = strbuf_new_nm(); - int estart; - - /* - * The public part of the key. - */ - put_datapl(buf, rsa1_signature); - put_byte(buf, passphrase ? SSH1_CIPHER_3DES : 0); /* encryption type */ - put_uint32(buf, 0); /* reserved */ - rsa_ssh1_public_blob(BinarySink_UPCAST(buf), key, - RSA_SSH1_MODULUS_FIRST); - put_stringz(buf, NULLTOEMPTY(key->comment)); - - /* - * The encrypted portion starts here. - */ - estart = buf->len; - - /* - * Two bytes, then the same two bytes repeated. - */ - { - uint8_t bytes[2]; - random_read(bytes, 2); - put_data(buf, bytes, 2); - put_data(buf, bytes, 2); - } - - /* - * Four more bignums: the decryption exponent, then iqmp, then - * q, then p. - */ - put_mp_ssh1(buf, key->private_exponent); - put_mp_ssh1(buf, key->iqmp); - put_mp_ssh1(buf, key->q); - put_mp_ssh1(buf, key->p); - - /* - * Now write zeros until the encrypted portion is a multiple of - * 8 bytes. - */ - put_padding(buf, (estart - buf->len) & 7, 0); - - /* - * Now encrypt the encrypted portion. - */ - if (passphrase) { - unsigned char keybuf[16]; - - hash_simple(&ssh_md5, ptrlen_from_asciz(passphrase), keybuf); - des3_encrypt_pubkey(keybuf, buf->u + estart, buf->len - estart); - smemclr(keybuf, sizeof(keybuf)); /* burn the evidence */ - } - - return buf; -} - -/* - * Save an RSA key file. Return true on success. - */ -bool rsa1_save_f(const Filename *filename, RSAKey *key, const char *passphrase) -{ - FILE *fp = f_open(filename, "wb", true); - if (!fp) - return false; - - strbuf *buf = rsa1_save_sb(key, passphrase); - bool toret = fwrite(buf->s, 1, buf->len, fp) == buf->len; - if (fclose(fp)) - toret = false; - strbuf_free(buf); - return toret; -} - -/* ---------------------------------------------------------------------- - * SSH-2 private key load/store functions. - * - * PuTTY's own file format for SSH-2 keys is given in doc/ppk.but, aka - * the "PPK file format" appendix in the PuTTY manual. - */ - -static bool read_header(BinarySource *src, char *header) -{ - int len = 39; - int c; - - while (1) { - c = get_byte(src); - if (c == '\n' || c == '\r' || get_err(src)) - return false; /* failure */ - if (c == ':') { - c = get_byte(src); - if (c != ' ') - return false; - *header = '\0'; - return true; /* success! */ - } - if (len == 0) - return false; /* failure */ - *header++ = c; - len--; - } - return false; /* failure */ -} - -static char *read_body(BinarySource *src) -{ - strbuf *buf = strbuf_new_nm(); - - while (1) { - int c = get_byte(src); - if (c == '\r' || c == '\n' || get_err(src)) { - if (!get_err(src)) { - c = get_byte(src); - if (c != '\r' && c != '\n' && !get_err(src)) - src->pos--; - } - return strbuf_to_str(buf); - } - put_byte(buf, c); - } -} - -static bool read_blob(BinarySource *src, int nlines, BinarySink *bs) -{ - unsigned char *blob; - char *line; - int linelen; - int i, j, k; - - /* We expect at most 64 base64 characters, ie 48 real bytes, per line. */ - assert(nlines < MAX_KEY_BLOB_LINES); - blob = snewn(48 * nlines, unsigned char); - - for (i = 0; i < nlines; i++) { - line = read_body(src); - if (!line) { - sfree(blob); - return false; - } - linelen = strlen(line); - if (linelen % 4 != 0 || linelen > 64) { - sfree(blob); - sfree(line); - return false; - } - for (j = 0; j < linelen; j += 4) { - unsigned char decoded[3]; - k = base64_decode_atom(line + j, decoded); - if (!k) { - sfree(line); - sfree(blob); - return false; - } - put_data(bs, decoded, k); - } - sfree(line); - } - sfree(blob); - return true; -} - -/* - * Magic error return value for when the passphrase is wrong. - */ -ssh2_userkey ssh2_wrong_passphrase = { NULL, NULL }; - -const ssh_keyalg *const all_keyalgs[] = { - &ssh_rsa, - &ssh_rsa_sha256, - &ssh_rsa_sha512, - &ssh_dsa, - &ssh_ecdsa_nistp256, - &ssh_ecdsa_nistp384, - &ssh_ecdsa_nistp521, - &ssh_ecdsa_ed25519, - &ssh_ecdsa_ed448, - &opensshcert_ssh_dsa, - &opensshcert_ssh_rsa, - &opensshcert_ssh_rsa_sha256, - &opensshcert_ssh_rsa_sha512, - &opensshcert_ssh_ecdsa_ed25519, - &opensshcert_ssh_ecdsa_nistp256, - &opensshcert_ssh_ecdsa_nistp384, - &opensshcert_ssh_ecdsa_nistp521, -}; -const size_t n_keyalgs = lenof(all_keyalgs); - -const ssh_keyalg *find_pubkey_alg_len(ptrlen name) -{ - for (size_t i = 0; i < n_keyalgs; i++) - if (ptrlen_eq_string(name, all_keyalgs[i]->ssh_id)) - return all_keyalgs[i]; - - return NULL; -} - -const ssh_keyalg *find_pubkey_alg(const char *name) -{ - return find_pubkey_alg_len(ptrlen_from_asciz(name)); -} - -ptrlen pubkey_blob_to_alg_name(ptrlen blob) -{ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, blob); - return get_string(src); -} - -const ssh_keyalg *pubkey_blob_to_alg(ptrlen blob) -{ - return find_pubkey_alg_len(pubkey_blob_to_alg_name(blob)); -} - -struct ppk_cipher { - const char *name; - size_t blocklen, keylen, ivlen; -}; -static const struct ppk_cipher ppk_cipher_none = { "none", 1, 0, 0 }; -static const struct ppk_cipher ppk_cipher_aes256_cbc = { "aes256-cbc", 16, 32, 16 }; - -static void ssh2_ppk_derive_keys( - unsigned fmt_version, const struct ppk_cipher *ciphertype, - ptrlen passphrase, strbuf *storage, ptrlen *cipherkey, ptrlen *cipheriv, - ptrlen *mackey, ptrlen passphrase_salt, ppk_save_parameters *params) -{ - size_t mac_keylen; - - switch (fmt_version) { - case 3: { - if (ciphertype->keylen == 0) { - mac_keylen = 0; - break; - } - ptrlen empty = PTRLEN_LITERAL(""); - - mac_keylen = 32; - - uint32_t taglen = ciphertype->keylen + ciphertype->ivlen + mac_keylen; - - if (params->argon2_passes_auto) { - uint32_t passes; - - argon2_choose_passes( - params->argon2_flavour, params->argon2_mem, - params->argon2_milliseconds, &passes, - params->argon2_parallelism, taglen, - passphrase, passphrase_salt, empty, empty, storage); - - params->argon2_passes_auto = false; - params->argon2_passes = passes; - } else { - argon2(params->argon2_flavour, params->argon2_mem, - params->argon2_passes, params->argon2_parallelism, taglen, - passphrase, passphrase_salt, empty, empty, storage); - } - - break; - } - - case 2: - case 1: { - /* Counter-mode iteration to generate cipher key data. */ - for (unsigned ctr = 0; ctr * 20 < ciphertype->keylen; ctr++) { - ssh_hash *h = ssh_hash_new(&ssh_sha1); - put_uint32(h, ctr); - put_datapl(h, passphrase); - ssh_hash_final(h, strbuf_append(storage, 20)); - } - strbuf_shrink_to(storage, ciphertype->keylen); - - /* In this version of the format, the CBC IV was always all 0. */ - put_padding(storage, ciphertype->ivlen, 0); - - /* Completely separate hash for the MAC key. */ - ssh_hash *h = ssh_hash_new(&ssh_sha1); - mac_keylen = ssh_hash_alg(h)->hlen; - put_datapl(h, PTRLEN_LITERAL("putty-private-key-file-mac-key")); - put_datapl(h, passphrase); - ssh_hash_final(h, strbuf_append(storage, mac_keylen)); - - break; - } - - default: - unreachable("bad format version in ssh2_ppk_derive_keys"); - } - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(storage)); - *cipherkey = get_data(src, ciphertype->keylen); - *cipheriv = get_data(src, ciphertype->ivlen); - *mackey = get_data(src, mac_keylen); -} - -static int userkey_parse_line_counter(const char *text) -{ - char *endptr; - unsigned long ul = strtoul(text, &endptr, 10); - if (*text && !*endptr && ul < MAX_KEY_BLOB_LINES) - return ul; - else - return -1; -} - -static bool str_to_uint32_t(const char *s, uint32_t *out) -{ - char *endptr; - unsigned long converted = strtoul(s, &endptr, 10); - if (*s && !*endptr && converted <= ~(uint32_t)0) { - *out = converted; - return true; - } else { - return false; - } -} - -ssh2_userkey *ppk_load_s(BinarySource *src, const char *passphrase, - const char **errorstr) -{ - char header[40], *b, *encryption, *comment, *mac; - const ssh_keyalg *alg; - ssh2_userkey *ret; - strbuf *public_blob, *private_blob, *cipher_mac_keys_blob; - strbuf *passphrase_salt = strbuf_new(); - ptrlen cipherkey, cipheriv, mackey; - const struct ppk_cipher *ciphertype; - int i; - bool is_mac; - unsigned fmt_version; - const char *error = NULL; - ppk_save_parameters params; - - ret = NULL; /* return NULL for most errors */ - encryption = comment = mac = NULL; - public_blob = private_blob = cipher_mac_keys_blob = NULL; - - /* Read the first header line which contains the key type. */ - if (!read_header(src, header)) { - error = "no header line found in key file"; - goto error; - } - if (0 == strcmp(header, "PuTTY-User-Key-File-3")) { - fmt_version = 3; - } else if (0 == strcmp(header, "PuTTY-User-Key-File-2")) { - fmt_version = 2; - } else if (0 == strcmp(header, "PuTTY-User-Key-File-1")) { - /* this is an old key file; warn and then continue */ - old_keyfile_warning(); - fmt_version = 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(src)) == 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(src, header) || 0 != strcmp(header, "Encryption")) - goto error; - if ((encryption = read_body(src)) == NULL) - goto error; - if (!strcmp(encryption, "aes256-cbc")) { - ciphertype = &ppk_cipher_aes256_cbc; - } else if (!strcmp(encryption, "none")) { - ciphertype = &ppk_cipher_none; - } else { - goto error; - } - - /* Read the Comment header line. */ - if (!read_header(src, header) || 0 != strcmp(header, "Comment")) - goto error; - if ((comment = read_body(src)) == NULL) - goto error; - - memset(¶ms, 0, sizeof(params)); /* in particular, sets - * passes_auto=false */ - - /* Read the Public-Lines header line and the public blob. */ - if (!read_header(src, header) || 0 != strcmp(header, "Public-Lines")) - goto error; - if ((b = read_body(src)) == NULL) - goto error; - i = userkey_parse_line_counter(b); - sfree(b); - if (i < 0) - goto error; - public_blob = strbuf_new(); - if (!read_blob(src, i, BinarySink_UPCAST(public_blob))) - goto error; - - if (fmt_version >= 3 && ciphertype->keylen != 0) { - /* Read Argon2 key derivation parameters. */ - if (!read_header(src, header) || 0 != strcmp(header, "Key-Derivation")) - goto error; - if ((b = read_body(src)) == NULL) - goto error; - if (!strcmp(b, "Argon2d")) { - params.argon2_flavour = Argon2d; - } else if (!strcmp(b, "Argon2i")) { - params.argon2_flavour = Argon2i; - } else if (!strcmp(b, "Argon2id")) { - params.argon2_flavour = Argon2id; - } else { - sfree(b); - goto error; - } - sfree(b); - - if (!read_header(src, header) || 0 != strcmp(header, "Argon2-Memory")) - goto error; - if ((b = read_body(src)) == NULL) - goto error; - if (!str_to_uint32_t(b, ¶ms.argon2_mem)) { - sfree(b); - goto error; - } - sfree(b); - - if (!read_header(src, header) || 0 != strcmp(header, "Argon2-Passes")) - goto error; - if ((b = read_body(src)) == NULL) - goto error; - if (!str_to_uint32_t(b, ¶ms.argon2_passes)) { - sfree(b); - goto error; - } - sfree(b); - - if (!read_header(src, header) || - 0 != strcmp(header, "Argon2-Parallelism")) - goto error; - if ((b = read_body(src)) == NULL) - goto error; - if (!str_to_uint32_t(b, ¶ms.argon2_parallelism)) { - sfree(b); - goto error; - } - sfree(b); - - if (!read_header(src, header) || 0 != strcmp(header, "Argon2-Salt")) - goto error; - if ((b = read_body(src)) == NULL) - goto error; - for (size_t i = 0; b[i]; i += 2) { - if (isxdigit((unsigned char)b[i]) && b[i+1] && - isxdigit((unsigned char)b[i+1])) { - char s[3]; - s[0] = b[i]; - s[1] = b[i+1]; - s[2] = '\0'; - put_byte(passphrase_salt, strtoul(s, NULL, 16)); - } else { - sfree(b); - goto error; - } - } - sfree(b); - } - - /* Read the Private-Lines header line and the Private blob. */ - if (!read_header(src, header) || 0 != strcmp(header, "Private-Lines")) - goto error; - if ((b = read_body(src)) == NULL) - goto error; - i = userkey_parse_line_counter(b); - sfree(b); - if (i < 0) - goto error; - private_blob = strbuf_new_nm(); - if (!read_blob(src, i, BinarySink_UPCAST(private_blob))) - goto error; - - /* Read the Private-MAC or Private-Hash header line. */ - if (!read_header(src, header)) - goto error; - if (0 == strcmp(header, "Private-MAC")) { - if ((mac = read_body(src)) == NULL) - goto error; - is_mac = true; - } else if (0 == strcmp(header, "Private-Hash") && fmt_version == 1) { - if ((mac = read_body(src)) == NULL) - goto error; - is_mac = false; - } else - goto error; - - cipher_mac_keys_blob = strbuf_new(); - ssh2_ppk_derive_keys(fmt_version, ciphertype, - ptrlen_from_asciz(passphrase ? passphrase : ""), - cipher_mac_keys_blob, &cipherkey, &cipheriv, &mackey, - ptrlen_from_strbuf(passphrase_salt), ¶ms); - - /* - * Decrypt the private blob. - */ - if (private_blob->len % ciphertype->blocklen) - goto error; - if (ciphertype == &ppk_cipher_aes256_cbc) { - aes256_decrypt_pubkey(cipherkey.ptr, cipheriv.ptr, - private_blob->u, private_blob->len); - } - - /* - * Verify the MAC. - */ - { - unsigned char binary[32]; - char realmac[sizeof(binary) * 2 + 1]; - strbuf *macdata; - bool free_macdata; - - const ssh2_macalg *mac_alg = - fmt_version <= 2 ? &ssh_hmac_sha1 : &ssh_hmac_sha256; - - if (fmt_version == 1) { - /* MAC (or hash) only covers the private blob. */ - macdata = private_blob; - free_macdata = false; - } else { - macdata = strbuf_new_nm(); - put_stringz(macdata, alg->ssh_id); - put_stringz(macdata, encryption); - put_stringz(macdata, comment); - put_string(macdata, public_blob->s, - public_blob->len); - put_string(macdata, private_blob->s, - private_blob->len); - free_macdata = true; - } - - if (is_mac) { - ssh2_mac *mac; - - mac = ssh2_mac_new(mac_alg, NULL); - ssh2_mac_setkey(mac, mackey); - ssh2_mac_start(mac); - put_data(mac, macdata->s, macdata->len); - ssh2_mac_genresult(mac, binary); - ssh2_mac_free(mac); - } else { - hash_simple(&ssh_sha1, ptrlen_from_strbuf(macdata), binary); - } - - if (free_macdata) - strbuf_free(macdata); - - for (i = 0; i < mac_alg->len; 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 (ciphertype->keylen != 0) { - error = "wrong passphrase"; - ret = SSH2_WRONG_PASSPHRASE; - } else { - error = "MAC failed"; - ret = NULL; - } - goto error; - } - } - - /* - * Create and return the key. - */ - ret = snew(ssh2_userkey); - ret->comment = comment; - comment = NULL; - ret->key = ssh_key_new_priv( - alg, ptrlen_from_strbuf(public_blob), - ptrlen_from_strbuf(private_blob)); - if (!ret->key) { - sfree(ret); - ret = NULL; - error = "createkey failed"; - goto error; - } - error = NULL; - - /* - * Error processing. - */ - error: - if (comment) - sfree(comment); - if (encryption) - sfree(encryption); - if (mac) - sfree(mac); - if (public_blob) - strbuf_free(public_blob); - if (private_blob) - strbuf_free(private_blob); - if (cipher_mac_keys_blob) - strbuf_free(cipher_mac_keys_blob); - strbuf_free(passphrase_salt); - if (errorstr) - *errorstr = error; - return ret; -} - -ssh2_userkey *ppk_load_f(const Filename *filename, const char *passphrase, - const char **errorstr) -{ - LoadedFile *lf = lf_load_keyfile(filename, errorstr); - ssh2_userkey *toret; - if (lf) { - toret = ppk_load_s(BinarySource_UPCAST(lf), passphrase, errorstr); - lf_free(lf); - } else { - toret = NULL; - *errorstr = "can't open file"; - } - return toret; -} - -static bool rfc4716_loadpub(BinarySource *src, char **algorithm, - BinarySink *bs, - char **commentptr, const char **errorstr) -{ - const char *error; - char *line, *colon, *value; - char *comment = NULL; - strbuf *pubblob = NULL; - char base64in[4]; - unsigned char base64out[3]; - int base64bytes; - int alglen; - - line = mkstr(get_chomped_line(src)); - if (!line || 0 != strcmp(line, "---- BEGIN SSH2 PUBLIC KEY ----")) { - error = "invalid begin line in SSH-2 public key file"; - goto error; - } - sfree(line); line = NULL; - - while (1) { - line = mkstr(get_chomped_line(src)); - if (!line) { - error = "truncated SSH-2 public key file"; - goto error; - } - colon = strstr(line, ": "); - if (!colon) - break; - *colon = '\0'; - value = colon + 2; - - if (!strcmp(line, "Comment")) { - char *p, *q; - - /* Remove containing double quotes, if present */ - p = value; - if (*p == '"' && p[strlen(p)-1] == '"') { - p[strlen(p)-1] = '\0'; - p++; - } - - /* Remove \-escaping, not in RFC4716 but seen in the wild - * in practice. */ - for (q = line; *p; p++) { - if (*p == '\\' && p[1]) - p++; - *q++ = *p; - } - - *q = '\0'; - sfree(comment); /* *just* in case of multiple Comment headers */ - comment = dupstr(line); - } else if (!strcmp(line, "Subject") || - !strncmp(line, "x-", 2)) { - /* Headers we recognise and ignore. Do nothing. */ - } else { - error = "unrecognised header in SSH-2 public key file"; - goto error; - } - - sfree(line); line = NULL; - } - - /* - * Now line contains the initial line of base64 data. Loop round - * while it still does contain base64. - */ - pubblob = strbuf_new(); - base64bytes = 0; - while (line && line[0] != '-') { - char *p; - for (p = line; *p; p++) { - base64in[base64bytes++] = *p; - if (base64bytes == 4) { - int n = base64_decode_atom(base64in, base64out); - put_data(pubblob, base64out, n); - base64bytes = 0; - } - } - sfree(line); line = NULL; - line = mkstr(get_chomped_line(src)); - } - - /* - * Finally, check the END line makes sense. - */ - if (!line || 0 != strcmp(line, "---- END SSH2 PUBLIC KEY ----")) { - error = "invalid end line in SSH-2 public key file"; - goto error; - } - sfree(line); line = NULL; - - /* - * OK, we now have a public blob and optionally a comment. We must - * return the key algorithm string too, so look for that at the - * start of the public blob. - */ - if (pubblob->len < 4) { - error = "not enough data in SSH-2 public key file"; - goto error; - } - alglen = toint(GET_32BIT_MSB_FIRST(pubblob->u)); - if (alglen < 0 || alglen > pubblob->len-4) { - error = "invalid algorithm prefix in SSH-2 public key file"; - goto error; - } - if (algorithm) - *algorithm = dupprintf("%.*s", alglen, pubblob->s+4); - if (commentptr) - *commentptr = comment; - else - sfree(comment); - put_datapl(bs, ptrlen_from_strbuf(pubblob)); - strbuf_free(pubblob); - return true; - - error: - sfree(line); - sfree(comment); - if (pubblob) - strbuf_free(pubblob); - if (errorstr) - *errorstr = error; - return false; -} - -static bool openssh_loadpub(BinarySource *src, char **algorithm, - BinarySink *bs, - char **commentptr, const char **errorstr) -{ - const char *error; - char *line, *base64; - char *comment = NULL; - unsigned char *pubblob = NULL; - int pubbloblen, pubblobsize; - int alglen; - - line = mkstr(get_chomped_line(src)); - - base64 = strchr(line, ' '); - if (!base64) { - error = "no key blob in OpenSSH public key file"; - goto error; - } - *base64++ = '\0'; - - comment = strchr(base64, ' '); - if (comment) { - *comment++ = '\0'; - comment = dupstr(comment); - } - - pubblobsize = strlen(base64) / 4 * 3; - pubblob = snewn(pubblobsize, unsigned char); - pubbloblen = 0; - - while (!memchr(base64, '\0', 4)) { - assert(pubbloblen + 3 <= pubblobsize); - pubbloblen += base64_decode_atom(base64, pubblob + pubbloblen); - base64 += 4; - } - if (*base64) { - error = "invalid length for base64 data in OpenSSH public key file"; - goto error; - } - - /* - * Sanity check: the first word on the line should be the key - * algorithm, and should match the encoded string at the start of - * the public blob. - */ - alglen = strlen(line); - if (pubbloblen < alglen + 4 || - GET_32BIT_MSB_FIRST(pubblob) != alglen || - 0 != memcmp(pubblob + 4, line, alglen)) { - error = "key algorithms do not match in OpenSSH public key file"; - goto error; - } - - /* - * Done. - */ - if (algorithm) - *algorithm = dupstr(line); - if (commentptr) - *commentptr = comment; - else - sfree(comment); - sfree(line); - put_data(bs, pubblob, pubbloblen); - sfree(pubblob); - return true; - - error: - sfree(line); - sfree(comment); - sfree(pubblob); - if (errorstr) - *errorstr = error; - return false; -} - -bool ppk_loadpub_s(BinarySource *src, char **algorithm, BinarySink *bs, - char **commentptr, const char **errorstr) -{ - char header[40], *b; - const ssh_keyalg *alg; - int type, i; - const char *error = NULL; - char *comment = NULL; - - /* Initially, check if this is a public-only key file. Sometimes - * we'll be asked to read a public blob from one of those. */ - type = key_type_s(src); - if (type == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716) { - bool ret = rfc4716_loadpub(src, algorithm, bs, commentptr, errorstr); - return ret; - } else if (type == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { - bool ret = openssh_loadpub(src, algorithm, bs, commentptr, errorstr); - return ret; - } else if (type != SSH_KEYTYPE_SSH2) { - error = "not a public key or a PuTTY SSH-2 private key"; - goto error; - } - - /* Read the first header line which contains the key type. */ - if (!read_header(src, header) - || (0 != strcmp(header, "PuTTY-User-Key-File-3") && - 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 public key or a PuTTY SSH-2 private key"; - goto error; - } - error = "file format error"; - if ((b = read_body(src)) == 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(src, header) || 0 != strcmp(header, "Encryption")) - goto error; - if ((b = read_body(src)) == NULL) - goto error; - sfree(b); /* we don't care */ - - /* Read the Comment header line. */ - if (!read_header(src, header) || 0 != strcmp(header, "Comment")) - goto error; - if ((comment = read_body(src)) == NULL) - goto error; - - if (commentptr) - *commentptr = comment; - else - sfree(comment); - - /* Read the Public-Lines header line and the public blob. */ - if (!read_header(src, header) || 0 != strcmp(header, "Public-Lines")) - goto error; - if ((b = read_body(src)) == NULL) - goto error; - i = userkey_parse_line_counter(b); - sfree(b); - if (i < 0) - goto error; - if (!read_blob(src, i, bs)) - goto error; - - if (algorithm) - *algorithm = dupstr(alg->ssh_id); - return true; - - /* - * Error processing. - */ - error: - if (errorstr) - *errorstr = error; - if (comment && commentptr) { - sfree(comment); - *commentptr = NULL; - } - return false; -} - -bool ppk_loadpub_f(const Filename *filename, char **algorithm, BinarySink *bs, - char **commentptr, const char **errorstr) -{ - LoadedFile *lf = lf_load_keyfile(filename, errorstr); - if (!lf) - return false; - - bool toret = ppk_loadpub_s(BinarySource_UPCAST(lf), algorithm, bs, - commentptr, errorstr); - lf_free(lf); - return toret; -} - -bool ppk_encrypted_s(BinarySource *src, char **commentptr) -{ - char header[40], *b, *comment; - bool ret; - - if (commentptr) - *commentptr = NULL; - - if (!read_header(src, header) - || (0 != strcmp(header, "PuTTY-User-Key-File-3") && - 0 != strcmp(header, "PuTTY-User-Key-File-2") && - 0 != strcmp(header, "PuTTY-User-Key-File-1"))) { - return false; - } - if ((b = read_body(src)) == NULL) { - return false; - } - sfree(b); /* we don't care about key type here */ - /* Read the Encryption header line. */ - if (!read_header(src, header) || 0 != strcmp(header, "Encryption")) { - return false; - } - if ((b = read_body(src)) == NULL) { - return false; - } - - /* Read the Comment header line. */ - if (!read_header(src, header) || 0 != strcmp(header, "Comment")) { - sfree(b); - return true; - } - if ((comment = read_body(src)) == NULL) { - sfree(b); - return true; - } - - if (commentptr) - *commentptr = comment; - else - sfree(comment); - - if (!strcmp(b, "aes256-cbc")) - ret = true; - else - ret = false; - sfree(b); - return ret; -} - -bool ppk_encrypted_f(const Filename *filename, char **commentptr) -{ - LoadedFile *lf = lf_load_keyfile(filename, NULL); - if (!lf) { - if (commentptr) - *commentptr = NULL; - return false; - } - - bool toret = ppk_encrypted_s(BinarySource_UPCAST(lf), commentptr); - lf_free(lf); - return toret; -} - -int base64_lines(int datalen) -{ - /* When encoding, we use 64 chars/line, which equals 48 real chars. */ - return (datalen + 47) / 48; -} - -const ppk_save_parameters ppk_save_default_parameters = { - .fmt_version = 3, - - /* - * The Argon2 spec recommends the hybrid variant Argon2id, where - * you don't have a good reason to go with the pure Argon2d or - * Argon2i. - */ - .argon2_flavour = Argon2id, - - /* - * Memory requirement for hashing a password: I don't want to set - * this to some truly huge thing like a gigabyte, because for all - * I know people might perfectly reasonably be running PuTTY on - * machines that don't _have_ a gigabyte spare to hash a private - * key passphrase in the legitimate use cases. - * - * I've picked 8 MB as an amount of memory that isn't unreasonable - * to expect a desktop client machine to have, but is also large - * compared to the memory requirements of the PPK v2 password hash - * (which was plain SHA-1), so it still imposes a limit on - * parallel attacks on someone's key file. - */ - .argon2_mem = 8192, /* require 8 Mb memory */ - - /* - * Automatically scale the number of Argon2 passes so that the - * overall time taken is about 1/10 second. (Again, I could crank - * this up to a larger time and _most_ people might be OK with it, - * but for the moment, I'm trying to err on the side of not - * stopping anyone from using the tools at all.) - */ - .argon2_passes_auto = true, - .argon2_milliseconds = 100, - - /* - * PuTTY's own Argon2 implementation is single-threaded. So we - * might as well set parallelism to 1, which requires that - * attackers' implementations must also be effectively - * single-threaded, and they don't get any benefit from using - * multiple cores on the same hash attempt. (Of course they can - * still use multiple cores for _separate_ hash attempts, but at - * least they don't get a speed advantage over us in computing - * even one hash.) - */ - .argon2_parallelism = 1, -}; - -strbuf *ppk_save_sb(ssh2_userkey *key, const char *passphrase, - const ppk_save_parameters *params_orig) -{ - strbuf *pub_blob, *priv_blob, *cipher_mac_keys_blob; - unsigned char *priv_blob_encrypted; - int priv_encrypted_len; - int cipherblk; - int i; - const char *cipherstr; - ptrlen cipherkey, cipheriv, mackey; - const struct ppk_cipher *ciphertype; - unsigned char priv_mac[32]; - - /* - * Fetch the key component blobs. - */ - pub_blob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(pub_blob)); - priv_blob = strbuf_new_nm(); - ssh_key_private_blob(key->key, BinarySink_UPCAST(priv_blob)); - - /* - * Determine encryption details, and encrypt the private blob. - */ - if (passphrase) { - cipherstr = "aes256-cbc"; - cipherblk = 16; - ciphertype = &ppk_cipher_aes256_cbc; - } else { - cipherstr = "none"; - cipherblk = 1; - ciphertype = &ppk_cipher_none; - } - 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->u, 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. */ - hash_simple(&ssh_sha1, ptrlen_from_strbuf(priv_blob), 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); - - /* Copy the save parameters, so that when derive_keys chooses the - * number of Argon2 passes, it can write the result back to our - * copy for us to retrieve. */ - ppk_save_parameters params = *params_orig; - - strbuf *passphrase_salt = strbuf_new(); - - if (params.fmt_version == 3) { - /* Invent a salt for the password hash. */ - if (params.salt) - put_data(passphrase_salt, params.salt, params.saltlen); - else - random_read(strbuf_append(passphrase_salt, 16), 16); - } - - cipher_mac_keys_blob = strbuf_new(); - ssh2_ppk_derive_keys(params.fmt_version, ciphertype, - ptrlen_from_asciz(passphrase ? passphrase : ""), - cipher_mac_keys_blob, &cipherkey, &cipheriv, &mackey, - ptrlen_from_strbuf(passphrase_salt), ¶ms); - - const ssh2_macalg *macalg = (params.fmt_version == 2 ? - &ssh_hmac_sha1 : &ssh_hmac_sha256); - - /* Now create the MAC. */ - { - strbuf *macdata; - - macdata = strbuf_new_nm(); - put_stringz(macdata, ssh_key_ssh_id(key->key)); - put_stringz(macdata, cipherstr); - put_stringz(macdata, key->comment); - put_string(macdata, pub_blob->s, pub_blob->len); - put_string(macdata, priv_blob_encrypted, priv_encrypted_len); - - mac_simple(macalg, mackey, ptrlen_from_strbuf(macdata), priv_mac); - strbuf_free(macdata); - } - - if (passphrase) { - assert(cipherkey.len == 32); - aes256_encrypt_pubkey(cipherkey.ptr, cipheriv.ptr, - priv_blob_encrypted, priv_encrypted_len); - } - - strbuf *out = strbuf_new_nm(); - put_fmt(out, "PuTTY-User-Key-File-%u: %s\n", - params.fmt_version, ssh_key_ssh_id(key->key)); - put_fmt(out, "Encryption: %s\n", cipherstr); - put_fmt(out, "Comment: %s\n", key->comment); - put_fmt(out, "Public-Lines: %d\n", base64_lines(pub_blob->len)); - base64_encode_bs(BinarySink_UPCAST(out), ptrlen_from_strbuf(pub_blob), 64); - if (params.fmt_version == 3 && ciphertype->keylen != 0) { - put_fmt(out, "Key-Derivation: %s\n", - params.argon2_flavour == Argon2d ? "Argon2d" : - params.argon2_flavour == Argon2i ? "Argon2i" : "Argon2id"); - put_fmt(out, "Argon2-Memory: %"PRIu32"\n", params.argon2_mem); - assert(!params.argon2_passes_auto); - put_fmt(out, "Argon2-Passes: %"PRIu32"\n", params.argon2_passes); - put_fmt(out, "Argon2-Parallelism: %"PRIu32"\n", - params.argon2_parallelism); - put_fmt(out, "Argon2-Salt: "); - for (size_t i = 0; i < passphrase_salt->len; i++) - put_fmt(out, "%02x", passphrase_salt->u[i]); - put_fmt(out, "\n"); - } - put_fmt(out, "Private-Lines: %d\n", base64_lines(priv_encrypted_len)); - base64_encode_bs(BinarySink_UPCAST(out), - make_ptrlen(priv_blob_encrypted, priv_encrypted_len), 64); - put_fmt(out, "Private-MAC: "); - for (i = 0; i < macalg->len; i++) - put_fmt(out, "%02x", priv_mac[i]); - put_fmt(out, "\n"); - - strbuf_free(cipher_mac_keys_blob); - strbuf_free(passphrase_salt); - strbuf_free(pub_blob); - strbuf_free(priv_blob); - smemclr(priv_blob_encrypted, priv_encrypted_len); - sfree(priv_blob_encrypted); - return out; -} - -bool ppk_save_f(const Filename *filename, ssh2_userkey *key, - const char *passphrase, const ppk_save_parameters *params) -{ - FILE *fp = f_open(filename, "wb", true); - if (!fp) - return false; - - strbuf *buf = ppk_save_sb(key, passphrase, params); - bool toret = fwrite(buf->s, 1, buf->len, fp) == buf->len; - if (fclose(fp)) - toret = false; - strbuf_free(buf); - return toret; -} - -/* ---------------------------------------------------------------------- - * Output public keys. - */ -char *ssh1_pubkey_str(RSAKey *key) -{ - char *buffer; - char *dec1, *dec2; - - dec1 = mp_get_decimal(key->exponent); - dec2 = mp_get_decimal(key->modulus); - buffer = dupprintf("%"SIZEu" %s %s%s%s", mp_get_nbits(key->modulus), - dec1, dec2, key->comment ? " " : "", - key->comment ? key->comment : ""); - sfree(dec1); - sfree(dec2); - return buffer; -} - -void ssh1_write_pubkey(FILE *fp, RSAKey *key) -{ - char *buffer = ssh1_pubkey_str(key); - fprintf(fp, "%s\n", buffer); - sfree(buffer); -} - -static char *ssh2_pubkey_openssh_str_internal(const char *comment, - const void *v_pub_blob, - int pub_len) -{ - const unsigned char *ssh2blob = (const unsigned char *)v_pub_blob; - ptrlen alg; - char *buffer, *p; - int i; - - { - BinarySource src[1]; - BinarySource_BARE_INIT(src, ssh2blob, pub_len); - alg = get_string(src); - if (get_err(src)) { - const char *replacement_str = "INVALID-ALGORITHM"; - alg.ptr = replacement_str; - alg.len = strlen(replacement_str); - } - } - - buffer = snewn(alg.len + - 4 * ((pub_len+2) / 3) + - (comment ? strlen(comment) : 0) + 3, char); - p = buffer + sprintf(buffer, "%.*s ", PTRLEN_PRINTF(alg)); - i = 0; - while (i < pub_len) { - int n = (pub_len - i < 3 ? pub_len - i : 3); - base64_encode_atom(ssh2blob + i, n, p); - i += n; - p += 4; - } - if (comment) { - *p++ = ' '; - strcpy(p, comment); - } else - *p++ = '\0'; - - return buffer; -} - -char *ssh2_pubkey_openssh_str(ssh2_userkey *key) -{ - strbuf *blob; - char *ret; - - blob = strbuf_new(); - ssh_key_public_blob(key->key, BinarySink_UPCAST(blob)); - ret = ssh2_pubkey_openssh_str_internal( - key->comment, blob->s, blob->len); - strbuf_free(blob); - - return ret; -} - -void ssh2_write_pubkey(FILE *fp, const char *comment, - const void *v_pub_blob, int pub_len, - int keytype) -{ - unsigned char *pub_blob = (unsigned char *)v_pub_blob; - - if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716) { - const char *p; - int i, column; - - 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"); - } else if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { - char *buffer = ssh2_pubkey_openssh_str_internal(comment, - v_pub_blob, pub_len); - fprintf(fp, "%s\n", buffer); - sfree(buffer); - } else { - unreachable("Bad key type in ssh2_write_pubkey"); - } -} - -/* ---------------------------------------------------------------------- - * Utility functions to compute SSH-2 fingerprints in a uniform way. - */ -static void ssh2_fingerprint_blob_md5(ptrlen blob, strbuf *sb) -{ - unsigned char digest[16]; - - hash_simple(&ssh_md5, blob, digest); - for (unsigned i = 0; i < 16; i++) - put_fmt(sb, "%02x%s", digest[i], i==15 ? "" : ":"); -} - -static void ssh2_fingerprint_blob_sha256(ptrlen blob, strbuf *sb) -{ - unsigned char digest[32]; - hash_simple(&ssh_sha256, blob, digest); - - put_datapl(sb, PTRLEN_LITERAL("SHA256:")); - - for (unsigned i = 0; i < 32; i += 3) { - char buf[5]; - unsigned len = 32-i; - if (len > 3) - len = 3; - base64_encode_atom(digest + i, len, buf); - put_data(sb, buf, 4); - } - strbuf_chomp(sb, '='); -} - -char *ssh2_fingerprint_blob(ptrlen blob, FingerprintType fptype) -{ - strbuf *sb = strbuf_new(); - strbuf *tmp = NULL; - - /* - * Identify the key algorithm, if possible. - * - * If we can't do that, then we have a seriously confused key - * blob, in which case we return only the hash. - */ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, blob); - ptrlen algname = get_string(src); - if (!get_err(src)) { - const ssh_keyalg *alg = find_pubkey_alg_len(algname); - if (alg) { - int bits = ssh_key_public_bits(alg, blob); - put_fmt(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits); - - if (!ssh_fptype_is_cert(fptype) && alg->is_certificate) { - ssh_key *key = ssh_key_new_pub(alg, blob); - if (key) { - tmp = strbuf_new(); - ssh_key_public_blob(ssh_key_base_key(key), - BinarySink_UPCAST(tmp)); - blob = ptrlen_from_strbuf(tmp); - ssh_key_free(key); - } - } - } else { - put_fmt(sb, "%.*s ", PTRLEN_PRINTF(algname)); - } - } - - switch (ssh_fptype_from_cert(fptype)) { - case SSH_FPTYPE_MD5: - ssh2_fingerprint_blob_md5(blob, sb); - break; - case SSH_FPTYPE_SHA256: - ssh2_fingerprint_blob_sha256(blob, sb); - break; - default: - unreachable("ssh_fptype_from_cert ruled out the other values"); - } - - if (tmp) - strbuf_free(tmp); - - return strbuf_to_str(sb); -} - -char *ssh2_double_fingerprint_blob(ptrlen blob, FingerprintType fptype) -{ - if (ssh_fptype_is_cert(fptype)) - fptype = ssh_fptype_from_cert(fptype); - - char *fp = ssh2_fingerprint_blob(blob, fptype); - char *p = strrchr(fp, ' '); - char *hash = p ? p + 1 : fp; - - char *fpc = ssh2_fingerprint_blob(blob, ssh_fptype_to_cert(fptype)); - char *pc = strrchr(fpc, ' '); - char *hashc = pc ? pc + 1 : fpc; - - if (strcmp(hash, hashc)) { - char *tmp = dupprintf("%s (with certificate: %s)", fp, hashc); - sfree(fp); - fp = tmp; - } - - sfree(fpc); - return fp; -} - -char **ssh2_all_fingerprints_for_blob(ptrlen blob) -{ - char **fps = snewn(SSH_N_FPTYPES, char *); - for (unsigned i = 0; i < SSH_N_FPTYPES; i++) - fps[i] = ssh2_fingerprint_blob(blob, i); - return fps; -} - -char *ssh2_fingerprint(ssh_key *data, FingerprintType fptype) -{ - strbuf *blob = strbuf_new(); - ssh_key_public_blob(data, BinarySink_UPCAST(blob)); - char *ret = ssh2_fingerprint_blob(ptrlen_from_strbuf(blob), fptype); - strbuf_free(blob); - return ret; -} - -char *ssh2_double_fingerprint(ssh_key *data, FingerprintType fptype) -{ - strbuf *blob = strbuf_new(); - ssh_key_public_blob(data, BinarySink_UPCAST(blob)); - char *ret = ssh2_double_fingerprint_blob(ptrlen_from_strbuf(blob), fptype); - strbuf_free(blob); - return ret; -} - -char **ssh2_all_fingerprints(ssh_key *data) -{ - strbuf *blob = strbuf_new(); - ssh_key_public_blob(data, BinarySink_UPCAST(blob)); - char **ret = ssh2_all_fingerprints_for_blob(ptrlen_from_strbuf(blob)); - strbuf_free(blob); - return ret; -} - -void ssh2_free_all_fingerprints(char **fps) -{ - for (unsigned i = 0; i < SSH_N_FPTYPES; i++) - sfree(fps[i]); - sfree(fps); -} - -/* ---------------------------------------------------------------------- - * Determine the type of a private key file. - */ -static int key_type_s_internal(BinarySource *src) -{ - static const ptrlen public_std_sig = - PTRLEN_DECL_LITERAL("---- BEGIN SSH2 PUBLIC KEY"); - static const ptrlen putty2_sig = - PTRLEN_DECL_LITERAL("PuTTY-User-Key-File-"); - static const ptrlen sshcom_sig = - PTRLEN_DECL_LITERAL("---- BEGIN SSH2 ENCRYPTED PRIVAT"); - static const ptrlen openssh_new_sig = - PTRLEN_DECL_LITERAL("-----BEGIN OPENSSH PRIVATE KEY"); - static const ptrlen openssh_sig = - PTRLEN_DECL_LITERAL("-----BEGIN "); - - if (BinarySource_REWIND(src), expect_signature(src, rsa1_signature)) - return SSH_KEYTYPE_SSH1; - if (BinarySource_REWIND(src), expect_signature(src, public_std_sig)) - return SSH_KEYTYPE_SSH2_PUBLIC_RFC4716; - if (BinarySource_REWIND(src), expect_signature(src, putty2_sig)) - return SSH_KEYTYPE_SSH2; - if (BinarySource_REWIND(src), expect_signature(src, openssh_new_sig)) - return SSH_KEYTYPE_OPENSSH_NEW; - if (BinarySource_REWIND(src), expect_signature(src, openssh_sig)) - return SSH_KEYTYPE_OPENSSH_PEM; - if (BinarySource_REWIND(src), expect_signature(src, sshcom_sig)) - return SSH_KEYTYPE_SSHCOM; - - BinarySource_REWIND(src); - if (get_chars(src, "0123456789").len > 0 && get_chars(src, " ").len == 1 && - get_chars(src, "0123456789").len > 0 && get_chars(src, " ").len == 1 && - get_chars(src, "0123456789").len > 0 && - get_nonchars(src, " \n").len == 0) - return SSH_KEYTYPE_SSH1_PUBLIC; - - BinarySource_REWIND(src); - if (find_pubkey_alg_len(get_nonchars(src, " \n")) > 0 && - get_chars(src, " ").len == 1 && - get_chars(src, "0123456789ABCDEFGHIJKLMNOPQRSTUV" - "WXYZabcdefghijklmnopqrstuvwxyz+/=").len > 0 && - get_nonchars(src, " \n").len == 0) - return SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH; - - return SSH_KEYTYPE_UNKNOWN; /* unrecognised or EOF */ -} - -int key_type_s(BinarySource *src) -{ - int toret = key_type_s_internal(src); - BinarySource_REWIND(src); - return toret; -} - -int key_type(const Filename *filename) -{ - LoadedFile *lf = lf_new(1024); - if (lf_load(lf, filename) == LF_ERROR) { - lf_free(lf); - return SSH_KEYTYPE_UNOPENABLE; - } - - int toret = key_type_s(BinarySource_UPCAST(lf)); - lf_free(lf); - return toret; -} - -/* - * Convert the type word to a string, for `wrong type' error - * messages. - */ -const char *key_type_to_str(int type) -{ - switch (type) { - case SSH_KEYTYPE_UNOPENABLE: - return "unable to open file"; - case SSH_KEYTYPE_UNKNOWN: - return "not a recognised key file format"; - case SSH_KEYTYPE_SSH1_PUBLIC: - return "SSH-1 public key"; - case SSH_KEYTYPE_SSH2_PUBLIC_RFC4716: - return "SSH-2 public key (RFC 4716 format)"; - case SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH: - return "SSH-2 public key (OpenSSH format)"; - case SSH_KEYTYPE_SSH1: - return "SSH-1 private key"; - case SSH_KEYTYPE_SSH2: - return "PuTTY SSH-2 private key"; - case SSH_KEYTYPE_OPENSSH_PEM: - return "OpenSSH SSH-2 private key (old PEM format)"; - case SSH_KEYTYPE_OPENSSH_NEW: - return "OpenSSH SSH-2 private key (new format)"; - case SSH_KEYTYPE_SSHCOM: - return "ssh.com SSH-2 private key"; - - /* - * This function is called with a key type derived from - * looking at an actual key file, so the output-only type - * OPENSSH_AUTO should never get here, and is much an INTERNAL - * ERROR as a code we don't even understand. - */ - case SSH_KEYTYPE_OPENSSH_AUTO: - unreachable("OPENSSH_AUTO should never reach key_type_to_str"); - default: - unreachable("bad key type in key_type_to_str"); - } -} diff --git a/sshrand.c b/sshrand.c deleted file mode 100644 index 7f3d64115..000000000 --- a/sshrand.c +++ /dev/null @@ -1,151 +0,0 @@ -/* - * sshrand.c: manage the global live PRNG instance. - */ - -#include "putty.h" -#include "ssh.h" -#include "storage.h" -#include - -/* Collect environmental noise every 5 minutes */ -#define NOISE_REGULAR_INTERVAL (5*60*TICKSPERSEC) - -int random_active = 0; - -#ifdef FUZZING - -/* - * Special dummy version of the RNG for use when fuzzing. - */ -void random_add_noise(NoiseSourceId source, const void *noise, int length) { } -void random_ref(void) { } -void random_setup_custom(const ssh_hashalg *hash) { } -void random_unref(void) { } -void random_read(void *out, size_t size) -{ - memset(out, 0x45, size); /* Chosen by eight fair coin tosses */ -} -void random_get_savedata(void **data, int *len) { } - -#else /* !FUZZING */ - -/* Dummy structure for the sake of having something to expire_timer_context */ -static struct random_timer_context { int dummy; } random_timer_ctx; - -static prng *global_prng; -static unsigned long next_noise_collection; - -void random_add_noise(NoiseSourceId source, const void *noise, int length) -{ - if (!random_active) - return; - - prng_add_entropy(global_prng, source, make_ptrlen(noise, length)); -} - -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, - &random_timer_ctx); - } -} - -static void random_seed_callback(void *noise, int length) -{ - put_data(global_prng, noise, length); -} - -static void random_create(const ssh_hashalg *hashalg) -{ - assert(!global_prng); - global_prng = prng_new(hashalg); - - prng_seed_begin(global_prng); - noise_get_heavy(random_seed_callback); - prng_seed_finish(global_prng); - - next_noise_collection = - schedule_timer(NOISE_REGULAR_INTERVAL, random_timer, - &random_timer_ctx); - - /* noise_get_heavy probably read our random seed file. - * Therefore (in fact, even if it didn't), we should write a - * fresh one, in case another instance of ourself starts up - * before we finish, and also in case an attacker gets hold of - * the seed data we used. */ - 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); - } -} - -void random_ref(void) -{ - if (!random_active++) - random_create(&ssh_sha256); -} - -void random_setup_custom(const ssh_hashalg *hash) -{ - random_active++; - random_create(hash); -} - -void random_reseed(ptrlen seed) -{ - prng_seed_begin(global_prng); - put_datapl(global_prng, seed); - prng_seed_finish(global_prng); -} - -void random_clear(void) -{ - if (global_prng) { - random_save_seed(); - expire_timer_context(&random_timer_ctx); - prng_free(global_prng); - global_prng = NULL; - random_active = 0; - } -} - -void random_unref(void) -{ - assert(random_active > 0); - if (--random_active == 0) - random_clear(); -} - -void random_read(void *buf, size_t size) -{ - assert(random_active > 0); - prng_read(global_prng, buf, size); -} - -void random_get_savedata(void **data, int *len) -{ - void *buf = snewn(global_prng->savesize, char); - random_read(buf, global_prng->savesize); - *len = global_prng->savesize; - *data = buf; -} - -size_t random_seed_bits(void) -{ - assert(random_active > 0); - return prng_seed_bits(global_prng); -} - -#endif /* FUZZING */ diff --git a/storage.h b/storage.h deleted file mode 100644 index e9138f403..000000000 --- a/storage.h +++ /dev/null @@ -1,143 +0,0 @@ -/* - * storage.h: interface defining functions for storage and recovery - * of PuTTY's persistent data. - */ - -#ifndef PUTTY_STORAGE_H -#define PUTTY_STORAGE_H - -#include "defs.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. - */ -settings_w *open_settings_w(const char *sessionname, char **errmsg); -void write_setting_s(settings_w *handle, const char *key, const char *value); -void write_setting_i(settings_w *handle, const char *key, int value); -void write_setting_filename(settings_w *handle, - const char *key, Filename *value); -void write_setting_fontspec(settings_w *handle, - const char *key, FontSpec *font); -void close_settings_w(settings_w *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. - */ -settings_r *open_settings_r(const char *sessionname); -char *read_setting_s(settings_r *handle, const char *key); -int read_setting_i(settings_r *handle, const char *key, int defvalue); -Filename *read_setting_filename(settings_r *handle, const char *key); -FontSpec *read_setting_fontspec(settings_r *handle, const char *key); -void close_settings_r(settings_r *handle); - -/* - * Delete a whole saved session. - */ -void del_settings(const char *sessionname); - -/* - * Enumerate all saved sessions. - */ -settings_e *enum_settings_start(void); -bool enum_settings_next(settings_e *handle, strbuf *out); -void enum_settings_finish(settings_e *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 check_stored_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 configuration for trusted host - * certification authorities. This must be stored separately from the - * saved-session data, because the whole point is to avoid having to - * configure CAs separately per session. - */ - -struct host_ca { - char *name; - strbuf *ca_public_key; - char *validity_expression; - ca_options opts; -}; - -host_ca_enum *enum_host_ca_start(void); -bool enum_host_ca_next(host_ca_enum *handle, strbuf *out); -void enum_host_ca_finish(host_ca_enum *handle); - -host_ca *host_ca_load(const char *name); -char *host_ca_save(host_ca *); /* NULL on success, or dynamic error msg */ -char *host_ca_delete(const char *name); /* likewise */ - -host_ca *host_ca_new(void); /* initialises to default settings */ -void host_ca_free(host_ca *); - -/* ---------------------------------------------------------------------- - * 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/stubs/CMakeLists.txt b/stubs/CMakeLists.txt deleted file mode 100644 index dc02aca37..000000000 --- a/stubs/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -# This subdirectory is generally full of 'stubs' in the sense of -# functions and types that don't do anything interesting, and are -# substituted in some contexts for ones that do. -# -# Some of the files here, with names beginning 'no-', are substituted -# at link time, conditional on the application. For example, a program -# that doesn't use the timing subsystem but still includes a module -# that makes a passing reference to it (say, in a context that never -# turns out to be called) can link against no-timing.c in place of the -# real timing.c. -# -# Other files, with names beginning 'null-', provide non-functional -# implementations of a particular internal API, or a selection of -# non-functional methods for that API that real implementations can -# selectively use. Those are linked in to a program _alongside_ real -# implementations of the same API. -# -# So the cmake setup for this directory puts all the 'null-' files -# into the utils library (at the end of the link, where they'll be -# available everywhere), but doesn't mention the 'no-' files, because -# those will be selected manually by add_executable() commands -# elsewhere. - -add_sources_from_current_dir(utils - null-lp.c - null-cipher.c - null-key.c - null-mac.c - null-opener.c - null-plug.c - null-seat.c) diff --git a/stubs/no-agent.c b/stubs/no-agent.c deleted file mode 100644 index d4c755e2c..000000000 --- a/stubs/no-agent.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "putty.h" - -bool agent_exists(void) { return false; } -Socket *agent_connect(Plug *plug) { - return new_error_socket_fmt( - plug, "no actual networking in this application"); -} -void agent_cancel_query(agent_pending_query *pq) {} -agent_pending_query *agent_query( - strbuf *query, void **out, int *outlen, - void (*callback)(void *, void *, int), void *callback_ctx) {return NULL;} diff --git a/stubs/no-ca-config.c b/stubs/no-ca-config.c deleted file mode 100644 index 573f770fc..000000000 --- a/stubs/no-ca-config.c +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Stub version of setup_ca_config_box, for tools that don't have SSH - * code linked in. - */ - -#include "putty.h" -#include "dialog.h" - -const bool has_ca_config_box = false; - -void setup_ca_config_box(struct controlbox *b) -{ - unreachable("should never call setup_ca_config_box in this application"); -} diff --git a/stubs/no-callback.c b/stubs/no-callback.c deleted file mode 100644 index 6c12f2b75..000000000 --- a/stubs/no-callback.c +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Stub version of the callback.c functions. Doesn't let anyone - * _schedule_ a callback (because that would lead them into the false - * assumption that it would actually happen later on), but permits the - * other functions without error, on the grounds that it's well - * defined what they would do if nobody had scheduled any callbacks. - */ - -#include "putty.h" - -void queue_idempotent_callback(struct IdempotentCallback *ic) -{ - unreachable("callbacks are not supported in this application"); -} - -void delete_callbacks_for_context(void *ctx) -{ -} - -void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) -{ - unreachable("callbacks are not supported in this application"); -} - -bool run_toplevel_callbacks(void) -{ - return false; -} - -bool toplevel_callback_pending(void) -{ - return false; -} diff --git a/stubs/no-cmdline.c b/stubs/no-cmdline.c deleted file mode 100644 index 2476354e5..000000000 --- a/stubs/no-cmdline.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * no-cmdline.c - stubs in applications which don't do the - * standard(ish) PuTTY tools' command-line parsing - */ - -#include -#include -#include -#include "putty.h" - -/* - * Stub version of the function in cmdline.c which provides the - * password to SSH authentication by remembering it having been passed - * as a command-line option. If we're not doing normal command-line - * handling, then there is no such option, so that function always - * returns failure. - */ -SeatPromptResult cmdline_get_passwd_input( - prompts_t *p, cmdline_get_passwd_input_state *state, bool restartable) -{ - return SPR_INCOMPLETE; -} diff --git a/stubs/no-console.c b/stubs/no-console.c deleted file mode 100644 index 580cfd700..000000000 --- a/stubs/no-console.c +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Stub functions for when console.c is not linked into a program. - */ - -#include "putty.h" - -bool console_set_batch_mode(bool newvalue) -{ - return false; -} - -bool console_set_stdio_prompts(bool newvalue) -{ - return false; -} diff --git a/stubs/no-gss.c b/stubs/no-gss.c deleted file mode 100644 index dd0adb6da..000000000 --- a/stubs/no-gss.c +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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" - -#include "ssh/pgssapi.h" -#include "ssh/gss.h" -#include "ssh/gssc.h" - -const int ngsslibs = 0; -const char *const gsslibnames[1] = { "dummy" }; -const struct keyvalwhere gsslibkeywords[1] = { { "dummy", 0, -1, -1 } }; - -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->libraries); /* I know it's always NULL, but stay consistent */ - sfree(list); -} diff --git a/stubs/no-ldisc.c b/stubs/no-ldisc.c deleted file mode 100644 index a3d115ce0..000000000 --- a/stubs/no-ldisc.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "putty.h" - -struct Ldisc_tag { - int dummy; -}; - -Ldisc *ldisc_create(Conf *conf, Terminal *term, Backend *backend, Seat *seat) -{ - Ldisc *ldisc = snew(Ldisc); - memset(ldisc, 0, sizeof(Ldisc)); - return ldisc; -} - -void ldisc_configure(Ldisc *ldisc, Conf *conf) -{ -} - -void ldisc_free(Ldisc *ldisc) -{ - sfree(ldisc); -} - -void ldisc_echoedit_update(Ldisc *ldisc) -{ -} - -void ldisc_provide_userpass_le(Ldisc *ldisc, TermLineEditor *le) -{ -} - -void ldisc_check_sendok(Ldisc *ldisc) -{ -} - -void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) -{ -} diff --git a/stubs/no-lineedit.c b/stubs/no-lineedit.c deleted file mode 100644 index 219572d4c..000000000 --- a/stubs/no-lineedit.c +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Stubs of functions in lineedit.c, for use in programs that don't - * have any use for line editing (e.g. because they don't have a - * terminal either). - */ - -#include "putty.h" -#include "terminal.h" - -TermLineEditor *lineedit_new(Terminal *term, unsigned flags, - TermLineEditorCallbackReceiver *receiver) -{ - return NULL; -} -void lineedit_free(TermLineEditor *le) {} -void lineedit_input(TermLineEditor *le, char ch, bool dedicated) {} -void lineedit_modify_flags(TermLineEditor *le, unsigned clr, unsigned flip) {} -void lineedit_send_line(TermLineEditor *le) {} diff --git a/stubs/no-logging.c b/stubs/no-logging.c deleted file mode 100644 index cb361c08d..000000000 --- a/stubs/no-logging.c +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Stub module implementing the logging API for tools that don't do - * session logging. - */ - -#include "putty.h" - -void logtraffic(LogContext *ctx, unsigned char c, int logmode) {} -void logflush(LogContext *ctx) {} -void logevent(LogContext *ctx, const char *event) {} -void log_free(LogContext *ctx) {} -void log_reconfig(LogContext *ctx, Conf *conf) {} -void log_packet(LogContext *ctx, int direction, int type, - const char *texttype, const void *data, size_t len, - int n_blanks, const struct logblank_t *blanks, - const unsigned long *seq, - unsigned downstream_id, const char *additional_log_text) {} - -LogContext *log_init(LogPolicy *lp, Conf *conf) -{ return NULL; } diff --git a/stubs/no-network.c b/stubs/no-network.c deleted file mode 100644 index df97438a9..000000000 --- a/stubs/no-network.c +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Stub version of the whole networking abstraction. - */ - -#include "putty.h" -#include "network.h" - -struct SockAddr { - int dummy; -}; - -void sk_init(void) -{ -} - -void sk_cleanup(void) -{ -} - -SockAddr *sk_namelookup(const char *host, char **canonicalname, - int address_family) -{ - return snew(SockAddr); -} -SockAddr *sk_nonamelookup(const char *host) -{ - return snew(SockAddr); -} - -void sk_getaddr(SockAddr *addr, char *buf, int buflen) -{ - strncpy(buf, "nonsense", buflen); -} - -bool sk_addr_needs_port(SockAddr *addr) -{ - return true; -} - -bool sk_hostname_is_local(const char *name) -{ - return false; -} - -bool sk_address_is_local(SockAddr *addr) -{ - return false; -} - -bool sk_address_is_special_local(SockAddr *addr) -{ - return false; -} - -int sk_addrtype(SockAddr *addr) -{ - return ADDRTYPE_UNSPEC; -} - -void sk_addrcopy(SockAddr *addr, char *buf) -{ -} - -void sk_addr_free(SockAddr *addr) -{ - sfree(addr); -} - -SockAddr *sk_addr_dup(SockAddr *addr) -{ - return snew(SockAddr); -} - -Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, - bool nodelay, bool keepalive, Plug *plug) -{ - return new_error_socket_fmt( - plug, "no actual networking in this application"); -} - -Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, int orig_address_family) -{ - return new_error_socket_fmt( - plug, "no actual networking in this application"); -} - -void *(sk_getxdmdata)(Socket *sock, int *lenp) -{ - return NULL; -} - -void plug_closing_errno(Plug *plug, int error) -{ - plug_closing(plug, PLUGCLOSE_ERROR, "dummy"); -} - -const char *sk_addr_error(SockAddr *addr) -{ - return "no actual network addresses in this application"; -} - -int net_service_lookup(const char *service) -{ - return 0; -} - -char *get_hostname(void) -{ - return dupstr("dummy-hostname"); -} - -SockAddr *platform_get_x11_unix_address(const char *sockpath, int displaynum) -{ - return snew(SockAddr); -} - -SockAddr *unix_sock_addr(const char *path) -{ - return snew(SockAddr); -} - -SockAddr *sk_namedpipe_addr(const char *pipename) -{ - return snew(SockAddr); -} - -Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug) -{ - return new_error_socket_fmt( - plug, "no actual networking in this application"); -} - -Socket *platform_start_subprocess(const char *cmd, Plug *plug, - const char *prefix) -{ - return new_error_socket_fmt( - plug, "no actual networking in this application"); -} - -#ifdef PUTTY_WINDOWS_PLATFORM_H -void plug_closing_system_error(Plug *plug, DWORD error) {} -void plug_closing_winsock_error(Plug *plug, DWORD error) {} -#endif diff --git a/stubs/no-print.c b/stubs/no-print.c deleted file mode 100644 index 941da68c9..000000000 --- a/stubs/no-print.c +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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, const void *data, size_t 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/stubs/no-printing.c b/stubs/no-printing.c deleted file mode 100644 index 6c8859e73..000000000 --- a/stubs/no-printing.c +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Stub module implementing the printing API for tools that don't - * print. - */ - -#include "putty.h" - -printer_job *printer_start_job(char *printer) { return NULL; } -void printer_job_data(printer_job *pj, const void *data, size_t 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/stubs/no-rand.c b/stubs/no-rand.c deleted file mode 100644 index 2ad9f6615..000000000 --- a/stubs/no-rand.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Stub implementations of RNG functions for applications without an RNG. - */ - -#include "putty.h" - -void random_read(void *out, size_t size) -{ - unreachable("Random numbers are not available in this application"); -} - -void random_save_seed(void) -{ -} - -void random_destroy_seed(void) -{ -} - -void noise_ultralight(NoiseSourceId id, unsigned long data) -{ -} diff --git a/stubs/no-storage.c b/stubs/no-storage.c deleted file mode 100644 index a8671a15d..000000000 --- a/stubs/no-storage.c +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Stub module implementing the saved-session storage APIs for tools - * that don't load or save sessions. - */ - -#include "putty.h" - -settings_w *open_settings_w(const char *sessionname, char **errmsg) -{ return NULL; } -void write_setting_s(settings_w *handle, const char *key, const char *value) -{ unreachable("where did you get a settings_w from?"); } -void write_setting_i(settings_w *handle, const char *key, int value) -{ unreachable("where did you get a settings_w from?"); } -void write_setting_fontspec(settings_w *handle, const char *name, FontSpec *fs) -{ unreachable("where did you get a settings_w from?"); } -void write_setting_filename(settings_w *handle, const char *name, Filename *fn) -{ unreachable("where did you get a settings_w from?"); } -void close_settings_w(settings_w *handle) -{ unreachable("where did you get a settings_w from?"); } - -settings_r *open_settings_r(const char *sessionname) -{ return NULL; } -char *read_setting_s(settings_r *handle, const char *key) -{ return NULL; } -int read_setting_i(settings_r *handle, const char *key, int defvalue) -{ return defvalue; } -FontSpec *read_setting_fontspec(settings_r *handle, const char *name) -{ return fontspec_new_default(); } -Filename *read_setting_filename(settings_r *handle, const char *name) -{ return filename_from_str(""); } -void close_settings_r(settings_r *handle) { } - -void del_settings(const char *sessionname) {} - -settings_e *enum_settings_start(void) -{ return NULL; } -bool enum_settings_next(settings_e *handle, strbuf *out) -{ unreachable("where did you get a settings_e from?"); } -void enum_settings_finish(settings_e *handle) -{ unreachable("where did you get a settings_e from?"); } diff --git a/stubs/no-term.c b/stubs/no-term.c deleted file mode 100644 index c2e534b28..000000000 --- a/stubs/no-term.c +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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) -{ -} - -SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p) -{ - return SPR_SW_ABORT("No terminal to send interactive prompts to"); -} diff --git a/stubs/no-timing.c b/stubs/no-timing.c deleted file mode 100644 index 0575fb533..000000000 --- a/stubs/no-timing.c +++ /dev/null @@ -1,26 +0,0 @@ -/* - * no-timing.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) -{ -} - -unsigned long timing_last_clock(void) -{ - return 0; -} diff --git a/stubs/null-cipher.c b/stubs/null-cipher.c deleted file mode 100644 index e11c7bbcd..000000000 --- a/stubs/null-cipher.c +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Implementation of shared trivial routines that ssh_cipher - * implementations might use. - */ - -#include "ssh.h" - -void nullcipher_next_message(ssh_cipher *cipher) -{ - /* Most ciphers don't do anything at all with this */ -} diff --git a/stubs/null-key.c b/stubs/null-key.c deleted file mode 100644 index dae5c1bb1..000000000 --- a/stubs/null-key.c +++ /dev/null @@ -1,22 +0,0 @@ -#include "misc.h" -#include "ssh.h" - -unsigned nullkey_supported_flags(const ssh_keyalg *self) -{ - return 0; -} - -const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags) -{ - /* There are no alternate ids */ - return self->ssh_id; -} - -ssh_key *nullkey_base_key(ssh_key *key) -{ - /* When a key is not certified, it is its own base */ - return key; -} - -bool nullkey_variable_size_no(const ssh_keyalg *self) { return false; } -bool nullkey_variable_size_yes(const ssh_keyalg *self) { return true; } diff --git a/stubs/null-lp.c b/stubs/null-lp.c deleted file mode 100644 index 193c33923..000000000 --- a/stubs/null-lp.c +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Stub methods usable by LogPolicy implementations. - */ - -#include "putty.h" - -bool null_lp_verbose_no(LogPolicy *lp) { return false; } -bool null_lp_verbose_yes(LogPolicy *lp) { return true; } diff --git a/stubs/null-mac.c b/stubs/null-mac.c deleted file mode 100644 index 4d836704e..000000000 --- a/stubs/null-mac.c +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Implementation of shared trivial routines that ssh2_mac - * implementations might use. - */ - -#include "ssh.h" - -void nullmac_next_message(ssh2_mac *m) -{ - /* Most MACs don't do anything at all with this */ -} diff --git a/stubs/null-opener.c b/stubs/null-opener.c deleted file mode 100644 index 6fdb7c28e..000000000 --- a/stubs/null-opener.c +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Null implementation of DeferredSocketOpener. Doesn't even bother to - * allocate and free itself: there's just one static implementation - * which we hand out to any caller. - */ - -#include "putty.h" - -static void null_opener_free(DeferredSocketOpener *opener) {} - -static const DeferredSocketOpenerVtable NullOpener_vt = { - .free = null_opener_free, -}; - -static DeferredSocketOpener null_opener = { .vt = &NullOpener_vt }; - -DeferredSocketOpener *null_deferred_socket_opener(void) -{ - return &null_opener; -} diff --git a/stubs/null-plug.c b/stubs/null-plug.c deleted file mode 100644 index d583d1565..000000000 --- a/stubs/null-plug.c +++ /dev/null @@ -1,40 +0,0 @@ -/* - * nullplug.c: provide a null implementation of the Plug vtable which - * ignores all calls. Occasionally useful in cases where we want to - * make a network connection just to see if it works, but not do - * anything with it afterwards except close it again. - */ - -#include "putty.h" - -void nullplug_log(Plug *plug, PlugLogType type, SockAddr *addr, - int port, const char *err_msg, int err_code) -{ -} - -void nullplug_closing(Plug *plug, PlugCloseType type, const char *error_msg) -{ -} - -void nullplug_receive(Plug *plug, int urgent, const char *data, size_t len) -{ -} - -void nullplug_sent(Plug *plug, size_t bufsize) -{ -} - -static const PlugVtable nullplug_plugvt = { - .log = nullplug_log, - .closing = nullplug_closing, - .receive = nullplug_receive, - .sent = nullplug_sent, -}; - -static Plug nullplug_plug = { &nullplug_plugvt }; - -/* - * There's a singleton instance of nullplug, because it's not - * interesting enough to worry about making more than one of them. - */ -Plug *const nullplug = &nullplug_plug; diff --git a/stubs/null-seat.c b/stubs/null-seat.c deleted file mode 100644 index 67c25af0e..000000000 --- a/stubs/null-seat.c +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Stub methods usable by Seat implementations. - */ - -#include "putty.h" - -size_t nullseat_output( - Seat *seat, SeatOutputType type, const void *data, size_t len) {return 0;} -bool nullseat_eof(Seat *seat) { return true; } -void nullseat_sent(Seat *seat, size_t bufsize) {} -size_t nullseat_banner(Seat *seat, const void *data, size_t len) {return 0;} -size_t nullseat_banner_to_stderr(Seat *seat, const void *data, size_t len) -{ return seat_output(seat, SEAT_OUTPUT_STDERR, data, len); } -SeatPromptResult nullseat_get_userpass_input(Seat *seat, prompts_t *p) -{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } -void nullseat_notify_session_started(Seat *seat) {} -void nullseat_notify_remote_exit(Seat *seat) {} -void nullseat_notify_remote_disconnect(Seat *seat) {} -void nullseat_connection_fatal(Seat *seat, const char *message) {} -void nullseat_nonfatal(Seat *seat, const char *message) {} -void nullseat_update_specials_menu(Seat *seat) {} -char *nullseat_get_ttymode(Seat *seat, const char *mode) { return NULL; } -void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} -SeatPromptResult nullseat_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } -SeatPromptResult nullseat_confirm_weak_crypto_primitive( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } -SeatPromptResult nullseat_confirm_weak_cached_hostkey( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ return SPR_SW_ABORT("this seat can't handle interactive prompts"); } -bool nullseat_is_never_utf8(Seat *seat) { return false; } -bool nullseat_is_always_utf8(Seat *seat) { return true; } -void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing) {} -const char *nullseat_get_x_display(Seat *seat) { return NULL; } -bool nullseat_get_windowid(Seat *seat, long *id_out) { return false; } -bool nullseat_get_window_pixel_size( - Seat *seat, int *width, int *height) { return false; } -StripCtrlChars *nullseat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;} -void nullseat_set_trust_status(Seat *seat, bool trusted) {} -bool nullseat_can_set_trust_status_yes(Seat *seat) { return true; } -bool nullseat_can_set_trust_status_no(Seat *seat) { return false; } -bool nullseat_has_mixed_input_stream_yes(Seat *seat) { return true; } -bool nullseat_has_mixed_input_stream_no(Seat *seat) { return false; } -bool nullseat_verbose_no(Seat *seat) { return false; } -bool nullseat_verbose_yes(Seat *seat) { return true; } -bool nullseat_interactive_no(Seat *seat) { return false; } -bool nullseat_interactive_yes(Seat *seat) { return true; } -bool nullseat_get_cursor_position(Seat *seat, int *x, int *y) { return false; } - -const SeatDialogPromptDescriptions *nullseat_prompt_descriptions(Seat *seat) -{ - static const SeatDialogPromptDescriptions descs = { - .hk_accept_action = "", - .hk_connect_once_action = "", - .hk_cancel_action = "", - .hk_cancel_action_Participle = "", - .weak_accept_action = "", - .weak_cancel_action = "", - }; - return &descs; -} diff --git a/terminal/bidi.c b/terminal/bidi.c deleted file mode 100644 index ba92118bf..000000000 --- a/terminal/bidi.c +++ /dev/null @@ -1,1685 +0,0 @@ -/* - * Implementation of the Unicode bidirectional and Arabic shaping - * algorithms for PuTTY. - * - * Original version written and kindly contributed to this code base - * by Ahmad Khalifa of Arabeyes. The bidi part was almost completely - * rewritten in 2021 by Simon Tatham to bring it up to date, but the - * shaping part is still the one by the original authors. - * - * Implementation notes: - * - * Algorithm version - * ----------------- - * - * This algorithm is up to date with Unicode Standard Annex #9 - * revision 46: - * - * https://www.unicode.org/reports/tr9/tr9-46.html - * - * and passes the full conformance test suite in Unicode 15.0.0. - * - * Paragraph and line handling - * --------------------------- - * - * The full Unicode bidi algorithm expects to receive text containing - * multiple paragraphs, together with a decision about how those - * paragraphs are broken up into lines. It calculates embedding levels - * a whole paragraph at a time without considering the line breaks, - * but then the final reordering of the text for display is done to - * each _line_ independently based on the levels computed for the text - * in that line. - * - * This algorithm omits all of that, because it's intended for use as - * a display-time transformation of a text terminal, which doesn't - * preserve enough semantic information to decide what's a paragraph - * break and what is not. So a piece of input text provided to this - * algorithm is always expected to consist of exactly one paragraph - * *and* exactly one line. - * - * Embeddings, overrides and isolates - * ---------------------------------- - * - * This implementation has full support for all the Unicode special - * control characters that modify bidi behaviour, such as - * - * U+202A LEFT-TO-RIGHT EMBEDDING - * U+202B RIGHT-TO-LEFT EMBEDDING - * U+202D LEFT-TO-RIGHT OVERRIDE - * U+202E RIGHT-TO-LEFT OVERRIDE - * U+202C POP DIRECTIONAL FORMATTING - * U+2068 FIRST STRONG ISOLATE - * U+2066 LEFT-TO-RIGHT ISOLATE - * U+2067 RIGHT-TO-LEFT ISOLATE - * U+2069 POP DIRECTIONAL ISOLATE - * - * However, at present, the terminal emulator that is a client of this - * code has no way to pass those in (because they're dropped during - * escape sequence processing and don't get stored in the terminal - * state). Nonetheless, the code is all here, so if the terminal - * emulator becomes able to record those characters at some later - * point, we'll be all set to take account of them during bidi. - * - * But the _main_ purpose of supporting the full bidi algorithm is - * simply that that's the easiest way to be sure it's correct, because - * if you support the whole thing, you can run the full conformance - * test suite. (And I don't 100% believe that restricting to the - * subset of _tests_ valid with a reduced character set will test the - * full set of _functionality_ relevant to the reduced set.) - * - * Retained formatting characters - * ------------------------------ - * - * The standard bidi algorithm, in step X9, deletes assorted - * formatting characters from the text: all the embedding and override - * section initiator characters, the Pop Directional Formatting - * character that closes one of those sections again, and any - * character labelled as Boundary Neutral. So the characters it - * returns are not a _full_ reordering of the input; some input - * characters vanish completely. - * - * This would be fine, if it were not for the fact that - as far as I - * can see - _exactly one_ Unicode code point in the discarded - * category has a wcwidth() of more than 0, namely U+00AD SOFT HYPHEN - * which is a printing character for terminal purposes but has a bidi - * class of BN. - * - * Therefore, we must implement a modified version of the algorithm, - * as described in section 5.2 of TR9, which retains those formatting - * characters so that a client can find out where they ended up in the - * reordering. - * - * Section 5.2 describes a set of modifications to the algorithm that - * are _intended_ to achieve this without changing the rest of the - * behaviour: that is, if you take the output of the modified - * algorithm and delete all the characters that the standard algorithm - * would have removed, you should end up with the remaining characters - * in the same order that the standard algorithm would have delivered. - * However, section 5.2 admits the possibility of error, and says "in - * case of any deviation the explicit algorithm is the normative - * statement for conformance". And indeed, in one or two places I - * found I had to make my own tweaks to the section 5.2 description in - * order to get the whole test suite to pass, because I think the 5.2 - * modifications if taken literally don't quite achieve that. My - * justification is that sentence of 5.2: in case of doubt, the right - * thing is to make the code behave the same as the official - * algorithm. - * - * It's possible that there might still be some undiscovered - * discrepancies between the behaviour of the standard and modified - * algorithms. So, just in case, I've kept in this code the ability to - * implement the _standard_ algorithm too! If you compile with - * -DREMOVE_FORMATTING_CHARS, this code should go back to implementing - * the literal UAX#9 bidi algorithm - so you can run your suspect - * input through both versions, making it much easier to figure out - * why they differ, and in which of the many stages of the algorithm - * the difference was introduced. - * - * However, beware that when compiling in this mode, the do_bidi - * interface to the terminal will stop working, and just abort() when - * called! The only useful thing you can do with this mode is to run - * the companion program bidi_test.c. - */ - -#include /* definition of wchar_t */ - -#include "putty.h" -#include "misc.h" -#include "bidi.h" - -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) - -static 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}, -}; - -/* - * Returns the bidi character type of ch. - */ -unsigned char bidi_getType(int ch) -{ - static const struct { - int first, last, type; - } lookup[] = { - #include "unicode/bidi_type.h" - }; - - int i, j, k; - - i = -1; - j = lenof(lookup); - - while (j - 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; -} - -/* - * Return the mirrored version of a glyph. - * - * FIXME: there are also glyphs which the text rendering engine is - * supposed to display left-right reflected, since no mirrored glyph - * exists in Unicode itself to indicate the reflected form. Those are - * listed in comments in BidiMirroring.txt. Many of them are - * mathematical, e.g. the square root sign, or set difference - * operator, or integral sign. No API currently exists here to - * communicate the need for that reflected display back to the client. - */ -static unsigned mirror_glyph(unsigned int ch) -{ - static const struct { - unsigned src, dst; - } mirror_pairs[] = { - #include "unicode/bidi_mirror.h" - }; - - int i, j, k; - - i = -1; - j = lenof(mirror_pairs); - - while (j - i > 1) { - k = (i + j) / 2; - if (ch < mirror_pairs[k].src) - j = k; - else if (ch > mirror_pairs[k].src) - i = k; - else - return mirror_pairs[k].dst; - } - - return ch; -} - -/* - * Identify the bracket characters treated specially by bidi rule - * BD19, and return their paired character(s). - */ -typedef enum { BT_NONE, BT_OPEN, BT_CLOSE } BracketType; -typedef struct BracketTypeData { - unsigned partner, equiv_partner; - BracketType type; -} BracketTypeData; -static BracketTypeData bracket_type(unsigned int ch) -{ - static const struct { - unsigned src; - BracketTypeData payload; - } bracket_pairs[] = { - #include "unicode/bidi_brackets.h" - }; - - int i, j, k; - - i = -1; - j = lenof(bracket_pairs); - - while (j - i > 1) { - k = (i + j) / 2; - if (ch < bracket_pairs[k].src) { - j = k; - } else if (ch > bracket_pairs[k].src) { - i = k; - } else { - return bracket_pairs[k].payload; - } - } - - static const BracketTypeData null = { 0, 0, BT_NONE }; - return null; -} - -/* - * 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.) - */ -bool is_rtl(int c) -{ - return typeIsBidiActive(bidi_getType(c)); -} - -/* 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; - bool ligFlag = false; - - for (i=0; i 0) switch (line[i-1].wc) { - case 0x622: - ligFlag = true; - if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) - to[i].wc = 0xFEF6; - else - to[i].wc = 0xFEF5; - break; - case 0x623: - ligFlag = true; - if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) - to[i].wc = 0xFEF8; - else - to[i].wc = 0xFEF7; - break; - case 0x625: - ligFlag = true; - if ((tempShape == SL) || (tempShape == SD) || (tempShape == SC)) - to[i].wc = 0xFEFA; - else - to[i].wc = 0xFEF9; - break; - case 0x627: - ligFlag = true; - 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 = false; - 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; -} - -typedef enum { DO_NEUTRAL, DO_LTR, DO_RTL } DirectionalOverride; - -typedef struct DSStackEntry { - /* - * An entry in the directional status stack (rule section X). - */ - unsigned char level; - bool isolate; - DirectionalOverride override; -} DSStackEntry; - -typedef struct BracketStackEntry { - /* - * An entry in the bracket-pair-tracking stack (rule BD16). - */ - unsigned ch; - size_t c; -} BracketStackEntry; - -typedef struct IsolatingRunSequence { - size_t start, end; - BidiType sos, eos, embeddingDirection; -} IsolatingRunSequence; - -#define MAX_DEPTH 125 /* specified in the standard */ - -struct BidiContext { - /* - * Storage space preserved between runs, all allocated to the same - * length (internal_array_sizes). - */ - size_t internal_array_sizes; - BidiType *types, *origTypes; - unsigned char *levels; - size_t *irsindices, *bracketpos; - bool *irsdone; - - /* - * Separately allocated with its own size field - */ - IsolatingRunSequence *irslist; - size_t irslistsize; - - /* - * Rewritten to point to the input to the currently active run of - * the bidi algorithm - */ - bidi_char *text; - size_t textlen; - - /* - * State within a run of the algorithm - */ - BidiType paragraphOverride; - DSStackEntry dsstack[MAX_DEPTH + 2]; - size_t ds_sp; - size_t overflowIsolateCount, overflowEmbeddingCount, validIsolateCount; - unsigned char paragraphLevel; - size_t *irs; - size_t irslen; - BidiType sos, eos, embeddingDirection; - BracketStackEntry bstack[63]; /* constant size specified in rule BD16 */ -}; - -BidiContext *bidi_new_context(void) -{ - BidiContext *ctx = snew(BidiContext); - memset(ctx, 0, sizeof(BidiContext)); - return ctx; -} - -void bidi_free_context(BidiContext *ctx) -{ - sfree(ctx->types); - sfree(ctx->origTypes); - sfree(ctx->levels); - sfree(ctx->irsindices); - sfree(ctx->irsdone); - sfree(ctx->bracketpos); - sfree(ctx->irslist); - sfree(ctx); -} - -static void ensure_arrays(BidiContext *ctx, size_t textlen) -{ - if (textlen <= ctx->internal_array_sizes) - return; - ctx->internal_array_sizes = textlen; - ctx->types = sresize(ctx->types, ctx->internal_array_sizes, BidiType); - ctx->origTypes = sresize(ctx->origTypes, ctx->internal_array_sizes, - BidiType); - ctx->levels = sresize(ctx->levels, ctx->internal_array_sizes, - unsigned char); - ctx->irsindices = sresize(ctx->irsindices, ctx->internal_array_sizes, - size_t); - ctx->irsdone = sresize(ctx->irsdone, ctx->internal_array_sizes, bool); - ctx->bracketpos = sresize(ctx->bracketpos, ctx->internal_array_sizes, - size_t); -} - -static void setup_types(BidiContext *ctx) -{ - for (size_t i = 0; i < ctx->textlen; i++) - ctx->types[i] = ctx->origTypes[i] = bidi_getType(ctx->text[i].wc); -} - -static bool text_needs_bidi(BidiContext *ctx) -{ - /* - * Initial optimisation: check for any bidi-active character at - * all in an input line. If there aren't any, we can skip the - * whole algorithm. - * - * Also include the paragraph override in this check! - */ - for (size_t i = 0; i < ctx->textlen; i++) - if (typeIsBidiActive(ctx->types[i])) - return true; - return typeIsBidiActive(ctx->paragraphOverride); -} - -static size_t find_matching_pdi(const BidiType *types, size_t i, size_t size) -{ - /* Assuming that types[i] is an isolate initiator, find its - * matching PDI by rule BD9. */ - unsigned counter = 1; - i++; - for (; i < size; i++) { - BidiType t = types[i]; - if (typeIsIsolateInitiator(t)) { - counter++; - } else if (t == PDI) { - counter--; - if (counter == 0) - return i; - } - } - - /* If no PDI was found, return the length of the array. */ - return size; -} - -static unsigned char rule_p2_p3(const BidiType *types, size_t size) -{ - /* - * Rule P2. Find the first strong type (L, R or AL), ignoring - * anything inside an isolated segment. - * - * Rule P3. If that type is R or AL, choose a paragraph embeddding - * level of 1, otherwise 0. - */ - for (size_t i = 0; i < size; i++) { - BidiType t = types[i]; - if (typeIsIsolateInitiator(t)) - i = find_matching_pdi(types, i, size); - else if (typeIsStrong(t)) - return (t == L ? 0 : 1); - } - - return 0; /* default if no strong type found */ -} - -static void set_paragraph_level(BidiContext *ctx) -{ - if (ctx->paragraphOverride == L) - ctx->paragraphLevel = 0; - else if (ctx->paragraphOverride == R) - ctx->paragraphLevel = 1; - else - ctx->paragraphLevel = rule_p2_p3(ctx->types, ctx->textlen); -} - -static inline unsigned char nextOddLevel(unsigned char x) { return (x+1)|1; } -static inline unsigned char nextEvenLevel(unsigned char x) { return (x|1)+1; } - -static inline void push(BidiContext *ctx, unsigned char level, - DirectionalOverride override, bool isolate) -{ - ctx->ds_sp++; - assert(ctx->ds_sp < lenof(ctx->dsstack)); - ctx->dsstack[ctx->ds_sp].level = level; - ctx->dsstack[ctx->ds_sp].override = override; - ctx->dsstack[ctx->ds_sp].isolate = isolate; -} - -static inline void pop(BidiContext *ctx) -{ - assert(ctx->ds_sp > 0); - ctx->ds_sp--; -} - -static void process_explicit_embeddings(BidiContext *ctx) -{ - /* - * Rule X1 initialisation. - */ - ctx->ds_sp = (size_t)-1; - push(ctx, ctx->paragraphLevel, DO_NEUTRAL, false); - ctx->overflowIsolateCount = 0; - ctx->overflowEmbeddingCount = 0; - ctx->validIsolateCount = 0; - - #define stk (&ctx->dsstack[ctx->ds_sp]) - - for (size_t i = 0; i < ctx->textlen; i++) { - BidiType t = ctx->types[i]; - switch (t) { - case RLE: case LRE: case RLO: case LRO: { - /* Rules X2-X5 */ - unsigned char newLevel; - DirectionalOverride override; - -#ifndef REMOVE_FORMATTING_CHARS - ctx->levels[i] = stk->level; -#endif - - switch (t) { - case RLE: /* rule X2 */ - newLevel = nextOddLevel(stk->level); - override = DO_NEUTRAL; - break; - case LRE: /* rule X3 */ - newLevel = nextEvenLevel(stk->level); - override = DO_NEUTRAL; - break; - case RLO: /* rule X4 */ - newLevel = nextOddLevel(stk->level); - override = DO_RTL; - break; - case LRO: /* rule X5 */ - newLevel = nextEvenLevel(stk->level); - override = DO_LTR; - break; - default: - unreachable("how did this get past the outer switch?"); - } - - if (newLevel <= MAX_DEPTH && - ctx->overflowIsolateCount == 0 && - ctx->overflowEmbeddingCount == 0) { - /* Embedding code is valid. Push a stack entry. */ - push(ctx, newLevel, override, false); - } else { - /* Embedding code is an overflow one. */ - if (ctx->overflowIsolateCount == 0) - ctx->overflowEmbeddingCount++; - } - break; - } - - case RLI: case LRI: case FSI: { - /* Rules X5a, X5b, X5c */ - - if (t == FSI) { - /* Rule X5c: decide whether this should be treated - * like RLI or LRI */ - size_t pdi = find_matching_pdi(ctx->types, i, ctx->textlen); - unsigned char level = rule_p2_p3(ctx->types + (i + 1), - pdi - (i + 1)); - t = (level == 1 ? RLI : LRI); - } - - ctx->levels[i] = stk->level; - if (stk->override != DO_NEUTRAL) - ctx->types[i] = (stk->override == DO_LTR ? L : - stk->override == DO_RTL ? R : t); - - unsigned char newLevel = (t == RLI ? nextOddLevel(stk->level) : - nextEvenLevel(stk->level)); - - if (newLevel <= MAX_DEPTH && - ctx->overflowIsolateCount == 0 && - ctx->overflowEmbeddingCount == 0) { - /* Isolate code is valid. Push a stack entry. */ - push(ctx, newLevel, DO_NEUTRAL, true); - ctx->validIsolateCount++; - } else { - /* Isolate code is an overflow one. */ - ctx->overflowIsolateCount++; - } - break; - } - - case PDI: { - /* Rule X6a */ - if (ctx->overflowIsolateCount > 0) { - ctx->overflowIsolateCount--; - } else if (ctx->validIsolateCount == 0) { - /* Do nothing: spurious isolate-pop */ - } else { - /* Valid isolate-pop. We expect that the stack must - * therefore contain at least one isolate==true entry, - * so pop everything up to and including it. */ - ctx->overflowEmbeddingCount = 0; - while (!stk->isolate) - pop(ctx); - pop(ctx); - ctx->validIsolateCount--; - } - ctx->levels[i] = stk->level; - if (stk->override != DO_NEUTRAL) - ctx->types[i] = (stk->override == DO_LTR ? L : R); - break; - } - - case PDF: { - /* Rule X7 */ - if (ctx->overflowIsolateCount > 0) { - /* Do nothing if we've overflowed on isolates */ - } else if (ctx->overflowEmbeddingCount > 0) { - ctx->overflowEmbeddingCount--; - } else if (ctx->ds_sp > 0 && !stk->isolate) { - pop(ctx); - } else { - /* Do nothing: spurious embedding-pop */ - } - -#ifndef REMOVE_FORMATTING_CHARS - ctx->levels[i] = stk->level; -#endif - break; - } - - case B: { - /* Rule X8: if an explicit paragraph separator appears in - * this text at all then it does not participate in any of - * the above, and just gets assigned the paragraph level. - * - * PS, it had better be right at the end of the text, - * because we have not implemented rule P1 in this code. */ - assert(i == ctx->textlen - 1); - ctx->levels[i] = ctx->paragraphLevel; - break; - } - - case BN: { - /* - * The section 5.2 adjustment to rule X6 says that we - * apply it to BN just like any other class. But I think - * this can't possibly give the same results as the - * unmodified algorithm. - * - * Proof: adding RLO BN or LRO BN at the end of a - * paragraph should not change the output of the standard - * algorithm, because the override doesn't affect the BN - * in rule X6, and then rule X9 removes both. But with the - * modified rule X6, the BN is changed into R or L, and - * then rule X9 doesn't remove it, and then you've added a - * strong type that will set eos for the level run just - * before the override. And whatever the standard - * algorithm set eos to, _one_ of these override sequences - * will disagree with it. - * - * So I think we just set the BN's level, and don't change - * its type. - */ - ctx->levels[i] = stk->level; - break; - } - - default: { - /* Rule X6. */ - ctx->levels[i] = stk->level; - if (stk->override != DO_NEUTRAL) - ctx->types[i] = (stk->override == DO_LTR ? L : R); - break; - } - } - } - - #undef stk -} - -static void remove_embedding_characters(BidiContext *ctx) -{ -#ifndef REMOVE_FORMATTING_CHARS - /* - * Rule X9, as modified by section 5.2: turn embedding (but not - * isolate) characters into BN. - */ - for (size_t i = 0; i < ctx->textlen; i++) { - BidiType t = ctx->types[i]; - if (typeIsRemovedDuringProcessing(t)) { - ctx->types[i] = BN; - - /* - * My own adjustment to the section 5.2 mods: a sequence - * of contiguous BN generated by this setup should never - * be at different levels from each other. - * - * An example where this goes wrong is if you open two - * LREs in sequence, then close them again: - * - * ... LRE LRE PDF PDF ... - * - * The initial level assignment gives level 0 to the outer - * LRE/PDF pair, and level 2 to the inner one. The - * standard algorithm would remove all four, so this - * doesn't matter, and you end up with no break in the - * surrounding level run. But if you just rewrite the - * types of all those characters to BN and leave the - * levels in that state, then the modified algorithm will - * leave the middle two BN at level 2, dividing what - * should have been a long level run at level 0 into two - * separate ones. - */ - if (i > 0 && ctx->types[i-1] == BN) - ctx->levels[i] = ctx->levels[i-1]; - } - } -#else - /* - * Rule X9, original version: completely remove embedding - * start/end characters and also boundary neutrals. - */ - size_t outpos = 0; - for (size_t i = 0; i < ctx->textlen; i++) { - BidiType t = ctx->types[i]; - if (!typeIsRemovedDuringProcessing(t)) { - ctx->text[outpos] = ctx->text[i]; - ctx->levels[outpos] = ctx->levels[i]; - ctx->types[outpos] = ctx->types[i]; - ctx->origTypes[outpos] = ctx->origTypes[i]; - outpos++; - } - } - ctx->textlen = outpos; -#endif -} - -typedef void (*irs_fn_t)(BidiContext *ctx); - -static void find_isolating_run_sequences(BidiContext *ctx, irs_fn_t process) -{ - /* - * Rule X10 / BD13. Now that we've assigned an embedding level to - * each character in the text, we have to divide the text into - * subsequences on which to do the next stage of processing. - * - * In earlier issues of the bidi algorithm, these subsequences - * were contiguous in the original text, and each one was a 'level - * run': a maximal contiguous subsequence of characters all at the - * same embedding level. - * - * But now we have isolates, and the point of an (isolate - * initiator ... PDI) sequence is that the whole sequence should - * be treated like a single BN for the purposes of formatting - * everything outside it. As a result, we now have to recombine - * our level runs into longer sequences, on the principle that if - * a level run ends with an isolate initiator, then we bring it - * together with whatever later level run starts with the matching - * PDI. - * - * These subsequences are no longer contiguous (the whole point is - * that between the isolate initiator and the PDI is some other - * text that we've skipped over). They're called 'isolating run - * sequences'. - */ - - memset(ctx->irsdone, 0, ctx->textlen); - size_t i = 0; - size_t n_irs = 0; - size_t indexpos = 0; - while (i < ctx->textlen) { - if (ctx->irsdone[i]) { - i++; - continue; - } - - /* - * Found a character not already processed. Start a new - * sequence here. - */ - sgrowarray(ctx->irslist, ctx->irslistsize, n_irs); - IsolatingRunSequence *irs = &ctx->irslist[n_irs++]; - irs->start = indexpos; - size_t j = i; - size_t irslevel = ctx->levels[i]; - while (j < ctx->textlen) { - /* - * We expect that all level runs in this sequence will be - * at the same level as each other, by construction of how - * we set up the levels from the isolates in the first - * place. - */ - assert(ctx->levels[j] == irslevel); - - do { - ctx->irsdone[j] = true; - ctx->irsindices[indexpos++] = j++; - } while (j < ctx->textlen && ctx->levels[j] == irslevel); - if (!typeIsIsolateInitiator(ctx->types[j-1])) - break; /* this IRS is ended */ - j = find_matching_pdi(ctx->types, j-1, ctx->textlen); - } - irs->end = indexpos; - - /* - * Determine the start-of-sequence and end-of-sequence types - * for this sequence. - * - * These depend on the embedding levels of surrounding text. - * But processing each run can change those levels. That's why - * we have to use a two-pass strategy here, first identifying - * all the isolating run sequences using the input level data, - * and not processing any of them until we know where they all - * are. - */ - size_t p; - unsigned char level_inside, level_outside, level_max; - - p = i; - level_inside = ctx->levels[p]; - level_outside = ctx->paragraphLevel; - while (p > 0) { - p--; - if (ctx->types[p] != BN) { - level_outside = ctx->levels[p]; - break; - } - } - level_max = max(level_inside, level_outside); - irs->sos = (level_max % 2 ? R : L); - - p = ctx->irsindices[irs->end - 1]; - level_inside = ctx->levels[p]; - level_outside = ctx->paragraphLevel; - if (typeIsIsolateInitiator(ctx->types[p])) { - /* Special case: if an isolating run sequence ends in an - * unmatched isolate initiator, then level_outside is - * taken to be the paragraph embedding level and the - * loop below is skipped. */ - } else { - while (p+1 < ctx->textlen) { - p++; - if (ctx->types[p] != BN) { - level_outside = ctx->levels[p]; - break; - } - } - } - level_max = max(level_inside, level_outside); - irs->eos = (level_max % 2 ? R : L); - - irs->embeddingDirection = (irslevel % 2 ? R : L); - - /* - * Now we've listed in ctx->irsindices[] the index of every - * character that's part of this isolating run sequence, and - * recorded an entry in irslist containing the interval of - * indices relevant to this IRS, plus its assorted metadata. - * We've also marked those locations in the input text as done - * in ctx->irsdone, so that we'll skip over them when the - * outer iteration reaches them later. - */ - } - - for (size_t k = 0; k < n_irs; k++) { - IsolatingRunSequence *irs = &ctx->irslist[k]; - ctx->irs = ctx->irsindices + irs->start; - ctx->irslen = irs->end - irs->start; - ctx->sos = irs->sos; - ctx->eos = irs->eos; - ctx->embeddingDirection = irs->embeddingDirection; - process(ctx); - } - - /* Reset irslen to 0 when we've finished. This means any other - * functions that absentmindedly try to use irslen at all will end - * up doing nothing at all, which should be easier to detect and - * debug than if they run on subtly the wrong subset of the - * text. */ - ctx->irslen = 0; -} - -static void remove_nsm(BidiContext *ctx) -{ - /* Rule W1: NSM gains the type of the previous character, or sos - * at the start of the run, with the exception that isolation - * boundaries turn into ON. */ - BidiType prevType = ctx->sos; - for (size_t c = 0; c < ctx->irslen; c++) { - size_t i = ctx->irs[c]; - BidiType t = ctx->types[i]; - if (t == NSM) { - ctx->types[i] = prevType; - } else if (typeIsIsolateInitiatorOrPDI(t)) { - prevType = ON; -#ifndef REMOVE_FORMATTING_CHARS - } else if (t == BN) { - /* section 5.2 adjustment: these don't affect prevType */ -#endif - } else { - prevType = t; - } - } -} - -static void change_en_to_an(BidiContext *ctx) -{ - /* Rule W2: EN becomes AN if the previous strong type is AL. (The - * spec says that the 'previous strong type' is counted as sos at - * the start of the run, although it hardly matters, since sos - * can't be AL.) */ - BidiType prevStrongType = ctx->sos; - for (size_t c = 0; c < ctx->irslen; c++) { - size_t i = ctx->irs[c]; - BidiType t = ctx->types[i]; - if (t == EN && prevStrongType == AL) { - ctx->types[i] = AN; - } else if (typeIsStrong(t)) { - prevStrongType = t; - } - } -} - -static void change_al_to_r(BidiContext *ctx) -{ - /* Rule W3: AL becomes R unconditionally. (The only difference - * between the two types was their effect on nearby numbers, which - * was dealt with in rule W2, so now we're done with the - * distinction.) */ - for (size_t c = 0; c < ctx->irslen; c++) { - size_t i = ctx->irs[c]; - if (ctx->types[i] == AL) - ctx->types[i] = R; - } -} - -static void eliminate_separators_between_numbers(BidiContext *ctx) -{ - /* Rule W4: a single numeric separator between two numbers of the - * same type compatible with that separator takes the type of the - * number. ES is a separator type compatible only with EN; CS is a - * separator type compatible with either EN or AN. - * - * Section 5.2 adjustment: intervening BNs do not break this, so - * instead of simply looking at types[irs[c-1]] and types[irs[c+1]], - * we must track the last three indices we saw that were not BN. */ - size_t i1 = 0, i2 = 0; - BidiType t0 = ON, t1 = ON, t2 = ON; - for (size_t c = 0; c < ctx->irslen; c++) { - size_t i = ctx->irs[c]; - BidiType t = ctx->types[i]; - -#ifndef REMOVE_FORMATTING_CHARS - if (t == BN) - continue; -#endif - - i1 = i2; i2 = i; - t0 = t1; t1 = t2; t2 = t; - if (t0 == t2 && ((t1 == ES && t0 == EN) || - (t1 == CS && (t0 == EN || t0 == AN)))) { - ctx->types[i1] = t0; - } - } -} - -static void eliminate_et_next_to_en(BidiContext *ctx) -{ - /* Rule W5: a sequence of ET adjacent to an EN take the type EN. - * This is easiest to implement with one loop in each direction. - * - * Section 5.2 adjustment: include BN with ET. (We don't need to - * #ifdef that out, because in the standard algorithm, we won't - * have any BN left in any case.) */ - - bool modifying = false; - - for (size_t c = 0; c < ctx->irslen; c++) { - size_t i = ctx->irs[c]; - BidiType t = ctx->types[i]; - if (t == EN) { - modifying = true; - } else if (modifying && typeIsETOrBN(t)) { - ctx->types[i] = EN; - } else { - modifying = false; - } - } - - for (size_t c = ctx->irslen; c-- > 0 ;) { - size_t i = ctx->irs[c]; - BidiType t = ctx->types[i]; - if (t == EN) { - modifying = true; - } else if (modifying && typeIsETOrBN(t)) { - ctx->types[i] = EN; - } else { - modifying = false; - } - } -} - -static void eliminate_separators_and_terminators(BidiContext *ctx) -{ - /* Rule W6: all separators and terminators change to ON. - * - * (The spec is not quite clear on which bidi types are included - * in this; one assumes ES, ET and CS, but what about S? I _think_ - * the answer is that this is a rule in the W section, so it's - * implicitly supposed to only apply to types designated as weakly - * directional, so not S.) */ - -#ifndef REMOVE_FORMATTING_CHARS - /* - * Section 5.2 adjustment: this also applies to any BN adjacent on - * either side to one of these types, which is easiest to - * implement with a separate double-loop converting those to an - * arbitrary one of the affected types, say CS. - * - * This double loop can be completely skipped in the standard - * algorithm. - */ - bool modifying = false; - - for (size_t c = 0; c < ctx->irslen; c++) { - size_t i = ctx->irs[c]; - BidiType t = ctx->types[i]; - if (typeIsWeakSeparatorOrTerminator(t)) { - modifying = true; - } else if (modifying && t == BN) { - ctx->types[i] = CS; - } else { - modifying = false; - } - } - - for (size_t c = ctx->irslen; c-- > 0 ;) { - size_t i = ctx->irs[c]; - BidiType t = ctx->types[i]; - if (typeIsWeakSeparatorOrTerminator(t)) { - modifying = true; - } else if (modifying && t == BN) { - ctx->types[i] = CS; - } else { - modifying = false; - } - } -#endif - - /* Now the main part of rule W6 */ - for (size_t c = 0; c < ctx->irslen; c++) { - size_t i = ctx->irs[c]; - BidiType t = ctx->types[i]; - if (typeIsWeakSeparatorOrTerminator(t)) - ctx->types[i] = ON; - } -} - -static void change_en_to_l(BidiContext *ctx) -{ - /* Rule W7: EN becomes L if the previous strong type (or sos) is L. */ - BidiType prevStrongType = ctx->sos; - for (size_t c = 0; c < ctx->irslen; c++) { - size_t i = ctx->irs[c]; - BidiType t = ctx->types[i]; - if (t == EN && prevStrongType == L) { - ctx->types[i] = L; - } else if (typeIsStrong(t)) { - prevStrongType = t; - } - } -} - -typedef void (*bracket_pair_fn)(BidiContext *ctx, size_t copen, size_t cclose); - -static void find_bracket_pairs(BidiContext *ctx, bracket_pair_fn process) -{ - const size_t NO_BRACKET = ~(size_t)0; - - /* - * Rule BD16. - */ - size_t sp = 0; - for (size_t c = 0; c < ctx->irslen; c++) - ctx->bracketpos[c] = NO_BRACKET; - - for (size_t c = 0; c < ctx->irslen; c++) { - size_t i = ctx->irs[c]; - unsigned wc = ctx->text[i].wc; - BracketTypeData bt = bracket_type(wc); - if (bt.type == BT_OPEN) { - if (sp >= lenof(ctx->bstack)) { - /* - * Stack overflow. The spec says we simply give up at - * this point. - */ - goto found_all_pairs; - } - - ctx->bstack[sp].ch = wc; - ctx->bstack[sp].c = c; - sp++; - } else if (bt.type == BT_CLOSE) { - size_t new_sp = sp; - - /* - * Search up the stack for an entry containing a matching - * open bracket. If we find it, pop that entry and - * everything deeper, and record a matching pair. If we - * reach the bottom of the stack without finding anything, - * leave sp where it started. - */ - while (new_sp-- > 0) { - if (ctx->bstack[new_sp].ch == bt.partner || - ctx->bstack[new_sp].ch == bt.equiv_partner) { - /* Found a stack element matching this one */ - size_t cstart = ctx->bstack[new_sp].c; - ctx->bracketpos[cstart] = c; - sp = new_sp; - break; - } - } - } - } - - found_all_pairs: - for (size_t c = 0; c < ctx->irslen; c++) { - if (ctx->bracketpos[c] != NO_BRACKET) { - process(ctx, c, ctx->bracketpos[c]); - } - } -} - -static BidiType get_bracket_type(BidiContext *ctx, size_t copen, size_t cclose) -{ - /* - * Rule N0: a pair of matched brackets containing at least one - * strong type takes on the current embedding direction, unless - * all of these are true at once: - * - * (a) there are no strong types inside the brackets matching the - * current embedding direction - * (b) there _is_ at least one strong type inside the brackets - * that is _opposite_ to the current embedding direction - * (c) the strong type preceding the open bracket is also - * opposite to the current embedding direction - * - * in which case they take on the opposite direction. - * - * For these purposes, number types (EN and AN) count as R. - */ - - bool foundOppositeTypeInside = false; - for (size_t c = copen + 1; c < cclose; c++) { - size_t i = ctx->irs[c]; - BidiType t = ctx->types[i]; - if (typeIsStrongOrNumber(t)) { - t = t == L ? L : R; /* numbers count as R */ - if (t == ctx->embeddingDirection) { - /* Found something inside the brackets matching the - * current level, so (a) is violated. */ - return ctx->embeddingDirection; - } else { - foundOppositeTypeInside = true; - } - } - } - - if (!foundOppositeTypeInside) { - /* No strong types at all inside the brackets, so return ON to - * indicate that we're not messing with their type at all. */ - return ON; - } - - /* There was an opposite strong type in the brackets. Look - * backwards to the preceding strong type, and go with that, - * whichever it is. */ - for (size_t c = copen; c-- > 0 ;) { - size_t i = ctx->irs[c]; - BidiType t = ctx->types[i]; - if (typeIsStrongOrNumber(t)) { - t = t == L ? L : R; /* numbers count as R */ - return t; - } - } - - /* Fallback: if the preceding strong type was not found, go with - * sos. */ - return ctx->sos; -} - -static void reset_bracket_type(BidiContext *ctx, size_t c, BidiType t) -{ - /* Final bullet point of rule N0: when we change the type of a - * bracket, the same change applies to any contiguous sequence of - * characters after it whose _original_ bidi type was NSM. */ - do { - ctx->types[ctx->irs[c++]] = t; - -#ifndef REMOVE_FORMATTING_CHARS - while (c < ctx->irslen && ctx->origTypes[ctx->irs[c]] == BN) { - /* Section 5.2 adjustment: skip past BN in the process. */ - c++; - } -#endif - } while (c < ctx->irslen && ctx->origTypes[ctx->irs[c]] == NSM); -} - -static void resolve_brackets(BidiContext *ctx, size_t copen, size_t cclose) -{ - if (typeIsNeutral(ctx->types[ctx->irs[copen]]) && - typeIsNeutral(ctx->types[ctx->irs[cclose]])) { - BidiType t = get_bracket_type(ctx, copen, cclose); - if (t != ON) { - reset_bracket_type(ctx, copen, t); - reset_bracket_type(ctx, cclose, t); - } - } -} - -static void remove_ni(BidiContext *ctx) -{ - /* - * Rules N1 and N2 together: neutral or isolate characters take - * the direction of the surrounding strong text if the nearest - * strong characters on each side match, and otherwise, they take - * the embedding direction. - */ - const size_t NO_INDEX = ~(size_t)0; - BidiType prevStrongType = ctx->sos; - size_t c_ni_start = NO_INDEX; - for (size_t c = 0; c <= ctx->irslen; c++) { - BidiType t; - - if (c < ctx->irslen) { - size_t i = ctx->irs[c]; - t = ctx->types[i]; - } else { - /* One extra loop iteration, using eos to resolve the - * final sequence of NI if any */ - t = ctx->eos; - } - - if (typeIsStrongOrNumber(t)) { - t = t == L ? L : R; /* numbers count as R */ - if (c_ni_start != NO_INDEX) { - /* There are some NI we have to fix up */ - BidiType ni_type = (t == prevStrongType ? t : - ctx->embeddingDirection); - for (size_t c2 = c_ni_start; c2 < c; c2++) { - size_t i2 = ctx->irs[c2]; - BidiType t2 = ctx->types[i2]; - if (typeIsNeutralOrIsolate(t2)) - ctx->types[i2] = ni_type; - } - } - prevStrongType = t; - c_ni_start = NO_INDEX; - } else if (typeIsNeutralOrIsolate(t) && c_ni_start == NO_INDEX) { - c_ni_start = c; - } - } -} - -static void resolve_implicit_levels(BidiContext *ctx) -{ - /* Rules I1 and I2 */ - for (size_t c = 0; c < ctx->irslen; c++) { - size_t i = ctx->irs[c]; - unsigned char level = ctx->levels[i]; - BidiType t = ctx->types[i]; - if (level % 2 == 0) { - /* Rule I1 */ - if (t == R) - ctx->levels[i] += 1; - else if (t == AN || t == EN) - ctx->levels[i] += 2; - } else { - /* Rule I2 */ - if (t == L || t == AN || t == EN) - ctx->levels[i] += 1; - } - } -} - -static void process_isolating_run_sequence(BidiContext *ctx) -{ - /* Section W: resolve weak types */ - remove_nsm(ctx); - change_en_to_an(ctx); - change_al_to_r(ctx); - eliminate_separators_between_numbers(ctx); - eliminate_et_next_to_en(ctx); - eliminate_separators_and_terminators(ctx); - change_en_to_l(ctx); - - /* Section N: resolve neutral types (and isolates) */ - find_bracket_pairs(ctx, resolve_brackets); - remove_ni(ctx); - - /* Section I: resolve implicit levels */ - resolve_implicit_levels(ctx); -} - -static void reset_whitespace_and_separators(BidiContext *ctx) -{ - /* - * Rule L1: segment and paragraph separators, plus whitespace - * preceding them, all reset to the paragraph embedding level. - * This also applies to whitespace at the very end. - * - * This is done using the original types, not the versions that - * the rest of this algorithm has been merrily mutating. - */ - bool modifying = true; - for (size_t i = ctx->textlen; i-- > 0 ;) { - BidiType t = ctx->origTypes[i]; - if (typeIsSegmentOrParaSeparator(t)) { - ctx->levels[i] = ctx->paragraphLevel; - modifying = true; - } else if (modifying) { - if (typeIsWhitespaceOrIsolate(t)) { - ctx->levels[i] = ctx->paragraphLevel; - } else if (!typeIsRemovedDuringProcessing(t)) { - modifying = false; - } - } - } - -#ifndef REMOVE_FORMATTING_CHARS - /* - * Section 5.2 adjustment: types removed by rule X9 take the level - * of the character to their left. - */ - for (size_t i = 0; i < ctx->textlen; i++) { - BidiType t = ctx->origTypes[i]; - if (typeIsRemovedDuringProcessing(t)) { - /* Section 5.2 adjustment */ - ctx->levels[i] = (i > 0 ? ctx->levels[i-1] : ctx->paragraphLevel); - } - } -#endif /* ! REMOVE_FORMATTING_CHARS */ -} - -static void reverse(BidiContext *ctx, size_t start, size_t end) -{ - for (size_t i = start, j = end; i < j; i++, j--) { - bidi_char tmp = ctx->text[i]; - ctx->text[i] = ctx->text[j]; - ctx->text[j] = tmp; - } -} - -static void mirror_glyphs(BidiContext *ctx) -{ - /* - * Rule L3: any character with a mirror-image pair at an odd - * embedding level is replaced by its mirror image. - * - * This is specified in the standard as happening _after_ rule L2 - * (the actual reordering of the text). But it's much easier to - * implement it before, while our levels[] array still matches up - * to the text order. - */ - for (size_t i = 0; i < ctx->textlen; i++) { - if (ctx->levels[i] % 2) - ctx->text[i].wc = mirror_glyph(ctx->text[i].wc); - } -} - -static void reverse_sequences(BidiContext *ctx) -{ - /* - * Rule L2: every maximal contiguous sequence of characters at a - * given level or higher is reversed. - */ - unsigned level = 0; - for (size_t i = 0; i < ctx->textlen; i++) - level = max(level, ctx->levels[i]); - - for (; level >= 1; level--) { - for (size_t i = 0; i < ctx->textlen; i++) { - if (ctx->levels[i] >= level) { - size_t start = i; - while (i+1 < ctx->textlen && ctx->levels[i+1] >= level) - i++; - reverse(ctx, start, i); - } - } - } -} - -/* - * The Main Bidi Function. The two wrappers below it present different - * external APIs for different purposes, but everything comes through - * here. - * - * text: a buffer of size textlen containing text to apply the - * Bidirectional algorithm to. - */ -static void do_bidi_new(BidiContext *ctx, bidi_char *text, size_t textlen) -{ - ensure_arrays(ctx, textlen); - ctx->text = text; - ctx->textlen = textlen; - setup_types(ctx); - - /* Quick initial test: see if we need to bother with any work at all */ - if (!text_needs_bidi(ctx)) - return; - - set_paragraph_level(ctx); - process_explicit_embeddings(ctx); - remove_embedding_characters(ctx); - find_isolating_run_sequences(ctx, process_isolating_run_sequence); - - /* If this implementation distinguished paragraphs from lines, - * then this would be the point where we repeat the remainder of - * the algorithm once for each line in the paragraph. */ - - reset_whitespace_and_separators(ctx); - mirror_glyphs(ctx); - reverse_sequences(ctx); -} - -size_t do_bidi_test(BidiContext *ctx, bidi_char *text, size_t textlen, - int override) -{ - ctx->paragraphOverride = (override > 0 ? L : override < 0 ? R : ON); - do_bidi_new(ctx, text, textlen); - return ctx->textlen; -} - -void do_bidi(BidiContext *ctx, bidi_char *text, size_t textlen) -{ -#ifdef REMOVE_FORMATTING_CHARACTERS - abort(); /* can't use the standard algorithm in a live terminal */ -#else - ctx->paragraphOverride = ON; - do_bidi_new(ctx, text, textlen); -#endif -} diff --git a/terminal/bidi.h b/terminal/bidi.h deleted file mode 100644 index 90d68e5b5..000000000 --- a/terminal/bidi.h +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Header file shared between bidi.c and its tests. Not used by - * anything outside the bidi subsystem. - */ - -#ifndef PUTTY_BIDI_H -#define PUTTY_BIDI_H - -#define LMASK 0x3F /* Embedding Level mask */ -#define OMASK 0xC0 /* Override mask */ -#define OISL 0x80 /* Override is L */ -#define OISR 0x40 /* Override is R */ - -/* 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 ) - -/* Function declarations used outside bidi.c */ -unsigned char bidi_getType(int ch); - -/* Bidi character types */ -#define BIDI_CHAR_TYPE_LIST(X) \ - X(L) \ - X(LRE) \ - X(LRO) \ - X(LRI) \ - X(R) \ - X(AL) \ - X(RLE) \ - X(RLO) \ - X(RLI) \ - X(PDF) \ - X(PDI) \ - X(FSI) \ - X(EN) \ - X(ES) \ - X(ET) \ - X(AN) \ - X(CS) \ - X(NSM) \ - X(BN) \ - X(B) \ - X(S) \ - X(WS) \ - X(ON) \ - /* end of list */ - -/* Shaping Types */ -#define SHAPING_CHAR_TYPE_LIST(X) \ - X(SL) /* Left-Joining, doesn't exist in U+0600 - U+06FF */ \ - X(SR) /* Right-Joining, ie has Isolated, Final */ \ - X(SD) /* Dual-Joining, ie has Isolated, Final, Initial, Medial */ \ - X(SU) /* Non-Joining */ \ - X(SC) /* Join-Causing, like U+0640 (TATWEEL) */ \ - /* end of list */ - -#define ENUM_DECL(name) name, -typedef enum { BIDI_CHAR_TYPE_LIST(ENUM_DECL) N_BIDI_TYPES } BidiType; -typedef enum { SHAPING_CHAR_TYPE_LIST(ENUM_DECL) N_SHAPING_TYPES } ShapingType; -#undef ENUM_DECL - -static inline bool typeIsStrong(BidiType t) -{ - return ((1< -#include - -#include "putty.h" -#include "misc.h" -#include "bidi.h" - -void out_of_memory(void) -{ - fprintf(stderr, "out of memory!\n"); - exit(2); -} - -#define TYPETONAME(X) #X, -static const char *const typenames[] = { BIDI_CHAR_TYPE_LIST(TYPETONAME) }; -#undef TYPETONAME - -int main(int argc, char **argv) -{ - int i; - - for (i = 1; i < argc; i++) { - unsigned long chr = strtoul(argv[i], NULL, 0); - int type = bidi_getType(chr); - printf("U+%04x: %s\n", (unsigned)chr, typenames[type]); - } - - return 0; -} diff --git a/terminal/bidi_test.c b/terminal/bidi_test.c deleted file mode 100644 index 1acd1d68d..000000000 --- a/terminal/bidi_test.c +++ /dev/null @@ -1,372 +0,0 @@ -/* - * Test program that reads the Unicode bidi algorithm test case lists - * that form part of the Unicode Character Database: - * - * https://www.unicode.org/Public/UCD/latest/ucd/BidiTest.txt - * https://www.unicode.org/Public/UCD/latest/ucd/BidiCharacterTest.txt - */ - -#include - -#include "putty.h" -#include "misc.h" -#include "bidi.h" - -static int pass = 0, fail = 0; - -static BidiContext *ctx; - -static const char *extract_word(char **ptr) -{ - char *p = *ptr; - while (*p && isspace((unsigned char)*p)) p++; - - char *start = p; - while (*p && !isspace((unsigned char)*p)) p++; - - if (*p) { - *p++ = '\0'; - while (*p && isspace((unsigned char)*p)) p++; - } - - *ptr = p; - return start; -} - -#define TYPETONAME(X) #X, -static const char *const typenames[] = { BIDI_CHAR_TYPE_LIST(TYPETONAME) }; -#undef TYPETONAME - -static void run_test(const char *filename, unsigned lineno, - bidi_char *bcs, size_t bcs_len, - const unsigned *order, size_t order_len, - int override) -{ - size_t bcs_orig_len = bcs_len; - bidi_char *bcs_orig = snewn(bcs_orig_len, bidi_char); - if (bcs_orig_len) - memcpy(bcs_orig, bcs, bcs_orig_len * sizeof(bidi_char)); - - bcs_len = do_bidi_test(ctx, bcs, bcs_len, override); - - /* - * TR9 revision 44 rule X9 says we remove explicit embedding - * controls and BN characters. So the test cases don't list them - * in the expected outputs. Do the same to our own output - unless - * we're testing the standard version of the algorithm, in which - * case, we expect the output to be exactly as the test cases say. - */ - unsigned *our_order = snewn(bcs_len, unsigned); - size_t our_order_len = 0; - for (size_t i = 0; i < bcs_len; i++) { - BidiType t = bidi_getType(bcs[i].wc); -#ifndef REMOVE_FORMATTING_CHARS - if (typeIsRemovedDuringProcessing(t)) - continue; -#endif - our_order[our_order_len++] = bcs[i].index; - } - - bool ok = false; - if (our_order_len == order_len) { - ok = true; - for (size_t i = 0; i < our_order_len; i++) - if (our_order[i] != order[i]) - ok = false; - } - if (ok) { - pass++; - } else { - fail++; - printf("%s:%u: failed order\n", filename, lineno); - printf(" input chars:"); - for (size_t i = 0; i < bcs_orig_len; i++) - printf(" %04x", bcs_orig[i].wc); - printf("\n"); - printf(" classes: "); - for (size_t i = 0; i < bcs_orig_len; i++) - printf(" %-4s", typenames[bidi_getType(bcs_orig[i].wc)]); - printf("\n"); - printf(" para level = %s\n", - override > 0 ? "LTR" : override < 0 ? "RTL" : "auto"); - printf(" expected:"); - for (size_t i = 0; i < order_len; i++) - printf(" %u", order[i]); - printf("\n"); - printf(" got: "); - for (size_t i = 0; i < our_order_len; i++) - printf(" %u", our_order[i]); - printf("\n"); - } - - /* Put the original data back so we can re-test with another override */ - memcpy(bcs, bcs_orig, bcs_orig_len * sizeof(bidi_char)); - - sfree(bcs_orig); - sfree(our_order); -} - -static void class_test(const char *filename, FILE *fp) -{ - unsigned lineno = 0; - size_t bcs_size = 0, bcs_len = 0; - bidi_char *bcs = NULL; - size_t order_size = 0, order_len = 0; - unsigned *order = NULL; - - /* Preliminary: find a representative character of every bidi - * type. Prefer positive-width ones if available. */ - unsigned representatives[N_BIDI_TYPES]; - for (size_t i = 0; i < N_BIDI_TYPES; i++) - representatives[i] = 0; - for (unsigned uc = 1; uc < 0x110000; uc++) { - unsigned type = bidi_getType(uc); - if (!representatives[type] || - (mk_wcwidth(representatives[type]) <= 0 && mk_wcwidth(uc) > 0)) - representatives[type] = uc; - } - - while (true) { - lineno++; - char *line = chomp(fgetline(fp)); - if (!line) - break; - - /* Skip blank lines and comments */ - if (!line[0] || line[0] == '#') { - sfree(line); - continue; - } - - /* Parse @Reorder lines, which tell us the expected output - * order for all following test cases (until superseded) */ - if (strstartswith(line, "@Reorder:")) { - char *p = line; - extract_word(&p); /* eat the "@Reorder:" header itself */ - order_len = 0; - while (1) { - const char *word = extract_word(&p); - if (!*word) - break; - sgrowarray(order, order_size, order_len); - order[order_len++] = strtoul(word, NULL, 0); - } - - sfree(line); - continue; - } - - /* Skip @Levels lines, which we don't (yet?) do anything with */ - if (strstartswith(line, "@Levels:")) { - sfree(line); - continue; - } - - /* Everything remaining should be an actual test */ - char *semicolon = strchr(line, ';'); - if (!semicolon) { - printf("%s:%u: bad test line': no bitmap\n", filename, lineno); - sfree(line); - continue; - } - *semicolon++ = '\0'; - unsigned bitmask = strtoul(semicolon, NULL, 0); - char *p = line; - bcs_len = 0; - bool test_ok = true; - while (1) { - const char *word = extract_word(&p); - if (!*word) - break; - unsigned type; - for (type = 0; type < N_BIDI_TYPES; type++) - if (!strcmp(word, typenames[type])) - break; - if (type == N_BIDI_TYPES) { - printf("%s:%u: bad test line: bad bidi type '%s'\n", - filename, lineno, word); - test_ok = false; - break; - } - sgrowarray(bcs, bcs_size, bcs_len); - bcs[bcs_len].wc = representatives[type]; - bcs[bcs_len].origwc = bcs[bcs_len].wc; - bcs[bcs_len].index = bcs_len; - bcs[bcs_len].nchars = 1; - bcs_len++; - } - - if (!test_ok) { - sfree(line); - continue; - } - - if (bitmask & 1) - run_test(filename, lineno, bcs, bcs_len, order, order_len, 0); - if (bitmask & 2) - run_test(filename, lineno, bcs, bcs_len, order, order_len, +1); - if (bitmask & 4) - run_test(filename, lineno, bcs, bcs_len, order, order_len, -1); - - sfree(line); - } - - sfree(bcs); - sfree(order); -} - -static void char_test(const char *filename, FILE *fp) -{ - unsigned lineno = 0; - size_t bcs_size = 0, bcs_len = 0; - bidi_char *bcs = NULL; - size_t order_size = 0, order_len = 0; - unsigned *order = NULL; - - while (true) { - lineno++; - char *line = chomp(fgetline(fp)); - if (!line) - break; - - /* Skip blank lines and comments */ - if (!line[0] || line[0] == '#') { - sfree(line); - continue; - } - - /* Break each test line up into its main fields */ - ptrlen input_pl, para_dir_pl, order_pl; - { - ptrlen pl = ptrlen_from_asciz(line); - input_pl = ptrlen_get_word(&pl, ";"); - para_dir_pl = ptrlen_get_word(&pl, ";"); - ptrlen_get_word(&pl, ";"); /* paragraph level, which we ignore */ - ptrlen_get_word(&pl, ";"); /* embedding levels, which we ignore */ - order_pl = ptrlen_get_word(&pl, ";"); - } - - int override; - { - char *para_dir_str = mkstr(para_dir_pl); - unsigned para_dir = strtoul(para_dir_str, NULL, 0); - sfree(para_dir_str); - - override = (para_dir == 0 ? +1 : para_dir == 1 ? -1 : 0); - } - - /* Break up the input into Unicode characters */ - bcs_len = 0; - { - ptrlen pl = input_pl; - while (pl.len) { - ptrlen chr = ptrlen_get_word(&pl, " "); - char *chrstr = mkstr(chr); - sgrowarray(bcs, bcs_size, bcs_len); - bcs[bcs_len].wc = strtoul(chrstr, NULL, 16); - bcs[bcs_len].origwc = bcs[bcs_len].wc; - bcs[bcs_len].index = bcs_len; - bcs[bcs_len].nchars = 1; - bcs_len++; - sfree(chrstr); - } - } - - /* Ditto the expected output order */ - order_len = 0; - { - ptrlen pl = order_pl; - while (pl.len) { - ptrlen chr = ptrlen_get_word(&pl, " "); - char *chrstr = mkstr(chr); - sgrowarray(order, order_size, order_len); - order[order_len++] = strtoul(chrstr, NULL, 0); - sfree(chrstr); - } - } - - run_test(filename, lineno, bcs, bcs_len, order, order_len, override); - sfree(line); - } - - sfree(bcs); - sfree(order); -} - -void out_of_memory(void) -{ - fprintf(stderr, "out of memory!\n"); - exit(2); -} - -static void usage(FILE *fp) -{ - fprintf(fp, "\ -usage: bidi_test ( ( --class | --char ) infile... )...\n\ -e.g.: bidi_test --class BidiTest.txt --char BidiCharacterTest.txt\n\ -also: --help display this text\n\ -"); -} - -int main(int argc, char **argv) -{ - void (*testfn)(const char *, FILE *) = NULL; - bool doing_opts = true; - const char *filename = NULL; - bool done_something = false; - - ctx = bidi_new_context(); - - while (--argc > 0) { - const char *arg = *++argv; - if (doing_opts && arg[0] == '-' && arg[1]) { - if (!strcmp(arg, "--")) { - doing_opts = false; - } else if (!strcmp(arg, "--class")) { - testfn = class_test; - } else if (!strcmp(arg, "--char")) { - testfn = char_test; - } else if (!strcmp(arg, "--help")) { - usage(stdout); - return 0; - } else { - fprintf(stderr, "unrecognised option '%s'\n", arg); - return 1; - } - } else { - const char *filename = arg; - - if (!testfn) { - fprintf(stderr, "no mode argument provided before filename " - "'%s'\n", filename); - return 1; - } - - if (!strcmp(filename, "-")) { - testfn("", stdin); - } else { - FILE *fp = fopen(filename, "r"); - if (!fp) { - fprintf(stderr, "unable to open '%s'\n", filename); - return 1; - } - testfn(filename, fp); - fclose(fp); - } - done_something = true; - } - } - - if (!done_something) { - usage(stderr); - return 1; - } - - if (!filename) - filename = "-"; - - printf("pass %d fail %d total %d\n", pass, fail, pass + fail); - - bidi_free_context(ctx); - return fail != 0; -} diff --git a/terminal/lineedit.c b/terminal/lineedit.c deleted file mode 100644 index eaaa3a186..000000000 --- a/terminal/lineedit.c +++ /dev/null @@ -1,520 +0,0 @@ -/* - * Implementation of local line editing. Used during username and - * password input at login time, and also by ldisc during the main - * session (if the session's virtual terminal is in that mode). - * - * Because we're tied into a real GUI terminal (and not a completely - * standalone line-discipline module that deals purely with byte - * streams), we can support a slightly richer input interface than - * plain bytes. - * - * In particular, the 'dedicated' flag sent along with every byte is - * used to distinguish control codes input via Ctrl+letter from the - * same code input by a dedicated key like Return or Backspace. This - * allows us to interpret the Ctrl+letter key combination as inputting - * a literal control character to go into the line buffer, and the - * dedicated-key version as performing an editing function. - */ - -#include "putty.h" -#include "terminal.h" - -typedef struct BufChar BufChar; - -struct TermLineEditor { - Terminal *term; - BufChar *head, *tail; - unsigned flags; - bool quote_next_char; - TermLineEditorCallbackReceiver *receiver; -}; - -struct BufChar { - BufChar *prev, *next; - - /* The bytes of the character, to be sent on the wire */ - char wire[6]; - uint8_t nwire; - - /* Whether this character is considered complete */ - bool complete; - - /* Width of the character when it was displayed, in terminal cells */ - uint8_t width; - - /* Whether this character counts as whitespace, for ^W purposes */ - bool space; -}; - -TermLineEditor *lineedit_new(Terminal *term, unsigned flags, - TermLineEditorCallbackReceiver *receiver) -{ - TermLineEditor *le = snew(TermLineEditor); - le->term = term; - le->head = le->tail = NULL; - le->flags = flags; - le->quote_next_char = false; - le->receiver = receiver; - return le; -} - -static void bufchar_free(BufChar *bc) -{ - smemclr(bc, sizeof(*bc)); - sfree(bc); -} - -static void lineedit_free_buffer(TermLineEditor *le) -{ - while (le->head) { - BufChar *bc = le->head; - le->head = bc->next; - bufchar_free(bc); - } - le->tail = NULL; -} - -void lineedit_free(TermLineEditor *le) -{ - lineedit_free_buffer(le); - sfree(le); -} - -void lineedit_modify_flags(TermLineEditor *le, unsigned clr, unsigned flip) -{ - le->flags &= ~clr; - le->flags ^= flip; -} - -static void lineedit_term_write(TermLineEditor *le, ptrlen data) -{ - le->receiver->vt->to_terminal(le->receiver, data); -} - -static void lineedit_term_newline(TermLineEditor *le) -{ - lineedit_term_write(le, PTRLEN_LITERAL("\x0D\x0A")); -} - -static inline void lineedit_send_data(TermLineEditor *le, ptrlen data) -{ - le->receiver->vt->to_backend(le->receiver, data); -} - -static inline void lineedit_special(TermLineEditor *le, - SessionSpecialCode code, int arg) -{ - le->receiver->vt->special(le->receiver, code, arg); -} - -static inline void lineedit_send_newline(TermLineEditor *le) -{ - le->receiver->vt->newline(le->receiver); -} - -static void lineedit_delete_char(TermLineEditor *le) -{ - if (le->tail) { - BufChar *bc = le->tail; - le->tail = bc->prev; - if (!le->tail) - le->head = NULL; - else - le->tail->next = NULL; - - for (unsigned i = 0; i < bc->width; i++) - lineedit_term_write(le, PTRLEN_LITERAL("\x08 \x08")); - - bufchar_free(bc); - } -} - -static void lineedit_delete_word(TermLineEditor *le) -{ - /* - * Deleting a word stops at the _start_ of a word, i.e. at any - * boundary with a space on the left and a non-space on the right. - */ - if (!le->tail) - return; - - while (true) { - bool deleted_char_is_space = le->tail->space; - lineedit_delete_char(le); - if (!le->tail) - break; /* we've cleared the whole line */ - if (le->tail->space && !deleted_char_is_space) - break; /* we've just reached a word boundary */ - } -} - -static void lineedit_delete_line(TermLineEditor *le) -{ - while (le->tail) - lineedit_delete_char(le); - lineedit_special(le, SS_EL, 0); -} - -void lineedit_send_line(TermLineEditor *le) -{ - for (BufChar *bc = le->head; bc; bc = bc->next) - lineedit_send_data(le, make_ptrlen(bc->wire, bc->nwire)); - lineedit_free_buffer(le); - le->quote_next_char = false; -} - -static void lineedit_complete_line(TermLineEditor *le) -{ - lineedit_term_newline(le); - lineedit_send_line(le); - lineedit_send_newline(le); -} - -/* - * Send data to the terminal to display a BufChar. As a side effect, - * update bc->width to indicate how many character cells we think were - * taken up by what we just wrote. No other change to bc is made. - */ -static void lineedit_display_bufchar(TermLineEditor *le, BufChar *bc, - unsigned chr) -{ - char buf[6]; - buffer_sink bs[1]; - buffer_sink_init(bs, buf, lenof(buf)); - - /* Handle single-byte character set translation. */ - if (!in_utf(le->term) && DIRECT_CHAR(chr)) { - /* - * If we're not in UTF-8, i.e. we're in a single-byte - * character set, then first we must feed the input byte - * through term_translate, which will tell us whether it's a - * control character or not. (That varies with the charset: - * e.g. ISO 8859-1 and Win1252 disagree on a lot of - * 0x80-0x9F). - * - * In principle, we could pass NULL as our term_utf8_decode - * pointer, on the grounds that since the terminal isn't in - * UTF-8 mode term_translate shouldn't access it. But that - * seems needlessly reckless; we'll make up an empty one. - */ - term_utf8_decode dummy_utf8 = { .state = 0, .chr = 0, .size = 0 }; - chr = term_translate( - le->term, &dummy_utf8, (unsigned char)chr); - - /* - * After that, chr will be either a control-character value - * (00-1F, 7F, 80-9F), or a byte value ORed with one of the - * CSET_FOO character set indicators. The latter indicates - * that it's a printing character in this charset, in which - * case it takes up one character cell. - */ - if (chr & CSET_MASK) { - put_byte(bs, chr); - bc->width = 1; - goto got_char; - } - } - - /* - * If we got here without taking the 'goto' above, then we're now - * holding an actual Unicode character. - */ - assert(!IS_SURROGATE(chr)); /* and it should be an _actual_ one */ - - /* - * Deal with symbolic representations of control characters. - */ - - if (chr < 0x20 || chr == 0x7F) { - /* - * Represent C0 controls as '^C' or similar, and 7F as ^?. - */ - put_byte(bs, '^'); - put_byte(bs, chr ^ 0x40); - bc->width = 2; - goto got_char; - } - - if (chr >= 0x80 && chr < 0xA0) { - /* - * Represent C1 controls as <9B> or similar. - */ - put_fmt(bs, "<%02X>", chr); - bc->width = 4; - goto got_char; - } - - /* - * And if we get _here_, we're holding a printing (or at least not - * _control_, even if zero-width) Unicode character, which _must_ - * mean that the terminal is currently in UTF-8 mode (since if it - * were not then printing characters would have gone through the - * term_translate case above). So we can just write the UTF-8 for - * the character - but we must also pay attention to its width in - * character cells, which might be 0, 1 or 2. - */ - assert(in_utf(le->term)); - put_utf8_char(bs, chr); - bc->width = term_char_width(le->term, chr); - - got_char: - lineedit_term_write(le, make_ptrlen(buf, bs->out - buf)); -} - -/* Called when we've just added a byte to a UTF-8 character and want - * to see if it's complete */ -static void lineedit_check_utf8_complete(TermLineEditor *le, BufChar *bc) -{ - BinarySource src[1]; - BinarySource_BARE_INIT(src, bc->wire, bc->nwire); - DecodeUTF8Failure err; - unsigned chr = decode_utf8(src, &err); - if (err == DUTF8_E_OUT_OF_DATA) - return; /* not complete yet */ - - /* Any other error code is regarded as complete, and we just - * display the character as the U+FFFD that decode_utf8 will have - * returned anyway */ - bc->complete = true; - bc->space = (chr == ' '); - lineedit_display_bufchar(le, bc, chr); -} - -static void lineedit_input_printing_char(TermLineEditor *le, char ch); - -static void lineedit_redraw_line(TermLineEditor *le) -{ - /* FIXME: I'm not 100% sure this is the behaviour I really want in - * this situation, but it's at least very simple to implement */ - BufChar *prevhead = le->head; - le->head = le->tail = NULL; - while (prevhead) { - BufChar *bc = prevhead; - prevhead = prevhead->next; - - for (unsigned i = 0; i < bc->nwire; i++) - lineedit_input_printing_char(le, bc->wire[i]); - bufchar_free(bc); - } -} - -#define CTRL(c) ((char) (0x40 ^ (unsigned char)c)) - -void lineedit_input(TermLineEditor *le, char ch, bool dedicated) -{ - if (le->quote_next_char) { - /* - * If the previous keypress was ^V, 'quoting' the next - * character to be treated literally, then skip all the - * editing-control processing, and clear that flag. - */ - le->quote_next_char = false; - } else { - /* - * Input events that are only valid with the 'dedicated' flag. - * These are limited to the control codes that _have_ - * dedicated keys. - * - * Any case we actually handle here ends with a 'return' - * statement, so that if we fall out of the end of this switch - * at all, it's because the byte hasn't been handled here and - * will fall into the next switch dealing with ordinary input. - */ - if (dedicated) { - switch (ch) { - /* - * The Backspace key. - * - * Since our terminal can be configured to send either - * ^H or 7F (aka ^?) via the backspace key, we accept - * both. - * - * (We could query the Terminal's configuration here - * and accept only the one of those codes that the - * terminal is currently set to. But it's pointless, - * because whichever one the terminal isn't set to, - * the front end won't be sending it with - * dedicated=true anyway.) - */ - case CTRL('H'): - case 0x7F: - lineedit_delete_char(le); - return; - - /* - * The Return key. - */ - case CTRL('M'): - lineedit_complete_line(le); - return; - } - } - - /* - * Editing and special functions in response to ordinary keys - * or Ctrl+key combinations. Many editing functions have to be - * supported in this mode, like ^W and ^U, because there are - * no dedicated keys that generate the same control codes - * anyway. - * - * Again, we return if the key was handled. The final - * processing of ordinary data to go into the input buffer - * happens if we break from this switch. - */ - switch (ch) { - case CTRL('W'): - lineedit_delete_word(le); - return; - - case CTRL('U'): - lineedit_delete_line(le); - return; - - case CTRL('['): - if (!(le->flags & LE_ESC_ERASES)) - break; /* treat as normal input */ - lineedit_delete_line(le); - return; - - case CTRL('R'): - lineedit_term_write(le, PTRLEN_LITERAL("^R")); - lineedit_term_newline(le); - lineedit_redraw_line(le); - return; - - case CTRL('V'): - le->quote_next_char = true; - return; - - case CTRL('C'): - lineedit_delete_line(le); - if (!(le->flags & LE_INTERRUPT)) - break; /* treat as normal input */ - lineedit_special(le, SS_IP, 0); - return; - - case CTRL('Z'): - lineedit_delete_line(le); - if (!(le->flags & LE_SUSPEND)) - break; /* treat as normal input */ - lineedit_special(le, SS_SUSP, 0); - return; - - case CTRL('\\'): - lineedit_delete_line(le); - if (!(le->flags & LE_ABORT)) - break; /* treat as normal input */ - lineedit_special(le, SS_ABORT, 0); - return; - - case CTRL('D'): - if (le->flags & LE_EOF_ALWAYS) { - /* Our client wants to treat ^D / EOF as a special - * character in their own way. Just send an EOF - * special. */ - lineedit_special(le, SS_EOF, 0); - return; - } - - /* - * Otherwise, ^D has the same behaviour as in Unix tty - * line editing: if the edit buffer is non-empty then it's - * sent immediately without a newline, and if it is empty - * then an EOF is sent. - */ - if (le->head) { - lineedit_send_line(le); - return; - } - - lineedit_special(le, SS_EOF, 0); - return; - - case CTRL('J'): - if (le->flags & LE_CRLF_NEWLINE) { - /* - * If the previous character in the buffer is a - * literal Ctrl-M, and now the user sends Ctrl-J, then - * we amalgamate both into a newline event. - */ - if (le->tail && le->tail->nwire == 1 && - le->tail->wire[0] == CTRL('M')) { - lineedit_delete_char(le); /* erase ^J from buffer */ - lineedit_complete_line(le); - return; - } - } - } - } - - /* - * If we get to here, we've exhausted the options for treating our - * character as an editing or special function of any kind. Treat - * it as a printing character, or part of one. - */ - lineedit_input_printing_char(le, ch); -} - -static void lineedit_input_printing_char(TermLineEditor *le, char ch) -{ - /* - * Append ch to the line buffer, either as a new BufChar or by - * adding it to a previous one containing an as yet incomplete - * UTF-8 encoding. - */ - if (le->tail && !le->tail->complete) { - BufChar *bc = le->tail; - - /* - * If we're in UTF-8 mode, and ch is a UTF-8 continuation - * byte, then we can append it to bc, which we've just checked - * is missing at least one of those. - */ - if (in_utf(le->term) && (unsigned char)ch - 0x80U < 0x40) { - assert(bc->nwire < lenof(bc->wire)); - bc->wire[bc->nwire++] = ch; - lineedit_check_utf8_complete(le, bc); - return; - } - - /* - * Otherwise, the previous incomplete character can't be - * extended. Mark it as complete, and if possible, display it - * as a replacement character indicating that something weird - * happened. - */ - bc->complete = true; - if (in_utf(le->term)) - lineedit_display_bufchar(le, bc, 0xFFFD); - - /* - * But we still haven't processed the byte we're holding. Fall - * through to the next step, where we make a fresh BufChar for - * it. - */ - } - - /* - * Make a fresh BufChar. - */ - BufChar *bc = snew(BufChar); - bc->prev = le->tail; - le->tail = bc; - if (bc->prev) - bc->prev->next = bc; - else - le->head = bc; - bc->next = NULL; - bc->complete = false; - bc->space = false; - - bc->nwire = 1; - bc->wire[0] = ch; - if (in_utf(le->term)) { - lineedit_check_utf8_complete(le, bc); - } else { - bc->complete = true; /* always, in a single-byte charset */ - bc->space = (bc->wire[0] == ' '); - lineedit_display_bufchar(le, bc, CSET_ASCII | (unsigned char)ch); - } -} diff --git a/terminal/terminal.c b/terminal/terminal.c deleted file mode 100644 index 13aaeabb2..000000000 --- a/terminal/terminal.c +++ /dev/null @@ -1,8054 +0,0 @@ -/* - * Terminal emulator. - */ - -#include -#include -#include -#include -#include - -#include -#include -#include "putty.h" -#include "terminal.h" - -#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 ) - -static const char *const EMPTY_WINDOW_TITLE = ""; - -static const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; - -#define sel_nl_sz (sizeof(sel_nl)/sizeof(wchar_t)) -static const wchar_t sel_nl[] = SEL_NL; - -/* forward declaration */ -static void term_userpass_state_free(struct term_userpass_state *s); - -/* - * 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); -static void check_line_size(Terminal *, termline *); -static void do_paint(Terminal *); -static void erase_lots(Terminal *, bool, bool, bool); -static int find_last_nonempty_line(Terminal *, tree234 *); -static void swap_screen(Terminal *, int, bool, bool); -static void update_sbar(Terminal *); -static void deselect(Terminal *); -static void term_print_finish(Terminal *); -static void scroll(Terminal *, int, int, int, bool); -static void parse_optionalrgb(optionalrgb *out, unsigned *values); -static void term_added_data(Terminal *term, bool); -static void term_update_raw_mouse_mode(Terminal *term); -static void term_out_cb(void *); - -static termline *newtermline(Terminal *term, int cols, bool 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->trusted = false; - line->temporary = false; - line->cc_free = 0; - - return line; -} - -static void freetermline(termline *line) -{ - if (line) { - sfree(line->chars); - sfree(line); - } -} - -void term_release_line(termline *line) -{ - if (line->temporary) - freetermline(line); -} - -const int colour_indices_conf_to_oscp[CONF_NCOLOURS] = { - #define COLOUR_ENTRY(id,name) OSCP_COLOUR_##id, - CONF_COLOUR_LIST(COLOUR_ENTRY) - #undef COLOUR_ENTRY -}; - -const int colour_indices_conf_to_osc4[CONF_NCOLOURS] = { - #define COLOUR_ENTRY(id,name) OSC4_COLOUR_##id, - CONF_COLOUR_LIST(COLOUR_ENTRY) - #undef COLOUR_ENTRY -}; - -const int colour_indices_oscp_to_osc4[OSCP_NCOLOURS] = { - #define COLOUR_ENTRY(id) OSC4_COLOUR_##id, - OSCP_COLOUR_LIST(COLOUR_ENTRY) - #undef COLOUR_ENTRY -}; - -#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 - -static void clear_cc(termline *line, int col); - -/* - * 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); - - /* - * Don't add combining characters at all to U+FFFD REPLACEMENT - * CHARACTER. (Partly it's a slightly incoherent idea in the first - * place; mostly, U+FFFD is what we generate if a cell already has - * too many ccs, in which case we want it to be a fixed point when - * further ccs are added.) - */ - if (line->chars[col].chr == 0xFFFD) - return; - - /* - * Walk the cc list of the cell in question to find its current - * end point. - */ - size_t ncc = 0; - int origcol = col; - while (line->chars[col].cc_next) { - col += line->chars[col].cc_next; - if (++ncc >= CC_LIMIT) { - /* - * There are already too many combining characters in this - * character cell. Change strategy: throw out the entire - * chain and replace the main character with U+FFFD. - * - * (Rationale: extrapolating from UTR #36 section 3.6.2 - * suggests the principle that it's better to substitute - * U+FFFD than to _ignore_ input completely. Also, if the - * user copies and pastes an overcombined character cell, - * this way it will clearly indicate that we haven't - * reproduced the writer's original intentions, instead of - * looking as if it was the _writer's_ fault that the 33rd - * cc is missing.) - * - * Per the code above, this will also prevent any further - * ccs from being added to this cell. - */ - clear_cc(line, origcol); - line->chars[origcol].chr = 0xFFFD; - return; - } - } - - /* - * Extend the cols array if the free list is empty. - */ - if (!line->cc_free) { - int n = line->size; - - size_t tmpsize = line->size; - sgrowarray(line->chars, tmpsize, tmpsize); - assert(tmpsize <= INT_MAX); - line->size = tmpsize; - - 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++; - } - } - - /* - * `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 bool termchars_equal_override(termchar *a, termchar *b, - unsigned long bchr, unsigned long battr) -{ - /* FULL-TERMCHAR */ - if (!truecolour_equal(a->truecolour, b->truecolour)) - return false; - 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 bool 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 -} - -#ifndef NO_SCROLLBACK_COMPRESSION - -/* - * 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.) - */ -static void makerle(strbuf *b, termline *ldata, - void (*makeliteral)(strbuf *b, termchar *c, - unsigned long *state)) -{ - int hdrpos, hdrsize, n, prevlen, prevpos, thislen, thispos; - bool prev2; - termchar *c = ldata->chars; - unsigned long state = 0, oldstate; - - n = ldata->cols; - - hdrpos = b->len; - hdrsize = 0; - put_byte(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->u + prevpos, b->u + 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; - strbuf_shrink_to(b, prevpos+prevlen); - } else { - memmove(b->u + prevpos+1, b->u + prevpos, prevlen); - runpos = prevpos; - strbuf_shrink_to(b, prevpos+prevlen+1); - /* - * Terminate the previous run of ordinary - * literals. - */ - assert(hdrsize >= 1 && hdrsize <= 128); - b->u[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; - bool match = tmplen == thislen && - !memcmp(b->u + runpos+1, b->u + tmppos, tmplen); - strbuf_shrink_to(b, tmppos); - if (!match) { - state = oldstate; - break; /* run over */ - } - n--, c++, runlen++; - } - - assert(runlen >= 2 && runlen <= 129); - b->u[runpos] = runlen + 0x80 - 2; - - hdrpos = b->len; - hdrsize = 0; - put_byte(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->u[hdrpos] = hdrsize - 1; - hdrpos = b->len; - hdrsize = 0; - put_byte(b, 0); - prevlen = prevpos = 0; - prev2 = false; - } - } - - /* - * Clean up. - */ - if (hdrsize > 0) { - assert(hdrsize <= 128); - b->u[hdrpos] = hdrsize - 1; - } else { - strbuf_shrink_to(b, hdrpos); - } -} -static void makeliteral_chr(strbuf *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) { - put_byte(b, (unsigned char)(c->chr & 0x7F)); - } else if (c->chr < 0x4000) { - put_byte(b, (unsigned char)(((c->chr >> 8) & 0x3F) | 0x80)); - put_byte(b, (unsigned char)(c->chr & 0xFF)); - } else if (c->chr < 0x200000) { - put_byte(b, (unsigned char)(((c->chr >> 16) & 0x1F) | 0xC0)); - put_uint16(b, c->chr & 0xFFFF); - } else if (c->chr < 0x10000000) { - put_byte(b, (unsigned char)(((c->chr >> 24) & 0x0F) | 0xE0)); - put_byte(b, (unsigned char)((c->chr >> 16) & 0xFF)); - put_uint16(b, c->chr & 0xFFFF); - } else { - put_byte(b, 0xF0); - put_uint32(b, c->chr); - } - *state = c->chr & ~0xFF; -} -static void makeliteral_attr(strbuf *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) { - put_byte(b, (unsigned char)((attr >> 8) & 0xFF)); - put_byte(b, (unsigned char)(attr & 0xFF)); - } else { - put_byte(b, (unsigned char)(((attr >> 24) & 0x7F) | 0x80)); - put_byte(b, (unsigned char)((attr >> 16) & 0xFF)); - put_byte(b, (unsigned char)((attr >> 8) & 0xFF)); - put_byte(b, (unsigned char)(attr & 0xFF)); - } -} -static void makeliteral_truecolour(strbuf *b, termchar *c, unsigned long *state) -{ - /* - * Put the used parts of the colour info into the buffer. - */ - put_byte(b, ((c->truecolour.fg.enabled ? 1 : 0) | - (c->truecolour.bg.enabled ? 2 : 0))); - if (c->truecolour.fg.enabled) { - put_byte(b, c->truecolour.fg.r); - put_byte(b, c->truecolour.fg.g); - put_byte(b, c->truecolour.fg.b); - } - if (c->truecolour.bg.enabled) { - put_byte(b, c->truecolour.bg.r); - put_byte(b, c->truecolour.bg.g); - put_byte(b, c->truecolour.bg.b); - } -} -static void makeliteral_cc(strbuf *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); -} - -typedef struct compressed_scrollback_line { - size_t len; - /* compressed data follows after this */ -} compressed_scrollback_line; - -static termline *decompressline_no_free(compressed_scrollback_line *line); - -static compressed_scrollback_line *compressline_no_free(termline *ldata) -{ - strbuf *b = strbuf_new(); - - /* Leave space for the header structure */ - strbuf_append(b, sizeof(compressed_scrollback_line)); - - /* - * 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) { - put_byte(b, (unsigned char)((n & 0x7F) | 0x80)); - n >>= 7; - } - put_byte(b, (unsigned char)(n)); - } - - /* - * Next store the lattrs; same principle. We add one extra bit to - * this to indicate the trust state of the line. - */ - { - int n = ldata->lattr | (ldata->trusted ? 0x10000 : 0); - while (n >= 128) { - put_byte(b, (unsigned char)((n & 0x7F) | 0x80)); - n >>= 7; - } - put_byte(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_truecolour); - makerle(b, ldata, makeliteral_cc); - - size_t linelen = b->len - sizeof(compressed_scrollback_line); - compressed_scrollback_line *line = - (compressed_scrollback_line *)strbuf_to_str(b); - line->len = linelen; - - /* - * 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 - { - 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_no_free(line); - 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 - - freetermline(dcl); - } -#endif -#endif /* TERM_CC_DIAGS */ - - return line; -} - -static compressed_scrollback_line *compressline_and_free(termline *ldata) -{ - compressed_scrollback_line *cline = compressline_no_free(ldata); - freetermline(ldata); - return cline; -} - -static void readrle(BinarySource *bs, termline *ldata, - void (*readliteral)(BinarySource *bs, termchar *c, - termline *ldata, unsigned long *state)) -{ - int n = 0; - unsigned long state = 0; - - while (n < ldata->cols) { - int hdr = get_byte(bs); - - if (hdr >= 0x80) { - /* A run. */ - - size_t pos = bs->pos, count = hdr + 2 - 0x80; - while (count--) { - assert(n < ldata->cols); - bs->pos = pos; - readliteral(bs, ldata->chars + n, ldata, &state); - n++; - } - } else { - /* Just a sequence of consecutive literals. */ - - int count = hdr + 1; - while (count--) { - assert(n < ldata->cols); - readliteral(bs, ldata->chars + n, ldata, &state); - n++; - } - } - } - - assert(n == ldata->cols); -} -static void readliteral_chr(BinarySource *bs, 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_byte(bs); - if (byte < 0x80) { - c->chr = byte | *state; - } else if (byte < 0xC0) { - c->chr = (byte &~ 0xC0) << 8; - c->chr |= get_byte(bs); - } else if (byte < 0xE0) { - c->chr = (byte &~ 0xE0) << 16; - c->chr |= get_uint16(bs); - } else if (byte < 0xF0) { - c->chr = (byte &~ 0xF0) << 24; - c->chr |= get_byte(bs) << 16; - c->chr |= get_uint16(bs); - } else { - assert(byte == 0xF0); - c->chr = get_uint32(bs); - } - *state = c->chr & ~0xFF; -} -static void readliteral_attr(BinarySource *bs, termchar *c, termline *ldata, - unsigned long *state) -{ - unsigned val, attr, colourbits; - - val = get_uint16(bs); - - if (val >= 0x8000) { - val &= ~0x8000; - val <<= 16; - val |= get_uint16(bs); - } - - 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_truecolour( - BinarySource *bs, termchar *c, termline *ldata, unsigned long *state) -{ - int flags = get_byte(bs); - - if (flags & 1) { - c->truecolour.fg.enabled = true; - c->truecolour.fg.r = get_byte(bs); - c->truecolour.fg.g = get_byte(bs); - c->truecolour.fg.b = get_byte(bs); - } else { - c->truecolour.fg = optionalrgb_none; - } - - if (flags & 2) { - c->truecolour.bg.enabled = true; - c->truecolour.bg.r = get_byte(bs); - c->truecolour.bg.g = get_byte(bs); - c->truecolour.bg.b = get_byte(bs); - } else { - c->truecolour.bg = optionalrgb_none; - } -} -static void readliteral_cc(BinarySource *bs, 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(bs, &n, ldata, &zstate); - if (!n.chr) - break; - add_cc(ldata, x, n.chr); - } -} - -static termline *decompressline_no_free(compressed_scrollback_line *line) -{ - int ncols, byte, shift; - BinarySource bs[1]; - termline *ldata; - - BinarySource_BARE_INIT(bs, line+1, line->len); - - /* - * First read in the column count. - */ - ncols = shift = 0; - do { - byte = get_byte(bs); - 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. - */ - int lattr = shift = 0; - do { - byte = get_byte(bs); - lattr |= (byte & 0x7F) << shift; - shift += 7; - } while (byte & 0x80); - ldata->lattr = lattr & 0xFFFF; - ldata->trusted = (lattr & 0x10000) != 0; - - /* - * Now we read in each of the RLE streams in turn. - */ - readrle(bs, ldata, readliteral_chr); - readrle(bs, ldata, readliteral_attr); - readrle(bs, ldata, readliteral_truecolour); - readrle(bs, ldata, readliteral_cc); - - /* And we always expect that we ended up exactly at the end of the - * compressed data. */ - assert(!get_err(bs)); - assert(get_avail(bs) == 0); - - return ldata; -} - -static inline void free_compressed_line(compressed_scrollback_line *cline) -{ - sfree(cline); -} - -static termline *decompressline_and_free(compressed_scrollback_line *cline) -{ - termline *ldata = decompressline_no_free(cline); - free_compressed_line(cline); - return ldata; -} - -#else /* NO_SCROLLBACK_COMPRESSION */ - -static termline *duptermline(termline *oldline) -{ - termline *newline = snew(termline); - *newline = *oldline; /* copy the POD structure fields */ - newline->chars = snewn(newline->size, termchar); - for (int j = 0; j < newline->size; j++) - newline->chars[j] = oldline->chars[j]; - return newline; -} - -typedef termline compressed_scrollback_line; - -static inline compressed_scrollback_line *compressline_and_free( - termline *ldata) -{ - return ldata; -} - -static inline compressed_scrollback_line *compressline_no_free(termline *ldata) -{ - return duptermline(ldata); -} - -static inline termline *decompressline_no_free( - compressed_scrollback_line *line) -{ - /* This will return a line without the 'temporary' flag, which - * means that unlineptr() is already set up to avoid freeing it */ - return line; -} - -static inline termline *decompressline_and_free( - compressed_scrollback_line *line) -{ - /* Same as decompressline_no_free, because the caller will free - * our returned termline, and that does all the freeing necessary */ - return line; -} - -static inline void free_compressed_line(compressed_scrollback_line *line) -{ - freetermline(line); -} - -#endif /* NO_SCROLLBACK_COMPRESSION */ - -/* - * 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; -} - -static void null_line_error(Terminal *term, int y, int lineno, - tree234 *whichtree, int treeindex, - const char *varname) -{ - modalfatalbox("%s==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" - "commitid=%s\n\n" - "Please contact " - "and pass on the above information.", - varname, 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, commitid); -} - -static inline int checkscr(int y, int lineno) -{ - if (y < 0) - modalfatalbox("screen line %d < 0 in terminal.c:%d", y, lineno); - return y; -} - -/* - * 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) -{ - termline *line; - tree234 *whichtree; - int treeindex; - - if (y >= 0) { - whichtree = term->screen; - treeindex = y; - } else { - int altlines = 0; - - 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) { - compressed_scrollback_line *cline = index234(whichtree, treeindex); - if (!cline) - null_line_error(term, y, lineno, whichtree, treeindex, "cline"); - line = decompressline_no_free(cline); - } else { - line = index234(whichtree, treeindex); - } - - /* We assume that we don't screw up and retrieve something out of range. */ - if (line == NULL) - null_line_error(term, y, lineno, whichtree, treeindex, "line"); - 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; -} - -/* - * Macro wrappers for lineptr. The distinction between lineptr and - * scrlineptr is that lineptr can retrieve any line, from the screen - * _or_ from scrollback, and in return, you have to call unlineptr - * when you're done with it, in case it was a dynamically allocated - * line decompressed from scrollback that needs freeing. But - * scrlineptr will only retrieve lines from the active screen (and - * enforces this by an assertion), which means it's always just - * returning a pointer to an existing unpacked termline, and you don't - * have to call unlineptr afterwards. So drawing code (which might - * need the scrollback) will have to call lineptr/unlineptr, but - * update code during term_out can call scrlineptr. - * - * The 'assertion' in scrlineptr is done using a helper function that - * returns the input column number, which allows this macro to avoid - * double-evaluating its argument. - */ -#define lineptr(x) (lineptr)(term,x,__LINE__) -#define scrlineptr(x) (lineptr)(term,checkscr(x,__LINE__),__LINE__) -#define unlineptr(line) term_release_line(line) - -/* Wrapper for external use (e.g. tests), without the __LINE__ parameter */ -termline *term_get_line(Terminal *term, int y) { return lineptr(y); } - -/* - * 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_update_callback(void *ctx); - -static void term_timer(void *ctx, unsigned long now) -{ - Terminal *term = (Terminal *)ctx; - - if (term->tblink_pending && now == term->next_tblink) { - term->tblinker = !term->tblinker; - term->tblink_pending = false; - term_schedule_tblink(term); - term->window_update_pending = true; - } - - if (term->cblink_pending && now == term->next_cblink) { - term->cblinker = !term->cblinker; - term->cblink_pending = false; - term_schedule_cblink(term); - term->window_update_pending = true; - } - - if (term->in_vbell && now == term->vbell_end) { - term->in_vbell = false; - term->window_update_pending = true; - } - - if (term->window_update_cooldown && - now == term->window_update_cooldown_end) { - term->window_update_cooldown = false; - } - - if (term->window_update_pending) - term_update_callback(term); -} - -static void term_update_callback(void *ctx) -{ - Terminal *term = (Terminal *)ctx; - if (!term->window_update_pending) - return; - if (!term->window_update_cooldown) { - term_update(term); - term->window_update_cooldown = true; - term->window_update_cooldown_end = schedule_timer( - UPDATE_DELAY, term_timer, term); - } -} - -static void term_schedule_update(Terminal *term) -{ - if (!term->window_update_pending) { - term->window_update_pending = true; - queue_toplevel_callback(term_update_callback, term); - } -} - -/* - * Call this whenever the terminal window state changes, to queue an - * update. This also resets the phase of cursor blinking, so that the - * cursor remains visible as it moves with the output, and sets a flag - * to indicate that if we have the 'reset scrollback on display - * activity' setting enabled, then we should activate it. - */ -static void seen_disp_event(Terminal *term) -{ - if (term->scroll_on_disp) { - term->disptop = 0; - term->win_scrollbar_update_pending = true; - } - term->cblinker = true; - term->cblink_pending = false; - term_schedule_cblink(term); - 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 = true; /* 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 = true; /* reset when not in use */ - term->cblink_pending = false; - } -} - -/* - * Call to begin a visual bell. - */ -static void term_schedule_vbell(Terminal *term, bool 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, bool 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_bool(term->conf, CONF_dec_om); - term->alt_ins = false; - term->insert = false; - term->alt_wnext = false; - term->wrapnext = false; - term->save_wnext = false; - term->alt_save_wnext = false; - term->alt_wrap = term->wrap = conf_get_bool(term->conf, CONF_wrap_mode); - term->alt_cset = term->cset = term->save_cset = term->alt_save_cset = 0; - term->alt_utf = false; - term->utf = false; - term->save_utf = false; - term->alt_save_utf = false; - term->utf8.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 = false; - term->in_vbell = false; - term->cursor_on = true; - term->big_cursor = false; - term->default_attr = term->save_attr = - term->alt_save_attr = term->curr_attr = ATTR_DEFAULT; - term->curr_truecolour.fg = term->curr_truecolour.bg = optionalrgb_none; - term->save_truecolour = term->alt_save_truecolour = term->curr_truecolour; - term->app_cursor_keys = conf_get_bool(term->conf, CONF_app_cursor); - term->app_keypad_keys = conf_get_bool(term->conf, CONF_app_keypad); - term->use_bce = conf_get_bool(term->conf, CONF_bce); - term->blink_is_real = conf_get_bool(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 = false; - term->urxvt_extended_mouse = false; - term->raw_mouse_reported_x = 0; - term->raw_mouse_reported_y = 0; - win_set_raw_mouse_mode(term->win, false); - term->win_pointer_shape_pending = true; - term->win_pointer_shape_raw = false; - term->bracketed_paste = false; - term->srm_echo = 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); - term_schedule_update(term); -} - -/* - * Force a screen update. - */ -void term_update(Terminal *term) -{ - term->window_update_pending = false; - - if (term->win_move_pending) { - win_move(term->win, term->win_move_pending_x, - term->win_move_pending_y); - term->win_move_pending = false; - } - if (term->win_resize_pending == WIN_RESIZE_NEED_SEND) { - term->win_resize_pending = WIN_RESIZE_AWAIT_REPLY; - win_request_resize(term->win, term->win_resize_pending_w, - term->win_resize_pending_h); - } - if (term->win_zorder_pending) { - win_set_zorder(term->win, term->win_zorder_top); - term->win_zorder_pending = false; - } - if (term->win_minimise_pending) { - win_set_minimised(term->win, term->win_minimise_enable); - term->win_minimise_pending = false; - } - if (term->win_maximise_pending) { - win_set_maximised(term->win, term->win_maximise_enable); - term->win_maximise_pending = false; - } - if (term->win_title_pending) { - win_set_title(term->win, term->window_title, - term->wintitle_codepage); - term->win_title_pending = false; - } - if (term->win_icon_title_pending) { - win_set_icon_title(term->win, term->icon_title, - term->icontitle_codepage); - term->win_icon_title_pending = false; - } - if (term->win_pointer_shape_pending) { - win_set_raw_mouse_mode_pointer(term->win, term->win_pointer_shape_raw); - term->win_pointer_shape_pending = false; - } - if (term->win_refresh_pending) { - win_refresh(term->win); - term->win_refresh_pending = false; - } - if (term->win_palette_pending) { - unsigned start = term->win_palette_pending_min; - unsigned ncolours = term->win_palette_pending_limit - start; - win_palette_set(term->win, start, ncolours, term->palette + start); - term->win_palette_pending = false; - } - - if (win_setup_draw_ctx(term->win)) { - if (term->win_scrollbar_update_pending) { - term->win_scrollbar_update_pending = false; - update_sbar(term); - } - do_paint(term); - win_set_cursor_pos( - term->win, term->curs.x, term->curs.y - term->disptop); - win_free_draw_ctx(term->win); - } -} - -/* - * 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) { - term->disptop = 0; - term->win_scrollbar_update_pending = true; - term_schedule_update(term); - } -} - -/* - * Same as power_on(), but an external function. - */ -void term_pwron(Terminal *term, bool clear) -{ - power_on(term, clear); - if (term->ldisc) /* cause ldisc to notice changes */ - ldisc_echoedit_update(term->ldisc); - 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)); - term->erase_char.truecolour.bg = term->curr_truecolour.bg; - } -} - -/* - * 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. - */ -static void term_copy_stuff_from_conf(Terminal *term) -{ - term->ansi_colour = conf_get_bool(term->conf, CONF_ansi_colour); - term->no_arabicshaping = conf_get_bool(term->conf, CONF_no_arabicshaping); - term->beep = conf_get_int(term->conf, CONF_beep); - term->bellovl = conf_get_bool(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->no_bidi = conf_get_bool(term->conf, CONF_no_bidi); - term->bksp_is_delete = conf_get_bool(term->conf, CONF_bksp_is_delete); - term->blink_cur = conf_get_bool(term->conf, CONF_blink_cur); - term->blinktext = conf_get_bool(term->conf, CONF_blinktext); - term->cjk_ambig_wide = conf_get_bool(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_bool(term->conf, CONF_crhaslf); - term->erase_to_scrollback = conf_get_bool(term->conf, CONF_erase_to_scrollback); - term->funky_type = conf_get_int(term->conf, CONF_funky_type); - term->sharrow_type = conf_get_int(term->conf, CONF_sharrow_type); - term->lfhascr = conf_get_bool(term->conf, CONF_lfhascr); - term->logflush = conf_get_bool(term->conf, CONF_logflush); - term->logtype = conf_get_int(term->conf, CONF_logtype); - term->mouse_override = conf_get_bool(term->conf, CONF_mouse_override); - term->nethack_keypad = conf_get_bool(term->conf, CONF_nethack_keypad); - term->no_alt_screen = conf_get_bool(term->conf, CONF_no_alt_screen); - term->no_applic_c = conf_get_bool(term->conf, CONF_no_applic_c); - term->no_applic_k = conf_get_bool(term->conf, CONF_no_applic_k); - term->no_dbackspace = conf_get_bool(term->conf, CONF_no_dbackspace); - term->no_mouse_rep = conf_get_bool(term->conf, CONF_no_mouse_rep); - term->no_remote_charset = conf_get_bool(term->conf, CONF_no_remote_charset); - term->no_remote_resize = conf_get_bool(term->conf, CONF_no_remote_resize); - term->no_remote_wintitle = conf_get_bool(term->conf, CONF_no_remote_wintitle); - term->no_remote_clearscroll = conf_get_bool(term->conf, CONF_no_remote_clearscroll); - term->rawcnp = conf_get_bool(term->conf, CONF_rawcnp); - term->utf8linedraw = conf_get_bool(term->conf, CONF_utf8linedraw); - term->rect_select = conf_get_bool(term->conf, CONF_rect_select); - term->remote_qtitle_action = conf_get_int(term->conf, CONF_remote_qtitle_action); - term->rxvt_homeend = conf_get_bool(term->conf, CONF_rxvt_homeend); - term->scroll_on_disp = conf_get_bool(term->conf, CONF_scroll_on_disp); - term->scroll_on_key = conf_get_bool(term->conf, CONF_scroll_on_key); - term->xterm_mouse_forbidden = conf_get_bool(term->conf, CONF_no_mouse_rep); - term->xterm_256_colour = conf_get_bool(term->conf, CONF_xterm_256_colour); - term->true_colour = conf_get_bool(term->conf, CONF_true_colour); - - /* - * Parse the control-character escapes in the configured - * answerback string. - */ - { - char *answerback = conf_get_str(term->conf, CONF_answerback); - - strbuf_clear(term->answerback); - - while (*answerback) { - char *n; - char c = ctrlparse(answerback, &n); - if (n) { - put_byte(term->answerback, c); - answerback = n; - } else { - put_byte(term->answerback, *answerback++); - } - } - } -} - -void term_pre_reconfig(Terminal *term, Conf *conf) -{ - - /* - * Copy the current window title into the stored previous - * configuration, so that doing nothing to the window title field - * in the config box doesn't reset the title to its startup state. - */ - conf_set_str(conf, CONF_wintitle, term->window_title); -} - -/* - * 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. - */ - bool reset_wrap, reset_decom, reset_bce, reset_tblink, reset_charclass; - bool palette_changed = false; - int i; - - reset_wrap = (conf_get_bool(term->conf, CONF_wrap_mode) != - conf_get_bool(conf, CONF_wrap_mode)); - reset_decom = (conf_get_bool(term->conf, CONF_dec_om) != - conf_get_bool(conf, CONF_dec_om)); - reset_bce = (conf_get_bool(term->conf, CONF_bce) != - conf_get_bool(conf, CONF_bce)); - reset_tblink = (conf_get_bool(term->conf, CONF_blinktext) != - conf_get_bool(conf, CONF_blinktext)); - reset_charclass = false; - 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 = true; - - /* - * If the bidi or shaping settings have changed, flush the bidi - * cache completely. - */ - if (conf_get_bool(term->conf, CONF_no_arabicshaping) != - conf_get_bool(conf, CONF_no_arabicshaping) || - conf_get_bool(term->conf, CONF_no_bidi) != - conf_get_bool(conf, CONF_no_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; - } - } - - { - const char *old_title = conf_get_str(term->conf, CONF_wintitle); - const char *new_title = conf_get_str(conf, CONF_wintitle); - if (strcmp(old_title, new_title)) { - sfree(term->window_title); - term->window_title = dupstr(new_title); - term->wintitle_codepage = DEFAULT_CODEPAGE; - term->win_title_pending = true; - term_schedule_update(term); - } - } - - /* - * Just setting 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, so that - * they can be updated immediately if they haven't been overridden - * by some escape sequence. - */ - { - int i, j; - for (i = 0; i < CONF_NCOLOURS; i++) { - for (j = 0; j < 3; j++) - if (conf_get_int_int(term->conf, CONF_colours, i*3+j) != - conf_get_int_int(conf, CONF_colours, i*3+j)) - break; - if (j < 3) { - /* Actually enacting the change has to be deferred - * until the new conf is installed. */ - palette_changed = true; - break; - } - } - } - - conf_free(term->conf); - term->conf = conf_copy(conf); - - if (reset_wrap) - term->alt_wrap = term->wrap = conf_get_bool(term->conf, CONF_wrap_mode); - if (reset_decom) - term->alt_om = term->dec_om = conf_get_bool(term->conf, CONF_dec_om); - if (reset_bce) { - term->use_bce = conf_get_bool(term->conf, CONF_bce); - set_erase_char(term); - } - if (reset_tblink) { - term->blink_is_real = conf_get_bool(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_bool(term->conf, CONF_no_alt_screen)) - swap_screen(term, 0, false, false); - if (conf_get_bool(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 = false; - } - if (!conf_get_str(term->conf, CONF_printer)) { - term_print_finish(term); - } - if (palette_changed) - term_notify_palette_changed(term); - term_schedule_tblink(term); - term_schedule_cblink(term); - term_copy_stuff_from_conf(term); - term_update_raw_mouse_mode(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)); - - /* - * That operation has invalidated the selection, if it overlapped - * the scrollback at all. - */ - if (term->selstate != NO_SELECTION && term->selstart.y < 0) - deselect(term); - - /* - * 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; - - /* - * The scrollbar will need updating to reflect the new state of - * the world. - */ - term->win_scrollbar_update_pending = true; - term_schedule_update(term); -} - -const optionalrgb optionalrgb_none = {0, 0, 0, 0}; - -void term_setup_window_titles(Terminal *term, const char *title_hostname) -{ - const char *conf_title = conf_get_str(term->conf, CONF_wintitle); - sfree(term->window_title); - sfree(term->icon_title); - if (*conf_title) { - term->window_title = dupstr(conf_title); - term->icon_title = dupstr(conf_title); - } else { - if (title_hostname && *title_hostname) - term->window_title = dupcat(title_hostname, " - ", appname); - else - term->window_title = dupstr(appname); - term->icon_title = dupstr(term->window_title); - } - term->wintitle_codepage = term->icontitle_codepage = DEFAULT_CODEPAGE; - term->win_title_pending = true; - term->win_icon_title_pending = true; -} - -static void palette_rebuild(Terminal *term) -{ - unsigned min_changed = OSC4_NCOLOURS, max_changed = 0; - - if (term->win_palette_pending) { - /* Possibly extend existing range. */ - min_changed = term->win_palette_pending_min; - max_changed = term->win_palette_pending_limit - 1; - } else { - /* Start with empty range. */ - min_changed = OSC4_NCOLOURS; - max_changed = 0; - } - - for (unsigned i = 0; i < OSC4_NCOLOURS; i++) { - rgb new_value; - bool found = false; - - for (unsigned j = lenof(term->subpalettes); j-- > 0 ;) { - if (term->subpalettes[j].present[i]) { - new_value = term->subpalettes[j].values[i]; - found = true; - break; - } - } - - assert(found); /* we expect SUBPAL_CONF to always be set */ - - if (new_value.r != term->palette[i].r || - new_value.g != term->palette[i].g || - new_value.b != term->palette[i].b) { - term->palette[i] = new_value; - if (min_changed > i) - min_changed = i; - if (max_changed < i) - max_changed = i; - } - } - - if (min_changed <= max_changed) { - /* - * At least one colour changed (or we had an update scheduled - * already). Schedule a redraw event to pass the result back - * to the TermWin. This also requires invalidating the rest - * of the window, because usually all the text will need - * redrawing in the new colours. - * (If there was an update pending and this palette rebuild - * didn't actually change anything, we'll harmlessly reinforce - * the existing update request.) - */ - term->win_palette_pending = true; - term->win_palette_pending_min = min_changed; - term->win_palette_pending_limit = max_changed + 1; - term_invalidate(term); - } -} - -/* - * Rebuild the palette from configuration and platform colours. - * If 'keep_overrides' set, any escape-sequence-specified overrides will - * remain in place. - */ -static void palette_reset(Terminal *term, bool keep_overrides) -{ - for (unsigned i = 0; i < OSC4_NCOLOURS; i++) - term->subpalettes[SUBPAL_CONF].present[i] = true; - - /* - * Copy all the palette information out of the Conf. - */ - for (unsigned i = 0; i < CONF_NCOLOURS; i++) { - rgb *col = &term->subpalettes[SUBPAL_CONF].values[ - colour_indices_conf_to_osc4[i]]; - col->r = conf_get_int_int(term->conf, CONF_colours, i*3+0); - col->g = conf_get_int_int(term->conf, CONF_colours, i*3+1); - col->b = conf_get_int_int(term->conf, CONF_colours, i*3+2); - } - - /* - * Directly invent the rest of the xterm-256 colours. - */ - for (unsigned i = 0; i < 216; i++) { - rgb *col = &term->subpalettes[SUBPAL_CONF].values[i + 16]; - int r = i / 36, g = (i / 6) % 6, b = i % 6; - col->r = r ? r * 40 + 55 : 0; - col->g = g ? g * 40 + 55 : 0; - col->b = b ? b * 40 + 55 : 0; - } - for (unsigned i = 0; i < 24; i++) { - rgb *col = &term->subpalettes[SUBPAL_CONF].values[i + 232]; - int shade = i * 10 + 8; - col->r = col->g = col->b = shade; - } - - /* - * Re-fetch any OS-local overrides. - */ - for (unsigned i = 0; i < OSC4_NCOLOURS; i++) - term->subpalettes[SUBPAL_PLATFORM].present[i] = false; - win_palette_get_overrides(term->win, term); - - if (!keep_overrides) { - /* - * Get rid of all escape-sequence configuration. - */ - for (unsigned i = 0; i < OSC4_NCOLOURS; i++) - term->subpalettes[SUBPAL_SESSION].present[i] = false; - } - - /* - * Rebuild the composite palette. - */ - palette_rebuild(term); -} - -void term_palette_override(Terminal *term, unsigned osc4_index, rgb rgb) -{ - /* - * We never expect to be called except as re-entry from our own - * call to win_palette_get_overrides above, so we need not mess - * about calling palette_rebuild. - */ - term->subpalettes[SUBPAL_PLATFORM].present[osc4_index] = true; - term->subpalettes[SUBPAL_PLATFORM].values[osc4_index] = rgb; -} - -/* - * Initialise the terminal. - */ -Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win) -{ - Terminal *term; - - /* - * Allocate a new Terminal structure and initialise the fields - * that need it. - */ - term = snew(Terminal); - memset(term, 0, sizeof(Terminal)); - term->win = win; - term->ucsdata = ucsdata; - term->conf = conf_copy(myconf); - term->compatibility_level = TM_PUTTY; - strcpy(term->id_string, "\033[?6c"); - bufchain_init(&term->inbuf); - bufchain_init(&term->printer_buf); - term->has_focus = true; - term->termstate = TOPLEVEL; - term->selstate = NO_SELECTION; - term->answerback = strbuf_new(); - - term_copy_stuff_from_conf(term); - - term->dispcursx = term->dispcursy = -1; - deselect(term); - term->rows = term->cols = -1; - power_on(term, true); - term->attr_mask = 0xffffffff; - - /* FULL-TERMCHAR */ - term->basic_erase_char.chr = CSET_ASCII | ' '; - term->basic_erase_char.attr = ATTR_DEFAULT; - term->basic_erase_char.truecolour.fg = optionalrgb_none; - term->basic_erase_char.truecolour.bg = optionalrgb_none; - term->erase_char = term->basic_erase_char; - - /* TermWin implementations will typically extend these with - * clipboard ids they know about */ - term->mouse_select_clipboards[0] = CLIP_LOCAL; - term->n_mouse_select_clipboards = 1; - term->mouse_paste_clipboard = CLIP_NULL; - - term->trusted = true; - - term->window_title = dupstr(""); - term->icon_title = dupstr(""); - term->wintitle_codepage = term->icontitle_codepage = DEFAULT_CODEPAGE; - - term->win_resize_pending = WIN_RESIZE_NO; - - term->bidi_ctx = bidi_new_context(); - - palette_reset(term, false); - - return term; -} - -void term_free(Terminal *term) -{ - compressed_scrollback_line *cline; - termline *line; - struct beeptime *beep; - int i; - - while ((cline = delpos234(term->scrollback, 0)) != NULL) - free_compressed_line(cline); - freetree234(term->scrollback); - while ((line = delpos234(term->screen, 0)) != NULL) - freetermline(line); - freetree234(term->screen); - while ((line = delpos234(term->alt_screen, 0)) != NULL) - freetermline(line); - freetree234(term->alt_screen); - if (term->disptext) { - for (i = 0; i < term->rows; i++) - freetermline(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); - strbuf_free(term->answerback); - - 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); - delete_callbacks_for_context(term); - - conf_free(term->conf); - - sfree(term->window_title); - sfree(term->icon_title); - - bidi_free_context(term->bidi_ctx); - - /* In case a term_userpass_state is still around */ - if (term->userpass_state) - term_userpass_state_free(term->userpass_state); - - sfree(term); -} - -void term_set_trust_status(Terminal *term, bool trusted) -{ - term->trusted = trusted; -} - -void term_get_cursor_position(Terminal *term, int *x, int *y) -{ - *x = term->curs.x; - *y = term->curs.y; -} - -/* - * 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) { - compressed_scrollback_line *cline; - /* Insert a line from the scrollback at the top of the screen. */ - assert(sblen >= term->tempsblines); - cline = delpos234(term->scrollback, --sblen); - line = decompressline_and_free(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 = newtermline(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); - freetermline(line); - } else { - /* push top row to scrollback */ - line = delpos234(term->screen, 0); - addpos234(term->scrollback, compressline_and_free(line), sblen++); - 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] = newtermline(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++) - freetermline(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 = newtermline(term, newcols, true); - addpos234(newalt, line, i); - } - if (term->alt_screen) { - while (NULL != (line = delpos234(term->alt_screen, 0))) - freetermline(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 = false; - term->alt_wnext = false; - - term->rows = newrows; - term->cols = newcols; - term->savelines = newsavelines; - - swap_screen(term, save_alt_which, false, false); - - term->win_scrollbar_update_pending = true; - term_schedule_update(term); - if (term->backend) - backend_size(term->backend, term->cols, term->rows); -} - -void term_resize_request_completed(Terminal *term) -{ - assert(term->win_resize_pending == WIN_RESIZE_AWAIT_REPLY); - term->win_resize_pending = WIN_RESIZE_NO; - queue_toplevel_callback(term_out_cb, term); -} - -/* - * Hand a backend to the terminal, so it can be notified of resizes. - */ -void term_provide_backend(Terminal *term, Backend *backend) -{ - term->backend = backend; - if (term->backend && term->cols > 0 && term->rows > 0) - backend_size(term->backend, 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, - bool reset, bool keep_cur_pos) -{ - int t; - bool bt; - pos tp; - truecolour ttc; - tree234 *ttr; - - if (!which) - reset = false; /* do no weird resetting if which==0 */ - - if (which != term->alt_which) { - if (term->erase_to_scrollback && term->alt_screen && - term->alt_which && term->disptop < 0) { - /* - * We're swapping away from the alternate screen, so some - * lines are about to vanish from the virtual scrollback. - * Adjust disptop by that much, so that (if we're not - * resetting the scrollback anyway on a display event) the - * current scroll position still ends up pointing at the - * same text. - */ - term->disptop += term->alt_sblines; - if (term->disptop > 0) - term->disptop = 0; - } - - term->alt_which = which; - - ttr = term->alt_screen; - term->alt_screen = term->screen; - term->screen = ttr; - term->alt_sblines = ( - term->alt_screen ? - find_last_nonempty_line(term, term->alt_screen) + 1 : 0); - 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; - bt = term->dec_om; - if (!reset) term->dec_om = term->alt_om; - term->alt_om = bt; - bt = term->wrap; - if (!reset) term->wrap = term->alt_wrap; - term->alt_wrap = bt; - bt = term->wrapnext; - if (!reset) term->wrapnext = term->alt_wnext; - term->alt_wnext = bt; - bt = term->insert; - if (!reset) term->insert = term->alt_ins; - term->alt_ins = bt; - t = term->cset; - if (!reset) term->cset = term->alt_cset; - term->alt_cset = t; - bt = term->utf; - if (!reset) term->utf = term->alt_utf; - term->alt_utf = bt; - t = term->sco_acs; - if (!reset) term->sco_acs = term->alt_sco_acs; - term->alt_sco_acs = t; - - tp = term->savecurs; - if (!reset) - term->savecurs = term->alt_savecurs; - term->alt_savecurs = tp; - t = term->save_cset; - if (!reset) - term->save_cset = term->alt_save_cset; - term->alt_save_cset = t; - t = term->save_csattr; - if (!reset) - term->save_csattr = term->alt_save_csattr; - term->alt_save_csattr = t; - t = term->save_attr; - if (!reset) - term->save_attr = term->alt_save_attr; - term->alt_save_attr = t; - ttc = term->save_truecolour; - if (!reset) - term->save_truecolour = term->alt_save_truecolour; - term->alt_save_truecolour = ttc; - bt = term->save_utf; - if (!reset) - term->save_utf = term->alt_save_utf; - term->alt_save_utf = bt; - bt = term->save_wnext; - if (!reset) - term->save_wnext = term->alt_save_wnext; - term->alt_save_wnext = bt; - t = term->save_sco_acs; - if (!reset) - term->save_sco_acs = term->alt_save_sco_acs; - term->alt_save_sco_acs = t; - - if (term->erase_to_scrollback && term->alt_screen && - term->alt_which && term->disptop < 0) { - /* - * Inverse of the adjustment at the top of this function. - * This time, we're swapping _to_ the alternate screen, so - * some lines are about to _appear_ in the virtual - * scrollback, and we adjust disptop in the other - * direction. - * - * Both these adjustments depend on the value stored in - * term->alt_sblines while the alt screen is selected, - * which is why we had to do one _before_ switching away - * from it and the other _after_ switching to it. - */ - term->disptop -= term->alt_sblines; - int limit = -sblines(term); - if (term->disptop < limit) - term->disptop = limit; - } - } - - if (reset && term->screen) { - /* - * Yes, this _is_ supposed to honour background-colour-erase. - */ - erase_lots(term, false, true, true); - } - - seen_disp_event(term); -} - -/* - * Update the scroll bar. - */ -static void update_sbar(Terminal *term) -{ - int nscroll = sblines(term); - win_set_scrollbar(term->win, 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); -} - -static void clear_line(Terminal *term, termline *line) -{ - resizeline(term, line, term->cols); - for (int i = 0; i < term->cols; i++) - copy_termchar(line, i, &term->erase_char); - line->lattr = LATTR_NORM; -} - -static void check_trust_status(Terminal *term, termline *line) -{ - if (line->trusted != term->trusted) { - /* - * If we're displaying trusted output on a previously - * untrusted line, or vice versa, we need to switch the - * 'trusted' attribute on this terminal line, and also clear - * all its previous contents. - */ - clear_line(term, line); - line->trusted = term->trusted; - } -} - -/* - * 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, bool sb) -{ - termline *line; - int seltop, scrollwinsize; - - if (topline != 0 || term->alt_which != 0) - sb = false; - - 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); - clear_line(term, line); - 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) { - compressed_scrollback_line *cline; - - sblen--; - cline = delpos234(term->scrollback, 0); - free_compressed_line(cline); - } else - term->tempsblines += 1; - - addpos234(term->scrollback, compressline_no_free(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--; - - /* - * We've just modified the data that the terminal's - * scrollbar is based on, so remember to update it. - */ - term->win_scrollbar_update_pending = true; - } - resizeline(term, line, term->cols); - clear_line(term, line); - line->trusted = false; - 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; - } - } - } - } - } - - seen_disp_event(term); -} - -/* - * 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; - seen_disp_event(term); -} - -/* - * Save or restore the cursor and SGR mode. - */ -static void save_cursor(Terminal *term, bool save) -{ - if (save) { - term->savecurs = term->curs; - term->save_attr = term->curr_attr; - term->save_truecolour = term->curr_truecolour; - 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->curr_truecolour = term->save_truecolour; - 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); - seen_disp_event(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_trust_status(term, ldata); - 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, - bool line_only, bool from_begin, bool to_end) -{ - pos start, end; - bool erase_lattr; - bool erasing_lines_from_top = false; - - 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; - } - - /* This is the endpoint of the clearing operation that is not - * either the start or end of the line / screen. */ - pos boundary = term->curs; - - if (!from_begin) { - /* - * If we're erasing from the current char to the end of - * line/screen, then we take account of wrapnext, so as to - * maintain the invariant that writing a printing character - * followed by ESC[K should not overwrite the character you - * _just wrote_. That is, when wrapnext says the cursor is - * 'logically' at the very rightmost edge of the screen - * instead of just before the last printing char, ESC[K should - * do nothing at all, and ESC[J should clear the next line but - * leave this one unchanged. - * - * This adjusted position will also be the position we use for - * check_boundary (i.e. the thing we ensure isn't in the - * middle of a double-width printing char). - */ - if (term->wrapnext) - incpos(boundary); - - start = boundary; - } - if (!to_end) { - /* - * If we're erasing from the start of (at least) the line _to_ - * the current position, then that is taken to mean 'inclusive - * of the cell under the cursor', which means we don't - * consider wrapnext at all: whether it's set or not, we still - * clear the cell under the cursor. - * - * Again, that incremented boundary position is where we - * should be careful of a straddling wide character. - */ - incpos(boundary); - end = boundary; - } - if (!from_begin || !to_end) - check_boundary(term, boundary.x, boundary.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 = true; - - 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); - check_trust_status(term, ldata); - 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); - check_trust_status(term, ldata); - } - } - } - - /* 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 explicitly thrown them away). */ - if (erasing_lines_from_top && !(term->alt_which)) - term->tempsblines = 0; - - seen_disp_event(term); -} - -/* - * 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); - check_trust_status(term, ldata); - 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); - } -} - -static void term_update_raw_mouse_mode(Terminal *term) -{ - bool want_raw = (term->xterm_mouse != 0 && !term->xterm_mouse_forbidden); - win_set_raw_mouse_mode(term->win, want_raw); - term->win_pointer_shape_pending = true; - term->win_pointer_shape_raw = want_raw; - term_schedule_update(term); -} - -static void term_request_resize(Terminal *term, int cols, int rows) -{ - if (term->cols == cols && term->rows == rows) - return; /* don't need to do anything */ - - term->win_resize_pending = WIN_RESIZE_NEED_SEND; - term->win_resize_pending_w = cols; - term->win_resize_pending_h = rows; - term_schedule_update(term); -} - -/* - * 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, bool state) -{ - if (query == 1) { - 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) - term_request_resize(term, 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 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; - term_update_raw_mouse_mode(term); - break; - case 1002: /* xterm mouse 2 (inc. button drags) */ - term->xterm_mouse = state ? 2 : 0; - term_update_raw_mouse_mode(term); - break; - case 1003: /* xterm mouse any-event tracking */ - term->xterm_mouse = state ? 3 : 0; - term_update_raw_mouse_mode(term); - break; - case 1006: /* xterm extended mouse */ - term->xterm_extended_mouse = state; - break; - case 1015: /* urxvt extended mouse */ - term->urxvt_extended_mouse = state; - 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 if (query == 0) { - switch (mode) { - case 4: /* IRM: set insert mode */ - compatibility(VT102); - term->insert = state; - break; - case 12: /* SRM: set echo mode */ - term->srm_echo = !state; - 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_is_apc) { - /* This OSC was really an APC, and we don't support that - * sequence at all. We only recognise it in order to ignore it - * and filter it out of input. */ - return; - } - - 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) { - sfree(term->icon_title); - term->icon_title = dupstr(term->osc_string); - term->icontitle_codepage = term->ucsdata->line_codepage; - term->win_icon_title_pending = true; - term_schedule_update(term); - } - if (term->esc_args[0] == 1) - break; - /* fall through: parameter 0 means set both */ - case 2: - case 21: - if (!term->no_remote_wintitle) { - sfree(term->window_title); - term->window_title = dupstr(term->osc_string); - term->wintitle_codepage = term->ucsdata->line_codepage; - term->win_title_pending = true; - term_schedule_update(term); - } - break; - case 4: - if (term->ldisc && !strcmp(term->osc_string, "?")) { - unsigned index = term->esc_args[1]; - if (index < OSC4_NCOLOURS) { - rgb colour = term->palette[index]; - char *reply_buf = dupprintf( - "\033]4;%u;rgb:%04x/%04x/%04x\007", index, - (unsigned)colour.r * 0x0101, - (unsigned)colour.g * 0x0101, - (unsigned)colour.b * 0x0101); - ldisc_send(term->ldisc, reply_buf, strlen(reply_buf), - false); - sfree(reply_buf); - } - } - 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) -{ - size_t size; - while ((size = bufchain_size(&term->printer_buf)) > 5) { - ptrlen data = bufchain_prefix(&term->printer_buf); - if (data.len > size-5) - data.len = size-5; - printer_job_data(term->print_job, data.ptr, data.len); - bufchain_consume(&term->printer_buf, data.len); - } -} -static void term_print_finish(Terminal *term) -{ - size_t 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) { - ptrlen data = bufchain_prefix(&term->printer_buf); - c = *(char *)data.ptr; - 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; -} - -static void term_display_graphic_char(Terminal *term, unsigned long c) -{ - termline *cline = scrlineptr(term->curs.y); - int width = 0; - if (DIRECT_CHAR(c)) - width = 1; - if (!width) - width = term_char_width(term, 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); - - check_trust_status(term, cline); - - int linecols = term->cols; - if (cline->trusted) - linecols -= TRUST_SIGIL_WIDTH; - - /* - * Before we switch on the character width, do a preliminary check for - * cases where we might have no room at all to display a double-width - * character. Our fallback is to substitute REPLACEMENT CHARACTER, - * which is single-width, and it's easiest to do that _before_ having - * to 'goto' from one switch case to another. - */ - if (width == 2 && term->curs.x >= linecols-1) { - /* - * If we're in wrapping mode and the terminal is at least 2 cells - * wide, it's OK, we have a fallback. But otherwise, substitute. - */ - if (linecols < 2 || !term->wrap) { - width = 1; - c = 0xFFFD; - } - } - - switch (width) { - case 2: - /* - * If we're about to display a double-width character starting in - * the rightmost column (and we're in wrapping mode - the other - * case was disposed of above), 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 >= linecols-1) { - assert(term->wrap); /* we handled the non-wrapping case above */ - 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; - cline->chars[term->curs.x].truecolour = - term->curr_truecolour; - - 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; - cline->chars[term->curs.x].truecolour = - term->curr_truecolour; - - 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; - cline->chars[term->curs.x].truecolour = - term->curr_truecolour; - - 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); - } - return; - default: - return; - } - term->curs.x++; - if (term->curs.x >= linecols) { - term->curs.x = linecols - 1; - 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); -} - -static strbuf *term_input_data_from_unicode( - Terminal *term, const wchar_t *widebuf, int len) -{ - strbuf *buf = strbuf_new(); - - if (in_utf(term)) { - /* - * Translate input wide characters into UTF-8 to go in the - * terminal's input data queue. - */ - for (int 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 = '.'; - } - } - - put_utf8_char(buf, ch); - } - } else { - /* - * Call to the character-set subsystem to translate into - * whatever charset the terminal is currently configured in. - * - * Since the terminal doesn't currently support any multibyte - * character set other than UTF-8, we can assume here that - * there will be at most one output byte per input wchar_t. - * (But also we must allow space for the trailing NUL that - * wc_to_mb will write.) - */ - char *bufptr = strbuf_append(buf, len + 1); - int rv; - rv = wc_to_mb(term->ucsdata->line_codepage, 0, widebuf, len, - bufptr, len + 1, NULL); - strbuf_shrink_to(buf, rv < 0 ? 0 : rv); - } - - return buf; -} - -static strbuf *term_input_data_from_charset( - Terminal *term, int codepage, const char *str, int len) -{ - strbuf *buf; - - if (codepage < 0) { - buf = strbuf_new(); - put_data(buf, str, len); - } else { - int widesize = len * 2; /* allow for UTF-16 surrogates */ - wchar_t *widebuf = snewn(widesize, wchar_t); - int widelen = mb_to_wc(codepage, 0, str, len, widebuf, widesize); - buf = term_input_data_from_unicode(term, widebuf, widelen); - sfree(widebuf); - } - - return buf; -} - -static inline void term_bracketed_paste_start(Terminal *term) -{ - ptrlen seq = PTRLEN_LITERAL("\033[200~"); - if (term->ldisc) - ldisc_send(term->ldisc, seq.ptr, seq.len, false); - term->bracketed_paste_active = true; -} - -static inline void term_bracketed_paste_stop(Terminal *term) -{ - if (!term->bracketed_paste_active) - return; - - ptrlen seq = PTRLEN_LITERAL("\033[201~"); - if (term->ldisc) - ldisc_send(term->ldisc, seq.ptr, seq.len, false); - term->bracketed_paste_active = false; -} - -static inline void term_keyinput_internal( - Terminal *term, const void *buf, int len, bool interactive) -{ - if (term->srm_echo) { - /* - * Implement the terminal-level local echo behaviour that - * ECMA-48 specifies when terminal mode 12 is configured off - * (ESC[12l). In this mode, data input to the terminal via the - * keyboard is also added to the output buffer. But this - * doesn't apply to escape sequences generated as session - * input _within_ the terminal, e.g. in response to terminal - * query sequences, or the bracketing sequences of bracketed - * paste mode. Those will be sent directly via - * ldisc_send(term->ldisc, ...) and won't go through this - * function. - */ - - /* Mimic the special case of negative length in ldisc_send */ - int true_len = len >= 0 ? len : strlen(buf); - - bufchain_add(&term->inbuf, buf, true_len); - term_added_data(term, false); - } - if (interactive) - term_bracketed_paste_stop(term); - if (term->ldisc) - ldisc_send(term->ldisc, buf, len, interactive); - term_seen_key_event(term); -} - -unsigned long term_translate( - Terminal *term, struct term_utf8_decode *utf8, unsigned char c) -{ - if (in_utf(term)) { - switch (utf8->state) { - case 0: - if (c < 0x80) { - /* UTF-8 must be stateless so we ignore iso2022. */ - if (term->ucsdata->unitab_ctrl[c] != 0xFF) { - return term->ucsdata->unitab_ctrl[c]; - } else if ((term->utf8linedraw) && - (term->cset_attr[term->cset] == CSET_LINEDRW)) { - /* Linedraw characters are explicitly enabled */ - return c | CSET_LINEDRW; - } else { - return c | CSET_ASCII; - } - } else if ((c & 0xe0) == 0xc0) { - utf8->size = utf8->state = 1; - utf8->chr = (c & 0x1f); - } else if ((c & 0xf0) == 0xe0) { - utf8->size = utf8->state = 2; - utf8->chr = (c & 0x0f); - } else if ((c & 0xf8) == 0xf0) { - utf8->size = utf8->state = 3; - utf8->chr = (c & 0x07); - } else if ((c & 0xfc) == 0xf8) { - utf8->size = utf8->state = 4; - utf8->chr = (c & 0x03); - } else if ((c & 0xfe) == 0xfc) { - utf8->size = utf8->state = 5; - utf8->chr = (c & 0x01); - } else { - return UCSINVALID; - } - return UCSINCOMPLETE; - case 1: - case 2: - case 3: - case 4: - case 5: - if ((c & 0xC0) != 0x80) { - utf8->state = 0; - return UCSTRUNCATED; /* caller will then give us the - * same byte again */ - } - utf8->chr = (utf8->chr << 6) | (c & 0x3f); - if (--utf8->state) - return UCSINCOMPLETE; - - unsigned long t = utf8->chr; - - /* Is somebody trying to be evil! */ - if (t < 0x80 || - (t < 0x800 && utf8->size >= 2) || - (t < 0x10000 && utf8->size >= 3) || - (t < 0x200000 && utf8->size >= 4) || - (t < 0x4000000 && utf8->size >= 5)) - return UCSINVALID; - - /* Unicode line separator and paragraph separator are CR-LF */ - if (t == 0x2028 || t == 0x2029) - return 0x85; - - /* High controls are probably a Baaad idea too. */ - if (t < 0xA0) - return 0xFFFD; - - /* The UTF-16 surrogates are not nice either. */ - /* The standard give the option of decoding these: - * I don't want to! */ - if (t >= 0xD800 && t < 0xE000) - return UCSINVALID; - - /* ISO 10646 characters now limited to UTF-16 range. */ - if (t > 0x10FFFF) - return UCSINVALID; - - /* This is currently a TagPhobic application.. */ - if (t >= 0xE0000 && t <= 0xE007F) - return UCSINCOMPLETE; - - /* U+FEFF is best seen as a null. */ - if (t == 0xFEFF) - return UCSINCOMPLETE; - /* But U+FFFE is an error. */ - if (t == 0xFFFE || t == 0xFFFF) - return UCSINVALID; - - return t; - } - } else if (term->sco_acs && - (c!='\033' && c!='\012' && c!='\015' && c!='\b')) { - /* Are we in the nasty ACS mode? Note: no sco in utf mode. */ - if (term->sco_acs == 2) - c |= 0x80; - - return 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) - return term->ucsdata->unitab_ctrl[c]; - else - return c | CSET_LINEDRW; - break; - - case CSET_GBCHR: - /* If UK-ASCII, make the '#' a LineDraw Pound */ - if (c == '#') - return '}' | CSET_LINEDRW; - /* fall through */ - - case CSET_ASCII: - if (term->ucsdata->unitab_ctrl[c] != 0xFF) - return term->ucsdata->unitab_ctrl[c]; - else - return c | CSET_ASCII; - break; - case CSET_SCOACS: - if (c >= ' ') - return c | CSET_SCOACS; - break; - } - } - return c; -} - -/* - * 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, bool called_from_term_data) -{ - unsigned long c; - int unget; - const unsigned char *chars; - size_t nchars_got = 0, nchars_used = 0; - - /* - * 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) - return; - - unget = -1; - - chars = NULL; /* placate compiler warnings */ - while (nchars_got < nchars_used || - unget != -1 || - bufchain_size(&term->inbuf) > 0) { - if (unget != -1) { - /* - * Handle a character we left in 'unget' the last time - * round this loop. This happens if a UTF-8 sequence is - * aborted early, by containing fewer continuation bytes - * than its introducer expected: the non-continuation byte - * that interrupted the sequence must now be processed - * as a fresh piece of input in its own right. - */ - c = unget; - unget = -1; - } else { - /* - * If we're waiting for a terminal resize triggered by an - * escape sequence, we defer processing the terminal - * output until we receive acknowledgment from the front - * end that the resize has happened, so that further - * output will be processed in the context of the new - * size. - * - * This test goes inside the main while-loop, so that we - * exit early if we encounter a resize escape sequence - * part way through term->inbuf. - * - * It's also in the branch of this if statement that - * doesn't deal with a character left in 'unget' by the - * previous loop iteration, because if we break out of - * this loop with an ungot character still pending, we'll - * lose it. (And in any case, if the previous thing that - * happened was a truncated UTF-8 sequence, then it won't - * have scheduled a pending resize.) - */ - if (term->win_resize_pending != WIN_RESIZE_NO) - break; - - if (nchars_got == nchars_used) { - /* Delete the previous chunk from the bufchain */ - bufchain_consume(&term->inbuf, nchars_used); - nchars_used = 0; - - if (bufchain_size(&term->inbuf) == 0) - break; /* no more data */ - - ptrlen data = bufchain_prefix(&term->inbuf); - chars = data.ptr; - nchars_got = data.len; - assert(chars != NULL); - assert(nchars_used < nchars_got); - } - c = chars[nchars_used++]; - - /* - * 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); - } - - /* 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; - } - } - - /* Do character-set translation. */ - if (term->termstate == TOPLEVEL) { - unsigned long t = term_translate(term, &term->utf8, c); - switch (t) { - case UCSINCOMPLETE: - continue; /* didn't complete a multibyte char */ - case UCSTRUNCATED: - unget = c; - /* fall through */ - case UCSINVALID: - c = UCSERR; - break; - default: - c = t; - 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 = 0; - 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); - } - seen_disp_event(term); - } 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) { - strbuf *buf = term_input_data_from_charset( - term, DEFAULT_CODEPAGE, - term->answerback->s, term->answerback->len); - ldisc_send(term->ldisc, buf->s, buf->len, false); - strbuf_free(buf); - } - break; - case '\007': { /* BEL: Bell */ - if (term->termstate == SEEN_OSC || - term->termstate == SEEN_OSC_W) { - /* - * In an OSC context, BEL is one of the ways to terminate - * the whole sequence. We process it as such even if we - * haven't got into the final OSC_STRING state yet, so that - * OSC sequences without a string will be handled cleanly. - */ - do_osc(term); - term->termstate = TOPLEVEL; - break; - } - - 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) { - win_bell(term->win, 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->wrapnext) { - term->wrapnext = false; - } else if (term->curs.x == 0 && - (term->curs.y == 0 || !term->wrap)) { - /* do nothing */ - } else if (term->curs.x == 0 && term->curs.y > 0) { - term->curs.x = term->cols - 1, term->curs.y--; - - /* - * If the line we've just wrapped back on to had the - * LATTR_WRAPPED2 flag set, it means that the line wrapped - * because a double-width character was printed with the - * cursor in the rightmost column, and the best handling - * available was to leave that column empty and move the - * whole character to the next line. In that situation, - * backspacing needs to put the cursor on the previous - * _logical_ character, i.e. skip the empty space left by - * the wrapping. This arranges that if an application - * unaware of the terminal width or cursor position prints - * a number of printing characters and then tries to return - * to a particular one of them by emitting the right number - * of backspaces, it's still the right number even if a - * line break appeared in a maximally awkward position. - */ - termline *ldata = scrlineptr(term->curs.y); - if (term->curs.x > 0 && (ldata->lattr & LATTR_WRAPPED2)) - term->curs.x--; - } 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 if (term->termstate == SEEN_OSC || - term->termstate == SEEN_OSC_W) { - /* Be prepared to terminate an OSC early */ - term->termstate = OSC_MAYBE_ST; - } else { - compatibility(ANSIMIN); - term->termstate = SEEN_ESC; - term->esc_query = 0; - } - 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 */ - term_display_graphic_char(term, c); - term->last_graphic_char = c; - 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 = 0; - break; - case ']': /* OSC: xterm escape sequences */ - /* Compatibility is nasty here, xterm, linux, decterm yuk! */ - compatibility(OTHER); - term->termstate = SEEN_OSC; - term->osc_is_apc = false; - term->osc_strlen = 0; - term->esc_args[0] = 0; - term->esc_nargs = 1; - break; - case '_': /* APC: application program command */ - /* APC sequences are just a string, terminated by - * ST or (I've observed in practice) ^G. That is, - * they have the same termination convention as - * OSC. So we handle them by going straight into - * OSC_STRING state and setting a flag indicating - * that it's not really an OSC. */ - compatibility(OTHER); - term->termstate = SEEN_OSC; - term->osc_is_apc = true; - term->osc_strlen = 0; - term->esc_args[0] = 0; - term->esc_nargs = 1; - break; - case '7': /* DECSC: save cursor */ - compatibility(VT100); - save_cursor(term, true); - break; - case '8': /* DECRC: restore cursor */ - compatibility(VT100); - save_cursor(term, false); - 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), false); - break; - case 'c': /* RIS: restore power-on settings */ - compatibility(VT100); - power_on(term, true); - if (term->ldisc) /* cause ldisc to notice changes */ - ldisc_echoedit_update(term->ldisc); - if (term->reset_132) { - if (!term->no_remote_resize) - term_request_resize(term, 80, term->rows); - term->reset_132 = false; - } - 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); - check_trust_status(term, ldata); - ldata->lattr = nlattr; - seen_disp_event(term); - 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 = true; - break; - case ANSI('@', '%'): - compatibility(OTHER); - if (!term->no_remote_charset) - term->utf = false; - 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 = 1; - 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 'b': /* REP: repeat previous grap */ - CLAMP(term->esc_args[0], term->rows * term->cols); - if (term->last_graphic_char) { - unsigned i; - for (i = 0; i < term->esc_args[0]; i++) - term_display_graphic_char( - term, term->last_graphic_char); - } - 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, - false); - 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. */ - if (!term->no_remote_clearscroll) - 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), false); - 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), - false); - } else if (term->esc_args[0] == 5) { - ldisc_send(term->ldisc, "\033[0n", 4, false); - } - } - break; - case 'h': /* SM: toggle modes to high */ - case ANSI_QUE('h'): - compatibility(VT100); - for (int 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); - for (int 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. - */ - for (int i = 0; i < term->esc_nargs; i++) - switch (def(term->esc_args[i], 0)) { - case 0: /* restore defaults */ - term->curr_attr = term->default_attr; - term->curr_truecolour = - term->basic_erase_char.truecolour; - break; - case 1: /* enable bold */ - compatibility(VT100AVO); - term->curr_attr |= ATTR_BOLD; - break; - case 2: /* enable dim */ - compatibility(OTHER); - term->curr_attr |= ATTR_DIM; - 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 9: /* enable strikethrough */ - term->curr_attr |= ATTR_STRIKE; - 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 and dim */ - compatibility2(OTHER, VT220); - term->curr_attr &= ~(ATTR_BOLD | ATTR_DIM); - 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 29: /* disable strikethrough */ - term->curr_attr &= ~ATTR_STRIKE; - break; - case 30: - case 31: - case 32: - case 33: - case 34: - case 35: - case 36: - case 37: - /* foreground */ - term->curr_truecolour.fg.enabled = false; - term->curr_attr &= ~ATTR_FGMASK; - term->curr_attr |= - (term->esc_args[i] - 30)<curr_truecolour.fg.enabled = false; - term->curr_attr &= ~ATTR_FGMASK; - term->curr_attr |= - ((term->esc_args[i] - 90 + 8) - << ATTR_FGSHIFT); - break; - case 39: /* default-foreground */ - term->curr_truecolour.fg.enabled = false; - 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_truecolour.bg.enabled = false; - term->curr_attr &= ~ATTR_BGMASK; - term->curr_attr |= - (term->esc_args[i] - 40)<curr_truecolour.bg.enabled = false; - term->curr_attr &= ~ATTR_BGMASK; - term->curr_attr |= - ((term->esc_args[i] - 100 + 8) - << ATTR_BGSHIFT); - break; - case 49: /* default-background */ - term->curr_truecolour.bg.enabled = false; - term->curr_attr &= ~ATTR_BGMASK; - term->curr_attr |= ATTR_DEFBG; - break; - - /* - * 256-colour and true-colour - * sequences. A 256-colour - * foreground is selected by a - * sequence of 3 arguments in the - * form 38;5;n, where n is in the - * range 0-255. A true-colour RGB - * triple is selected by 5 args of - * the form 38;2;r;g;b. Replacing - * the initial 38 with 48 in both - * cases selects the same colour - * as the background. - */ - case 38: - 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); - term->curr_truecolour.fg = - optionalrgb_none; - i += 2; - } - if (i + 4 < term->esc_nargs && - term->esc_args[i + 1] == 2) { - parse_optionalrgb( - &term->curr_truecolour.fg, - term->esc_args + (i+2)); - i += 4; - } - break; - case 48: - 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); - term->curr_truecolour.bg = - optionalrgb_none; - i += 2; - } - if (i + 4 < term->esc_nargs && - term->esc_args[i+1] == 2) { - parse_optionalrgb( - &term->curr_truecolour.bg, - term->esc_args + (i+2)); - i += 4; - } - break; - } - set_erase_char(term); - break; - case 's': /* save cursor */ - save_cursor(term, true); - break; - case 'u': /* restore cursor */ - save_cursor(term, false); - 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) - term_request_resize(term, term->cols, 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 len; - char buf[80]; - const char *p; - case 1: - term->win_minimise_pending = true; - term->win_minimise_enable = false; - term_schedule_update(term); - break; - case 2: - term->win_minimise_pending = true; - term->win_minimise_enable = true; - term_schedule_update(term); - break; - case 3: - if (term->esc_nargs >= 3) { - if (!term->no_remote_resize) { - term->win_move_pending = true; - term->win_move_pending_x = - def(term->esc_args[1], 0); - term->win_move_pending_y = - def(term->esc_args[2], 0); - term_schedule_update(term); - } - } - 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 */ - term->win_zorder_pending = true; - term->win_zorder_top = true; - term_schedule_update(term); - break; - case 6: - /* move to bottom */ - term->win_zorder_pending = true; - term->win_zorder_top = false; - term_schedule_update(term); - break; - case 7: - term->win_refresh_pending = true; - term_schedule_update(term); - break; - case 8: - if (term->esc_nargs >= 3 && - !term->no_remote_resize) { - term_request_resize( - term, - def(term->esc_args[2], - term->conf_width), - def(term->esc_args[1], - term->conf_height)); - } - break; - case 9: - if (term->esc_nargs >= 2) { - term->win_maximise_pending = true; - term->win_maximise_enable = - term->esc_args[1]; - term_schedule_update(term); - } - break; - case 11: - if (term->ldisc) - ldisc_send(term->ldisc, term->minimised ? - "\033[2t" : "\033[1t", 4, - false); - break; - case 13: - if (term->ldisc) { - len = sprintf(buf, "\033[3;%u;%ut", - term->winpos_x, - term->winpos_y); - ldisc_send(term->ldisc, buf, len, false); - } - break; - case 14: - if (term->ldisc) { - len = sprintf(buf, "\033[4;%u;%ut", - term->winpixsize_y, - term->winpixsize_x); - ldisc_send(term->ldisc, buf, len, false); - } - break; - case 18: - if (term->ldisc) { - len = sprintf(buf, "\033[8;%d;%dt", - term->rows, term->cols); - ldisc_send(term->ldisc, buf, len, false); - } - 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 = term->icon_title; - else - p = EMPTY_WINDOW_TITLE; - len = strlen(p); - ldisc_send(term->ldisc, "\033]L", 3, - false); - ldisc_send(term->ldisc, p, len, false); - ldisc_send(term->ldisc, "\033\\", 2, - false); - } - break; - case 21: - if (term->ldisc && - term->remote_qtitle_action != TITLE_NONE) { - if(term->remote_qtitle_action == TITLE_REAL) - p = term->window_title; - else - p = EMPTY_WINDOW_TITLE; - len = strlen(p); - ldisc_send(term->ldisc, "\033]l", 3, - false); - ldisc_send(term->ldisc, p, len, false); - ldisc_send(term->ldisc, "\033\\", 2, - false); - } - 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; - 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; - 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) - term_request_resize( - term, - 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) - term_request_resize( - term, - 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); - - check_trust_status(term, cline); - 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, false); - } - } - 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->curr_truecolour.fg = optionalrgb_none; - 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->curr_truecolour.bg = optionalrgb_none; - 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, "%u", 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) - term_request_resize(term, - term->reset_132 ? 132 : 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, false); - 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[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; - break; - case 0x9C: - /* Terminate even though we aren't in OSC_STRING yet */ - do_osc(term); - term->termstate = TOPLEVEL; - break; - case 0xC2: - if (in_utf(term)) { - /* Or be prepared for the UTF-8 version of that */ - term->termstate = OSC_MAYBE_ST_UTF8; - } - break; - default: - /* - * _Most_ other characters here terminate the - * immediate parsing of the OSC sequence and go - * into OSC_STRING state, but we deal with a - * couple of exceptions first. - */ - if (c == 'L' && term->esc_args[0] == 2) { - /* - * Grotty hack to support xterm and DECterm title - * sequences concurrently. - */ - term->esc_args[0] = 1; - } else if (c == ';' && term->esc_nargs == 1 && - term->esc_args[0] == 4) { - /* - * xterm's OSC 4 sequence to query the current - * RGB value of a colour takes a second - * numeric argument which is easiest to parse - * using the existing system rather than in - * do_osc. - */ - term->esc_args[term->esc_nargs++] = 0; - } else { - term->termstate = OSC_STRING; - term->osc_strlen = 0; - } - } - break; - case OSC_STRING: - /* - * OSC sequences can be terminated or aborted in - * various ways. - * - * The official way to terminate an OSC, per written - * standards, is the String Terminator, SC. That can - * appear in a 7-bit two-character form ESC \, or as - * an 8-bit C1 control 0x9C. - * - * We only accept 0x9C in circumstances where it - * doesn't interfere with our main character set - * processing: so in ISO 8859-1, for example, the byte - * 0x9C is interpreted as ST, but in CP437 it's - * interpreted as an ordinary printing character (as - * it happens, the pound sign), because you might - * perfectly well want to put it in the window title - * like any other printing character. - * - * In particular, in UTF-8 mode, 0x9C is a perfectly - * valid continuation byte for an ordinary printing - * character, so we don't accept the C1 control form - * of ST unless it appears as a full UTF-8 character - * in its own right, i.e. bytes 0xC2 0x9C. - * - * BEL is also treated as a clean termination of OSC, - * which I believe was a behaviour introduced by - * xterm. - * - * To prevent run-on storage of OSC data forever if - * emission of a control sequence is interrupted, we - * also treat various control characters as illegal, - * so that they abort the OSC without processing it - * and return to TOPLEVEL state. These are CR, LF, and - * any ESC that is *not* followed by \. - */ - - if (c == '\012' || c == '\015') { - /* CR or LF aborts */ - term->termstate = TOPLEVEL; - break; - } - - if (c == '\033') { - /* ESC goes into a state where we wait to see if - * the next character is \ */ - term->termstate = OSC_MAYBE_ST; - break; - } - - if (c == '\007' || (c == 0x9C && !in_utf(term) && - term->ucsdata->unitab_ctrl[c] != 0xFF)) { - /* BEL, or the C1 ST appearing as a one-byte - * encoding, cleanly terminates the OSC right here */ - do_osc(term); - term->termstate = TOPLEVEL; - break; - } - - if (c == 0xC2 && in_utf(term)) { - /* 0xC2 is the UTF-8 character that might - * introduce the encoding of C1 ST */ - term->termstate = OSC_MAYBE_ST_UTF8; - break; - } - - /* Anything else gets added to the string */ - if (term->osc_strlen < OSC_STR_MAX) - term->osc_string[term->osc_strlen++] = (char)c; - break; - case OSC_MAYBE_ST_UTF8: - /* In UTF-8 mode, we've seen C2, so are we now seeing - * 9C? */ - if (c == 0x9C) { - /* Yes, so cleanly terminate the OSC */ - do_osc(term); - term->termstate = TOPLEVEL; - break; - } - /* No, so append the pending C2 byte to the OSC string - * followed by the current character, and go back to - * OSC string accumulation */ - if (term->osc_strlen < OSC_STR_MAX) - term->osc_string[term->osc_strlen++] = 0xC2; - if (term->osc_strlen < OSC_STR_MAX) - term->osc_string[term->osc_strlen++] = (char)c; - term->termstate = OSC_STRING; - 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) { - unsigned oscp_index = term->osc_string[0]; - assert(oscp_index < OSCP_NCOLOURS); - unsigned osc4_index = - colour_indices_oscp_to_osc4[oscp_index]; - - rgb *value = &term->subpalettes[SUBPAL_SESSION].values[ - osc4_index]; - value->r = term->osc_string[1] * 16 + term->osc_string[2]; - value->g = term->osc_string[3] * 16 + term->osc_string[4]; - value->b = term->osc_string[5] * 16 + term->osc_string[6]; - term->subpalettes[SUBPAL_SESSION].present[ - osc4_index] = true; - - palette_rebuild(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; - case 0x9C: - /* Terminate even though we aren't in OSC_STRING yet */ - do_osc(term); - term->termstate = TOPLEVEL; - break; - case 0xC2: - if (in_utf(term)) { - /* Or be prepared for the UTF-8 version of that */ - term->termstate = OSC_MAYBE_ST_UTF8; - } - break; - default: - term->termstate = OSC_STRING; - term->osc_strlen = 0; - } - break; - case VT52_ESC: - term->termstate = TOPLEVEL; - 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--; - seen_disp_event(term); - } - 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, false); - 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; - seen_disp_event(term); - break; - case 'f': - /* compatibility(ATARI) */ - term->cursor_on = false; - seen_disp_event(term); - 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 = true; - break; - case 'w': /* Autowrap off */ - /* compatibility(ATARI) */ - term->wrap = false; - break; - - case 'R': - /* compatibility(OTHER) */ - term->vt52_bold = false; - term->curr_attr = ATTR_DEFAULT; - term->curr_truecolour.fg = optionalrgb_none; - term->curr_truecolour.bg = optionalrgb_none; - 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); - } - } - - bufchain_consume(&term->inbuf, nchars_used); - - if (!called_from_term_data) - win_unthrottle(term->win, bufchain_size(&term->inbuf)); - - term_print_flush(term); - if (term->logflush && term->logctx) - logflush(term->logctx); -} - -/* Wrapper on term_out with the right prototype to be a toplevel callback */ -void term_out_cb(void *ctx) -{ - term_out((Terminal *)ctx, false); -} - -/* - * Small subroutine to parse three consecutive escape-sequence - * arguments representing a true-colour RGB triple into an - * optionalrgb. - */ -static void parse_optionalrgb(optionalrgb *out, unsigned *values) -{ - out->enabled = true; - out->r = values[0] < 256 ? values[0] : 0; - out->g = values[1] < 256 ? values[1] : 0; - out->b = values[2] < 256 ? values[2] : 0; -} - -/* - * 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 bool term_bidi_cache_hit(Terminal *term, int line, - termchar *lbefore, int width, bool trusted) -{ - 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 */ - - if (term->pre_bidi_cache[line].trusted != trusted) - return false; /* line has wrong trust state */ - - 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, bool trusted) -{ - size_t i, j; - - if (!term->pre_bidi_cache || term->bidi_cache_size <= line) { - j = term->bidi_cache_size; - sgrowarray(term->pre_bidi_cache, term->bidi_cache_size, line); - 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].trusted = false; - term->post_bidi_cache[j].trusted = false; - 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].trusted = trusted; - term->pre_bidi_cache[line].chars = snewn(size, termchar); - term->post_bidi_cache[line].width = width; - term->post_bidi_cache[line].trusted = trusted; - 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 = j = 0; j < width; j += wcTo[i].nchars, i++) { - int p = wcTo[i].index; - - if (p != BIDI_CHAR_INDEX_NONE) { - assert(0 <= p && p < width); - - for (int x = 0; x < wcTo[i].nchars; x++) { - term->post_bidi_cache[line].backward[j+x] = p+x; - term->post_bidi_cache[line].forward[p+x] = j+x; - } - } - } -} - -/* - * 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->no_bidi || !term->no_arabicshaping || - (ldata->trusted && term->cols > TRUST_SIGIL_WIDTH)) { - - if (!term_bidi_cache_hit(term, scr_y, ldata->chars, term->cols, - ldata->trusted)) { - - 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; - term->wcFrom[it].nchars = 1; - } - - if (ldata->trusted && term->cols > TRUST_SIGIL_WIDTH) { - memmove( - term->wcFrom + TRUST_SIGIL_WIDTH, term->wcFrom, - (term->cols - TRUST_SIGIL_WIDTH) * sizeof(*term->wcFrom)); - for (it = 0; it < TRUST_SIGIL_WIDTH; it++) { - term->wcFrom[it].origwc = term->wcFrom[it].wc = - (it == 0 ? TRUST_SIGIL_CHAR : - it == 1 ? UCSWIDE : ' '); - term->wcFrom[it].index = BIDI_CHAR_INDEX_NONE; - term->wcFrom[it].nchars = 1; - } - } - - int nbc = 0; - for (it = 0; it < term->cols; it++) { - term->wcFrom[nbc] = term->wcFrom[it]; - if (it+1 < term->cols && term->wcFrom[it+1].wc == UCSWIDE) { - term->wcFrom[nbc].nchars++; - it++; - } - nbc++; - } - - if (!term->no_bidi) - do_bidi(term->bidi_ctx, term->wcFrom, nbc); - - if (!term->no_arabicshaping) { - do_shape(term->wcFrom, term->wcTo, nbc); - } else { - /* If we're not calling do_shape, we must copy the - * data into wcTo anyway, unchanged */ - memcpy(term->wcTo, term->wcFrom, nbc * sizeof(*term->wcTo)); - } - - 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); - - int opos = 0; - for (it=0; itwcTo[it].index; - for (int j = 0; j < term->wcTo[it].nchars; j++) { - if (ipos != BIDI_CHAR_INDEX_NONE) { - term->ltemp[opos] = ldata->chars[ipos]; - if (term->ltemp[opos].cc_next) - term->ltemp[opos].cc_next -= opos - ipos; - - if (j > 0) - term->ltemp[opos].chr = UCSWIDE; - else if (term->wcTo[it].origwc != term->wcTo[it].wc) - term->ltemp[opos].chr = term->wcTo[it].wc; - } else { - term->ltemp[opos] = term->basic_erase_char; - term->ltemp[opos].chr = - j > 0 ? UCSWIDE : term->wcTo[it].origwc; - } - opos++; - } - } - assert(opos == term->cols); - term_bidi_cache_store(term, scr_y, ldata->chars, - term->ltemp, term->wcTo, - term->cols, ldata->size, ldata->trusted); - - lchars = term->ltemp; - } else { - lchars = term->post_bidi_cache[scr_y].chars; - } - } else { - lchars = NULL; - } - - return lchars; -} - -static void do_paint_draw(Terminal *term, termline *ldata, int x, int y, - wchar_t *ch, int ccount, - unsigned long attr, truecolour tc) -{ - if (ch[0] == TRUST_SIGIL_CHAR) { - assert(ldata->trusted); - assert(ccount == 1); - assert(attr & ATTR_WIDE); - wchar_t tch[2]; - tch[0] = tch[1] = L' '; - win_draw_text(term->win, x, y, tch, 2, term->basic_erase_char.attr, - ldata->lattr, term->basic_erase_char.truecolour); - win_draw_trust_sigil(term->win, x, y); - } else { - win_draw_text(term->win, x, y, ch, ccount, attr, ldata->lattr, tc); - if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) - win_draw_cursor(term->win, x, y, ch, ccount, - attr, ldata->lattr, tc); - } -} - -/* - * Given a context, update the window. - */ -static void do_paint(Terminal *term) -{ - int i, j, our_curs_y, our_curs_x; - int rv, cursor; - pos scrpos; - wchar_t *ch; - size_t chlen; - 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; - - /* The normal screen data */ - for (i = 0; i < term->rows; i++) { - termline *ldata; - termchar *lchars; - bool dirty_line, dirty_run, selected; - unsigned long attr = 0, cset = 0; - int start = 0; - int ccount = 0; - bool last_run_dirty = false; - int laststart; - bool dirtyrect; - int *backward; - truecolour tc; - - 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; - } - - if (term->true_colour) { - tc = d->truecolour; - } else { - tc.fg = tc.bg = optionalrgb_none; - } - - 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) && - posPle_left(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 && - win_char_width(term->win, 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; - newline[j].truecolour = tc; - /* 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; - - tc = term->erase_char.truecolour; - for (j = 0; j < term->cols; j++) { - unsigned long tattr, tchar; - bool 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; - - if (!truecolour_equal(newline[j].truecolour, tc)) - break_run = true; - -#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; - - /* - * Break on both sides of a trust sigil. - */ - if (d->chr == TRUST_SIGIL_CHAR || - (j >= 2 && d[-1].chr == UCSWIDE && - d[-2].chr == TRUST_SIGIL_CHAR)) - 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 && - truecolour_equal( - term->disptext[i]->chars[j].truecolour, tc)) - break_run = true; - else if (!dirty_run && ccount == 1) - break_run = true; - } - - if (break_run) { - if ((dirty_run || last_run_dirty) && ccount > 0) - do_paint_draw(term, ldata, start, i, ch, ccount, attr, tc); - start = j; - ccount = 0; - attr = tattr; - tc = newline[j].truecolour; - 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; - } - - sgrowarrayn(ch, chlen, ccount, 2); - -#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; - } - - sgrowarrayn(ch, chlen, ccount, 2); - -#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; - term->disptext[i]->chars[j].truecolour = tc; - 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_paint_draw(term, ldata, start, i, ch, ccount, attr, tc); - - 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, - int left, int top, int right, int bottom, bool 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); - } 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); - - 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; - term->win_scrollbar_update_pending = true; - term_schedule_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 { - size_t bufsize; /* amount of allocated space in textbuf/attrbuf */ - size_t 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 */ - truecolour *tcbuf; /* buffer for copied colours */ - truecolour *tcptr; /* = tcbuf + bufpos */ -} clip_workbuf; - -static void clip_addchar(clip_workbuf *b, wchar_t chr, int attr, truecolour tc) -{ - if (b->bufpos >= b->bufsize) { - sgrowarray(b->textbuf, b->bufsize, b->bufpos); - b->textptr = b->textbuf + b->bufpos; - b->attrbuf = sresize(b->attrbuf, b->bufsize, int); - b->attrptr = b->attrbuf + b->bufpos; - b->tcbuf = sresize(b->tcbuf, b->bufsize, truecolour); - b->tcptr = b->tcbuf + b->bufpos; - } - *b->textptr++ = chr; - *b->attrptr++ = attr; - *b->tcptr++ = tc; - b->bufpos++; -} - -static void clipme(Terminal *term, pos top, pos bottom, bool rect, bool desel, - const int *clipboards, int n_clipboards) -{ - clip_workbuf buf; - int old_top_x; - int attr; - truecolour tc; - - buf.bufsize = 5120; - buf.bufpos = 0; - buf.textptr = buf.textbuf = snewn(buf.bufsize, wchar_t); - buf.attrptr = buf.attrbuf = snewn(buf.bufsize, int); - buf.tcptr = buf.tcbuf = snewn(buf.bufsize, truecolour); - - old_top_x = top.x; /* needed for rect==1 */ - - while (poslt(top, bottom)) { - bool 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->trusted) { - /* A wrapped line with a trust sigil on it terminates - * a few characters earlier. */ - nlpos.x = (nlpos.x < TRUST_SIGIL_WIDTH ? 0 : - nlpos.x - TRUST_SIGIL_WIDTH); - } - 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; - tc = ldata->chars[x].truecolour; - - 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, tc); - - 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, term->basic_erase_char.truecolour); - } - top.y++; - top.x = rect ? old_top_x : 0; - - unlineptr(ldata); - } -#if SELECTION_NUL_TERMINATED - clip_addchar(&buf, 0, 0, term->basic_erase_char.truecolour); -#endif - /* Finally, transfer all that to the clipboard(s). */ - { - int i; - bool clip_local = false; - for (i = 0; i < n_clipboards; i++) { - if (clipboards[i] == CLIP_LOCAL) { - clip_local = true; - } else if (clipboards[i] != CLIP_NULL) { - win_clip_write( - term->win, clipboards[i], buf.textbuf, buf.attrbuf, - buf.tcbuf, buf.bufpos, desel); - } - } - if (clip_local) { - sfree(term->last_selected_text); - sfree(term->last_selected_attr); - sfree(term->last_selected_tc); - term->last_selected_text = buf.textbuf; - term->last_selected_attr = buf.attrbuf; - term->last_selected_tc = buf.tcbuf; - term->last_selected_len = buf.bufpos; - } else { - sfree(buf.textbuf); - sfree(buf.attrbuf); - sfree(buf.tcbuf); - } - } -} - -void term_copyall(Terminal *term, const int *clipboards, int n_clipboards) -{ - 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, false, true, clipboards, n_clipboards); -} - -static void paste_from_clip_local(void *vterm) -{ - Terminal *term = (Terminal *)vterm; - term_do_paste(term, term->last_selected_text, term->last_selected_len); -} - -void term_request_copy(Terminal *term, const int *clipboards, int n_clipboards) -{ - int i; - for (i = 0; i < n_clipboards; i++) { - assert(clipboards[i] != CLIP_LOCAL); - if (clipboards[i] != CLIP_NULL) { - win_clip_write(term->win, clipboards[i], - term->last_selected_text, term->last_selected_attr, - term->last_selected_tc, term->last_selected_len, - false); - } - } -} - -void term_request_paste(Terminal *term, int clipboard) -{ - switch (clipboard) { - case CLIP_NULL: - /* Do nothing: CLIP_NULL never has data in it. */ - break; - case CLIP_LOCAL: - queue_toplevel_callback(paste_from_clip_local, term); - break; - default: - win_clip_request_paste(term->win, clipboard); - break; - } -} - -/* - * 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; -} - -static int line_cols(Terminal *term, termline *ldata) -{ - int cols = term->cols; - if (ldata->trusted) { - cols -= TRUST_SIGIL_WIDTH; - } - if (ldata->lattr & LATTR_WRAPPED2) - cols--; - if (cols < 0) - cols = 0; - return cols; -} - -/* - * 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 + line_cols(term, ldata); - 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 = line_cols(term, ldata); - 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 = line_cols(term, ldata2); - 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) { - strbuf *buf = term_input_data_from_unicode( - term, term->paste_buffer + term->paste_pos, n); - term_keyinput_internal(term, buf->s, buf->len, false); - strbuf_free(buf); - } - term->paste_pos += n; - - if (term->paste_pos < term->paste_len) { - queue_toplevel_callback(term_paste_callback, term); - return; - } - } - term_bracketed_paste_stop(term); - sfree(term->paste_buffer); - term->paste_buffer = NULL; - term->paste_len = 0; -} - -/* - * Specialist string compare function. Returns true if the buffer of - * alen wide characters starting at a has as a prefix the buffer of - * blen characters starting at b. - */ -static bool wstartswith(const wchar_t *a, size_t alen, - const wchar_t *b, size_t blen) -{ - return alen >= blen && !wcsncmp(a, b, blen); -} - -void term_do_paste(Terminal *term, const wchar_t *data, int len) -{ - const wchar_t *p; - bool paste_controls = conf_get_bool(term->conf, CONF_paste_controls); - - /* - * Pasting data into the terminal counts as a keyboard event (for - * purposes of the 'Reset scrollback on keypress' config option), - * unless the paste is zero-length. - */ - if (len == 0) - return; - term_seen_key_event(term); - - 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) - term_bracketed_paste_start(term); - - p = data; - while (p < data + len) { - wchar_t wc = *p++; - - if (wc == sel_nl[0] && - wstartswith(p-1, data+len-(p-1), sel_nl, sel_nl_sz)) { - /* - * This is the (platform-dependent) sequence that the host - * OS uses to represent newlines in clipboard data. - * Normalise it to a press of CR. - */ - p += sel_nl_sz - 1; - wc = '\015'; - } - - if ((wc & ~(wint_t)0x9F) == 0) { - /* - * This is a control code, either in the range 0x00-0x1F - * or 0x80-0x9F. We reject all of these in pastecontrols - * mode, except for a small set of permitted ones. - */ - if (!paste_controls) { - /* In line with xterm 292, accepted control chars are: - * CR, LF, tab, backspace. (And DEL, i.e. 0x7F, but - * that's permitted by virtue of not matching the bit - * mask that got us into this if statement, so we - * don't have to permit it here. */ - static const unsigned mask = - (1<<13) | (1<<10) | (1<<9) | (1<<8); - - if (wc > 15 || !((mask >> wc) & 1)) - continue; - } - - if (wc == '\033' && term->bracketed_paste && - wstartswith(p-1, data+len-(p-1), L"\033[201~", 6)) { - /* - * Also, in bracketed-paste mode, reject the ESC - * character that begins the end-of-paste sequence. - */ - continue; - } - } - - term->paste_buffer[term->paste_len++] = wc; - } - - /* Assume a small paste will be OK in one go. */ - if (term->paste_len < 256) { - if (term->ldisc) { - strbuf *buf = term_input_data_from_unicode( - term, term->paste_buffer, term->paste_len); - term_keyinput_internal(term, buf->s, buf->len, false); - strbuf_free(buf); - } - if (term->paste_buffer) - sfree(term->paste_buffer); - term_bracketed_paste_stop(term); - term->paste_buffer = NULL; - term->paste_pos = term->paste_len = 0; - } - - 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, bool shift, bool ctrl, bool alt) -{ - pos selpoint; - termline *ldata; - bool raw_mouse = (term->xterm_mouse && - !term->no_mouse_rep && - !(term->mouse_override && shift)); - int default_seltype; - - // Don't do anything if mouse movement events weren't requested; - // Note: return early to avoid doing all of this code on every mouse move - // event only to throw it away. - if (a == MA_MOVE && (!raw_mouse || term->xterm_mouse < 3)) { - return; - } - - 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 && !raw_mouse && term->seltype != RECTANGULAR) { - /* - * When we're using the mouse for normal raster-based - * selection, dragging off the left edge of a terminal row - * is treated the same as the right-hand end of the - * previous row, in that it's considered to identify a - * point _before_ the first character on row y. - * - * But if the mouse action is going to be used for - * anything else - rectangular selection, or xterm mouse - * tracking - then we disable this special treatment. - */ - 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; - bool 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; - case MBT_WHEEL_LEFT: - encstate = 0x42; - wheel = true; - break; - case MBT_WHEEL_RIGHT: - encstate = 0x43; - wheel = true; - break; - case MBT_NOTHING: - assert( a == MA_MOVE ); - encstate = 0x03; // release; no buttons pressed - wheel = false; - 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; // motion indicator - break; - case MA_MOVE: // mouse move without buttons - assert( braw == MBT_NOTHING && bcooked == MBT_NOTHING ); - if (term->xterm_mouse < 3) - return; - - if (selpoint.x == term->raw_mouse_reported_x && - selpoint.y == term->raw_mouse_reported_y) - return; - - term->raw_mouse_reported_x = x; - term->raw_mouse_reported_y = y; - - encstate += 0x20; // motion indicator - 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); - } - if (len > 0) - ldisc_send(term->ldisc, abuf, len, false); - } - 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 (a == MA_DRAG && - (term->selstate == NO_SELECTION || term->selstate == SELECTED)) { - /* - * This can happen if a front end has passed us a MA_DRAG - * without a prior MA_CLICK. OS X GTK does so, for - * example, if the initial button press was eaten by the - * WM when it activated the window in the first place. The - * nicest thing to do in this situation is to ignore - * further drags, and wait for the user to click in the - * window again properly if they want to select. - */ - return; - } - 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->mouse_select_clipboards, - term->n_mouse_select_clipboards); - 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 - )) { - term_request_paste(term, term->mouse_paste_clipboard); - } - - /* - * Since terminal output is suppressed during drag-selects, we - * should make sure to write any pending output if one has just - * finished. - */ - term_out(term, false); - term_schedule_update(term); -} - -void term_cancel_selection_drag(Terminal *term) -{ - /* - * In unusual circumstances, a mouse drag might be interrupted by - * something that steals the rest of the mouse gesture. An example - * is the GTK popup menu appearing. In that situation, we'll never - * receive the MA_RELEASE that finishes the DRAGGING state, which - * means terminal output could be suppressed indefinitely. Call - * this function from the front end in such situations to restore - * sensibleness. - */ - if (term->selstate == DRAGGING) - term->selstate = NO_SELECTION; - term_out(term, false); - term_schedule_update(term); -} - -static int shift_bitmap(bool shift, bool ctrl, bool alt, bool *consumed_alt) -{ - int bitmap = (shift ? 1 : 0) + (alt ? 2 : 0) + (ctrl ? 4 : 0); - if (bitmap) - bitmap++; - if (alt && consumed_alt) - *consumed_alt = true; - return bitmap; -} - -int format_arrow_key(char *buf, Terminal *term, int xkey, - bool shift, bool ctrl, bool alt, bool *consumed_alt) -{ - char *p = buf; - - if (term->vt52_mode) - p += sprintf(p, "\x1B%c", xkey); - else { - bool 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 - - int bitmap = 0; - - /* Adjustment based on Shift, Ctrl and/or Alt */ - switch (term->sharrow_type) { - case SHARROW_APPLICATION: - if (ctrl) - app_flg = !app_flg; - break; - case SHARROW_BITMAP: - bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt); - break; - } - - if (app_flg) - p += sprintf(p, "\x1BO%c", xkey); - else if (bitmap) - p += sprintf(p, "\x1B[1;%d%c", bitmap, xkey); - else - p += sprintf(p, "\x1B[%c", xkey); - } - - return p - buf; -} - -int format_function_key(char *buf, Terminal *term, int key_number, - bool shift, bool ctrl, bool alt, bool *consumed_alt) -{ - char *p = buf; - - static const int key_number_to_tilde_code[] = { - -1, /* no such key as F0 */ - 11, 12, 13, 14, 15, /*gap*/ 17, 18, 19, 20, 21, /*gap*/ - 23, 24, 25, 26, /*gap*/ 28, 29, /*gap*/ 31, 32, 33, 34, - }; - - assert(key_number > 0); - assert(key_number < lenof(key_number_to_tilde_code)); - - int index = key_number; - if (term->funky_type != FUNKY_XTERM_216 && term->funky_type != FUNKY_SCO) { - if (shift && index <= 10) { - shift = false; - index += 10; - } - } - - int code = key_number_to_tilde_code[index]; - - if (term->funky_type == FUNKY_SCO) { - /* SCO function keys */ - static const char sco_codes[] = - "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{"; - index = (key_number >= 1 && key_number <= 12) ? key_number - 1 : 0; - if (shift) index += 12; - if (ctrl) index += 24; - p += sprintf(p, "\x1B[%c", sco_codes[index]); - } else if ((term->vt52_mode || term->funky_type == FUNKY_VT100P) && - code >= 11 && code <= 24) { - int offt = 0; - if (code > 15) - offt++; - if (code > 21) - offt++; - if (term->vt52_mode) - p += sprintf(p, "\x1B%c", code + 'P' - 11 - offt); - else - p += sprintf(p, "\x1BO%c", code + 'P' - 11 - offt); - } else if (term->funky_type == FUNKY_LINUX && code >= 11 && code <= 15) { - p += sprintf(p, "\x1B[[%c", code + 'A' - 11); - } else if ((term->funky_type == FUNKY_XTERM || - term->funky_type == FUNKY_XTERM_216) && - code >= 11 && code <= 14) { - if (term->vt52_mode) - p += sprintf(p, "\x1B%c", code + 'P' - 11); - else { - int bitmap = 0; - if (term->funky_type == FUNKY_XTERM_216) - bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt); - if (bitmap) - p += sprintf(p, "\x1B[1;%d%c", bitmap, code + 'P' - 11); - else - p += sprintf(p, "\x1BO%c", code + 'P' - 11); - } - } else { - int bitmap = 0; - if (term->funky_type == FUNKY_XTERM_216) - bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt); - if (bitmap) - p += sprintf(p, "\x1B[%d;%d~", code, bitmap); - else - p += sprintf(p, "\x1B[%d~", code); - } - - return p - buf; -} - -int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key, - bool shift, bool ctrl, bool alt, - bool *consumed_alt) -{ - char *p = buf; - - int code; - switch (key) { - case SKK_HOME: code = 1; break; - case SKK_INSERT: code = 2; break; - case SKK_DELETE: code = 3; break; - case SKK_END: code = 4; break; - case SKK_PGUP: code = 5; break; - case SKK_PGDN: code = 6; break; - default: unreachable("bad small keypad key enum value"); - } - - /* Reorder edit keys to physical order */ - if (term->funky_type == FUNKY_VT400 && code <= 6) - code = "\0\2\1\4\5\3\6"[code]; - - if (term->vt52_mode && code > 0 && code <= 6) { - p += sprintf(p, "\x1B%c", " HLMEIG"[code]); - } else if (term->funky_type == FUNKY_SCO) { - static const char codes[] = "HL.FIG"; - if (code == 3) { - *p++ = '\x7F'; - } else { - p += sprintf(p, "\x1B[%c", codes[code-1]); - } - } else if ((code == 1 || code == 4) && term->rxvt_homeend) { - p += sprintf(p, code == 1 ? "\x1B[H" : "\x1BOw"); - } else { - if (term->vt52_mode) { - p += sprintf(p, "\x1B[%d~", code); - } else { - int bitmap = 0; - if (term->funky_type == FUNKY_XTERM_216) - bitmap = shift_bitmap(shift, ctrl, alt, consumed_alt); - if (bitmap) - p += sprintf(p, "\x1B[%d;%d~", code, bitmap); - else - p += sprintf(p, "\x1B[%d~", code); - } - } - - return p - buf; -} - -int format_numeric_keypad_key(char *buf, Terminal *term, char key, - bool shift, bool ctrl) -{ - char *p = buf; - bool app_keypad = (term->app_keypad_keys && !term->no_applic_k); - - if (term->nethack_keypad && (key >= '1' && key <= '9')) { - static const char nh_base[] = "bjnh.lyku"; - char c = nh_base[key - '1']; - if (ctrl && c != '.') - c &= 0x1F; - else if (shift && c != '.') - c += 'A'-'a'; - *p++ = c; - } else { - int xkey = 0; - - if (term->funky_type == FUNKY_VT400 || - (term->funky_type <= FUNKY_LINUX && app_keypad)) { - switch (key) { - case 'G': xkey = 'P'; break; - case '/': xkey = 'Q'; break; - case '*': xkey = 'R'; break; - case '-': xkey = 'S'; break; - } - } - - if (app_keypad) { - switch (key) { - case '0': xkey = 'p'; break; - case '1': xkey = 'q'; break; - case '2': xkey = 'r'; break; - case '3': xkey = 's'; break; - case '4': xkey = 't'; break; - case '5': xkey = 'u'; break; - case '6': xkey = 'v'; break; - case '7': xkey = 'w'; break; - case '8': xkey = 'x'; break; - case '9': xkey = 'y'; break; - case '.': xkey = 'n'; break; - case '\r': xkey = 'M'; break; - - case '+': - /* - * 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... - */ - if (term->funky_type == FUNKY_XTERM) - xkey = shift ? 'l' : 'k'; - else - xkey = shift ? 'm' : 'l'; - break; - - case '/': - if (term->funky_type == FUNKY_XTERM) - xkey = 'o'; - break; - case '*': - if (term->funky_type == FUNKY_XTERM) - xkey = 'j'; - break; - case '-': - if (term->funky_type == FUNKY_XTERM) - xkey = 'm'; - break; - } - } - - if (xkey) { - if (term->vt52_mode) { - if (xkey >= 'P' && xkey <= 'S') - p += sprintf(p, "\x1B%c", xkey); - else - p += sprintf(p, "\x1B?%c", xkey); - } else - p += sprintf(p, "\x1BO%c", xkey); - } - } - - return p - buf; -} - -void term_keyinputw(Terminal *term, const wchar_t *widebuf, int len) -{ - strbuf *buf = term_input_data_from_unicode(term, widebuf, len); - if (buf->len) - term_keyinput_internal(term, buf->s, buf->len, true); - strbuf_free(buf); -} - -void term_keyinput(Terminal *term, int codepage, const void *str, int len) -{ - if (codepage < 0 || codepage == term->ucsdata->line_codepage) { - /* - * This text needs no translation, either because it's already - * in the right character set, or because we got the special - * codepage value -1 from our caller which means 'this data - * should be charset-agnostic, just send it raw' (for really - * simple things like control characters). - */ - term_keyinput_internal(term, str, len, true); - } else { - strbuf *buf = term_input_data_from_charset(term, codepage, str, len); - if (buf->len) - term_keyinput_internal(term, buf->s, buf->len, true); - strbuf_free(buf); - } -} - -void term_nopaste(Terminal *term) -{ - if (term->paste_len == 0) - return; - sfree(term->paste_buffer); - term_bracketed_paste_stop(term); - 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_lost_clipboard_ownership(Terminal *term, int clipboard) -{ - if (!(term->n_mouse_select_clipboards > 1 && - clipboard == term->mouse_select_clipboards[1])) - return; - - 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. - */ - term_out(term, false); -} - -static void term_added_data(Terminal *term, bool called_from_term_data) -{ - if (!term->in_term_out) { - term->in_term_out = true; - term_out(term, called_from_term_data); - term->in_term_out = false; - } -} - -size_t term_data(Terminal *term, const void *data, size_t len) -{ - bufchain_add(&term->inbuf, data, len); - term_added_data(term, true); - return bufchain_size(&term->inbuf); -} - -void term_provide_logctx(Terminal *term, LogContext *logctx) -{ - term->logctx = logctx; -} - -void term_set_focus(Terminal *term, bool 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) -{ - const char *val = NULL; - if (strcmp(mode, "ERASE") == 0) { - val = term->bksp_is_delete ? "^?" : "^H"; - } else if (strcmp(mode, "IUTF8") == 0) { - val = (term->ucsdata->line_codepage == CP_UTF8) ? "yes" : "no"; - } - /* 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 { - prompts_t *prompts; - size_t curr_prompt; - enum TermUserpassPromptState { - TUS_INITIAL, /* haven't even printed the prompt yet */ - TUS_ACTIVE, /* prompt is currently receiving user input */ - TUS_ABORTED, /* user pressed ^C or ^D to cancel prompt */ - } prompt_state; - Terminal *term; - TermLineEditor *le; - TermLineEditorCallbackReceiver le_rcv; -}; - -static void term_userpass_next_prompt(struct term_userpass_state *s); - -/* - * Signal that a prompts_t is done. This involves sending a - * notification to the caller, and also turning off our own callback - * that listens for more data arriving in the ldisc's input queue. - */ -static inline SeatPromptResult signal_prompts_t(Terminal *term, prompts_t *p, - SeatPromptResult spr) -{ - assert(p->callback && "Asynchronous userpass input requires a callback"); - queue_toplevel_callback(p->callback, p->callback_ctx); - if (term->ldisc) - ldisc_provide_userpass_le(term->ldisc, NULL); - p->spr = spr; - if (p->data) { - term_userpass_state_free(p->data); - p->data = NULL; - } - return spr; -} - -/* Tiny wrapper to make it easier to write lots of little strings */ -static inline void term_write(Terminal *term, ptrlen data) -{ - term_data(term, data.ptr, data.len); -} - -static void term_lineedit_to_terminal( - TermLineEditorCallbackReceiver *rcv, ptrlen data) -{ - struct term_userpass_state *s = container_of( - rcv, struct term_userpass_state, le_rcv); - prompt_t *pr = s->prompts->prompts[s->curr_prompt]; - if (pr->echo) - term_write(s->term, data); -} - -static void term_lineedit_to_backend( - TermLineEditorCallbackReceiver *rcv, ptrlen data) -{ - struct term_userpass_state *s = container_of( - rcv, struct term_userpass_state, le_rcv); - prompt_t *pr = s->prompts->prompts[s->curr_prompt]; - put_datapl(pr->result, data); -} - -static void term_lineedit_newline(TermLineEditorCallbackReceiver *rcv) -{ - struct term_userpass_state *s = container_of( - rcv, struct term_userpass_state, le_rcv); - - prompt_t *pr = s->prompts->prompts[s->curr_prompt]; - if (!pr->echo) { - /* If echo is disabled, we won't have printed the newline in - * term_lineedit_to_terminal, so print it now */ - term_write(s->term, PTRLEN_LITERAL("\x0D\x0A")); - } - - ldisc_provide_userpass_le(s->term->ldisc, NULL); - s->curr_prompt++; - s->prompt_state = TUS_INITIAL; - term_userpass_next_prompt(s); -} - -static void term_lineedit_special( - TermLineEditorCallbackReceiver *rcv, SessionSpecialCode code, int arg) -{ - struct term_userpass_state *s = container_of( - rcv, struct term_userpass_state, le_rcv); - switch (code) { - case SS_IP: - case SS_EOF: - ldisc_provide_userpass_le(s->term->ldisc, NULL); - s->prompt_state = TUS_ABORTED; - signal_prompts_t(s->term, s->prompts, SPR_USER_ABORT); - default: - break; - } -} - -static const TermLineEditorCallbackReceiverVtable -term_userpass_lineedit_receiver_vt = { - .to_terminal = term_lineedit_to_terminal, - .to_backend = term_lineedit_to_backend, - .special = term_lineedit_special, - .newline = term_lineedit_newline, -}; - -static struct term_userpass_state *term_userpass_state_new( - Terminal *term, prompts_t *prompts) -{ - struct term_userpass_state *s = snew(struct term_userpass_state); - s->prompts = prompts; - s->curr_prompt = 0; - s->prompt_state = TUS_INITIAL; - s->term = term; - s->le_rcv.vt = &term_userpass_lineedit_receiver_vt; - s->le = lineedit_new(term, LE_INTERRUPT | LE_EOF_ALWAYS | LE_ESC_ERASES, - &s->le_rcv); - assert(!term->userpass_state); - term->userpass_state = s; - return s; -} - -static void term_userpass_state_free(struct term_userpass_state *s) -{ - assert(s->term->userpass_state == s); - s->term->userpass_state = NULL; - lineedit_free(s->le); - sfree(s); -} - -static void term_userpass_next_prompt(struct term_userpass_state *s) -{ - if (s->prompt_state != TUS_INITIAL) - return; - if (s->curr_prompt < s->prompts->n_prompts) { - prompt_t *pr = s->prompts->prompts[s->curr_prompt]; - term_write(s->term, ptrlen_from_asciz(pr->prompt)); - s->prompt_state = TUS_ACTIVE; - ldisc_provide_userpass_le(s->term->ldisc, s->le); - } else { - /* This triggers the callback provided by the userpass client, - * which will call term_userpass_state to fetch the result - * we're storing here */ - signal_prompts_t(s->term, s->prompts, SPR_OK); - } -} - -static bool terminal_use_utf8 = true; -bool set_legacy_charset_handling(bool newvalue) -{ - terminal_use_utf8 = !newvalue; - return true; -} - -/* - * Process some terminal data in the course of username/password - * input. - */ -SeatPromptResult term_get_userpass_input(Terminal *term, prompts_t *p) -{ - if (!term->ldisc) { - /* Can't handle interactive prompts without an ldisc */ - return signal_prompts_t(term, p, SPR_SW_ABORT( - "Terminal not prepared for interactive prompts")); - } - - if (p->spr.kind != SPRK_INCOMPLETE) { - /* We've already finished these prompts, so return the same - * result again */ - return p->spr; - } - - struct term_userpass_state *s = (struct term_userpass_state *)p->data; - - if (!s) { - /* - * First call. Set some stuff up. - */ - p->data = s = term_userpass_state_new(term, p); - p->spr = SPR_INCOMPLETE; - term->userpass_utf8_override = p->utf8 && terminal_use_utf8; - /* We only print the `name' caption if we have to... */ - if (p->name_reqd && p->name) { - ptrlen plname = ptrlen_from_asciz(p->name); - term_write(term, plname); - if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) - term_write(term, PTRLEN_LITERAL("\r\n")); - } - /* ...but we always print any `instruction'. */ - if (p->instruction) { - ptrlen plinst = ptrlen_from_asciz(p->instruction); - term_write(term, plinst); - if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) - term_write(term, PTRLEN_LITERAL("\r\n")); - } - /* - * 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], ""); - } - /* And print the first prompt. */ - term_userpass_next_prompt(s); - } - - return SPR_INCOMPLETE; -} - -void term_notify_minimised(Terminal *term, bool minimised) -{ - term->minimised = minimised; -} - -void term_notify_palette_changed(Terminal *term) -{ - palette_reset(term, true); -} - -void term_notify_window_pos(Terminal *term, int x, int y) -{ - term->winpos_x = x; - term->winpos_y = y; -} - -void term_notify_window_size_pixels(Terminal *term, int x, int y) -{ - term->winpixsize_x = x; - term->winpixsize_y = y; -} diff --git a/terminal/terminal.h b/terminal/terminal.h deleted file mode 100644 index 056e770ce..000000000 --- a/terminal/terminal.h +++ /dev/null @@ -1,616 +0,0 @@ -/* - * Internals of the Terminal structure, used by other modules in the - * terminal subdirectory and by test suites. - */ - -#ifndef PUTTY_TERMINAL_H -#define PUTTY_TERMINAL_H - -#include "tree234.h" - -struct beeptime { - struct beeptime *next; - unsigned long ticks; -}; - -#define TRUST_SIGIL_WIDTH 3 -#define TRUST_SIGIL_CHAR 0xDFFE - -typedef struct { - int y, x; -} pos; - -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; - truecolour truecolour; - - /* - * 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) */ - bool temporary; /* true if decompressed from scrollback */ - int cc_free; /* offset to first cc in free list */ - struct termchar *chars; - bool trusted; -}; - -struct bidi_cache_entry { - int width; - bool trusted; - struct termchar *chars; - int *forward, *backward; /* the permutations of line positions */ -}; - -struct term_utf8_decode { - int state; /* Is there a pending UTF-8 character */ - int chr; /* and what is it so far? */ - int size; /* The size of the UTF character. */ -}; - -struct term_userpass_state; - -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; - bool beep_overloaded; - long lastbeep; - -#define TTYPE termchar -#define TSIZE (sizeof(TTYPE)) - - int default_attr, curr_attr, save_attr; - truecolour curr_truecolour, save_truecolour; - 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 */ - bool dec_om; /* DEC origin mode flag */ - bool wrap, wrapnext; /* wrap flags */ - bool insert; /* insert-mode flag */ - int cset; /* 0 or 1: which char set */ - int save_cset, save_csattr; /* saved with cursor position */ - bool save_utf, save_wnext; /* saved with cursor position */ - bool rvideo; /* global reverse video flag */ - unsigned long rvbell_startpoint; /* for ESC[?5hESC[?5l vbell */ - bool cursor_on; /* cursor enabled flag */ - bool reset_132; /* Flag ESC c resets to 80 cols */ - bool use_bce; /* Use Background coloured erase */ - bool cblinker; /* When blinking is the cursor on ? */ - bool tblinker; /* When the blinking text is on */ - bool blink_is_real; /* Actually blink blinking text */ - int sco_acs, save_sco_acs; /* CSI 10,11,12m -> OEM charset */ - bool vt52_bold; /* Force bold on non-bold colours */ - bool utf; /* Are we in toggleable UTF-8 mode? */ - term_utf8_decode utf8; /* If so, here's our decoding state */ - bool 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; - truecolour alt_save_truecolour; - int alt_save_cset, alt_save_csattr; - bool alt_save_utf; - bool alt_save_wnext; - int alt_save_sco_acs; - - int rows, cols, savelines; - bool has_focus; - bool in_vbell; - long vbell_end; - bool app_cursor_keys, app_keypad_keys, vt52_mode; - bool repeat_off, srm_echo, cr_lf_return; - bool big_cursor; - - bool xterm_mouse_forbidden; - int xterm_mouse; /* send mouse messages to host */ - bool xterm_extended_mouse; - bool urxvt_extended_mouse; - int mouse_is_down; /* used while tracking mouse buttons */ - int raw_mouse_reported_x; - int raw_mouse_reported_y; - - bool bracketed_paste, bracketed_paste_active; - - int cset_attr[2]; - -/* - * Saved settings on the alternate screen. - */ - int alt_x, alt_y; - bool alt_wnext, alt_ins; - bool alt_om, alt_wrap; - int alt_cset, alt_sco_acs; - bool 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)*256)) -#define ANSI_QUE(x) ANSI(x,1) - -#define OSC_STR_MAX 2048 - bool osc_is_apc; - int osc_strlen; - char osc_string[OSC_STR_MAX + 1]; - bool 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, OSC_MAYBE_ST_UTF8, - 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; - - Backend *backend; - - Ldisc *ldisc; - - TermWin *win; - - LogContext *logctx; - - struct unicode_data *ucsdata; - - unsigned long last_graphic_char; - - /* - * 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; - - /* - * GUI implementations of seat_output call 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 - * seat_output 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. - */ - bool in_term_out; - - /* - * We don't permit window updates too close together, to avoid CPU - * churn pointlessly redrawing the window faster than the user can - * read. So after an update, we set window_update_cooldown = true - * and schedule a timer to reset it to false. In between those - * times, window updates are not performed, and instead we set - * window_update_pending = true, which will remind us to perform - * the deferred redraw when the cooldown period ends and - * window_update_cooldown is reset to false. - */ - bool window_update_pending, window_update_cooldown; - long window_update_cooldown_end; - - /* - * Track pending blinks and tblinks. - */ - bool 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; - size_t bidi_cache_size; - - /* - * Current trust state, used to annotate every line of the - * terminal that a graphic character is output to. - */ - bool trusted; - - /* - * 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. - */ - bool ansi_colour; - strbuf *answerback; - bool no_arabicshaping; - int beep; - bool bellovl; - int bellovl_n; - int bellovl_s; - int bellovl_t; - bool no_bidi; - bool bksp_is_delete; - bool blink_cur; - bool blinktext; - bool cjk_ambig_wide; - int conf_height; - int conf_width; - bool crhaslf; - bool erase_to_scrollback; - int funky_type, sharrow_type; - bool lfhascr; - bool logflush; - int logtype; - bool mouse_override; - bool nethack_keypad; - bool no_alt_screen; - bool no_applic_c; - bool no_applic_k; - bool no_dbackspace; - bool no_mouse_rep; - bool no_remote_charset; - bool no_remote_resize; - bool no_remote_wintitle; - bool no_remote_clearscroll; - bool rawcnp; - bool utf8linedraw; - bool rect_select; - int remote_qtitle_action; - bool rxvt_homeend; - bool scroll_on_disp; - bool scroll_on_key; - bool xterm_256_colour; - bool true_colour; - - wchar_t *last_selected_text; - int *last_selected_attr; - truecolour *last_selected_tc; - size_t last_selected_len; - int mouse_select_clipboards[N_CLIPBOARDS]; - int n_mouse_select_clipboards; - int mouse_paste_clipboard; - - char *window_title, *icon_title; - int wintitle_codepage, icontitle_codepage; - bool minimised; - - BidiContext *bidi_ctx; - - /* Multi-layered colour palette. The colours from Conf (plus the - * default xterm-256 ones that don't have Conf ids at all) have - * lowest priority, followed by platform overrides if any, - * followed by escape-sequence overrides during the session. */ - struct term_subpalette { - rgb values[OSC4_NCOLOURS]; - bool present[OSC4_NCOLOURS]; - } subpalettes[3]; -#define SUBPAL_CONF 0 -#define SUBPAL_PLATFORM 1 -#define SUBPAL_SESSION 2 - - /* The composite palette that we make out of the above */ - rgb palette[OSC4_NCOLOURS]; - - unsigned winpos_x, winpos_y, winpixsize_x, winpixsize_y; - - /* - * Assorted 'pending' flags for ancillary window changes performed - * in term_update. Generally, to trigger one of these operations, - * you set the pending flag and/or the parameters here, then call - * term_schedule_update. - */ - bool win_move_pending; - int win_move_pending_x, win_move_pending_y; - bool win_zorder_pending; - bool win_zorder_top; - bool win_minimise_pending; - bool win_minimise_enable; - bool win_maximise_pending; - bool win_maximise_enable; - bool win_title_pending, win_icon_title_pending; - bool win_pointer_shape_pending; - bool win_pointer_shape_raw; - bool win_refresh_pending; - bool win_scrollbar_update_pending; - bool win_palette_pending; - unsigned win_palette_pending_min, win_palette_pending_limit; - - /* - * Unlike the rest of the above 'pending' flags, the one for - * window resizing has to be more complicated, because it's very - * likely that a server sending a window-resize escape sequence is - * going to follow it up immediately with further terminal output - * that draws a full-screen application expecting the terminal to - * be the new size. - * - * So, once we've requested a window resize from the TermWin, we - * have to stop processing terminal data until we get back the - * notification that our window really has changed size (or until - * we find out that it's not going to). - * - * Hence, window resizes go through a small state machine with two - * different kinds of 'pending'. NEED_SEND is the state where - * we've received an escape sequence asking for a new size but not - * yet sent it to the TermWin via win_request_resize; AWAIT_REPLY - * is the state where we've sent it to the TermWin and are - * expecting a call back to term_size(). - * - * So _both_ of those 'pending' states inhibit terminal output - * processing. - * - * (Hence, once we're in either state, we should never handle - * another resize sequence, so the only possible path through this - * state machine is to get all the way back to the ground state - * before doing anything else interesting.) - */ - enum { - WIN_RESIZE_NO, WIN_RESIZE_NEED_SEND, WIN_RESIZE_AWAIT_REPLY - } win_resize_pending; - int win_resize_pending_w, win_resize_pending_h; - - /* - * Indicates whether term_get_userpass_input is currently using - * the terminal to present a password prompt or similar, and if - * so, whether it's overridden the terminal into UTF-8 mode. - */ - struct term_userpass_state *userpass_state; - bool userpass_utf8_override; -}; - -static inline bool in_utf(Terminal *term) -{ - return (term->utf || - term->ucsdata->line_codepage == CP_UTF8 || - (term->userpass_state && term->userpass_utf8_override)); -} - -unsigned long term_translate( - Terminal *term, term_utf8_decode *utf8, unsigned char c); -static inline int term_char_width(Terminal *term, unsigned int c) -{ - return term->cjk_ambig_wide ? mk_wcwidth_cjk(c) : mk_wcwidth(c); -} - -/* - * UCSINCOMPLETE is returned from term_translate if it's successfully - * absorbed a byte but not emitted a complete character yet. - * UCSTRUNCATED indicates a truncated multibyte sequence (so the - * caller emits an error character and then calls term_translate again - * with the same input byte). UCSINVALID indicates some other invalid - * multibyte sequence, such as an overlong synonym, or a standalone - * continuation byte, or a completely illegal thing like 0xFE. These - * values are not stored in the terminal data structures at all. - */ -#define UCSINCOMPLETE 0x8000003FU /* '?' */ -#define UCSTRUNCATED 0x80000021U /* '!' */ -#define UCSINVALID 0x8000002AU /* '*' */ - -/* - * Maximum number of combining characters we're willing to store in a - * character cell. Our linked-list data representation permits an - * unlimited number of these in principle, but if we allowed that in - * practice then it would be an easy DoS to just squirt a squillion - * identical combining characters to someone's terminal and cause - * their PuTTY or pterm to consume lots of memory and CPU pointlessly. - * - * The precise figure of 32 is more or less arbitrary, but one point - * supporting it is UAX #15's comment that 30 combining characters is - * "significantly beyond what is required for any linguistic or - * technical usage". - */ -#define CC_LIMIT 32 - -/* ---------------------------------------------------------------------- - * Helper functions for dealing with the small 'pos' structure. - */ - -static inline bool poslt(pos p1, pos p2) -{ - if (p1.y != p2.y) - return p1.y < p2.y; - return p1.x < p2.x; -} - -static inline bool posle(pos p1, pos p2) -{ - if (p1.y != p2.y) - return p1.y < p2.y; - return p1.x <= p2.x; -} - -static inline bool poseq(pos p1, pos p2) -{ - return p1.y == p2.y && p1.x == p2.x; -} - -static inline int posdiff_fn(pos p1, pos p2, int cols) -{ - return (p1.y - p2.y) * (cols+1) + (p1.x - p2.x); -} - -/* Convenience wrapper on posdiff_fn which uses the 'Terminal *term' - * that more or less every function in terminal.c will have in scope. - * For safety's sake I include a TYPECHECK that ensures it really is a - * structure pointer of the right type. */ -#define GET_TERM_COLS TYPECHECK(term == (Terminal *)0, term->cols) -#define posdiff(p1,p2) posdiff_fn(p1, p2, GET_TERM_COLS) - -/* Product-order comparisons for rectangular block selection. */ - -static inline bool posPle(pos p1, pos p2) -{ - return p1.y <= p2.y && p1.x <= p2.x; -} - -static inline bool posPle_left(pos p1, pos p2) -{ - /* - * This function is used for checking whether a given character - * cell of the terminal ought to be highlighted as part of the - * selection, by comparing with term->selend. term->selend stores - * the location one space to the right of the last highlighted - * character. So we want to highlight the characters that are - * less-or-equal (in the product order) to the character just left - * of p2. - * - * (Setting up term->selend that way was the easiest way to get - * rectangular selection working at all, in a code base that had - * done lexicographic selection the way I happened to have done - * it.) - */ - return p1.y <= p2.y && p1.x < p2.x; -} - -static inline bool incpos_fn(pos *p, int cols) -{ - if (p->x == cols) { - p->x = 0; - p->y++; - return true; - } - p->x++; - return false; -} - -static inline bool decpos_fn(pos *p, int cols) -{ - if (p->x == 0) { - p->x = cols; - p->y--; - return true; - } - p->x--; - return false; -} - -/* Convenience wrappers on incpos and decpos which use term->cols - * (similarly to posdiff above), and also (for mild convenience and - * mostly historical inertia) let you leave off the & at every call - * site. */ -#define incpos(p) incpos_fn(&(p), GET_TERM_COLS) -#define decpos(p) decpos_fn(&(p), GET_TERM_COLS) - -struct TermLineEditorCallbackReceiverVtable { - void (*to_terminal)(TermLineEditorCallbackReceiver *rcv, ptrlen data); - void (*to_backend)(TermLineEditorCallbackReceiver *rcv, ptrlen data); - void (*special)(TermLineEditorCallbackReceiver *rcv, - SessionSpecialCode code, int arg); - void (*newline)(TermLineEditorCallbackReceiver *rcv); -}; -struct TermLineEditorCallbackReceiver { - const TermLineEditorCallbackReceiverVtable *vt; -}; -TermLineEditor *lineedit_new(Terminal *term, unsigned flags, - TermLineEditorCallbackReceiver *receiver); -void lineedit_free(TermLineEditor *le); -void lineedit_input(TermLineEditor *le, char ch, bool dedicated); -void lineedit_modify_flags(TermLineEditor *le, unsigned clr, unsigned flip); -void lineedit_send_line(TermLineEditor *le); - -/* - * Flags controlling the behaviour of TermLineEditor. - */ -#define LINEEDIT_FLAGS(X) \ - X(LE_INTERRUPT) /* pass SS_IP back to client on ^C */ \ - X(LE_SUSPEND) /* pass SS_SUSP back to client on ^Z */ \ - X(LE_ABORT) /* pass SS_ABORT back to client on ^\ */ \ - X(LE_EOF_ALWAYS) /* pass SS_EOF to client on *any* ^Z - * (X(not)just if the line buffer is empty) */ \ - X(LE_ESC_ERASES) /* make ESC erase the line, as well as ^U */ \ - X(LE_CRLF_NEWLINE) /* interpret manual ^M^J the same as Return */ \ - /* end of list */ -enum { - #define ALLOCATE_BIT_POSITION(flag) flag ## _bitpos, - LINEEDIT_FLAGS(ALLOCATE_BIT_POSITION) - #undef ALLOCATE_BIT_POSITION -}; -enum { - #define DEFINE_FLAG_BIT(flag) flag = 1 << flag ## _bitpos, - LINEEDIT_FLAGS(DEFINE_FLAG_BIT) - #undef DEFINE_FLAG_BIT -}; - -termline *term_get_line(Terminal *term, int y); -void term_release_line(termline *line); - -#endif diff --git a/test/agentmulti.py b/test/agentmulti.py deleted file mode 100644 index 019bf2b6c..000000000 --- a/test/agentmulti.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import os -import random -import socket -import sys - -from ssh import * - -def make_connections(n): - connections = [] - - for _ in range(n): - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - s.connect(os.environ["SSH_AUTH_SOCK"]) - connections.append(s) - - return connections - -def use_connection(s, idstring): - print("Trying {}...".format(idstring), end="") - sys.stdout.flush() - - s.send(ssh_string(ssh_byte(SSH2_AGENTC_EXTENSION) + ssh_string( - b"nonexistent-agent-extension@putty.projects.tartarus.org"))) - length = ssh_decode_uint32(s.recv(4)) - assert length < AGENT_MAX_MSGLEN - msg = s.recv(length) - msgtype = msg[0] - msgstring = ( - "SSH_AGENT_EXTENSION_FAILURE" if msgtype == SSH_AGENT_EXTENSION_FAILURE - else "SSH_AGENT_FAILURE" if msgtype == SSH_AGENT_FAILURE - else "type {:d}".format(msgtype)) - print("got", msgstring, "with {:d}-byte payload".format(len(msg)-1)) - -def randomly_use_connections(connections, iterations): - for _ in range(iterations): - index = random.randrange(0, len(connections)) - s = connections[index] - use_connection(connections[index], "#{:d}".format(index)) - -def main(): - parser = argparse.ArgumentParser( - description='Test handling of multiple agent connections.') - parser.add_argument("--nsockets", type=int, default=128, - help="Number of simultaneous connections to make.") - parser.add_argument("--ntries", type=int, default=1024, - help="Number of messages to send in total.") - args = parser.parse_args() - - connections = make_connections(args.nsockets) - randomly_use_connections(connections, args.ntries) - -if __name__ == '__main__': - main() diff --git a/test/agenttest.py b/test/agenttest.py deleted file mode 100644 index 0e063012a..000000000 --- a/test/agenttest.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/python3 - -import sys -import os -import socket -import base64 -import itertools -import collections - -from ssh import * -import agenttestdata - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -test_session_id = b'Test16ByteSessId' -assert len(test_session_id) == 16 -test_message_to_sign = b'test message to sign' - -TestSig2 = collections.namedtuple("TestSig2", "flags sig") - -class Key2(collections.namedtuple("Key2", "comment public sigs openssh")): - def public_only(self): - return Key2(self.comment, self.public, None, None) - - def Add(self): - alg = ssh_decode_string(self.public) - msg = (ssh_byte(SSH2_AGENTC_ADD_IDENTITY) + - ssh_string(alg) + - self.openssh + - ssh_string(self.comment)) - return agent_query(msg) - - verb = "sign" - def Use(self, flags): - msg = (ssh_byte(SSH2_AGENTC_SIGN_REQUEST) + - ssh_string(self.public) + - ssh_string(test_message_to_sign)) - if flags is not None: - msg += ssh_uint32(flags) - rsp = agent_query(msg) - t, rsp = ssh_decode_byte(rsp, True) - assert t == SSH2_AGENT_SIGN_RESPONSE - sig, rsp = ssh_decode_string(rsp, True) - assert len(rsp) == 0 - return sig - - def Del(self): - msg = (ssh_byte(SSH2_AGENTC_REMOVE_IDENTITY) + - ssh_string(self.public)) - return agent_query(msg) - - @staticmethod - def DelAll(): - msg = (ssh_byte(SSH2_AGENTC_REMOVE_ALL_IDENTITIES)) - return agent_query(msg) - - @staticmethod - def List(): - msg = (ssh_byte(SSH2_AGENTC_REQUEST_IDENTITIES)) - rsp = agent_query(msg) - t, rsp = ssh_decode_byte(rsp, True) - assert t == SSH2_AGENT_IDENTITIES_ANSWER - nk, rsp = ssh_decode_uint32(rsp, True) - keylist = [] - for _ in range(nk): - p, rsp = ssh_decode_string(rsp, True) - c, rsp = ssh_decode_string(rsp, True) - keylist.append(Key2(c, p, None, None)) - assert len(rsp) == 0 - return keylist - - @classmethod - def make_examples(cls): - cls.examples = agenttestdata.key2examples(cls, TestSig2) - - def iter_testsigs(self): - for testsig in self.sigs: - if testsig.flags == 0: - yield testsig._replace(flags=None) - yield testsig - - def iter_tests(self): - for testsig in self.iter_testsigs(): - yield ([testsig.flags], - " (flags={})".format(testsig.flags), - testsig.sig) - -class Key1(collections.namedtuple( - "Key1", "comment public challenge response private")): - def public_only(self): - return Key1(self.comment, self.public, None, None, None) - - def Add(self): - msg = (ssh_byte(SSH1_AGENTC_ADD_RSA_IDENTITY) + - self.private + - ssh_string(self.comment)) - return agent_query(msg) - - verb = "decrypt" - def Use(self, challenge): - msg = (ssh_byte(SSH1_AGENTC_RSA_CHALLENGE) + - self.public + - ssh1_mpint(challenge) + - test_session_id + - ssh_uint32(1)) - rsp = agent_query(msg) - t, rsp = ssh_decode_byte(rsp, True) - assert t == SSH1_AGENT_RSA_RESPONSE - assert len(rsp) == 16 - return rsp - - def Del(self): - msg = (ssh_byte(SSH1_AGENTC_REMOVE_RSA_IDENTITY) + - self.public) - return agent_query(msg) - - @staticmethod - def DelAll(): - msg = (ssh_byte(SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES)) - return agent_query(msg) - - @staticmethod - def List(): - msg = (ssh_byte(SSH1_AGENTC_REQUEST_RSA_IDENTITIES)) - rsp = agent_query(msg) - t, rsp = ssh_decode_byte(rsp, True) - assert t == SSH1_AGENT_RSA_IDENTITIES_ANSWER - nk, rsp = ssh_decode_uint32(rsp, True) - keylist = [] - for _ in range(nk): - b, rsp = ssh_decode_uint32(rsp, True) - e, rsp = ssh1_get_mpint(rsp, True) - m, rsp = ssh1_get_mpint(rsp, True) - c, rsp = ssh_decode_string(rsp, True) - keylist.append(Key1(c, ssh_uint32(b)+e+m, None, None, None)) - assert len(rsp) == 0 - return keylist - - @classmethod - def make_examples(cls): - cls.examples = agenttestdata.key1examples(cls) - - def iter_tests(self): - yield [self.challenge], "", self.response - -def agent_query(msg): - msg = ssh_string(msg) - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - s.connect(os.environ["SSH_AUTH_SOCK"]) - s.send(msg) - length = ssh_decode_uint32(s.recv(4)) - assert length < AGENT_MAX_MSGLEN - return s.recv(length) - -def enumerate_bits(iterable): - return ((1<>1) - diff = new ^ old - assert diff != 0 and (diff & (diff-1)) == 0 - yield old, new, diff - old = new - assert old == 0 - -class TestRunner: - def __init__(self): - self.ok = True - - @staticmethod - def fmt_response(response): - return "'{}'".format( - base64.encodebytes(response).decode("ASCII").replace("\n","")) - - @staticmethod - def fmt_keylist(keys): - return "{{{}}}".format( - ",".join(key.comment.decode("ASCII") for key in sorted(keys))) - - def expect_success(self, text, response): - if response == ssh_byte(SSH_AGENT_SUCCESS): - print(text, "=> success") - elif response == ssh_byte(SSH_AGENT_FAILURE): - print("FAIL!", text, "=> failure") - self.ok = False - else: - print("FAIL!", text, "=>", self.fmt_response(response)) - self.ok = False - - def check_keylist(self, K, expected_keys): - keys = K.List() - print("list keys =>", self.fmt_keylist(keys)) - if set(keys) != set(expected_keys): - print("FAIL! Should have been", self.fmt_keylist(expected_keys)) - self.ok = False - - def gray_code_test(self, K): - bks = list(enumerate_bits(K.examples)) - - self.check_keylist(K, {}) - - for old, new, diff in gray_code(len(K.examples)): - bit, key = next((bit, key) for bit, key in bks if diff & bit) - - if new & bit: - self.expect_success("insert " + key.comment.decode("ASCII"), - key.Add()) - else: - self.expect_success("delete " + key.comment.decode("ASCII"), - key.Del()) - - self.check_keylist(K, [key.public_only() for bit, key in bks - if new & bit]) - - def sign_test(self, K): - for key in K.examples: - for params, message, expected_answer in key.iter_tests(): - key.Add() - actual_answer = key.Use(*params) - key.Del() - record = "{} with {}{}".format( - K.verb, key.comment.decode("ASCII"), message) - if actual_answer == expected_answer: - print(record, "=> success") - else: - print("FAIL!", record, "=> {} but expected {}".format( - self.fmt_response(actual_answer), - self.fmt_response(expected_answer))) - self.ok = False - - def run(self): - self.expect_success("init: delete all ssh2 keys", Key2.DelAll()) - - for K in [Key2, Key1]: - self.gray_code_test(K) - self.sign_test(K) - - # TODO: negative tests of all kinds. - -def main(): - Key2.make_examples() - Key1.make_examples() - - tr = TestRunner() - tr.run() - if tr.ok: - print("Test run passed") - else: - sys.exit("Test run failed!") - -if __name__ == "__main__": - main() diff --git a/test/agenttestdata.py b/test/agenttestdata.py deleted file mode 100644 index 596c069b1..000000000 --- a/test/agenttestdata.py +++ /dev/null @@ -1,14 +0,0 @@ -# DO NOT EDIT DIRECTLY! Autogenerated by agenttestgen.py -# -# To regenerate, run -# python3 agenttestgen.py > agenttestdata.py -# -# agenttestgen.py depends on the testcrypt system, so you must also -# have built testcrypt in the parent directory, or else set -# PUTTY_TESTCRYPT to point at a working implementation of it. - - -def key2examples(Key2, TestSig2): - return [Key2(comment=b'RSA-1024', public=b'\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x01%\x00\x00\x00\x81\x00\x87B\x16\x99\x8c96\x92\xe7\x00-\xc5\xf0%\x13:\xe5a?_\x14\xb5\x15{%\xed\xd4\rB\x98\x02~\xb0\xfdWc\xa6\x8fSz\xa7\xfd\x94\xe1\xcegx\xe3\x14\xba\x87A4\xef\xb0\x056\x9c\x80r\x18\xd7Q\xb69\xed\x9a5\xba\x8b\xf8\xee\x84F\xceD\xfa\xccn\xd6\x9ba8\x8f\xb5\x9dz\x0b\xf1\xa3\xe9vH\x1dr\r[x\xbb\xd9\xd6\xf3\xcb~W\x8fYu\xd5|G)\xa9\xa8_\x91A\x1f\xef\x80\x83\xb3jp)\xef\xe8\x05', sigs=[TestSig2(flags=0, sig=b'\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x801\xaa\xb4t\xe10\x83Q\xe4\x18\x84\x1e\xdeN$\xde;\t\xf9\xae"H\r>\xa9\x91B"\xfd\x01\\19\xee*\xb9\xc1\x8a\x1b*:\xc3\t\x91\x85\xae\x7f\xf8\x84\x08\xbd\x89P\xa9\xdb\t\x8fc\x95\xa4\xcb\xda\x19<\x14\xc4\x1a|\n\xef)\xf4\xc8\xfb\xc1\x04"\xf6\x8a9\xac\xec\xa6x>\xd3-\xb1\xf7\x1e\x04\x8a\xd4k\xcb\x12\xf9\xc1\xaa\x1aV\xc3\xb8\xdd\xf8\x01\x10Z\xdd\x8c\xcd\x12w\x83pJOr\xb8\xed\x84\xa5\xf5&\r:\xd7H'), TestSig2(flags=2, sig=b"\x00\x00\x00\x0crsa-sha2-256\x00\x00\x00\x80J)H\xfe\x18t\xc8\xa9$\x07*\xc9\x16\xb3\\\x8cK\x7f\xdd\xd8\xb0g\xed\x10o\xac\xe8\xd3\xefQj\xe2\x9fi\x13\x9b\x93\x07`}\x12\x9f\xc1Y\x19{\xb8\xc0\x8c\xe6\x03\xfd\x8d\xc1\xfat\xf0T\x055\x02r:AOM\x18x\xb6:\xb7g\xe5k\x12$\xabX)\xf8\xe9\x12\xa2\x04\xff\xfa^\xc7 G\x9c7\x92\x03>^\x00,\x1e\x063\x16\x9b\xd4.'\x01\xa4Lv\xd2\xae\xf0\xc0\xed\x8d\xf6'aj\x1aq`\xc3\x85\x08\xc2\x8a"), TestSig2(flags=4, sig=b'\x00\x00\x00\x0crsa-sha2-512\x00\x00\x00\x80;k\xd5\xf4\xe8\xec\xeb\x8b\xe13L}\x96\x918?\xd9\x90\t\x9dN\xec+|<\xc1\xc6\x10\xf6]y\x8c\xf9a\xd5\x07c:\x90\x7fzBQ\xe49\x87s\x1d\x81Kz\xe9\xee\n\xf3|\xee6\x84A\xd0\xec\xc0\xf9h\xc5$\x13\xd8r\xa2j;\xb6$?*\xd59\xcb\xdf\x85\x19U\x1f\x10\xb4\xd8C\xbb"`\x8a)\x0fy`|\xd7\xa3\xec`tw\x8a>(\x0fj\x08\xbc\x92\xd8K\xb0qP\xbe\xd9\xaf\x91\x16\xd7\x9a\x9a\r\x9d\xe5')], openssh=b'\x00\x00\x00\x81\x00\x87B\x16\x99\x8c96\x92\xe7\x00-\xc5\xf0%\x13:\xe5a?_\x14\xb5\x15{%\xed\xd4\rB\x98\x02~\xb0\xfdWc\xa6\x8fSz\xa7\xfd\x94\xe1\xcegx\xe3\x14\xba\x87A4\xef\xb0\x056\x9c\x80r\x18\xd7Q\xb69\xed\x9a5\xba\x8b\xf8\xee\x84F\xceD\xfa\xccn\xd6\x9ba8\x8f\xb5\x9dz\x0b\xf1\xa3\xe9vH\x1dr\r[x\xbb\xd9\xd6\xf3\xcb~W\x8fYu\xd5|G)\xa9\xa8_\x91A\x1f\xef\x80\x83\xb3jp)\xef\xe8\x05\x00\x00\x00\x01%\x00\x00\x00\x80 \xe6\x8f\xe0)\x06\xffo\xd7S\x12\r\x8dp\xcdS\x83\xe78\xed\x9d@\xcd\xdf\xafG\xb0\x1e\xe6\xafZ\x8d\x84\xffZr/n\xf8\xa1Ku\x08\x89\xf3\xef\xa7\xc8\x88\x80f\x16\xc7\xaf\xec\x8b\xa5\x80\x03\x91_\xfd\x06\t_\xc5\xbf\xc1\xcb\xe3\r12k\xaeY\xe4RA\xab\xf1\xba\x8a\x99\xcf\xaf\x06\xf1\x82\xc6\x9d\xd5g[\xb2\xfb\xd8\xbc\x9b]\xb9l\t\xb2\x0b\xc9\x98JK\xfe\x8a\xd6\xfc[\xd19\xe7N|\xa6m\x9ei\xce&\xc6\xfcM\x00\x00\x00A\x00\x95\xc13\x16\xd2O*\xbc8\xc6{D\xb2\xe5\x85\x9ao\xf9\xfc.\xea\xe7\x9d\xca4}\x9c7\xca\xe1\x1e\xdb`o\x88w\x0c\xa3\xba\xde\nF\xb7\xf2x}\xd9\x00\x00\x00A\x00\xa3h\x11\x1a\x99\xc6\xa4\x0eAj\x93\xff\xa0&D(\x05#MoE\xdb\x8d\xf4\x99\xc8\xd2\xffv\xf1\x90C~\xe4\xed\xce\x1f\x85\x9f\x92\xce\xacMR\xd7\x0c\xb9z\x87\xea\xe97/\x97\xbd\x19q:TLB\xb7$\r'), Key2(comment=b'DSA-1024', public=b'\x00\x00\x00\x07ssh-dss\x00\x00\x00\x81\x00\xbc\x0c\xb0L\xfc<\x03TyZQ\xe1\xef\xd4\xd5\xe4\xa2\xb3\xaf\x14t\x0f\\,!E\xdbf\x01\x9e\x95\xddr\xeb\xab\xae;\xc1\xe3\x0c\xe9\xd9\x15\xc2\xa9\xc3g\x04\xa5\xf1\x965\xf1\x81\x9dS\x9c\x83en\x93\x11\xe0p\n)\xdaZ\x17y\xff\xf2\xbf\x9b[;"E1\xf0\xde\xbd\xe1;\x9a6Xnc\x8f\xd3\x1dg\xd1\x80\xa9\x8em\x86t\xc8\xa9!\xcd\xb3\xe4mx\xd5\x93%R\xbb9u\xd2\x99p\xe2\xbe\xf3\xfb%\xebd\xc4\x86\xe3\x00\x00\x00\x15\x00\xec^\x98\x84x\xc1\xa8`\xcfB\xc7\x1e\xf0\x8d\xd3\x89\xa3\xa8\xec\xc7\x00\x00\x00\x80Q\xecf\xfd\xcc\x9a+\xcclxu\x1b\x0b\xd7\xfd\xedDP\xd1\x82~H\x0eqn\x1e\xed\xd8\xad\xe9\xe3\xf8!\x1d\\\xb4\xde\xc1e\xd7\xc0(\x1dpQ\xee\xad\xeez\xe1\xb3\xa4\x12d\x92\x1a\x89\x82u\x99\xa3$\x85\x9c\xb840\xe7\xd6&O\x85~\xd6\xac\x1eq\xa1\x06\xa2\xd1ro\xd0}>\xc0O\xaf\x8a\xf8@B\r7\xf6\x89\xda\xd0\xb9\x0e\xb6\xae\xcdh\x1a\x86%\xdcN\x8cE\xd8\xcd(\x19\xa6y\x9a\xc0\xc2\xd6;\xc3\xc9{\x104\x00\x00\x00\x80`\xc1\xe8\x18\xe4\xd0\x16\xf9[l\xce\\L*\x19\x14"20K\xc6\x18*\xe5\x91\x80\xbf+\xec\xfe\xd3D\xf7\xa6\xf9Y0x#sl\x88BU\x7f\x1c\r\xb3\x08EL\x86\xa2\x8c\x81\x15pD\xac\x9c\xa3\x8e\x02\x89\xb9\xb6\x9f\xaa\xd0\xc8\x89o\x81Qm\x18f0\\\x92\x1f\xbf\xa1\x8d8\x8a\xb1\xec\xb8G\xbd9b\x8d\x7f\x9bk\xb9x\xe5\xde\xce/\'f\xc4\x8bmX\xd9 \xb4\xd5\xe8\xd1\xe1\xc8\xeb\xe7\xbc2LG\x90]\\%\x9f', sigs=[TestSig2(flags=0, sig=b"\x00\x00\x00\x07ssh-dss\x00\x00\x00(\xeb\xf2\xb0 2(\x93a\xfc\x0f\xad\x1al\xd5\xd0n\xd5\x10\x9d\\G\x18]?\xf4h5D\x12WL\xe6#\xa0\x89'\x17\xd3;\xb7")], openssh=b'\x00\x00\x00\x81\x00\xbc\x0c\xb0L\xfc<\x03TyZQ\xe1\xef\xd4\xd5\xe4\xa2\xb3\xaf\x14t\x0f\\,!E\xdbf\x01\x9e\x95\xddr\xeb\xab\xae;\xc1\xe3\x0c\xe9\xd9\x15\xc2\xa9\xc3g\x04\xa5\xf1\x965\xf1\x81\x9dS\x9c\x83en\x93\x11\xe0p\n)\xdaZ\x17y\xff\xf2\xbf\x9b[;"E1\xf0\xde\xbd\xe1;\x9a6Xnc\x8f\xd3\x1dg\xd1\x80\xa9\x8em\x86t\xc8\xa9!\xcd\xb3\xe4mx\xd5\x93%R\xbb9u\xd2\x99p\xe2\xbe\xf3\xfb%\xebd\xc4\x86\xe3\x00\x00\x00\x15\x00\xec^\x98\x84x\xc1\xa8`\xcfB\xc7\x1e\xf0\x8d\xd3\x89\xa3\xa8\xec\xc7\x00\x00\x00\x80Q\xecf\xfd\xcc\x9a+\xcclxu\x1b\x0b\xd7\xfd\xedDP\xd1\x82~H\x0eqn\x1e\xed\xd8\xad\xe9\xe3\xf8!\x1d\\\xb4\xde\xc1e\xd7\xc0(\x1dpQ\xee\xad\xeez\xe1\xb3\xa4\x12d\x92\x1a\x89\x82u\x99\xa3$\x85\x9c\xb840\xe7\xd6&O\x85~\xd6\xac\x1eq\xa1\x06\xa2\xd1ro\xd0}>\xc0O\xaf\x8a\xf8@B\r7\xf6\x89\xda\xd0\xb9\x0e\xb6\xae\xcdh\x1a\x86%\xdcN\x8cE\xd8\xcd(\x19\xa6y\x9a\xc0\xc2\xd6;\xc3\xc9{\x104\x00\x00\x00\x80`\xc1\xe8\x18\xe4\xd0\x16\xf9[l\xce\\L*\x19\x14"20K\xc6\x18*\xe5\x91\x80\xbf+\xec\xfe\xd3D\xf7\xa6\xf9Y0x#sl\x88BU\x7f\x1c\r\xb3\x08EL\x86\xa2\x8c\x81\x15pD\xac\x9c\xa3\x8e\x02\x89\xb9\xb6\x9f\xaa\xd0\xc8\x89o\x81Qm\x18f0\\\x92\x1f\xbf\xa1\x8d8\x8a\xb1\xec\xb8G\xbd9b\x8d\x7f\x9bk\xb9x\xe5\xde\xce/\'f\xc4\x8bmX\xd9 \xb4\xd5\xe8\xd1\xe1\xc8\xeb\xe7\xbc2LG\x90]\\%\x9f\x00\x00\x00\x15\x00\x85mio\xa4\xa2\xcc\xadnC\x94\x84I*;\xf40\xc9\xd7\xa9'), Key2(comment=b'ECDSA-p256', public=b'\x00\x00\x00\x13ecdsa-sha2-nistp256\x00\x00\x00\x08nistp256\x00\x00\x00A\x04D\x01\'\xac\xa9\xeaJ#\x1e\x80\x1e\xd2R"xq\xb2h\x128c\x11\xf5\xe8\x19,;\x1d\xcf\xf4h\x8c\xaeQ\xee\x15\xc2\xdb\xc77\x80\xc4\xc2\x15\x1d0s\xe1\xbfa\xd9}pz\xc6af4d\xbd\xc6\xc6\x1as', sigs=[TestSig2(flags=0, sig=b'\x00\x00\x00\x13ecdsa-sha2-nistp256\x00\x00\x00J\x00\x00\x00!\x00\xe0\x0b\xa1\x01\xc1\xc6A\x0c\x0c\x02\x9f\xc2B\x18G=\xfa+\xde\x8a/)x\xea\xc0R\xc0\x92\xe9\t?\xc7\x00\x00\x00!\x00\xa3\xcd^!\xa5\x0f"\xcd\x9c\x82\xe0\x01\x9c\xf1\xcaa\x83\x83\x848\xe4\xfb\xd9p]\xe1\xcc<\xdb\xde\x99\xe8')], openssh=b'\x00\x00\x00\x08nistp256\x00\x00\x00A\x04D\x01\'\xac\xa9\xeaJ#\x1e\x80\x1e\xd2R"xq\xb2h\x128c\x11\xf5\xe8\x19,;\x1d\xcf\xf4h\x8c\xaeQ\xee\x15\xc2\xdb\xc77\x80\xc4\xc2\x15\x1d0s\xe1\xbfa\xd9}pz\xc6af4d\xbd\xc6\xc6\x1as\x00\x00\x00!\x00\xc3\x8b\xc3A\xde\xfd\xd4\xcb\xf7\x9c\xa0\xc7L\xd1\xb0\xfe\x8e\xf2\xf6o\xe4"\x88K\x15p0\xbc\x0b\x19\xa7\xad'), Key2(comment=b'Ed25519', public=b'\x00\x00\x00\x0bssh-ed25519\x00\x00\x00 \xd6\x9aC\xb8\xa4\x1b\x95\x8a\x97\x9a\x9d\x95\xaa5\xd5\x9b\xc3B\xd2\xd1*\x85\xde.E"\x1c\xe9\xef\xc0\x06\xdb', sigs=[TestSig2(flags=0, sig=b'\x00\x00\x00\x0bssh-ed25519\x00\x00\x00@\xda\xcbw6\xff\xb9\xc1)\x1e\xfa\xef/s!u\xe1\xc0%\xd9-;\x97<\xbc2o\x1a\xef\x17\xd3\x8dvU\xbf\x8d,\xed\xe8S\xe8\xc27\xdb\x1d\xe7\xda\\\xce\xdc\x00\xad\x97BpC\x9a\xec\xd3\r1\x9f\xc0n\x0b')], openssh=b'\x00\x00\x00 \xd6\x9aC\xb8\xa4\x1b\x95\x8a\x97\x9a\x9d\x95\xaa5\xd5\x9b\xc3B\xd2\xd1*\x85\xde.E"\x1c\xe9\xef\xc0\x06\xdb\x00\x00\x00@\rV\xff\xf36D\xe3\xb2\xcc\xda\xa1\x11\x9d\xa2\xa7\xc0\'}C\xcb"\xf0x\rlL\xfc\xcc\x99\x10\x91\xc8\xd6\x9aC\xb8\xa4\x1b\x95\x8a\x97\x9a\x9d\x95\xaa5\xd5\x9b\xc3B\xd2\xd1*\x85\xde.E"\x1c\xe9\xef\xc0\x06\xdb')] -def key1examples(Key1): - return [Key1(comment=b'RSA-1024a', public=b"\x00\x00\x04\x00\x00\x06%\x04\x00\x98\x8d\xac\xa1\xff\xd1\x05\xd4@\x93\x11\xfc\xd8\xb5\x8c\x18\xa8/\x9ePh\x06y,\xc1\xdd\xc2q\x90\xe0g\xebIgl\x12\xacs.\xc1\xd7\xd0,\x8d\xd4\xa4\xd1\x88F\x1dW\xa6\xb9\x808+0u`B\xa8\xd2z\x0c>}\xaeA\xa7\x945\x91\x0c\xd5@5s\xa8R\xc31\xc5\x8e'\xec6\x00\x98\xdd\x0b\x93\xa8\x8e\xe6\xa9\x19\xa2\xbaf\xd6\xa8@\x1b\x82\xf4\xf5j\xc4\x06\xdd\x08\x7f\xcce\xcdc%\xc4W\xd7k\xd2\xe3\xcf\xa2\xbaI=", challenge=105855771610781217219148893240371739231586890974005582821084910669621353003219053895226458126049169949952763904000891276244889049724351186264170655451406153989624471223276671179692313150356649135789040179224142632652879598695018927369451625737003799565133274183885945180682771324880529165922297762364545903323, response=b'U\xb2\xdf(\xb8\xc7\xf5\r\x16\xa0%O9l\xdb\xf0', private=b'\x00\x00\x04\x00\x04\x00\x98\x8d\xac\xa1\xff\xd1\x05\xd4@\x93\x11\xfc\xd8\xb5\x8c\x18\xa8/\x9ePh\x06y,\xc1\xdd\xc2q\x90\xe0g\xebIgl\x12\xacs.\xc1\xd7\xd0,\x8d\xd4\xa4\xd1\x88F\x1dW\xa6\xb9\x808+0u`B\xa8\xd2z\x0c>}\xaeA\xa7\x945\x91\x0c\xd5@5s\xa8R\xc31\xc5\x8e\'\xec6\x00\x98\xdd\x0b\x93\xa8\x8e\xe6\xa9\x19\xa2\xbaf\xd6\xa8@\x1b\x82\xf4\xf5j\xc4\x06\xdd\x08\x7f\xcce\xcdc%\xc4W\xd7k\xd2\xe3\xcf\xa2\xbaI=\x00\x06%\x04\x00\x94n+m0@\xfe\xc0\xad\x88--];\x04\xd9\xb8e\xae\xca\xc6\x14"\xdfp\x84\xbd09\xef\x19\x00\x9arv\xfdi\x84\xd3\x8c+\xed$nR[,\xbb\xf11N]\x07\x83\xacE\xb2\x9b\xb7\x9a\xcd\xc5\xde\x86\xe7\xf9O\xf9\xa0\xce\xca\x085\xe7\xb7En\x94\x1e;T)SH\xff\x85\xe5\x8d\x1f\xa6,\xc7\xffV\x80\xf2E=\x08\x8e\xe6\x9e\xb2}py\xad\xa55Z\xf4\xed\xc8^+tnIN)vE\xbd\x82\xb665\xdd\x01\xfb\x04d\xa4\xb80\x98l\xd0!l\x99[\x12\x1c\x85\xda\xa5\xd0#\xcc\x7f\x80\xcdE~\x8bF \xf1;\x9d\xfb\xc0\xa3\x93\xa2\xb89^\x91D\xb7\xd3\x18\xbdx\x93U\xc3{\xa4\t,F\xb5\xdd\xadk\xc9@\xd0$\xc6\x03\x02\x00\x9a\xa4\\\xb4\xe5\x05\xe0&\xb0\x06\xc3\x9dQ`\xe4w\xe3-6N "\xa2\x9a%\xf16T\x92%\x16\xf9\xfc\xb5\xc6\xc4\xbb\xa8\xf7?\xa7"\xa1\x9do\x10A3\x14\x12\x18Uj\x19k\x19\x93\x99\xc9\xab\xfa\xa7\x15\xcb\x02\x00\xfc\x8a\xdb\xcc2\x9d[\x1a\xd0\x12\x1c\xad7\xbdk\xaa\xc6Ql\xeb7;\x87f\x8fv\xafM\x8b\xa8\xaa\n40\x90)\xb8t\tBaU\xba<\xcb\xa1\x12\xad\xaad\xb3\x0e\xf4\xfc\x07\x13;\x1c\x17]P[|\x17'), Key1(comment=b'RSA-1024b', public=b'\x00\x00\x04\x00\x00\x06%\x04\x00\x97\xe4sJ\xf8i\x83\x9f\xe8k%\xc6\xb7\xcbm!\xd7\xdd\xd5!N\xad<8\x0e\x1f\xa15yV\xbcr\xec\x8c\xca\x94\xed\x0c\xdbDC\x9e\xe1\xf5\xe4_\xb6>\x19\xe0\xdf\xb1te\xc7n\x86\xf7\xd15\x9e\xfc\x81\x90V\x92\xae\x1cb\xcc\xde\x05k\x8eNIa\x87\x1a\x8aG\xdd\xc9\xc9K\xfe\xb3W\xb0%\xe2\x10bs\x18\xe2\x07I\xf8\x88P\x04i!\xa9\xdd5\x12\x12\xdbp\x06\x03\xbb\x0e(\x82\x0e\xe7\xe7r\x17\xdbN\x91\x141Q', challenge=106369452810277819728395930691403679528939443121481728310811020449278450935158409081846069415863931371431145424909167586350456375465223628112328681772147389155179853401808803792809242283837570604791348990555643874877574733757512944970974196016255159184273573080435875970788050018918135917906633435695051286334, response=b'U\xb2\xdf(\xb8\xc7\xf5\r\x16\xa0%O9l\xdb\xf0', private=b'\x00\x00\x04\x00\x04\x00\x97\xe4sJ\xf8i\x83\x9f\xe8k%\xc6\xb7\xcbm!\xd7\xdd\xd5!N\xad<8\x0e\x1f\xa15yV\xbcr\xec\x8c\xca\x94\xed\x0c\xdbDC\x9e\xe1\xf5\xe4_\xb6>\x19\xe0\xdf\xb1te\xc7n\x86\xf7\xd15\x9e\xfc\x81\x90V\x92\xae\x1cb\xcc\xde\x05k\x8eNIa\x87\x1a\x8aG\xdd\xc9\xc9K\xfe\xb3W\xb0%\xe2\x10bs\x18\xe2\x07I\xf8\x88P\x04i!\xa9\xdd5\x12\x12\xdbp\x06\x03\xbb\x0e(\x82\x0e\xe7\xe7r\x17\xdbN\x91\x141Q\x00\x06%\x03\xfe1C,O\xaa\x83\x15\xee\xac>m\x1d\xda\xbe\x84BS\xd9>4P\xde=\x0bB\xd9\xd3kI\xf2\x9d\xfb\xc2W,\xf2\x07\xb1$\x84\xd7\xa9&\xb0\x9d\x18\x1fn\x16;\x18\x1d\xe0\x8f\xb6M\\4\xb2\x8d\xee_\xbbP\xe7 j!\xc7W\xcb\xc9\x19\nM\x90\xfe4\xe5U\xef[\xdc&A\xde\xd9\x84\x02\xdek\xec\xaf\xb4\xd0I\xaaR\xa6\xc1\x8b\xbc\x13\xf1?,\xc6{\n\x02p\xa7\'\xa1\xb9\xf8\x1f\xeb\x99\xe2\xcf\xc4%"+Mu\x9d\x02\x00\xc4\x89W\x05\x8f\xff\xabX6\x8f\x9fQ\x19\xb8\xc2\xc2Y|\xa90g\xa9\xa7\xa9\x17 \xeb\xbbSMd\xf4YW\xa7\x93\xfcn\xc3AI\x04tK\x1a\xd74`\xec]+\xd8\x91`W@\xc7\xa6G\x82\x99\xac\x8c\x9b\x02\x00\xacs\x1e6\xa5\x10\xb2\xd1\xc9|\x87\x15\xb6\xd9*\x05O\x9e\x95\xec\x1f\xac\xbc/2\xc1\xdb\xa7\x97w@\xfe?d\xb2|\xd4\x96\x02\xc8y\xdf; \x89\x0b'), Key1(comment=b'RSA-768d', public=b'\x00\x00\x03\x00\x00\x06%\x03\x00\x9e4w\xb6C\x1c\xb1\xcdV\x96\t\x14\x04T\xb5\xca\x0ct?a8\xfd-\xb1l\x83/\xc3\x95\x97\x8b \xcdZW\x15\x87G\xa8\x1d\xea(\x1d\x03V\xe8\xe8/M.\xe6\xd6\x8d,\xf3>$"R\xcciYwp*\xc7z\x0c\xc3/k\x87\xc1{4\x1bw\xf1\x00\xda~\x84\x0e\xb0)\'\x84\x9e<\xd1\x19\x18\x81\xcb\xffo', challenge=600986133602165113984107876330614577281000470334319933978738264340033662158902949574073710382789301043986608302703563989903269508211841666685953915670687064469873711668788199922957307325206107166514327102609966835942325119838536897, response=b'U\xb2\xdf(\xb8\xc7\xf5\r\x16\xa0%O9l\xdb\xf0', private=b'\x00\x00\x03\x00\x03\x00\x9e4w\xb6C\x1c\xb1\xcdV\x96\t\x14\x04T\xb5\xca\x0ct?a8\xfd-\xb1l\x83/\xc3\x95\x97\x8b \xcdZW\x15\x87G\xa8\x1d\xea(\x1d\x03V\xe8\xe8/M.\xe6\xd6\x8d,\xf3>$"R\xcciYwp*\xc7z\x0c\xc3/k\x87\xc1{4\x1bw\xf1\x00\xda~\x84\x0e\xb0)\'\x84\x9e<\xd1\x19\x18\x81\xcb\xffo\x00\x06%\x02\xfe"4\xdb\x9d\x07\x97\x80c\xbf\xb1\xbc\xc6\x0e\xc65$\xc4l)a!\x14%\x8e%L\xcc\x0e\x9c\xe2~\xf2U\xea\x04\xfd\xbcb\x856\xe6\x856\xb4\x9d+px\x97\x0c\x17\xde\x93\xd8zOAO\xea\xab\xa2p\x14\x87\xf5\x1c\x00\xb2\xa3A\x08\x14\xe9v\xb8\xd2\xbf\x03C\xf2\xa3\xfeV/BZ\x11\x82#\xff)\xf8\x93\x0f\xf0m\x01\x80\x85\xde\xf4\x1b\xd2Pu\xf3\xd8\xf8Z\xabZ\x92q\xef\xab\x06\x85\xcd\xc3\x85\xd6?j\xd4\xaa\x96l\xeeRZ\xab8\x05\x84xT\xdd\x15j\xea9O_\xf4`R\x01\x80\xc4\xeb\x12\x92\xdc\x05\\b\x8c\xec\x11\x10\xc5\xaa\xdf\x97\x1eD\x92\x06_\xb3\xc28C\xceH\xa7\x8a\xf2F\xe6+\x01\xbf\xad\x81\x99&2\xcc\xff\xa2\xaad\x04\x08\x8d\x01\x80\xcd\xab\xe5\xdeE^a-\t$\xa4a\xd4h8\xe4>\xe1d\xcc0n\xe3\xee\xc5\xe7\xd4\xa59\x8f\x9f\xb2\x1d\n\x00h\x14\xad\xcdq\x89UTPu\x9e>\xeb')] diff --git a/test/agenttestgen.py b/test/agenttestgen.py deleted file mode 100644 index 1eef29654..000000000 --- a/test/agenttestgen.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python3 - -import sys - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -def generate(): - import hashlib - - print("""\ -# DO NOT EDIT DIRECTLY! Autogenerated by agenttestgen.py -# -# To regenerate, run -# python3 agenttestgen.py > agenttestdata.py -# -# agenttestgen.py depends on the testcrypt system, so you must also -# have built testcrypt in the parent directory, or else set -# PUTTY_TESTCRYPT to point at a working implementation of it. - -""") - - from testcrypt import (rsa_generate, dsa_generate, ecdsa_generate, - eddsa_generate, random_clear, random_queue, - ssh_key_public_blob, ssh_key_openssh_blob, - ssh_key_sign, rsa1_generate, rsa_ssh1_encrypt, - rsa_ssh1_public_blob, rsa_ssh1_private_blob_agent, - mp_from_bytes_be) - from agenttest import (Key2, TestSig2, test_message_to_sign, - Key1, test_session_id) - import ssh - - keygen2 = [ - ('RSA-1024', lambda: rsa_generate(1024, False), - (ssh.SSH_AGENT_RSA_SHA2_256, ssh.SSH_AGENT_RSA_SHA2_512)), - ('DSA-1024', lambda: dsa_generate(1024)), - ('ECDSA-p256', lambda: ecdsa_generate(256)), - ('Ed25519', lambda: eddsa_generate(256)), - ] - - keys2 = [] - - for record in keygen2: - if len(record) == 2: - record += ((),) - comment, genfn, flaglist = record - flaglist = (0,) + flaglist - - random_clear() - random_queue(b''.join(hashlib.sha512('{}{:d}'.format(comment, j) - .encode('ASCII')).digest() - for j in range(1000))) - key = genfn() - sigs = [TestSig2(flags, ssh_key_sign(key, test_message_to_sign, flags)) - for flags in flaglist] - - keys2.append(Key2(comment.encode("ASCII"), - ssh_key_public_blob(key), - sigs, - ssh_key_openssh_blob(key))) - - print("def key2examples(Key2, TestSig2):\n return {!r}".format(keys2)) - - keygen1 = [ - ('RSA-1024a', 1024), - ('RSA-1024b', 1024), - ('RSA-768c', 768), - ('RSA-768d', 768), - ] - - keys1 = [] - - for comment, bits in keygen1: - random_clear() - random_queue(b''.join(hashlib.sha512('{}{:d}'.format(comment, j) - .encode('ASCII')).digest() - for j in range(1000))) - key = rsa1_generate(bits) - preimage = b'Test128BitRSA1ChallengeCleartext' - assert len(preimage) == 32 - challenge_bytes = rsa_ssh1_encrypt(preimage, key) - assert len(challenge_bytes) > 0 - challenge = int(mp_from_bytes_be(challenge_bytes)) - response = hashlib.md5(preimage + test_session_id).digest() - - keys1.append(Key1(comment.encode("ASCII"), - rsa_ssh1_public_blob(key, 'exponent_first'), - challenge, response, - rsa_ssh1_private_blob_agent(key))) - - print("def key1examples(Key1):\n return {!r}".format(keys1)) - -if __name__ == "__main__": - generate() diff --git a/test/ca.py b/test/ca.py deleted file mode 100644 index ebd805992..000000000 --- a/test/ca.py +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env python3 -# -# Implementation of OpenSSH certificate creation. Used in -# cryptsuite.py to construct certificates for test purposes. -# -# Can also be run standalone to function as an actual CA, though I -# don't currently know of any reason you'd want to use it in place of -# ssh-keygen. In that mode, it depends on having an SSH agent -# available to do the signing. - -import argparse -import base64 -import enum -import hashlib -import io -import os - -import ssh - -class Container: - pass - -class CertType(enum.Enum): - user = 1 - host = 2 - -def maybe_encode(s): - if isinstance(s, bytes): - return s - return s.encode('UTF-8') - -def make_signature_preimage( - key_to_certify, ca_key, certtype, keyid, serial, principals, - valid_after=0, valid_before=0xFFFFFFFFFFFFFFFF, - critical_options={}, extensions={}, - reserved=b'', nonce=None): - - alg, pubkeydata = ssh.ssh_decode_string(key_to_certify, True) - - if nonce is None: - nonce = os.urandom(32) - - buf = io.BytesIO() - buf.write(ssh.ssh_string(alg + b"-cert-v01@openssh.com")) - buf.write(ssh.ssh_string(nonce)) - buf.write(pubkeydata) - buf.write(ssh.ssh_uint64(serial)) - buf.write(ssh.ssh_uint32(certtype.value if isinstance(certtype, CertType) - else certtype)) - buf.write(ssh.ssh_string(maybe_encode(keyid))) - buf.write(ssh.ssh_string(b''.join( - ssh.ssh_string(maybe_encode(principal)) - for principal in principals))) - buf.write(ssh.ssh_uint64(valid_after)) - buf.write(ssh.ssh_uint64(valid_before)) - buf.write(ssh.ssh_string(b''.join( - ssh.ssh_string(opt) + ssh.ssh_string(val) - for opt, val in sorted([(maybe_encode(opt), maybe_encode(val)) - for opt, val in critical_options.items()])))) - buf.write(ssh.ssh_string(b''.join( - ssh.ssh_string(opt) + ssh.ssh_string(val) - for opt, val in sorted([(maybe_encode(opt), maybe_encode(val)) - for opt, val in extensions.items()])))) - buf.write(ssh.ssh_string(reserved)) - # The CA key here can be a raw 'bytes', or an ssh_key object - # exposed via testcrypt - if type(ca_key) != bytes: - ca_key = ca_key.public_blob() - buf.write(ssh.ssh_string(ca_key)) - - return buf.getvalue() - -def make_full_cert(preimage, signature): - return preimage + ssh.ssh_string(signature) - -def sign_cert_via_testcrypt(preimage, ca_key, signflags=None): - # Expects ca_key to be a testcrypt ssh_key object - signature = ca_key.sign(preimage, 0 if signflags is None else signflags) - return make_full_cert(preimage, signature) - -def sign_cert_via_agent(preimage, ca_key, signflags=None): - # Expects ca_key to be a binary public key blob, and for a - # currently running SSH agent to contain the corresponding private - # key. - import agenttest - sign_request = (ssh.ssh_byte(ssh.SSH2_AGENTC_SIGN_REQUEST) + - ssh.ssh_string(ca_key) + ssh.ssh_string(preimage)) - if signflags is not None: - sign_request += ssh.ssh_uint32(signflags) - sign_response = agenttest.agent_query(sign_request) - msgtype, sign_response = ssh.ssh_decode_byte(sign_response, True) - if msgtype == ssh.SSH2_AGENT_SIGN_RESPONSE: - signature, sign_response = ssh.ssh_decode_string(sign_response, True) - return make_full_cert(preimage, signature) - elif msgtype == ssh.SSH2_AGENT_FAILURE: - raise IOError("Agent refused to return a signature") - else: - raise IOError("Agent returned unexpecteed message type {:d}" - .format(msgtype)) - -def read_pubkey_file(fh): - b64buf = io.StringIO() - comment = None - - lines = (line.rstrip("\r\n") for line in iter(fh.readline, "")) - line = next(lines) - - if line == "---- BEGIN SSH2 PUBLIC KEY ----": - # RFC 4716 public key. Read headers like Comment: - line = next(lines) - while ":" in line: - key, val = line.split(":", 1) - if key == "Comment": - comment = val.strip("\r\n") - line = next(lines) - # Now expect lines of base64 data. - while line != "---- END SSH2 PUBLIC KEY ----": - b64buf.write(line) - line = next(lines) - - else: - # OpenSSH public key. Expect the b64buf blob to be the second word. - fields = line.split(" ", 2) - b64buf.write(fields[1]) - if len(fields) > 1: - comment = fields[2] - - return base64.b64decode(b64buf.getvalue()), comment - -def write_pubkey_file(fh, key, comment=None): - alg = ssh.ssh_decode_string(key) - fh.write(alg.decode('ASCII')) - fh.write(" " + base64.b64encode(key).decode('ASCII')) - if comment is not None: - fh.write(" " + comment) - fh.write("\n") - -def default_signflags(key): - alg = ssh.ssh_decode_string(key) - if alg == b'ssh-rsa': - return 4 # RSA-SHA-512 - -def main(): - parser = argparse.ArgumentParser( - description='Create and sign OpenSSH certificates.') - parser.add_argument("key_to_certify", help="Public key to be certified.") - parser.add_argument("--ca-key", required=True, - help="Public key of the CA. Must be present in a " - "currently accessible SSH agent.") - parser.add_argument("-o", "--output", required=True, - help="File to write output OpenSSH key to.") - parser.add_argument("--type", required=True, choices={'user', 'host'}, - help="Type of certificate to make.") - parser.add_argument("--principal", "--user", "--host", - required=True, action="append", - help="User names or host names to authorise.") - parser.add_argument("--key-id", "--keyid", required=True, - help="Human-readable key ID string for log files.") - parser.add_argument("--serial", type=int, required=True, - help="Serial number to write into certificate.") - parser.add_argument("--signflags", type=int, help="Signature flags " - "(e.g. 2 = RSA-SHA-256, 4 = RSA-SHA-512).") - args = parser.parse_args() - - with open(args.key_to_certify) as fh: - key_to_certify, comment = read_pubkey_file(fh) - with open(args.ca_key) as fh: - ca_key, _ = read_pubkey_file(fh) - - extensions = { - 'permit-X11-forwarding': '', - 'permit-agent-forwarding': '', - 'permit-port-forwarding': '', - 'permit-pty': '', - 'permit-user-rc': '', - } - - # FIXME: for a full-featured command-line CA we'd need to add - # command-line options for crit opts, extensions and validity - # period - preimage = make_signature_preimage( - key_to_certify = key_to_certify, - ca_key = ca_key, - certtype = getattr(CertType, args.type), - keyid = args.key_id, - serial = args.serial, - principals = args.principal, - extensions = extensions) - - signflags = (args.signflags if args.signflags is not None - else default_signflags(ca_key)) - cert = sign_cert_via_agent(preimage, ca_key, signflags) - - with open(args.output, "w") as fh: - write_pubkey_file(fh, cert, comment) - -if __name__ == '__main__': - main() diff --git a/test/colours.txt b/test/colours.txt deleted file mode 100644 index 34dff8a5a..000000000 --- a/test/colours.txt +++ /dev/null @@ -1,15 +0,0 @@ -Test of most colour rendering. Omits the SCO fg and bg sequences, -since they are destructive. -Normal text and bold; reverse video and bold -ANSI plus bold: 0 bold 1 bold 2 bold 3 bold 4 bold 5 bold 6 bold 7 bold -xterm bright: fg0 bg0 fg1 bg1 fg2 bg2 fg3 bg3 fg4 bg4 fg5 bg5 fg6 bg6 fg7 bg7 -xterm 256: greys                      reds   greens blues  yellow magent cyans  -0001020304050607 08090a0b0c0d0e0f 1011121314151617 18191a1b1c1d1e1f -2021222324252627 28292a2b2c2d2e2f 3031323334353637 38393a3b3c3d3e3f -4041424344454647 48494a4b4c4d4e4f 5051525354555657 58595a5b5c5d5e5f -6061626364656667 68696a6b6c6d6e6f 7071727374757677 78797a7b7c7d7e7f -8081828384858687 88898a8b8c8d8e8f 9091929394959697 98999a9b9c9d9e9f -a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf -c0c1c2c3c4c5c6c7 c8c9cacbcccdcecf d0d1d2d3d4d5d6d7 d8d9dadbdcdddedf -e0e1e2e3e4e5e6e7 e8e9eaebecedeeef f0f1f2f3f4f5f6f7 f8f9fafbfcfdfeff -24-bit colour: SlateGrey OliveDrab goldenrod SaddleBrown DarkViolet (bg) diff --git a/test/cryptsuite.py b/test/cryptsuite.py deleted file mode 100644 index 99b23d25f..000000000 --- a/test/cryptsuite.py +++ /dev/null @@ -1,4118 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import unittest -import struct -import itertools -import functools -import contextlib -import hashlib -import binascii -from base64 import b64decode as b64 -import json -try: - from math import gcd -except ImportError: - from fractions import gcd - -from eccref import * -from testcrypt import * -from ssh import * -from ca import CertType, make_signature_preimage, sign_cert_via_testcrypt - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -def unhex(s): - return binascii.unhexlify(s.replace(" ", "").replace("\n", "")) - -def rsa_bare(e, n): - rsa = rsa_new() - get_rsa_ssh1_pub(ssh_uint32(nbits(n)) + ssh1_mpint(e) + ssh1_mpint(n), - rsa, 'exponent_first') - return rsa - -def find_non_square_mod(p): - # Find a non-square mod p, using the Jacobi symbol - # calculation function from eccref.py. - return next(z for z in itertools.count(2) if jacobi(z, p) == -1) - -def fibonacci_scattered(n=10): - # Generate a list of Fibonacci numbers with power-of-2 indices - # (F_1, F_2, F_4, ...), to be used as test inputs of varying - # sizes. Also put F_0 = 0 into the list as a bonus. - yield 0 - a, b, c = 0, 1, 1 - while True: - yield b - n -= 1 - if n <= 0: - break - a, b, c = (a**2+b**2, b*(a+c), b**2+c**2) - -def fibonacci(n=10): - # Generate the full Fibonacci sequence starting from F_0 = 0. - a, b = 0, 1 - while True: - yield a - n -= 1 - if n <= 0: - break - a, b = b, a+b - -def mp_mask(mp): - # Return the value that mp would represent if all its bits - # were set. Useful for masking a true mathematical output - # value (e.g. from an operation that can over/underflow, like - # mp_sub or mp_anything_into) to check it's right within the - # ability of that particular mp_int to represent. - return ((1 << mp_max_bits(mp))-1) - -def adjtuples(iterable, n): - # Return all the contiguous n-tuples of an iterable, including - # overlapping ones. E.g. if called on [0,1,2,3,4] with n=3 it - # would return (0,1,2), (1,2,3), (2,3,4) and then stop. - it = iter(iterable) - toret = [next(it) for _ in range(n-1)] - for element in it: - toret.append(element) - yield tuple(toret) - toret[:1] = [] - -def last(iterable): - # Return the last element of an iterable, or None if it is empty. - it = iter(iterable) - toret = None - for toret in it: - pass - return toret - -def le_integer(x, nbits): - assert nbits % 8 == 0 - return bytes([0xFF & (x >> (8*n)) for n in range(nbits//8)]) - -@contextlib.contextmanager -def queued_random_data(nbytes, seed): - hashsize = 512 // 8 - data = b''.join( - hashlib.sha512("preimage:{:d}:{}".format(i, seed).encode('ascii')) - .digest() for i in range((nbytes + hashsize - 1) // hashsize)) - data = data[:nbytes] - random_queue(data) - yield None - random_clear() - -@contextlib.contextmanager -def queued_specific_random_data(data): - random_queue(data) - yield None - random_clear() - -@contextlib.contextmanager -def random_prng(seed): - random_make_prng('sha256', seed) - yield None - random_clear() - -def hash_str(alg, message): - h = ssh_hash_new(alg) - ssh_hash_update(h, message) - return ssh_hash_final(h) - -def hash_str_iter(alg, message_iter): - h = ssh_hash_new(alg) - for string in message_iter: - ssh_hash_update(h, string) - return ssh_hash_final(h) - -def mac_str(alg, key, message, cipher=None): - m = ssh2_mac_new(alg, cipher) - ssh2_mac_setkey(m, key) - ssh2_mac_start(m) - ssh2_mac_update(m, "dummy") - # Make sure ssh_mac_start erases previous state - ssh2_mac_start(m) - ssh2_mac_update(m, message) - return ssh2_mac_genresult(m) - -def lcm(a, b): - return a * b // gcd(a, b) - -def get_implementations(alg): - return get_implementations_commasep(alg).decode("ASCII").split(",") - -def get_aes_impls(): - return [impl.rsplit("_", 1)[-1] - for impl in get_implementations("aes128_cbc") - if impl.startswith("aes128_cbc_")] - -def get_aesgcm_impls(): - return [impl.split("_", 1)[1] - for impl in get_implementations("aesgcm") - if impl.startswith("aesgcm_")] - -class MyTestBase(unittest.TestCase): - "Intermediate class that adds useful helper methods." - def assertEqualBin(self, x, y): - # Like assertEqual, but produces more legible error reports - # for random-looking binary data. - self.assertEqual(binascii.hexlify(x), binascii.hexlify(y)) - -class mpint(MyTestBase): - def testCreation(self): - self.assertEqual(int(mp_new(128)), 0) - self.assertEqual(int(mp_from_bytes_be(b'ABCDEFGHIJKLMNOP')), - 0x4142434445464748494a4b4c4d4e4f50) - self.assertEqual(int(mp_from_bytes_le(b'ABCDEFGHIJKLMNOP')), - 0x504f4e4d4c4b4a494847464544434241) - self.assertEqual(int(mp_from_integer(12345)), 12345) - decstr = '91596559417721901505460351493238411077414937428167' - self.assertEqual(int(mp_from_decimal_pl(decstr)), int(decstr, 10)) - self.assertEqual(int(mp_from_decimal(decstr)), int(decstr, 10)) - self.assertEqual(int(mp_from_decimal("")), 0) - # For hex, test both upper and lower case digits - hexstr = 'ea7cb89f409ae845215822e37D32D0C63EC43E1381C2FF8094' - self.assertEqual(int(mp_from_hex_pl(hexstr)), int(hexstr, 16)) - self.assertEqual(int(mp_from_hex(hexstr)), int(hexstr, 16)) - self.assertEqual(int(mp_from_hex("")), 0) - p2 = mp_power_2(123) - self.assertEqual(int(p2), 1 << 123) - p2c = mp_copy(p2) - self.assertEqual(int(p2c), 1 << 123) - # Check mp_copy really makes a copy, not an alias (ok, that's - # testing the testcrypt system more than it's testing the - # underlying C functions) - mp_set_bit(p2c, 120, 1) - self.assertEqual(int(p2c), (1 << 123) + (1 << 120)) - self.assertEqual(int(p2), 1 << 123) - - def testBytesAndBits(self): - x = mp_new(128) - self.assertEqual(mp_get_byte(x, 2), 0) - mp_set_bit(x, 2*8+3, 1) - self.assertEqual(mp_get_byte(x, 2), 1<<3) - self.assertEqual(mp_get_bit(x, 2*8+3), 1) - mp_set_bit(x, 2*8+3, 0) - self.assertEqual(mp_get_byte(x, 2), 0) - self.assertEqual(mp_get_bit(x, 2*8+3), 0) - # Currently I expect 128 to be a multiple of any - # BIGNUM_INT_BITS value we might be running with, so these - # should be exact equality - self.assertEqual(mp_max_bytes(x), 128/8) - self.assertEqual(mp_max_bits(x), 128) - - nb = lambda hexstr: mp_get_nbits(mp_from_hex(hexstr)) - self.assertEqual(nb('00000000000000000000000000000000'), 0) - self.assertEqual(nb('00000000000000000000000000000001'), 1) - self.assertEqual(nb('00000000000000000000000000000002'), 2) - self.assertEqual(nb('00000000000000000000000000000003'), 2) - self.assertEqual(nb('00000000000000000000000000000004'), 3) - self.assertEqual(nb('000003ffffffffffffffffffffffffff'), 106) - self.assertEqual(nb('000003ffffffffff0000000000000000'), 106) - self.assertEqual(nb('80000000000000000000000000000000'), 128) - self.assertEqual(nb('ffffffffffffffffffffffffffffffff'), 128) - - def testDecAndHex(self): - def checkHex(hexstr): - n = mp_from_hex(hexstr) - i = int(hexstr, 16) - self.assertEqual(mp_get_hex(n), - "{:x}".format(i).encode('ascii')) - self.assertEqual(mp_get_hex_uppercase(n), - "{:X}".format(i).encode('ascii')) - checkHex("0") - checkHex("f") - checkHex("00000000000000000000000000000000000000000000000000") - checkHex("d5aa1acd5a9a1f6b126ed416015390b8dc5fceee4c86afc8c2") - checkHex("ffffffffffffffffffffffffffffffffffffffffffffffffff") - - def checkDec(hexstr): - n = mp_from_hex(hexstr) - i = int(hexstr, 16) - self.assertEqual(mp_get_decimal(n), - "{:d}".format(i).encode('ascii')) - checkDec("0") - checkDec("f") - checkDec("00000000000000000000000000000000000000000000000000") - checkDec("d5aa1acd5a9a1f6b126ed416015390b8dc5fceee4c86afc8c2") - checkDec("ffffffffffffffffffffffffffffffffffffffffffffffffff") - checkDec("f" * 512) - - def testComparison(self): - inputs = [ - "0", "1", "2", "10", "314159265358979", "FFFFFFFFFFFFFFFF", - - # Test over-long versions of some of the same numbers we - # had short forms of above - "0000000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000000", - - "0000000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000001", - - "0000000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000000000000000000000000000000002", - - "0000000000000000000000000000000000000000000000000000000000000000" - "000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFF", - - "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" - "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", - ] - values = [(mp_from_hex(s), int(s, 16)) for s in inputs] - for am, ai in values: - for bm, bi in values: - self.assertEqual(mp_cmp_eq(am, bm) == 1, ai == bi) - self.assertEqual(mp_cmp_hs(am, bm) == 1, ai >= bi) - if (bi >> 64) == 0: - self.assertEqual(mp_eq_integer(am, bi) == 1, ai == bi) - self.assertEqual(mp_hs_integer(am, bi) == 1, ai >= bi) - - # mp_{min,max}{,_into} is a reasonable thing to test - # here as well - self.assertEqual(int(mp_min(am, bm)), min(ai, bi)) - self.assertEqual(int(mp_max(am, bm)), max(ai, bi)) - am_small = mp_copy(am if aibi else bm) - mp_max_into(am_big, am, bm) - self.assertEqual(int(am_big), max(ai, bi)) - - # Test mp_{eq,hs}_integer in the case where the integer is as - # large as possible and the bignum contains very few words. In - # modes where BIGNUM_INT_BITS < 64, this used to go wrong. - mp10 = mp_new(4) - mp_copy_integer_into(mp10, 10) - highbit = 1 << 63 - self.assertEqual(mp_hs_integer(mp10, highbit | 9), 0) - self.assertEqual(mp_hs_integer(mp10, highbit | 10), 0) - self.assertEqual(mp_hs_integer(mp10, highbit | 11), 0) - self.assertEqual(mp_eq_integer(mp10, highbit | 9), 0) - self.assertEqual(mp_eq_integer(mp10, highbit | 10), 0) - self.assertEqual(mp_eq_integer(mp10, highbit | 11), 0) - - def testConditionals(self): - testnumbers = [(mp_copy(n),n) for n in fibonacci_scattered()] - for am, ai in testnumbers: - for bm, bi in testnumbers: - cm = mp_copy(am) - mp_select_into(cm, am, bm, 0) - self.assertEqual(int(cm), ai & mp_mask(am)) - mp_select_into(cm, am, bm, 1) - self.assertEqual(int(cm), bi & mp_mask(am)) - - mp_cond_add_into(cm, am, bm, 0) - self.assertEqual(int(cm), ai & mp_mask(am)) - mp_cond_add_into(cm, am, bm, 1) - self.assertEqual(int(cm), (ai+bi) & mp_mask(am)) - - mp_cond_sub_into(cm, am, bm, 0) - self.assertEqual(int(cm), ai & mp_mask(am)) - mp_cond_sub_into(cm, am, bm, 1) - self.assertEqual(int(cm), (ai-bi) & mp_mask(am)) - - maxbits = max(mp_max_bits(am), mp_max_bits(bm)) - cm = mp_new(maxbits) - dm = mp_new(maxbits) - mp_copy_into(cm, am) - mp_copy_into(dm, bm) - - self.assertEqual(int(cm), ai) - self.assertEqual(int(dm), bi) - mp_cond_swap(cm, dm, 0) - self.assertEqual(int(cm), ai) - self.assertEqual(int(dm), bi) - mp_cond_swap(cm, dm, 1) - self.assertEqual(int(cm), bi) - self.assertEqual(int(dm), ai) - - if bi != 0: - mp_cond_clear(cm, 0) - self.assertEqual(int(cm), bi) - mp_cond_clear(cm, 1) - self.assertEqual(int(cm), 0) - - def testBasicArithmetic(self): - testnumbers = list(fibonacci_scattered(5)) - testnumbers.extend([1 << (1 << i) for i in range(3,10)]) - testnumbers.extend([(1 << (1 << i)) - 1 for i in range(3,10)]) - - testnumbers = [(mp_copy(n),n) for n in testnumbers] - - for am, ai in testnumbers: - for bm, bi in testnumbers: - self.assertEqual(int(mp_add(am, bm)), ai + bi) - self.assertEqual(int(mp_mul(am, bm)), ai * bi) - # Cope with underflow in subtraction - diff = mp_sub(am, bm) - self.assertEqual(int(diff), (ai - bi) & mp_mask(diff)) - - for bits in range(64, 512, 64): - cm = mp_new(bits) - mp_add_into(cm, am, bm) - self.assertEqual(int(cm), (ai + bi) & mp_mask(cm)) - mp_mul_into(cm, am, bm) - self.assertEqual(int(cm), (ai * bi) & mp_mask(cm)) - mp_sub_into(cm, am, bm) - self.assertEqual(int(cm), (ai - bi) & mp_mask(cm)) - - # A test cherry-picked from the old bignum test script, - # involving two numbers whose product has a single 1 bit miles - # in the air and then all 0s until a bunch of cruft at the - # bottom, the aim being to test that carry propagation works - # all the way up. - ai, bi = 0xb4ff6ed2c633847562087ed9354c5c17be212ac83b59c10c316250f50b7889e5b058bf6bfafd12825225ba225ede0cba583ffbd0882de88c9e62677385a6dbdedaf81959a273eb7909ebde21ae5d12e2a584501a6756fe50ccb93b93f0d6ee721b6052a0d88431e62f410d608532868cdf3a6de26886559e94cc2677eea9bd797918b70e2717e95b45918bd1f86530cb9989e68b632c496becff848aa1956cd57ed46676a65ce6dd9783f230c8796909eef5583fcfe4acbf9c8b4ea33a08ec3fd417cf7175f434025d032567a00fc329aee154ca20f799b961fbab8f841cb7351f561a44aea45746ceaf56874dad99b63a7d7af2769d2f185e2d1c656cc6630b5aba98399fa57, 0xb50a77c03ac195225021dc18d930a352f27c0404742f961ca828c972737bad3ada74b1144657ab1d15fe1b8aefde8784ad61783f3c8d4584aa5f22a4eeca619f90563ae351b5da46770df182cf348d8e23b25fda07670c6609118e916a57ce4043608752c91515708327e36f5bb5ebd92cd4cfb39424167a679870202b23593aa524bac541a3ad322c38102a01e9659b06a4335c78d50739a51027954ac2bf03e500f975c2fa4d0ab5dd84cc9334f219d2ae933946583e384ed5dbf6498f214480ca66987b867df0f69d92e4e14071e4b8545212dd5e29ff0248ed751e168d78934da7930bcbe10e9a212128a68de5d749c61f5e424cf8cf6aa329674de0cf49c6f9b4c8b8cc3 - am = mp_copy(ai) - bm = mp_copy(bi) - self.assertEqual(int(mp_mul(am, bm)), ai * bi) - - # A regression test for a bug that came up during development - # of mpint.c, relating to an intermediate value overflowing - # its container. - ai, bi = (2**8512 * 2 // 3), (2**4224 * 11 // 15) - am = mp_copy(ai) - bm = mp_copy(bi) - self.assertEqual(int(mp_mul(am, bm)), ai * bi) - - def testAddInteger(self): - initial = mp_copy(4444444444444444444444444) - - x = mp_new(mp_max_bits(initial) + 64) - - # mp_{add,sub,copy}_integer_into should be able to cope with - # any uintmax_t. Test a number that requires more than 32 bits. - mp_add_integer_into(x, initial, 123123123123123) - self.assertEqual(int(x), 4444444444567567567567567) - mp_sub_integer_into(x, initial, 123123123123123) - self.assertEqual(int(x), 4444444444321321321321321) - mp_copy_integer_into(x, 123123123123123) - self.assertEqual(int(x), 123123123123123) - - # mp_mul_integer_into only takes a uint16_t integer input - mp_mul_integer_into(x, initial, 10001) - self.assertEqual(int(x), 44448888888888888888888884444) - - def testDivision(self): - divisors = [1, 2, 3, 2**16+1, 2**32-1, 2**32+1, 2**128-159, - 141421356237309504880168872420969807856967187537694807] - quotients = [0, 1, 2, 2**64-1, 2**64, 2**64+1, 17320508075688772935] - for d in divisors: - for q in quotients: - remainders = {0, 1, d-1, 2*d//3} - for r in sorted(remainders): - if r >= d: - continue # silly cases with tiny divisors - n = q*d + r - mq = mp_new(max(nbits(q), 1)) - mr = mp_new(max(nbits(r), 1)) - mp_divmod_into(n, d, mq, mr) - self.assertEqual(int(mq), q) - self.assertEqual(int(mr), r) - self.assertEqual(int(mp_div(n, d)), q) - self.assertEqual(int(mp_mod(n, d)), r) - - # Make sure divmod_into can handle not getting one - # of its output pointers (or even both). - mp_clear(mq) - mp_divmod_into(n, d, mq, None) - self.assertEqual(int(mq), q) - mp_clear(mr) - mp_divmod_into(n, d, None, mr) - self.assertEqual(int(mr), r) - mp_divmod_into(n, d, None, None) - # No tests we can do after that last one - we just - # insist that it isn't allowed to have crashed! - - def testNthRoot(self): - roots = [1, 13, 1234567654321, - 57721566490153286060651209008240243104215933593992] - tests = [] - tests.append((0, 2, 0, 0)) - tests.append((0, 3, 0, 0)) - for r in roots: - for n in 2, 3, 5: - tests.append((r**n, n, r, 0)) - tests.append((r**n+1, n, r, 1)) - tests.append((r**n-1, n, r-1, r**n - (r-1)**n - 1)) - for x, n, eroot, eremainder in tests: - with self.subTest(x=x): - mx = mp_copy(x) - remainder = mp_copy(mx) - root = mp_nthroot(x, n, remainder) - self.assertEqual(int(root), eroot) - self.assertEqual(int(remainder), eremainder) - self.assertEqual(int(mp_nthroot(2*10**100, 2, None)), - 141421356237309504880168872420969807856967187537694) - self.assertEqual(int(mp_nthroot(3*10**150, 3, None)), - 144224957030740838232163831078010958839186925349935) - - def testBitwise(self): - p = 0x3243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e - e = 0x2b7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190 - x = mp_new(nbits(p)) - - mp_and_into(x, p, e) - self.assertEqual(int(x), p & e) - - mp_or_into(x, p, e) - self.assertEqual(int(x), p | e) - - mp_xor_into(x, p, e) - self.assertEqual(int(x), p ^ e) - - mp_bic_into(x, p, e) - self.assertEqual(int(x), p & ~e) - - def testInversion(self): - # Test mp_invert_mod_2to. - testnumbers = [(mp_copy(n),n) for n in fibonacci_scattered() - if n & 1] - for power2 in [1, 2, 3, 5, 13, 32, 64, 127, 128, 129]: - for am, ai in testnumbers: - bm = mp_invert_mod_2to(am, power2) - bi = int(bm) - self.assertEqual(((ai * bi) & ((1 << power2) - 1)), 1) - - # mp_reduce_mod_2to is a much simpler function, but - # this is as good a place as any to test it. - rm = mp_copy(am) - mp_reduce_mod_2to(rm, power2) - self.assertEqual(int(rm), ai & ((1 << power2) - 1)) - - # Test mp_invert proper. - moduli = [2, 3, 2**16+1, 2**32-1, 2**32+1, 2**128-159, - 141421356237309504880168872420969807856967187537694807, - 2**128-1] - for m in moduli: - # Prepare a MontyContext for the monty_invert test below - # (unless m is even, in which case we can't) - mc = monty_new(m) if m & 1 else None - - to_invert = {1, 2, 3, 7, 19, m-1, 5*m//17, (m-1)//2, (m+1)//2} - for x in sorted(to_invert): - if gcd(x, m) != 1: - continue # filter out non-invertible cases - inv = int(mp_invert(x, m)) - assert x * inv % m == 1 - - # Test monty_invert too, while we're here - if mc is not None: - self.assertEqual( - int(monty_invert(mc, monty_import(mc, x))), - int(monty_import(mc, inv))) - - def testGCD(self): - powerpairs = [(0,0), (1,0), (1,1), (2,1), (2,2), (75,3), (17,23)] - for a2, b2 in powerpairs: - for a3, b3 in powerpairs: - for a5, b5 in powerpairs: - a = 2**a2 * 3**a3 * 5**a5 * 17 * 19 * 23 - b = 2**b2 * 3**b3 * 5**b5 * 65423 - d = 2**min(a2, b2) * 3**min(a3, b3) * 5**min(a5, b5) - - ma = mp_copy(a) - mb = mp_copy(b) - - self.assertEqual(int(mp_gcd(ma, mb)), d) - - md = mp_new(nbits(d)) - mA = mp_new(nbits(b)) - mB = mp_new(nbits(a)) - mp_gcd_into(ma, mb, md, mA, mB) - self.assertEqual(int(md), d) - A = int(mA) - B = int(mB) - self.assertEqual(a*A - b*B, d) - self.assertTrue(0 <= A < b//d) - self.assertTrue(0 <= B < a//d) - - self.assertEqual(mp_coprime(ma, mb), 1 if d==1 else 0) - - # Make sure gcd_into can handle not getting some - # of its output pointers. - mp_clear(md) - mp_gcd_into(ma, mb, md, None, None) - self.assertEqual(int(md), d) - mp_clear(mA) - mp_gcd_into(ma, mb, None, mA, None) - self.assertEqual(int(mA), A) - mp_clear(mB) - mp_gcd_into(ma, mb, None, None, mB) - self.assertEqual(int(mB), B) - mp_gcd_into(ma, mb, None, None, None) - # No tests we can do after that last one - we just - # insist that it isn't allowed to have crashed! - - def testMonty(self): - moduli = [5, 19, 2**16+1, 2**31-1, 2**128-159, 2**255-19, - 293828847201107461142630006802421204703, - 113064788724832491560079164581712332614996441637880086878209969852674997069759] - - for m in moduli: - mc = monty_new(m) - - # Import some numbers - inputs = [(monty_import(mc, n), n) - for n in sorted({0, 1, 2, 3, 2*m//3, m-1})] - - # Check modulus and identity - self.assertEqual(int(monty_modulus(mc)), m) - self.assertEqual(int(monty_identity(mc)), int(inputs[1][0])) - - # Check that all those numbers export OK - for mn, n in inputs: - self.assertEqual(int(monty_export(mc, mn)), n) - - for ma, a in inputs: - for mb, b in inputs: - xprod = int(monty_export(mc, monty_mul(mc, ma, mb))) - self.assertEqual(xprod, a*b % m) - - xsum = int(monty_export(mc, monty_add(mc, ma, mb))) - self.assertEqual(xsum, (a+b) % m) - - xdiff = int(monty_export(mc, monty_sub(mc, ma, mb))) - self.assertEqual(xdiff, (a-b) % m) - - # Test the ordinary mp_mod{add,sub,mul} at the - # same time, even though those don't do any - # montying at all - - xprod = int(mp_modmul(a, b, m)) - self.assertEqual(xprod, a*b % m) - - xsum = int(mp_modadd(a, b, m)) - self.assertEqual(xsum, (a+b) % m) - - xdiff = int(mp_modsub(a, b, m)) - self.assertEqual(xdiff, (a-b) % m) - - for ma, a in inputs: - # Compute a^0, a^1, a^1, a^2, a^3, a^5, ... - indices = list(fibonacci()) - powers = [int(monty_export(mc, monty_pow(mc, ma, power))) - for power in indices] - # Check the first two make sense - self.assertEqual(powers[0], 1) - self.assertEqual(powers[1], a) - # Check the others using the Fibonacci identity: - # F_n + F_{n+1} = F_{n+2}, so a^{F_n} a^{F_{n+1}} = a^{F_{n+2}} - for p0, p1, p2 in adjtuples(powers, 3): - self.assertEqual(p2, p0 * p1 % m) - - # Test the ordinary mp_modpow here as well, while - # we've got the machinery available - for index, power in zip(indices, powers): - self.assertEqual(int(mp_modpow(a, index, m)), power) - - # A regression test for a bug I encountered during initial - # development of mpint.c, in which an incomplete reduction - # happened somewhere in an intermediate value. - b, e, m = 0x2B5B93812F253FF91F56B3B4DAD01CA2884B6A80719B0DA4E2159A230C6009EDA97C5C8FD4636B324F9594706EE3AD444831571BA5E17B1B2DFA92DEA8B7E, 0x25, 0xC8FCFD0FD7371F4FE8D0150EFC124E220581569587CCD8E50423FA8D41E0B2A0127E100E92501E5EE3228D12EA422A568C17E0AD2E5C5FCC2AE9159D2B7FB8CB - assert(int(mp_modpow(b, e, m)) == pow(b, e, m)) - - # Make sure mp_modpow can handle a base larger than the - # modulus, by pre-reducing it - assert(int(mp_modpow(1<<877, 907, 999979)) == pow(2, 877*907, 999979)) - - def testModsqrt(self): - moduli = [ - 5, 19, 2**16+1, 2**31-1, 2**128-159, 2**255-19, - 293828847201107461142630006802421204703, - 113064788724832491560079164581712332614996441637880086878209969852674997069759, - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6FFFFFFFF00000001] - for p in moduli: - # Count the factors of 2 in the group. (That is, we want - # p-1 to be an odd multiple of 2^{factors_of_2}.) - factors_of_2 = nbits((p-1) & (1-p)) - 1 - assert (p & ((2 << factors_of_2)-1)) == ((1 << factors_of_2)+1) - - z = find_non_square_mod(p) - - sc = modsqrt_new(p, z) - - def ptest(x): - root, success = mp_modsqrt(sc, x) - r = int(root) - self.assertTrue(success) - self.assertEqual((r * r - x) % p, 0) - - def ntest(x): - root, success = mp_modsqrt(sc, x) - self.assertFalse(success) - - # Make up some more or less random values mod p to square - v1 = pow(3, nbits(p), p) - v2 = pow(5, v1, p) - test_roots = [0, 1, 2, 3, 4, 3*p//4, v1, v2, v1+1, 12873*v1, v1*v2] - known_squares = {r*r % p for r in test_roots} - for s in known_squares: - ptest(s) - if s != 0: - ntest(z*s % p) - - # Make sure we've tested a value that is in each of the - # subgroups of order (p-1)/2^k but not in the next one - # (with the exception of k=0, which just means 'have we - # tested a non-square?', which we have in the above loop). - # - # We do this by starting with a known non-square; then - # squaring it (factors_of_2) times will return values - # nested deeper and deeper in those subgroups. - vbase = z - for k in range(factors_of_2): - # Adjust vbase by an arbitrary odd power of - # z, so that it won't look too much like the previous - # value. - vbase = vbase * pow(z, (vbase + v1 + v2) | 1, p) % p - - # Move vbase into the next smaller group by squaring - # it. - vbase = pow(vbase, 2, p) - - ptest(vbase) - - def testShifts(self): - x = ((1<<900) // 9949) | 1 - for i in range(2049): - mp = mp_copy(x) - - mp_lshift_fixed_into(mp, mp, i) - self.assertEqual(int(mp), (x << i) & mp_mask(mp)) - - mp_copy_into(mp, x) - mp_lshift_safe_into(mp, mp, i) - self.assertEqual(int(mp), (x << i) & mp_mask(mp)) - - mp_copy_into(mp, x) - mp_rshift_fixed_into(mp, mp, i) - self.assertEqual(int(mp), x >> i) - - mp_copy_into(mp, x) - mp_rshift_safe_into(mp, mp, i) - self.assertEqual(int(mp), x >> i) - - self.assertEqual(int(mp_rshift_fixed(x, i)), x >> i) - - self.assertEqual(int(mp_rshift_safe(x, i)), x >> i) - - def testRandom(self): - # Test random_bits to ensure it correctly masks the return - # value, and uses exactly as many random bytes as we expect it - # to. - for bits in range(512): - bytes_needed = (bits + 7) // 8 - with queued_random_data(bytes_needed, "random_bits test"): - mp = mp_random_bits(bits) - self.assertTrue(int(mp) < (1 << bits)) - self.assertEqual(random_queue_len(), 0) - - # Test mp_random_in_range to ensure it returns things in the - # right range. - for rangesize in [2, 3, 19, 35]: - for lo in [0, 1, 0x10001, 1<<512]: - hi = lo + rangesize - bytes_needed = mp_max_bytes(hi) + 16 - for trial in range(rangesize*3): - with queued_random_data( - bytes_needed, - "random_in_range {:d}".format(trial)): - v = int(mp_random_in_range(lo, hi)) - self.assertTrue(lo <= v < hi) - -class ecc(MyTestBase): - def testWeierstrassSimple(self): - # Simple tests using a Weierstrass curve I made up myself, - # which (unlike the ones used for serious crypto) is small - # enough that you can fit all the coordinates for a curve on - # to your retina in one go. - - p = 3141592661 - a, b = -3 % p, 12345 - rc = WeierstrassCurve(p, a, b) - wc = ecc_weierstrass_curve(p, a, b, None) - - def check_point(wp, rp): - self.assertTrue(ecc_weierstrass_point_valid(wp)) - is_id = ecc_weierstrass_is_identity(wp) - x, y = ecc_weierstrass_get_affine(wp) - if rp.infinite: - self.assertEqual(is_id, 1) - else: - self.assertEqual(is_id, 0) - self.assertEqual(int(x), int(rp.x)) - self.assertEqual(int(y), int(rp.y)) - - def make_point(x, y): - wp = ecc_weierstrass_point_new(wc, x, y) - rp = rc.point(x, y) - check_point(wp, rp) - return wp, rp - - # Some sample points, including the identity and also a pair - # of mutual inverses. - wI, rI = ecc_weierstrass_point_new_identity(wc), rc.point() - wP, rP = make_point(102, 387427089) - wQ, rQ = make_point(1000, 546126574) - wmP, rmP = make_point(102, p - 387427089) - - # Check the simple arithmetic functions. - check_point(ecc_weierstrass_add(wP, wQ), rP + rQ) - check_point(ecc_weierstrass_add(wQ, wP), rP + rQ) - check_point(ecc_weierstrass_double(wP), rP + rP) - check_point(ecc_weierstrass_double(wQ), rQ + rQ) - - # Check all the special cases with add_general: - # Adding two finite unequal non-mutually-inverse points - check_point(ecc_weierstrass_add_general(wP, wQ), rP + rQ) - # Doubling a finite point - check_point(ecc_weierstrass_add_general(wP, wP), rP + rP) - check_point(ecc_weierstrass_add_general(wQ, wQ), rQ + rQ) - # Adding the identity to a point (both ways round) - check_point(ecc_weierstrass_add_general(wI, wP), rP) - check_point(ecc_weierstrass_add_general(wI, wQ), rQ) - check_point(ecc_weierstrass_add_general(wP, wI), rP) - check_point(ecc_weierstrass_add_general(wQ, wI), rQ) - # Doubling the identity - check_point(ecc_weierstrass_add_general(wI, wI), rI) - # Adding a point to its own inverse, giving the identity. - check_point(ecc_weierstrass_add_general(wmP, wP), rI) - check_point(ecc_weierstrass_add_general(wP, wmP), rI) - - # Verify that point_valid fails if we pass it nonsense. - bogus = ecc_weierstrass_point_new(wc, int(rP.x), int(rP.y * 3)) - self.assertFalse(ecc_weierstrass_point_valid(bogus)) - - # Re-instantiate the curve with the ability to take square - # roots, and check that we can reconstruct P and Q from their - # x coordinate and y parity only. - wc = ecc_weierstrass_curve(p, a, b, find_non_square_mod(p)) - - x, yp = int(rP.x), (int(rP.y) & 1) - check_point(ecc_weierstrass_point_new_from_x(wc, x, yp), rP) - check_point(ecc_weierstrass_point_new_from_x(wc, x, yp ^ 1), rmP) - x, yp = int(rQ.x), (int(rQ.y) & 1) - check_point(ecc_weierstrass_point_new_from_x(wc, x, yp), rQ) - - def testMontgomerySimple(self): - p, a, b = 3141592661, 0xabc, 0xde - - rc = MontgomeryCurve(p, a, b) - mc = ecc_montgomery_curve(p, a, b) - - rP = rc.cpoint(0x1001) - rQ = rc.cpoint(0x20001) - rdiff = rP - rQ - rsum = rP + rQ - - def make_mpoint(rp): - return ecc_montgomery_point_new(mc, int(rp.x)) - - mP = make_mpoint(rP) - mQ = make_mpoint(rQ) - mdiff = make_mpoint(rdiff) - msum = make_mpoint(rsum) - - def check_point(mp, rp): - x = ecc_montgomery_get_affine(mp) - self.assertEqual(int(x), int(rp.x)) - - check_point(ecc_montgomery_diff_add(mP, mQ, mdiff), rsum) - check_point(ecc_montgomery_diff_add(mQ, mP, mdiff), rsum) - check_point(ecc_montgomery_diff_add(mP, mQ, msum), rdiff) - check_point(ecc_montgomery_diff_add(mQ, mP, msum), rdiff) - check_point(ecc_montgomery_double(mP), rP + rP) - check_point(ecc_montgomery_double(mQ), rQ + rQ) - - zero = ecc_montgomery_point_new(mc, 0) - self.assertEqual(ecc_montgomery_is_identity(zero), False) - identity = ecc_montgomery_double(zero) - ecc_montgomery_get_affine(identity) - self.assertEqual(ecc_montgomery_is_identity(identity), True) - - def testEdwardsSimple(self): - p, d, a = 3141592661, 2688750488, 367934288 - - rc = TwistedEdwardsCurve(p, d, a) - ec = ecc_edwards_curve(p, d, a, None) - - def check_point(ep, rp): - x, y = ecc_edwards_get_affine(ep) - self.assertEqual(int(x), int(rp.x)) - self.assertEqual(int(y), int(rp.y)) - - def make_point(x, y): - ep = ecc_edwards_point_new(ec, x, y) - rp = rc.point(x, y) - check_point(ep, rp) - return ep, rp - - # Some sample points, including the identity and also a pair - # of mutual inverses. - eI, rI = make_point(0, 1) - eP, rP = make_point(196270812, 1576162644) - eQ, rQ = make_point(1777630975, 2717453445) - emP, rmP = make_point(p - 196270812, 1576162644) - - # Check that the ordinary add function handles all the special - # cases. - - # Adding two finite unequal non-mutually-inverse points - check_point(ecc_edwards_add(eP, eQ), rP + rQ) - check_point(ecc_edwards_add(eQ, eP), rP + rQ) - # Doubling a finite point - check_point(ecc_edwards_add(eP, eP), rP + rP) - check_point(ecc_edwards_add(eQ, eQ), rQ + rQ) - # Adding the identity to a point (both ways round) - check_point(ecc_edwards_add(eI, eP), rP) - check_point(ecc_edwards_add(eI, eQ), rQ) - check_point(ecc_edwards_add(eP, eI), rP) - check_point(ecc_edwards_add(eQ, eI), rQ) - # Doubling the identity - check_point(ecc_edwards_add(eI, eI), rI) - # Adding a point to its own inverse, giving the identity. - check_point(ecc_edwards_add(emP, eP), rI) - check_point(ecc_edwards_add(eP, emP), rI) - - # Re-instantiate the curve with the ability to take square - # roots, and check that we can reconstruct P and Q from their - # y coordinate and x parity only. - ec = ecc_edwards_curve(p, d, a, find_non_square_mod(p)) - - y, xp = int(rP.y), (int(rP.x) & 1) - check_point(ecc_edwards_point_new_from_y(ec, y, xp), rP) - check_point(ecc_edwards_point_new_from_y(ec, y, xp ^ 1), rmP) - y, xp = int(rQ.y), (int(rQ.x) & 1) - check_point(ecc_edwards_point_new_from_y(ec, y, xp), rQ) - - # For testing point multiplication, let's switch to the full-sized - # standard curves, because I want to have tested those a bit too. - - def testWeierstrassMultiply(self): - wc = ecc_weierstrass_curve(p256.p, int(p256.a), int(p256.b), None) - wG = ecc_weierstrass_point_new(wc, int(p256.G.x), int(p256.G.y)) - self.assertTrue(ecc_weierstrass_point_valid(wG)) - - ints = set(i % p256.p for i in fibonacci_scattered(10)) - ints.remove(0) # the zero multiple isn't expected to work - for i in sorted(ints): - wGi = ecc_weierstrass_multiply(wG, i) - x, y = ecc_weierstrass_get_affine(wGi) - rGi = p256.G * i - self.assertEqual(int(x), int(rGi.x)) - self.assertEqual(int(y), int(rGi.y)) - - def testMontgomeryMultiply(self): - mc = ecc_montgomery_curve( - curve25519.p, int(curve25519.a), int(curve25519.b)) - mG = ecc_montgomery_point_new(mc, int(curve25519.G.x)) - - ints = set(i % p256.p for i in fibonacci_scattered(10)) - ints.remove(0) # the zero multiple isn't expected to work - for i in sorted(ints): - mGi = ecc_montgomery_multiply(mG, i) - x = ecc_montgomery_get_affine(mGi) - rGi = curve25519.G * i - self.assertEqual(int(x), int(rGi.x)) - - def testEdwardsMultiply(self): - ec = ecc_edwards_curve(ed25519.p, int(ed25519.d), int(ed25519.a), None) - eG = ecc_edwards_point_new(ec, int(ed25519.G.x), int(ed25519.G.y)) - - ints = set(i % ed25519.p for i in fibonacci_scattered(10)) - ints.remove(0) # the zero multiple isn't expected to work - for i in sorted(ints): - eGi = ecc_edwards_multiply(eG, i) - x, y = ecc_edwards_get_affine(eGi) - rGi = ed25519.G * i - self.assertEqual(int(x), int(rGi.x)) - self.assertEqual(int(y), int(rGi.y)) - -class keygen(MyTestBase): - def testPrimeCandidateSource(self): - def inspect(pcs): - # Returns (pcs->limit, pcs->factor, pcs->addend) as Python integers - return tuple(map(int, pcs_inspect(pcs))) - - # Test accumulating modular congruence requirements, by - # inspecting the internal values computed during - # require_residue. We ensure that the addend satisfies all our - # congruences and the factor is the lcm of all the moduli - # (hence, the arithmetic progression defined by those - # parameters is precisely the set of integers satisfying the - # requirements); we also ensure that the limiting values - # (addend itself at the low end, and addend + (limit-1) * - # factor at the high end) are the maximal subsequence of that - # progression that are within the originally specified range. - - def check(pcs, lo, hi, mod_res_pairs): - limit, factor, addend = inspect(pcs) - - for mod, res in mod_res_pairs: - self.assertEqual(addend % mod, res % mod) - - self.assertEqual(factor, functools.reduce( - lcm, [mod for mod, res in mod_res_pairs])) - - self.assertFalse(lo <= addend + (-1) * factor < hi) - self.assertTrue (lo <= addend < hi) - self.assertTrue (lo <= addend + (limit-1) * factor < hi) - self.assertFalse(lo <= addend + limit * factor < hi) - - pcs = pcs_new(64) - check(pcs, 2**63, 2**64, [(2, 1)]) - pcs_require_residue(pcs, 3, 2) - check(pcs, 2**63, 2**64, [(2, 1), (3, 2)]) - pcs_require_residue_1(pcs, 7) - check(pcs, 2**63, 2**64, [(2, 1), (3, 2), (7, 1)]) - pcs_require_residue(pcs, 16, 7) - check(pcs, 2**63, 2**64, [(2, 1), (3, 2), (7, 1), (16, 7)]) - pcs_require_residue(pcs, 49, 8) - check(pcs, 2**63, 2**64, [(2, 1), (3, 2), (7, 1), (16, 7), (49, 8)]) - - # Now test-generate some actual values, and ensure they - # satisfy all the congruences, and also avoid one residue mod - # 5 that we told them to. Also, give a nontrivial range. - pcs = pcs_new_with_firstbits(64, 0xAB, 8) - pcs_require_residue(pcs, 0x100, 0xCD) - pcs_require_residue_1(pcs, 65537) - pcs_avoid_residue_small(pcs, 5, 3) - pcs_ready(pcs) - with random_prng("test seed"): - for i in range(100): - n = int(pcs_generate(pcs)) - self.assertTrue((0xAB<<56) < n < (0xAC<<56)) - self.assertEqual(n % 0x100, 0xCD) - self.assertEqual(n % 65537, 1) - self.assertNotEqual(n % 5, 3) - - # I'm not actually testing here that the outputs of - # pcs_generate are non-multiples of _all_ primes up to - # 2^16. But checking this many for 100 turns is enough - # to be pretty sure. (If you take the product of - # (1-1/p) over all p in the list below, you find that - # a given random number has about a 13% chance of - # avoiding being a multiple of any of them. So 100 - # trials without a mistake gives you 0.13^100 < 10^-88 - # as the probability of it happening by chance. More - # likely the code is actually working :-) - - for p in [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61]: - self.assertNotEqual(n % p, 0) - - def testPocklePositive(self): - def add_small(po, *ps): - for p in ps: - self.assertEqual(pockle_add_small_prime(po, p), 'POCKLE_OK') - def add(po, *args): - self.assertEqual(pockle_add_prime(po, *args), 'POCKLE_OK') - - # Transcription of the proof that 2^130-5 is prime from - # Theorem 3.1 from http://cr.yp.to/mac/poly1305-20050329.pdf - po = pockle_new() - p1 = (2**130 - 6) // 1517314646 - p2 = (p1 - 1) // 222890620702 - add_small(po, 37003, 221101) - add(po, p2, [37003, 221101], 2) - add(po, p1, [p2], 2) - add(po, 2**130 - 5, [p1], 2) - - # My own proof that 2^255-19 is prime - po = pockle_new() - p1 = 8574133 - p2 = 1919519569386763 - p3 = 75445702479781427272750846543864801 - p4 = (2**255 - 20) // (65147*12) - p = 2**255 - 19 - add_small(po, p1) - add(po, p2, [p1], 2) - add(po, p3, [p2], 2) - add(po, p4, [p3], 2) - add(po, p, [p4], 2) - - # And the prime used in Ed448, while I'm here - po = pockle_new() - p1 = 379979 - p2 = 1764234391 - p3 = 97859369123353 - p4 = 34741861125639557 - p5 = 36131535570665139281 - p6 = 167773885276849215533569 - p7 = 596242599987116128415063 - p = 2**448 - 2**224 - 1 - add_small(po, p1, p2) - add(po, p3, [p1], 2) - add(po, p4, [p2], 2) - add(po, p5, [p4], 2) - add(po, p6, [p3], 3) - add(po, p7, [p5], 3) - add(po, p, [p6, p7], 2) - - p = 4095744004479977 - factors = [2, 79999] # just enough factors to exceed cbrt(p) - po = pockle_new() - for q in factors: - add_small(po, q) - add(po, p, factors, 3) - - # The order of the generator in Ed25519 - po = pockle_new() - p1a, p1b = 132667, 137849 - p2 = 3044861653679985063343 - p3 = 198211423230930754013084525763697 - p = 2**252 + 0x14def9dea2f79cd65812631a5cf5d3ed - add_small(po, p1a, p1b) - add(po, p2, [p1a, p1b], 2) - add(po, p3, [p2], 2) - add(po, p, [p3], 2) - - # And the one in Ed448 - po = pockle_new() - p1 = 766223 - p2 = 3009341 - p3 = 7156907 - p4 = 671065561 - p5 = 342682509629 - p6 = 6730519843040614479184435237013 - p = 2**446 - 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d - add_small(po, p1, p2, p3, p4) - add(po, p5, [p1], 2) - add(po, p6, [p3,p4], 2) - add(po, p, [p2,p5,p6], 2) - - # Combined certificate for the moduli and generator orders of - # the three NIST curves, generated by contrib/proveprime.py - # (with some cosmetic tidying) - p256 = 2**256 - 2**224 + 2**192 + 2**96 - 1 - p384 = 2**384 - 2**128 - 2**96 + 2**32 - 1 - p521 = 2**521 - 1 - order256 = p256 - 0x4319055358e8617b0c46353d039cdaae - order384 = p384 - 0x389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68c - t = 0x5ae79787c40d069948033feb708f65a2fc44a36477663b851449048e16ec79bf6 - order521 = p521 - t - p0 = order384 // 12895580879789762060783039592702 - p1 = 1059392654943455286185473617842338478315215895509773412096307 - p2 = 55942463741690639 - p3 = 37344768852931 - p4 = order521 // 1898873518475180724503002533770555108536 - p5 = p4 // 994165722 - p6 = 144471089338257942164514676806340723 - p7 = p384 // 2054993070433694 - p8 = 1357291859799823621 - po = pockle_new() - add_small(po, 2, 3, 5, 11, 17, 19, 31, 41, 53, 67, 71, 109, 131, 149, - 157, 257, 521, 641, 1613, 2731, 3407, 6317, 8191, 8389, - 14461, 17449, 38189, 38557, 42641, 51481, 61681, 65537, - 133279, 248431, 312289, 409891, 490463, 858001, 6700417, - 187019741) - add(po, p3, [149, 11, 5, 3, 2], 3) - add(po, p2, [p3], 2) - add(po, p8, [6317, 67, 2, 2], 2) - add(po, p6, [133279, 14461, 109, 3], 7) - add(po, p1, [p2, 248431], 2) - add(po, order256, [187019741, 38189, 17449, 3407, 131, 71, 2, 2, 2, 2], - 7) - add(po, p256, [6700417, 490463, 65537, 641, 257, 17, 5, 5, 3, 2], 6) - add(po, p0, [p1], 2) - add(po, p7, [p8, 312289, 38557, 8389, 11, 2], 3) - add(po, p5, [p6, 19], 2) - add(po, order384, [p0], 2) - add(po, p384, [p7], 2) - add(po, p4, [p5], 2) - add(po, order521, [p4], 2) - add(po, p521, [858001, 409891, 61681, 51481, 42641, 8191, 2731, 1613, - 521, 157, 131, 53, 41, 31, 17, 11, 5, 5, 3, 2], 3) - - def testPockleNegative(self): - def add_small(po, p): - self.assertEqual(pockle_add_small_prime(po, p), 'POCKLE_OK') - - po = pockle_new() - self.assertEqual(pockle_add_small_prime(po, 0), - 'POCKLE_PRIME_SMALLER_THAN_2') - self.assertEqual(pockle_add_small_prime(po, 1), - 'POCKLE_PRIME_SMALLER_THAN_2') - self.assertEqual(pockle_add_small_prime(po, 2**61 - 1), - 'POCKLE_SMALL_PRIME_NOT_SMALL') - self.assertEqual(pockle_add_small_prime(po, 4), - 'POCKLE_SMALL_PRIME_NOT_PRIME') - - po = pockle_new() - self.assertEqual(pockle_add_prime(po, 1919519569386763, [8574133], 2), - 'POCKLE_FACTOR_NOT_KNOWN_PRIME') - - po = pockle_new() - add_small(po, 8574133) - self.assertEqual(pockle_add_prime(po, 1919519569386765, [8574133], 2), - 'POCKLE_FACTOR_NOT_A_FACTOR') - - p = 4095744004479977 - factors = [2, 79997] # not quite enough factors to reach cbrt(p) - po = pockle_new() - for q in factors: - add_small(po, q) - self.assertEqual(pockle_add_prime(po, p, factors, 3), - 'POCKLE_PRODUCT_OF_FACTORS_TOO_SMALL') - - p = 1999527 * 3999053 - factors = [999763] - po = pockle_new() - for q in factors: - add_small(po, q) - self.assertEqual(pockle_add_prime(po, p, factors, 3), - 'POCKLE_DISCRIMINANT_IS_SQUARE') - - p = 9999929 * 9999931 - factors = [257, 2593] - po = pockle_new() - for q in factors: - add_small(po, q) - self.assertEqual(pockle_add_prime(po, p, factors, 3), - 'POCKLE_FERMAT_TEST_FAILED') - - p = 1713000920401 # a Carmichael number - po = pockle_new() - add_small(po, 561787) - self.assertEqual(pockle_add_prime(po, p, [561787], 2), - 'POCKLE_WITNESS_POWER_IS_1') - - p = 4294971121 - factors = [3, 5, 11, 17] - po = pockle_new() - for q in factors: - add_small(po, q) - self.assertEqual(pockle_add_prime(po, p, factors, 17), - 'POCKLE_WITNESS_POWER_NOT_COPRIME') - - po = pockle_new() - add_small(po, 2) - self.assertEqual(pockle_add_prime(po, 1, [2], 1), - 'POCKLE_PRIME_SMALLER_THAN_2') - - def testMillerRabin(self): - # A prime congruent to 3 mod 4, so M-R can only do one - # iteration: either a^{(p-1)/2} == +1, or -1. Either counts as - # a pass; the latter also means the number is potentially a - # primitive root. - n = 0xe76e6aaa42b5d7423aa4da5613eb21c3 - mr = miller_rabin_new(n) - self.assertEqual(miller_rabin_test(mr, 2), "passed+ppr") - self.assertEqual(miller_rabin_test(mr, 4), "passed") - - # The 'potential primitive root' test only means that M-R - # didn't _rule out_ the number being a primitive root, by - # finding that any of the powers _it tested_ less than n-1 - # came out to be 1. In this case, 2 really is a primitive - # root, but since 13 | n-1, the 13th powers mod n form a - # multiplicative subgroup. So 2^13 is not a primitive root, - # and yet, M-R can't tell the difference, because it only - # tried the exponent (n-1)/2, not the actual counterexample - # (n-1)/13. - self.assertEqual(miller_rabin_test(mr, 2**13), "passed+ppr") - - # A prime congruent to 1 mod a reasonably large power of 2, so - # M-R has lots of scope to have different things happen. 3 is - # a primitive root, so we expect that 3, 3^2, 3^4, ..., 3^256 - # should all pass for different reasons, with only the first - # of them returning passed+ppr. - n = 0xb1b65ebe489ff0ab4597bb67c3d22d01 - mr = miller_rabin_new(n) - w = 3 - self.assertEqual(miller_rabin_test(mr, w), "passed+ppr") - for i in range(1, 10): - w = w * w % n - self.assertEqual(miller_rabin_test(mr, w), "passed") - - # A prime with an _absurdly_ large power-of-2 factor in its - # multiplicative group. - n = 0x600000000000000000000000000000000000000000000001 - mr = miller_rabin_new(n) - w = 10 - self.assertEqual(miller_rabin_test(mr, w), "passed+ppr") - for i in range(1, 200): - w = w * w % n - self.assertEqual(miller_rabin_test(mr, w), "passed") - - # A blatantly composite number. But we still expect to see a - # pass if we give the witness 1 (which will give a maximal - # trailing string of 1s), or -1 (which will give -1 when - # raised to the maximal odd factor of n-1, or indeed any other - # odd power). - n = 0x1010101010101010101010101010101 - mr = miller_rabin_new(n) - self.assertEqual(miller_rabin_test(mr, 1), "passed") - self.assertEqual(miller_rabin_test(mr, n-1), "passed") - self.assertEqual(miller_rabin_test(mr, 2), "failed") - - # A Carmichael number, as a proper test that M-R detects - # things the Fermat test would not. - # - # (Its prime factorisation is 26823115100268314289505807 * - # 53646230200536628579011613 * 80469345300804942868517419, - # which is enough to re-check its Carmichaelness.) - n = 0xffffffffffffffffcf8032f3e044b4a8b1b1bf0b526538eae953d90f44d65511 - mr = miller_rabin_new(n) - self.assertEqual(miller_rabin_test(mr, 16), "passed") - assert(pow(2, n-1, n) == 1) # Fermat test would pass, but ... - self.assertEqual(miller_rabin_test(mr, 2), "failed") # ... this fails - - # A white-box test for the side-channel-safe M-R - # implementation, which has to check a^e against +-1 for every - # exponent e of the form floor((n-1) / power of 2), so as to - # avoid giving away exactly how many of the trailing values of - # that sequence are significant to the test. - # - # When the power of 2 is large enough that the division was - # not exact, the results of these comparisons are _not_ - # significant to the test, and we're required to ignore them! - # - # This pair of values has the property that none of the values - # legitimately computed by M-R is either +1 _or_ -1, but if - # you shift n-1 right by one too many bits (losing the lowest - # set bit of 0x6d00 to get 0x36), then _that_ power of the - # witness integer is -1. This should not cause a spurious pass. - n = 0x6d01 - mr = miller_rabin_new(n) - self.assertEqual(miller_rabin_test(mr, 0x251), "failed") - -class ntru(MyTestBase): - def testMultiply(self): - self.assertEqual( - ntru_ring_multiply([1,1,1,1,1,1], [1,1,1,1,1,1], 11, 59), - [1,2,3,4,5,6,5,4,3,2,1]) - self.assertEqual(ntru_ring_multiply( - [1,0,1,2,0,0,1,2,0,1,2], [2,0,0,1,0,1,2,2,2,0,2], 11, 3), - [1,0,0,0,0,0,0,0,0,0,0]) - - def testInvert(self): - # Over GF(3), x^11-x-1 factorises as - # (x^3+x^2+2) * (x^8+2*x^7+x^6+2*x^4+2*x^3+x^2+x+1) - # so we expect that 2,0,1,1 has no inverse, being one of those factors. - self.assertEqual(ntru_ring_invert([0], 11, 3), None) - self.assertEqual(ntru_ring_invert([1], 11, 3), - [1,0,0,0,0,0,0,0,0,0,0]) - self.assertEqual(ntru_ring_invert([2,0,1,1], 11, 3), None) - self.assertEqual(ntru_ring_invert([1,0,1,2,0,0,1,2,0,1,2], 11, 3), - [2,0,0,1,0,1,2,2,2,0,2]) - - self.assertEqual(ntru_ring_invert([1,0,1,2,0,0,1,2,0,1,2], 11, 59), - [1,26,10,1,38,48,34,37,53,3,53]) - - def testMod3Round3(self): - # Try a prime congruent to 1 mod 3 - self.assertEqual(ntru_mod3([4,5,6,0,1,2,3], 7, 7), - [0,1,-1,0,1,-1,0]) - self.assertEqual(ntru_round3([4,5,6,0,1,2,3], 7, 7), - [-3,-3,0,0,0,3,3]) - - # And one congruent to 2 mod 3 - self.assertEqual(ntru_mod3([6,7,8,9,10,0,1,2,3,4,5], 11, 11), - [1,-1,0,1,-1,0,1,-1,0,1,-1]) - self.assertEqual(ntru_round3([6,7,8,9,10,0,1,2,3,4,5], 11, 11), - [-6,-3,-3,-3,0,0,0,3,3,3,6]) - - def testBiasScale(self): - self.assertEqual(ntru_bias([0,1,2,3,4,5,6,7,8,9,10], 4, 11, 11), - [4,5,6,7,8,9,10,0,1,2,3]) - self.assertEqual(ntru_scale([0,1,2,3,4,5,6,7,8,9,10], 4, 11, 11), - [0,4,8,1,5,9,2,6,10,3,7]) - - def testEncode(self): - # Test a small case. Worked through in detail: - # - # Pass 1: - # Input list is (89:123, 90:234, 344:345, 432:456, 222:567) - # (89:123, 90:234) -> (89+123*90 : 123*234) = (11159:28782) - # Emit low byte of 11159 = 0x97, and get (43:113) - # (344:345, 432:456) -> (344+345*432 : 345*456) = (149384:157320) - # Emit low byte of 149384 = 0x88, and get (583:615) - # Odd pair (222:567) is copied to end of new list - # Final list is (43:113, 583:615, 222:567) - # Pass 2: - # Input list is (43:113, 583:615, 222:567) - # (43:113, 583:615) -> (43+113*583, 113*615) = (65922:69495) - # Emit low byte of 65922 = 0x82, and get (257:272) - # Odd pair (222:567) is copied to end of new list - # Final list is (257:272, 222:567) - # Pass 3: - # Input list is (257:272, 222:567) - # (257:272, 222:567) -> (257+272*222, 272*567) = (60641:154224) - # Emit low byte of 60641 = 0xe1, and get (236:603) - # Final list is (236:603) - # Cleanup: - # Emit low byte of 236 = 0xec, and get (0:3) - # Emit low byte of 0 = 0x00, and get (0:1) - - ms = [123,234,345,456,567] - rs = [89,90,344,432,222] - encoding = unhex('978882e1ec00') - sched = ntru_encode_schedule(ms) - self.assertEqual(sched.encode(rs), encoding) - self.assertEqual(sched.decode(encoding), rs) - - # Encode schedules for sntrup761 public keys and ciphertexts - pubsched = ntru_encode_schedule([4591]*761) - self.assertEqual(pubsched.length(), 1158) - ciphersched = ntru_encode_schedule([1531]*761) - self.assertEqual(ciphersched.length(), 1007) - - # Test round-trip encoding using those schedules - testlist = list(range(761)) - pubtext = pubsched.encode(testlist) - self.assertEqual(pubsched.decode(pubtext), testlist) - ciphertext = ciphersched.encode(testlist) - self.assertEqual(ciphersched.decode(ciphertext), testlist) - - def testCore(self): - # My own set of NTRU Prime parameters, satisfying all the - # requirements and tiny enough for convenient testing - p, q, w = 11, 59, 3 - - with random_prng('ntru keygen seed'): - keypair = ntru_keygen(p, q, w) - plaintext = ntru_gen_short(p, w) - - ciphertext = ntru_encrypt(plaintext, ntru_pubkey(keypair), p, q) - recovered = ntru_decrypt(ciphertext, keypair) - self.assertEqual(plaintext, recovered) - -class crypt(MyTestBase): - def testSSH1Fingerprint(self): - # Example key and reference fingerprint value generated by - # OpenSSH 6.7 ssh-keygen - rsa = rsa_bare(65537, 984185866443261798625575612408956568591522723900235822424492423996716524817102482330189709310179009158443944785704183009867662230534501187034891091310377917105259938712348098594526746211645472854839799025154390701673823298369051411) - fp = rsa_ssh1_fingerprint(rsa) - self.assertEqual( - fp, b"768 96:12:c8:bc:e6:03:75:86:e8:c7:b9:af:d8:0c:15:75") - - def testSSH2Fingerprints(self): - # A sensible key blob that we can make sense of. - sensible_blob = b64( - 'AAAAC3NzaC1lZDI1NTE5AAAAICWiV0VAD4lQ7taUN7vZ5Rkc' - 'SLJBW5ubn6ZINwCOzpn3') - self.assertEqual(ssh2_fingerprint_blob(sensible_blob, "sha256"), - b'ssh-ed25519 255 SHA256:' - b'E4VmaHW0sUF7SUgSEOmMJ8WBtt0e/j3zbsKvyqfFnu4') - self.assertEqual(ssh2_fingerprint_blob(sensible_blob, "md5"), - b'ssh-ed25519 255 ' - b'35:73:80:df:a3:2c:1a:f2:2c:a6:5c:84:ce:48:6a:7e') - - # A key blob with an unknown algorithm name, so that we can't - # extract the bit count. - silly_blob = ssh_string(b'foo') + ssh_string(b'key data') - self.assertEqual(ssh2_fingerprint_blob(silly_blob, "sha256"), - b'foo SHA256:' - b'mvfJTB4PaRI7hxYaYwn0sH8G6zW1HbLkbWnZE2YIKc4') - self.assertEqual(ssh2_fingerprint_blob(silly_blob, "md5"), - b'foo ' - b'5f:5f:97:94:97:be:01:5c:f6:3f:e3:6e:55:46:ea:52') - - # A key blob without even a valid algorithm-name string at the start. - very_silly_blob = b'foo' - self.assertEqual(ssh2_fingerprint_blob(very_silly_blob, "sha256"), - b'SHA256:' - b'LCa0a2j/xo/5m0U8HTBBNBNCLXBkg7+g+YpeiGJm564') - self.assertEqual(ssh2_fingerprint_blob(very_silly_blob, "md5"), - b'ac:bd:18:db:4c:c2:f8:5c:ed:ef:65:4f:cc:c4:a4:d8') - - # A certified key. - cert_blob = b64( - 'AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIJ4Ds9YwRHxs' - 'xdtUitRbZGz0MgKGZSBVrTHI1AbvetofAAAAIMt0/CMBL+64GQ/r/JyGxo6oHs86' - 'i9bOHhMJYbDbxEJfAAAAAAAAAG8AAAABAAAAAmlkAAAADAAAAAh1c2VybmFtZQAA' - 'AAAAAAPoAAAAAAAAB9AAAAAAAAAAAAAAAAAAAAE+AAAAIHNzaC1lZDI1NTE5LWNl' - 'cnQtdjAxQG9wZW5zc2guY29tAAAAICl5MiUNt8hoAAHT0v00JYOkWe2UW31+Qq5Q' - 'HYKWGyVjAAAAIMUJEFAmSV/qtoxSmVOHUgTMKYjqkDy8fTfsfCKV+sN7AAAAAAAA' - 'AG8AAAABAAAAAmlkAAAAEgAAAA5kb2Vzbid0IG1hdHRlcgAAAAAAAAPoAAAAAAAA' - 'B9AAAAAAAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIMUJEFAmSV/qtoxS' - 'mVOHUgTMKYjqkDy8fTfsfCKV+sN7AAAAUwAAAAtzc2gtZWQyNTUxOQAAAEAXbRz3' - 'lBmoU4FVge29jn04MfubF6U0CoPG1nbeZSgDN2iz7qtZ75XIk5O/Z/W9nA8jwsiz' - 'iSEMItjvR7HEN8MIAAAAUwAAAAtzc2gtZWQyNTUxOQAAAECszhkY8bUbSCjmHEMP' - 'LjcOX6OaeBzPIYYYXJzpLn+m+CIwDXRIxyvON5/d/TomgAFNJutfOEsqIzy5OAvl' - 'p5IO') - self.assertEqual(ssh2_fingerprint_blob(cert_blob, "sha256"), - b'ssh-ed25519-cert-v01@openssh.com 255 ' - b'SHA256:42JaqhHUNa5CoKxGWqtKXF0Awz7b0aPrtgBZ9VLLHfY') - self.assertEqual(ssh2_fingerprint_blob(cert_blob, "md5"), - b'ssh-ed25519-cert-v01@openssh.com 255 ' - b'8e:40:00:e0:1f:4a:9c:b3:c8:e9:05:59:04:03:44:b3') - self.assertEqual(ssh2_fingerprint_blob(cert_blob, "sha256-cert"), - b'ssh-ed25519-cert-v01@openssh.com 255 ' - b'SHA256:W/+SDEg7S+/dAn4SrodJ2c8bYvt13XXA7YYlQ6E8R5U') - self.assertEqual(ssh2_fingerprint_blob(cert_blob, "md5-cert"), - b'ssh-ed25519-cert-v01@openssh.com 255 ' - b'03:cf:aa:8e:aa:c3:a0:97:bb:2e:7e:57:9d:08:b5:be') - - - def testAES(self): - # My own test cases, generated by a mostly independent - # reference implementation of AES in Python. ('Mostly' - # independent in that it was written by me.) - - def vector(cipherbase, key, iv, plaintext, ciphertext): - for cipher in get_implementations(cipherbase): - c = ssh_cipher_new(cipher) - if c is None: return # skip test if HW AES not available - ssh_cipher_setkey(c, key) - ssh_cipher_setiv(c, iv) - self.assertEqualBin( - ssh_cipher_encrypt(c, plaintext), ciphertext) - ssh_cipher_setiv(c, iv) - self.assertEqualBin( - ssh_cipher_decrypt(c, ciphertext), plaintext) - - # Tests of CBC mode. - - key = unhex( - '98483c6eb40b6c31a448c22a66ded3b5e5e8d5119cac8327b655c8b5c4836489') - iv = unhex('38f87b0b9b736160bfc0cbd8447af6ee') - plaintext = unhex(''' - ee16271827b12d828f61d56fddccc38ccaa69601da2b36d3af1a34c51947b71a - 362f05e07bf5e7766c24599799b252ad2d5954353c0c6ca668c46779c2659c94 - 8df04e4179666e335470ff042e213c8bcff57f54842237fbf9f3c7e6111620ac - 1c007180edd25f0e337c2a49d890a7173f6b52d61e3d2a21ddc8e41513a0e825 - afd5932172270940b01014b5b7fb8495946151520a126518946b44ea32f9b2a9 - ''') - - vector('aes128_cbc', key[:16], iv, plaintext, unhex(''' - 547ee90514cb6406d5bb00855c8092892c58299646edda0b4e7c044247795c8d - 3c3eb3d91332e401215d4d528b94a691969d27b7890d1ae42fe3421b91c989d5 - 113fefa908921a573526259c6b4f8e4d90ea888e1d8b7747457ba3a43b5b79b9 - 34873ebf21102d14b51836709ee85ed590b7ca618a1e884f5c57c8ea73fe3d0d - 6bf8c082dd602732bde28131159ed0b6e9cf67c353ffdd010a5a634815aaa963''')) - - vector('aes192_cbc', key[:24], iv, plaintext, unhex(''' - e3dee5122edd3fec5fab95e7db8c784c0cb617103e2a406fba4ae3b4508dd608 - 4ff5723a670316cc91ed86e413c11b35557c56a6f5a7a2c660fc6ee603d73814 - 73a287645be0f297cdda97aef6c51faeb2392fec9d33adb65138d60f954babd9 - 8ee0daab0d1decaa8d1e07007c4a3c7b726948025f9fb72dd7de41f74f2f36b4 - 23ac6a5b4b6b39682ec74f57d9d300e547f3c3e467b77f5e4009923b2f94c903''')) - - vector('aes256_cbc', key[:32], iv, plaintext, unhex(''' - 088c6d4d41997bea79c408925255266f6c32c03ea465a5f607c2f076ec98e725 - 7e0beed79609b3577c16ebdf17d7a63f8865278e72e859e2367de81b3b1fe9ab - 8f045e1d008388a3cfc4ff87daffedbb47807260489ad48566dbe73256ce9dd4 - ae1689770a883b29695928f5983f33e8d7aec4668f64722e943b0b671c365709 - dfa86c648d5fb00544ff11bd29121baf822d867e32da942ba3a0d26299bcee13''')) - - # Tests of SDCTR mode, one with a random IV and one with an IV - # about to wrap round. More vigorous tests of IV carry and - # wraparound behaviour are in the testAESSDCTR method. - - sdctrIVs = [ - unhex('38f87b0b9b736160bfc0cbd8447af6ee'), - unhex('fffffffffffffffffffffffffffffffe'), - ] - - vector('aes128_ctr', key[:16], sdctrIVs[0], plaintext[:64], unhex(''' - d0061d7b6e8c4ef4fe5614b95683383f46cdd2766e66b6fb0b0f0b3a24520b2d - 15d869b06cbf685ede064bcf8fb5fb6726cfd68de7016696a126e9e84420af38''')) - vector('aes128_ctr', key[:16], sdctrIVs[1], plaintext[:64], unhex(''' - 49ac67164fd9ce8701caddbbc9a2b06ac6524d4aa0fdac95253971974b8f3bc2 - bb8d7c970f6bcd79b25218cc95582edf7711aae2384f6cf91d8d07c9d9b370bc''')) - - vector('aes192_ctr', key[:24], sdctrIVs[0], plaintext[:64], unhex(''' - 0baa86acbe8580845f0671b7ebad4856ca11b74e5108f515e34e54fa90f87a9a - c6eee26686253c19156f9be64957f0dbc4f8ecd7cabb1f4e0afefe33888faeec''')) - vector('aes192_ctr', key[:24], sdctrIVs[1], plaintext[:64], unhex(''' - 2da1791250100dc0d1461afe1bbfad8fa0320253ba5d7905d837386ba0a3a41f - 01965c770fcfe01cf307b5316afb3981e0e4aa59a6e755f0a5784d9accdc52be''')) - - vector('aes256_ctr', key[:32], sdctrIVs[0], plaintext[:64], unhex(''' - 49c7b284222d408544c770137b6ef17ef770c47e24f61fa66e7e46cae4888882 - f980a0f2446956bf47d2aed55ebd2e0694bfc46527ed1fd33efe708fec2f8b1f''')) - vector('aes256_ctr', key[:32], sdctrIVs[1], plaintext[:64], unhex(''' - f1d013c3913ccb4fc0091e25d165804480fb0a1d5c741bf012bba144afda6db2 - c512f3942018574bd7a8fdd88285a73d25ef81e621aebffb6e9b8ecc8e2549d4''')) - - def testAESSDCTR(self): - # A thorough test of the IV-incrementing component of SDCTR - # mode. We set up an AES-SDCTR cipher object with the given - # input IV; we encrypt two all-zero blocks, expecting the - # return values to be the AES-ECB encryptions of the input IV - # and the incremented version. Then we decrypt each of them by - # feeding them to an AES-CBC cipher object with its IV set to - # zero. - - def increment(keylen, suffix, iv): - key = b'\xab' * (keylen//8) - sdctr = ssh_cipher_new("aes{}_ctr_{}".format(keylen, suffix)) - if sdctr is None: return # skip test if HW AES not available - ssh_cipher_setkey(sdctr, key) - cbc = ssh_cipher_new("aes{}_cbc_{}".format(keylen, suffix)) - ssh_cipher_setkey(cbc, key) - - ssh_cipher_setiv(sdctr, iv) - ec0 = ssh_cipher_encrypt(sdctr, b'\x00' * 16) - ec1 = ssh_cipher_encrypt(sdctr, b'\x00' * 16) - ssh_cipher_setiv(cbc, b'\x00' * 16) - dc0 = ssh_cipher_decrypt(cbc, ec0) - ssh_cipher_setiv(cbc, b'\x00' * 16) - dc1 = ssh_cipher_decrypt(cbc, ec1) - self.assertEqualBin(iv, dc0) - return dc1 - - def test(keylen, suffix, ivInteger): - mask = (1 << 128) - 1 - ivInteger &= mask - ivBinary = unhex("{:032x}".format(ivInteger)) - ivIntegerInc = (ivInteger + 1) & mask - ivBinaryInc = unhex("{:032x}".format((ivIntegerInc))) - actualResult = increment(keylen, suffix, ivBinary) - if actualResult is not None: - self.assertEqualBin(actualResult, ivBinaryInc) - - # Check every input IV you can make by gluing together 32-bit - # pieces of the form 0, 1 or -1. This should test all the - # places where carry propagation within the 128-bit integer - # can go wrong. - # - # We also test this at all three AES key lengths, in case the - # core cipher routines are written separately for each one. - - for suffix in get_aes_impls(): - for keylen in [128, 192, 256]: - hexTestValues = ["00000000", "00000001", "ffffffff"] - for ivHexBytes in itertools.product(*([hexTestValues] * 4)): - ivInteger = int("".join(ivHexBytes), 16) - test(keylen, suffix, ivInteger) - - def testAESParallelism(self): - # Since at least one of our implementations of AES works in - # parallel, here's a test that CBC decryption works the same - # way no matter how the input data is divided up. - - # A pile of conveniently available random-looking test data. - test_ciphertext = ssh2_mpint(last(fibonacci_scattered(14))) - test_ciphertext += b"x" * (15 & -len(test_ciphertext)) # pad to a block - - # Test key and IV. - test_key = b"foobarbazquxquuxFooBarBazQuxQuux" - test_iv = b"FOOBARBAZQUXQUUX" - - for keylen in [128, 192, 256]: - decryptions = [] - - for suffix in get_aes_impls(): - c = ssh_cipher_new("aes{:d}_cbc_{}".format(keylen, suffix)) - if c is None: continue - ssh_cipher_setkey(c, test_key[:keylen//8]) - for chunklen in range(16, 16*12, 16): - ssh_cipher_setiv(c, test_iv) - decryption = b"" - for pos in range(0, len(test_ciphertext), chunklen): - chunk = test_ciphertext[pos:pos+chunklen] - decryption += ssh_cipher_decrypt(c, chunk) - decryptions.append(decryption) - - for d in decryptions: - self.assertEqualBin(d, decryptions[0]) - - def testCRC32(self): - # Check the effect of every possible single-byte input to - # crc32_update. In the traditional implementation with a - # 256-word lookup table, this exercises every table entry; in - # _any_ implementation which iterates over the input one byte - # at a time, it should be a similarly exhaustive test. (But if - # a more optimised implementation absorbed _more_ than 8 bits - # at a time, then perhaps this test wouldn't be enough...) - - # It would be nice if there was a functools.iterate() which - # would apply a function n times. Failing that, making shift1 - # accept and ignore a second argument allows me to iterate it - # 8 times using functools.reduce. - shift1 = lambda x, dummy=None: (x >> 1) ^ (0xEDB88320 * (x & 1)) - shift8 = lambda x: functools.reduce(shift1, [None]*8, x) - - # A small selection of choices for the other input to - # crc32_update, just to check linearity. - test_prior_values = [0, 0xFFFFFFFF, 0x45CC1F6A, 0xA0C4ADCF, 0xD482CDF1] - - for prior in test_prior_values: - prior_shifted = shift8(prior) - for i in range(256): - exp = shift8(i) ^ prior_shifted - self.assertEqual(crc32_update(prior, struct.pack("B", i)), exp) - - # Check linearity of the _reference_ implementation, while - # we're at it! - self.assertEqual(shift8(i ^ prior), exp) - - def testCRCDA(self): - def pattern(badblk, otherblks, pat): - # Arrange copies of the bad block in a pattern - # corresponding to the given bit string. - retstr = b"" - while pat != 0: - retstr += (badblk if pat & 1 else next(otherblks)) - pat >>= 1 - return retstr - - def testCases(pat): - badblock = b'muhahaha' # the block we'll maliciously repeat - - # Various choices of the other blocks, including all the - # same, all different, and all different but only in the - # byte at one end. - for otherblocks in [ - itertools.repeat(b'GoodData'), - (struct.pack('>Q', i) for i in itertools.count()), - (struct.pack('= maxlen - buf = b''.join(hash_str(hashname, text[:i]) - for i in range(maxlen)) - self.assertEqualBin(hash_str(hashname, buf), unhex(expected)) - - test('md5', 128, '8169d766cc3b8df182b3ce756ae19a15') - test('sha1', 128, '3691759577deb3b70f427763a9c15acb9dfc0259') - test('sha256', 128, 'ec539c4d678412c86c13ee4eb9452232' - '35d4eed3368d876fdf10c9df27396640') - test('sha512', 256, - 'cb725b4b4ec0ac1174d69427b4d97848b7db4fc01181f99a8049a4d721862578' - 'f91e026778bb2d389a9dd88153405189e6ba438b213c5387284103d2267fd055' - ) - - def testDSA(self): - p = 0xe93618c54716992ffd54e79df6e1b0edd517f7bbe4a49d64631eb3efe8105f676e8146248cfb4f05720862533210f0c2ab0f9dd61dbc0e5195200c4ebd95364b - q = 0xf3533bcece2e164ca7c5ce64bc1e395e9a15bbdd - g = 0x5ac9d0401c27d7abfbc5c17cdc1dc43323cd0ef18b79e1909bdace6d17af675a10d37dde8bd8b70e72a8666592216ccb00614629c27e870e4fbf393b812a9f05 - y = 0xac3ddeb22d65a5a2ded4a28418b2a748d8e5e544ba5e818c137d7b042ef356b0ef6d66cfca0b3ab5affa2969522e7b07bee60562fa4869829a5afce0ad0c4cd0 - x = 0x664f8250b7f1a5093047fe0c7fe4b58e46b73295 - pubblob = ssh_string(b"ssh-dss") + b"".join(map(ssh2_mpint, [p,q,g,y])) - privblob = ssh2_mpint(x) - pubkey = ssh_key_new_pub('dsa', pubblob) - privkey = ssh_key_new_priv('dsa', pubblob, privblob) - - sig = ssh_key_sign(privkey, b"hello, world", 0) - self.assertTrue(ssh_key_verify(pubkey, sig, b"hello, world")) - self.assertFalse(ssh_key_verify(pubkey, sig, b"hello, again")) - - badsig0 = unhex('{:040x}{:040x}'.format(1, 0)) - badsigq = unhex('{:040x}{:040x}'.format(1, q)) - self.assertFalse(ssh_key_verify(pubkey, badsig0, "hello, world")) - self.assertFalse(ssh_key_verify(pubkey, badsigq, "hello, world")) - self.assertFalse(ssh_key_verify(pubkey, badsig0, "hello, again")) - self.assertFalse(ssh_key_verify(pubkey, badsigq, "hello, again")) - - def testBLAKE2b(self): - # The standard test vectors for BLAKE2b (in the separate class - # below) don't satisfy me because they only test one hash - # size. These additional tests exercise BLAKE2b's configurable - # output length. The expected results are derived from the - # BLAKE2 reference implementation. - - def b2_with_len(data, length): - h = blake2b_new_general(length) - h.update(data) - return h.digest()[:length] - - self.assertEqualBin(b2_with_len(b'hello', 1), unhex("29")) - self.assertEqualBin(b2_with_len(b'hello', 2), unhex("accd")) - self.assertEqualBin(b2_with_len(b'hello', 3), unhex("980032")) - self.assertEqualBin(b2_with_len(b'hello', 5), unhex("9baecc38f2")) - self.assertEqualBin(b2_with_len(b'hello', 8), unhex( - "a7b6eda801e5347d")) - self.assertEqualBin(b2_with_len(b'hello', 13), unhex( - "6eedb122c6707328a66aa34a07")) - self.assertEqualBin(b2_with_len(b'hello', 21), unhex( - "c7f0f74a227116547b3d2788e927ee2a76c87d8797")) - self.assertEqualBin(b2_with_len(b'hello', 34), unhex( - "2f5fcdf2b870fa254051dd448193a1fb6e92be122efca539ba2aeac0bc6c77d0" - "dadc")) - self.assertEqualBin(b2_with_len(b'hello', 55), unhex( - "daafcf2bd6fccf976cbc234b71cd9f4f7d56fe0eb33a40018707089a215c44a8" - "4b272d0329ae6d85a0f8acc7e964dc2facb715ba472bb6")) - - def testArgon2LongHash(self): - # Unit-test the Argon2 long hash function H', which starts off - # the same as BLAKE2b, but comes with its own method of - # extending the output length past 64 bytes. - # - # I generated these test values using a test program linked - # against the reference implementation's libargon2.a and - # calling its blake2b_long function. - preimage = b'hello, world' - - self.assertEqualBin(argon2_long_hash(1, preimage), unhex("8b")) - self.assertEqualBin(argon2_long_hash(2, preimage), unhex("1ff9")) - self.assertEqualBin(argon2_long_hash(63, preimage), unhex( - "e2c997721f1d64aa8c25e588fb8ab19646ce6d5c2a431fa560fcb813e55dd481" - "322d2630d95ca6b1b63317b13d6b111e5816170c80c3ca7d5b4bf894096de4")) - self.assertEqualBin(argon2_long_hash(64, preimage), unhex( - "0c7ba7ee6d510b4bb5c9b69ac91e25e0b11aa30dd6234b8e61b0fe1537c037b8" - "8ed5aa59a277e8cc07095c81aff26d08967e4dfdabd32db8b6af6ceb78cf8c47")) - self.assertEqualBin(argon2_long_hash(65, preimage), unhex( - "680941abbd8fc80f28c38d623e90903f08709bf76575e2775d4ce01c31b192c8" - "73038d9a31af8991c8b1ad4f2b1991f4d15f73ab0f4f3add415c297a12eb9ddb" - "76")) - self.assertEqualBin(argon2_long_hash(95, preimage), unhex( - "4be28c51850fed70d9403e1406b6ba68a83d98cf222a4ee162beef60fd3384df" - "eba3fce9d95f646982eb384ac943ce5263cb03428fd8d261cc41ffdb7ba328fe" - "098526f2b49593f9e7f38188598ce4693b59f4dd32db30c1be9a9d35784fa0")) - self.assertEqualBin(argon2_long_hash(96, preimage), unhex( - "20295ea01e822cca113f668f33e5e481ed5879bfd7de6359ea42d497da97be52" - "2cdd518d34ae32c44cabd45249b4e697626b0b14b6a33a2bd138be0a4bceeaf4" - "9528f93acef01b093ee84d8d871d1ee6cf7c10e83ad0619631aed19345166f03")) - self.assertEqualBin(argon2_long_hash(97, preimage), unhex( - "d24b31f3ac0baad168d524efc4bafee55fef743fd60b14e28b860d7523e319c7" - "520e2d5457cc3d06dc1044530afdf6990fa12e38d5802eb642f8e77fcfee2c0b" - "1f84a28877f2f2f049ed9299e1e0230f98af3a161185970aad21f0ea0f5184cf" - "90")) - self.assertEqualBin(argon2_long_hash(127, preimage), unhex( - "5d1e8380450dbc985418ed1f3700b925ae0719e4486e29131c81bca7083ac6b8" - "f535c3398488e34d3dc1390de44097f1eee498f10ebe85b579e99a7672023b01" - "ca5c20e63c595b640e00d80f113a52e3773719889b266ab4c65269c11fb212e4" - "75f2b769bb26321bb60ecc0d490821e5056d7dfc9def3cd065d3ba90360764")) - self.assertEqualBin(argon2_long_hash(128, preimage), unhex( - "be15b316f3483c4d0d00f71a65b974894a2025f441b79b9fe461bc740cb0b039" - "c4fe914f61c05a612d63ebc50a662b2d59b1996091e5e3474340544ea46a46cb" - "25c41ff700fafcd96c4f12ddc698cd2426558f960696837ea8170fd2fe284b54" - "8f585f97919ef14f2b3cbb351eb98872add7ba6d08c1401232df6cc878fbeb22")) - self.assertEqualBin(argon2_long_hash(129, preimage), unhex( - "83da464c278dcb12c29b6685fee6d32f0b461337c155369ad0d56b58b0aa5f80" - "9aa7b56bd41b664c8d768957f8f0e40999fb0178eb53cf83f31d725bf92881bc" - "900774bce4cdf56b6386ad3de6891d11a0ccd4564a3431fc4c24105a02d0a6a2" - "434712b9a7471f3223c72a6e64912200d0a3d149a19d06fe9dc8ec09d7ed5a48" - "bb")) - self.assertEqualBin(argon2_long_hash(511, preimage), unhex( - "30c0c0d0467e7665368db0b40a2324a61fb569d35172de2df53a9739a8d18e60" - "b4f25d521c8855604be3e24ea56302566074323d94c0bd3a33d08f185d8ba5ac" - "a2bc3fb2e4c4e5ffec5778daea67c6b5913c9cac16f2e5c7b7818e757fa747b3" - "69e586d616010a752762f69c604238ed8738430366fbdb7493454fa02391a76b" - "30f241695b9fa8d3a3116227c6bb6f72d325cf104ab153d15f928b22767d467d" - "4bf7e16176aaa7315954b7872061933c12d548f1f93a8abb9d73791661bee521" - "b2ae51be373a229dfef32787234c1be5846d133563002b9a029178716ad41e70" - "1539d3fad300c77607c5217701e3e485d72c980f3f71d525c8148375a2f8d22c" - "a211ba165330a90b7e0e6baa6073833925c23bdd388ee904f38463c7e6b85475" - "09b810aae5c9ffc5dd902c2ffe049c338e3ae2c6416d3b874d6a9d384089564c" - "0d8e4dce9b6e47e1d5ec9087bf526cc9fa35aab1893a0588d31b77fea37e0799" - "468deacde47629d2960a3519b3bcd4e22364a9cccd3b128cba21cac27f140d53" - "f79c11e4157e4cb48272eecdf62f52084a27e5b0933bbe66ded17e2df6f8d398" - "f6c479c3c716457820ad177b8bd9334cb594e03d09fcc4f82d4385e141eacd7d" - "9ad1e1c4cb42788af70bac0509f0a891e662960955490abf2763373803e8c89c" - "df632579cb9c647634b30df214a3d67b92fd55d283c42c63b470a48a78cd5b")) - self.assertEqualBin(argon2_long_hash(512, preimage), unhex( - "79a6974e29a9a6c069e0156774d35c5014a409f5ffc60013725367a7208d4929" - "7d228637751768a31a59e27aa89372f1bcc095a6fa331198a5bd5ad053ba2ebb" - "cbcc501ea55cf142e8d95209228c9ab60cd104d5077472f2a9ecaa071aed6ee9" - "5de29e188b7399d5b6b7ed897b2bc4dd1ea745eb9974e39ca6fb983380cc537a" - "c04dfe6caefe85faf206b1613092ebadf791eaa8a5b814c9a79a73a5733b0505" - "a47163c10a0f7309df6663896df6079a7c88c6879bb591a40abd398c6deda792" - "1cc3986435b1c840a768b2fa507446f2f77a406b1b2f739f7795db24789c8927" - "24b4c84b7005445123154f8cd2ba63a7ede672af5d197f846700732025c9931d" - "1c67c5493417ca394a8f68ba532645815cf7b5102af134ecb4fd9e326f53779a" - "3039dbef6a0880db9e38b6b61d2f9ead969e4224c2d9c69b5897e5eeb7032e83" - "334e192ff50017056ccb84d4cc8eee3ab248d2614643d0174fe18c72186dd967" - "92d8545645ddf4a9b2c7a91c9a71857a399449d7154077a8e9580f1a2d20227d" - "671b455ccb897cba0491e50892120d7877f7776d653cfdb176fa3f64a9e6f848" - "cd681c487b488775aaf698294eec813b2cca90d68d63b5d886d61c1a8e922aaa" - "330fd658ede56e34bcd288048e845eba7b8e2e7cc22ba6c91b523e48017aa878" - "8ce4f91d0e6d6c6706762fb0cc7f465cee3916684fb21e337cfe1b583e0b1e92")) - self.assertEqualBin(argon2_long_hash(513, preimage), unhex( - "32243cfbd7eca582d60b3b8ea3ba3d93783537689c7cbcd1d1cbde46200b8c86" - "617fc00e8a9ae991a1e2f91c67e07d5f0a777d982c1461d0c5474e4e164b053c" - "2808559e2b8a5ac4a46a5fcbc825b1d5302c7b0611940194eb494d45ce7113a2" - "3424b51c199c6a5100ab159ff323eda5feffee4da4155a028a81da9d44e4286b" - "ac3dab4ffce43a80b6ce97a47ea0ac51ee16e8b4d3b68942afdc20e1c21747c4" - "94859c3d3883e7dc19ea416a393a3507683d9d03e6a3a91f8f1cb8a7d5d9892e" - "80c8fb0222527a73a1f59b9dd41770982f2af177a6e96093064534803edd0713" - "71ede53024cedc291d768325bb4e4def9af1b5569c349b64816496c37a8787b5" - "4fbe248372ebadb5ce20e03eaa935dc55ff4b8cbe5d6d844c7b71d4656fef22c" - "5a49f13d75a7a8368a2dbc1e78d732b879bfc5c9467eda2bf4918f0c59037ae3" - "dee7880a171409dd1a4e143c814e60301ac77237f261fa7519a04e68000530f9" - "708ed9fda5609d655560a9491f80f5875ad5725e3120686b73319c6a727932e3" - "20a2174422523498c38fea47aeb20d135ff9fd93c6fa6db0005e0001685d7577" - "33a82a4dc9dd6556b938f7b8dafd0d670846780b9931b815063708189b17877b" - "825533bcc250fb576a28be4caa107e6a3a6f7b0c60fb51b0def27008b7e272ac" - "95d610bfa912339799a2e537ce543d7862dddbe31bb224fda4ae283571847a28" - "54")) - self.assertEqualBin(argon2_long_hash(1024, preimage), unhex( - "951252f6fa152124f381266a358d9b78b88e469d08d5fc78e4ea32253c7fc26c" - "3ff1c93529ab4ee6fcf00acf29bbaba934a4014ce2625e0806601c55e6ce70d7" - "121fd82f0904f335c5c7ba07dc6e6adf7582c92f7f255072203ea85844b4fe54" - "817476a20bb742710ffc42750361be94332d0fc721b192309acfa70da43db6ae" - "1d0f0bbe8a3250966a4532b36728162073c9eb3e119ea4c1c187c775dbb25a5d" - "d883e3f65706a5fca897cdc4a8aa7b68ba3f57940c72f3a3396c417e758ba071" - "95be4afba325237c0e2738a74d96fd1350fb623cb2ad40ea8b1e070cf398b98c" - "2865ea40225b81f031f2b405409ca01dc5d9903d3d8e1d6381fbe7ccfc8f3dab" - "eadafd7c976c0ba84a936f78ff7df0f112c089ba88f82bed7f9a6e31a91e5fee" - "f675755454b948de22695660b243b9eca3bcc89608f83d2baa1d73dd6b8bd4f9" - "b995ed9cb0f1edc6e98a49ed841b506c1bf59b43f4b3457a376bbff116c1a4f6" - "07cc62381fc5c19953c68f300c1b51198d40784d812d25810ba404862f04b680" - "6039a074f612ad8b84e0941ba23c915c3e7162c225fbecffdb7dc1ab559b2b54" - "32fe8a498c32e918d8e7e33254ff75077f648827705e987f4d90fba971e78e1a" - "6896b4d775c7359dc950f1e964fa04621aacf3c0988969490f4c72c54caf79e8" - "481053cc0a27ffcd3580aabf9ef1268d498d8a18bd70e9b8402e011753bb7dc7" - "e856c00d988fca924ee7cf61979c38cda8a872e4cc4fbdc90c23a0ded71eb944" - "bb816ab22d9a4380e3e9d1cec818165c2fba6c5d51dcbf452c0cb1779a384937" - "64d695370e13a301eca7be68d4112d2177381514efbb36fe08fc5bc2970301b8" - "06f8e5a57a780e894d5276e2025bb775b6d1861e33c54ab6e3eb72947fbe6f91" - "8174ce24eb4682efbb3c4f01233dc7ce9ef44792e9e876bb03e6751b3d559047" - "d045127d976aa042fc55c690c9048e200065e7b7de19d9353aa9ac9b3e7611f0" - "d1c42d069a300455ca1f7420a352bace89215e705106927510c11b3b1c1486d9" - "f3ab006d2de2ee2c94574f760ce8c246bca229f98c66f06042b14f1fff9a16c0" - "1550237e16d108ce5597299b1eb406a9ee505a29a6e0fa526b3e6beafd336aea" - "138b2f31971586f67c5ffffbd6826d1c75666038c43d0bdff4edfc294e064a49" - "2eed43e2dc78d00abc4e85edcd9563b8251b66f57b0f4b6d17f5a3f35c87c488" - "dbeeb84fd720286197c2dec8290eccf3a313747de285b9cd3548e90cf81b3838" - "3ffcc8c2a7f582feb369d05cb96b9b224d05902b3e39e5b96536032e9dddeb9b" - "9d4f40a9c8f544ca37cf8d39d7c8c6a33880e9184ed017bd642db9590759bd10" - "7362048ede5c0257feecc4984584592c566f37fba8469c064015339fb4f03023" - "56ece37fd3655aae2bfc989b9b4c1384efc3503c8866db901802cb36eda9fb00")) - - def testArgon2(self): - # A few tests of my own of Argon2, derived from the reference - # implementation. - pwd = b"password" - salt = b"salt of at least 16 bytes" - secret = b"secret" - assoc = b"associated data" - - # Smallest memory (8Kbyte) and parallelism (1) parameters the - # reference implementation will accept, but lots of passes - self.assertEqualBin( - argon2('i', 8, 16, 1, 24, pwd, salt, secret, assoc), unhex( - "314da280240a3ca1eedd1f1db417a76eb0741e7df64b8cdf")) - self.assertEqualBin( - argon2('d', 8, 16, 1, 24, pwd, salt, secret, assoc), unhex( - "9cc961cf43e0f86c2d4e202b816dc5bc5b2177e68faa0b08")) - self.assertEqualBin( - argon2('id', 8, 16, 1, 24, pwd, salt, secret, assoc), unhex( - "6cd6c490c582fa597721d772d4e3de166987792491b48c51")) - - # Test a memory cost value that isn't a power of 2. This - # checks a wraparound case during the conversion of J1 to a - # block index, and is a regression test for a bug that nearly - # got past me during original development. - self.assertEqualBin( - argon2('i', 104, 16, 2, 24, pwd, salt, secret, assoc), unhex( - "a561963623f1073c9aa8caecdb600c73ffc6de677ba8d97c")) - self.assertEqualBin( - argon2('d', 104, 16, 2, 24, pwd, salt, secret, assoc), unhex( - "a9014db7f1d468fb25b88fa7fc0deac0f2e7f27e25d2cf6e")) - self.assertEqualBin( - argon2('id', 104, 16, 2, 24, pwd, salt, secret, assoc), unhex( - "64f3212b1e7725ffcf9ae2d1753d63e763bcd6970061a435")) - - # Larger parameters that should exercise the pseudorandom - # block indexing reasonably thoroughly. Also generate plenty - # of output data. - self.assertEqualBin( - argon2('i', 1024, 5, 16, 77, pwd, salt, secret, assoc), unhex( - "b008a685ff57730fad0e6f3ef3b9189282c0d9b05303675f43b5f3054724" - "733fcbe8e2639cc2c930535b31b723339041bcd703bf2483455acf86c0e6" - "9ed88c545ad40f1f2068855e4d61e99407")) - self.assertEqualBin( - argon2('d', 1024, 5, 16, 111, pwd, salt, secret, assoc), unhex( - "399ffbcd720c47745b9deb391ed0de7d5e0ffe53aef9f8ef7a7918cfa212" - "53df8cc577affbd5e0c0f8bf6d93c11b2f63973f8fc8f89dccd832fc587e" - "5d61717be6e88ca33eef5d1e168c028bae632a2a723c6c83f8e755f39171" - "5eda1c77c8e2fe06fbdd4e56d35262587e7df73cd7")) - self.assertEqualBin( - argon2('id', 1024, 5, 16, 123, pwd, salt, secret, assoc), unhex( - "6636807289cb9b9c032f48dcc31ffed1de4ca6c1b97e1ce768d690486341" - "2ac84b39d568a81dd01d9ee3ceec6cc23441d95e6abeb4a2024f1f540d56" - "9b799277c4037ddc7195ba783c9158a901adc7d4a5df8357b34a3869e5d6" - "aeae2a21201eef5e347de22c922192e8f46274b0c9d33e965155a91e7686" - "9d530e")) - - def testOpenSSHBcrypt(self): - # Test case created by making an OpenSSH private key file - # using their own ssh-keygen, then decrypting it successfully - # using PuTTYgen and printing the inputs and outputs to - # openssh_bcrypt in the process. So this output key is known - # to agree with OpenSSH's own answer. - - self.assertEqualBin( - openssh_bcrypt('test passphrase', - unhex('d0c3b40ace4afeaf8c0f81202ae36718'), - 16, 48), - unhex('d78ba86e7273de0e007ab0ba256646823d5c902bc44293ae' - '78547e9a7f629be928cc78ff78a75a4feb7aa6f125079c7d')) - - def testRSAVerify(self): - def blobs(n, e, d, p, q, iqmp): - pubblob = ssh_string(b"ssh-rsa") + ssh2_mpint(e) + ssh2_mpint(n) - privblob = (ssh2_mpint(d) + ssh2_mpint(p) + - ssh2_mpint(q) + ssh2_mpint(iqmp)) - return pubblob, privblob - - def failure_test(*args): - pubblob, privblob = blobs(*args) - key = ssh_key_new_priv('rsa', pubblob, privblob) - self.assertEqual(key, None) - - def success_test(*args): - pubblob, privblob = blobs(*args) - key = ssh_key_new_priv('rsa', pubblob, privblob) - self.assertNotEqual(key, None) - - # Parameters for a (trivially small) test key. - n = 0xb5d545a2f6423eabd55ffede53e21628d5d4491541482e10676d9d6f2783b9a5 - e = 0x25 - d = 0x6733db6a546ac99fcc21ba2b28b0c077156e8a705976205a955c6d9cef98f419 - p = 0xe30ebd7348bf10dca72b36f2724dafa7 - q = 0xcd02c87a7f7c08c4e9dc80c9b9bad5d3 - iqmp = 0x60a129b30db9227910efe1608976c513 - - # Check the test key makes sense unmodified. - success_test(n, e, d, p, q, iqmp) - - # Try modifying the values one by one to ensure they are - # rejected, except iqmp, which sshrsa.c regenerates anyway so - # it won't matter at all. - failure_test(n+1, e, d, p, q, iqmp) - failure_test(n, e+1, d, p, q, iqmp) - failure_test(n, e, d+1, p, q, iqmp) - failure_test(n, e, d, p+1, q, iqmp) - failure_test(n, e, d, p, q+1, iqmp) - success_test(n, e, d, p, q, iqmp+1) - - # The key should also be accepted with p,q reversed. (Again, - # iqmp gets regenerated, so it won't matter if that's wrong.) - success_test(n, e, d, q, p, iqmp) - - # Replace each of p and q with 0, and with 1. These should - # still fail validation (obviously), but the point is that the - # validator should also avoid trying to divide by zero in the - # process. - failure_test(n, e, d, 0, q, iqmp) - failure_test(n, e, d, p, 0, iqmp) - failure_test(n, e, d, 1, q, iqmp) - failure_test(n, e, d, p, 1, iqmp) - - def testKeyMethods(self): - # Exercise all the methods of the ssh_key trait on all key - # types, and ensure that they're consistent with each other. - # No particular test is done on the rightness of the - # signatures by any objective standard, only that the output - # from our signing method can be verified by the corresponding - # verification method. - # - # However, we do include the expected signature text in each - # case, which checks determinism in the sense of being - # independent of any random numbers, and also in the sense of - # tomorrow's change to the code not having accidentally - # changed the behaviour. - - test_message = b"Message to be signed by crypt.testKeyMethods\n" - - test_keys = [ - ('ed25519', 'AAAAC3NzaC1lZDI1NTE5AAAAIM7jupzef6CD0ps2JYxJp9IlwY49oorOseV5z5JFDFKn', 'AAAAIAf4/WRtypofgdNF2vbZOUFE1h4hvjw4tkGJZyOzI7c3', 255, b'0xf4d6e7f6f4479c23f0764ef43cea1711dbfe02aa2b5a32ff925c7c1fbf0f0db,0x27520c4592cf79e5b1ce8aa23d8ec125d2a7498c25369bd283a07fde9cbae3ce', [(0, 'AAAAC3NzaC1lZDI1NTE5AAAAQN73EqfyA4WneqDhgZ98TlRj9V5Wg8zCrMxTLJN1UtyfAnPUJDtfG/U0vOsP8PrnQxd41DDDnxrAXuqJz8rOagc=')]), - ('ed448', 'AAAACXNzaC1lZDQ0OAAAADnRI0CQDym5IqUidLNDcSdHe54bYEwqjpjBlab8uKGoe6FRqqejha7+5U/VAHy7BmE23+ju26O9XgA=', 'AAAAObP9klqyiJSJsdFJf+xwZQdkbZGUqXE07K6e5plfRTGjYYkyWJFUNFH4jzIn9xH1TX9z9EGycPaXAA==', 448, b'0x4bf4a2b6586c60d8cdb52c2b45b897f6d2224bc37987489c0d70febb449e8c82964ed5785827be808e44d31dd31e6ff7c99f43e49f419928,0x5ebda3dbeee8df366106bb7c00d54fe5feae85a3a7aa51a17ba8a1b8fca695c1988e2a4c601b9e7b47277143b37422a522b9290f904023d1', [(0, 'AAAACXNzaC1lZDQ0OAAAAHLkSVioGMvLesZp3Tn+Z/sSK0Hl7RHsHP4q9flLzTpZG5h6JDH3VmZBEjTJ6iOLaa0v4FoNt0ng4wAB53WrlQC4h3iAusoGXnPMAKJLmqzplKOCi8HKXk8Xl8fsXbaoyhatv1OZpwJcffmh1x+x+LSgNQA=')]), - ('p256', 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHkYQ0sQoq5LbJI1VMWhw3bV43TSYi3WVpqIgKcBKK91TcFFlAMZgceOHQ0xAFYcSczIttLvFu+xkcLXrRd4N7Q=', 'AAAAIQCV/1VqiCsHZm/n+bq7lHEHlyy7KFgZBEbzqYaWtbx48Q==', 256, b'nistp256,0x7918434b10a2ae4b6c923554c5a1c376d5e374d2622dd6569a8880a70128af75,0x4dc14594031981c78e1d0d3100561c49ccc8b6d2ef16efb191c2d7ad177837b4', [(0, 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAABIAAAAIAryzHDGi/TcCnbdxZkIYR5EGR6SNYXr/HlQRF8le+/IAAAAIERfzn6eHuBbqWIop2qL8S7DWRB3lenN1iyL10xYQPKw')]), - ('p384', 'AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBMYK8PUtfAlJwKaBTIGEuCzH0vqOMa4UbcjrBbTbkGVSUnfo+nuC80NCdj9JJMs1jvfF8GzKLc5z8H3nZyM741/BUFjV7rEHsQFDek4KyWvKkEgKiTlZid19VukNo1q2Hg==', 'AAAAMGsfTmdB4zHdbiQ2euTSdzM6UKEOnrVjMAWwHEYvmG5qUOcBnn62fJDRJy67L+QGdg==', 384, b'nistp384,0xc60af0f52d7c0949c0a6814c8184b82cc7d2fa8e31ae146dc8eb05b4db9065525277e8fa7b82f34342763f4924cb358e,0xf7c5f06cca2dce73f07de767233be35fc15058d5eeb107b101437a4e0ac96bca90480a89395989dd7d56e90da35ab61e', [(0, 'AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAABpAAAAMDmHrtXCADzLvkkWG/duBAHlf6B1mVvdt6F0uzXfsf8Yub8WXNUNVnYq6ovrWPzLggAAADEA9izzwoUuFcXYRJeKcRLZEGMmSDDPzUZb7oZR0UgD1jsMQXs8UfpO31Qur/FDSCRK')]), - ('p521', 'AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFrGthlKM152vu2Ghk+R7iO9/M6e+hTehNZ6+FBwof4HPkPB2/HHXj5+w5ynWyUrWiX5TI2riuJEIrJErcRH5LglADnJDX2w4yrKZ+wDHSz9lwh9p2F+B5R952es6gX3RJRkGA+qhKpKup8gKx78RMbleX8wgRtIu+4YMUnKb1edREiRg==', 'AAAAQgFh7VNJFUljWhhyAEiL0z+UPs/QggcMTd3Vv2aKDeBdCRl5di8r+BMm39L7bRzxRMEtW5NSKlDtE8MFEGdIE9khsw==', 521, b'nistp521,0x16b1ad86528cd79dafbb61a193e47b88ef7f33a7be8537a1359ebe141c287f81cf90f076fc71d78f9fb0e729d6c94ad6897e53236ae2b89108ac912b7111f92e094,0xe72435f6c38cab299fb00c74b3f65c21f69d85f81e51f79d9eb3a817dd125190603eaa12a92aea7c80ac7bf1131b95e5fcc2046d22efb860c52729bd5e75112246', [(0, 'AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAACMAAAAQgCLgvftvwM3CUaigrW0yzmCHoYjC6GLtO+6S91itqpgMEtWPNlaTZH6QQqkgscijWdXx98dDkQao/gcAKVmOZKPXgAAAEIB1PIrsDF1y6poJ/czqujB7NSUWt31v+c2t6UA8m2gTA1ARuVJ9XBGLMdceOTB00Hi9psC2RYFLpaWREOGCeDa6ow=')]), - ('dsa', 'AAAAB3NzaC1kc3MAAABhAJyWZzjVddGdyc5JPu/WPrC07vKRAmlqO6TUi49ah96iRcM7/D1aRMVAdYBepQ2mf1fsQTmvoC9KgQa79nN3kHhz0voQBKOuKI1ZAodfVOgpP4xmcXgjaA73Vjz22n4newAAABUA6l7/vIveaiA33YYv+SKcKLQaA8cAAABgbErc8QLw/WDz7mhVRZrU+9x3Tfs68j3eW+B/d7Rz1ZCqMYDk7r/F8dlBdQlYhpQvhuSBgzoFa0+qPvSSxPmutgb94wNqhHlVIUb9ZOJNloNr2lXiPP//Wu51TxXAEvAAAAAAYQCcQ9mufXtZa5RyfwT4NuLivdsidP4HRoLXdlnppfFAbNdbhxE0Us8WZt+a/443bwKnYxgif8dgxv5UROnWTngWu0jbJHpaDcTc9lRyTeSUiZZK312s/Sl7qDk3/Du7RUI=', 'AAAAFGx3ft7G8AQzFsjhle7PWardUXh3', 768, b'0x9c966738d575d19dc9ce493eefd63eb0b4eef29102696a3ba4d48b8f5a87dea245c33bfc3d5a44c54075805ea50da67f57ec4139afa02f4a8106bbf67377907873d2fa1004a3ae288d5902875f54e8293f8c66717823680ef7563cf6da7e277b,0xea5effbc8bde6a2037dd862ff9229c28b41a03c7,0x6c4adcf102f0fd60f3ee6855459ad4fbdc774dfb3af23dde5be07f77b473d590aa3180e4eebfc5f1d94175095886942f86e481833a056b4faa3ef492c4f9aeb606fde3036a8479552146fd64e24d96836bda55e23cffff5aee754f15c012f000,0x9c43d9ae7d7b596b94727f04f836e2e2bddb2274fe074682d77659e9a5f1406cd75b87113452cf1666df9aff8e376f02a76318227fc760c6fe5444e9d64e7816bb48db247a5a0dc4dcf654724de49489964adf5dacfd297ba83937fc3bbb4542', [(0, 'AAAAB3NzaC1kc3MAAAAo0T2t6dr8Qr5DK2B0ETwUa3BhxMLPjLY0ZtlOACmP/kUt3JgByLv+3g==')]), - ('rsa', 'AAAAB3NzaC1yc2EAAAABJQAAAGEA2ChX9+mQD/NULFkBrxLDI8d1PHgrInC2u11U4Grqu4oVzKvnFROo6DZeCu6sKhFJE5CnIL7evAthQ9hkXVHDhQ7xGVauzqyHGdIU4/pHRScAYWBv/PZOlNMrSoP/PP91', 'AAAAYCMNdgyGvWpez2EjMLSbQj0nQ3GW8jzvru3zdYwtA3hblNUU9QpWNxDmOMOApkwCzUgsdIPsBxctIeWT2h+v8sVOH+d66LCaNmNR0lp+dQ+iXM67hcGNuxJwRdMupD9ZbQAAADEA7XMrMAb4WuHaFafoTfGrf6Jhdy9Ozjqi1fStuld7Nj9JkoZluiL2dCwIrxqOjwU5AAAAMQDpC1gYiGVSPeDRILr2oxREtXWOsW+/ZZTfZNX7lvoufnp+qvwZPqvZnXQFHyZ8qB0AAAAwQE0wx8TPgcvRVEVv8Wt+o1NFlkJZayWD5hqpe/8AqUMZbqfg/aiso5mvecDLFgfV', 768, b'0x25,0xd82857f7e9900ff3542c5901af12c323c7753c782b2270b6bb5d54e06aeabb8a15ccabe71513a8e8365e0aeeac2a11491390a720bedebc0b6143d8645d51c3850ef11956aeceac8719d214e3fa4745270061606ffcf64e94d32b4a83ff3cff75', [(0, 'AAAAB3NzaC1yc2EAAABgrLSC4635RCsH1b3en58NqLsrH7PKRZyb3YmRasOyr8xIZMSlKZyxNg+kkn9OgBzbH9vChafzarfHyVwtJE2IMt3uwxTIWjwgwH19tc16k8YmNfDzujmB6OFOArmzKJgJ'), (2, 'AAAADHJzYS1zaGEyLTI1NgAAAGAJszr04BZlVBEdRLGOv1rTJwPiid/0I6/MycSH+noahvUH2wjrRhqDuv51F4nKYF5J9vBsEotTSrSF/cnLsliCdvVkEfmvhdcn/jx2LWF2OfjqETiYSc69Dde9UFmAPds='), (4, 'AAAADHJzYS1zaGEyLTUxMgAAAGBxfZ2m+WjvZ5YV5RFm0+w84CgHQ95EPndoAha0PCMc93AUHBmoHnezsJvEGuLovUm35w/0POmUNHI7HzM9PECwXrV0rO6N/HL/oFxJuDYmeqCpjMVmN8QXka+yxs2GEtA=')]), - ] - - for alg, pubb64, privb64, bits, cachestr, siglist in test_keys: - # Decode the blobs in the above test data. - pubblob = b64(pubb64) - privblob = b64(privb64) - - # Check the method that examines a public blob directly - # and returns an integer showing the key size. - self.assertEqual(ssh_key_public_bits(alg, pubblob), bits) - - # Make a public-only and a full ssh_key object. - pubkey = ssh_key_new_pub(alg, pubblob) - privkey = ssh_key_new_priv(alg, pubblob, privblob) - - # Test that they re-export the public and private key - # blobs unchanged. - self.assertEqual(ssh_key_public_blob(pubkey), pubblob) - self.assertEqual(ssh_key_public_blob(privkey), pubblob) - self.assertEqual(ssh_key_private_blob(privkey), privblob) - - # Round-trip through the OpenSSH wire encoding used by the - # agent protocol (and the newer OpenSSH key file format), - # and check the result still exports all the same blobs. - osshblob = ssh_key_openssh_blob(privkey) - privkey2 = ssh_key_new_priv_openssh(alg, osshblob) - self.assertEqual(ssh_key_public_blob(privkey2), pubblob) - self.assertEqual(ssh_key_private_blob(privkey2), privblob) - self.assertEqual(ssh_key_openssh_blob(privkey2), osshblob) - - # Test that the string description used in the host key - # cache is as expected. - for key in [pubkey, privkey, privkey2]: - self.assertEqual(ssh_key_cache_str(key), cachestr) - - # Now test signatures, separately for each provided flags - # value. - for flags, sigb64 in siglist: - # Decode the signature blob from the test data. - sigblob = b64(sigb64) - - # Sign our test message, and check it produces exactly - # the expected signature blob. - # - # We do this with both the original private key and - # the one we round-tripped through OpenSSH wire - # format, just in case that round trip made some kind - # of a mess that didn't show up in the re-extraction - # of the blobs. - for key in [privkey, privkey2]: - self.assertEqual(ssh_key_sign( - key, test_message, flags), sigblob) - - if flags != 0: - # Currently we only support _generating_ - # signatures with flags != 0, not verifying them. - continue - - # Check the signature verifies successfully, with all - # three of the key objects we have. - for key in [pubkey, privkey, privkey2]: - self.assertTrue(ssh_key_verify(key, sigblob, test_message)) - - # A crude check that at least _something_ doesn't - # verify successfully: flip a bit of the signature - # and expect it to fail. - # - # We do this twice, at the 1/3 and 2/3 points along - # the signature's length, so that in the case of - # signatures in two parts (DSA-like) we try perturbing - # both parts. Other than that, we don't do much to - # make this a rigorous cryptographic test. - for n, d in [(1,3),(2,3)]: - sigbytes = list(sigblob) - bit = 8 * len(sigbytes) * n // d - sigbytes[bit // 8] ^= 1 << (bit % 8) - badsig = bytes(sigbytes) - for key in [pubkey, privkey, privkey2]: - self.assertFalse(ssh_key_verify( - key, badsig, test_message)) - - def testPPKLoadSave(self): - # Stability test of PPK load/save functions. - input_clear_key = b"""\ -PuTTY-User-Key-File-3: ssh-ed25519 -Encryption: none -Comment: ed25519-key-20200105 -Public-Lines: 2 -AAAAC3NzaC1lZDI1NTE5AAAAIHJCszOHaI9X/yGLtjn22f0hO6VPMQDVtctkym6F -JH1W -Private-Lines: 1 -AAAAIGvvIpl8jyqn8Xufkw6v3FnEGtXF3KWw55AP3/AGEBpY -Private-MAC: 816c84093fc4877e8411b8e5139c5ce35d8387a2630ff087214911d67417a54d -""" - input_encrypted_key = b"""\ -PuTTY-User-Key-File-3: ssh-ed25519 -Encryption: aes256-cbc -Comment: ed25519-key-20200105 -Public-Lines: 2 -AAAAC3NzaC1lZDI1NTE5AAAAIHJCszOHaI9X/yGLtjn22f0hO6VPMQDVtctkym6F -JH1W -Key-Derivation: Argon2id -Argon2-Memory: 8192 -Argon2-Passes: 13 -Argon2-Parallelism: 1 -Argon2-Salt: 37c3911bfefc8c1d11ec579627d2b3d9 -Private-Lines: 1 -amviz4sVUBN64jLO3gt4HGXJosUArghc4Soi7aVVLb2Tir5Baj0OQClorycuaPRd -Private-MAC: 6f5e588e475e55434106ec2c3569695b03f423228b44993a9e97d52ffe7be5a8 -""" - algorithm = b'ssh-ed25519' - comment = b'ed25519-key-20200105' - pp = b'test-passphrase' - public_blob = unhex( - '0000000b7373682d65643235353139000000207242b33387688f57ff218bb639' - 'f6d9fd213ba54f3100d5b5cb64ca6e85247d56') - - self.assertEqual(ppk_encrypted_s(input_clear_key), (False, comment)) - self.assertEqual(ppk_encrypted_s(input_encrypted_key), (True, comment)) - self.assertEqual(ppk_encrypted_s("not a key file"), (False, None)) - - self.assertEqual(ppk_loadpub_s(input_clear_key), - (True, algorithm, public_blob, comment, None)) - self.assertEqual(ppk_loadpub_s(input_encrypted_key), - (True, algorithm, public_blob, comment, None)) - self.assertEqual(ppk_loadpub_s("not a key file"), - (False, None, b'', None, - b'not a public key or a PuTTY SSH-2 private key')) - - k1, c, e = ppk_load_s(input_clear_key, None) - self.assertEqual((c, e), (comment, None)) - k2, c, e = ppk_load_s(input_encrypted_key, pp) - self.assertEqual((c, e), (comment, None)) - privblob = ssh_key_private_blob(k1) - self.assertEqual(ssh_key_private_blob(k2), privblob) - - salt = unhex('37c3911bfefc8c1d11ec579627d2b3d9') - with queued_specific_random_data(salt): - self.assertEqual(ppk_save_sb(k1, comment, None, - 3, 'id', 8192, 13, 1), - input_clear_key) - with queued_specific_random_data(salt): - self.assertEqual(ppk_save_sb(k2, comment, None, - 3, 'id', 8192, 13, 1), - input_clear_key) - - with queued_specific_random_data(salt): - self.assertEqual(ppk_save_sb(k1, comment, pp, - 3, 'id', 8192, 13, 1), - input_encrypted_key) - with queued_specific_random_data(salt): - self.assertEqual(ppk_save_sb(k2, comment, pp, - 3, 'id', 8192, 13, 1), - input_encrypted_key) - - # And check we can still handle v2 key files. - v2_clear_key = b"""\ -PuTTY-User-Key-File-2: ssh-ed25519 -Encryption: none -Comment: ed25519-key-20200105 -Public-Lines: 2 -AAAAC3NzaC1lZDI1NTE5AAAAIHJCszOHaI9X/yGLtjn22f0hO6VPMQDVtctkym6F -JH1W -Private-Lines: 1 -AAAAIGvvIpl8jyqn8Xufkw6v3FnEGtXF3KWw55AP3/AGEBpY -Private-MAC: 2a629acfcfbe28488a1ba9b6948c36406bc28422 -""" - v2_encrypted_key = b"""\ -PuTTY-User-Key-File-2: ssh-ed25519 -Encryption: aes256-cbc -Comment: ed25519-key-20200105 -Public-Lines: 2 -AAAAC3NzaC1lZDI1NTE5AAAAIHJCszOHaI9X/yGLtjn22f0hO6VPMQDVtctkym6F -JH1W -Private-Lines: 1 -4/jKlTgC652oa9HLVGrMjHZw7tj0sKRuZaJPOuLhGTvb25Jzpcqpbi+Uf+y+uo+Z -Private-MAC: 5b1f6f4cc43eb0060d2c3e181bc0129343adba2b -""" - - self.assertEqual(ppk_encrypted_s(v2_clear_key), (False, comment)) - self.assertEqual(ppk_encrypted_s(v2_encrypted_key), (True, comment)) - self.assertEqual(ppk_encrypted_s("not a key file"), (False, None)) - - self.assertEqual(ppk_loadpub_s(v2_clear_key), - (True, algorithm, public_blob, comment, None)) - self.assertEqual(ppk_loadpub_s(v2_encrypted_key), - (True, algorithm, public_blob, comment, None)) - self.assertEqual(ppk_loadpub_s("not a key file"), - (False, None, b'', None, - b'not a public key or a PuTTY SSH-2 private key')) - - k1, c, e = ppk_load_s(v2_clear_key, None) - self.assertEqual((c, e), (comment, None)) - k2, c, e = ppk_load_s(v2_encrypted_key, pp) - self.assertEqual((c, e), (comment, None)) - self.assertEqual(ssh_key_private_blob(k1), privblob) - self.assertEqual(ssh_key_private_blob(k2), privblob) - - self.assertEqual(ppk_save_sb(k2, comment, None, - 2, 'id', 8192, 13, 1), - v2_clear_key) - self.assertEqual(ppk_save_sb(k1, comment, pp, - 2, 'id', 8192, 13, 1), - v2_encrypted_key) - - def testRSA1LoadSave(self): - # Stability test of SSH-1 RSA key-file load/save functions. - input_clear_key = unhex( - "5353482050524956415445204B45592046494C4520464F524D415420312E310A" - "000000000000000002000200BB115A85B741E84E3D940E690DF96A0CBFDC07CA" - "70E51DA8234D211DE77341CEF40C214CAA5DCF68BE2127447FD6C84CCB17D057" - "A74F2365B9D84A78906AEB51000625000000107273612D6B65792D3230323030" - "313036208E208E0200929EE615C6FC4E4B29585E52570F984F2E97B3144AA5BD" - "4C6EB2130999BB339305A21FFFA79442462A8397AF8CAC395A3A3827DE10457A" - "1F1B277ABFB8C069C100FF55B1CAD69B3BD9E42456CF28B1A4B98130AFCE08B2" - "8BCFFF5FFFED76C5D51E9F0100C5DE76889C62B1090A770AE68F087A19AB5126" - "E60DF87710093A2AD57B3380FB0100F2068AC47ECB33BF8F13DF402BABF35EE7" - "26BD32F7564E51502DF5C8F4888B2300000000") - input_encrypted_key = unhex( - "5353482050524956415445204b45592046494c4520464f524d415420312e310a" - "000300000000000002000200bb115a85b741e84e3d940e690df96a0cbfdc07ca" - "70e51da8234d211de77341cef40c214caa5dcf68be2127447fd6c84ccb17d057" - "a74f2365b9d84a78906aeb51000625000000107273612d6b65792d3230323030" - "3130363377f926e811a5f044c52714801ecdcf9dd572ee0a193c4f67e87ab2ce" - "4569d0c5776fd6028909ed8b6d663bef15d207d3ef6307e7e21dbec56e8d8b4e" - "894ded34df891bb29bae6b2b74805ac80f7304926abf01ae314dd69c64240761" - "34f15d50c99f7573252993530ec9c4d5016dd1f5191730cda31a5d95d362628b" - "2a26f4bb21840d01c8360e4a6ce216c4686d25b8699d45cf361663bb185e2c5e" - "652012a1e0f9d6d19afbb28506f7775bfd8129") - - comment = b'rsa-key-20200106' - pp = b'test-passphrase' - public_blob = unhex( - "000002000006250200bb115a85b741e84e3d940e690df96a0cbfdc07ca70e51d" - "a8234d211de77341cef40c214caa5dcf68be2127447fd6c84ccb17d057a74f23" - "65b9d84a78906aeb51") - - self.assertEqual(rsa1_encrypted_s(input_clear_key), (False, comment)) - self.assertEqual(rsa1_encrypted_s(input_encrypted_key), - (True, comment)) - self.assertEqual(rsa1_encrypted_s("not a key file"), (False, None)) - - self.assertEqual(rsa1_loadpub_s(input_clear_key), - (1, public_blob, comment, None)) - self.assertEqual(rsa1_loadpub_s(input_encrypted_key), - (1, public_blob, comment, None)) - - k1 = rsa_new() - status, c, e = rsa1_load_s(input_clear_key, k1, None) - self.assertEqual((status, c, e), (1, comment, None)) - k2 = rsa_new() - status, c, e = rsa1_load_s(input_clear_key, k2, None) - self.assertEqual((status, c, e), (1, comment, None)) - - with queued_specific_random_data(unhex("208e")): - self.assertEqual(rsa1_save_sb(k1, comment, None), input_clear_key) - with queued_specific_random_data(unhex("208e")): - self.assertEqual(rsa1_save_sb(k2, comment, None), input_clear_key) - - with queued_specific_random_data(unhex("99f3")): - self.assertEqual(rsa1_save_sb(k1, comment, pp), - input_encrypted_key) - with queued_specific_random_data(unhex("99f3")): - self.assertEqual(rsa1_save_sb(k2, comment, pp), - input_encrypted_key) - - def testOpenSSHCert(self): - def per_base_keytype_tests(alg, run_validation_tests=False, - run_ca_rsa_tests=False, ca_signflags=None): - cert_pub = sign_cert_via_testcrypt( - make_signature_preimage( - key_to_certify = base_key.public_blob(), - ca_key = ca_key, - certtype = CertType.user, - keyid = b'id', - serial = 111, - principals = [b'username'], - valid_after = 1000, - valid_before = 2000), ca_key, signflags=ca_signflags) - - certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, - base_key.private_blob()) - - # Check the simple certificate methods - self.assertEqual(certified_key.cert_id_string(), b'id') - self.assertEqual(certified_key.ca_public_blob(), - ca_key.public_blob()) - recovered_base_key = certified_key.base_key() - self.assertEqual(recovered_base_key.public_blob(), - base_key.public_blob()) - self.assertEqual(recovered_base_key.private_blob(), - base_key.private_blob()) - - # Check that an ordinary key also supports base_key() - redundant_base_key = base_key.base_key() - self.assertEqual(redundant_base_key.public_blob(), - base_key.public_blob()) - self.assertEqual(redundant_base_key.private_blob(), - base_key.private_blob()) - - # Test signing and verifying using the certified key type - test_string = b'hello, world' - base_sig = base_key.sign(test_string, 0) - certified_sig = certified_key.sign(test_string, 0) - self.assertEqual(base_sig, certified_sig) - self.assertEqual(certified_key.verify(base_sig, test_string), True) - - # Check a successful certificate verification - result, err = certified_key.check_cert( - False, b'username', 1000, '') - self.assertEqual(result, True) - - # If the key type is RSA, check that the validator rejects - # wrong kinds of CA signature - if run_ca_rsa_tests: - forbid_all = ",".join(["permit_rsa_sha1=false", - "permit_rsa_sha256=false," - "permit_rsa_sha512=false"]) - result, err = certified_key.check_cert( - False, b'username', 1000, forbid_all) - self.assertEqual(result, False) - - algname = ("rsa-sha2-512" if ca_signflags == 4 else - "rsa-sha2-256" if ca_signflags == 2 else - "ssh-rsa") - self.assertEqual(err, ( - "Certificate signature uses '{}' signature type " - "(forbidden by user configuration)".format(algname) - .encode("ASCII"))) - - permitflag = ("permit_rsa_sha512" if ca_signflags == 4 else - "permit_rsa_sha256" if ca_signflags == 2 else - "permit_rsa_sha1") - result, err = certified_key.check_cert( - False, b'username', 1000, "{},{}=true".format( - forbid_all, permitflag)) - self.assertEqual(result, True) - - # That's the end of the tests we need to repeat for all - # the key types. Now we move on to detailed tests of the - # validation, which are independent of key type, so we - # only need to test this part once. - if not run_validation_tests: - return - - # Check cert verification at the other end of the valid - # time range - result, err = certified_key.check_cert( - False, b'username', 1999, '') - self.assertEqual(result, True) - - # Oops, wrong certificate type - result, err = certified_key.check_cert( - True, b'username', 1000, '') - self.assertEqual(result, False) - self.assertEqual(err, b'Certificate type is user; expected host') - - # Oops, wrong username - result, err = certified_key.check_cert( - False, b'someoneelse', 1000, '') - self.assertEqual(result, False) - self.assertEqual(err, b'Certificate\'s username list ["username"] ' - b'does not contain expected username "someoneelse"') - - # Oops, time is wrong. (But we can't check the full error - # message including the translated start/end times, because - # those vary with LC_TIME.) - result, err = certified_key.check_cert( - False, b'someoneelse', 999, '') - self.assertEqual(result, False) - self.assertEqual(err[:30], b'Certificate is not valid until') - result, err = certified_key.check_cert( - False, b'someoneelse', 2000, '') - self.assertEqual(result, False) - self.assertEqual(err[:22], b'Certificate expired at') - - # Modify the certificate so that the signature doesn't validate - username_position = cert_pub.index(b'username') - bytelist = list(cert_pub) - bytelist[username_position] ^= 1 - miscertified_key = ssh_key_new_priv(alg + '-cert', bytes(bytelist), - base_key.private_blob()) - result, err = miscertified_key.check_cert( - False, b'username', 1000, '') - self.assertEqual(result, False) - self.assertEqual(err, b"Certificate's signature is invalid") - - # Make a certificate containing a critical option, to test we - # reject it - cert_pub = sign_cert_via_testcrypt( - make_signature_preimage( - key_to_certify = base_key.public_blob(), - ca_key = ca_key, - certtype = CertType.user, - keyid = b'id', - serial = 112, - principals = [b'username'], - critical_options = {b'unknown-option': b'yikes!'}), ca_key) - certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, - base_key.private_blob()) - result, err = certified_key.check_cert( - False, b'username', 1000, '') - self.assertEqual(result, False) - self.assertEqual(err, b'Certificate specifies an unsupported ' - b'critical option "unknown-option"') - - # Make a certificate containing a non-critical extension, to - # test we _accept_ it - cert_pub = sign_cert_via_testcrypt( - make_signature_preimage( - key_to_certify = base_key.public_blob(), - ca_key = ca_key, - certtype = CertType.user, - keyid = b'id', - serial = 113, - principals = [b'username'], - extensions = {b'unknown-ext': b'whatever, dude'}), ca_key) - certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, - base_key.private_blob()) - result, err = certified_key.check_cert( - False, b'username', 1000, '') - self.assertEqual(result, True) - - # Make a certificate on the CA key, and re-sign the main - # key using that, to ensure that two-level certs are rejected - ca_self_certificate = sign_cert_via_testcrypt( - make_signature_preimage( - key_to_certify = ca_key.public_blob(), - ca_key = ca_key, - certtype = CertType.user, - keyid = b'id', - serial = 111, - principals = [b"doesn't matter"], - valid_after = 1000, - valid_before = 2000), ca_key, signflags=ca_signflags) - import base64 - self_signed_ca_key = ssh_key_new_pub( - alg + '-cert', ca_self_certificate) - cert_pub = sign_cert_via_testcrypt( - make_signature_preimage( - key_to_certify = base_key.public_blob(), - ca_key = self_signed_ca_key, - certtype = CertType.user, - keyid = b'id', - serial = 111, - principals = [b'username'], - valid_after = 1000, - valid_before = 2000), ca_key, signflags=ca_signflags) - certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, - base_key.private_blob()) - result, err = certified_key.check_cert( - False, b'username', 1500, '') - self.assertEqual(result, False) - self.assertEqual( - err, b'Certificate is signed with a certified key ' - b'(forbidden by OpenSSH certificate specification)') - - # Now try a host certificate. We don't need to do _all_ the - # checks over again, but at least make sure that setting - # CertType.host leads to the certificate validating with - # host=True and not with host=False. - # - # Also, in this test, give two hostnames. - cert_pub = sign_cert_via_testcrypt( - make_signature_preimage( - key_to_certify = base_key.public_blob(), - ca_key = ca_key, - certtype = CertType.host, - keyid = b'id', - serial = 114, - principals = [b'hostname.example.com', - b'hostname2.example.com'], - valid_after = 1000, - valid_before = 2000), ca_key) - - certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, - base_key.private_blob()) - - # Check certificate type - result, err = certified_key.check_cert( - True, b'hostname.example.com', 1000, '') - self.assertEqual(result, True) - result, err = certified_key.check_cert( - False, b'hostname.example.com', 1000, '') - self.assertEqual(result, False) - self.assertEqual(err, b'Certificate type is host; expected user') - - # Check the second hostname and an unknown one - result, err = certified_key.check_cert( - True, b'hostname2.example.com', 1000, '') - self.assertEqual(result, True) - result, err = certified_key.check_cert( - True, b'hostname3.example.com', 1000, '') - self.assertEqual(result, False) - self.assertEqual(err, b'Certificate\'s hostname list [' - b'"hostname.example.com", "hostname2.example.com"] ' - b'does not contain expected hostname ' - b'"hostname3.example.com"') - - # And just for luck, try a totally unknown certificate type, - # making sure that it's rejected in both modes and gives the - # right error message - cert_pub = sign_cert_via_testcrypt( - make_signature_preimage( - key_to_certify = base_key.public_blob(), - ca_key = ca_key, - certtype = 12345, - keyid = b'id', - serial = 114, - principals = [b'username', b'hostname.example.com'], - valid_after = 1000, - valid_before = 2000), ca_key) - certified_key = ssh_key_new_priv(alg + '-cert', cert_pub, - base_key.private_blob()) - result, err = certified_key.check_cert( - False, b'username', 1000, '') - self.assertEqual(result, False) - self.assertEqual(err, b'Certificate type is unknown value 12345; ' - b'expected user') - result, err = certified_key.check_cert( - True, b'hostname.example.com', 1000, '') - self.assertEqual(result, False) - self.assertEqual(err, b'Certificate type is unknown value 12345; ' - b'expected host') - - ca_key = ssh_key_new_priv('ed25519', b64('AAAAC3NzaC1lZDI1NTE5AAAAIMUJEFAmSV/qtoxSmVOHUgTMKYjqkDy8fTfsfCKV+sN7'), b64('AAAAIK4STyaf63xHidqhvUop9/OKiYqSh/YEWLCp1lL5Vs4u')) - - base_key = ssh_key_new_priv('ed25519', b64('AAAAC3NzaC1lZDI1NTE5AAAAIMt0/CMBL+64GQ/r/JyGxo6oHs86i9bOHhMJYbDbxEJf'), b64('AAAAIB38jy02ZWYb4EXrJG9RIljEhqidrG5DdhZvMvoeOTZs')) - per_base_keytype_tests('ed25519', run_validation_tests=True) - - base_key = ssh_key_new_priv('p256', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGc8VXplXScdWckJgAw6Hag5PP7g0JEVdLY5lP2ujvVxU5GwwquYLbX3yyj1zY5h2n9GoXrnRxzR5+5g8wsNjTA='), b64('AAAAICVRicPD5MyOHfKdnC/8IP84t+nQ4bqmMUyX7NHyCKjS')) - per_base_keytype_tests('p256') - - base_key = ssh_key_new_priv('p384', b64('AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLITujAbKwHDEzVDFqWtA+CleAhN/Y+53mHbEoTpU0aof9L+2lHeUshXdxHDLxY69wO5+WfqWJCwSY58PuXIZzIisQkvIKq6LhpzK6C5JpWJ8Kbv7su+qZPf5sYoxx0xZg=='), b64('AAAAMHyQTQYcIA/bR4ZvWS86ohb5Lu0MhzjD8bUb3q8jnROOe3BrE9I8oJcx+l1lddPouA==')) - per_base_keytype_tests('p384') - - base_key = ssh_key_new_priv('p521', b64('AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADButwMRGdLkFhWcSDsLhRhgyrLQq1/A0M8x4GgEmesh4iydo4tGKZR14GhHvx150IWTE1Tre4wyH+1FsTfAlpUBgBDQjsZE0D3u3SLp4qjjhzyrJGhEUDd9J6lsr6JrXbTefz5+LkM9m5l86y9PoAgT+F25OiTYlfvR5qx/pzIPoCnpA=='), b64('AAAAQgFV8xBXC7XZNxdW1oWg6yCZjys2AX4beZVehE9A2R/4m11dHnfqoE1FzbRxj9xqwKvHZRhMOJ//DYuhtcG6+6yHsA==')) - per_base_keytype_tests('p521') - - base_key = ssh_key_new_priv('dsa', b64('AAAAB3NzaC1kc3MAAABCAXgDrF9Fw/Ty+QcoljAGjGL/Ph5+NBQqUYADm4wxF+aazjQXLuZ0VW9OdYBisgDZlYDj/w7y9NxCBgax2BSkhDNxAAAAFQC/YwnFzcom6cRRHPXtOUDLi2I29QAAAEIAqGOUYpfFPwzhgAmYXwWKdK8ouSUplNE29FOpv6NYjyf7k+tLSWF3b8oZdtw6XP8lr4vcKXC9Ik0YpKYKM7iKfb8AAABCAUDCcojlDLQmLHg8HhFCtT/CpayNh4OfmSrP8XOwJnFD/eBaSGuPB5EvGd+m6gr+Pc0RSAlWP1aIzUbYkQ33Yk58'), b64('AAAAFQChVuOTNrCwLSJygxlRQhDwHozwSg==')) - per_base_keytype_tests('dsa') - - base_key = ssh_key_new_priv('rsa', b64('AAAAB3NzaC1yc2EAAAADAQABAAAAgQDXLnqGPQLL9byoHFQWPiF5Uzcd0KedMRRJmuwyCAWprlh8EN43mL2F7q27Uv54m/ztqW4DsVtiCN6cDYvB9QPNYFR5npwsEAJ06Ro4s9ZpFsZVOvitqeoYIs+jkS8vq5V8X4hwLlJ8vXYPD6rHJhOz6HFpImHmVu40Mu5lq+MCQQ=='), b64('AAAAgH5dBwrJzVilKHK4oBCnz9SFr7pMjAHdjoJi/g2rdFfe0IubBEQ16CY8sb1t0Y5WXEPc2YRFpNp/RurxcX8nOWFPzgNJXEtkKpKO9Juqu5hL4xcf8QKC2aJFk3EXrn/M6dXEdjqN4UhsT6iFTsHKU4b8T6VTtgKzwkOdic/YotaBAAAAQQD6liDTlzTKzLhbypI6l+y2BGA3Kkzz71Y2o7XH/6bZ6HJOFgHuJeL3eNQptzd8Q+ctfvR0fa2PItYydDOlVUeZAAAAQQDb1IsO1/fkflDZhPQT2XOxtrjgQhotKjr6CSmJtDNmo1mOCN+mOgxtDfJ0PNEEM1P9CO2Ia3njtkxt4Ep2EpjpAAAAQQClRxLEHsRK9nMPZ4HW45iyw5dHhYar9pYUql2VnixWQxrHy13ZIaWxi6xwWjuPglrdBgEQfYwH9KGmlFmZXT/Z')) - per_base_keytype_tests('rsa') - - # Now switch to an RSA certifying key, and test different RSA - # signature subtypes being used to sign the certificate - ca_key = ssh_key_new_priv('rsa', b64('AAAAB3NzaC1yc2EAAAADAQABAAAAgQCKHiavhtnAZQLUPtYlzlQmVTHSKq2ChCKZP0cLNtN2YSS0/f4D1hi8W04Qh/JuSXZAdUThTAVjxDmxpiOMNwa/2WDXMuqip47dzZSQxtSdvTfeL9TVC/M1NaOzy8bqFx6pzi37zPATETT4PP1Zt/Pd23ZJYhwjxSyTlqj7529v0w=='), b64('AAAAgCwTZyEIlaCyG28EBm7WI0CAW3/IIsrNxATHjrJjcqQKaB5iF5e90PL66DSaTaEoTFZRlgOXsPiffBHXBO0P+lTyZ2jlq2J2zgeofRH3Yong4BT4xDtqBKtxixgC1MAHmrOnRXjAcDUiLxIGgU0YKSv0uAlgARsUwDsk0GEvK+jBAAAAQQDMi7liRBQ4/Z6a4wDL/rVnIJ9x+2h2UPK9J8U7f97x/THIBtfkbf9O7nDP6onValuSr86tMR24DJZsEXaGPwjDAAAAQQCs3J3D3jNVwwk16oySRSjA5x3tKCEITYMluyXX06cvFew8ldgRCYl1sh8RYAfbBKXhnJD77qIxtVNaF1yl/guxAAAAQFTRdKRUF2wLu/K/Rr34trwKrV6aW0GWyHlLuWvF7FUB85aDmtqYI2BSk92mVCKHBNw2T3cJMabN9JOznjtADiM=')) - per_base_keytype_tests('rsa', run_ca_rsa_tests=True) - per_base_keytype_tests('rsa', run_ca_rsa_tests=True, ca_signflags=2) - per_base_keytype_tests('rsa', run_ca_rsa_tests=True, ca_signflags=4) - - def testAESGCMBlockBoundaries(self): - # For standard AES-GCM test vectors, see the separate tests in - # standard_test_vectors.testAESGCM. This function will test - # the local interface, including the skip length and the - # machinery for incremental MAC update. - - def aesgcm(key, iv, aes_impl, gcm_impl): - c = ssh_cipher_new('aes{:d}_gcm_{}'.format(8*len(key), aes_impl)) - if c is None: return None, None # skip test if HW AES not available - m = ssh2_mac_new('aesgcm_{}'.format(gcm_impl), c) - if m is None: return None, None # skip test if HW GCM not available - c.setkey(key) - c.setiv(iv + b'\0'*4) - m.setkey(b'') - return c, m - - def test_one(aes_impl, gcm_impl): - # An actual test from a session with OpenSSH, which - # demonstrates that the implementation in practice matches up - # to what the test vectors say. This is its SSH2_MSG_EXT_INFO - # packet. - key = unhex('dbf98b2f56c83fb2f9476aa876511225') - iv = unhex('9af15ecccf2bacaaa9625a6a') - plain = unhex('1007000000020000000f736572766572' - '2d7369672d616c6773000000db737368' - '2d656432353531392c736b2d7373682d' - '65643235353139406f70656e7373682e' - '636f6d2c7373682d7273612c7273612d' - '736861322d3235362c7273612d736861' - '322d3531322c7373682d6473732c6563' - '6473612d736861322d6e697374703235' - '362c65636473612d736861322d6e6973' - '74703338342c65636473612d73686132' - '2d6e697374703532312c736b2d656364' - '73612d736861322d6e69737470323536' - '406f70656e7373682e636f6d2c776562' - '617574686e2d736b2d65636473612d73' - '6861322d6e69737470323536406f7065' - '6e7373682e636f6d0000001f7075626c' - '69636b65792d686f7374626f756e6440' - '6f70656e7373682e636f6d0000000130' - '5935130804ad4b19ed2789210290c438') - aad = unhex('00000130') - cipher = unhex('c4b88f35c1ef8aa6225033c3f185d648' - '3c485d84930d5846f7851daacbff49d5' - '8cf72169fca7ab3c170376df65dd69de' - 'c40a94c6b8e3da6d61161ab19be27466' - '02e0dfa3330faae291ef4173a20e87a4' - 'd40728c645baa72916c1958531ef7b54' - '27228513e53005e6d17b9bb384b8d8c1' - '92b8a10b731459eed5a0fb120c283412' - 'e34445981df1257f1c35a06196731fed' - '1b3115f419e754de0b634bf68768cb02' - '29e70bb2259cedb5101ff6a4ac19aaad' - '46f1c30697361b45d6c152c3069cee6b' - 'd46e9785d65ea6bf7fca41f0ac3c8e93' - 'ce940b0059c39d51e49c17f60d48d633' - '5bae4402faab61d8d65221b24b400e65' - '89f941ff48310231a42641851ea00832' - '2c2d188f4cc6a4ec6002161c407d0a92' - 'f1697bb319fbec1ca63fa8e7ac171c85' - '5b60142bfcf4e5b0a9ada3451799866e') - - c, m = aesgcm(key, iv, aes_impl, gcm_impl) - if c is None or m is None: return # skip if HW impl unavailable - len_dec = c.decrypt_length(aad, 123) - self.assertEqual(len_dec, aad) # length not actually encrypted - m.start() - # We expect 4 bytes skipped (the sequence number that - # ChaCha20-Poly1305 wants at the start of its MAC), and 4 - # bytes AAD. These were initialised by the call to - # encrypt_length. - m.update(b'fake' + aad + cipher) - self.assertEqualBin(m.genresult(), - unhex('4a5a6d57d54888b4e58c57a96e00b73a')) - self.assertEqualBin(c.decrypt(cipher), plain) - - c, m = aesgcm(key, iv, aes_impl, gcm_impl) - len_enc = c.encrypt_length(aad, 123) - self.assertEqual(len_enc, aad) # length not actually encrypted - self.assertEqualBin(c.encrypt(plain), cipher) - - # Test incremental update. - def testIncremental(skiplen, aad, plain): - key, iv = b'SomeRandomKeyVal', b'SomeRandomIV' - mac_input = b'x' * skiplen + aad + plain - - c, m = aesgcm(key, iv, aes_impl, gcm_impl) - aesgcm_set_prefix_lengths(m, skiplen, len(aad)) - - m.start() - m.update(mac_input) - reference_mac = m.genresult() - - # Break the input just once, at each possible byte - # position. - for i in range(1, len(mac_input)): - c.setiv(iv + b'\0'*4) - m.setkey(b'') - aesgcm_set_prefix_lengths(m, skiplen, len(aad)) - m.start() - m.update(mac_input[:i]) - m.update(mac_input[i:]) - self.assertEqualBin(m.genresult(), reference_mac) - - # Feed the entire input in a byte at a time. - c.setiv(iv + b'\0'*4) - m.setkey(b'') - aesgcm_set_prefix_lengths(m, skiplen, len(aad)) - m.start() - for i in range(len(mac_input)): - m.update(mac_input[i:i+1]) - self.assertEqualBin(m.genresult(), reference_mac) - - # Incremental test with more than a full block of each thing - testIncremental(23, b'abcdefghijklmnopqrst', - b'Lorem ipsum dolor sit amet') - - # Incremental test with exactly a full block of each thing - testIncremental(16, b'abcdefghijklmnop', - b'Lorem ipsum dolo') - - # Incremental test with less than a full block of each thing - testIncremental(7, b'abcdefghij', - b'Lorem ipsum') - - for aes_impl in get_aes_impls(): - for gcm_impl in get_aesgcm_impls(): - with self.subTest(aes_impl=aes_impl, gcm_impl=gcm_impl): - test_one(aes_impl, gcm_impl) - - def testAESGCMIV(self): - key = b'SomeRandomKeyVal' - - def test(gcm, cbc, iv_fixed, iv_msg): - gcm.setiv(ssh_uint32(iv_fixed) + ssh_uint64(iv_msg) + b'fake') - - cbc.setiv(b'\0' * 16) - preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16)) - self.assertEqualBin(preimage, ssh_uint32(iv_fixed) + - ssh_uint64(iv_msg) + ssh_uint32(1)) - cbc.setiv(b'\0' * 16) - preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16)) - self.assertEqualBin(preimage, ssh_uint32(iv_fixed) + - ssh_uint64(iv_msg) + ssh_uint32(2)) - - gcm.next_message() - iv_msg = (iv_msg + 1) & ((1<<64)-1) - - cbc.setiv(b'\0' * 16) - preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16)) - self.assertEqualBin(preimage, ssh_uint32(iv_fixed) + - ssh_uint64(iv_msg) + ssh_uint32(1)) - cbc.setiv(b'\0' * 16) - preimage = cbc.decrypt(gcm.encrypt(b'\0' * 16)) - self.assertEqualBin(preimage, ssh_uint32(iv_fixed) + - ssh_uint64(iv_msg) + ssh_uint32(2)) - - - for impl in get_aes_impls(): - with self.subTest(aes_impl=impl): - gcm = ssh_cipher_new('aes{:d}_gcm_{}'.format(8*len(key), impl)) - if gcm is None: continue # skip if HW AES unavailable - gcm.setkey(key) - - cbc = ssh_cipher_new('aes{:d}_cbc_{}'.format(8*len(key), impl)) - # assume if gcm_ is available, cbc_ will be too - cbc.setkey(key) - - # A simple test to ensure the low word gets - # incremented and that the whole IV looks basically - # the way we expect it to - test(gcm, cbc, 0x27182818, 0x3141592653589793) - - # Test that carries are propagated into the high word - test(gcm, cbc, 0x27182818, 0x00000000FFFFFFFF) - - # Test that carries _aren't_ propagated out of the - # high word of the message counter into the fixed word - # at the top - test(gcm, cbc, 0x27182818, 0xFFFFFFFFFFFFFFFF) - -class standard_test_vectors(MyTestBase): - def testAES(self): - def vector(cipher, key, plaintext, ciphertext): - for suffix in get_aes_impls(): - c = ssh_cipher_new("{}_{}".format(cipher, suffix)) - if c is None: return # skip test if HW AES not available - ssh_cipher_setkey(c, key) - - # The AES test vectors are implicitly in ECB mode, - # because they're testing the cipher primitive rather - # than any mode layered on top of it. We fake this by - # using PuTTY's CBC setting, and clearing the IV to - # all zeroes before each operation. - - ssh_cipher_setiv(c, b'\x00' * 16) - self.assertEqualBin( - ssh_cipher_encrypt(c, plaintext), ciphertext) - - ssh_cipher_setiv(c, b'\x00' * 16) - self.assertEqualBin( - ssh_cipher_decrypt(c, ciphertext), plaintext) - - # The test vector from FIPS 197 appendix B. (This is also the - # same key whose key setup phase is shown in detail in - # appendix A.) - vector('aes128_cbc', - unhex('2b7e151628aed2a6abf7158809cf4f3c'), - unhex('3243f6a8885a308d313198a2e0370734'), - unhex('3925841d02dc09fbdc118597196a0b32')) - - # The test vectors from FIPS 197 appendix C: the key bytes go - # 00 01 02 03 ... for as long as needed, and the plaintext - # bytes go 00 11 22 33 ... FF. - fullkey = struct.pack("B"*32, *range(32)) - plaintext = struct.pack("B"*16, *[0x11*i for i in range(16)]) - vector('aes128_cbc', fullkey[:16], plaintext, - unhex('69c4e0d86a7b0430d8cdb78070b4c55a')) - vector('aes192_cbc', fullkey[:24], plaintext, - unhex('dda97ca4864cdfe06eaf70a0ec0d7191')) - vector('aes256_cbc', fullkey[:32], plaintext, - unhex('8ea2b7ca516745bfeafc49904b496089')) - - def testDES(self): - c = ssh_cipher_new("des_cbc") - def vector(key, plaintext, ciphertext): - key = unhex(key) - plaintext = unhex(plaintext) - ciphertext = unhex(ciphertext) - - # Similarly to above, we fake DES ECB by using DES CBC and - # resetting the IV to zero all the time - ssh_cipher_setkey(c, key) - ssh_cipher_setiv(c, b'\x00' * 8) - self.assertEqualBin(ssh_cipher_encrypt(c, plaintext), ciphertext) - ssh_cipher_setiv(c, b'\x00' * 8) - self.assertEqualBin(ssh_cipher_decrypt(c, ciphertext), plaintext) - - # Source: FIPS SP PUB 500-20 - - # 'Initial permutation and expansion tests': key fixed at 8 - # copies of the byte 01, but ciphertext and plaintext in turn - # run through all possible values with exactly 1 bit set. - # Expected plaintexts and ciphertexts (respectively) listed in - # the arrays below. - ipe_key = '01' * 8 - ipe_plaintexts = [ -'166B40B44ABA4BD6', '06E7EA22CE92708F', 'D2FD8867D50D2DFE', 'CC083F1E6D9E85F6', -'5B711BC4CEEBF2EE', '0953E2258E8E90A1', 'E07C30D7E4E26E12', '2FBC291A570DB5C4', -'DD7C0BBD61FAFD54', '48221B9937748A23', 'E643D78090CA4207', '8405D1ABE24FB942', -'CE332329248F3228', '1D1CA853AE7C0C5F', '5D86CB23639DBEA9', '1029D55E880EC2D0', -'8DD45A2DDF90796C', 'CAFFC6AC4542DE31', 'EA51D3975595B86B', '8B54536F2F3E64A8', -'866ECEDD8072BB0E', '79E90DBC98F92CCA', 'AB6A20C0620D1C6F', '25EB5FC3F8CF0621', -'4D49DB1532919C9F', '814EEB3B91D90726', '5E0905517BB59BCF', 'CA3A2B036DBC8502', -'FA0752B07D9C4AB8', 'B160E4680F6C696F', 'DF98C8276F54B04B', 'E943D7568AEC0C5C', -'AEB5F5EDE22D1A36', 'E428581186EC8F46', 'E1652C6B138C64A5', 'D106FF0BED5255D7', -'9D64555A9A10B852', 'F02B263B328E2B60', '64FEED9C724C2FAF', '750D079407521363', -'FBE00A8A1EF8AD72', 'A484C3AD38DC9C19', '12A9F5817FF2D65D', 'E7FCE22557D23C97', -'329A8ED523D71AEC', 'E19E275D846A1298', '889DE068A16F0BE6', '2B9F982F20037FA9', -'F356834379D165CD', 'ECBFE3BD3F591A5E', 'E6D5F82752AD63D1', 'ADD0CC8D6E5DEBA1', -'F15D0F286B65BD28', 'B8061B7ECD9A21E5', '424250B37C3DD951', 'D9031B0271BD5A0A', -'0D9F279BA5D87260', '6CC5DEFAAF04512F', '55579380D77138EF', '20B9E767B2FB1456', -'4BD388FF6CD81D4F', '2E8653104F3834EA', 'DD7F121CA5015619', '95F8A5E5DD31D900', - ] - ipe_ciphertexts = [ -'166B40B44ABA4BD6', '06E7EA22CE92708F', 'D2FD8867D50D2DFE', 'CC083F1E6D9E85F6', -'5B711BC4CEEBF2EE', '0953E2258E8E90A1', 'E07C30D7E4E26E12', '2FBC291A570DB5C4', -'DD7C0BBD61FAFD54', '48221B9937748A23', 'E643D78090CA4207', '8405D1ABE24FB942', -'CE332329248F3228', '1D1CA853AE7C0C5F', '5D86CB23639DBEA9', '1029D55E880EC2D0', -'8DD45A2DDF90796C', 'CAFFC6AC4542DE31', 'EA51D3975595B86B', '8B54536F2F3E64A8', -'866ECEDD8072BB0E', '79E90DBC98F92CCA', 'AB6A20C0620D1C6F', '25EB5FC3F8CF0621', -'4D49DB1532919C9F', '814EEB3B91D90726', '5E0905517BB59BCF', 'CA3A2B036DBC8502', -'FA0752B07D9C4AB8', 'B160E4680F6C696F', 'DF98C8276F54B04B', 'E943D7568AEC0C5C', -'AEB5F5EDE22D1A36', 'E428581186EC8F46', 'E1652C6B138C64A5', 'D106FF0BED5255D7', -'9D64555A9A10B852', 'F02B263B328E2B60', '64FEED9C724C2FAF', '750D079407521363', -'FBE00A8A1EF8AD72', 'A484C3AD38DC9C19', '12A9F5817FF2D65D', 'E7FCE22557D23C97', -'329A8ED523D71AEC', 'E19E275D846A1298', '889DE068A16F0BE6', '2B9F982F20037FA9', -'F356834379D165CD', 'ECBFE3BD3F591A5E', 'E6D5F82752AD63D1', 'ADD0CC8D6E5DEBA1', -'F15D0F286B65BD28', 'B8061B7ECD9A21E5', '424250B37C3DD951', 'D9031B0271BD5A0A', -'0D9F279BA5D87260', '6CC5DEFAAF04512F', '55579380D77138EF', '20B9E767B2FB1456', -'4BD388FF6CD81D4F', '2E8653104F3834EA', 'DD7F121CA5015619', '95F8A5E5DD31D900', - ] - ipe_single_bits = ["{:016x}".format(1 << bit) for bit in range(64)] - for plaintext, ciphertext in zip(ipe_plaintexts, ipe_single_bits): - vector(ipe_key, plaintext, ciphertext) - for plaintext, ciphertext in zip(ipe_single_bits, ipe_ciphertexts): - vector(ipe_key, plaintext, ciphertext) - - # 'Key permutation tests': plaintext fixed at all zeroes, key - # is a succession of tweaks of the previous key made by - # replacing each 01 byte in turn with one containing a - # different single set bit (e.g. 01 20 01 01 01 01 01 01). - # Expected ciphertexts listed. - kp_ciphertexts = [ -'95A8D72813DAA94D', '0EEC1487DD8C26D5', '7AD16FFB79C45926', 'D3746294CA6A6CF3', -'809F5F873C1FD761', 'C02FAFFEC989D1FC', '4615AA1D33E72F10', '2055123350C00858', -'DF3B99D6577397C8', '31FE17369B5288C9', 'DFDD3CC64DAE1642', '178C83CE2B399D94', -'50F636324A9B7F80', 'A8468EE3BC18F06D', 'A2DC9E92FD3CDE92', 'CAC09F797D031287', -'90BA680B22AEB525', 'CE7A24F350E280B6', '882BFF0AA01A0B87', '25610288924511C2', -'C71516C29C75D170', '5199C29A52C9F059', 'C22F0A294A71F29F', 'EE371483714C02EA', -'A81FBD448F9E522F', '4F644C92E192DFED', '1AFA9A66A6DF92AE', 'B3C1CC715CB879D8', -'19D032E64AB0BD8B', '3CFAA7A7DC8720DC', 'B7265F7F447AC6F3', '9DB73B3C0D163F54', -'8181B65BABF4A975', '93C9B64042EAA240', '5570530829705592', '8638809E878787A0', -'41B9A79AF79AC208', '7A9BE42F2009A892', '29038D56BA6D2745', '5495C6ABF1E5DF51', -'AE13DBD561488933', '024D1FFA8904E389', 'D1399712F99BF02E', '14C1D7C1CFFEC79E', -'1DE5279DAE3BED6F', 'E941A33F85501303', 'DA99DBBC9A03F379', 'B7FC92F91D8E92E9', -'AE8E5CAA3CA04E85', '9CC62DF43B6EED74', 'D863DBB5C59A91A0', 'A1AB2190545B91D7', -'0875041E64C570F7', '5A594528BEBEF1CC', 'FCDB3291DE21F0C0', '869EFD7F9F265A09', - ] - kp_key_repl_bytes = ["{:02x}".format(0x80>>i) for i in range(7)] - kp_keys = ['01'*j + b + '01'*(7-j) - for j in range(8) for b in kp_key_repl_bytes] - kp_plaintext = '0' * 16 - for key, ciphertext in zip(kp_keys, kp_ciphertexts): - vector(key, kp_plaintext, ciphertext) - - # 'Data permutation test': plaintext fixed at all zeroes, - # pairs of key and expected ciphertext listed below. - dp_keys_and_ciphertexts = [ -'1046913489980131:88D55E54F54C97B4', '1007103489988020:0C0CC00C83EA48FD', -'10071034C8980120:83BC8EF3A6570183', '1046103489988020:DF725DCAD94EA2E9', -'1086911519190101:E652B53B550BE8B0', '1086911519580101:AF527120C485CBB0', -'5107B01519580101:0F04CE393DB926D5', '1007B01519190101:C9F00FFC74079067', -'3107915498080101:7CFD82A593252B4E', '3107919498080101:CB49A2F9E91363E3', -'10079115B9080140:00B588BE70D23F56', '3107911598080140:406A9A6AB43399AE', -'1007D01589980101:6CB773611DCA9ADA', '9107911589980101:67FD21C17DBB5D70', -'9107D01589190101:9592CB4110430787', '1007D01598980120:A6B7FF68A318DDD3', -'1007940498190101:4D102196C914CA16', '0107910491190401:2DFA9F4573594965', -'0107910491190101:B46604816C0E0774', '0107940491190401:6E7E6221A4F34E87', -'19079210981A0101:AA85E74643233199', '1007911998190801:2E5A19DB4D1962D6', -'10079119981A0801:23A866A809D30894', '1007921098190101:D812D961F017D320', -'100791159819010B:055605816E58608F', '1004801598190101:ABD88E8B1B7716F1', -'1004801598190102:537AC95BE69DA1E1', '1004801598190108:AED0F6AE3C25CDD8', -'1002911498100104:B3E35A5EE53E7B8D', '1002911598190104:61C79C71921A2EF8', -'1002911598100201:E2F5728F0995013C', '1002911698100101:1AEAC39A61F0A464', - ] - dp_plaintext = '0' * 16 - for key_and_ciphertext in dp_keys_and_ciphertexts: - key, ciphertext = key_and_ciphertext.split(":") - vector(key, dp_plaintext, ciphertext) - - # Tests intended to select every entry in every S-box. Full - # arbitrary triples (key, plaintext, ciphertext). - sb_complete_tests = [ - '7CA110454A1A6E57:01A1D6D039776742:690F5B0D9A26939B', - '0131D9619DC1376E:5CD54CA83DEF57DA:7A389D10354BD271', - '07A1133E4A0B2686:0248D43806F67172:868EBB51CAB4599A', - '3849674C2602319E:51454B582DDF440A:7178876E01F19B2A', - '04B915BA43FEB5B6:42FD443059577FA2:AF37FB421F8C4095', - '0113B970FD34F2CE:059B5E0851CF143A:86A560F10EC6D85B', - '0170F175468FB5E6:0756D8E0774761D2:0CD3DA020021DC09', - '43297FAD38E373FE:762514B829BF486A:EA676B2CB7DB2B7A', - '07A7137045DA2A16:3BDD119049372802:DFD64A815CAF1A0F', - '04689104C2FD3B2F:26955F6835AF609A:5C513C9C4886C088', - '37D06BB516CB7546:164D5E404F275232:0A2AEEAE3FF4AB77', - '1F08260D1AC2465E:6B056E18759F5CCA:EF1BF03E5DFA575A', - '584023641ABA6176:004BD6EF09176062:88BF0DB6D70DEE56', - '025816164629B007:480D39006EE762F2:A1F9915541020B56', - '49793EBC79B3258F:437540C8698F3CFA:6FBF1CAFCFFD0556', - '4FB05E1515AB73A7:072D43A077075292:2F22E49BAB7CA1AC', - '49E95D6D4CA229BF:02FE55778117F12A:5A6B612CC26CCE4A', - '018310DC409B26D6:1D9D5C5018F728C2:5F4C038ED12B2E41', - '1C587F1C13924FEF:305532286D6F295A:63FAC0D034D9F793', - ] - for test in sb_complete_tests: - key, plaintext, ciphertext = test.split(":") - vector(key, plaintext, ciphertext) - - def testMD5(self): - MD5 = lambda s: hash_str('md5', s) - - # The test vectors from RFC 1321 section A.5. - self.assertEqualBin(MD5(""), - unhex('d41d8cd98f00b204e9800998ecf8427e')) - self.assertEqualBin(MD5("a"), - unhex('0cc175b9c0f1b6a831c399e269772661')) - self.assertEqualBin(MD5("abc"), - unhex('900150983cd24fb0d6963f7d28e17f72')) - self.assertEqualBin(MD5("message digest"), - unhex('f96b697d7cb7938d525a2f31aaf161d0')) - self.assertEqualBin(MD5("abcdefghijklmnopqrstuvwxyz"), - unhex('c3fcd3d76192e4007dfb496cca67e13b')) - self.assertEqualBin(MD5("ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz0123456789"), - unhex('d174ab98d277d9f5a5611c2c9f419d9f')) - self.assertEqualBin(MD5("1234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890"), - unhex('57edf4a22be3c955ac49da2e2107b67a')) - - def testHmacMD5(self): - # The test vectors from the RFC 2104 Appendix. - self.assertEqualBin(mac_str('hmac_md5', unhex('0b'*16), "Hi There"), - unhex('9294727a3638bb1c13f48ef8158bfc9d')) - self.assertEqualBin(mac_str('hmac_md5', "Jefe", - "what do ya want for nothing?"), - unhex('750c783e6ab0b503eaa86e310a5db738')) - self.assertEqualBin(mac_str('hmac_md5', unhex('aa'*16), unhex('dd'*50)), - unhex('56be34521d144c88dbb8c733f0e8b3f6')) - - def testSHA1(self): - for hashname in get_implementations("sha1"): - if ssh_hash_new(hashname) is None: - continue # skip testing of unavailable HW implementation - - # Test cases from RFC 6234 section 8.5, omitting the ones - # whose input is not a multiple of 8 bits - self.assertEqualBin(hash_str(hashname, "abc"), unhex( - "a9993e364706816aba3e25717850c26c9cd0d89d")) - self.assertEqualBin(hash_str(hashname, - "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), - unhex("84983e441c3bd26ebaae4aa1f95129e5e54670f1")) - self.assertEqualBin(hash_str_iter(hashname, - ("a" * 1000 for _ in range(1000))), unhex( - "34aa973cd4c4daa4f61eeb2bdbad27316534016f")) - self.assertEqualBin(hash_str(hashname, - "01234567012345670123456701234567" * 20), unhex( - "dea356a2cddd90c7a7ecedc5ebb563934f460452")) - self.assertEqualBin(hash_str(hashname, b"\x5e"), unhex( - "5e6f80a34a9798cafc6a5db96cc57ba4c4db59c2")) - self.assertEqualBin(hash_str(hashname, - unhex("9a7dfdf1ecead06ed646aa55fe757146")), unhex( - "82abff6605dbe1c17def12a394fa22a82b544a35")) - self.assertEqualBin(hash_str(hashname, unhex( - "f78f92141bcd170ae89b4fba15a1d59f" - "3fd84d223c9251bdacbbae61d05ed115" - "a06a7ce117b7beead24421ded9c32592" - "bd57edeae39c39fa1fe8946a84d0cf1f" - "7beead1713e2e0959897347f67c80b04" - "00c209815d6b10a683836fd5562a56ca" - "b1a28e81b6576654631cf16566b86e3b" - "33a108b05307c00aff14a768ed735060" - "6a0f85e6a91d396f5b5cbe577f9b3880" - "7c7d523d6d792f6ebc24a4ecf2b3a427" - "cdbbfb")), unhex( - "cb0082c8f197d260991ba6a460e76e202bad27b3")) - - def testSHA256(self): - for hashname in get_implementations("sha256"): - if ssh_hash_new(hashname) is None: - continue # skip testing of unavailable HW implementation - - # Test cases from RFC 6234 section 8.5, omitting the ones - # whose input is not a multiple of 8 bits - self.assertEqualBin(hash_str(hashname, "abc"), - unhex("ba7816bf8f01cfea414140de5dae2223" - "b00361a396177a9cb410ff61f20015ad")) - self.assertEqualBin(hash_str(hashname, - "abcdbcdecdefdefgefghfghighijhijk""ijkljklmklmnlmnomnopnopq"), - unhex("248d6a61d20638b8e5c026930c3e6039" - "a33ce45964ff2167f6ecedd419db06c1")) - self.assertEqualBin( - hash_str_iter(hashname, ("a" * 1000 for _ in range(1000))), - unhex("cdc76e5c9914fb9281a1c7e284d73e67" - "f1809a48a497200e046d39ccc7112cd0")) - self.assertEqualBin( - hash_str(hashname, "01234567012345670123456701234567" * 20), - unhex("594847328451bdfa85056225462cc1d8" - "67d877fb388df0ce35f25ab5562bfbb5")) - self.assertEqualBin(hash_str(hashname, b"\x19"), - unhex("68aa2e2ee5dff96e3355e6c7ee373e3d" - "6a4e17f75f9518d843709c0c9bc3e3d4")) - self.assertEqualBin( - hash_str(hashname, unhex("e3d72570dcdd787ce3887ab2cd684652")), - unhex("175ee69b02ba9b58e2b0a5fd13819cea" - "573f3940a94f825128cf4209beabb4e8")) - self.assertEqualBin(hash_str(hashname, unhex( - "8326754e2277372f4fc12b20527afef0" - "4d8a056971b11ad57123a7c137760000" - "d7bef6f3c1f7a9083aa39d810db31077" - "7dab8b1e7f02b84a26c773325f8b2374" - "de7a4b5a58cb5c5cf35bcee6fb946e5b" - "d694fa593a8beb3f9d6592ecedaa66ca" - "82a29d0c51bcf9336230e5d784e4c0a4" - "3f8d79a30a165cbabe452b774b9c7109" - "a97d138f129228966f6c0adc106aad5a" - "9fdd30825769b2c671af6759df28eb39" - "3d54d6")), unhex( - "97dbca7df46d62c8a422c941dd7e835b" - "8ad3361763f7e9b2d95f4f0da6e1ccbc")) - - def testSHA384(self): - for hashname in get_implementations("sha384"): - if ssh_hash_new(hashname) is None: - continue # skip testing of unavailable HW implementation - - # Test cases from RFC 6234 section 8.5, omitting the ones - # whose input is not a multiple of 8 bits - self.assertEqualBin(hash_str(hashname, "abc"), unhex( - 'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded163' - '1a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7')) - self.assertEqualBin(hash_str(hashname, - "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" - "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"), - unhex('09330c33f71147e83d192fc782cd1b4753111b173b3b05d2' - '2fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039')) - self.assertEqualBin(hash_str_iter(hashname, - ("a" * 1000 for _ in range(1000))), unhex( - '9d0e1809716474cb086e834e310a4a1ced149e9c00f24852' - '7972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985')) - self.assertEqualBin(hash_str(hashname, - "01234567012345670123456701234567" * 20), unhex( - '2fc64a4f500ddb6828f6a3430b8dd72a368eb7f3a8322a70' - 'bc84275b9c0b3ab00d27a5cc3c2d224aa6b61a0d79fb4596')) - self.assertEqualBin(hash_str(hashname, b"\xB9"), unhex( - 'bc8089a19007c0b14195f4ecc74094fec64f01f90929282c' - '2fb392881578208ad466828b1c6c283d2722cf0ad1ab6938')) - self.assertEqualBin(hash_str(hashname, - unhex("a41c497779c0375ff10a7f4e08591739")), unhex( - 'c9a68443a005812256b8ec76b00516f0dbb74fab26d66591' - '3f194b6ffb0e91ea9967566b58109cbc675cc208e4c823f7')) - self.assertEqualBin(hash_str(hashname, unhex( - "399669e28f6b9c6dbcbb6912ec10ffcf74790349b7dc8fbe4a8e7b3b5621" - "db0f3e7dc87f823264bbe40d1811c9ea2061e1c84ad10a23fac1727e7202" - "fc3f5042e6bf58cba8a2746e1f64f9b9ea352c711507053cf4e5339d5286" - "5f25cc22b5e87784a12fc961d66cb6e89573199a2ce6565cbdf13dca4038" - "32cfcb0e8b7211e83af32a11ac17929ff1c073a51cc027aaedeff85aad7c" - "2b7c5a803e2404d96d2a77357bda1a6daeed17151cb9bc5125a422e941de" - "0ca0fc5011c23ecffefdd09676711cf3db0a3440720e1615c1f22fbc3c72" - "1de521e1b99ba1bd5577408642147ed096")), unhex( - '4f440db1e6edd2899fa335f09515aa025ee177a79f4b4aaf' - '38e42b5c4de660f5de8fb2a5b2fbd2a3cbffd20cff1288c0')) - - def testSHA512(self): - for hashname in get_implementations("sha512"): - if ssh_hash_new(hashname) is None: - continue # skip testing of unavailable HW implementation - - # Test cases from RFC 6234 section 8.5, omitting the ones - # whose input is not a multiple of 8 bits - self.assertEqualBin(hash_str(hashname, "abc"), unhex( - 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55' - 'd39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94f' - 'a54ca49f')) - self.assertEqualBin(hash_str(hashname, - "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" - "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"), - unhex('8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299' - 'aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26' - '545e96e55b874be909')) - self.assertEqualBin(hash_str_iter(hashname, - ("a" * 1000 for _ in range(1000))), unhex( - 'e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa9' - '73ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217' - 'ad8cc09b')) - self.assertEqualBin(hash_str(hashname, - "01234567012345670123456701234567" * 20), unhex( - '89d05ba632c699c31231ded4ffc127d5a894dad412c0e024db872d1abd2b' - 'a8141a0f85072a9be1e2aa04cf33c765cb510813a39cd5a84c4acaa64d3f' - '3fb7bae9')) - self.assertEqualBin(hash_str(hashname, b"\xD0"), unhex( - '9992202938e882e73e20f6b69e68a0a7149090423d93c81bab3f21678d4a' - 'ceeee50e4e8cafada4c85a54ea8306826c4ad6e74cece9631bfa8a549b4a' - 'b3fbba15')) - self.assertEqualBin(hash_str(hashname, - unhex("8d4e3c0e3889191491816e9d98bff0a0")), unhex( - 'cb0b67a4b8712cd73c9aabc0b199e9269b20844afb75acbdd1c153c98289' - '24c3ddedaafe669c5fdd0bc66f630f6773988213eb1b16f517ad0de4b2f0' - 'c95c90f8')) - self.assertEqualBin(hash_str(hashname, unhex( - "a55f20c411aad132807a502d65824e31a2305432aa3d06d3e282a8d84e0d" - "e1de6974bf495469fc7f338f8054d58c26c49360c3e87af56523acf6d89d" - "03e56ff2f868002bc3e431edc44df2f0223d4bb3b243586e1a7d92493669" - "4fcbbaf88d9519e4eb50a644f8e4f95eb0ea95bc4465c8821aacd2fe15ab" - "4981164bbb6dc32f969087a145b0d9cc9c67c22b763299419cc4128be9a0" - "77b3ace634064e6d99283513dc06e7515d0d73132e9a0dc6d3b1f8b246f1" - "a98a3fc72941b1e3bb2098e8bf16f268d64f0b0f4707fe1ea1a1791ba2f3" - "c0c758e5f551863a96c949ad47d7fb40d2")), unhex( - 'c665befb36da189d78822d10528cbf3b12b3eef726039909c1a16a270d48' - '719377966b957a878e720584779a62825c18da26415e49a7176a894e7510' - 'fd1451f5')) - - def testSHA3(self): - # Source: all the SHA-3 test strings from - # https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values#aHashing - # which are a multiple of 8 bits long. - - self.assertEqualBin(hash_str('sha3_224', ''), unhex("6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7")) - self.assertEqualBin(hash_str('sha3_224', unhex('a3')*200), unhex("9376816aba503f72f96ce7eb65ac095deee3be4bf9bbc2a1cb7e11e0")) - self.assertEqualBin(hash_str('sha3_256', ''), unhex("a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a")) - self.assertEqualBin(hash_str('sha3_256', unhex('a3')*200), unhex("79f38adec5c20307a98ef76e8324afbfd46cfd81b22e3973c65fa1bd9de31787")) - self.assertEqualBin(hash_str('sha3_384', ''), unhex("0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004")) - self.assertEqualBin(hash_str('sha3_384', unhex('a3')*200), unhex("1881de2ca7e41ef95dc4732b8f5f002b189cc1e42b74168ed1732649ce1dbcdd76197a31fd55ee989f2d7050dd473e8f")) - self.assertEqualBin(hash_str('sha3_512', ''), unhex("a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26")) - self.assertEqualBin(hash_str('sha3_512', unhex('a3')*200), unhex("e76dfad22084a8b1467fcf2ffa58361bec7628edf5f3fdc0e4805dc48caeeca81b7c13c30adf52a3659584739a2df46be589c51ca1a4a8416df6545a1ce8ba00")) - self.assertEqualBin(hash_str('shake256_114bytes', ''), unhex("46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762fd75dc4ddd8c0f200cb05019d67b592f6fc821c49479ab48640292eacb3b7c4be141e96616fb13957692cc7edd0b45ae3dc07223c8e92937bef84bc0eab862853349ec75546f58fb7c2775c38462c5010d846")) - self.assertEqualBin(hash_str('shake256_114bytes', unhex('a3')*200), unhex("cd8a920ed141aa0407a22d59288652e9d9f1a7ee0c1e7c1ca699424da84a904d2d700caae7396ece96604440577da4f3aa22aeb8857f961c4cd8e06f0ae6610b1048a7f64e1074cd629e85ad7566048efc4fb500b486a3309a8f26724c0ed628001a1099422468de726f1061d99eb9e93604")) - - def testBLAKE2b(self): - # Test case from RFC 7693 appendix A. - self.assertEqualBin(hash_str('blake2b', b'abc'), unhex( - "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1" - "7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923")) - - # A small number of test cases from the larger test vector - # set, testing multiple blocks and the empty input. - self.assertEqualBin(hash_str('blake2b', b''), unhex( - "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419" - "d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce")) - self.assertEqualBin(hash_str('blake2b', unhex('00')), unhex( - "2fa3f686df876995167e7c2e5d74c4c7b6e48f8068fe0e44208344d480f7904c" - "36963e44115fe3eb2a3ac8694c28bcb4f5a0f3276f2e79487d8219057a506e4b")) - self.assertEqualBin(hash_str('blake2b', bytes(range(255))), unhex( - "5b21c5fd8868367612474fa2e70e9cfa2201ffeee8fafab5797ad58fefa17c9b" - "5b107da4a3db6320baaf2c8617d5a51df914ae88da3867c2d41f0cc14fa67928")) - - # You can get this test program to run the full version of the - # test vectors by modifying the source temporarily to set this - # variable to a pathname where you downloaded the JSON file - # blake2-kat.json. - blake2_test_vectors_path = None - if blake2_test_vectors_path is not None: - with open(blake2_test_vectors_path) as fh: - vectors = json.load(fh) - for vector in vectors: - if vector['hash'] != 'blake2b': - continue - if len(vector['key']) != 0: - continue - - h = blake2b_new_general(len(vector['out']) // 2) - ssh_hash_update(h, unhex(vector['in'])) - digest = ssh_hash_digest(h) - self.assertEqualBin(digest, unhex(vector['out'])) - - def testArgon2(self): - # draft-irtf-cfrg-argon2-12 section 5 - self.assertEqualBin( - argon2('d', 32, 3, 4, 32, b'\x01' * 32, b'\x02' * 16, - b'\x03' * 8, b'\x04' * 12), - unhex("512b391b6f1162975371d30919734294" - "f868e3be3984f3c1a13a4db9fabe4acb")) - self.assertEqualBin( - argon2('i', 32, 3, 4, 32, b'\x01' * 32, b'\x02' * 16, - b'\x03' * 8, b'\x04' * 12), - unhex("c814d9d1dc7f37aa13f0d77f2494bda1" - "c8de6b016dd388d29952a4c4672b6ce8")) - self.assertEqualBin( - argon2('id', 32, 3, 4, 32, b'\x01' * 32, b'\x02' * 16, - b'\x03' * 8, b'\x04' * 12), - unhex("0d640df58d78766c08c037a34a8b53c9" - "d01ef0452d75b65eb52520e96b01e659")) - - def testHmacSHA(self): - # Test cases from RFC 6234 section 8.5. - def vector(key, message, s1=None, s256=None, s512=None): - if s1 is not None: - self.assertEqualBin( - mac_str('hmac_sha1', key, message), unhex(s1)) - if s256 is not None: - self.assertEqualBin( - mac_str('hmac_sha256', key, message), unhex(s256)) - if s512 is not None: - self.assertEqualBin( - mac_str('hmac_sha512', key, message), unhex(s512)) - vector( - unhex("0b"*20), "Hi There", - "b617318655057264e28bc0b6fb378c8ef146be00", - "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", - "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cde" - "daa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854") - vector( - "Jefe", "what do ya want for nothing?", - "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79", - "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", - "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea250554" - "9758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737") - vector( - unhex("aa"*20), unhex('dd'*50), - "125d7342b9ac11cd91a39af48aa17b4f63f175d3", - "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565FE", - "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39" - "bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb") - vector( - unhex("0102030405060708090a0b0c0d0e0f10111213141516171819"), - unhex("cd"*50), - "4c9007f4026250c6bc8414f9bf50c86c2d7235da", - "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", - "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3db" - "a91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd") - vector( - unhex("aa"*80), - "Test Using Larger Than Block-Size Key - Hash Key First", - s1="aa4ae5e15272d00e95705637ce8a3b55ed402112") - vector( - unhex("aa"*131), - "Test Using Larger Than Block-Size Key - Hash Key First", - s256="60e431591ee0b67f0d8a26aacbf5b77f" - "8e0bc6213728c5140546040f0ee37f54", s512= - "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f352" - "6b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598") - vector( - unhex("aa"*80), - "Test Using Larger Than Block-Size Key and " - "Larger Than One Block-Size Data", - s1="e8e99d0f45237d786d6bbaa7965c7808bbff1a91") - vector( - unhex("aa"*131), - "This is a test using a larger than block-size key and a " - "larger than block-size data. The key needs to be hashed " - "before being used by the HMAC algorithm.", - s256="9b09ffa71b942fcb27635fbcd5b0e944" - "bfdc63644f0713938a7f51535c3a35e2", s512= - "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944" - "b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58") - - def testEd25519(self): - def vector(privkey, pubkey, message, signature): - x, y = ecc_edwards_get_affine(eddsa_public( - mp_from_bytes_le(privkey), 'ed25519')) - self.assertEqual(int(y) | ((int(x) & 1) << 255), - int(mp_from_bytes_le(pubkey))) - pubblob = ssh_string(b"ssh-ed25519") + ssh_string(pubkey) - privblob = ssh_string(privkey) - sigblob = ssh_string(b"ssh-ed25519") + ssh_string(signature) - pubkey = ssh_key_new_pub('ed25519', pubblob) - self.assertTrue(ssh_key_verify(pubkey, sigblob, message)) - privkey = ssh_key_new_priv('ed25519', pubblob, privblob) - # By testing that the signature is exactly the one expected in - # the test vector and not some equivalent one generated with a - # different nonce, we're verifying in particular that we do - # our deterministic nonce generation in the manner specified - # by Ed25519. Getting that wrong would lead to no obvious - # failure, but would surely turn out to be a bad idea sooner - # or later... - self.assertEqualBin(ssh_key_sign(privkey, message, 0), sigblob) - - # A cherry-picked example from DJB's test vector data at - # https://ed25519.cr.yp.to/python/sign.input, which is too - # large to copy into here in full. - privkey = unhex( - 'c89955e0f7741d905df0730b3dc2b0ce1a13134e44fef3d40d60c020ef19df77') - pubkey = unhex( - 'fdb30673402faf1c8033714f3517e47cc0f91fe70cf3836d6c23636e3fd2287c') - message = unhex( - '507c94c8820d2a5793cbf3442b3d71936f35fe3afef316') - signature = unhex( - '7ef66e5e86f2360848e0014e94880ae2920ad8a3185a46b35d1e07dea8fa8ae4' - 'f6b843ba174d99fa7986654a0891c12a794455669375bf92af4cc2770b579e0c') - vector(privkey, pubkey, message, signature) - - # You can get this test program to run the full version of - # DJB's test vectors by modifying the source temporarily to - # set this variable to a pathname where you downloaded the - # file. - ed25519_test_vector_path = None - if ed25519_test_vector_path is not None: - with open(ed25519_test_vector_path) as f: - for line in iter(f.readline, ""): - words = line.split(":") - # DJB's test vector input format concatenates a - # spare copy of the public key to the end of the - # private key, and a spare copy of the message to - # the end of the signature. Strip those off. - privkey = unhex(words[0])[:32] - pubkey = unhex(words[1]) - message = unhex(words[2]) - signature = unhex(words[3])[:64] - vector(privkey, pubkey, message, signature) - - def testEd448(self): - def vector(privkey, pubkey, message, signature): - x, y = ecc_edwards_get_affine(eddsa_public( - mp_from_bytes_le(privkey), 'ed448')) - self.assertEqual(int(y) | ((int(x) & 1) << 455), - int(mp_from_bytes_le(pubkey))) - pubblob = ssh_string(b"ssh-ed448") + ssh_string(pubkey) - privblob = ssh_string(privkey) - sigblob = ssh_string(b"ssh-ed448") + ssh_string(signature) - pubkey = ssh_key_new_pub('ed448', pubblob) - self.assertTrue(ssh_key_verify(pubkey, sigblob, message)) - privkey = ssh_key_new_priv('ed448', pubblob, privblob) - # Deterministic signature check as in Ed25519 - self.assertEqualBin(ssh_key_sign(privkey, message, 0), sigblob) - - # Source: RFC 8032 section 7.4 - - privkey = unhex('6c82a562cb808d10d632be89c8513ebf6c929f34ddfa8c9f63c9960ef6e348a3528c8a3fcc2f044e39a3fc5b94492f8f032e7549a20098f95b') - pubkey = unhex('5fd7449b59b461fd2ce787ec616ad46a1da1342485a70e1f8a0ea75d80e96778edf124769b46c7061bd6783df1e50f6cd1fa1abeafe8256180') - message = b'' - signature = unhex('533a37f6bbe457251f023c0d88f976ae2dfb504a843e34d2074fd823d41a591f2b233f034f628281f2fd7a22ddd47d7828c59bd0a21bfd3980ff0d2028d4b18a9df63e006c5d1c2d345b925d8dc00b4104852db99ac5c7cdda8530a113a0f4dbb61149f05a7363268c71d95808ff2e652600') - vector(privkey, pubkey, message, signature) - - privkey = unhex('c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e') - pubkey = unhex('43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a9480') - message = unhex('03') - signature = unhex('26b8f91727bd62897af15e41eb43c377efb9c610d48f2335cb0bd0087810f4352541b143c4b981b7e18f62de8ccdf633fc1bf037ab7cd779805e0dbcc0aae1cbcee1afb2e027df36bc04dcecbf154336c19f0af7e0a6472905e799f1953d2a0ff3348ab21aa4adafd1d234441cf807c03a00') - vector(privkey, pubkey, message, signature) - - privkey = unhex('cd23d24f714274e744343237b93290f511f6425f98e64459ff203e8985083ffdf60500553abc0e05cd02184bdb89c4ccd67e187951267eb328') - pubkey = unhex('dcea9e78f35a1bf3499a831b10b86c90aac01cd84b67a0109b55a36e9328b1e365fce161d71ce7131a543ea4cb5f7e9f1d8b00696447001400') - message = unhex('0c3e544074ec63b0265e0c') - signature = unhex('1f0a8888ce25e8d458a21130879b840a9089d999aaba039eaf3e3afa090a09d389dba82c4ff2ae8ac5cdfb7c55e94d5d961a29fe0109941e00b8dbdeea6d3b051068df7254c0cdc129cbe62db2dc957dbb47b51fd3f213fb8698f064774250a5028961c9bf8ffd973fe5d5c206492b140e00') - vector(privkey, pubkey, message, signature) - - privkey = unhex('258cdd4ada32ed9c9ff54e63756ae582fb8fab2ac721f2c8e676a72768513d939f63dddb55609133f29adf86ec9929dccb52c1c5fd2ff7e21b') - pubkey = unhex('3ba16da0c6f2cc1f30187740756f5e798d6bc5fc015d7c63cc9510ee3fd44adc24d8e968b6e46e6f94d19b945361726bd75e149ef09817f580') - message = unhex('64a65f3cdedcdd66811e2915') - signature = unhex('7eeeab7c4e50fb799b418ee5e3197ff6bf15d43a14c34389b59dd1a7b1b85b4ae90438aca634bea45e3a2695f1270f07fdcdf7c62b8efeaf00b45c2c96ba457eb1a8bf075a3db28e5c24f6b923ed4ad747c3c9e03c7079efb87cb110d3a99861e72003cbae6d6b8b827e4e6c143064ff3c00') - vector(privkey, pubkey, message, signature) - - privkey = unhex('d65df341ad13e008567688baedda8e9dcdc17dc024974ea5b4227b6530e339bff21f99e68ca6968f3cca6dfe0fb9f4fab4fa135d5542ea3f01') - pubkey = unhex('df9705f58edbab802c7f8363cfe5560ab1c6132c20a9f1dd163483a26f8ac53a39d6808bf4a1dfbd261b099bb03b3fb50906cb28bd8a081f00') - message = unhex('bd0f6a3747cd561bdddf4640a332461a4a30a12a434cd0bf40d766d9c6d458e5512204a30c17d1f50b5079631f64eb3112182da3005835461113718d1a5ef944') - signature = unhex('554bc2480860b49eab8532d2a533b7d578ef473eeb58c98bb2d0e1ce488a98b18dfde9b9b90775e67f47d4a1c3482058efc9f40d2ca033a0801b63d45b3b722ef552bad3b4ccb667da350192b61c508cf7b6b5adadc2c8d9a446ef003fb05cba5f30e88e36ec2703b349ca229c2670833900') - vector(privkey, pubkey, message, signature) - - privkey = unhex('2ec5fe3c17045abdb136a5e6a913e32ab75ae68b53d2fc149b77e504132d37569b7e766ba74a19bd6162343a21c8590aa9cebca9014c636df5') - pubkey = unhex('79756f014dcfe2079f5dd9e718be4171e2ef2486a08f25186f6bff43a9936b9bfe12402b08ae65798a3d81e22e9ec80e7690862ef3d4ed3a00') - message = unhex('15777532b0bdd0d1389f636c5f6b9ba734c90af572877e2d272dd078aa1e567cfa80e12928bb542330e8409f3174504107ecd5efac61ae7504dabe2a602ede89e5cca6257a7c77e27a702b3ae39fc769fc54f2395ae6a1178cab4738e543072fc1c177fe71e92e25bf03e4ecb72f47b64d0465aaea4c7fad372536c8ba516a6039c3c2a39f0e4d832be432dfa9a706a6e5c7e19f397964ca4258002f7c0541b590316dbc5622b6b2a6fe7a4abffd96105eca76ea7b98816af0748c10df048ce012d901015a51f189f3888145c03650aa23ce894c3bd889e030d565071c59f409a9981b51878fd6fc110624dcbcde0bf7a69ccce38fabdf86f3bef6044819de11') - signature = unhex('c650ddbb0601c19ca11439e1640dd931f43c518ea5bea70d3dcde5f4191fe53f00cf966546b72bcc7d58be2b9badef28743954e3a44a23f880e8d4f1cfce2d7a61452d26da05896f0a50da66a239a8a188b6d825b3305ad77b73fbac0836ecc60987fd08527c1a8e80d5823e65cafe2a3d00') - vector(privkey, pubkey, message, signature) - - privkey = unhex('872d093780f5d3730df7c212664b37b8a0f24f56810daa8382cd4fa3f77634ec44dc54f1c2ed9bea86fafb7632d8be199ea165f5ad55dd9ce8') - pubkey = unhex('a81b2e8a70a5ac94ffdbcc9badfc3feb0801f258578bb114ad44ece1ec0e799da08effb81c5d685c0c56f64eecaef8cdf11cc38737838cf400') - message = unhex('6ddf802e1aae4986935f7f981ba3f0351d6273c0a0c22c9c0e8339168e675412a3debfaf435ed651558007db4384b650fcc07e3b586a27a4f7a00ac8a6fec2cd86ae4bf1570c41e6a40c931db27b2faa15a8cedd52cff7362c4e6e23daec0fbc3a79b6806e316efcc7b68119bf46bc76a26067a53f296dafdbdc11c77f7777e972660cf4b6a9b369a6665f02e0cc9b6edfad136b4fabe723d2813db3136cfde9b6d044322fee2947952e031b73ab5c603349b307bdc27bc6cb8b8bbd7bd323219b8033a581b59eadebb09b3c4f3d2277d4f0343624acc817804728b25ab797172b4c5c21a22f9c7839d64300232eb66e53f31c723fa37fe387c7d3e50bdf9813a30e5bb12cf4cd930c40cfb4e1fc622592a49588794494d56d24ea4b40c89fc0596cc9ebb961c8cb10adde976a5d602b1c3f85b9b9a001ed3c6a4d3b1437f52096cd1956d042a597d561a596ecd3d1735a8d570ea0ec27225a2c4aaff26306d1526c1af3ca6d9cf5a2c98f47e1c46db9a33234cfd4d81f2c98538a09ebe76998d0d8fd25997c7d255c6d66ece6fa56f11144950f027795e653008f4bd7ca2dee85d8e90f3dc315130ce2a00375a318c7c3d97be2c8ce5b6db41a6254ff264fa6155baee3b0773c0f497c573f19bb4f4240281f0b1f4f7be857a4e59d416c06b4c50fa09e1810ddc6b1467baeac5a3668d11b6ecaa901440016f389f80acc4db977025e7f5924388c7e340a732e554440e76570f8dd71b7d640b3450d1fd5f0410a18f9a3494f707c717b79b4bf75c98400b096b21653b5d217cf3565c9597456f70703497a078763829bc01bb1cbc8fa04eadc9a6e3f6699587a9e75c94e5bab0036e0b2e711392cff0047d0d6b05bd2a588bc109718954259f1d86678a579a3120f19cfb2963f177aeb70f2d4844826262e51b80271272068ef5b3856fa8535aa2a88b2d41f2a0e2fda7624c2850272ac4a2f561f8f2f7a318bfd5caf9696149e4ac824ad3460538fdc25421beec2cc6818162d06bbed0c40a387192349db67a118bada6cd5ab0140ee273204f628aad1c135f770279a651e24d8c14d75a6059d76b96a6fd857def5e0b354b27ab937a5815d16b5fae407ff18222c6d1ed263be68c95f32d908bd895cd76207ae726487567f9a67dad79abec316f683b17f2d02bf07e0ac8b5bc6162cf94697b3c27cd1fea49b27f23ba2901871962506520c392da8b6ad0d99f7013fbc06c2c17a569500c8a7696481c1cd33e9b14e40b82e79a5f5db82571ba97bae3ad3e0479515bb0e2b0f3bfcd1fd33034efc6245eddd7ee2086ddae2600d8ca73e214e8c2b0bdb2b047c6a464a562ed77b73d2d841c4b34973551257713b753632efba348169abc90a68f42611a40126d7cb21b58695568186f7e569d2ff0f9e745d0487dd2eb997cafc5abf9dd102e62ff66cba87') - signature = unhex('e301345a41a39a4d72fff8df69c98075a0cc082b802fc9b2b6bc503f926b65bddf7f4c8f1cb49f6396afc8a70abe6d8aef0db478d4c6b2970076c6a0484fe76d76b3a97625d79f1ce240e7c576750d295528286f719b413de9ada3e8eb78ed573603ce30d8bb761785dc30dbc320869e1a00') - vector(privkey, pubkey, message, signature) - - def testMontgomeryKex(self): - # Unidirectional tests, consisting of an input random number - # string and peer public value, giving the expected output - # shared key. Source: RFC 7748 section 5.2. - rfc7748s5_2 = [ - ('curve25519', - 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4', - 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c', - 0xc3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552), - ('curve25519', - '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d', - 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493', - 0x95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957), - ('curve448', - '3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3', - '06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086', - 0xce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f), - ('curve448', - '203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f', - '0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db', - 0x884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d), - ] - - for method, priv, pub, expected in rfc7748s5_2: - with queued_specific_random_data(unhex(priv)): - ecdh = ecdh_key_new(method, False) - key = ecdh_key_getkey(ecdh, unhex(pub)) - self.assertEqual(key, ssh2_mpint(expected)) - - # Bidirectional tests, consisting of the input random number - # strings for both parties, and the expected public values and - # shared key. Source: RFC 7748 section 6. - rfc7748s6 = [ - ('curve25519', # section 6.1 - '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a', - '8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a', - '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb', - 'de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f', - 0x4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742), - ('curve448', # section 6.2 - '9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b', - '9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0', - '1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d', - '3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609', - 0x07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d), - ] - - for method, apriv, apub, bpriv, bpub, expected in rfc7748s6: - with queued_specific_random_data(unhex(apriv)): - alice = ecdh_key_new(method, False) - with queued_specific_random_data(unhex(bpriv)): - bob = ecdh_key_new(method, False) - self.assertEqualBin(ecdh_key_getpublic(alice), unhex(apub)) - self.assertEqualBin(ecdh_key_getpublic(bob), unhex(bpub)) - akey = ecdh_key_getkey(alice, unhex(bpub)) - bkey = ecdh_key_getkey(bob, unhex(apub)) - self.assertEqual(akey, ssh2_mpint(expected)) - self.assertEqual(bkey, ssh2_mpint(expected)) - - def testCRC32(self): - self.assertEqual(crc32_rfc1662("123456789"), 0xCBF43926) - self.assertEqual(crc32_ssh1("123456789"), 0x2DFD2D88) - - # Source: - # http://reveng.sourceforge.net/crc-catalogue/17plus.htm#crc.cat.crc-32-iso-hdlc - # which collected these from various sources. - reveng_tests = [ - '000000001CDF4421', - 'F20183779DAB24', - '0FAA005587B2C9B6', - '00FF55111262A032', - '332255AABBCCDDEEFF3D86AEB0', - '926B559BA2DE9C', - 'FFFFFFFFFFFFFFFF', - 'C008300028CFE9521D3B08EA449900E808EA449900E8300102007E649416', - '6173640ACEDE2D15', - ] - for vec in map(unhex, reveng_tests): - # Each of these test vectors can be read two ways. One - # interpretation is that the last four bytes are the - # little-endian encoding of the CRC of the rest. (Because - # that's how the CRC is attached to a string at the - # sending end.) - # - # The other interpretation is that if you CRC the whole - # string, _including_ the final four bytes, you expect to - # get the same value for any correct string (because the - # little-endian encoding matches the way the rest of the - # string was interpreted as a polynomial in the first - # place). That's how a receiver is intended to check - # things. - # - # The expected output value is listed in RFC 1662, and in - # the reveng.sourceforge.net catalogue, as 0xDEBB20E3. But - # that's because their checking procedure omits the final - # complement step that the construction procedure - # includes. Our crc32_rfc1662 function does do the final - # complement, so we expect the bitwise NOT of that value, - # namely 0x2144DF1C. - expected = struct.unpack("= (3,0), "This is Python 3 code" - -def bitor(x, y): - return x | y -def split_words(val, width=32): - mask = ((1<> width), mask & val -def combine_words(hi, lo, width=32): - mask = ((1<> (shift % width)) | (val << (-shift % width))) -def rol(val, shift, width=32): - return ror(val, -shift, width) -def bitselect(bits, val): - # bits[i] gives the input bit index of the output bit at index i - return functools.reduce( - bitor, ((1 & (val >> inbit)) << outbit - for outbit, inbit in enumerate(bits))) -def SB(hexstring): - return [int(c,16) for c in hexstring] - -def debug(string): - sys.stdout.write(string + "\n") - -class DESBase(object): - def __init__(self): - # Automatically construct FP by inverting IP - self.FP = [None] * 64 - for i, j in enumerate(self.IP): - self.FP[j] = i - - def f(self, word, key_material): - debug("computing f({:08x}, {}):".format( - word, " ".join(map("{:02x}".format,key_material)))) - sbox_inputs = [0x3F & (ror(word, offset) ^ key_element) - for offset, key_element in - zip(self.sbox_index_offsets, key_material)] - sbox_outputs = [sbox[sbox_input] for sbox, sbox_input - in zip(self.sboxes, sbox_inputs)] - debug(" S-boxes: {} -> {}".format( - " ".join(map("{:02x}".format,sbox_inputs)), - " ".join(map("{:x}".format,sbox_outputs)))) - word = functools.reduce( - bitor, (v << (4*i) for i,v in enumerate(sbox_outputs))) - debug(" S output = {:08x}".format(word)) - word = bitselect(self.P, word) - debug(" P output = {:08x}".format(word)) - return word - - def cipher(self, integer, key_schedule): - L, R = split_words(bitselect(self.IP, integer)) - debug("cipher start {:016x} -> {:08x} {:08x}".format(integer, L, R)) - for roundIndex, key_material in enumerate(key_schedule): - L, R = R, L ^ self.f(R, key_material) - debug("after round {:2d}: {:08x} {:08x}".format(roundIndex, L, R)) - output = bitselect(self.FP, combine_words(R, L)) - debug("cipher end {:08x} {:08x} -> {:016x}".format(R, L, output)) - return output - - def encipher(self, integer): - return self.cipher(integer, self.key_schedule) - def decipher(self, integer): - return self.cipher(integer, list(reversed(self.key_schedule))) - - def setkey(self, key): - self.key_schedule = [] - - CD = bitselect(self.PC1, key) - debug("initial CD = {:014x}".format(CD)) - for roundIndex, shift in enumerate(self.key_setup_shifts): - C, D = split_words(CD, 28) - C = rol(C, shift, 28) - D = rol(D, shift, 28) - CD = combine_words(C, D, 28) - self.key_schedule.append( - [bitselect(bits, CD) for bits in self.PC2]) - debug("CD[{:d}] = {:014x} -> {}):".format( - roundIndex, CD, " ".join( - map("{:02x}".format,self.key_schedule[-1])))) - - # The PC1 permutation is fixed and arbitrary - PC1 = [ - 0x3c, 0x34, 0x2c, 0x24, 0x3b, 0x33, 0x2b, - 0x23, 0x1b, 0x13, 0x0b, 0x03, 0x3a, 0x32, - 0x2a, 0x22, 0x1a, 0x12, 0x0a, 0x02, 0x39, - 0x31, 0x29, 0x21, 0x19, 0x11, 0x09, 0x01, - 0x1c, 0x14, 0x0c, 0x04, 0x3d, 0x35, 0x2d, - 0x25, 0x1d, 0x15, 0x0d, 0x05, 0x3e, 0x36, - 0x2e, 0x26, 0x1e, 0x16, 0x0e, 0x06, 0x3f, - 0x37, 0x2f, 0x27, 0x1f, 0x17, 0x0f, 0x07, - ] - - PC2 = [ - [0x18, 0x1b, 0x14, 0x06, 0x0e, 0x0a], - [0x03, 0x16, 0x00, 0x11, 0x07, 0x0c], - [0x08, 0x17, 0x0b, 0x05, 0x10, 0x1a], - [0x01, 0x09, 0x13, 0x19, 0x04, 0x0f], - [0x36, 0x2b, 0x24, 0x1d, 0x31, 0x28], - [0x30, 0x1e, 0x34, 0x2c, 0x25, 0x21], - [0x2e, 0x23, 0x32, 0x29, 0x1c, 0x35], - [0x33, 0x37, 0x20, 0x2d, 0x27, 0x2a], - ] - - key_setup_shifts = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1] - - # IP is better understood as a permutation and flipping of the - # bits _in the index of each actual bit_ than as a long list of - # individual indices - IP = [bitselect([5,3,4,0,1,2], index ^ 0x27) for index in range(64)] - -class DES(DESBase): - sboxes = [ - SB('d12f8d486af3b714ac9536eb500ec97272b14e1794cae82d0f6ca9d0f335568b'), - SB('4db02be7f40981da3ec3957c52af6816164bbdd8c1347ae7a9f5608f0e52932c'), - SB('ca1fa4f2972c698506d13d4ee07b53b894e3f25c2985cf3a7b0e41a716d0b86d'), - SB('2ecb421c74a7bd6185503ffad309e8964b281cb7a1de728df69fc0596a3405e3'), - SB('7dd8eb35066f90a31427825cb1ca4ef9a36f9006cab17dd8f91435eb5c27824e'), - SB('ad0790e96334f65a12d8c57ebc4b2f81d16a4d9086f93807b41f2ec35ba5e27c'), - SB('f31d84e76fb2384e9c7021dac6095ba50de87ab1a34fd4125b86c76c90352ef9'), - SB('e04fd7142ef2bd813aa66ccb599503784f1ce882d46921b7f5cb937e3aa0560d'), - ] - P = [ - 0x07, 0x1c, 0x15, 0x0a, 0x1a, 0x02, 0x13, 0x0d, - 0x17, 0x1d, 0x05, 0x00, 0x12, 0x08, 0x18, 0x1e, - 0x16, 0x01, 0x0e, 0x1b, 0x06, 0x09, 0x11, 0x1f, - 0x0f, 0x04, 0x14, 0x03, 0x0b, 0x0c, 0x19, 0x10, - ] - sbox_index_offsets = [4*i-1 for i in range(8)] - -class SGTDES(DESBase): - sboxes = [ - SB('e41f8e2839f5d7429ac653bd600bac7171d42b47c2a9b81e0f3a9ce0f556638d'), - SB('4db02be7f40981da3ec3957c52af6816164bbdd8c1347ae7a9f5608f0e52932c'), - SB('c52f58f16b1c964a09e23e8dd0b7a37468d3f1ac164acf35b70d825b29e0749e'), - SB('4ead241a72c7db6183305ffcb509e8962d481ad7c1be748bf69fa0396c5203e5'), - SB('edd1b76c0aaf5036482e12c974938bf536af500a9374edd1f5486cb7c92e128b'), - SB('9e07a0da5334f56921e8c67dbc4b1f82e2594ea085fa3807b42f1dc36b96d17c'), - SB('f31d84e76fb2384e9c7021dac6095ba50de87ab1a34fd4125b86c76c90352ef9'), - SB('d08feb281df17e4235599cc7a66a03b48f2cd441e896127bfac763bd3550a90e'), - ] - P = [ - 0x1d, 0x14, 0x0b, 0x1a, 0x01, 0x10, 0x0e, 0x17, - 0x1c, 0x05, 0x02, 0x13, 0x09, 0x18, 0x1f, 0x16, - 0x00, 0x0d, 0x1b, 0x06, 0x08, 0x11, 0x1e, 0x0f, - 0x04, 0x15, 0x03, 0x0a, 0x0c, 0x19, 0x12, 0x07 - ] - sbox_index_offsets = [4*i-2 for i in range(8)] - IP = [DES.IP[i ^ ((i^(i+1)) & 0x1F)] for i in range(64)] - -def main(): - hexstr = lambda s: int(s, 16) - - parser = argparse.ArgumentParser(description='') - group = parser.add_mutually_exclusive_group() - group.add_argument("--des", action="store_const", dest="cipher", const=DES, - help="Use the official DES definition.") - group.add_argument("--sgtdes", action="store_const", dest="cipher", - const=SGTDES, help="Use the equivalent SGT-DES.") - group = parser.add_mutually_exclusive_group(required=True) - group.add_argument("--encipher", "-e", action="store_const", dest="method", - const="encipher", help="Encipher.") - group.add_argument("--decipher", "-d", action="store_const", dest="method", - const="decipher", help="Decipher.") - parser.add_argument("key", type=hexstr, help="Cipher key (hex, 8 bytes, " - "low bit of each byte unused).") - parser.add_argument("input", type=hexstr, - help="Cipher input (hex, 8 bytes).") - parser.set_defaults(const=SGTDES) # main purpose is to debug sshdes.c - args = parser.parse_args() - - des = args.cipher() - des.setkey(args.key) - method = getattr(des, args.method) - output = method(args.input) - - sys.stdout.write("{} with key {:016x}: {:016x} -> {:016x}\n".format( - args.method, args.key, args.input, output)) - -if __name__ == '__main__': - main() diff --git a/test/display.txt b/test/display.txt deleted file mode 100644 index a61f618da..000000000 --- a/test/display.txt +++ /dev/null @@ -1,14 +0,0 @@ -Test of all features involved in do_text() -========================================== - -Reverse video + red on yellow:  bing!  -Yellow on red should look the same:  bong!  - -Basic attrs, combining chars, both widths: Bold blink under strike [Λ̊][チ][text] -Wide char should be off by 1 narrow char: Bold blink under strike [Λ̊][チ][text] - -Double width, double height. Should be red top, magenta bottom, blue DW only: -#3Bold blink under strike [Λ̊][チ][text] -#4Bold blink under strike [Λ̊][チ][text] -#6Bold blink under strike [Λ̊][チ][text] - diff --git a/test/eccref.py b/test/eccref.py deleted file mode 100644 index f39fa5e44..000000000 --- a/test/eccref.py +++ /dev/null @@ -1,331 +0,0 @@ -import sys -import numbers -import itertools - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -from numbertheory import * - -class AffinePoint(object): - """Base class for points on an elliptic curve.""" - - def __init__(self, curve, *args): - self.curve = curve - if len(args) == 0: - self.infinite = True - self.x = self.y = None - else: - assert len(args) == 2 - self.infinite = False - self.x = ModP(self.curve.p, args[0]) - self.y = ModP(self.curve.p, args[1]) - self.check_equation() - def __neg__(self): - if self.infinite: - return self - return type(self)(self.curve, self.x, -self.y) - def __mul__(self, rhs): - if not isinstance(rhs, numbers.Integral): - raise ValueError("Elliptic curve points can only be multiplied by integers") - P = self - if rhs < 0: - rhs = -rhs - P = -P - toret = self.curve.point() - n = 1 - nP = P - while rhs != 0: - if rhs & n: - rhs -= n - toret += nP - n += n - nP += nP - return toret - def __rmul__(self, rhs): - return self * rhs - def __sub__(self, rhs): - return self + (-rhs) - def __rsub__(self, rhs): - return (-self) + rhs - def __str__(self): - if self.infinite: - return "inf" - else: - return "({},{})".format(self.x, self.y) - def __repr__(self): - if self.infinite: - args = "" - else: - args = ", {}, {}".format(self.x, self.y) - return "{}.Point({}{})".format(type(self.curve).__name__, - self.curve, args) - def __eq__(self, rhs): - if self.infinite or rhs.infinite: - return self.infinite and rhs.infinite - return (self.x, self.y) == (rhs.x, rhs.y) - def __ne__(self, rhs): - return not (self == rhs) - def __lt__(self, rhs): - raise ValueError("Elliptic curve points have no ordering") - def __le__(self, rhs): - raise ValueError("Elliptic curve points have no ordering") - def __gt__(self, rhs): - raise ValueError("Elliptic curve points have no ordering") - def __ge__(self, rhs): - raise ValueError("Elliptic curve points have no ordering") - def __hash__(self): - if self.infinite: - return hash((True,)) - else: - return hash((False, self.x, self.y)) - -class CurveBase(object): - def point(self, *args): - return self.Point(self, *args) - -class WeierstrassCurve(CurveBase): - class Point(AffinePoint): - def check_equation(self): - assert (self.y*self.y == - self.x*self.x*self.x + - self.curve.a*self.x + self.curve.b) - def __add__(self, rhs): - if self.infinite: - return rhs - if rhs.infinite: - return self - if self.x == rhs.x and self.y != rhs.y: - return self.curve.point() - x1, x2, y1, y2 = self.x, rhs.x, self.y, rhs.y - xdiff = x2-x1 - if xdiff != 0: - slope = (y2-y1) / xdiff - else: - assert y1 == y2 - slope = (3*x1*x1 + self.curve.a) / (2*y1) - xp = slope*slope - x1 - x2 - yp = -(y1 + slope * (xp-x1)) - return self.curve.point(xp, yp) - - def __init__(self, p, a, b): - self.p = p - self.a = ModP(p, a) - self.b = ModP(p, b) - - def cpoint(self, x, yparity=0): - if not hasattr(self, 'sqrtmodp'): - self.sqrtmodp = RootModP(2, self.p) - rhs = x**3 + self.a.n * x + self.b.n - y = self.sqrtmodp.root(rhs) - if (y - yparity) % 2: - y = -y - return self.point(x, y) - - def __repr__(self): - return "{}(0x{:x}, {}, {})".format( - type(self).__name__, self.p, self.a, self.b) - -class MontgomeryCurve(CurveBase): - class Point(AffinePoint): - def check_equation(self): - assert (self.curve.b*self.y*self.y == - self.x*self.x*self.x + - self.curve.a*self.x*self.x + self.x) - def __add__(self, rhs): - if self.infinite: - return rhs - if rhs.infinite: - return self - if self.x == rhs.x and self.y != rhs.y: - return self.curve.point() - x1, x2, y1, y2 = self.x, rhs.x, self.y, rhs.y - xdiff = x2-x1 - if xdiff != 0: - slope = (y2-y1) / xdiff - elif y1 != 0: - assert y1 == y2 - slope = (3*x1*x1 + 2*self.curve.a*x1 + 1) / (2*self.curve.b*y1) - else: - # If y1 was 0 as well, then we must have found an - # order-2 point that doubles to the identity. - return self.curve.point() - xp = self.curve.b*slope*slope - self.curve.a - x1 - x2 - yp = -(y1 + slope * (xp-x1)) - return self.curve.point(xp, yp) - - def __init__(self, p, a, b): - self.p = p - self.a = ModP(p, a) - self.b = ModP(p, b) - - def cpoint(self, x, yparity=0): - if not hasattr(self, 'sqrtmodp'): - self.sqrtmodp = RootModP(2, self.p) - rhs = (x**3 + self.a.n * x**2 + x) / self.b - y = self.sqrtmodp.root(int(rhs)) - if (y - yparity) % 2: - y = -y - return self.point(x, y) - - def __repr__(self): - return "{}(0x{:x}, {}, {})".format( - type(self).__name__, self.p, self.a, self.b) - -class TwistedEdwardsCurve(CurveBase): - class Point(AffinePoint): - def check_equation(self): - x2, y2 = self.x*self.x, self.y*self.y - assert (self.curve.a*x2 + y2 == 1 + self.curve.d*x2*y2) - def __neg__(self): - return type(self)(self.curve, -self.x, self.y) - def __add__(self, rhs): - x1, x2, y1, y2 = self.x, rhs.x, self.y, rhs.y - x1y2, y1x2, y1y2, x1x2 = x1*y2, y1*x2, y1*y2, x1*x2 - dxxyy = self.curve.d*x1x2*y1y2 - return self.curve.point((x1y2+y1x2)/(1+dxxyy), - (y1y2-self.curve.a*x1x2)/(1-dxxyy)) - - def __init__(self, p, d, a): - self.p = p - self.d = ModP(p, d) - self.a = ModP(p, a) - - def point(self, *args): - # This curve form represents the identity using finite - # numbers, so it doesn't need the special infinity flag. - # Detect a no-argument call to point() and substitute the pair - # of integers that gives the identity. - if len(args) == 0: - args = [0, 1] - return super(TwistedEdwardsCurve, self).point(*args) - - def cpoint(self, y, xparity=0): - if not hasattr(self, 'sqrtmodp'): - self.sqrtmodp = RootModP(self.p) - y = ModP(self.p, y) - y2 = y**2 - radicand = (y2 - 1) / (self.d * y2 - self.a) - x = self.sqrtmodp.root(radicand.n) - if (x - xparity) % 2: - x = -x - return self.point(x, y) - - def __repr__(self): - return "{}(0x{:x}, {}, {})".format( - type(self).__name__, self.p, self.d, self.a) - -def find_montgomery_power2_order_x_values(p, a): - # Find points on a Montgomery elliptic curve that have order a - # power of 2. - # - # Motivation: both Curve25519 and Curve448 are abelian groups - # whose overall order is a large prime times a small factor of 2. - # The approved base point of each curve generates a cyclic - # subgroup whose order is the large prime. Outside that cyclic - # subgroup there are many other points that have large prime - # order, plus just a handful that have tiny order. If one of the - # latter is presented to you as a Diffie-Hellman public value, - # nothing useful is going to happen, and RFC 7748 says we should - # outlaw those values. And any actual attempt to outlaw them is - # going to need to know what they are, either to check for each - # one directly, or to use them as test cases for some other - # approach. - # - # In a group of order p 2^k, an obvious way to search for points - # with order dividing 2^k is to generate random group elements and - # raise them to the power p. That guarantees that you end up with - # _something_ with order dividing 2^k (even if it's boringly the - # identity). And you also know from theory how many such points - # you expect to exist, so you can count the distinct ones you've - # found, and stop once you've got the right number. - # - # But that isn't actually good enough to find all the public - # values that are problematic! The reason why not is that in - # Montgomery key exchange we don't actually use a full elliptic - # curve point: we only use its x-coordinate. And the formulae for - # doubling and differential addition on x-coordinates can accept - # some values that don't correspond to group elements _at all_ - # without detecting any error - and some of those nonsense x - # coordinates can also behave like low-order points. - # - # (For example, the x-coordinate -1 in Curve25519 is such a value. - # The reference ECC code in this module will raise an exception if - # you call curve25519.cpoint(-1): it corresponds to no valid point - # at all. But if you feed it into the doubling formula _anyway_, - # it doubles to the valid curve point with x-coord 0, which in - # turn doubles to the curve identity. Bang.) - # - # So we use an alternative approach which discards the group - # theory of the actual elliptic curve, and focuses purely on the - # doubling formula as an algebraic transformation on Z_p. Our - # question is: what values of x have the property that if you - # iterate the doubling map you eventually end up dividing by zero? - # To answer that, we must solve cubics and quartics mod p, via the - # code in numbertheory.py for doing so. - - E = EquationSolverModP(p) - - def viableSolutions(it): - for x in it: - try: - yield int(x) - except ValueError: - pass # some field-extension element that isn't a real value - - def valuesDoublingTo(y): - # The doubling formula for a Montgomery curve point given only - # by x coordinate is (x+1)^2(x-1)^2 / (4(x^3+ax^2+x)). - # - # If we want to find a point that doubles to some particular - # value, we can set that formula equal to y and expand to get the - # quartic equation x^4 + (-4y)x^3 + (-4ay-2)x^2 + (-4y)x + 1 = 0. - return viableSolutions(E.solve_monic_quartic(-4*y, -4*a*y-2, -4*y, 1)) - - queue = [] - qset = set() - pos = 0 - def insert(x): - if x not in qset: - queue.append(x) - qset.add(x) - - # Our ultimate aim is to find points that end up going to the - # curve identity / point at infinity after some number of - # doublings. So our starting point is: what values of x make the - # denominator of the doubling formula zero? - for x in viableSolutions(E.solve_monic_cubic(a, 1, 0)): - insert(x) - - while pos < len(queue): - y = queue[pos] - pos += 1 - for x in valuesDoublingTo(y): - insert(x) - - return queue - -p256 = WeierstrassCurve(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff, -3, 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b) -p256.G = p256.point(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5) -p256.G_order = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 - -p384 = WeierstrassCurve(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff, -3, 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef) -p384.G = p384.point(0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7, 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f) -p384.G_order = 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973 - -p521 = WeierstrassCurve(0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, -3, 0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00) -p521.G = p521.point(0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66,0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650) -p521.G_order = 0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409 - -curve25519 = MontgomeryCurve(2**255-19, 0x76d06, 1) -curve25519.G = curve25519.cpoint(9) - -curve448 = MontgomeryCurve(2**448-2**224-1, 0x262a6, 1) -curve448.G = curve448.cpoint(5) - -ed25519 = TwistedEdwardsCurve(2**255-19, 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3, -1) -ed25519.G = ed25519.point(0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a,0x6666666666666666666666666666666666666666666666666666666666666658) -ed25519.G_order = 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed - -ed448 = TwistedEdwardsCurve(2**448-2**224-1, -39081, +1) -ed448.G = ed448.point(0x4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e,0x693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14) -ed448.G_order = 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3 diff --git a/test/fuzzterm.c b/test/fuzzterm.c deleted file mode 100644 index 45ba91ddc..000000000 --- a/test/fuzzterm.c +++ /dev/null @@ -1,213 +0,0 @@ -#include -#include -#include - -#include "putty.h" -#include "dialog.h" -#include "terminal.h" - -static const TermWinVtable fuzz_termwin_vt; - -int main(int argc, char **argv) -{ - char blk[512]; - size_t len; - Terminal *term; - Conf *conf; - struct unicode_data ucsdata; - TermWin termwin; - - termwin.vt = &fuzz_termwin_vt; - - conf = conf_new(); - do_defaults(NULL, conf); - init_ucs_generic(conf, &ucsdata); - - term = term_init(conf, &ucsdata, &termwin); - term_size(term, 24, 80, 10000); - term->ldisc = NULL; - /* Tell american fuzzy lop that this is a good place to fork. */ -#ifdef __AFL_HAVE_MANUAL_CONTROL - __AFL_INIT(); -#endif - while (!feof(stdin)) { - len = fread(blk, 1, sizeof(blk), stdin); - term_data(term, blk, len); - } - term_update(term); - return 0; -} - -/* functions required by terminal.c */ -static bool fuzz_setup_draw_ctx(TermWin *tw) { return true; } -static void fuzz_draw_text( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour tc) -{ - int i; - - printf("TEXT[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y); - for (i = 0; i < len; i++) { - printf(" %x", (unsigned)text[i]); - } - printf("\n"); -} -static void fuzz_draw_cursor( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour tc) -{ - int i; - - printf("CURS[attr=%08lx,lattr=%02x]@(%d,%d):", attr, lattr, x, y); - for (i = 0; i < len; i++) { - printf(" %x", (unsigned)text[i]); - } - printf("\n"); -} -static void fuzz_draw_trust_sigil(TermWin *tw, int x, int y) -{ - printf("TRUST@(%d,%d)\n", x, y); -} -static int fuzz_char_width(TermWin *tw, int uc) { return 1; } -static void fuzz_free_draw_ctx(TermWin *tw) {} -static void fuzz_set_cursor_pos(TermWin *tw, int x, int y) {} -static void fuzz_set_raw_mouse_mode(TermWin *tw, bool enable) {} -static void fuzz_set_scrollbar(TermWin *tw, int total, int start, int page) {} -static void fuzz_bell(TermWin *tw, int mode) {} -static void fuzz_clip_write( - TermWin *tw, int clipboard, wchar_t *text, int *attrs, - truecolour *colours, int len, bool must_deselect) {} -static void fuzz_clip_request_paste(TermWin *tw, int clipboard) {} -static void fuzz_refresh(TermWin *tw) {} -static void fuzz_request_resize(TermWin *tw, int w, int h) {} -static void fuzz_set_title(TermWin *tw, const char *title, int codepage) {} -static void fuzz_set_icon_title(TermWin *tw, const char *icontitle, int cp) {} -static void fuzz_set_minimised(TermWin *tw, bool minimised) {} -static void fuzz_set_maximised(TermWin *tw, bool maximised) {} -static void fuzz_move(TermWin *tw, int x, int y) {} -static void fuzz_set_zorder(TermWin *tw, bool top) {} -static void fuzz_palette_set(TermWin *tw, unsigned start, unsigned ncolours, - const rgb *colours) {} -static void fuzz_palette_get_overrides(TermWin *tw, Terminal *term) {} -static void fuzz_unthrottle(TermWin *tw, size_t size) {} - -static const TermWinVtable fuzz_termwin_vt = { - .setup_draw_ctx = fuzz_setup_draw_ctx, - .draw_text = fuzz_draw_text, - .draw_cursor = fuzz_draw_cursor, - .draw_trust_sigil = fuzz_draw_trust_sigil, - .char_width = fuzz_char_width, - .free_draw_ctx = fuzz_free_draw_ctx, - .set_cursor_pos = fuzz_set_cursor_pos, - .set_raw_mouse_mode = fuzz_set_raw_mouse_mode, - .set_scrollbar = fuzz_set_scrollbar, - .bell = fuzz_bell, - .clip_write = fuzz_clip_write, - .clip_request_paste = fuzz_clip_request_paste, - .refresh = fuzz_refresh, - .request_resize = fuzz_request_resize, - .set_title = fuzz_set_title, - .set_icon_title = fuzz_set_icon_title, - .set_minimised = fuzz_set_minimised, - .set_maximised = fuzz_set_maximised, - .move = fuzz_move, - .set_zorder = fuzz_set_zorder, - .palette_set = fuzz_palette_set, - .palette_get_overrides = fuzz_palette_get_overrides, - .unthrottle = fuzz_unthrottle, -}; - -void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {} -void ldisc_echoedit_update(Ldisc *ldisc) {} -void ldisc_provide_userpass_le(Ldisc *ldisc, TermLineEditor *le) -{ unreachable("This fake ldisc should never be used for user/pass prompts"); } -void modalfatalbox(const char *fmt, ...) { exit(0); } -void nonfatal(const char *fmt, ...) { } - -/* needed by timing.c */ -void timer_change_notify(unsigned long next) { } - -/* needed by config.c */ - -void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int whichbutton) { } -int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) { return 0; } -void dlg_checkbox_set(dlgcontrol *ctrl, dlgparam *dp, bool checked) { } -bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) { return false; } -void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { } -char *dlg_editbox_get(dlgcontrol *ctrl, dlgparam *dp) -{ return dupstr("moo"); } -void dlg_listbox_clear(dlgcontrol *ctrl, dlgparam *dp) { } -void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) { } -void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text) { } -void dlg_listbox_addwithid(dlgcontrol *ctrl, dlgparam *dp, - char const *text, int id) { } -int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index) -{ return 0; } -int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) { return -1; } -bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index) -{ return false; } -void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) { } -void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) { } -void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) { } -Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) { return NULL; } -void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fn) { } -FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) { return NULL; } -void dlg_update_start(dlgcontrol *ctrl, dlgparam *dp) { } -void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp) { } -void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) { } -void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) { } -dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp) -{ return NULL; } -void dlg_beep(dlgparam *dp) { } -void dlg_error_msg(dlgparam *dp, const char *msg) { } -void dlg_end(dlgparam *dp, int value) { } -void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, - int r, int g, int b) { } -bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, - int *r, int *g, int *b) { return false; } -void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) { } -bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp) { return false; } - -const int ngsslibs = 0; -const char *const gsslibnames[0] = { }; -const struct keyvalwhere gsslibkeywords[0] = { }; - -/* - * 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; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} - -FontSpec *platform_default_fontspec(const char *name) -{ - return fontspec_new_default(); -} - -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 */ -} diff --git a/test/lattrs.txt b/test/lattrs.txt deleted file mode 100644 index 5cafc9533..000000000 --- a/test/lattrs.txt +++ /dev/null @@ -1,6 +0,0 @@ -Test of line attributes: - -#3Double-height top -#4Double-height bottom -#5Normal text (#5) -#6Double-width only diff --git a/test/list-accel.py b/test/list-accel.py deleted file mode 100644 index ac92d376e..000000000 --- a/test/list-accel.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 - -# Simple client of the testcrypt system that reports the available -# variants of each of the crypto primitives that have hardware- -# accelerated implementations. -# -# It will report the set of primitives compiled in to testcrypt, and -# also report whether each one can be instantiated at run time. - -from testcrypt import * - -def get_implementations(alg): - return get_implementations_commasep(alg).decode("ASCII").split(",") - -def list_implementations(alg, checkfn): - print(f"Implementations of {alg}:") - for impl in get_implementations(alg): - if impl == alg: - continue - if checkfn(impl): - print(f" {impl:<32s} available") - else: - print(f" {impl:<32s} compiled in, but unavailable at run time") - -def list_cipher_implementations(alg): - list_implementations(alg, lambda impl: ssh_cipher_new(impl) is not None) - -def list_mac_implementations(alg): - list_implementations(alg, lambda impl: ssh2_mac_new(impl, None) is not None) - -def list_hash_implementations(alg): - list_implementations(alg, lambda impl: ssh_hash_new(impl) is not None) - -list_cipher_implementations("aes256_cbc") -list_mac_implementations("aesgcm") -list_hash_implementations("sha1") -list_hash_implementations("sha256") -list_hash_implementations("sha512") diff --git a/test/mpu-check.pl b/test/mpu-check.pl deleted file mode 100644 index f5f0e815d..000000000 --- a/test/mpu-check.pl +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/perl - -# Trivial command-line client for the function -# Math::Prime::Util::verify_prime, which checks a certificate of -# primality in MPU format. - -use strict; -use warnings; -use Math::Prime::Util; - -Math::Prime::Util::prime_set_config(verbose => 1); - -my $cert = ""; -$cert .= $_ while <<>>; - -my $success = Math::Prime::Util::verify_prime($cert); - -die "verification failed\n" unless $success; -warn "verification succeeded\n"; diff --git a/test/numbertheory.py b/test/numbertheory.py deleted file mode 100644 index 58f601eb1..000000000 --- a/test/numbertheory.py +++ /dev/null @@ -1,638 +0,0 @@ -import sys -import numbers -import itertools -import unittest - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -def invert(a, b): - "Multiplicative inverse of a mod b. a,b must be coprime." - A = (a, 1, 0) - B = (b, 0, 1) - while B[0]: - q = A[0] // B[0] - A, B = B, tuple(Ai - q*Bi for Ai, Bi in zip(A, B)) - assert abs(A[0]) == 1 - return A[1]*A[0] % b - -def jacobi(n,m): - """Compute the Jacobi symbol. - - The special case of this when m is prime is the Legendre symbol, - which is 0 if n is congruent to 0 mod m; 1 if n is congruent to a - non-zero square number mod m; -1 if n is not congruent to any - square mod m. - - """ - assert m & 1 - acc = 1 - while True: - n %= m - if n == 0: - return 0 - while not (n & 1): - n >>= 1 - if (m & 7) not in {1,7}: - acc *= -1 - if n == 1: - return acc - if (n & 3) == 3 and (m & 3) == 3: - acc *= -1 - n, m = m, n - -class CyclicGroupRootFinder(object): - """Class for finding rth roots in a cyclic group. r must be prime.""" - - # Basic strategy: - # - # We write |G| = r^k u, with u coprime to r. This gives us a - # nested sequence of subgroups G = G_0 > G_1 > ... > G_k, each - # with index r in its predecessor. G_0 is the whole group, and the - # innermost G_k has order u. - # - # Within G_k, you can take an rth root by raising an element to - # the power of (r^{-1} mod u). If k=0 (so G = G_0 = G_k) then - # that's all that's needed: every element has a unique rth root. - # But if k>0, then things go differently. - # - # Define the 'rank' of an element g as the highest i such that - # g \in G_i. Elements of rank 0 are the non-rth-powers: they don't - # even _have_ an rth root. Elements of rank k are the easy ones to - # take rth roots of, as above. - # - # In between, you can follow an inductive process, as long as you - # know one element z of rank 0. Suppose we're trying to take the - # rth root of some g with rank i. Repeatedly multiply g by z^{r^i} - # until its rank increases; then take the root of that - # (recursively), and divide off z^{r^{i-1}} once you're done. - - def __init__(self, r, order): - self.order = order # order of G - self.r = r - self.k = next(k for k in itertools.count() - if self.order % (r**(k+1)) != 0) - self.u = self.order // (r**self.k) - self.z = next(z for z in self.iter_elements() - if self.index(z) == 0) - self.zinv = self.inverse(self.z) - self.root_power = invert(self.r, self.u) if self.u > 1 else 0 - - self.roots_of_unity = {self.identity()} - if self.k > 0: - exponent = self.order // self.r - for z in self.iter_elements(): - root_of_unity = self.pow(z, exponent) - if root_of_unity not in self.roots_of_unity: - self.roots_of_unity.add(root_of_unity) - if len(self.roots_of_unity) == r: - break - - def index(self, g): - h = self.pow(g, self.u) - for i in range(self.k+1): - if h == self.identity(): - return self.k - i - h = self.pow(h, self.r) - assert False, ("Not a cyclic group! Raising {} to u r^k should give e." - .format(g)) - - def all_roots(self, g): - try: - r = self.root(g) - except ValueError: - return [] - return {r * rou for rou in self.roots_of_unity} - - def root(self, g): - i = self.index(g) - if i == 0 and self.k > 0: - raise ValueError("{} has no {}th root".format(g, self.r)) - out = self.root_recurse(g, i) - assert self.pow(out, self.r) == g - return out - - def root_recurse(self, g, i): - if i == self.k: - return self.pow(g, self.root_power) - z_in = self.pow(self.z, self.r**i) - z_out = self.pow(self.zinv, self.r**(i-1)) - adjust = self.identity() - while True: - g = self.mul(g, z_in) - adjust = self.mul(adjust, z_out) - i2 = self.index(g) - if i2 > i: - return self.mul(self.root_recurse(g, i2), adjust) - -class AdditiveGroupRootFinder(CyclicGroupRootFinder): - """Trivial test subclass for CyclicGroupRootFinder. - - Represents a cyclic group of any order additively, as the integers - mod n under addition. This makes root-finding trivial without - having to use the complicated algorithm above, and therefore it's - a good way to test the complicated algorithm under conditions - where the right answers are obvious.""" - - def __init__(self, r, order): - super().__init__(r, order) - - def mul(self, x, y): - return (x + y) % self.order - def pow(self, x, n): - return (x * n) % self.order - def inverse(self, x): - return (-x) % self.order - def identity(self): - return 0 - def iter_elements(self): - return range(self.order) - -class TestCyclicGroupRootFinder(unittest.TestCase): - def testRootFinding(self): - for order in 10, 11, 12, 18: - grf = AdditiveGroupRootFinder(3, order) - for i in range(order): - try: - r = grf.root(i) - except ValueError: - r = None - - if order % 3 == 0 and i % 3 != 0: - self.assertEqual(r, None) - else: - self.assertEqual(r*3 % order, i) - -class RootModP(CyclicGroupRootFinder): - """The live class that can take rth roots mod a prime.""" - - def __init__(self, r, p): - self.modulus = p - super().__init__(r, p-1) - - def mul(self, x, y): - return (x * y) % self.modulus - def pow(self, x, n): - return pow(x, n, self.modulus) - def inverse(self, x): - return invert(x, self.modulus) - def identity(self): - return 1 - def iter_elements(self): - return range(1, self.modulus) - - def root(self, g): - return 0 if g == 0 else super().root(g) - -class ModP(object): - """Class that represents integers mod p as a field. - - All the usual arithmetic operations are supported directly, - including division, so you can write formulas in a natural way - without having to keep saying '% p' everywhere or call a - cumbersome modular_inverse() function. - - """ - def __init__(self, p, n=0): - self.p = p - if isinstance(n, type(self)): - self.check(n) - n = n.n - self.n = n % p - def check(self, other): - assert isinstance(other, type(self)) - assert isinstance(self, type(other)) - assert self.p == other.p - def coerce_to(self, other): - if not isinstance(other, type(self)): - other = type(self)(self.p, other) - else: - self.check(other) - return other - def __int__(self): - return self.n - def __add__(self, rhs): - rhs = self.coerce_to(rhs) - return type(self)(self.p, (self.n + rhs.n) % self.p) - def __neg__(self): - return type(self)(self.p, -self.n % self.p) - def __radd__(self, rhs): - rhs = self.coerce_to(rhs) - return type(self)(self.p, (self.n + rhs.n) % self.p) - def __sub__(self, rhs): - rhs = self.coerce_to(rhs) - return type(self)(self.p, (self.n - rhs.n) % self.p) - def __rsub__(self, rhs): - rhs = self.coerce_to(rhs) - return type(self)(self.p, (rhs.n - self.n) % self.p) - def __mul__(self, rhs): - rhs = self.coerce_to(rhs) - return type(self)(self.p, (self.n * rhs.n) % self.p) - def __rmul__(self, rhs): - rhs = self.coerce_to(rhs) - return type(self)(self.p, (self.n * rhs.n) % self.p) - def __div__(self, rhs): - rhs = self.coerce_to(rhs) - return type(self)(self.p, (self.n * invert(rhs.n, self.p)) % self.p) - def __rdiv__(self, rhs): - rhs = self.coerce_to(rhs) - return type(self)(self.p, (rhs.n * invert(self.n, self.p)) % self.p) - def __truediv__(self, rhs): return self.__div__(rhs) - def __rtruediv__(self, rhs): return self.__rdiv__(rhs) - def __pow__(self, exponent): - assert exponent >= 0 - n, b_to_n = 1, self - total = type(self)(self.p, 1) - while True: - if exponent & n: - exponent -= n - total *= b_to_n - n *= 2 - if n > exponent: - break - b_to_n *= b_to_n - return total - def __cmp__(self, rhs): - rhs = self.coerce_to(rhs) - return cmp(self.n, rhs.n) - def __eq__(self, rhs): - rhs = self.coerce_to(rhs) - return self.n == rhs.n - def __ne__(self, rhs): - rhs = self.coerce_to(rhs) - return self.n != rhs.n - def __lt__(self, rhs): - raise ValueError("Elements of a modular ring have no ordering") - def __le__(self, rhs): - raise ValueError("Elements of a modular ring have no ordering") - def __gt__(self, rhs): - raise ValueError("Elements of a modular ring have no ordering") - def __ge__(self, rhs): - raise ValueError("Elements of a modular ring have no ordering") - def __str__(self): - return "0x{:x}".format(self.n) - def __repr__(self): - return "{}(0x{:x},0x{:x})".format(type(self).__name__, self.p, self.n) - def __hash__(self): - return hash((type(self).__name__, self.p, self.n)) - -class QuadraticFieldExtensionModP(object): - """Class representing Z_p[sqrt(d)] for a given non-square d. - """ - def __init__(self, p, d, n=0, m=0): - self.p = p - self.d = d - if isinstance(n, ModP): - assert self.p == n.p - n = n.n - if isinstance(m, ModP): - assert self.p == m.p - m = m.n - if isinstance(n, type(self)): - self.check(n) - m += n.m - n = n.n - self.n = n % p - self.m = m % p - - @classmethod - def constructor(cls, p, d): - return lambda *args: cls(p, d, *args) - - def check(self, other): - assert isinstance(other, type(self)) - assert isinstance(self, type(other)) - assert self.p == other.p - assert self.d == other.d - def coerce_to(self, other): - if not isinstance(other, type(self)): - other = type(self)(self.p, self.d, other) - else: - self.check(other) - return other - def __int__(self): - if self.m != 0: - raise ValueError("Can't coerce a non-element of Z_{} to integer" - .format(self.p)) - return int(self.n) - def __add__(self, rhs): - rhs = self.coerce_to(rhs) - return type(self)(self.p, self.d, - (self.n + rhs.n) % self.p, - (self.m + rhs.m) % self.p) - def __neg__(self): - return type(self)(self.p, self.d, - -self.n % self.p, - -self.m % self.p) - def __radd__(self, rhs): - rhs = self.coerce_to(rhs) - return type(self)(self.p, self.d, - (self.n + rhs.n) % self.p, - (self.m + rhs.m) % self.p) - def __sub__(self, rhs): - rhs = self.coerce_to(rhs) - return type(self)(self.p, self.d, - (self.n - rhs.n) % self.p, - (self.m - rhs.m) % self.p) - def __rsub__(self, rhs): - rhs = self.coerce_to(rhs) - return type(self)(self.p, self.d, - (rhs.n - self.n) % self.p, - (rhs.m - self.m) % self.p) - def __mul__(self, rhs): - rhs = self.coerce_to(rhs) - n, m, N, M = self.n, self.m, rhs.n, rhs.m - return type(self)(self.p, self.d, - (n*N + self.d*m*M) % self.p, - (n*M + m*N) % self.p) - def __rmul__(self, rhs): - return self.__mul__(rhs) - def __div__(self, rhs): - rhs = self.coerce_to(rhs) - n, m, N, M = self.n, self.m, rhs.n, rhs.m - # (n+m sqrt d)/(N+M sqrt d) = (n+m sqrt d)(N-M sqrt d)/(N^2-dM^2) - denom = (N*N - self.d*M*M) % self.p - if denom == 0: - raise ValueError("division by zero") - recipdenom = invert(denom, self.p) - return type(self)(self.p, self.d, - (n*N - self.d*m*M) * recipdenom % self.p, - (m*N - n*M) * recipdenom % self.p) - def __rdiv__(self, rhs): - rhs = self.coerce_to(rhs) - return rhs.__div__(self) - def __truediv__(self, rhs): return self.__div__(rhs) - def __rtruediv__(self, rhs): return self.__rdiv__(rhs) - def __pow__(self, exponent): - assert exponent >= 0 - n, b_to_n = 1, self - total = type(self)(self.p, self.d, 1) - while True: - if exponent & n: - exponent -= n - total *= b_to_n - n *= 2 - if n > exponent: - break - b_to_n *= b_to_n - return total - def __cmp__(self, rhs): - rhs = self.coerce_to(rhs) - return cmp((self.n, self.m), (rhs.n, rhs.m)) - def __eq__(self, rhs): - rhs = self.coerce_to(rhs) - return self.n == rhs.n and self.m == rhs.m - def __ne__(self, rhs): - rhs = self.coerce_to(rhs) - return self.n != rhs.n or self.m != rhs.m - def __lt__(self, rhs): - raise ValueError("Elements of a modular ring have no ordering") - def __le__(self, rhs): - raise ValueError("Elements of a modular ring have no ordering") - def __gt__(self, rhs): - raise ValueError("Elements of a modular ring have no ordering") - def __ge__(self, rhs): - raise ValueError("Elements of a modular ring have no ordering") - def __str__(self): - if self.m == 0: - return "0x{:x}".format(self.n) - else: - return "0x{:x}+0x{:x}*sqrt({:d})".format(self.n, self.m, self.d) - def __repr__(self): - return "{}(0x{:x},0x{:x},0x{:x},0x{:x})".format( - type(self).__name__, self.p, self.d, self.n, self.m) - def __hash__(self): - return hash((type(self).__name__, self.p, self.d, self.n, self.m)) - -class RootInQuadraticExtension(CyclicGroupRootFinder): - """Take rth roots in the quadratic extension of Z_p.""" - - def __init__(self, r, p, d): - self.modulus = p - self.constructor = QuadraticFieldExtensionModP.constructor(p, d) - super().__init__(r, p*p-1) - - def mul(self, x, y): - return x * y - def pow(self, x, n): - return x ** n - def inverse(self, x): - return 1/x - def identity(self): - return self.constructor(1, 0) - def iter_elements(self): - p = self.modulus - for n_plus_m in range(1, 2*p-1): - n_min = max(0, n_plus_m-(p-1)) - n_max = min(p-1, n_plus_m) - for n in range(n_min, n_max + 1): - m = n_plus_m - n - assert(0 <= n < p) - assert(0 <= m < p) - assert(n != 0 or m != 0) - yield self.constructor(n, m) - - def root(self, g): - return 0 if g == 0 else super().root(g) - -class EquationSolverModP(object): - """Class that can solve quadratics, cubics and quartics over Z_p. - - p must be a nontrivial prime (bigger than 3). - """ - - # This is a port to Z_p of reasonably standard algorithms for - # solving quadratics, cubics and quartics over the reals. - # - # When you solve a cubic in R, you sometimes have to deal with - # intermediate results that are complex numbers. In particular, - # you have to solve a quadratic whose coefficients are in R but - # its roots may be complex, and then having solved that quadratic, - # you need to iterate over all three cube roots of the solution in - # order to recover all the roots of your cubic. (Even if the cubic - # ends up having three real roots, you can't calculate them - # without going through those complex intermediate values.) - # - # So over Z_p, the same thing applies: we're going to need to be - # able to solve any quadratic with coefficients in Z_p, even if - # its discriminant turns out not to be a quadratic residue mod p, - # and then we'll need to find _three_ cube roots of the result, - # even if p == 2 (mod 3) so that numbers only have one cube root - # each. - # - # Both of these problems can be solved at once if we work in the - # finite field GF(p^2), i.e. make a quadratic field extension of - # Z_p by adjoining a square root of some non-square d. The - # multiplicative group of GF(p^2) is cyclic and has order p^2-1 = - # (p-1)(p+1), with the mult group of Z_p forming the unique - # subgroup of order (p-1) within it. So we've multiplied the group - # order by p+1, which is even (since by assumption p > 3), and - # therefore a square root is now guaranteed to exist for every - # number in the Z_p subgroup. Moreover, no matter whether p itself - # was congruent to 1 or 2 mod 3, p^2 is always congruent to 1, - # which means that the mult group of GF(p^2) has order divisible - # by 3. So there are guaranteed to be three distinct cube roots of - # unity, and hence, three cube roots of any number that's a cube - # at all. - # - # Quartics don't introduce any additional problems. To solve a - # quartic, you factorise it into two quadratic factors, by solving - # a cubic to find one of the coefficients. So if you can already - # solve cubics, then you're more or less done. The only wrinkle is - # that the two quadratic factors will have coefficients in GF(p^2) - # but not necessarily in Z_p. But that doesn't stop us at least - # _trying_ to solve them by taking square roots in GF(p^2) - and - # if the discriminant of one of those quadratics has is not a - # square even in GF(p^2), then its solutions will only exist if - # you escalate further to GF(p^4), in which case the answer is - # simply that there aren't any solutions in Z_p to that quadratic. - - def __init__(self, p): - self.p = p - self.nonsquare_mod_p = d = RootModP(2, p).z - self.constructor = QuadraticFieldExtensionModP.constructor(p, d) - self.sqrt = RootInQuadraticExtension(2, p, d) - self.cbrt = RootInQuadraticExtension(3, p, d) - - def solve_quadratic(self, a, b, c): - "Solve ax^2 + bx + c = 0." - a, b, c = map(self.constructor, (a, b, c)) - assert a != 0 - return self.solve_monic_quadratic(b/a, c/a) - - def solve_monic_quadratic(self, b, c): - "Solve x^2 + bx + c = 0." - b, c = map(self.constructor, (b, c)) - s = b/2 - return [y - s for y in self.solve_depressed_quadratic(c - s*s)] - - def solve_depressed_quadratic(self, c): - "Solve x^2 + c = 0." - return self.sqrt.all_roots(-c) - - def solve_cubic(self, a, b, c, d): - "Solve ax^3 + bx^2 + cx + d = 0." - a, b, c, d = map(self.constructor, (a, b, c, d)) - assert a != 0 - return self.solve_monic_cubic(b/a, c/a, d/a) - - def solve_monic_cubic(self, b, c, d): - "Solve x^3 + bx^2 + cx + d = 0." - b, c, d = map(self.constructor, (b, c, d)) - s = b/3 - return [y - s for y in self.solve_depressed_cubic( - c - 3*s*s, 2*s*s*s - c*s + d)] - - def solve_depressed_cubic(self, c, d): - "Solve x^3 + cx + d = 0." - c, d = map(self.constructor, (c, d)) - solutions = set() - # To solve x^3 + cx + d = 0, set p = -c/3, then - # substitute x = z + p/z to get z^6 + d z^3 + p^3 = 0. - # Solve that quadratic for z^3, then take cube roots. - p = -c/3 - for z3 in self.solve_monic_quadratic(d, p**3): - # As I understand the theory, we _should_ only need to - # take cube roots of one root of that quadratic: the other - # one should give the same set of answers after you map - # each one through z |-> z+p/z. But speed isn't at a - # premium here, so I'll do this the way that must work. - for z in self.cbrt.all_roots(z3): - solutions.add(z + p/z) - return solutions - - def solve_quartic(self, a, b, c, d, e): - "Solve ax^4 + bx^3 + cx^2 + dx + e = 0." - a, b, c, d, e = map(self.constructor, (a, b, c, d, e)) - assert a != 0 - return self.solve_monic_quartic(b/a, c/a, d/a, e/a) - - def solve_monic_quartic(self, b, c, d, e): - "Solve x^4 + bx^3 + cx^2 + dx + e = 0." - b, c, d, e = map(self.constructor, (b, c, d, e)) - s = b/4 - return [y - s for y in self.solve_depressed_quartic( - c - 6*s*s, d - 2*c*s + 8*s*s*s, e - d*s + c*s*s - 3*s*s*s*s)] - - def solve_depressed_quartic(self, c, d, e): - "Solve x^4 + cx^2 + dx + e = 0." - c, d, e = map(self.constructor, (c, d, e)) - solutions = set() - # To solve an equation of this form, we search for a value y - # such that subtracting the original polynomial from (x^2+y)^2 - # yields a quadratic of the special form (ux+v)^2. - # - # Then our equation is rewritten as (x^2+y)^2 - (ux+v)^2 = 0 - # i.e. ((x^2+y) + (ux+v)) ((x^2+y) - (ux+v)) = 0 - # i.e. the product of two quadratics, each of which we then solve. - # - # To find y, we write down the discriminant of the quadratic - # (x^2+y)^2 - (x^4 + cx^2 + dx + e) and set it to 0, which - # gives a cubic in y. Maxima gives the coefficients as - # (-8)y^3 + (4c)y^2 + (8e)y + (d^2-4ce). - # - # As above, we _should_ only need one value of y. But I go - # through them all just in case, because I don't care about - # speed, and because checking the assertions inside this loop - # for every value is extra reassurance that I've done all of - # this right. - for y in self.solve_cubic(-8, 4*c, 8*e, d*d-4*c*e): - # Subtract the original equation from (x^2+y)^2 to get the - # coefficients of our quadratic residual. - A, B, C = 2*y-c, -d, y*y-e - # Expect that to have zero discriminant, i.e. a repeated root. - assert B*B - 4*A*C == 0 - # If (Ax^2+Bx+C) == (ux+v)^2 then we have u^2=A, 2uv=B, v^2=C. - # So we can either recover u as sqrt(A) or v as sqrt(C), and - # whichever we did, find the other from B by division. But - # either of the end coefficients might be zero, so we have - # to be prepared to try either option. - try: - if A != 0: - u = self.sqrt.root(A) - v = B/(2*u) - elif C != 0: - v = self.sqrt.root(C) - u = B/(2*v) - else: - # One last possibility is that all three coefficients - # of our residual quadratic are 0, in which case, - # obviously, u=v=0 as well. - u = v = 0 - except ValueError: - # If Ax^2+Bx+C looked like a perfect square going by - # its discriminant, but actually taking the square - # root of A or C threw an exception, that means that - # it's the square of a polynomial whose coefficients - # live in a yet-higher field extension of Z_p. In that - # case we're not going to end up with roots of the - # original quartic in Z_p if we start from here! - continue - # So now our quartic is factorised into the form - # (x^2 - ux - v + y) (x^2 + ux + v + y). - for x in self.solve_monic_quadratic(-u, y-v): - solutions.add(x) - for x in self.solve_monic_quadratic(u, y+v): - solutions.add(x) - return solutions - -class EquationSolverTest(unittest.TestCase): - def testQuadratic(self): - E = EquationSolverModP(11) - solns = E.solve_quadratic(3, 2, 6) - self.assertEqual(sorted(map(str, solns)), ["0x1", "0x2"]) - - def testCubic(self): - E = EquationSolverModP(11) - solns = E.solve_cubic(7, 2, 0, 2) - self.assertEqual(sorted(map(str, solns)), ["0x1", "0x2", "0x3"]) - - def testQuartic(self): - E = EquationSolverModP(11) - solns = E.solve_quartic(9, 9, 7, 1, 7) - self.assertEqual(sorted(map(str, solns)), ["0x1", "0x2", "0x3", "0x4"]) - -if __name__ == "__main__": - import sys - if sys.argv[1:] == ["--test"]: - sys.argv[1:2] = [] - unittest.main() diff --git a/test/primegen.py b/test/primegen.py deleted file mode 100644 index 45b340f4f..000000000 --- a/test/primegen.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 - -from testcrypt import * -import base64 -import argparse -import itertools - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -def main(): - opener = lambda mode: lambda fname: lambda: argparse.FileType(mode)(fname) - parser = argparse.ArgumentParser(description='') - IntArg = lambda x: int(x, 0) - parser.add_argument("bits", type=IntArg, nargs="?", default=1024) - parser.add_argument("-s", "--seed") - parser.add_argument("-f", "--firstbits", type=IntArg, default=1) - parser.add_argument("--fast", action='store_const', - dest='policy', const='provable_fast') - parser.add_argument("--complex", action='store_const', - dest='policy', const='provable_maurer_complex') - parser.add_argument("--probabilistic", action='store_const', - dest='policy', const='probabilistic') - parser.add_argument("-q", "--quiet", action='store_true') - parser.add_argument("-b", "--binary", action='store_const', - dest='fmt', const='{:b}') - parser.add_argument("-x", "--hex", action='store_const', - dest='fmt', const='{:x}') - parser.add_argument("-o", "--output", type=opener("w"), - default=opener("w")("-"), - help="file to write the prime to") - parser.add_argument("--mpu", type=opener("w"), - help="MPU certificate output file") - parser.add_argument("--safe", action='store_true') - parser.set_defaults(fmt='{:d}', policy='provable_maurer_simple') - args = parser.parse_args() - - seed = args.seed - if seed is None: - with open("/dev/urandom", "rb") as f: - seed = base64.b64encode(f.read(32)).decode("ASCII") - - if not args.quiet: - print("seed =", seed) - random_make_prng('sha256', seed) - assert args.firstbits > 0 - nfirst = next(i for i in itertools.count() if (args.firstbits >> i) == 0) - pgc = primegen_new_context(args.policy) - if args.safe: - while True: - pcs_q = pcs_new_with_firstbits(args.bits - 1, - args.firstbits, nfirst) - pcs_try_sophie_germain(pcs_q) - q = primegen_generate(pgc, pcs_q) - pcs = pcs_new(args.bits) - pcs_require_residue_1_mod_prime(pcs, q) - pcs_set_oneshot(pcs) - p = primegen_generate(pgc, pcs) - if p is not None: - break - else: - pcs = pcs_new_with_firstbits(args.bits, args.firstbits, nfirst) - p = primegen_generate(pgc, pcs) - - with args.output() as f: - print(args.fmt.format(int(p)), file=f) - - if args.mpu is not None: - s = primegen_mpu_certificate(pgc, p) - with args.mpu() as f: - f.write(s.decode("ASCII")) - -if __name__ == '__main__': - main() diff --git a/test/sclog/.gitignore b/test/sclog/.gitignore deleted file mode 100644 index 75512ebb5..000000000 --- a/test/sclog/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -/.ninja_* -/CMakeFiles -/CMakeCache.txt -/cmake_install.cmake -/*.ninja -/*.ldscript -/libsclog.so diff --git a/test/sclog/CMakeLists.txt b/test/sclog/CMakeLists.txt deleted file mode 100644 index 8a8ac5704..000000000 --- a/test/sclog/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -# CMake script for the 'sclog' DynamoRIO instrumentation system that -# goes with the PuTTY test binary 'testsc'. For build instructions see -# the comment at the top of testsc.c. - -cmake_minimum_required(VERSION 3.5) - -project(sclog LANGUAGES C) - -find_package(DynamoRIO) -if (NOT DynamoRIO_FOUND) - message(FATAL_ERROR "DynamoRIO not found") -endif() - -add_library(sclog SHARED sclog.c) -configure_DynamoRIO_client(sclog) -foreach(extension drmgr drsyms drreg drutil drwrap) - use_DynamoRIO_extension(sclog ${extension}) -endforeach() diff --git a/test/sclog/sclog.c b/test/sclog/sclog.c deleted file mode 100644 index d5304a015..000000000 --- a/test/sclog/sclog.c +++ /dev/null @@ -1,672 +0,0 @@ -/* - * sclog: the DynamoRIO instrumentation system that goes with the - * PuTTY test binary 'testsc'. - * - * For general discussion and build instructions, see the comment at - * the top of testsc.c. - */ - -#include -#include - -#include "dr_api.h" -#include "drmgr.h" -#include "drsyms.h" -#include "drreg.h" -#include "drutil.h" -#include "drwrap.h" - -/* - * The file we're currently logging to, if any. - */ -static file_t outfile = INVALID_FILE; - -/* - * A counter which we can increment and decrement around any library - * function we don't want to log the details of what happens inside. - * Mainly this is for memory allocation functions, which will diverge - * control depending on the progress of their search for something - * they can allocate. - */ -size_t logging_paused = 0; - -/* - * This log message appears at the start of whatever DynamoRIO - * considers a 'basic block', i.e. a sequence of instructions with no - * branches. Logging these is cheaper than logging every single - * instruction, and should still be adequate to detect any divergence - * of control flow. - */ -static void log_pc(const char *loc) -{ - if (outfile == INVALID_FILE || logging_paused) - return; - dr_fprintf(outfile, "%s: start basic block\n", loc); -} - -/* - * Hardware division instructions are unlikely to run in time - * independent of the data, so we log both their parameters. - */ -static void log_div(uint n, uint d, const char *loc) -{ - if (outfile == INVALID_FILE || logging_paused) - return; - dr_fprintf(outfile, "%s: divide %"PRIuMAX" / %"PRIuMAX"\n", - loc, (uintmax_t)n, (uintmax_t)d); -} - -/* - * Register-controlled shift instructions are not reliably one cycle - * long on all platforms, so we log the shift couhnt. - */ -static void log_var_shift(uint sh, const char *loc) -{ - if (outfile == INVALID_FILE || logging_paused) - return; - dr_fprintf(outfile, "%s: var shift by %"PRIuMAX"\n", loc, (uintmax_t)sh); -} - -/* - * We need to log memory accesses, so as to detect data-dependent - * changes in the access pattern (e.g. incautious use of a lookup - * table). But one thing we _can't_ control for perfectly is that in - * two successive runs of the same crypto primitive, malloc may be - * called, and may return different addresses - which of course is not - * dependent on the data (unless the size of the allocated block - * does). - * - * So we track all the memory allocations that happen during logging, - * and any addresses accessed within those blocks are logged as - * something along the lines of 'n bytes from the start of the mth - * allocation'. - * - * Allocations that happened before a given log file was opened are - * not tracked. The program under test will ensure that any of those - * used by the primitive are at the same address in all runs anyway. - */ -struct allocation { - /* - * We store the list of allocations in a linked list, so we can - * look them up by address, and delete them as they're freed. - * - * A balanced binary search tree would be faster, but this is - * easier to get right first time! - */ - struct allocation *prev, *next; - uintptr_t start, size, index; -}; -static struct allocation alloc_ends[1] = { alloc_ends, alloc_ends, 0, 0, 0 }; -static uintptr_t next_alloc_index = 0; - -static void free_allocation(struct allocation *alloc) -{ - alloc->next->prev = alloc->prev; - alloc->prev->next = alloc->next; - dr_global_free(alloc, sizeof(struct allocation)); -} - -/* - * Wrap the log_set_file() function in testsc.c, and respond to it by - * opening or closing log files. - */ -static void wrap_logsetfile(void *wrapctx, void **user_data) -{ - if (outfile) { - dr_close_file(outfile); - outfile = INVALID_FILE; - } - - const char *outfilename = drwrap_get_arg(wrapctx, 0); - if (outfilename) { - outfile = dr_open_file(outfilename, DR_FILE_WRITE_OVERWRITE); - DR_ASSERT(outfile != INVALID_FILE); - } - - /* - * Reset the allocation list to empty, whenever we open or close a - * log file. - */ - while (alloc_ends->next != alloc_ends) - free_allocation(alloc_ends->next); - next_alloc_index = 0; -} - -/* - * Wrap the dry_run() function in testsc.c, to tell it we're here. - */ -static void wrap_dryrun(void *wrapctx, void *user_data) -{ - drwrap_set_retval(wrapctx, (void *)0); -} - -/* - * Look up the memory allocation record corresponding to an address. - */ -static struct allocation *find_allocation(const void *ptr) -{ - uintptr_t address = (uintptr_t)ptr; - for (struct allocation *alloc = alloc_ends->next; - alloc != alloc_ends; alloc = alloc->next) { - if (alloc && address - alloc->start < alloc->size) - return alloc; - } - return NULL; -} - -/* - * Log a memory access. - */ -static void log_mem(app_pc addr, uint size, uint write, const char *loc) -{ - if (outfile == INVALID_FILE || logging_paused) - return; - - struct allocation *alloc = find_allocation((const void *)addr); - if (!alloc) { - dr_fprintf(outfile, "%s: %s %"PRIuMAX" @ %"PRIxMAX"\n", - loc, write ? "store" : "load", (uintmax_t)size, - (uintmax_t)addr); - } else { - dr_fprintf(outfile, "%s: %s %"PRIuMAX" @ allocations[%"PRIuPTR"]" - " + %"PRIxMAX"\n", - loc, write ? "store" : "load", (uintmax_t)size, - alloc->index, (uintmax_t)(addr - alloc->start)); - } -} - -/* - * Record the allocation of some memory. (Common code between malloc - * and realloc.) - */ -static void allocated(void *ptr, size_t size) -{ - if (outfile == INVALID_FILE) - return; /* no need to track allocations outside a logging interval */ - - struct allocation *alloc = dr_global_alloc(sizeof(struct allocation)); - alloc->start = (uintptr_t)ptr; - alloc->size = size; - alloc->index = next_alloc_index++; - alloc->prev = alloc_ends->prev; - alloc->next = alloc_ends; - alloc->prev->next = alloc->next->prev = alloc; -} - -/* - * Record that memory has been freed. Note that we may free something - * that was allocated when we weren't logging, so we must cope with - * find_allocation returning NULL. - */ -static void freed(void *ptr) -{ - struct allocation *alloc = find_allocation(ptr); - if (alloc) - free_allocation(alloc); -} - -/* - * The actual wrapper functions for malloc, realloc and free. - */ -static void wrap_malloc_pre(void *wrapctx, void **user_data) -{ - logging_paused++; - *user_data = drwrap_get_arg(wrapctx, 0); - dr_fprintf(outfile, "malloc %"PRIuMAX"\n", (uintmax_t)*user_data); -} -static void wrap_free_pre(void *wrapctx, void **user_data) -{ - logging_paused++; - void *ptr = drwrap_get_arg(wrapctx, 0); - freed(ptr); -} -static void wrap_realloc_pre(void *wrapctx, void **user_data) -{ - logging_paused++; - void *ptr = drwrap_get_arg(wrapctx, 0); - freed(ptr); - *user_data = drwrap_get_arg(wrapctx, 1); - dr_fprintf(outfile, "realloc %"PRIuMAX"\n", (uintmax_t)*user_data); -} -static void wrap_alloc_post(void *wrapctx, void *user_data) -{ - void *ptr = drwrap_get_retval(wrapctx); - if (!ptr) - return; - size_t size = (size_t)user_data; - allocated(ptr, size); - logging_paused--; -} - -/* - * We wrap the C library function memset, because I've noticed that at - * least one optimised implementation of it diverges control flow - * internally based on what appears to be the _alignment_ of the input - * pointer - and that alignment check can vary depending on the - * addresses of allocated blocks. So I can't guarantee no divergence - * of control flow inside memset if malloc doesn't return the same - * values, and instead I just have to trust that memset isn't reading - * the contents of the block and basing control flow decisions on that. - */ -static void wrap_memset_pre(void *wrapctx, void **user_data) -{ - uint was_already_paused = logging_paused++; - - if (outfile == INVALID_FILE || was_already_paused) - return; - - const void *addr = drwrap_get_arg(wrapctx, 0); - size_t size = (size_t)drwrap_get_arg(wrapctx, 2); - - struct allocation *alloc = find_allocation(addr); - if (!alloc) { - dr_fprintf(outfile, "memset %"PRIuMAX" @ %"PRIxMAX"\n", - (uintmax_t)size, (uintmax_t)addr); - } else { - dr_fprintf(outfile, "memset %"PRIuMAX" @ allocations[%"PRIuPTR"]" - " + %"PRIxMAX"\n", (uintmax_t)size, alloc->index, - (uintmax_t)(addr - alloc->start)); - } -} - -/* - * Similarly to the above, wrap some versions of memmove. - */ -static void wrap_memmove_pre(void *wrapctx, void **user_data) -{ - uint was_already_paused = logging_paused++; - - if (outfile == INVALID_FILE || was_already_paused) - return; - - const void *daddr = drwrap_get_arg(wrapctx, 0); - const void *saddr = drwrap_get_arg(wrapctx, 1); - size_t size = (size_t)drwrap_get_arg(wrapctx, 2); - - - struct allocation *alloc; - - dr_fprintf(outfile, "memmove %"PRIuMAX" ", (uintmax_t)size); - if (!(alloc = find_allocation(daddr))) { - dr_fprintf(outfile, "to %"PRIxMAX" ", (uintmax_t)daddr); - } else { - dr_fprintf(outfile, "to allocations[%"PRIuPTR"] + %"PRIxMAX" ", - alloc->index, (uintmax_t)(daddr - alloc->start)); - } - if (!(alloc = find_allocation(saddr))) { - dr_fprintf(outfile, "from %"PRIxMAX"\n", (uintmax_t)saddr); - } else { - dr_fprintf(outfile, "from allocations[%"PRIuPTR"] + %"PRIxMAX"\n", - alloc->index, (uintmax_t)(saddr - alloc->start)); - } -} - -/* - * Common post-wrapper function for memset and free, whose entire - * function is to unpause the logging. - */ -static void unpause_post(void *wrapctx, void *user_data) -{ - logging_paused--; -} - -/* - * Make a string representation of the address of an instruction, - * including a function name and/or a file+line combination if - * possible. These will be logged alongside every act of interest - * where we can make one. - */ -static void instr_format_location(instr_t *instr, char **outloc) -{ - app_pc addr = (app_pc)instr_get_app_pc(instr); - char location[2048], symbol[512], fileline[1024]; - bool got_sym = false, got_line = false; - - if (*outloc) - return; - - symbol[0] = '\0'; - fileline[0] = '\0'; - - module_data_t *data = dr_lookup_module(addr); - if (data) { - drsym_info_t sym; - char file[MAXIMUM_PATH]; - - sym.struct_size = sizeof(sym); - sym.name = symbol; - sym.name_size = sizeof(symbol); - sym.file = file; - sym.file_size = sizeof(file); - - drsym_error_t status = drsym_lookup_address( - data->full_path, addr - data->start, &sym, DRSYM_DEFAULT_FLAGS); - - got_line = (status == DRSYM_SUCCESS); - got_sym = got_line || status == DRSYM_ERROR_LINE_NOT_AVAILABLE; - - if (got_line) - snprintf(fileline, sizeof(fileline), " = %s:%"PRIu64, - file, (uint64_t)sym.line); - } - - snprintf(location, sizeof(location), - "%"PRIx64"%s%s%s", - (uint64_t)addr, got_sym ? " = " : "", got_sym ? symbol : "", - fileline); - size_t len = strlen(location) + 1; - char *loc = dr_global_alloc(len); - memcpy(loc, location, len); - *outloc = loc; -} - -/* - * Function that tests a single operand of an instruction to see if - * it's a memory reference, and if so, adds a call to log_mem. - */ -static void try_mem_opnd( - void *drcontext, instrlist_t *bb, instr_t *instr, char **loc, - opnd_t opnd, bool write) -{ - if (!opnd_is_memory_reference(opnd)) - return; - - instr_format_location(instr, loc); - - reg_id_t r0, r1; - drreg_status_t st; - st = drreg_reserve_register(drcontext, bb, instr, NULL, &r0); - DR_ASSERT(st == DRREG_SUCCESS); - st = drreg_reserve_register(drcontext, bb, instr, NULL, &r1); - DR_ASSERT(st == DRREG_SUCCESS); - - bool ok = drutil_insert_get_mem_addr(drcontext, bb, instr, opnd, r0, r1); - DR_ASSERT(ok); - - uint size = drutil_opnd_mem_size_in_bytes(opnd, instr); - - dr_insert_clean_call( - drcontext, bb, instr, (void *)log_mem, false, - 4, opnd_create_reg(r0), OPND_CREATE_INT32(size), - OPND_CREATE_INT32(write), OPND_CREATE_INTPTR(*loc)); - - st = drreg_unreserve_register(drcontext, bb, instr, r1); - DR_ASSERT(st == DRREG_SUCCESS); - st = drreg_unreserve_register(drcontext, bb, instr, r0); - DR_ASSERT(st == DRREG_SUCCESS); -} - -/* - * The main function called to instrument each machine instruction. - */ -static dr_emit_flags_t instrument_instr( - void *drcontext, void *tag, instrlist_t *bb, instr_t *instr, - bool for_trace, bool translating, void *user_data) -{ - char *loc = NULL; - - /* - * If this instruction is the first in its basic block, call - * log_pc to record that we're executing this block at all. - */ - if (drmgr_is_first_instr(drcontext, instr)) { - instr_format_location(instr, &loc); - dr_insert_clean_call( - drcontext, bb, instr, (void *)log_pc, false, - 1, OPND_CREATE_INTPTR(loc)); - } - - /* - * If the instruction reads or writes memory, log its access. - */ - if (instr_reads_memory(instr) || instr_writes_memory(instr)) { - for (int i = 0, limit = instr_num_srcs(instr); i < limit; i++) - try_mem_opnd(drcontext, bb, instr, &loc, - instr_get_src(instr, i), instr_writes_memory(instr)); - for (int i = 0, limit = instr_num_dsts(instr); i < limit; i++) - try_mem_opnd(drcontext, bb, instr, &loc, - instr_get_dst(instr, i), instr_writes_memory(instr)); - } - - /* - * Now do opcode-specific checks. - */ - int opcode = instr_get_opcode(instr); - - switch (opcode) { -#if defined(X86) - case OP_div: - case OP_idiv: - /* - * x86 hardware divisions. The operand order for DR's - * representation of these seem to be: 0 = denominator, 1 = - * numerator MSW, 2 = numerator LSW. - */ - instr_format_location(instr, &loc); - dr_insert_clean_call( - drcontext, bb, instr, (void *)log_div, false, - 3, instr_get_src(instr, 2), instr_get_src(instr, 0), - OPND_CREATE_INTPTR(loc)); - break; -#endif -#if defined(AARCH64) - case OP_sdiv: - case OP_udiv: - /* - * AArch64 hardware divisions. 0 = numerator, 1 = denominator. - */ - instr_format_location(instr, &loc); - dr_insert_clean_call( - drcontext, bb, instr, (void *)log_div, false, - 3, instr_get_src(instr, 0), instr_get_src(instr, 1), - OPND_CREATE_INTPTR(loc)); - break; -#endif -#if defined(X86) - case OP_shl: - case OP_shr: - case OP_sar: - case OP_shlx: - case OP_shrx: - case OP_sarx: - case OP_rol: - case OP_ror: - case OP_rcl: - case OP_rcr: { - /* - * Shift instructions. If they're register-controlled, log the - * shift count. - */ - opnd_t shiftcount = instr_get_src(instr, 0); - if (!opnd_is_immed(shiftcount)) { - reg_id_t r0; - drreg_status_t st; - st = drreg_reserve_register(drcontext, bb, instr, NULL, &r0); - DR_ASSERT(st == DRREG_SUCCESS); - opnd_t op_r0 = opnd_create_reg(r0); - instr_t *movzx = INSTR_CREATE_movzx(drcontext, op_r0, shiftcount); - instr_set_translation(movzx, instr_get_app_pc(instr)); - instrlist_preinsert(bb, instr, movzx); - instr_format_location(instr, &loc); - dr_insert_clean_call( - drcontext, bb, instr, (void *)log_var_shift, false, - 2, op_r0, OPND_CREATE_INTPTR(loc)); - st = drreg_unreserve_register(drcontext, bb, instr, r0); - DR_ASSERT(st == DRREG_SUCCESS); - } - break; - } -#endif -#if defined(AARCH64) - case OP_lslv: - case OP_asrv: - case OP_lsrv: - case OP_rorv: { - /* - * AArch64 variable shift instructions. - */ - opnd_t shiftcount = instr_get_src(instr, 1); - DR_ASSERT(opnd_is_reg(shiftcount)); - reg_id_t shiftreg = opnd_get_reg(shiftcount); - if (shiftreg >= DR_REG_W0 && shiftreg <= DR_REG_WSP) - shiftreg = reg_32_to_64(shiftreg); - instr_format_location(instr, &loc); - dr_insert_clean_call( - drcontext, bb, instr, (void *)log_var_shift, false, - 2, opnd_create_reg(shiftreg), OPND_CREATE_INTPTR(loc)); - break; - } -#endif - } - - return DR_EMIT_DEFAULT; -} - -static void exit_event(void) -{ - if (outfile != INVALID_FILE) { - dr_fprintf(outfile, "exit while recording enabled\n"); - dr_close_file(outfile); - outfile = INVALID_FILE; - } - drsym_exit(); - drreg_exit(); - drwrap_exit(); - drutil_exit(); - drmgr_exit(); -} - -/* - * We ask DR to expand any x86 string instructions like REP MOVSB, so - * that we can log all the individual memory accesses without getting - * confused. - */ -static dr_emit_flags_t expand_rep_movsb( - void *drcontext, void *tag, instrlist_t *bb, bool for_trace, - bool translating) -{ - bool ok = drutil_expand_rep_string(drcontext, bb); - DR_ASSERT(ok); - return DR_EMIT_DEFAULT; -} - -typedef void (*prewrapper_t)(void *wrapctx, void **user_data); -typedef void (*postwrapper_t)(void *wrapctx, void *user_data); - -/* - * Helper function for bulk use of drwrap. - */ -static void try_wrap_fn(const module_data_t *module, const char *name, - prewrapper_t pre, postwrapper_t post, bool *done) -{ - if (*done) - return; - - size_t offset; - drsym_error_t status = drsym_lookup_symbol( - module->full_path, name, &offset, DRSYM_DEFAULT_FLAGS); - if (status == DRSYM_SUCCESS) { - app_pc notify_fn = module->start + offset; - bool ok = drwrap_wrap(notify_fn, pre, post); - DR_ASSERT(ok); - *done = true; - } -} - -/* - * When each module (e.g. shared library) is loaded, try to wrap all - * the functions we care about. For each one, we keep a static bool - * that will stop us trying again once we've found it the first time. - */ -static void load_module( - void *drcontext, const module_data_t *module, bool loaded) -{ - bool libc = !strncmp(dr_module_preferred_name(module), "libc", 4); - -#define TRY_WRAP(fn, pre, post) do \ - { \ - static bool done_this_one = false; \ - try_wrap_fn(module, fn, pre, post, &done_this_one); \ - } while (0) - - if (loaded) { - TRY_WRAP("log_to_file_real", wrap_logsetfile, NULL); - TRY_WRAP("dry_run_real", NULL, wrap_dryrun); - if (libc) { - TRY_WRAP("malloc", wrap_malloc_pre, wrap_alloc_post); - TRY_WRAP("realloc", wrap_realloc_pre, wrap_alloc_post); - TRY_WRAP("free", wrap_free_pre, unpause_post); - TRY_WRAP("memset", wrap_memset_pre, unpause_post); - TRY_WRAP("memmove", wrap_memmove_pre, unpause_post); - - /* - * More strangely named versions of standard C library - * functions, which I've observed in practice to be where the - * calls end up. I think these are probably selected by - * STT_IFUNC in libc.so, so that the normally named version of - * the function is never reached at all. - * - * This list is not expected to be complete. If you re-run - * this test on a different platform and find control flow - * diverging inside some libc function that looks as if it's - * another name for malloc or memset or whatever, then you may - * need to add more aliases here to stop the test failing. - */ - TRY_WRAP("__GI___libc_malloc", wrap_malloc_pre, wrap_alloc_post); - TRY_WRAP("__libc_malloc", wrap_malloc_pre, wrap_alloc_post); - TRY_WRAP("__GI___libc_realloc", wrap_realloc_pre, wrap_alloc_post); - TRY_WRAP("__GI___libc_free", wrap_free_pre, unpause_post); - TRY_WRAP("__memset_sse2_unaligned", wrap_memset_pre, unpause_post); - TRY_WRAP("__memset_sse2", wrap_memset_pre, unpause_post); - TRY_WRAP("__memmove_avx_unaligned_erms", wrap_memmove_pre, - unpause_post); - TRY_WRAP("cfree", wrap_free_pre, unpause_post); - } - } -} - -/* - * Main entry point that sets up all the facilities we need. - */ -DR_EXPORT void dr_client_main(client_id_t id, int argc, const char **argv) -{ - dr_set_client_name( - "Time-sensitive activity logger for PuTTY crypto testing", - "https://www.chiark.greenend.org.uk/~sgtatham/putty/"); - - outfile = INVALID_FILE; - - bool ok = drmgr_init(); - DR_ASSERT(ok); - - /* - * Run our main instrumentation pass with lower priority than - * drwrap, so that we don't start logging the inside of a function - * whose drwrap pre-wrapper would have wanted to disable logging. - */ - drmgr_priority_t pri = {sizeof(pri), "sclog", NULL, NULL, - DRMGR_PRIORITY_INSERT_DRWRAP+1}; - ok = drmgr_register_bb_instrumentation_event( - NULL, instrument_instr, &pri); - DR_ASSERT(ok); - - ok = drutil_init(); - DR_ASSERT(ok); - - ok = drwrap_init(); - DR_ASSERT(ok); - - drsym_error_t symstatus = drsym_init(0); - DR_ASSERT(symstatus == DRSYM_SUCCESS); - - dr_register_exit_event(exit_event); - - drreg_options_t ops = { sizeof(ops), 3, false }; - drreg_status_t regstatus = drreg_init(&ops); - DR_ASSERT(regstatus == DRREG_SUCCESS); - - drmgr_register_module_load_event(load_module); - - ok = drmgr_register_bb_app2app_event(expand_rep_movsb, NULL); - DR_ASSERT(ok); -} diff --git a/test/scocols.txt b/test/scocols.txt deleted file mode 100644 index 142d662b1..000000000 --- a/test/scocols.txt +++ /dev/null @@ -1,3 +0,0 @@ -Test of (destructive) SCO colour rendering. -SCO fg: [=0F0[=7F [=1F1[=7F [=2F2[=7F [=3F3[=7F [=4F4[=7F [=5F5[=7F [=6F6[=7F [=7F7[=7F [=8F8[=7F [=9F9[=7F [=10F10[=7F [=11F11[=7F [=12F12[=7F [=13F13[=7F [=14F14[=7F [=15F15[=7F -SCO bg: [=0G0[=0G [=1G1[=0G [=2G2[=0G [=3G3[=0G [=4G4[=0G [=5G5[=0G [=6G6[=0G [=7G7[=0G [=8G8[=0G [=9G9[=0G [=10G10[=0G [=11G11[=0G [=12G12[=0G [=13G13[=0G [=14G14[=0G [=15G15[=0G diff --git a/test/ssh.py b/test/ssh.py deleted file mode 100644 index 53b151833..000000000 --- a/test/ssh.py +++ /dev/null @@ -1,109 +0,0 @@ -import sys -import struct -import itertools - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -def nbits(n): - # Mimic mp_get_nbits for ordinary Python integers. - assert 0 <= n - smax = next(s for s in itertools.count() if (n >> (1 << s)) == 0) - toret = 0 - for shift in reversed([1 << s for s in range(smax)]): - if n >> shift != 0: - n >>= shift - toret += shift - assert n <= 1 - if n == 1: - toret += 1 - return toret - -def ssh_byte(n): - return struct.pack("B", n) - -def ssh_uint32(n): - return struct.pack(">L", n) - -def ssh_uint64(n): - return struct.pack(">Q", n) - -def ssh_string(s): - return ssh_uint32(len(s)) + s - -def ssh1_mpint(x): - bits = nbits(x) - bytevals = [0xFF & (x >> (8*n)) for n in range((bits-1)//8, -1, -1)] - return struct.pack(">H" + "B" * len(bytevals), bits, *bytevals) - -def ssh2_mpint(x): - bytevals = [0xFF & (x >> (8*n)) for n in range(nbits(x)//8, -1, -1)] - return struct.pack(">L" + "B" * len(bytevals), len(bytevals), *bytevals) - -def decoder(fn): - def decode(s, return_rest = False): - item, length_consumed = fn(s) - if return_rest: - return item, s[length_consumed:] - else: - return item - return decode - -@decoder -def ssh_decode_byte(s): - return struct.unpack_from("B", s, 0)[0], 1 - -@decoder -def ssh_decode_uint32(s): - return struct.unpack_from(">L", s, 0)[0], 4 - -@decoder -def ssh_decode_uint64(s): - return struct.unpack_from(">Q", s, 0)[0], 8 - -@decoder -def ssh_decode_string(s): - length = ssh_decode_uint32(s) - assert length + 4 <= len(s) - return s[4:length+4], length+4 - -@decoder -def ssh1_get_mpint(s): # returns it unconsumed, still in wire encoding - nbits = struct.unpack_from(">H", s, 0)[0] - nbytes = (nbits + 7) // 8 - assert nbytes + 2 <= len(s) - return s[:nbytes+2], nbytes+2 - -@decoder -def ssh1_decode_mpint(s): - nbits = struct.unpack_from(">H", s, 0)[0] - nbytes = (nbits + 7) // 8 - assert nbytes + 2 <= len(s) - data = s[2:nbytes+2] - v = 0 - for b in struct.unpack("B" * len(data), data): - v = (v << 8) | b - return v, nbytes+2 - -AGENT_MAX_MSGLEN = 262144 - -SSH1_AGENTC_REQUEST_RSA_IDENTITIES = 1 -SSH1_AGENT_RSA_IDENTITIES_ANSWER = 2 -SSH1_AGENTC_RSA_CHALLENGE = 3 -SSH1_AGENT_RSA_RESPONSE = 4 -SSH1_AGENTC_ADD_RSA_IDENTITY = 7 -SSH1_AGENTC_REMOVE_RSA_IDENTITY = 8 -SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES = 9 -SSH_AGENT_FAILURE = 5 -SSH_AGENT_SUCCESS = 6 -SSH_AGENT_EXTENSION_FAILURE = 28 -SSH2_AGENTC_REQUEST_IDENTITIES = 11 -SSH2_AGENT_IDENTITIES_ANSWER = 12 -SSH2_AGENTC_SIGN_REQUEST = 13 -SSH2_AGENT_SIGN_RESPONSE = 14 -SSH2_AGENTC_ADD_IDENTITY = 17 -SSH2_AGENTC_REMOVE_IDENTITY = 18 -SSH2_AGENTC_REMOVE_ALL_IDENTITIES = 19 -SSH2_AGENTC_EXTENSION = 27 - -SSH_AGENT_RSA_SHA2_256 = 2 -SSH_AGENT_RSA_SHA2_512 = 4 diff --git a/test/test_conf.c b/test/test_conf.c deleted file mode 100644 index d9f215b30..000000000 --- a/test/test_conf.c +++ /dev/null @@ -1,938 +0,0 @@ -#define SUPERSEDE_FONTSPEC_FOR_TESTING - -#include "putty.h" -#include "storage.h" - -void modalfatalbox(const 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); -} - -char *platform_default_s(const char *name) -{ return NULL; } -bool platform_default_b(const char *name, bool def) -{ return def; } -int platform_default_i(const char *name, int def) -{ return def; } -FontSpec *platform_default_fontspec(const char *name) -{ return fontspec_new_default(); } -Filename *platform_default_filename(const char *name) -{ return filename_from_str(""); } -char *platform_get_x_display(void) { return NULL; } - -void read_random_seed(noise_consumer_t consumer) {} -void write_random_seed(void *data, int len) -{ unreachable("no random seed in this application"); } -bool have_ssh_host_key(const char *hostname, int port, - const char *keytype) { return false; } -int check_stored_host_key(const char *hostname, int port, - const char *keytype, const char *key) { return 1; } -void store_host_key(Seat *seat, const char *hostname, int port, - const char *keytype, const char *key) -{ unreachable("no actual host keys in this application"); } - -host_ca_enum *enum_host_ca_start(void) { return NULL; } -bool enum_host_ca_next(host_ca_enum *handle, strbuf *out) { return false; } -void enum_host_ca_finish(host_ca_enum *handle) {} -host_ca *host_ca_load(const char *name) { return NULL; } - -void old_keyfile_warning(void) { } - -const bool share_can_be_upstream = false; -const bool share_can_be_downstream = false; - -struct FontSpec { - char *name; -}; - -FontSpec *fontspec_new(const char *name) -{ - FontSpec *f = snew(FontSpec); - f->name = dupstr(name); - return f; -} - -FontSpec *fontspec_new_default(void) -{ - return fontspec_new(""); -} - -FontSpec *fontspec_copy(const FontSpec *f) -{ - return fontspec_new(f->name); -} - -void fontspec_free(FontSpec *f) -{ - sfree(f->name); - sfree(f); -} - -void fontspec_serialise(BinarySink *bs, FontSpec *f) -{ - put_asciz(bs, f->name); -} - -FontSpec *fontspec_deserialise(BinarySource *src) -{ - return fontspec_new(get_asciz(src)); -} - -#define MAXKEY 16 - -typedef enum { - SAVE_UNSET, SAVE_S, SAVE_I, SAVE_FONTSPEC, SAVE_FILENAME -} SaveType; - -typedef struct SaveItem { - const char *key; - SaveType type; - union { - char sval[4096]; - int ival; - }; -} SaveItem; - -struct settings_w { - size_t n; - SaveItem si[MAXKEY]; -}; - -settings_w *open_settings_w(const char *sessionname, char **errmsg) -{ return NULL; } -void close_settings_w(settings_w *sw) -{ unreachable("we don't open and close in this test program"); } -settings_r *open_settings_r(const char *sessionname) -{ return NULL; } -void close_settings_r(settings_r *sr) { } - -/* Work around lack of true snprintf before VS2015 */ -#if defined _WINDOWS && \ - !defined __MINGW32__ && \ - !defined __WINE__ && \ - _MSC_VER < 1900 -#define snprintf _snprintf -#endif - -void write_setting_s(settings_w *sw, const char *key, const char *value) -{ - for (size_t i = 0; i < sw->n; i++) { - if (!strcmp(key, sw->si[i].key)) { - sw->si[i].type = SAVE_S; - snprintf(sw->si[i].sval, sizeof(sw->si[i].sval), "%s", value); - break; - } - } -} - -void write_setting_i(settings_w *sw, const char *key, int value) -{ - for (size_t i = 0; i < sw->n; i++) { - if (!strcmp(key, sw->si[i].key)) { - sw->si[i].type = SAVE_I; - sw->si[i].ival = value; - break; - } - } -} - -void write_setting_fontspec(settings_w *sw, const char *key, FontSpec *fs) -{ - for (size_t i = 0; i < sw->n; i++) { - if (!strcmp(key, sw->si[i].key)) { - sw->si[i].type = SAVE_FONTSPEC; - snprintf(sw->si[i].sval, sizeof(sw->si[i].sval), "%s", fs->name); - break; - } - } -} - -void write_setting_filename(settings_w *sw, const char *key, Filename *fn) -{ - for (size_t i = 0; i < sw->n; i++) { - if (!strcmp(key, sw->si[i].key)) { - sw->si[i].type = SAVE_FILENAME; - snprintf(sw->si[i].sval, sizeof(sw->si[i].sval), "%s", - filename_to_str(fn)); - break; - } - } -} - -struct settings_r { - size_t n; - SaveItem si[MAXKEY]; -}; - -char *read_setting_s(settings_r *sr, const char *key) -{ - if (sr) - for (size_t i = 0; i < sr->n; i++) - if (!strcmp(key, sr->si[i].key) && sr->si[i].type == SAVE_S) - return dupstr(sr->si[i].sval); - return NULL; -} - -int read_setting_i(settings_r *sr, const char *key, int defvalue) -{ - if (sr) - for (size_t i = 0; i < sr->n; i++) - if (!strcmp(key, sr->si[i].key) && sr->si[i].type == SAVE_I) - return sr->si[i].ival; - return defvalue; -} - -FontSpec *read_setting_fontspec(settings_r *sr, const char *key) -{ - if (sr) - for (size_t i = 0; i < sr->n; i++) - if (!strcmp(key, sr->si[i].key) && sr->si[i].type == SAVE_FONTSPEC) - return fontspec_new(sr->si[i].sval); - return NULL; -} - -Filename *read_setting_filename(settings_r *sr, const char *key) -{ - if (sr) - for (size_t i = 0; i < sr->n; i++) - if (!strcmp(key, sr->si[i].key) && sr->si[i].type == SAVE_FILENAME) - return filename_from_str(sr->si[i].sval); - return NULL; -} - -void del_settings(const char *sessionname) {} - -settings_e *enum_settings_start(void) -{ return NULL; } -bool enum_settings_next(settings_e *handle, strbuf *out) -{ unreachable("where did you get a settings_e from?"); } -void enum_settings_finish(settings_e *handle) -{ unreachable("where did you get a settings_e from?"); } - -static int nfails = 0; - -void test_str_simple(int confid, const char *saveid, const char *defexp) -{ - Conf *conf = conf_new(); - - do_defaults(NULL, conf); - const char *defgot = conf_get_str(conf, confid); - if (0 != strcmp(defgot, defexp)) { - printf("fail test_str_simple(%s): default = '%s', expected '%s'\n", - saveid, defgot, defexp); - nfails++; - } - - for (int i = 0; i < 2; i++) { - settings_w sw = { - .n = 1, - .si[0].key = saveid, - .si[0].type = SAVE_UNSET, - }; - static const char *const teststrings[] = { "foo", "bar" }; - const char *teststring = teststrings[i]; - conf_set_str(conf, confid, teststring); - save_open_settings(&sw, conf); - if (sw.si[0].type != SAVE_S) { - printf("fail test_str_simple(%s): saved type = %d, expected %d\n", - saveid, sw.si[0].type, SAVE_S); - nfails++; - } else if (0 != strcmp(sw.si[0].sval, teststring)) { - printf("fail test_str_simple(%s): " - "saved string = '%s', expected '%s'\n", - saveid, sw.si[0].sval, teststring); - nfails++; - } - - conf_clear(conf); - settings_r sr = { - .n = 1, - .si[0].key = saveid, - .si[0].type = SAVE_S, - }; - snprintf(sr.si[0].sval, sizeof(sr.si[0].sval), "%s", teststring); - load_open_settings(&sr, conf); - const char *loaded = conf_get_str(conf, confid); - if (0 != strcmp(loaded, teststring)) { - printf("fail test_str_simple(%s): " - "loaded string = '%s', expected '%s'\n", - saveid, loaded, teststring); - nfails++; - } - } - - conf_free(conf); -} - -void test_int_simple(int confid, const char *saveid, int defexp) -{ - Conf *conf = conf_new(); - - do_defaults(NULL, conf); - int defgot = conf_get_int(conf, confid); - if (defgot != defexp) { - printf("fail test_int_simple(%s): default = %d, expected %d\n", - saveid, defgot, defexp); - nfails++; - } - - for (int i = 0; i < 2; i++) { - settings_w sw = { - .n = 1, - .si[0].key = saveid, - .si[0].type = SAVE_UNSET, - }; - static const int testints[] = { 12345, 54321 }; - int testint = testints[i]; - conf_set_int(conf, confid, testint); - save_open_settings(&sw, conf); - if (sw.si[0].type != SAVE_I) { - printf("fail test_int_simple(%s): saved type = %d, expected %d\n", - saveid, sw.si[0].type, SAVE_I); - nfails++; - } else if (sw.si[0].ival != testint) { - printf("fail test_int_simple(%s): " - "saved integer = %d, expected %d\n", - saveid, sw.si[0].ival, testint); - nfails++; - } - - conf_clear(conf); - settings_r sr = { - .n = 1, - .si[0].key = saveid, - .si[0].type = SAVE_I, - .si[0].ival = testint, - }; - load_open_settings(&sr, conf); - int loaded = conf_get_int(conf, confid); - if (loaded != testint) { - printf("fail test_int_simple(%s): " - "loaded integer = %d, expected %d\n", - saveid, loaded, testint); - nfails++; - } - } - - conf_free(conf); -} - -void test_int_translated_internal( - int confid, const char *saveid, bool test_save, bool test_load, - void (*load_prepare)(settings_r *), int defexp, va_list ap) -{ - Conf *conf = conf_new(); - - do_defaults(NULL, conf); - int defgot = conf_get_int(conf, confid); - if (defgot != defexp) { - printf("fail test_int_translated(%s): default = %d, expected %d\n", - saveid, defgot, defexp); - nfails++; - } - - int confval = va_arg(ap, int); - while (confval != -1) { - int storageval = va_arg(ap, int); - - if (test_save) { - settings_w sw = { - .n = 1, - .si[0].key = saveid, - .si[0].type = SAVE_UNSET, - }; - conf_set_int(conf, confid, confval); - save_open_settings(&sw, conf); - if (sw.si[0].type != SAVE_I) { - printf("fail test_int_translated(%s): " - "saved type = %d, expected %d\n", - saveid, sw.si[0].type, SAVE_I); - nfails++; - } else if (sw.si[0].ival != storageval) { - printf("fail test_int_translated(%s.%d.%d): " - "saved integer = %d, expected %d\n", - saveid, confval, storageval, sw.si[0].ival, storageval); - nfails++; - } - } - - if (test_load) { - conf_clear(conf); - settings_r sr = { - .n = 1, - .si[0].key = saveid, - .si[0].type = SAVE_I, - .si[0].ival = storageval, - }; - if (load_prepare) - load_prepare(&sr); - load_open_settings(&sr, conf); - int loaded = conf_get_int(conf, confid); - if (loaded != confval) { - printf("fail test_int_translated(%s.%d.%d): " - "loaded integer = %d, expected %d\n", - saveid, confval, storageval, loaded, confval); - nfails++; - } - } - - confval = va_arg(ap, int); - } - - conf_free(conf); -} - -void test_int_translated(int confid, const char *saveid, int defexp, ...) -{ - va_list ap; - va_start(ap, defexp); - test_int_translated_internal(confid, saveid, true, true, NULL, defexp, ap); - va_end(ap); -} - -void test_int_translated_load_legacy( - int confid, const char *saveid, void (*load_prepare)(settings_r *), - int defexp, ...) -{ - va_list ap; - va_start(ap, defexp); - test_int_translated_internal(confid, saveid, false, true, load_prepare, - defexp, ap); - va_end(ap); -} - -void test_bool_simple(int confid, const char *saveid, bool defexp) -{ - Conf *conf = conf_new(); - - do_defaults(NULL, conf); - bool defgot = conf_get_bool(conf, confid); - if (defgot != defexp) { - printf("fail test_bool_simple(%s): default = %d, expected %d\n", - saveid, defgot, defexp); - nfails++; - } - - for (int i = 0; i < 2; i++) { - settings_w sw = { - .n = 1, - .si[0].key = saveid, - .si[0].type = SAVE_UNSET, - }; - static const bool testbools[] = { false, true }; - bool testbool = testbools[i]; - conf_set_bool(conf, confid, testbool); - save_open_settings(&sw, conf); - if (sw.si[0].type != SAVE_I) { - printf("fail test_bool_simple(%s): saved type = %d, expected %d\n", - saveid, sw.si[0].type, SAVE_I); - nfails++; - } else if (sw.si[0].ival != testbool) { - printf("fail test_bool_simple(%s): " - "saved integer = %d, expected %d\n", - saveid, sw.si[0].ival, testbool); - nfails++; - } - - conf_clear(conf); - settings_r sr = { - .n = 1, - .si[0].key = saveid, - .si[0].type = SAVE_I, - .si[0].ival = testbool, - }; - load_open_settings(&sr, conf); - bool loaded = conf_get_bool(conf, confid); - if (loaded != testbool) { - printf("fail test_bool_simple(%s): " - "loaded boolean = %d, expected %d\n", - saveid, loaded, testbool); - nfails++; - } - } - - conf_free(conf); -} - -void test_file_simple(int confid, const char *saveid) -{ - Conf *conf = conf_new(); - - do_defaults(NULL, conf); - - for (int i = 0; i < 2; i++) { - settings_w sw = { - .n = 1, - .si[0].key = saveid, - .si[0].type = SAVE_UNSET, - }; - static const char *const teststrings[] = { "foo", "bar" }; - const char *teststring = teststrings[i]; - Filename *testfn = filename_from_str(teststring); - conf_set_filename(conf, confid, testfn); - filename_free(testfn); - save_open_settings(&sw, conf); - if (sw.si[0].type != SAVE_FILENAME) { - printf("fail test_file_simple(%s): saved type = %d, expected %d\n", - saveid, sw.si[0].type, SAVE_FILENAME); - nfails++; - } else if (0 != strcmp(sw.si[0].sval, teststring)) { - printf("fail test_file_simple(%s): " - "saved string = '%s', expected '%s'\n", - saveid, sw.si[0].sval, teststring); - nfails++; - } - - conf_clear(conf); - settings_r sr = { - .n = 1, - .si[0].key = saveid, - .si[0].type = SAVE_FILENAME, - }; - snprintf(sr.si[0].sval, sizeof(sr.si[0].sval), "%s", teststring); - load_open_settings(&sr, conf); - const char *loaded = filename_to_str(conf_get_filename(conf, confid)); - if (0 != strcmp(loaded, teststring)) { - printf("fail test_file_simple(%s): " - "loaded string = '%s', expected '%s'\n", - saveid, loaded, teststring); - nfails++; - } - } - - conf_free(conf); -} - -void test_font_simple(int confid, const char *saveid) -{ - Conf *conf = conf_new(); - - do_defaults(NULL, conf); - - for (int i = 0; i < 2; i++) { - settings_w sw = { - .n = 1, - .si[0].key = saveid, - .si[0].type = SAVE_UNSET, - }; - static const char *const teststrings[] = { "foo", "bar" }; - const char *teststring = teststrings[i]; - FontSpec *testfs = fontspec_new(teststring); - conf_set_fontspec(conf, confid, testfs); - fontspec_free(testfs); - save_open_settings(&sw, conf); - if (sw.si[0].type != SAVE_FONTSPEC) { - printf("fail test_font_simple(%s): saved type = %d, expected %d\n", - saveid, sw.si[0].type, SAVE_FONTSPEC); - nfails++; - } else if (0 != strcmp(sw.si[0].sval, teststring)) { - printf("fail test_font_simple(%s): " - "saved string = '%s', expected '%s'\n", - saveid, sw.si[0].sval, teststring); - nfails++; - } - - conf_clear(conf); - settings_r sr = { - .n = 1, - .si[0].key = saveid, - .si[0].type = SAVE_FONTSPEC, - }; - snprintf(sr.si[0].sval, sizeof(sr.si[0].sval), "%s", teststring); - load_open_settings(&sr, conf); - const char *loaded = conf_get_fontspec(conf, confid)->name; - if (0 != strcmp(loaded, teststring)) { - printf("fail test_file_simple(%s): " - "loaded string = '%s', expected '%s'\n", - saveid, loaded, teststring); - nfails++; - } - } - - conf_free(conf); -} - -static void load_prepare_socks4(settings_r *sr) -{ - size_t pos = sr->n++; - sr->si[pos].key = "ProxySOCKSVersion"; - sr->si[pos].type = SAVE_I; - sr->si[pos].ival = 4; -} - -void test_simple(void) -{ - test_str_simple(CONF_host, "HostName", ""); - test_int_translated(CONF_addressfamily, "AddressFamily", ADDRTYPE_UNSPEC, - ADDRTYPE_UNSPEC, 0, ADDRTYPE_IPV4, 1, - ADDRTYPE_IPV6, 2, -1); - test_bool_simple(CONF_warn_on_close, "WarnOnClose", true); - test_bool_simple(CONF_tcp_nodelay, "TCPNoDelay", true); - test_bool_simple(CONF_tcp_keepalives, "TCPKeepalives", false); - test_str_simple(CONF_loghost, "LogHost", ""); - test_str_simple(CONF_proxy_exclude_list, "ProxyExcludeList", ""); - test_bool_simple(CONF_even_proxy_localhost, "ProxyLocalhost", false); - test_str_simple(CONF_proxy_host, "ProxyHost", "proxy"); - test_int_simple(CONF_proxy_port, "ProxyPort", 80); - test_str_simple(CONF_proxy_username, "ProxyUsername", ""); - test_str_simple(CONF_proxy_password, "ProxyPassword", ""); - test_str_simple(CONF_proxy_telnet_command, "ProxyTelnetCommand", "connect %host %port\\n"); - test_int_translated(CONF_proxy_log_to_term, "ProxyLogToTerm", FORCE_OFF, - FORCE_ON, 0, FORCE_OFF, 1, AUTO, 2, -1); - test_str_simple(CONF_remote_cmd, "RemoteCommand", ""); - test_bool_simple(CONF_nopty, "NoPTY", false); - test_bool_simple(CONF_compression, "Compression", false); - test_bool_simple(CONF_ssh_prefer_known_hostkeys, "PreferKnownHostKeys", true); - test_int_simple(CONF_ssh_rekey_time, "RekeyTime", 60); - test_str_simple(CONF_ssh_rekey_data, "RekeyBytes", "1G"); - test_bool_simple(CONF_tryagent, "TryAgent", true); - test_bool_simple(CONF_agentfwd, "AgentFwd", false); - test_bool_simple(CONF_change_username, "ChangeUsername", false); - test_file_simple(CONF_keyfile, "PublicKeyFile"); - test_file_simple(CONF_detached_cert, "DetachedCertificate"); - test_str_simple(CONF_auth_plugin, "AuthPlugin", ""); - test_bool_simple(CONF_ssh2_des_cbc, "SSH2DES", false); - test_bool_simple(CONF_ssh_no_userauth, "SshNoAuth", false); - test_bool_simple(CONF_ssh_no_trivial_userauth, "SshNoTrivialAuth", false); - test_bool_simple(CONF_ssh_show_banner, "SshBanner", true); - test_bool_simple(CONF_try_tis_auth, "AuthTIS", false); - test_bool_simple(CONF_try_ki_auth, "AuthKI", true); - test_bool_simple(CONF_ssh_no_shell, "SshNoShell", false); - test_str_simple(CONF_termtype, "TerminalType", "xterm"); - test_str_simple(CONF_termspeed, "TerminalSpeed", "38400,38400"); - test_str_simple(CONF_username, "UserName", ""); - test_bool_simple(CONF_username_from_env, "UserNameFromEnvironment", false); - test_str_simple(CONF_localusername, "LocalUserName", ""); - test_bool_simple(CONF_rfc_environ, "RFCEnviron", false); - test_bool_simple(CONF_passive_telnet, "PassiveTelnet", false); - test_str_simple(CONF_serline, "SerialLine", ""); - test_int_simple(CONF_serspeed, "SerialSpeed", 9600); - test_int_simple(CONF_serdatabits, "SerialDataBits", 8); - test_int_simple(CONF_serstopbits, "SerialStopHalfbits", 2); - test_int_translated(CONF_serparity, "SerialParity", SER_PAR_NONE, - SER_PAR_NONE, 0, SER_PAR_ODD, 1, SER_PAR_EVEN, 2, - SER_PAR_MARK, 3, SER_PAR_SPACE, 4, -1); - test_int_translated(CONF_serflow, "SerialFlowControl", SER_FLOW_XONXOFF, - SER_FLOW_NONE, 0, SER_FLOW_XONXOFF, 1, - SER_FLOW_RTSCTS, 2, SER_FLOW_DSRDTR, 3, -1); - test_str_simple(CONF_supdup_location, "SUPDUPLocation", "The Internet"); - test_int_translated(CONF_supdup_ascii_set, "SUPDUPCharset", - SUPDUP_CHARSET_ASCII, - SUPDUP_CHARSET_ASCII, 0, - SUPDUP_CHARSET_ITS, 1, - SUPDUP_CHARSET_WAITS, 2, -1); - test_bool_simple(CONF_supdup_more, "SUPDUPMoreProcessing", false); - test_bool_simple(CONF_supdup_scroll, "SUPDUPScrolling", false); - test_bool_simple(CONF_bksp_is_delete, "BackspaceIsDelete", true); - test_bool_simple(CONF_rxvt_homeend, "RXVTHomeEnd", false); - test_int_translated(CONF_funky_type, "LinuxFunctionKeys", FUNKY_TILDE, - FUNKY_TILDE, 0, FUNKY_LINUX, 1, FUNKY_XTERM, 2, - FUNKY_VT400, 3, FUNKY_VT100P, 4, FUNKY_SCO, 5, - FUNKY_XTERM_216, 6, -1); - test_int_translated(CONF_sharrow_type, "ShiftedArrowKeys", - SHARROW_APPLICATION, - SHARROW_APPLICATION, 0, SHARROW_BITMAP, 1, -1); - test_bool_simple(CONF_no_applic_c, "NoApplicationCursors", false); - test_bool_simple(CONF_no_applic_k, "NoApplicationKeys", false); - test_bool_simple(CONF_no_mouse_rep, "NoMouseReporting", false); - test_bool_simple(CONF_no_remote_resize, "NoRemoteResize", false); - test_bool_simple(CONF_no_alt_screen, "NoAltScreen", false); - test_bool_simple(CONF_no_remote_wintitle, "NoRemoteWinTitle", false); - test_bool_simple(CONF_no_remote_clearscroll, "NoRemoteClearScroll", false); - test_bool_simple(CONF_no_dbackspace, "NoDBackspace", false); - test_bool_simple(CONF_no_remote_charset, "NoRemoteCharset", false); - /* note we have no test for CONF_remote_qtitle_action because no default */ - test_bool_simple(CONF_app_cursor, "ApplicationCursorKeys", false); - test_bool_simple(CONF_app_keypad, "ApplicationKeypad", false); - test_bool_simple(CONF_nethack_keypad, "NetHackKeypad", false); - test_bool_simple(CONF_telnet_keyboard, "TelnetKey", false); - test_bool_simple(CONF_telnet_newline, "TelnetRet", true); - test_bool_simple(CONF_alt_f4, "AltF4", true); - test_bool_simple(CONF_alt_space, "AltSpace", false); - test_bool_simple(CONF_alt_only, "AltOnly", false); - test_int_translated(CONF_localecho, "LocalEcho", AUTO, - FORCE_ON, 0, FORCE_OFF, 1, AUTO, 2, -1); - test_int_translated(CONF_localedit, "LocalEdit", AUTO, - FORCE_ON, 0, FORCE_OFF, 1, AUTO, 2, -1); - test_bool_simple(CONF_alwaysontop, "AlwaysOnTop", false); - test_bool_simple(CONF_fullscreenonaltenter, "FullScreenOnAltEnter", false); - test_bool_simple(CONF_scroll_on_key, "ScrollOnKey", false); - test_bool_simple(CONF_scroll_on_disp, "ScrollOnDisp", true); - test_bool_simple(CONF_erase_to_scrollback, "EraseToScrollback", true); - test_bool_simple(CONF_compose_key, "ComposeKey", false); - test_bool_simple(CONF_ctrlaltkeys, "CtrlAltKeys", true); - test_str_simple(CONF_wintitle, "WinTitle", ""); - test_int_simple(CONF_savelines, "ScrollbackLines", 2000); - test_bool_simple(CONF_dec_om, "DECOriginMode", false); - test_bool_simple(CONF_wrap_mode, "AutoWrapMode", true); - test_bool_simple(CONF_lfhascr, "LFImpliesCR", false); - test_int_translated(CONF_cursor_type, "CurType", CURSOR_BLOCK, - CURSOR_BLOCK, 0, CURSOR_UNDERLINE, 1, - CURSOR_VERTICAL_LINE, 2, -1); - test_bool_simple(CONF_blink_cur, "BlinkCur", false); - test_int_translated(CONF_beep, "Beep", 1, - BELL_DISABLED, 0, BELL_DEFAULT, 1, BELL_VISUAL, 2, - BELL_WAVEFILE, 3, BELL_PCSPEAKER, 4, -1); - test_int_translated(CONF_beep_ind, "BeepInd", 0, - B_IND_DISABLED, 0, B_IND_FLASH, 1, B_IND_STEADY, 2, -1); - test_bool_simple(CONF_bellovl, "BellOverload", true); - test_int_simple(CONF_bellovl_n, "BellOverloadN", 5); - test_file_simple(CONF_bell_wavefile, "BellWaveFile"); - test_bool_simple(CONF_scrollbar, "ScrollBar", true); - test_bool_simple(CONF_scrollbar_in_fullscreen, "ScrollBarFullScreen", false); - test_int_translated(CONF_resize_action, "LockSize", RESIZE_TERM, - RESIZE_TERM, 0, RESIZE_DISABLED, 1, RESIZE_FONT, 2, - RESIZE_EITHER, 3, -1); - test_bool_simple(CONF_bce, "BCE", true); - test_bool_simple(CONF_blinktext, "BlinkText", false); - test_bool_simple(CONF_win_name_always, "WinNameAlways", true); - test_int_simple(CONF_width, "TermWidth", 80); - test_int_simple(CONF_height, "TermHeight", 24); - test_font_simple(CONF_font, "Font"); - test_int_translated(CONF_font_quality, "FontQuality", FQ_DEFAULT, - FQ_DEFAULT, 0, FQ_ANTIALIASED, 1, FQ_NONANTIALIASED, 2, - FQ_CLEARTYPE, 3, -1); - test_file_simple(CONF_logfilename, "LogFileName"); - test_int_translated(CONF_logtype, "LogType", LGTYP_NONE, - LGTYP_NONE, 0, LGTYP_ASCII, 1, LGTYP_DEBUG, 2, - LGTYP_PACKETS, 3, LGTYP_SSHRAW, 4, -1); - /* FIXME: this won't work because -1 is also the terminator, darn */ - test_int_translated(CONF_logxfovr, "LogFileClash", LGXF_ASK, - LGXF_OVR, 1, LGXF_APN, 0, LGXF_ASK, -1, -1); - test_bool_simple(CONF_logflush, "LogFlush", true); - test_bool_simple(CONF_logheader, "LogHeader", true); - test_bool_simple(CONF_logomitpass, "SSHLogOmitPasswords", true); - test_bool_simple(CONF_logomitdata, "SSHLogOmitData", false); - test_bool_simple(CONF_hide_mouseptr, "HideMousePtr", false); - test_bool_simple(CONF_sunken_edge, "SunkenEdge", false); - test_int_simple(CONF_window_border, "WindowBorder", 1); - test_str_simple(CONF_answerback, "Answerback", "PuTTY"); - test_str_simple(CONF_printer, "Printer", ""); - test_bool_simple(CONF_no_arabicshaping, "DisableArabicShaping", false); - test_bool_simple(CONF_no_bidi, "DisableBidi", false); - test_bool_simple(CONF_ansi_colour, "ANSIColour", true); - test_bool_simple(CONF_xterm_256_colour, "Xterm256Colour", true); - test_bool_simple(CONF_true_colour, "TrueColour", true); - test_bool_simple(CONF_system_colour, "UseSystemColours", false); - test_bool_simple(CONF_try_palette, "TryPalette", false); - test_int_translated(CONF_mouse_is_xterm, "MouseIsXterm", 0, - MOUSE_COMPROMISE, 0, MOUSE_XTERM, 1, - MOUSE_WINDOWS, 2, -1); - test_bool_simple(CONF_rect_select, "RectSelect", false); - test_bool_simple(CONF_paste_controls, "PasteControls", false); - test_bool_simple(CONF_rawcnp, "RawCNP", false); - test_bool_simple(CONF_utf8linedraw, "UTF8linedraw", false); - test_bool_simple(CONF_rtf_paste, "PasteRTF", false); - test_bool_simple(CONF_mouse_override, "MouseOverride", true); - test_bool_simple(CONF_mouseautocopy, "MouseAutocopy", CLIPUI_DEFAULT_AUTOCOPY); - test_int_translated(CONF_vtmode, "FontVTMode", VT_UNICODE, - VT_XWINDOWS, 0, - VT_OEMANSI, 1, - VT_OEMONLY, 2, - VT_POORMAN, 3, - VT_UNICODE, 4, - -1); - test_str_simple(CONF_line_codepage, "LineCodePage", ""); - test_bool_simple(CONF_cjk_ambig_wide, "CJKAmbigWide", false); - test_bool_simple(CONF_utf8_override, "UTF8Override", true); - test_bool_simple(CONF_xlat_capslockcyr, "CapsLockCyr", false); - test_bool_simple(CONF_x11_forward, "X11Forward", false); - test_str_simple(CONF_x11_display, "X11Display", ""); - test_int_translated(CONF_x11_auth, "X11AuthType", X11_MIT, - X11_NO_AUTH, 0, X11_MIT, 1, X11_XDM, 2, -1); - test_file_simple(CONF_xauthfile, "X11AuthFile"); - test_bool_simple(CONF_lport_acceptall, "LocalPortAcceptAll", false); - test_bool_simple(CONF_rport_acceptall, "RemotePortAcceptAll", false); - test_bool_simple(CONF_ssh_connection_sharing, "ConnectionSharing", false); - test_bool_simple(CONF_ssh_connection_sharing_upstream, "ConnectionSharingUpstream", true); - test_bool_simple(CONF_ssh_connection_sharing_downstream, "ConnectionSharingDownstream", true); - test_bool_simple(CONF_stamp_utmp, "StampUtmp", true); - test_bool_simple(CONF_login_shell, "LoginShell", true); - test_bool_simple(CONF_scrollbar_on_left, "ScrollbarOnLeft", false); - test_bool_simple(CONF_shadowbold, "ShadowBold", false); - test_font_simple(CONF_boldfont, "BoldFont"); - test_font_simple(CONF_widefont, "WideFont"); - test_font_simple(CONF_wideboldfont, "WideBoldFont"); - test_int_simple(CONF_shadowboldoffset, "ShadowBoldOffset", 1); - test_bool_simple(CONF_crhaslf, "CRImpliesLF", false); - test_str_simple(CONF_winclass, "WindowClass", ""); - test_int_translated(CONF_close_on_exit, "CloseOnExit", AUTO, - FORCE_OFF, 0, AUTO, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_proxy_dns, "ProxyDNS", AUTO, - FORCE_OFF, 0, AUTO, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_bold_style, "BoldAsColour", AUTO, - 1, 0, 2, 1, 3, 2, -1); - test_int_translated(CONF_sshbug_ignore1, "BugIgnore1", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_plainpw1, "BugPlainPW1", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_rsa1, "BugRSA1", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_ignore2, "BugIgnore2", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_derivekey2, "BugDeriveKey2", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_rsapad2, "BugRSAPad2", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_pksessid2, "BugPKSessID2", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_rekey2, "BugRekey2", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_maxpkt2, "BugMaxPkt2", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_oldgex2, "BugOldGex2", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_winadj, "BugWinadj", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_chanreq, "BugChanReq", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_dropstart, "BugDropStart", FORCE_OFF, - FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_filter_kexinit, "BugFilterKexinit", FORCE_OFF, - FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_sshbug_rsa_sha2_cert_userauth, "BugRSASHA2CertUserauth", AUTO, - AUTO, 0, FORCE_OFF, 1, FORCE_ON, 2, -1); - test_int_translated(CONF_proxy_type, "ProxyMethod", PROXY_NONE, - PROXY_NONE, 0, PROXY_SOCKS4, 1, PROXY_SOCKS5, 2, - PROXY_HTTP, 3, PROXY_TELNET, 4, PROXY_CMD, 5, - PROXY_SSH_TCPIP, 6, PROXY_SSH_EXEC, 7, - PROXY_SSH_SUBSYSTEM, 8, -1); - test_int_translated_load_legacy( - CONF_proxy_type, "ProxyType", NULL, PROXY_NONE, - PROXY_HTTP, 1, PROXY_SOCKS5, 2, PROXY_TELNET, 3, PROXY_CMD, 4, -1); - test_int_translated_load_legacy( - CONF_proxy_type, "ProxyType", load_prepare_socks4, PROXY_NONE, - PROXY_HTTP, 1, PROXY_SOCKS4, 2, PROXY_TELNET, 3, PROXY_CMD, 4, -1); - test_int_translated(CONF_remote_qtitle_action, "RemoteQTitleAction", TITLE_EMPTY, - TITLE_NONE, 0, TITLE_EMPTY, 1, TITLE_REAL, 2, -1); - test_int_translated_load_legacy( - CONF_remote_qtitle_action, "NoRemoteQTitle", NULL, TITLE_EMPTY, - TITLE_REAL, 0, TITLE_EMPTY, 1, -1); -} - -void test_conf_key_info(void) -{ - struct test_data { - const char *name; - bool got_value_type : 1; - bool got_subkey_type : 1; - bool got_default : 1; - bool got_default_int : 1; - bool got_default_str : 1; - bool got_default_bool : 1; - bool got_save_keyword : 1; - bool got_storage_enum : 1; - bool save_custom : 1; - bool load_custom : 1; - bool not_saved : 1; - }; - -#define CONF_OPTION(id, ...) { .name = "CONF_" #id, __VA_ARGS__ }, -#define VALUE_TYPE(x) .got_value_type = true -#define SUBKEY_TYPE(x) .got_subkey_type = true -#define DEFAULT_INT(x) .got_default_int = true, .got_default = true -#define DEFAULT_STR(x) .got_default_str = true, .got_default = true -#define DEFAULT_BOOL(x) .got_default_bool = true, .got_default = true -#define SAVE_KEYWORD(x) .got_save_keyword = true -#define STORAGE_ENUM(x) .got_storage_enum = true -#define SAVE_CUSTOM .save_custom = true -#define LOAD_CUSTOM .load_custom = true -#define NOT_SAVED .not_saved = true - - static const struct test_data conf_key_test_data[] = { - #include "conf.h" - }; - - for (size_t key = 0; key < N_CONFIG_OPTIONS; key++) { - const ConfKeyInfo *info = &conf_key_info[key]; - const struct test_data *td = &conf_key_test_data[key]; - - if (!td->got_value_type) { - fprintf(stderr, "%s: no value type\n", td->name); - nfails++; - } - - if (td->got_default && info->subkey_type != CONF_TYPE_NONE) { - fprintf(stderr, "%s: is a mapping but has a default\n", td->name); - nfails++; - } - - if ((td->got_default_int && info->value_type != CONF_TYPE_INT) || - (td->got_default_str && info->value_type != CONF_TYPE_STR) || - (td->got_default_bool && info->value_type != CONF_TYPE_BOOL)) { - fprintf(stderr, "%s: default doesn't match type\n", td->name); - nfails++; - } - - if (td->got_storage_enum && info->value_type != CONF_TYPE_INT) { - fprintf(stderr, "%s: has STORAGE_ENUM but isn't an int\n", - td->name); - nfails++; - } - - if (td->not_saved) { - if (!td->got_default && info->subkey_type == CONF_TYPE_NONE) { - fprintf(stderr, "%s: simple unsaved setting but has no " - "default\n", td->name); - nfails++; - } - - if (td->got_save_keyword) { - fprintf(stderr, "%s: not saved but has SAVE_KEYWORD\n", - td->name); - nfails++; - } - - if (td->save_custom) { - fprintf(stderr, "%s: not saved but has SAVE_CUSTOM\n", - td->name); - nfails++; - } - - if (td->load_custom) { - fprintf(stderr, "%s: not saved but has LOAD_CUSTOM\n", - td->name); - nfails++; - } - - if (td->got_storage_enum) { - fprintf(stderr, "%s: not saved but has STORAGE_ENUM\n", - td->name); - nfails++; - } - - } else { - if (td->load_custom && td->save_custom) { - if (td->got_save_keyword) { - fprintf(stderr, "%s: no automatic save or load but has " - "SAVE_KEYWORD\n", td->name); - nfails++; - } - - if (td->got_storage_enum) { - fprintf(stderr, "%s: no automatic save or load but has " - "STORAGE_ENUM\n", td->name); - nfails++; - } - } else { - if (!td->got_save_keyword) { - fprintf(stderr, "%s: missing SAVE_KEYWORD\n", td->name); - nfails++; - } - } - } - } -} - -int main(void) -{ - test_conf_key_info(); - test_simple(); - return nfails != 0; -} diff --git a/test/test_lineedit.c b/test/test_lineedit.c deleted file mode 100644 index fe0be1620..000000000 --- a/test/test_lineedit.c +++ /dev/null @@ -1,773 +0,0 @@ -#include "putty.h" -#include "terminal.h" - -void modalfatalbox(const 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); -} - -const char *const appname = "test_lineedit"; - -char *platform_default_s(const char *name) -{ return NULL; } -bool platform_default_b(const char *name, bool def) -{ return def; } -int platform_default_i(const char *name, int def) -{ return def; } -FontSpec *platform_default_fontspec(const char *name) -{ return fontspec_new_default(); } -Filename *platform_default_filename(const char *name) -{ return filename_from_str(""); } - -struct SpecialRecord { - SessionSpecialCode code; - int arg; -}; - -typedef struct Mock { - Terminal *term; - Ldisc *ldisc; - Conf *conf; - struct unicode_data ucsdata[1]; - - bool echo, edit; - strbuf *to_terminal, *to_backend; - - struct SpecialRecord *specials; - size_t nspecials, specialsize; - - strbuf *context; /* for printing in failed tests */ - - bool any_test_failed; - - TermWin tw; - Seat seat; - Backend backend; -} Mock; - -static size_t mock_output(Seat *seat, SeatOutputType type, - const void *data, size_t len) -{ - Mock *mk = container_of(seat, Mock, seat); - put_data(mk->to_terminal, data, len); - return 0; -} - -static void mock_send(Backend *be, const char *buf, size_t len) -{ - Mock *mk = container_of(be, Mock, backend); - put_data(mk->to_backend, buf, len); -} - -static void mock_special(Backend *be, SessionSpecialCode code, int arg) -{ - Mock *mk = container_of(be, Mock, backend); - sgrowarray(mk->specials, mk->specialsize, mk->nspecials); - mk->specials[mk->nspecials].code = code; - mk->specials[mk->nspecials].arg = arg; - mk->nspecials++; -} - -static bool mock_ldisc_option_state(Backend *be, int option) -{ - Mock *mk = container_of(be, Mock, backend); - switch (option) { - case LD_ECHO: - return mk->echo; - case LD_EDIT: - return mk->edit; - default: - unreachable("bad ldisc option"); - } -} - -static void mock_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - Mock *mk = container_of(be, Mock, backend); - mk->ldisc = ldisc; -} - -static bool mock_sendok(Backend *be) -{ - Mock *mk = container_of(be, Mock, backend); - (void)mk; - /* FIXME: perhaps make this settable, to test the input_queue system? */ - return true; -} - -static void mock_set_raw_mouse_mode(TermWin *win, bool enable) {} -static void mock_palette_get_overrides(TermWin *tw, Terminal *term) {} - -static const TermWinVtable mock_termwin_vt = { - .set_raw_mouse_mode = mock_set_raw_mouse_mode, - .palette_get_overrides = mock_palette_get_overrides, -}; - -static const SeatVtable mock_seat_vt = { - .output = mock_output, - .echoedit_update = nullseat_echoedit_update, -}; - -static const BackendVtable mock_backend_vt = { - .sendok = mock_sendok, - .send = mock_send, - .special = mock_special, - .ldisc_option_state = mock_ldisc_option_state, - .provide_ldisc = mock_provide_ldisc, - .id = "mock", -}; - -static Mock *mock_new(void) -{ - Mock *mk = snew(Mock); - memset(mk, 0, sizeof(*mk)); - - mk->conf = conf_new(); - do_defaults(NULL, mk->conf); - - init_ucs_generic(mk->conf, mk->ucsdata); - mk->ucsdata->line_codepage = CP_437; - - mk->context = strbuf_new(); - mk->to_terminal = strbuf_new(); - mk->to_backend = strbuf_new(); - - mk->tw.vt = &mock_termwin_vt; - mk->seat.vt = &mock_seat_vt; - mk->backend.vt = &mock_backend_vt; - - return mk; -} - -static void mock_free(Mock *mk) -{ - strbuf_free(mk->context); - strbuf_free(mk->to_terminal); - strbuf_free(mk->to_backend); - conf_free(mk->conf); - term_free(mk->term); - sfree(mk->specials); - sfree(mk); -} - -static void reset(Mock *mk) -{ - strbuf_clear(mk->context); - strbuf_clear(mk->to_terminal); - strbuf_clear(mk->to_backend); - mk->nspecials = 0; -} - -static void test_context(Mock *mk, const char *fmt, ...) -{ - strbuf_clear(mk->context); - va_list ap; - va_start(ap, fmt); - put_fmtv(mk->context, fmt, ap); - va_end(ap); -} - -static void print_context(Mock *mk, const char *file, int line) -{ - printf("%s:%d", file, line); - if (mk->context->len) - printf(" (%s)", mk->context->s); - printf(": "); -} - -#define EXPECT(mk, what, ...) \ - expect_ ## what(mk, __FILE__, __LINE__, __VA_ARGS__) - -static void expect_backend(Mock *mk, const char *file, int line, - ptrlen expected) -{ - ptrlen actual = ptrlen_from_strbuf(mk->to_backend); - if (!ptrlen_eq_ptrlen(expected, actual)) { - print_context(mk, file, line); - printf("expected backend output \""); - write_c_string_literal(stdout, expected); - printf("\", got \""); - write_c_string_literal(stdout, actual); - printf("\"\n"); - mk->any_test_failed = true; - } -} - -static void expect_terminal(Mock *mk, const char *file, int line, - ptrlen expected) -{ - ptrlen actual = ptrlen_from_strbuf(mk->to_terminal); - if (!ptrlen_eq_ptrlen(expected, actual)) { - print_context(mk, file, line); - printf("expected terminal output \""); - write_c_string_literal(stdout, expected); - printf("\", got \""); - write_c_string_literal(stdout, actual); - printf("\"\n"); - mk->any_test_failed = true; - } -} - -static void expect_specials(Mock *mk, const char *file, int line, - size_t nspecials, ...) -{ - va_list ap; - - static const char *const special_names[] = { -#define SPECIAL(x) #x, -#include "specials.h" -#undef SPECIAL - }; - - bool match; - if (nspecials != mk->nspecials) { - match = false; - } else { - match = true; - va_start(ap, nspecials); - for (size_t i = 0; i < nspecials; i++) { - SessionSpecialCode code = va_arg(ap, SessionSpecialCode); - int arg = va_arg(ap, int); - if (code != mk->specials[i].code || arg != mk->specials[i].arg) - match = false; - } - va_end(ap); - } - - if (!match) { - print_context(mk, file, line); - printf("expected specials ["); - va_start(ap, nspecials); - for (size_t i = 0; i < nspecials; i++) { - SessionSpecialCode code = va_arg(ap, SessionSpecialCode); - int arg = va_arg(ap, int); - printf(" %s.%d", special_names[code], arg); - } - va_end(ap); - printf(" ], got ["); - for (size_t i = 0; i < mk->nspecials; i++) { - printf(" %s.%d", special_names[mk->specials[i].code], - mk->specials[i].arg); - } - printf(" ]\n"); - mk->any_test_failed = true; - } -} - -static void test_noedit(Mock *mk) -{ - mk->edit = false; - mk->echo = false; - - /* - * In non-echo and non-edit mode, the channel is 8-bit clean - */ - for (unsigned c = 0; c < 256; c++) { - char buf[1]; - - test_context(mk, "c=%02x", c); - buf[0] = c; - ldisc_send(mk->ldisc, buf, 1, false); - EXPECT(mk, backend, make_ptrlen(buf, 1)); - reset(mk); - } - /* ... regardless of the 'interactive' flag */ - for (unsigned c = 0; c < 256; c++) { - char buf[1]; - - test_context(mk, "c=%02x", c); - buf[0] = c; - ldisc_send(mk->ldisc, buf, 1, true); - EXPECT(mk, backend, make_ptrlen(buf, 1)); - reset(mk); - } - /* ... and any nonzero character does the same thing even if sent - * with the magic -1 length flag */ - for (unsigned c = 1; c < 256; c++) { - char buf[2]; - - test_context(mk, "c=%02x", c); - buf[0] = c; - buf[1] = '\0'; - ldisc_send(mk->ldisc, buf, -1, true); - EXPECT(mk, backend, make_ptrlen(buf, 1)); - reset(mk); - } - - /* - * Test the special-character cases for Telnet. - */ - conf_set_int(mk->conf, CONF_protocol, PROT_TELNET); - conf_set_bool(mk->conf, CONF_telnet_newline, false); - conf_set_bool(mk->conf, CONF_telnet_keyboard, false); - ldisc_configure(mk->ldisc, mk->conf); - - /* Without telnet_newline or telnet_keyboard, these all do the - * normal thing */ - ldisc_send(mk->ldisc, "\x0D", -1, true); - EXPECT(mk, backend, PTRLEN_LITERAL("\x0D")); - reset(mk); - ldisc_send(mk->ldisc, "\x08", -1, true); - EXPECT(mk, backend, PTRLEN_LITERAL("\x08")); - reset(mk); - ldisc_send(mk->ldisc, "\x7F", -1, true); - EXPECT(mk, backend, PTRLEN_LITERAL("\x7F")); - reset(mk); - ldisc_send(mk->ldisc, "\x03", -1, true); - EXPECT(mk, backend, PTRLEN_LITERAL("\x03")); - reset(mk); - ldisc_send(mk->ldisc, "\x1A", -1, true); - EXPECT(mk, backend, PTRLEN_LITERAL("\x1A")); - reset(mk); - - /* telnet_newline controls translation of CR into SS_EOL */ - conf_set_bool(mk->conf, CONF_telnet_newline, true); - ldisc_configure(mk->ldisc, mk->conf); - ldisc_send(mk->ldisc, "\x0D", -1, true); - EXPECT(mk, specials, 1, SS_EOL, 0); - reset(mk); - - /* And telnet_keyboard controls the others */ - conf_set_bool(mk->conf, CONF_telnet_newline, false); - conf_set_bool(mk->conf, CONF_telnet_keyboard, true); - ldisc_configure(mk->ldisc, mk->conf); - ldisc_send(mk->ldisc, "\x08", -1, true); - EXPECT(mk, specials, 1, SS_EC, 0); - reset(mk); - ldisc_send(mk->ldisc, "\x7F", -1, true); - EXPECT(mk, specials, 1, SS_EC, 0); - reset(mk); - ldisc_send(mk->ldisc, "\x03", -1, true); - EXPECT(mk, specials, 1, SS_IP, 0); - reset(mk); - ldisc_send(mk->ldisc, "\x1A", -1, true); - EXPECT(mk, specials, 1, SS_SUSP, 0); - reset(mk); - - /* - * In echo-but-no-edit mode, we also expect that every character - * is echoed back to the display as a side effect, including when - * it's sent as a special -1 keystroke. - * - * This state only comes up in Telnet, because that has protocol - * options to independently configure echo and edit. Telnet is - * also the most complicated of the protocols because of the above - * special cases, so we stay in Telnet mode for this test. - */ - mk->echo = true; - for (unsigned c = 0; c < 256; c++) { - char buf[1]; - - test_context(mk, "c=%02x", c); - buf[0] = c; - ldisc_send(mk->ldisc, buf, 1, false); - EXPECT(mk, terminal, make_ptrlen(buf, 1)); - reset(mk); - } - for (unsigned c = 1; c < 256; c++) { - char buf[2]; - - test_context(mk, "c=%02x", c); - buf[0] = c; - buf[1] = '\0'; - ldisc_send(mk->ldisc, buf, -1, true); - EXPECT(mk, terminal, make_ptrlen(buf, 1)); - reset(mk); - } - - do_defaults(NULL, mk->conf); - ldisc_configure(mk->ldisc, mk->conf); -} - -static void test_edit(Mock *mk, bool echo) -{ - static const char *const ctls = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"; - - mk->edit = true; - mk->echo = echo; - -#define EXPECT_TERMINAL(mk, val) do { \ - if (!echo) EXPECT(mk, terminal, PTRLEN_LITERAL("")); \ - else EXPECT(mk, terminal, val); \ - } while (0) - - /* ASCII printing characters all print when entered, but don't go - * to the terminal until Return is pressed */ - for (unsigned c = 0x20; c < 0x7F; c++) { - char buf[3]; - - test_context(mk, "c=%02x", c); - buf[0] = c; - ldisc_send(mk->ldisc, buf, 1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, make_ptrlen(buf, 1)); - ldisc_send(mk->ldisc, "\015", 1, false); - buf[1] = '\015'; - buf[2] = '\012'; - EXPECT(mk, backend, make_ptrlen(buf, 3)); - EXPECT_TERMINAL(mk, make_ptrlen(buf, 3)); - reset(mk); - } - - /* C0 control characters mostly show up as ^X or similar */ - for (unsigned c = 0; c < 0x1F; c++) { - char backbuf[3]; - char termbuf[4]; - - switch (ctls[c]) { - case 'D': continue; /* ^D sends EOF */ - case 'M': continue; /* ^M is Return */ - case 'R': continue; /* ^R redisplays line */ - case 'U': continue; /* ^U deletes the line */ - case 'V': continue; /* ^V makes the next char literal */ - case 'W': continue; /* ^W deletes a word */ - /* - * ^H / ^? are not included here. Those are treated - * literally if sent as plain input bytes. Only sending - * them as special via length==-1 causes them to act as - * backspace, which I think was simply because there _is_ - * a dedicated key that can do that function, so there's - * no need to also eat the Ctrl+thing combo. - */ - - /* - * Also, ^C, ^Z and ^\ self-insert (when not in Telnet - * mode) but have the side effect of erasing the line - * buffer so far. In this loop, that doesn't show up, - * because the line buffer is empty already. However, I - * don't test that, because it's silly, and probably - * doesn't want to keep happening! - */ - } - - test_context(mk, "c=%02x", c); - backbuf[0] = c; - ldisc_send(mk->ldisc, backbuf, 1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - termbuf[0] = '^'; - termbuf[1] = ctls[c]; - EXPECT_TERMINAL(mk, make_ptrlen(termbuf, 2)); - ldisc_send(mk->ldisc, "\015", 1, false); - backbuf[1] = '\015'; - backbuf[2] = '\012'; - EXPECT(mk, backend, make_ptrlen(backbuf, 3)); - termbuf[2] = '\015'; - termbuf[3] = '\012'; - EXPECT_TERMINAL(mk, make_ptrlen(termbuf, 4)); - reset(mk); - } - - /* Prefixed with ^V, the same is true of _all_ C0 controls */ - for (unsigned c = 0; c < 0x1F; c++) { - char backbuf[3]; - char termbuf[4]; - - test_context(mk, "c=%02x", c); - backbuf[0] = 'V' & 0x1F; - ldisc_send(mk->ldisc, backbuf, 1, false); - backbuf[0] = c; - ldisc_send(mk->ldisc, backbuf, 1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - termbuf[0] = '^'; - termbuf[1] = ctls[c]; - EXPECT_TERMINAL(mk, make_ptrlen(termbuf, 2)); - ldisc_send(mk->ldisc, "\015", 1, false); - backbuf[1] = '\015'; - backbuf[2] = '\012'; - EXPECT(mk, backend, make_ptrlen(backbuf, 3)); - termbuf[2] = '\015'; - termbuf[3] = '\012'; - EXPECT_TERMINAL(mk, make_ptrlen(termbuf, 4)); - reset(mk); - } - - /* Deleting an ASCII character sends a single BSB and deletes just - * that byte from the buffer */ - ldisc_send(mk->ldisc, "ab", 2, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("ab")); - ldisc_send(mk->ldisc, "\x08", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("ab\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("a\x0D\x0A")); - reset(mk); - - /* Deleting a character written as a ^X code sends two BSBs to - * wipe out the two-character display sequence */ - ldisc_send(mk->ldisc, "a\x02", 2, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^B")); - ldisc_send(mk->ldisc, "\x7F", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^B\x08 \x08\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("a\x0D\x0A")); - reset(mk); - - /* ^D sends the line editing buffer without a trailing Return, if - * it's non-empty */ - ldisc_send(mk->ldisc, "abc\x04", 4, false); - EXPECT(mk, backend, PTRLEN_LITERAL("abc")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("abc\x0D\x0A")); - reset(mk); - - /* But if the buffer is empty, ^D sends SS_EOF */ - ldisc_send(mk->ldisc, "\x04", 1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("")); - EXPECT(mk, specials, 1, SS_EOF, 0); - reset(mk); - - /* ^R redraws the current line, after printing "^R" at the end of - * the previous attempt to make it clear that that's what - * happened */ - ldisc_send(mk->ldisc, "a\x01", 2, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^A")); - ldisc_send(mk->ldisc, "\x12", 1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^A^R\x0D\x0A" "a^A")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - reset(mk); - - /* ^U deletes the whole line */ - ldisc_send(mk->ldisc, "a b c", 5, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a b c")); - ldisc_send(mk->ldisc, "\x15", 1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL( - mk, PTRLEN_LITERAL( - "a b c\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("\x0D\x0A")); - EXPECT_TERMINAL( - mk, PTRLEN_LITERAL( - "a b c\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x0D\x0A")); - reset(mk); - /* And it still knows that a control character written as ^X takes - * two BSBs to delete */ - ldisc_send(mk->ldisc, "a\x02" "c", 3, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^Bc")); - ldisc_send(mk->ldisc, "\x15", 1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL( - mk, PTRLEN_LITERAL("a^Bc\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - reset(mk); - - /* ^W deletes a word, which means that it deletes to the most - * recent boundary with a space on the left and a nonspace on the - * right. (Or the beginning of the string, whichever comes first.) */ - ldisc_send(mk->ldisc, "hello, world\x17", 13, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL( - mk, PTRLEN_LITERAL( - "hello, world\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", 1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("hello, \x0D\x0A")); - reset(mk); - ldisc_send(mk->ldisc, "hello, world \x17", 14, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL( - mk, PTRLEN_LITERAL( - "hello, world " - "\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", 1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("hello, \x0D\x0A")); - reset(mk); - ldisc_send(mk->ldisc, " hello \x17", 8, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL( - mk, PTRLEN_LITERAL( - " hello \x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", 1, false); - EXPECT(mk, backend, PTRLEN_LITERAL(" \x0D\x0A")); - reset(mk); - ldisc_send(mk->ldisc, "hello \x17", 7, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL( - mk, PTRLEN_LITERAL( - "hello \x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", 1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("\x0D\x0A")); - reset(mk); - /* And this too knows that a control character written as ^X takes - * two BSBs to delete */ - ldisc_send(mk->ldisc, "a\x02" "c", 3, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("a^Bc")); - ldisc_send(mk->ldisc, "\x17", 1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL( - mk, PTRLEN_LITERAL("a^Bc\x08 \x08\x08 \x08\x08 \x08\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - reset(mk); - - /* Test handling of ^C and friends in non-telnet_keyboard mode */ - ldisc_send(mk->ldisc, "abc\x03", 4, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08^C")); - EXPECT(mk, specials, 1, SS_EL, 0); - ldisc_send(mk->ldisc, "\x0D", -1, false); - reset(mk); - ldisc_send(mk->ldisc, "abc\x1a", 4, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08^Z")); - EXPECT(mk, specials, 1, SS_EL, 0); - ldisc_send(mk->ldisc, "\x0D", -1, false); - reset(mk); - ldisc_send(mk->ldisc, "abc\x1c", 4, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08^\\")); - EXPECT(mk, specials, 1, SS_EL, 0); - ldisc_send(mk->ldisc, "\x0D", -1, false); - reset(mk); - - /* And in telnet_keyboard mode */ - conf_set_bool(mk->conf, CONF_telnet_keyboard, true); - ldisc_configure(mk->ldisc, mk->conf); - - /* FIXME: should we _really_ be sending EL before each of these? */ - ldisc_send(mk->ldisc, "abc\x03", 4, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08")); - EXPECT(mk, specials, 2, SS_EL, 0, SS_IP, 0); - ldisc_send(mk->ldisc, "\x0D", -1, false); - reset(mk); - ldisc_send(mk->ldisc, "abc\x1a", 4, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08")); - EXPECT(mk, specials, 2, SS_EL, 0, SS_SUSP, 0); - ldisc_send(mk->ldisc, "\x0D", -1, false); - reset(mk); - ldisc_send(mk->ldisc, "abc\x1c", 4, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("abc\x08 \x08\x08 \x08\x08 \x08")); - EXPECT(mk, specials, 2, SS_EL, 0, SS_ABORT, 0); - ldisc_send(mk->ldisc, "\x0D", -1, false); - reset(mk); - - conf_set_bool(mk->conf, CONF_telnet_keyboard, false); - ldisc_configure(mk->ldisc, mk->conf); - - /* Test UTF-8 characters of various lengths and ensure deleting - * one deletes the whole character from the buffer (by pressing - * Return and seeing what gets sent) but sends a number of BSBs - * corresponding to the character's terminal width */ - mk->term->utf = true; - - ldisc_send(mk->ldisc, "\xC2\xA0\xC2\xA1", 4, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xC2\xA0\xC2\xA1")); - ldisc_send(mk->ldisc, "\x08", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xC2\xA0\xC2\xA1\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("\xC2\xA0\x0D\x0A")); - reset(mk); - - ldisc_send(mk->ldisc, "\xE2\xA0\x80\xE2\xA0\x81", 6, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xE2\xA0\x80\xE2\xA0\x81")); - ldisc_send(mk->ldisc, "\x08", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xE2\xA0\x80\xE2\xA0\x81\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("\xE2\xA0\x80\x0D\x0A")); - reset(mk); - - ldisc_send(mk->ldisc, "\xF0\x90\x80\x80\xF0\x90\x80\x81", 8, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xF0\x90\x80\x80\xF0\x90\x80\x81")); - ldisc_send(mk->ldisc, "\x08", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xF0\x90\x80\x80\xF0\x90\x80\x81" - "\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("\xF0\x90\x80\x80\x0D\x0A")); - reset(mk); - - /* Double-width characters (Hangul, as it happens) */ - ldisc_send(mk->ldisc, "\xEA\xB0\x80\xEA\xB0\x81", 6, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xEA\xB0\x80\xEA\xB0\x81")); - ldisc_send(mk->ldisc, "\x08", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xEA\xB0\x80\xEA\xB0\x81" - "\x08 \x08\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("\xEA\xB0\x80\x0D\x0A")); - reset(mk); - - /* Zero-width characters */ - ldisc_send(mk->ldisc, "\xE2\x80\x8B\xE2\x80\x8B", 6, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xE2\x80\x8B\xE2\x80\x8B")); - ldisc_send(mk->ldisc, "\x08", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xE2\x80\x8B\xE2\x80\x8B")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("\xE2\x80\x8B\x0D\x0A")); - reset(mk); - - /* And reset back to non-UTF-8 mode and expect high-bit-set bytes - * to be treated individually, as characters in a single-byte - * charset. (In our case, given the test config, that will be - * CP437, but it makes no difference to the editing behaviour.) */ - mk->term->utf = false; - ldisc_send(mk->ldisc, "\xC2\xA0\xC2\xA1", 4, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xC2\xA0\xC2\xA1")); - ldisc_send(mk->ldisc, "\x08", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("")); - EXPECT_TERMINAL(mk, PTRLEN_LITERAL("\xC2\xA0\xC2\xA1\x08 \x08")); - ldisc_send(mk->ldisc, "\x0D", -1, false); - EXPECT(mk, backend, PTRLEN_LITERAL("\xC2\xA0\xC2\x0D\x0A")); - reset(mk); - - /* Make sure we flush all the terminal contents at the end of this - * function */ - ldisc_send(mk->ldisc, "\x0D", 1, false); - reset(mk); - -#undef EXPECT_TERMINAL - -} - -const struct BackendVtable *const backends[] = { &mock_backend_vt, NULL }; - -int main(void) -{ - Mock *mk = mock_new(); - mk->term = term_init(mk->conf, mk->ucsdata, &mk->tw); - Ldisc *ldisc = ldisc_create(mk->conf, mk->term, &mk->backend, &mk->seat); - term_size(mk->term, 80, 24, 0); - - test_noedit(mk); - test_edit(mk, true); - test_edit(mk, false); - - ldisc_free(ldisc); - - bool failed = mk->any_test_failed; - mock_free(mk); - - if (failed) { - printf("Test suite FAILED!\n"); - return 1; - } else { - printf("Test suite passed\n"); - return 0; - } -} diff --git a/test/test_terminal.c b/test/test_terminal.c deleted file mode 100644 index 9fdca6ccc..000000000 --- a/test/test_terminal.c +++ /dev/null @@ -1,508 +0,0 @@ -#include "putty.h" -#include "terminal.h" - -void modalfatalbox(const 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); -} - -const char *const appname = "test_lineedit"; - -char *platform_default_s(const char *name) -{ return NULL; } -bool platform_default_b(const char *name, bool def) -{ return def; } -int platform_default_i(const char *name, int def) -{ return def; } -FontSpec *platform_default_fontspec(const char *name) -{ return fontspec_new_default(); } -Filename *platform_default_filename(const char *name) -{ return filename_from_str(""); } - -const struct BackendVtable *const backends[] = { NULL }; - -typedef struct Mock { - Terminal *term; - Conf *conf; - struct unicode_data ucsdata[1]; - - strbuf *context; - - bool any_test_failed; - - TermWin tw; -} Mock; - -static bool mock_setup_draw_ctx(TermWin *win) { return false; } -static void mock_draw_text(TermWin *win, int x, int y, wchar_t *text, int len, - unsigned long attrs, int lattrs, truecolour tc) {} -static void mock_draw_cursor(TermWin *win, int x, int y, wchar_t *text, - int len, unsigned long attrs, int lattrs, - truecolour tc) {} -static void mock_set_raw_mouse_mode(TermWin *win, bool enable) {} -static void mock_set_raw_mouse_mode_pointer(TermWin *win, bool enable) {} -static void mock_palette_set(TermWin *win, unsigned start, unsigned ncolours, - const rgb *colours) {} -static void mock_palette_get_overrides(TermWin *tw, Terminal *term) {} - -static const TermWinVtable mock_termwin_vt = { - .setup_draw_ctx = mock_setup_draw_ctx, - .draw_text = mock_draw_text, - .draw_cursor = mock_draw_cursor, - .set_raw_mouse_mode = mock_set_raw_mouse_mode, - .set_raw_mouse_mode_pointer = mock_set_raw_mouse_mode_pointer, - .palette_set = mock_palette_set, - .palette_get_overrides = mock_palette_get_overrides, -}; - -static Mock *mock_new(void) -{ - Mock *mk = snew(Mock); - memset(mk, 0, sizeof(*mk)); - - mk->conf = conf_new(); - do_defaults(NULL, mk->conf); - - init_ucs_generic(mk->conf, mk->ucsdata); - mk->ucsdata->line_codepage = CP_ISO8859_1; - - mk->context = strbuf_new(); - - mk->tw.vt = &mock_termwin_vt; - - return mk; -} - -static void mock_free(Mock *mk) -{ - strbuf_free(mk->context); - conf_free(mk->conf); - term_free(mk->term); - sfree(mk); -} - -static void reset(Mock *mk) -{ - term_pwron(mk->term, true); - term_size(mk->term, 24, 80, 0); - term_set_trust_status(mk->term, false); - strbuf_clear(mk->context); -} - -#if 0 - -static void test_context(Mock *mk, const char *fmt, ...) -{ - strbuf_clear(mk->context); - va_list ap; - va_start(ap, fmt); - put_fmtv(mk->context, fmt, ap); - va_end(ap); -} - -#endif - -static void report_fail(Mock *mk, const char *file, int line, - const char *fmt, ...) -{ - printf("%s:%d", file, line); - if (mk->context->len) - printf(" (%s)", mk->context->s); - printf(": "); - va_list ap; - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - printf("\n"); - mk->any_test_failed = true; -} - -static inline void check_iequal(Mock *mk, const char *file, int line, - long long lhs, long long rhs) -{ - if (lhs != rhs) - report_fail(mk, file, line, "%lld != %lld / %#llx != %#llx", - lhs, rhs, lhs, rhs); -} - -#define IEQUAL(lhs, rhs) check_iequal(mk, __FILE__, __LINE__, lhs, rhs) - -static inline void term_datapl(Terminal *term, ptrlen pl) -{ - term_data(term, pl.ptr, pl.len); -} - -static struct termchar get_termchar(Terminal *term, int x, int y) -{ - termline *tl = term_get_line(term, y); - termchar tc; - if (0 <= x && x < tl->cols) - tc = tl->chars[x]; - else - tc = term->erase_char; - term_release_line(tl); - return tc; -} - -static unsigned short get_lineattr(Terminal *term, int y) -{ - termline *tl = term_get_line(term, y); - unsigned short lattr = tl->lattr; - term_release_line(tl); - return lattr; -} - -static void test_hello_world(Mock *mk) -{ - /* A trivial test just to kick off this test framework */ - mk->ucsdata->line_codepage = CP_ISO8859_1; - - reset(mk); - term_datapl(mk->term, PTRLEN_LITERAL("hello, world")); - IEQUAL(mk->term->curs.x, 12); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(get_termchar(mk->term, 0, 0).chr, CSET_ASCII | 'h'); - IEQUAL(get_termchar(mk->term, 1, 0).chr, CSET_ASCII | 'e'); - IEQUAL(get_termchar(mk->term, 2, 0).chr, CSET_ASCII | 'l'); - IEQUAL(get_termchar(mk->term, 3, 0).chr, CSET_ASCII | 'l'); - IEQUAL(get_termchar(mk->term, 4, 0).chr, CSET_ASCII | 'o'); - IEQUAL(get_termchar(mk->term, 5, 0).chr, CSET_ASCII | ','); - IEQUAL(get_termchar(mk->term, 6, 0).chr, CSET_ASCII | ' '); - IEQUAL(get_termchar(mk->term, 7, 0).chr, CSET_ASCII | 'w'); - IEQUAL(get_termchar(mk->term, 8, 0).chr, CSET_ASCII | 'o'); - IEQUAL(get_termchar(mk->term, 9, 0).chr, CSET_ASCII | 'r'); - IEQUAL(get_termchar(mk->term, 10, 0).chr, CSET_ASCII | 'l'); - IEQUAL(get_termchar(mk->term, 11, 0).chr, CSET_ASCII | 'd'); -} - -static void test_wrap(Mock *mk) -{ - /* Test behaviour when printing characters wrap to the next line */ - mk->ucsdata->line_codepage = CP_UTF8; - - /* Print 'abc' without enough space for the c, in wrapping mode */ - reset(mk); - mk->term->curs.x = 78; - mk->term->curs.y = 0; - mk->term->wrap = true; - /* The 'a' prints without anything unusual happening */ - term_datapl(mk->term, PTRLEN_LITERAL("a")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); - /* The 'b' prints, leaving the cursor where it is with wrapnext set */ - term_datapl(mk->term, PTRLEN_LITERAL("b")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_lineattr(mk->term, 0), 0); - IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b'); - /* And now the 'c' causes a deferred wrap and goes to the next line */ - term_datapl(mk->term, PTRLEN_LITERAL("c")); - IEQUAL(mk->term->curs.x, 1); - IEQUAL(mk->term->curs.y, 1); - IEQUAL(mk->term->wrapnext, 0); - IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED); - IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b'); - IEQUAL(get_termchar(mk->term, 0, 1).chr, CSET_ASCII | 'c'); - /* If we backspace once, the cursor moves back on to the c */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 0); - IEQUAL(mk->term->curs.y, 1); - IEQUAL(mk->term->wrapnext, 0); - /* Now backspace again, and the cursor returns to the b */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - - /* Now try it with a double-width character in place of ab */ - mk->term->curs.x = 78; - mk->term->curs.y = 0; - mk->term->wrap = true; - /* The DW character goes directly to the wrapnext state */ - term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00); - IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE); - /* And the 'c' causes a deferred wrap as before */ - term_datapl(mk->term, PTRLEN_LITERAL("c")); - IEQUAL(mk->term->curs.x, 1); - IEQUAL(mk->term->curs.y, 1); - IEQUAL(mk->term->wrapnext, 0); - IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED); - IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00); - IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE); - IEQUAL(get_termchar(mk->term, 0, 1).chr, CSET_ASCII | 'c'); - /* If we backspace once, the cursor moves back on to the c */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 0); - IEQUAL(mk->term->curs.y, 1); - IEQUAL(mk->term->wrapnext, 0); - /* Now backspace again, and the cursor goes to the RHS of the DW char */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - - /* Now put the DW character in place of bc */ - reset(mk); - mk->term->curs.x = 78; - mk->term->curs.y = 0; - mk->term->wrap = true; - /* The 'a' prints as before */ - term_datapl(mk->term, PTRLEN_LITERAL("a")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); - /* The DW character wraps, setting LATTR_WRAPPED2 */ - term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); - IEQUAL(mk->term->curs.x, 2); - IEQUAL(mk->term->curs.y, 1); - IEQUAL(mk->term->wrapnext, 0); - IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED | LATTR_WRAPPED2); - IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); - IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | ' '); - IEQUAL(get_termchar(mk->term, 0, 1).chr, 0xAC00); - IEQUAL(get_termchar(mk->term, 1, 1).chr, UCSWIDE); - /* If we backspace once, cursor moves to the RHS of the DW char */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 1); - IEQUAL(mk->term->curs.y, 1); - IEQUAL(mk->term->wrapnext, 0); - /* Backspace again, and cursor moves from RHS to LHS of that char */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 0); - IEQUAL(mk->term->curs.y, 1); - IEQUAL(mk->term->wrapnext, 0); - /* Now backspace again, and the cursor skips the empty column so - * that it can return to the previous logical character, to wit, the a */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 78); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - - /* Print 'ab' up to the rightmost column, and then backspace */ - reset(mk); - mk->term->curs.x = 78; - mk->term->curs.y = 0; - mk->term->wrap = true; - /* As before, the 'ab' put us in the rightmost column with wrapnext set */ - term_datapl(mk->term, PTRLEN_LITERAL("ab")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_lineattr(mk->term, 0), 0); - IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); - IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b'); - /* Backspacing just clears the wrapnext flag, so we're logically - * back on the b again */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - - /* For completeness, the easy case: just print 'a' then backspace */ - reset(mk); - mk->term->curs.x = 78; - mk->term->curs.y = 0; - mk->term->wrap = true; - /* 'a' printed in column n-1 takes us to column n */ - term_datapl(mk->term, PTRLEN_LITERAL("a")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - IEQUAL(get_lineattr(mk->term, 0), 0); - IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); - /* Backspacing moves us back a space on to the a */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 78); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - - /* - * Now test the special cases that arise when the terminal is only - * one column wide! - */ - - reset(mk); - term_size(mk->term, 24, 1, 0); - mk->term->curs.x = 0; - mk->term->curs.y = 0; - mk->term->wrap = true; - /* Printing a single-width character takes us into wrapnext immediately */ - term_datapl(mk->term, PTRLEN_LITERAL("a")); - IEQUAL(mk->term->curs.x, 0); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_lineattr(mk->term, 0), 0); - IEQUAL(get_termchar(mk->term, 0, 0).chr, CSET_ASCII | 'a'); - /* Printing a second one wraps, and takes us _back_ to wrapnext */ - term_datapl(mk->term, PTRLEN_LITERAL("b")); - IEQUAL(mk->term->curs.x, 0); - IEQUAL(mk->term->curs.y, 1); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_lineattr(mk->term, 0), LATTR_WRAPPED); - IEQUAL(get_termchar(mk->term, 0, 0).chr, CSET_ASCII | 'a'); - IEQUAL(get_termchar(mk->term, 0, 1).chr, CSET_ASCII | 'b'); - /* Backspacing once clears the wrapnext flag, putting us on the b */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 0); - IEQUAL(mk->term->curs.y, 1); - IEQUAL(mk->term->wrapnext, 0); - /* Backspacing again returns to the previous line, putting us on the a */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 0); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - - /* And now try with a double-width character */ - reset(mk); - term_size(mk->term, 24, 1, 0); - mk->term->curs.x = 0; - mk->term->curs.y = 0; - mk->term->wrap = true; - /* DW character won't fit at all, so it transforms into U+FFFD - * REPLACEMENT CHARACTER and then behaves like a SW char */ - term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); - IEQUAL(mk->term->curs.x, 0); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_lineattr(mk->term, 0), 0); - IEQUAL(get_termchar(mk->term, 0, 0).chr, 0xFFFD); -} - -static void test_nonwrap(Mock *mk) -{ - /* Test behaviour when printing characters hit end of line without wrap */ - mk->ucsdata->line_codepage = CP_UTF8; - - /* Print 'abc' without enough space for the c */ - reset(mk); - mk->term->curs.x = 78; - mk->term->curs.y = 0; - mk->term->wrap = false; - /* The 'a' prints without anything unusual happening */ - term_datapl(mk->term, PTRLEN_LITERAL("a")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); - /* The 'b' prints, leaving the cursor where it is with wrapnext set */ - term_datapl(mk->term, PTRLEN_LITERAL("b")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_lineattr(mk->term, 0), 0); - IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'b'); - /* The 'c' overwrites the b, leaving wrapnext still set */ - term_datapl(mk->term, PTRLEN_LITERAL("c")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_lineattr(mk->term, 0), 0); - IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); - IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'c'); - /* So backspacing clears wrapnext, leaving us on the c */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - /* And backspacing again returns the cursor to the a */ - term_datapl(mk->term, PTRLEN_LITERAL("\b")); - IEQUAL(mk->term->curs.x, 78); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - - /* Now try it with a double-width character in place of ab */ - mk->term->curs.x = 78; - mk->term->curs.y = 0; - mk->term->wrap = false; - /* The DW character goes directly to the wrapnext state */ - term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00); - IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE); - /* The 'c' must overprint the RHS of the DW char, clearing the LHS */ - term_datapl(mk->term, PTRLEN_LITERAL("c")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_lineattr(mk->term, 0), 0); - IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | ' '); - IEQUAL(get_termchar(mk->term, 79, 0).chr, CSET_ASCII | 'c'); - - /* Now put the DW char in place of the bc */ - reset(mk); - mk->term->curs.x = 78; - mk->term->curs.y = 0; - mk->term->wrap = false; - /* The 'a' prints as before */ - term_datapl(mk->term, PTRLEN_LITERAL("a")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 0); - IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); - /* The DW char won't fit, so turns into U+FFFD REPLACEMENT CHARACTER */ - term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_lineattr(mk->term, 0), 0); - IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | 'a'); - IEQUAL(get_termchar(mk->term, 79, 0).chr, 0xFFFD); - - /* Just for completeness, try both of those together */ - reset(mk); - mk->term->curs.x = 78; - mk->term->curs.y = 0; - mk->term->wrap = false; - /* First DW character goes directly to the wrapnext state */ - term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x80")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_termchar(mk->term, 78, 0).chr, 0xAC00); - IEQUAL(get_termchar(mk->term, 79, 0).chr, UCSWIDE); - /* Second DW char becomes U+FFFD, overwriting RHS of the first one */ - term_datapl(mk->term, PTRLEN_LITERAL("\xEA\xB0\x81")); - IEQUAL(mk->term->curs.x, 79); - IEQUAL(mk->term->curs.y, 0); - IEQUAL(mk->term->wrapnext, 1); - IEQUAL(get_lineattr(mk->term, 0), 0); - IEQUAL(get_termchar(mk->term, 78, 0).chr, CSET_ASCII | ' '); - IEQUAL(get_termchar(mk->term, 79, 0).chr, 0xFFFD); -} - -int main(void) -{ - Mock *mk = mock_new(); - mk->term = term_init(mk->conf, mk->ucsdata, &mk->tw); - - test_hello_world(mk); - test_wrap(mk); - test_nonwrap(mk); - - bool failed = mk->any_test_failed; - mock_free(mk); - - if (failed) { - printf("Test suite FAILED!\n"); - return 1; - } else { - printf("Test suite passed\n"); - return 0; - } -} diff --git a/test/testcrypt-enum.h b/test/testcrypt-enum.h deleted file mode 100644 index 4e93c7868..000000000 --- a/test/testcrypt-enum.h +++ /dev/null @@ -1,179 +0,0 @@ -BEGIN_ENUM_TYPE(hashalg) - ENUM_VALUE("md5", &ssh_md5) - ENUM_VALUE("sha1", &ssh_sha1) - ENUM_VALUE("sha1_sw", &ssh_sha1_sw) - ENUM_VALUE("sha256", &ssh_sha256) - ENUM_VALUE("sha384", &ssh_sha384) - ENUM_VALUE("sha512", &ssh_sha512) - ENUM_VALUE("sha256_sw", &ssh_sha256_sw) - ENUM_VALUE("sha384_sw", &ssh_sha384_sw) - ENUM_VALUE("sha512_sw", &ssh_sha512_sw) -#if HAVE_SHA_NI - ENUM_VALUE("sha1_ni", &ssh_sha1_ni) - ENUM_VALUE("sha256_ni", &ssh_sha256_ni) -#endif -#if HAVE_NEON_CRYPTO - ENUM_VALUE("sha1_neon", &ssh_sha1_neon) - ENUM_VALUE("sha256_neon", &ssh_sha256_neon) -#endif -#if HAVE_NEON_SHA512 - ENUM_VALUE("sha384_neon", &ssh_sha384_neon) - ENUM_VALUE("sha512_neon", &ssh_sha512_neon) -#endif - ENUM_VALUE("sha3_224", &ssh_sha3_224) - ENUM_VALUE("sha3_256", &ssh_sha3_256) - ENUM_VALUE("sha3_384", &ssh_sha3_384) - ENUM_VALUE("sha3_512", &ssh_sha3_512) - ENUM_VALUE("shake256_114bytes", &ssh_shake256_114bytes) - ENUM_VALUE("blake2b", &ssh_blake2b) -END_ENUM_TYPE(hashalg) - -BEGIN_ENUM_TYPE(macalg) - ENUM_VALUE("hmac_md5", &ssh_hmac_md5) - ENUM_VALUE("hmac_sha1", &ssh_hmac_sha1) - ENUM_VALUE("hmac_sha1_buggy", &ssh_hmac_sha1_buggy) - ENUM_VALUE("hmac_sha1_96", &ssh_hmac_sha1_96) - ENUM_VALUE("hmac_sha1_96_buggy", &ssh_hmac_sha1_96_buggy) - ENUM_VALUE("hmac_sha256", &ssh_hmac_sha256) - ENUM_VALUE("hmac_sha512", &ssh_hmac_sha512) - ENUM_VALUE("poly1305", &ssh2_poly1305) - ENUM_VALUE("aesgcm", &ssh2_aesgcm_mac) - ENUM_VALUE("aesgcm", &ssh2_aesgcm_mac) - ENUM_VALUE("aesgcm_sw", &ssh2_aesgcm_mac_sw) - ENUM_VALUE("aesgcm_ref_poly", &ssh2_aesgcm_mac_ref_poly) -#if HAVE_CLMUL - ENUM_VALUE("aesgcm_clmul", &ssh2_aesgcm_mac_clmul) -#endif -#if HAVE_NEON_PMULL - ENUM_VALUE("aesgcm_neon", &ssh2_aesgcm_mac_neon) -#endif -END_ENUM_TYPE(macalg) - -BEGIN_ENUM_TYPE(keyalg) - ENUM_VALUE("dsa", &ssh_dsa) - ENUM_VALUE("rsa", &ssh_rsa) - ENUM_VALUE("ed25519", &ssh_ecdsa_ed25519) - ENUM_VALUE("ed448", &ssh_ecdsa_ed448) - ENUM_VALUE("p256", &ssh_ecdsa_nistp256) - ENUM_VALUE("p384", &ssh_ecdsa_nistp384) - ENUM_VALUE("p521", &ssh_ecdsa_nistp521) - ENUM_VALUE("dsa-cert", &opensshcert_ssh_dsa) - ENUM_VALUE("rsa-cert", &opensshcert_ssh_rsa) - ENUM_VALUE("ed25519-cert", &opensshcert_ssh_ecdsa_ed25519) - ENUM_VALUE("p256-cert", &opensshcert_ssh_ecdsa_nistp256) - ENUM_VALUE("p384-cert", &opensshcert_ssh_ecdsa_nistp384) - ENUM_VALUE("p521-cert", &opensshcert_ssh_ecdsa_nistp521) -END_ENUM_TYPE(keyalg) - -BEGIN_ENUM_TYPE(cipheralg) - ENUM_VALUE("3des_ctr", &ssh_3des_ssh2_ctr) - ENUM_VALUE("3des_ssh2", &ssh_3des_ssh2) - ENUM_VALUE("3des_ssh1", &ssh_3des_ssh1) - ENUM_VALUE("des_cbc", &ssh_des) - ENUM_VALUE("aes256_ctr", &ssh_aes256_sdctr) - ENUM_VALUE("aes256_gcm", &ssh_aes256_gcm) - ENUM_VALUE("aes256_cbc", &ssh_aes256_cbc) - ENUM_VALUE("aes192_ctr", &ssh_aes192_sdctr) - ENUM_VALUE("aes192_gcm", &ssh_aes192_gcm) - ENUM_VALUE("aes192_cbc", &ssh_aes192_cbc) - ENUM_VALUE("aes128_ctr", &ssh_aes128_sdctr) - ENUM_VALUE("aes128_gcm", &ssh_aes128_gcm) - ENUM_VALUE("aes128_cbc", &ssh_aes128_cbc) - ENUM_VALUE("aes256_ctr_sw", &ssh_aes256_sdctr_sw) - ENUM_VALUE("aes256_gcm_sw", &ssh_aes256_gcm_sw) - ENUM_VALUE("aes256_cbc_sw", &ssh_aes256_cbc_sw) - ENUM_VALUE("aes192_ctr_sw", &ssh_aes192_sdctr_sw) - ENUM_VALUE("aes192_gcm_sw", &ssh_aes192_gcm_sw) - ENUM_VALUE("aes192_cbc_sw", &ssh_aes192_cbc_sw) - ENUM_VALUE("aes128_ctr_sw", &ssh_aes128_sdctr_sw) - ENUM_VALUE("aes128_gcm_sw", &ssh_aes128_gcm_sw) - ENUM_VALUE("aes128_cbc_sw", &ssh_aes128_cbc_sw) -#if HAVE_AES_NI - ENUM_VALUE("aes256_ctr_ni", &ssh_aes256_sdctr_ni) - ENUM_VALUE("aes256_gcm_ni", &ssh_aes256_gcm_ni) - ENUM_VALUE("aes256_cbc_ni", &ssh_aes256_cbc_ni) - ENUM_VALUE("aes192_ctr_ni", &ssh_aes192_sdctr_ni) - ENUM_VALUE("aes192_gcm_ni", &ssh_aes192_gcm_ni) - ENUM_VALUE("aes192_cbc_ni", &ssh_aes192_cbc_ni) - ENUM_VALUE("aes128_ctr_ni", &ssh_aes128_sdctr_ni) - ENUM_VALUE("aes128_gcm_ni", &ssh_aes128_gcm_ni) - ENUM_VALUE("aes128_cbc_ni", &ssh_aes128_cbc_ni) -#endif -#if HAVE_NEON_CRYPTO - ENUM_VALUE("aes256_ctr_neon", &ssh_aes256_sdctr_neon) - ENUM_VALUE("aes256_gcm_neon", &ssh_aes256_gcm_neon) - ENUM_VALUE("aes256_cbc_neon", &ssh_aes256_cbc_neon) - ENUM_VALUE("aes192_ctr_neon", &ssh_aes192_sdctr_neon) - ENUM_VALUE("aes192_gcm_neon", &ssh_aes192_gcm_neon) - ENUM_VALUE("aes192_cbc_neon", &ssh_aes192_cbc_neon) - ENUM_VALUE("aes128_ctr_neon", &ssh_aes128_sdctr_neon) - ENUM_VALUE("aes128_gcm_neon", &ssh_aes128_gcm_neon) - ENUM_VALUE("aes128_cbc_neon", &ssh_aes128_cbc_neon) -#endif - ENUM_VALUE("blowfish_ctr", &ssh_blowfish_ssh2_ctr) - ENUM_VALUE("blowfish_ssh2", &ssh_blowfish_ssh2) - ENUM_VALUE("blowfish_ssh1", &ssh_blowfish_ssh1) - ENUM_VALUE("arcfour256", &ssh_arcfour256_ssh2) - ENUM_VALUE("arcfour128", &ssh_arcfour128_ssh2) - ENUM_VALUE("chacha20_poly1305", &ssh2_chacha20_poly1305) -END_ENUM_TYPE(cipheralg) - -BEGIN_ENUM_TYPE(dh_group) - ENUM_VALUE("group1", &ssh_diffiehellman_group1_sha1) - ENUM_VALUE("group14", &ssh_diffiehellman_group14_sha256) - ENUM_VALUE("group15", &ssh_diffiehellman_group15_sha512) - ENUM_VALUE("group16", &ssh_diffiehellman_group16_sha512) - ENUM_VALUE("group17", &ssh_diffiehellman_group17_sha512) - ENUM_VALUE("group18", &ssh_diffiehellman_group18_sha512) -END_ENUM_TYPE(dh_group) - -BEGIN_ENUM_TYPE(ecdh_alg) - ENUM_VALUE("curve25519", &ssh_ec_kex_curve25519) - ENUM_VALUE("curve448", &ssh_ec_kex_curve448) - ENUM_VALUE("nistp256", &ssh_ec_kex_nistp256) - ENUM_VALUE("nistp384", &ssh_ec_kex_nistp384) - ENUM_VALUE("nistp521", &ssh_ec_kex_nistp521) -END_ENUM_TYPE(ecdh_alg) - -BEGIN_ENUM_TYPE(rsaorder) - ENUM_VALUE("exponent_first", RSA_SSH1_EXPONENT_FIRST) - ENUM_VALUE("modulus_first", RSA_SSH1_MODULUS_FIRST) -END_ENUM_TYPE(rsaorder) - -BEGIN_ENUM_TYPE(primegenpolicy) - ENUM_VALUE("probabilistic", &primegen_probabilistic) - ENUM_VALUE("provable_fast", &primegen_provable_fast) - ENUM_VALUE("provable_maurer_simple", &primegen_provable_maurer_simple) - ENUM_VALUE("provable_maurer_complex", &primegen_provable_maurer_complex) -END_ENUM_TYPE(primegenpolicy) - -BEGIN_ENUM_TYPE(argon2flavour) - ENUM_VALUE("d", Argon2d) - ENUM_VALUE("i", Argon2i) - ENUM_VALUE("id", Argon2id) - /* I expect to forget which spelling I chose, so let's support many */ - ENUM_VALUE("argon2d", Argon2d) - ENUM_VALUE("argon2i", Argon2i) - ENUM_VALUE("argon2id", Argon2id) - ENUM_VALUE("Argon2d", Argon2d) - ENUM_VALUE("Argon2i", Argon2i) - ENUM_VALUE("Argon2id", Argon2id) -END_ENUM_TYPE(argon2flavour) - -BEGIN_ENUM_TYPE(fptype) - ENUM_VALUE("md5", SSH_FPTYPE_MD5) - ENUM_VALUE("sha256", SSH_FPTYPE_SHA256) - ENUM_VALUE("md5-cert", SSH_FPTYPE_MD5_CERT) - ENUM_VALUE("sha256-cert", SSH_FPTYPE_SHA256_CERT) -END_ENUM_TYPE(fptype) - -/* - * cproxy.h already has a list macro mapping protocol-specified - * strings to the list of HTTP Digest hash functions. Rather than - * invent a separate one for testcrypt, reuse the existing names. - */ -BEGIN_ENUM_TYPE(httpdigesthash) - #define DECL_ARRAY(id, str, alg, bits, accepted) ENUM_VALUE(str, id) - HTTP_DIGEST_HASHES(DECL_ARRAY) - #undef DECL_ARRAY -END_ENUM_TYPE(httpdigesthash) diff --git a/test/testcrypt-func.h b/test/testcrypt-func.h deleted file mode 100644 index 7b955b92e..000000000 --- a/test/testcrypt-func.h +++ /dev/null @@ -1,582 +0,0 @@ -/* - * List of functions exported by the 'testcrypt' system to provide a - * Python API for running unit tests and auxiliary programs. - * - * Each function definition in this file has the form - * - * FUNC(return-type, function-name, ...) - * - * where '...' in turn a variadic list of argument specifications of - * the form - * - * ARG(argument-type, argument-name) - * - * An empty argument list must be marked by including a - * pseudo-argument VOID: - * - * FUNC(return-type, function-name, VOID) - * - * Type names are always single identifiers, and they have some - * standard prefixes: - * - * 'val_' means that the type refers to something dynamically - * allocated, so that it has a persistent identity, needs to be freed - * when finished with (though this is done automatically by the - * testcrypt.py system via Python's reference counting), and may also - * be mutable. The argument type in C will be a pointer; in Python the - * corresponding argument will be an instance of a 'Value' object - * defined in testcrypt.py. - * - * 'opt_val_' is a modification of 'val_' to indicate that the pointer - * may be NULL. In Python this is translated by accepting (or - * returning) None as an alternative to a Value. - * - * 'out_' on an argument type indicates an additional output - * parameter. The argument type in C has an extra layer of - * indirection, e.g. an 'out_val_mpint' is an 'mpint **' instead of an - * 'mpint *', identifying a pointer variable where the returned - * pointer value will be written. In the Python API, these arguments - * do not appear in the argument list of the Python function; instead - * they cause the return value to become a tuple, with additional - * types appended. For example, a declaration like - * - * FUNC(val_foo, example, ARG(out_val_bar, bar), ARG(val_baz, baz)) - * - * would identify a function in C with the following prototype, which - * returns a 'foo *' directly and a 'bar *' by writing it through the - * provided 'bar **' pointer argument: - * - * foo *example(bar **extra_output, baz *input); - * - * and in Python this would become a function taking one argument of - * type 'baz' and returning a tuple of the form (foo, bar). - * - * 'out_' and 'opt_' can go together, if a function returns a second - * output value but it may in some cases be NULL. - * - * 'consumed_' on an argument type indicates that the C function - * receiving that argument frees it as a side effect. - * - * Any argument type which does not start 'val_' is plain old data - * with no dynamic allocation requirements. Ordinary C integers are - * sometimes handled this way (e.g. 'uint'). Other plain-data types - * are represented in Python as a string that must be one of a - * recognised set of keywords; in C these variously translate into - * enumeration types (e.g. argon2flavour, rsaorder) or pointers to - * const vtables of one kind or another (e.g. keyalg, hashalg, - * primegenpolicy). - * - * If a function definition begins with FUNC_WRAPPED rather than FUNC, - * it means that the underlying C function has a suffix "_wrapper", - * e.g. ssh_cipher_setiv_wrapper(). Those wrappers are defined in - * testcrypt.c itself, and change the API or semantics in a way that - * makes the function more Python-friendly. - */ - -/* - * mpint.h functions. - */ -FUNC(val_mpint, mp_new, ARG(uint, maxbits)) -FUNC(void, mp_clear, ARG(val_mpint, x)) -FUNC(val_mpint, mp_from_bytes_le, ARG(val_string_ptrlen, bytes)) -FUNC(val_mpint, mp_from_bytes_be, ARG(val_string_ptrlen, bytes)) -FUNC(val_mpint, mp_from_integer, ARG(uint, n)) -FUNC(val_mpint, mp_from_decimal_pl, ARG(val_string_ptrlen, decimal)) -FUNC(val_mpint, mp_from_decimal, ARG(val_string_asciz, decimal)) -FUNC(val_mpint, mp_from_hex_pl, ARG(val_string_ptrlen, hex)) -FUNC(val_mpint, mp_from_hex, ARG(val_string_asciz, hex)) -FUNC(val_mpint, mp_copy, ARG(val_mpint, x)) -FUNC(val_mpint, mp_power_2, ARG(uint, power)) -FUNC(uint, mp_get_byte, ARG(val_mpint, x), ARG(uint, byte)) -FUNC(uint, mp_get_bit, ARG(val_mpint, x), ARG(uint, bit)) -FUNC(void, mp_set_bit, ARG(val_mpint, x), ARG(uint, bit), ARG(uint, val)) -FUNC(uint, mp_max_bytes, ARG(val_mpint, x)) -FUNC(uint, mp_max_bits, ARG(val_mpint, x)) -FUNC(uint, mp_get_nbits, ARG(val_mpint, x)) -FUNC(val_string_asciz, mp_get_decimal, ARG(val_mpint, x)) -FUNC(val_string_asciz, mp_get_hex, ARG(val_mpint, x)) -FUNC(val_string_asciz, mp_get_hex_uppercase, ARG(val_mpint, x)) -FUNC(uint, mp_cmp_hs, ARG(val_mpint, a), ARG(val_mpint, b)) -FUNC(uint, mp_cmp_eq, ARG(val_mpint, a), ARG(val_mpint, b)) -FUNC(uint, mp_hs_integer, ARG(val_mpint, x), ARG(uint, n)) -FUNC(uint, mp_eq_integer, ARG(val_mpint, x), ARG(uint, n)) -FUNC(void, mp_min_into, ARG(val_mpint, dest), ARG(val_mpint, x), - ARG(val_mpint, y)) -FUNC(void, mp_max_into, ARG(val_mpint, dest), ARG(val_mpint, x), - ARG(val_mpint, y)) -FUNC(val_mpint, mp_min, ARG(val_mpint, x), ARG(val_mpint, y)) -FUNC(val_mpint, mp_max, ARG(val_mpint, x), ARG(val_mpint, y)) -FUNC(void, mp_copy_into, ARG(val_mpint, dest), ARG(val_mpint, src)) -FUNC(void, mp_select_into, ARG(val_mpint, dest), ARG(val_mpint, src0), - ARG(val_mpint, src1), ARG(uint, choose_src1)) -FUNC(void, mp_add_into, ARG(val_mpint, dest), ARG(val_mpint, a), - ARG(val_mpint, b)) -FUNC(void, mp_sub_into, ARG(val_mpint, dest), ARG(val_mpint, a), - ARG(val_mpint, b)) -FUNC(void, mp_mul_into, ARG(val_mpint, dest), ARG(val_mpint, a), - ARG(val_mpint, b)) -FUNC(val_mpint, mp_add, ARG(val_mpint, x), ARG(val_mpint, y)) -FUNC(val_mpint, mp_sub, ARG(val_mpint, x), ARG(val_mpint, y)) -FUNC(val_mpint, mp_mul, ARG(val_mpint, x), ARG(val_mpint, y)) -FUNC(void, mp_and_into, ARG(val_mpint, dest), ARG(val_mpint, a), - ARG(val_mpint, b)) -FUNC(void, mp_or_into, ARG(val_mpint, dest), ARG(val_mpint, a), - ARG(val_mpint, b)) -FUNC(void, mp_xor_into, ARG(val_mpint, dest), ARG(val_mpint, a), - ARG(val_mpint, b)) -FUNC(void, mp_bic_into, ARG(val_mpint, dest), ARG(val_mpint, a), - ARG(val_mpint, b)) -FUNC(void, mp_copy_integer_into, ARG(val_mpint, dest), ARG(uint, n)) -FUNC(void, mp_add_integer_into, ARG(val_mpint, dest), ARG(val_mpint, a), - ARG(uint, n)) -FUNC(void, mp_sub_integer_into, ARG(val_mpint, dest), ARG(val_mpint, a), - ARG(uint, n)) -FUNC(void, mp_mul_integer_into, ARG(val_mpint, dest), ARG(val_mpint, a), - ARG(uint, n)) -FUNC(void, mp_cond_add_into, ARG(val_mpint, dest), ARG(val_mpint, a), - ARG(val_mpint, b), ARG(uint, yes)) -FUNC(void, mp_cond_sub_into, ARG(val_mpint, dest), ARG(val_mpint, a), - ARG(val_mpint, b), ARG(uint, yes)) -FUNC(void, mp_cond_swap, ARG(val_mpint, x0), ARG(val_mpint, x1), - ARG(uint, swap)) -FUNC(void, mp_cond_clear, ARG(val_mpint, x), ARG(uint, clear)) -FUNC(void, mp_divmod_into, ARG(val_mpint, n), ARG(val_mpint, d), - ARG(opt_val_mpint, q), ARG(opt_val_mpint, r)) -FUNC(val_mpint, mp_div, ARG(val_mpint, n), ARG(val_mpint, d)) -FUNC(val_mpint, mp_mod, ARG(val_mpint, x), ARG(val_mpint, modulus)) -FUNC(val_mpint, mp_nthroot, ARG(val_mpint, y), ARG(uint, n), - ARG(opt_val_mpint, remainder)) -FUNC(void, mp_reduce_mod_2to, ARG(val_mpint, x), ARG(uint, p)) -FUNC(val_mpint, mp_invert_mod_2to, ARG(val_mpint, x), ARG(uint, p)) -FUNC(val_mpint, mp_invert, ARG(val_mpint, x), ARG(val_mpint, modulus)) -FUNC(void, mp_gcd_into, ARG(val_mpint, a), ARG(val_mpint, b), - ARG(opt_val_mpint, gcd_out), ARG(opt_val_mpint, A_out), - ARG(opt_val_mpint, B_out)) -FUNC(val_mpint, mp_gcd, ARG(val_mpint, a), ARG(val_mpint, b)) -FUNC(uint, mp_coprime, ARG(val_mpint, a), ARG(val_mpint, b)) -FUNC(val_modsqrt, modsqrt_new, ARG(val_mpint, p), - ARG(val_mpint, any_nonsquare_mod_p)) -/* The modsqrt functions' 'success' pointer becomes a second return value */ -FUNC(val_mpint, mp_modsqrt, ARG(val_modsqrt, sc), ARG(val_mpint, x), - ARG(out_uint, success)) -FUNC(val_monty, monty_new, ARG(val_mpint, modulus)) -FUNC_WRAPPED(val_mpint, monty_modulus, ARG(val_monty, mc)) -FUNC_WRAPPED(val_mpint, monty_identity, ARG(val_monty, mc)) -FUNC(void, monty_import_into, ARG(val_monty, mc), ARG(val_mpint, dest), - ARG(val_mpint, x)) -FUNC(val_mpint, monty_import, ARG(val_monty, mc), ARG(val_mpint, x)) -FUNC(void, monty_export_into, ARG(val_monty, mc), ARG(val_mpint, dest), - ARG(val_mpint, x)) -FUNC(val_mpint, monty_export, ARG(val_monty, mc), ARG(val_mpint, x)) -FUNC(void, monty_mul_into, ARG(val_monty, mc), ARG(val_mpint, dest), - ARG(val_mpint, x), ARG(val_mpint, y)) -FUNC(val_mpint, monty_add, ARG(val_monty, mc), ARG(val_mpint, x), - ARG(val_mpint, y)) -FUNC(val_mpint, monty_sub, ARG(val_monty, mc), ARG(val_mpint, x), - ARG(val_mpint, y)) -FUNC(val_mpint, monty_mul, ARG(val_monty, mc), ARG(val_mpint, x), - ARG(val_mpint, y)) -FUNC(val_mpint, monty_pow, ARG(val_monty, mc), ARG(val_mpint, base), - ARG(val_mpint, exponent)) -FUNC(val_mpint, monty_invert, ARG(val_monty, mc), ARG(val_mpint, x)) -FUNC(val_mpint, monty_modsqrt, ARG(val_modsqrt, sc), ARG(val_mpint, mx), - ARG(out_uint, success)) -FUNC(val_mpint, mp_modpow, ARG(val_mpint, base), ARG(val_mpint, exponent), - ARG(val_mpint, modulus)) -FUNC(val_mpint, mp_modmul, ARG(val_mpint, x), ARG(val_mpint, y), - ARG(val_mpint, modulus)) -FUNC(val_mpint, mp_modadd, ARG(val_mpint, x), ARG(val_mpint, y), - ARG(val_mpint, modulus)) -FUNC(val_mpint, mp_modsub, ARG(val_mpint, x), ARG(val_mpint, y), - ARG(val_mpint, modulus)) -FUNC(void, mp_lshift_safe_into, ARG(val_mpint, dest), ARG(val_mpint, x), - ARG(uint, shift)) -FUNC(void, mp_rshift_safe_into, ARG(val_mpint, dest), ARG(val_mpint, x), - ARG(uint, shift)) -FUNC(val_mpint, mp_rshift_safe, ARG(val_mpint, x), ARG(uint, shift)) -FUNC(void, mp_lshift_fixed_into, ARG(val_mpint, dest), ARG(val_mpint, x), - ARG(uint, shift)) -FUNC(void, mp_rshift_fixed_into, ARG(val_mpint, dest), ARG(val_mpint, x), - ARG(uint, shift)) -FUNC(val_mpint, mp_rshift_fixed, ARG(val_mpint, x), ARG(uint, shift)) -FUNC(val_mpint, mp_random_bits, ARG(uint, bits)) -FUNC(val_mpint, mp_random_in_range, ARG(val_mpint, lo), ARG(val_mpint, hi)) - -/* - * ecc.h functions. - */ -FUNC(val_wcurve, ecc_weierstrass_curve, ARG(val_mpint, p), ARG(val_mpint, a), - ARG(val_mpint, b), ARG(opt_val_mpint, nonsquare_mod_p)) -FUNC(val_wpoint, ecc_weierstrass_point_new_identity, ARG(val_wcurve, curve)) -FUNC(val_wpoint, ecc_weierstrass_point_new, ARG(val_wcurve, curve), - ARG(val_mpint, x), ARG(val_mpint, y)) -FUNC(val_wpoint, ecc_weierstrass_point_new_from_x, ARG(val_wcurve, curve), - ARG(val_mpint, x), ARG(uint, desired_y_parity)) -FUNC(val_wpoint, ecc_weierstrass_point_copy, ARG(val_wpoint, orig)) -FUNC(uint, ecc_weierstrass_point_valid, ARG(val_wpoint, P)) -FUNC(val_wpoint, ecc_weierstrass_add_general, ARG(val_wpoint, P), - ARG(val_wpoint, Q)) -FUNC(val_wpoint, ecc_weierstrass_add, ARG(val_wpoint, P), ARG(val_wpoint, Q)) -FUNC(val_wpoint, ecc_weierstrass_double, ARG(val_wpoint, P)) -FUNC(val_wpoint, ecc_weierstrass_multiply, ARG(val_wpoint, B), - ARG(val_mpint, n)) -FUNC(uint, ecc_weierstrass_is_identity, ARG(val_wpoint, P)) -/* The output pointers in get_affine all become extra output values */ -FUNC(void, ecc_weierstrass_get_affine, ARG(val_wpoint, P), - ARG(out_val_mpint, x), ARG(out_val_mpint, y)) -FUNC(val_mcurve, ecc_montgomery_curve, ARG(val_mpint, p), ARG(val_mpint, a), - ARG(val_mpint, b)) -FUNC(val_mpoint, ecc_montgomery_point_new, ARG(val_mcurve, curve), - ARG(val_mpint, x)) -FUNC(val_mpoint, ecc_montgomery_point_copy, ARG(val_mpoint, orig)) -FUNC(val_mpoint, ecc_montgomery_diff_add, ARG(val_mpoint, P), - ARG(val_mpoint, Q), ARG(val_mpoint, PminusQ)) -FUNC(val_mpoint, ecc_montgomery_double, ARG(val_mpoint, P)) -FUNC(val_mpoint, ecc_montgomery_multiply, ARG(val_mpoint, B), ARG(val_mpint, n)) -FUNC(void, ecc_montgomery_get_affine, ARG(val_mpoint, P), ARG(out_val_mpint, x)) -FUNC(boolean, ecc_montgomery_is_identity, ARG(val_mpoint, P)) -FUNC(val_ecurve, ecc_edwards_curve, ARG(val_mpint, p), ARG(val_mpint, d), - ARG(val_mpint, a), ARG(opt_val_mpint, nonsquare_mod_p)) -FUNC(val_epoint, ecc_edwards_point_new, ARG(val_ecurve, curve), - ARG(val_mpint, x), ARG(val_mpint, y)) -FUNC(val_epoint, ecc_edwards_point_new_from_y, ARG(val_ecurve, curve), - ARG(val_mpint, y), ARG(uint, desired_x_parity)) -FUNC(val_epoint, ecc_edwards_point_copy, ARG(val_epoint, orig)) -FUNC(val_epoint, ecc_edwards_add, ARG(val_epoint, P), ARG(val_epoint, Q)) -FUNC(val_epoint, ecc_edwards_multiply, ARG(val_epoint, B), ARG(val_mpint, n)) -FUNC(uint, ecc_edwards_eq, ARG(val_epoint, P), ARG(val_epoint, Q)) -FUNC(void, ecc_edwards_get_affine, ARG(val_epoint, P), ARG(out_val_mpint, x), - ARG(out_val_mpint, y)) - -/* - * The ssh_hash abstraction. Note the 'consumed', indicating that - * ssh_hash_final puts its input ssh_hash beyond use. - * - * ssh_hash_update is an invention of testcrypt, handled in the real C - * API by the hash object also functioning as a BinarySink. - */ -FUNC(opt_val_hash, ssh_hash_new, ARG(hashalg, alg)) -FUNC(void, ssh_hash_reset, ARG(val_hash, h)) -FUNC(val_hash, ssh_hash_copy, ARG(val_hash, orig)) -FUNC_WRAPPED(val_string, ssh_hash_digest, ARG(val_hash, h)) -FUNC_WRAPPED(val_string, ssh_hash_final, ARG(consumed_val_hash, h)) -FUNC(void, ssh_hash_update, ARG(val_hash, h), ARG(val_string_ptrlen, data)) - -FUNC(opt_val_hash, blake2b_new_general, ARG(uint, hashlen)) - -/* - * The ssh2_mac abstraction. Note the optional ssh_cipher parameter - * to ssh2_mac_new. Also, again, I've invented an ssh2_mac_update so - * you can put data into the MAC. - */ -FUNC(opt_val_mac, ssh2_mac_new, ARG(macalg, alg), ARG(opt_val_cipher, cipher)) -FUNC(void, ssh2_mac_setkey, ARG(val_mac, m), ARG(val_string_ptrlen, key)) -FUNC(void, ssh2_mac_start, ARG(val_mac, m)) -FUNC(void, ssh2_mac_update, ARG(val_mac, m), ARG(val_string_ptrlen, data)) -FUNC(void, ssh2_mac_next_message, ARG(val_mac, m)) -FUNC_WRAPPED(val_string, ssh2_mac_genresult, ARG(val_mac, m)) -FUNC(val_string_asciz_const, ssh2_mac_text_name, ARG(val_mac, m)) - -FUNC(void, aesgcm_set_prefix_lengths, - ARG(val_mac, m), ARG(uint, skip), ARG(uint, aad)) - -/* - * The ssh_key abstraction. All the uses of BinarySink and - * BinarySource in parameters are replaced with ordinary strings for - * the testing API: new_priv_openssh just takes a string input, and - * all the functions that output key and signature blobs do it by - * returning a string. - */ -FUNC(val_key, ssh_key_new_pub, ARG(keyalg, alg), ARG(val_string_ptrlen, pub)) -FUNC(opt_val_key, ssh_key_new_priv, ARG(keyalg, alg), - ARG(val_string_ptrlen, pub), ARG(val_string_ptrlen, priv)) -FUNC(opt_val_key, ssh_key_new_priv_openssh, ARG(keyalg, alg), - ARG(val_string_binarysource, src)) -FUNC(opt_val_string_asciz, ssh_key_invalid, ARG(val_key, key), ARG(uint, flags)) -FUNC(void, ssh_key_sign, ARG(val_key, key), ARG(val_string_ptrlen, data), - ARG(uint, flags), ARG(out_val_string_binarysink, sig)) -FUNC(boolean, ssh_key_verify, ARG(val_key, key), ARG(val_string_ptrlen, sig), - ARG(val_string_ptrlen, data)) -FUNC(void, ssh_key_public_blob, ARG(val_key, key), - ARG(out_val_string_binarysink, blob)) -FUNC(void, ssh_key_private_blob, ARG(val_key, key), - ARG(out_val_string_binarysink, blob)) -FUNC(void, ssh_key_openssh_blob, ARG(val_key, key), - ARG(out_val_string_binarysink, blob)) -FUNC(val_string_asciz, ssh_key_cache_str, ARG(val_key, key)) -FUNC(val_keycomponents, ssh_key_components, ARG(val_key, key)) -FUNC(uint, ssh_key_public_bits, ARG(keyalg, self), ARG(val_string_ptrlen, blob)) -FUNC_WRAPPED(val_key, ssh_key_base_key, ARG(val_key, key)) -FUNC_WRAPPED(void, ssh_key_ca_public_blob, ARG(val_key, key), - ARG(out_val_string_binarysink, blob)) -FUNC_WRAPPED(void, ssh_key_cert_id_string, ARG(val_key, key), - ARG(out_val_string_binarysink, blob)) -FUNC_WRAPPED(boolean, ssh_key_check_cert, ARG(val_key, key), - ARG(boolean, host), ARG(val_string_ptrlen, principal), - ARG(uint, time), ARG(val_string_ptrlen, options), - ARG(out_val_string_binarysink, error)) - -/* - * Accessors to retrieve the innards of a 'key_components'. - */ -FUNC(uint, key_components_count, ARG(val_keycomponents, kc)) -FUNC(opt_val_string_asciz_const, key_components_nth_name, - ARG(val_keycomponents, kc), ARG(uint, n)) -FUNC(opt_val_string, key_components_nth_str, - ARG(val_keycomponents, kc), ARG(uint, n)) -FUNC(opt_val_mpint, key_components_nth_mp, ARG(val_keycomponents, kc), - ARG(uint, n)) - -/* - * The ssh_cipher abstraction. The in-place encrypt and decrypt - * functions are wrapped to replace them with versions that take one - * string and return a separate string. - */ -FUNC(opt_val_cipher, ssh_cipher_new, ARG(cipheralg, alg)) -FUNC_WRAPPED(void, ssh_cipher_setiv, ARG(val_cipher, c), - ARG(val_string_ptrlen, iv)) -FUNC_WRAPPED(void, ssh_cipher_setkey, ARG(val_cipher, c), - ARG(val_string_ptrlen, key)) -FUNC_WRAPPED(val_string, ssh_cipher_encrypt, ARG(val_cipher, c), - ARG(val_string_ptrlen, blk)) -FUNC_WRAPPED(val_string, ssh_cipher_decrypt, ARG(val_cipher, c), - ARG(val_string_ptrlen, blk)) -FUNC_WRAPPED(val_string, ssh_cipher_encrypt_length, ARG(val_cipher, c), - ARG(val_string_ptrlen, blk), ARG(uint, seq)) -FUNC_WRAPPED(val_string, ssh_cipher_decrypt_length, ARG(val_cipher, c), - ARG(val_string_ptrlen, blk), ARG(uint, seq)) -FUNC(void, ssh_cipher_next_message, ARG(val_cipher, c)) - -/* - * Integer Diffie-Hellman. - */ -FUNC(val_dh, dh_setup_group, ARG(dh_group, group)) -FUNC(val_dh, dh_setup_gex, ARG(val_mpint, p), ARG(val_mpint, g)) -FUNC(uint, dh_modulus_bit_size, ARG(val_dh, ctx)) -FUNC(val_mpint, dh_create_e, ARG(val_dh, ctx)) -FUNC_WRAPPED(boolean, dh_validate_f, ARG(val_dh, ctx), ARG(val_mpint, f)) -FUNC(val_mpint, dh_find_K, ARG(val_dh, ctx), ARG(val_mpint, f)) - -/* - * Elliptic-curve Diffie-Hellman. - */ -FUNC(val_ecdh, ecdh_key_new, ARG(ecdh_alg, alg), ARG(boolean, is_server)) -FUNC(void, ecdh_key_getpublic, ARG(val_ecdh, key), - ARG(out_val_string_binarysink, pub)) -FUNC_WRAPPED(opt_val_string, ecdh_key_getkey, ARG(val_ecdh, key), - ARG(val_string_ptrlen, pub)) - -/* - * NTRU and its subroutines. - */ -FUNC_WRAPPED(int16_list, ntru_ring_multiply, ARG(int16_list, a), - ARG(int16_list, b), ARG(uint, p), ARG(uint, q)) -FUNC_WRAPPED(opt_int16_list, ntru_ring_invert, ARG(int16_list, r), - ARG(uint, p), ARG(uint, q)) -FUNC_WRAPPED(int16_list, ntru_mod3, ARG(int16_list, r), - ARG(uint, p), ARG(uint, q)) -FUNC_WRAPPED(int16_list, ntru_round3, ARG(int16_list, r), - ARG(uint, p), ARG(uint, q)) -FUNC_WRAPPED(int16_list, ntru_bias, ARG(int16_list, r), - ARG(uint, bias), ARG(uint, p), ARG(uint, q)) -FUNC_WRAPPED(int16_list, ntru_scale, ARG(int16_list, r), - ARG(uint, scale), ARG(uint, p), ARG(uint, q)) -FUNC_WRAPPED(val_ntruencodeschedule, ntru_encode_schedule, ARG(int16_list, ms)) -FUNC(uint, ntru_encode_schedule_length, ARG(val_ntruencodeschedule, sched)) -FUNC_WRAPPED(void, ntru_encode, ARG(val_ntruencodeschedule, sched), - ARG(int16_list, rs), ARG(out_val_string_binarysink, data)) -FUNC_WRAPPED(opt_int16_list, ntru_decode, ARG(val_ntruencodeschedule, sched), - ARG(val_string_ptrlen, data)) -FUNC_WRAPPED(int16_list, ntru_gen_short, ARG(uint, p), ARG(uint, w)) -FUNC(val_ntrukeypair, ntru_keygen, ARG(uint, p), ARG(uint, q), ARG(uint, w)) -FUNC_WRAPPED(int16_list, ntru_pubkey, ARG(val_ntrukeypair, keypair)) -FUNC_WRAPPED(int16_list, ntru_encrypt, ARG(int16_list, plaintext), - ARG(int16_list, pubkey), ARG(uint, p), ARG(uint, q)) -FUNC_WRAPPED(int16_list, ntru_decrypt, ARG(int16_list, ciphertext), - ARG(val_ntrukeypair, keypair)) - -/* - * RSA key exchange, and also the BinarySource get function - * get_ssh1_rsa_priv_agent, which is a convenient way to make an - * RSAKey for RSA kex testing purposes. - */ -FUNC(val_rsakex, ssh_rsakex_newkey, ARG(val_string_ptrlen, data)) -FUNC(uint, ssh_rsakex_klen, ARG(val_rsakex, key)) -FUNC(val_string, ssh_rsakex_encrypt, ARG(val_rsakex, key), ARG(hashalg, h), - ARG(val_string_ptrlen, plaintext)) -FUNC(opt_val_mpint, ssh_rsakex_decrypt, ARG(val_rsakex, key), ARG(hashalg, h), - ARG(val_string_ptrlen, ciphertext)) -FUNC(val_rsakex, get_rsa_ssh1_priv_agent, ARG(val_string_binarysource, src)) - -/* - * Bare RSA keys as used in SSH-1. The construction API functions - * write into an existing RSAKey object, so I've invented an 'rsa_new' - * function to make one in the first place. - */ -FUNC(val_rsa, rsa_new, VOID) -FUNC(void, get_rsa_ssh1_pub, ARG(val_string_binarysource, src), - ARG(val_rsa, key), ARG(rsaorder, order)) -FUNC(void, get_rsa_ssh1_priv, ARG(val_string_binarysource, src), - ARG(val_rsa, key)) -FUNC_WRAPPED(opt_val_string, rsa_ssh1_encrypt, ARG(val_string_ptrlen, data), - ARG(val_rsa, key)) -FUNC(val_mpint, rsa_ssh1_decrypt, ARG(val_mpint, input), ARG(val_rsa, key)) -FUNC_WRAPPED(val_string, rsa_ssh1_decrypt_pkcs1, ARG(val_mpint, input), - ARG(val_rsa, key)) -FUNC(val_string_asciz, rsastr_fmt, ARG(val_rsa, key)) -FUNC(val_string_asciz, rsa_ssh1_fingerprint, ARG(val_rsa, key)) -FUNC(void, rsa_ssh1_public_blob, ARG(out_val_string_binarysink, blob), - ARG(val_rsa, key), ARG(rsaorder, order)) -FUNC(int, rsa_ssh1_public_blob_len, ARG(val_string_ptrlen, data)) -FUNC(void, rsa_ssh1_private_blob_agent, ARG(out_val_string_binarysink, blob), - ARG(val_rsa, key)) - -/* - * The PRNG type. Similarly to hashes and MACs, I've invented an extra - * function prng_seed_update for putting seed data into the PRNG's - * exposed BinarySink. - */ -FUNC(val_prng, prng_new, ARG(hashalg, hashalg)) -FUNC(void, prng_seed_begin, ARG(val_prng, pr)) -FUNC(void, prng_seed_update, ARG(val_prng, pr), ARG(val_string_ptrlen, data)) -FUNC(void, prng_seed_finish, ARG(val_prng, pr)) -FUNC_WRAPPED(val_string, prng_read, ARG(val_prng, pr), ARG(uint, size)) -FUNC(void, prng_add_entropy, ARG(val_prng, pr), ARG(uint, source_id), - ARG(val_string_ptrlen, data)) - -/* - * Key load/save functions, or rather, the BinarySource / strbuf API - * that sits just inside the file I/O versions. - */ -FUNC(boolean, ppk_encrypted_s, ARG(val_string_binarysource, src), - ARG(out_opt_val_string_asciz, comment)) -FUNC(boolean, rsa1_encrypted_s, ARG(val_string_binarysource, src), - ARG(out_opt_val_string_asciz, comment)) -FUNC(boolean, ppk_loadpub_s, ARG(val_string_binarysource, src), - ARG(out_opt_val_string_asciz, algorithm), - ARG(out_val_string_binarysink, blob), - ARG(out_opt_val_string_asciz, comment), - ARG(out_opt_val_string_asciz_const, error)) -FUNC(int, rsa1_loadpub_s, ARG(val_string_binarysource, src), - ARG(out_val_string_binarysink, blob), - ARG(out_opt_val_string_asciz, comment), - ARG(out_opt_val_string_asciz_const, error)) -FUNC_WRAPPED(opt_val_key, ppk_load_s, ARG(val_string_binarysource, src), - ARG(out_opt_val_string_asciz, comment), - ARG(opt_val_string_asciz, passphrase), - ARG(out_opt_val_string_asciz_const, error)) -FUNC_WRAPPED(int, rsa1_load_s, ARG(val_string_binarysource, src), - ARG(val_rsa, key), ARG(out_opt_val_string_asciz, comment), - ARG(opt_val_string_asciz, passphrase), - ARG(out_opt_val_string_asciz_const, error)) -FUNC_WRAPPED(val_string, ppk_save_sb, ARG(val_key, key), - ARG(opt_val_string_asciz, comment), - ARG(opt_val_string_asciz, passphrase), ARG(uint, fmt_version), - ARG(argon2flavour, flavour), ARG(uint, mem), ARG(uint, passes), - ARG(uint, parallel)) -FUNC_WRAPPED(val_string, rsa1_save_sb, ARG(val_rsa, key), - ARG(opt_val_string_asciz, comment), - ARG(opt_val_string_asciz, passphrase)) - -FUNC(val_string_asciz, ssh2_fingerprint_blob, ARG(val_string_ptrlen, blob), - ARG(fptype, fptype)) - -/* - * Password hashing. - */ -FUNC_WRAPPED(val_string, argon2, ARG(argon2flavour, flavour), ARG(uint, mem), - ARG(uint, passes), ARG(uint, parallel), ARG(uint, taglen), - ARG(val_string_ptrlen, P), ARG(val_string_ptrlen, S), - ARG(val_string_ptrlen, K), ARG(val_string_ptrlen, X)) -FUNC(val_string, argon2_long_hash, ARG(uint, length), - ARG(val_string_ptrlen, data)) -FUNC_WRAPPED(val_string, openssh_bcrypt, ARG(val_string_ptrlen, passphrase), - ARG(val_string_ptrlen, salt), ARG(uint, rounds), - ARG(uint, outbytes)) - -/* - * Key generation functions. - */ -FUNC_WRAPPED(val_key, rsa_generate, ARG(uint, bits), ARG(boolean, strong), - ARG(val_pgc, pgc)) -FUNC_WRAPPED(val_key, dsa_generate, ARG(uint, bits), ARG(val_pgc, pgc)) -FUNC_WRAPPED(opt_val_key, ecdsa_generate, ARG(uint, bits)) -FUNC_WRAPPED(opt_val_key, eddsa_generate, ARG(uint, bits)) -FUNC(val_rsa, rsa1_generate, ARG(uint, bits), ARG(boolean, strong), - ARG(val_pgc, pgc)) -FUNC(val_pgc, primegen_new_context, ARG(primegenpolicy, policy)) -FUNC_WRAPPED(opt_val_mpint, primegen_generate, ARG(val_pgc, ctx), - ARG(consumed_val_pcs, pcs)) -FUNC(val_string, primegen_mpu_certificate, ARG(val_pgc, ctx), ARG(val_mpint, p)) -FUNC(val_pcs, pcs_new, ARG(uint, bits)) -FUNC(val_pcs, pcs_new_with_firstbits, ARG(uint, bits), ARG(uint, first), - ARG(uint, nfirst)) -FUNC(void, pcs_require_residue, ARG(val_pcs, s), ARG(val_mpint, mod), - ARG(val_mpint, res)) -FUNC(void, pcs_require_residue_1, ARG(val_pcs, s), ARG(val_mpint, mod)) -FUNC(void, pcs_require_residue_1_mod_prime, ARG(val_pcs, s), - ARG(val_mpint, mod)) -FUNC(void, pcs_avoid_residue_small, ARG(val_pcs, s), ARG(uint, mod), - ARG(uint, res)) -FUNC(void, pcs_try_sophie_germain, ARG(val_pcs, s)) -FUNC(void, pcs_set_oneshot, ARG(val_pcs, s)) -FUNC(void, pcs_ready, ARG(val_pcs, s)) -FUNC(void, pcs_inspect, ARG(val_pcs, pcs), ARG(out_val_mpint, limit_out), - ARG(out_val_mpint, factor_out), ARG(out_val_mpint, addend_out)) -FUNC(val_mpint, pcs_generate, ARG(val_pcs, s)) -FUNC(val_pockle, pockle_new, VOID) -FUNC(uint, pockle_mark, ARG(val_pockle, pockle)) -FUNC(void, pockle_release, ARG(val_pockle, pockle), ARG(uint, mark)) -FUNC(pocklestatus, pockle_add_small_prime, ARG(val_pockle, pockle), - ARG(val_mpint, p)) -FUNC_WRAPPED(pocklestatus, pockle_add_prime, ARG(val_pockle, pockle), - ARG(val_mpint, p), ARG(mpint_list, factors), - ARG(val_mpint, witness)) -FUNC(val_string, pockle_mpu, ARG(val_pockle, pockle), ARG(val_mpint, p)) -FUNC(val_millerrabin, miller_rabin_new, ARG(val_mpint, p)) -FUNC(mr_result, miller_rabin_test, ARG(val_millerrabin, mr), ARG(val_mpint, w)) - -/* - * Miscellaneous. - */ -FUNC(val_wpoint, ecdsa_public, ARG(val_mpint, private_key), ARG(keyalg, alg)) -FUNC(val_epoint, eddsa_public, ARG(val_mpint, private_key), ARG(keyalg, alg)) -FUNC_WRAPPED(val_string, des_encrypt_xdmauth, ARG(val_string_ptrlen, key), - ARG(val_string_ptrlen, blk)) -FUNC_WRAPPED(val_string, des_decrypt_xdmauth, ARG(val_string_ptrlen, key), - ARG(val_string_ptrlen, blk)) -FUNC_WRAPPED(val_string, des3_encrypt_pubkey, ARG(val_string_ptrlen, key), - ARG(val_string_ptrlen, blk)) -FUNC_WRAPPED(val_string, des3_decrypt_pubkey, ARG(val_string_ptrlen, key), - ARG(val_string_ptrlen, blk)) -FUNC_WRAPPED(val_string, des3_encrypt_pubkey_ossh, ARG(val_string_ptrlen, key), - ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk)) -FUNC_WRAPPED(val_string, des3_decrypt_pubkey_ossh, ARG(val_string_ptrlen, key), - ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk)) -FUNC_WRAPPED(val_string, aes256_encrypt_pubkey, ARG(val_string_ptrlen, key), - ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk)) -FUNC_WRAPPED(val_string, aes256_decrypt_pubkey, ARG(val_string_ptrlen, key), - ARG(val_string_ptrlen, iv), ARG(val_string_ptrlen, blk)) -FUNC(uint, crc32_rfc1662, ARG(val_string_ptrlen, data)) -FUNC(uint, crc32_ssh1, ARG(val_string_ptrlen, data)) -FUNC(uint, crc32_update, ARG(uint, crc_input), ARG(val_string_ptrlen, data)) -FUNC(boolean, crcda_detect, ARG(val_string_ptrlen, packet), - ARG(val_string_ptrlen, iv)) -FUNC(val_string, get_implementations_commasep, ARG(val_string_ptrlen, alg)) -FUNC(void, http_digest_response, ARG(out_val_string_binarysink, response), - ARG(val_string_ptrlen, username), ARG(val_string_ptrlen, password), - ARG(val_string_ptrlen, realm), ARG(val_string_ptrlen, method), - ARG(val_string_ptrlen, uri), ARG(val_string_ptrlen, qop), - ARG(val_string_ptrlen, nonce), ARG(val_string_ptrlen, opaque), - ARG(uint, nonce_count), ARG(httpdigesthash, hash), - ARG(boolean, hash_username)) - -/* - * These functions aren't part of PuTTY's own API, but are additions - * by testcrypt itself for administrative purposes. - */ -FUNC(void, random_queue, ARG(val_string_ptrlen, data)) -FUNC(uint, random_queue_len, VOID) -FUNC(void, random_make_prng, ARG(hashalg, hashalg), - ARG(val_string_ptrlen, seed)) -FUNC(void, random_clear, VOID) diff --git a/test/testcrypt.c b/test/testcrypt.c deleted file mode 100644 index 3b495bbab..000000000 --- a/test/testcrypt.c +++ /dev/null @@ -1,1714 +0,0 @@ -/* - * testcrypt: a standalone test program that provides direct access to - * PuTTY's cryptography and mp_int code. - */ - -/* - * This program speaks a line-oriented protocol on standard input and - * standard output. It's a half-duplex protocol: it expects to read - * one line of command, and then produce a fixed amount of output - * (namely a line containing a decimal integer, followed by that many - * lines each containing one return value). - * - * The protocol is human-readable enough to make it debuggable, but - * verbose enough that you probably wouldn't want to speak it by hand - * at any great length. The Python program test/testcrypt.py wraps it - * to give a more useful user-facing API, by invoking this binary as a - * subprocess. - * - * (I decided that was a better idea than making this program an - * actual Python module, partly because you can rewrap the same binary - * in another scripting language if you prefer, but mostly because - * it's easy to attach a debugger to testcrypt or to run it under - * sanitisers or valgrind or what have you.) - */ - -#include -#include -#include -#include -#include - -#include "defs.h" -#include "ssh.h" -#include "sshkeygen.h" -#include "misc.h" -#include "mpint.h" -#include "crypto/ecc.h" -#include "crypto/ntru.h" -#include "proxy/cproxy.h" - -static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "testcrypt: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); - exit(1); -} - -void out_of_memory(void) { fatal_error("out of memory"); } - -static bool old_keyfile_warning_given; -void old_keyfile_warning(void) { old_keyfile_warning_given = true; } - -static bufchain random_data_queue; -static prng *test_prng; -void random_read(void *buf, size_t size) -{ - if (test_prng) { - prng_read(test_prng, buf, size); - } else { - if (!bufchain_try_fetch_consume(&random_data_queue, buf, size)) - fatal_error("No random data in queue"); - } -} - -uint64_t prng_reseed_time_ms(void) -{ - static uint64_t previous_time = 0; - return previous_time += 200; -} - -#define VALUE_TYPES(X) \ - X(string, strbuf *, strbuf_free(v)) \ - X(mpint, mp_int *, mp_free(v)) \ - X(modsqrt, ModsqrtContext *, modsqrt_free(v)) \ - X(monty, MontyContext *, monty_free(v)) \ - X(wcurve, WeierstrassCurve *, ecc_weierstrass_curve_free(v)) \ - X(wpoint, WeierstrassPoint *, ecc_weierstrass_point_free(v)) \ - X(mcurve, MontgomeryCurve *, ecc_montgomery_curve_free(v)) \ - X(mpoint, MontgomeryPoint *, ecc_montgomery_point_free(v)) \ - X(ecurve, EdwardsCurve *, ecc_edwards_curve_free(v)) \ - X(epoint, EdwardsPoint *, ecc_edwards_point_free(v)) \ - X(hash, ssh_hash *, ssh_hash_free(v)) \ - X(key, ssh_key *, ssh_key_free(v)) \ - X(cipher, ssh_cipher *, ssh_cipher_free(v)) \ - X(mac, ssh2_mac *, ssh2_mac_free(v)) \ - X(dh, dh_ctx *, dh_cleanup(v)) \ - X(ecdh, ecdh_key *, ecdh_key_free(v)) \ - X(rsakex, RSAKey *, ssh_rsakex_freekey(v)) \ - X(rsa, RSAKey *, rsa_free(v)) \ - X(prng, prng *, prng_free(v)) \ - X(keycomponents, key_components *, key_components_free(v)) \ - X(pcs, PrimeCandidateSource *, pcs_free(v)) \ - X(pgc, PrimeGenerationContext *, primegen_free_context(v)) \ - X(pockle, Pockle *, pockle_free(v)) \ - X(millerrabin, MillerRabin *, miller_rabin_free(v)) \ - X(ntrukeypair, NTRUKeyPair *, ntru_keypair_free(v)) \ - X(ntruencodeschedule, NTRUEncodeSchedule *, ntru_encode_schedule_free(v)) \ - /* end of list */ - -typedef struct Value Value; - -enum ValueType { -#define VALTYPE_ENUM(n,t,f) VT_##n, - VALUE_TYPES(VALTYPE_ENUM) -#undef VALTYPE_ENUM -}; - -typedef enum ValueType ValueType; - -static const char *const type_names[] = { -#define VALTYPE_NAME(n,t,f) #n, - VALUE_TYPES(VALTYPE_NAME) -#undef VALTYPE_NAME -}; - -#define VALTYPE_TYPEDEF(n,t,f) \ - typedef t TD_val_##n; \ - typedef t *TD_out_val_##n; -VALUE_TYPES(VALTYPE_TYPEDEF) -#undef VALTYPE_TYPEDEF - -struct Value { - /* - * Protocol identifier assigned to this value when it was created. - * Lives in the same malloced block as this Value object itself. - */ - ptrlen id; - - /* - * Type of the value. - */ - ValueType type; - - /* - * Union of all the things it could hold. - */ - union { -#define VALTYPE_UNION(n,t,f) t vu_##n; - VALUE_TYPES(VALTYPE_UNION) -#undef VALTYPE_UNION - - char *bare_string; - }; -}; - -static int valuecmp(void *av, void *bv) -{ - Value *a = (Value *)av, *b = (Value *)bv; - return ptrlen_strcmp(a->id, b->id); -} - -static int valuefind(void *av, void *bv) -{ - ptrlen *a = (ptrlen *)av; - Value *b = (Value *)bv; - return ptrlen_strcmp(*a, b->id); -} - -static tree234 *values; - -static Value *value_new(ValueType vt) -{ - static uint64_t next_index = 0; - - char *name = dupprintf("%s%"PRIu64, type_names[vt], next_index++); - size_t namelen = strlen(name); - - Value *val = snew_plus(Value, namelen+1); - memcpy(snew_plus_get_aux(val), name, namelen+1); - val->id.ptr = snew_plus_get_aux(val); - val->id.len = namelen; - val->type = vt; - - Value *added = add234(values, val); - assert(added == val); - - sfree(name); - - return val; -} - -#define VALTYPE_RETURNFN(n,t,f) \ - void return_val_##n(strbuf *out, t v) { \ - Value *val = value_new(VT_##n); \ - val->vu_##n = v; \ - put_datapl(out, val->id); \ - put_byte(out, '\n'); \ - } -VALUE_TYPES(VALTYPE_RETURNFN) -#undef VALTYPE_RETURNFN - -static ptrlen get_word(BinarySource *in) -{ - ptrlen toret; - toret.ptr = get_ptr(in); - toret.len = 0; - while (get_avail(in) && get_byte(in) != ' ') - toret.len++; - return toret; -} - -typedef uintmax_t TD_uint; -typedef bool TD_boolean; -typedef ptrlen TD_val_string_ptrlen; -typedef char *TD_val_string_asciz; -typedef BinarySource *TD_val_string_binarysource; -typedef unsigned *TD_out_uint; -typedef BinarySink *TD_out_val_string_binarysink; -typedef const char *TD_opt_val_string_asciz; -typedef char **TD_out_val_string_asciz; -typedef char **TD_out_opt_val_string_asciz; -typedef const char **TD_out_opt_val_string_asciz_const; -typedef const ssh_hashalg *TD_hashalg; -typedef const ssh2_macalg *TD_macalg; -typedef const ssh_keyalg *TD_keyalg; -typedef const ssh_cipheralg *TD_cipheralg; -typedef const ssh_kex *TD_dh_group; -typedef const ssh_kex *TD_ecdh_alg; -typedef RsaSsh1Order TD_rsaorder; -typedef key_components *TD_keycomponents; -typedef const PrimeGenerationPolicy *TD_primegenpolicy; -typedef struct mpint_list TD_mpint_list; -typedef struct int16_list *TD_int16_list; -typedef PockleStatus TD_pocklestatus; -typedef struct mr_result TD_mr_result; -typedef Argon2Flavour TD_argon2flavour; -typedef FingerprintType TD_fptype; -typedef HttpDigestHash TD_httpdigesthash; - -#define BEGIN_ENUM_TYPE(name) \ - static bool enum_translate_##name(ptrlen valname, TD_##name *out) { \ - static const struct { \ - const char *key; \ - TD_##name value; \ - } mapping[] = { -#define ENUM_VALUE(name, value) {name, value}, -#define END_ENUM_TYPE(name) \ - }; \ - for (size_t i = 0; i < lenof(mapping); i++) \ - if (ptrlen_eq_string(valname, mapping[i].key)) { \ - if (out) \ - *out = mapping[i].value; \ - return true; \ - } \ - return false; \ - } \ - \ - static TD_##name get_##name(BinarySource *in) { \ - ptrlen valname = get_word(in); \ - TD_##name out; \ - if (enum_translate_##name(valname, &out)) \ - return out; \ - else \ - fatal_error("%s '%.*s': not found", \ - #name, PTRLEN_PRINTF(valname)); \ - } -#include "testcrypt-enum.h" -#undef BEGIN_ENUM_TYPE -#undef ENUM_VALUE -#undef END_ENUM_TYPE - -static uintmax_t get_uint(BinarySource *in) -{ - ptrlen word = get_word(in); - char *string = mkstr(word); - uintmax_t toret = strtoumax(string, NULL, 0); - sfree(string); - return toret; -} - -static bool get_boolean(BinarySource *in) -{ - return ptrlen_eq_string(get_word(in), "true"); -} - -static Value *lookup_value(ptrlen word) -{ - Value *val = find234(values, &word, valuefind); - if (!val) - fatal_error("id '%.*s': not found", PTRLEN_PRINTF(word)); - return val; -} - -static Value *get_value(BinarySource *in) -{ - return lookup_value(get_word(in)); -} - -typedef void (*finaliser_fn_t)(strbuf *out, void *ctx); -struct finaliser { - finaliser_fn_t fn; - void *ctx; -}; - -static struct finaliser *finalisers; -static size_t nfinalisers, finalisersize; - -static void add_finaliser(finaliser_fn_t fn, void *ctx) -{ - sgrowarray(finalisers, finalisersize, nfinalisers); - finalisers[nfinalisers].fn = fn; - finalisers[nfinalisers].ctx = ctx; - nfinalisers++; -} - -static void run_finalisers(strbuf *out) -{ - for (size_t i = 0; i < nfinalisers; i++) - finalisers[i].fn(out, finalisers[i].ctx); - nfinalisers = 0; -} - -static void finaliser_return_value(strbuf *out, void *ctx) -{ - Value *val = (Value *)ctx; - put_datapl(out, val->id); - put_byte(out, '\n'); -} - -static void finaliser_sfree(strbuf *out, void *ctx) -{ - sfree(ctx); -} - -#define VALTYPE_GETFN(n,t,f) \ - static Value *unwrap_value_##n(Value *val) { \ - ValueType expected = VT_##n; \ - if (expected != val->type) \ - fatal_error("id '%.*s': expected %s, got %s", \ - PTRLEN_PRINTF(val->id), \ - type_names[expected], type_names[val->type]); \ - return val; \ - } \ - static Value *get_value_##n(BinarySource *in) { \ - return unwrap_value_##n(get_value(in)); \ - } \ - static t get_val_##n(BinarySource *in) { \ - return get_value_##n(in)->vu_##n; \ - } -VALUE_TYPES(VALTYPE_GETFN) -#undef VALTYPE_GETFN - -static ptrlen get_val_string_ptrlen(BinarySource *in) -{ - return ptrlen_from_strbuf(get_val_string(in)); -} - -static char *get_val_string_asciz(BinarySource *in) -{ - return get_val_string(in)->s; -} - -static strbuf *get_opt_val_string(BinarySource *in); - -static char *get_opt_val_string_asciz(BinarySource *in) -{ - strbuf *sb = get_opt_val_string(in); - return sb ? sb->s : NULL; -} - -static mp_int **get_out_val_mpint(BinarySource *in) -{ - Value *val = value_new(VT_mpint); - add_finaliser(finaliser_return_value, val); - return &val->vu_mpint; -} - -struct mpint_list { - size_t n; - mp_int **integers; -}; - -static struct mpint_list get_mpint_list(BinarySource *in) -{ - size_t n = get_uint(in); - - struct mpint_list mpl; - mpl.n = n; - - mpl.integers = snewn(n, mp_int *); - for (size_t i = 0; i < n; i++) - mpl.integers[i] = get_val_mpint(in); - - add_finaliser(finaliser_sfree, mpl.integers); - return mpl; -} - -typedef struct int16_list { - size_t n; - uint16_t *integers; -} int16_list; - -static void finaliser_int16_list_free(strbuf *out, void *vlist) -{ - int16_list *list = (int16_list *)vlist; - sfree(list->integers); - sfree(list); -} - -static int16_list *make_int16_list(size_t n) -{ - int16_list *list = snew(int16_list); - list->n = n; - list->integers = snewn(n, uint16_t); - add_finaliser(finaliser_int16_list_free, list); - return list; -} - -static int16_list *get_int16_list(BinarySource *in) -{ - size_t n = get_uint(in); - int16_list *list = make_int16_list(n); - for (size_t i = 0; i < n; i++) - list->integers[i] = get_uint(in); - return list; -} - -static void return_int16_list(strbuf *out, int16_list *list) -{ - for (size_t i = 0; i < list->n; i++) { - if (i > 0) - put_byte(out, ','); - put_fmt(out, "%d", (int)(int16_t)list->integers[i]); - } - put_byte(out, '\n'); -} - -static void finaliser_return_uint(strbuf *out, void *ctx) -{ - unsigned *uval = (unsigned *)ctx; - put_fmt(out, "%u\n", *uval); - sfree(uval); -} - -static unsigned *get_out_uint(BinarySource *in) -{ - unsigned *uval = snew(unsigned); - add_finaliser(finaliser_return_uint, uval); - return uval; -} - -static BinarySink *get_out_val_string_binarysink(BinarySource *in) -{ - Value *val = value_new(VT_string); - val->vu_string = strbuf_new(); - add_finaliser(finaliser_return_value, val); - return BinarySink_UPCAST(val->vu_string); -} - -static void return_val_string_asciz_const(strbuf *out, const char *s); -static void return_val_string_asciz(strbuf *out, char *s); - -static void finaliser_return_opt_string_asciz(strbuf *out, void *ctx) -{ - char **valp = (char **)ctx; - char *val = *valp; - sfree(valp); - if (!val) - put_fmt(out, "NULL\n"); - else - return_val_string_asciz(out, val); -} - -static char **get_out_opt_val_string_asciz(BinarySource *in) -{ - char **valp = snew(char *); - *valp = NULL; - add_finaliser(finaliser_return_opt_string_asciz, valp); - return valp; -} - -static void finaliser_return_opt_string_asciz_const(strbuf *out, void *ctx) -{ - const char **valp = (const char **)ctx; - const char *val = *valp; - sfree(valp); - if (!val) - put_fmt(out, "NULL\n"); - else - return_val_string_asciz_const(out, val); -} - -static const char **get_out_opt_val_string_asciz_const(BinarySource *in) -{ - const char **valp = snew(const char *); - *valp = NULL; - add_finaliser(finaliser_return_opt_string_asciz_const, valp); - return valp; -} - -static BinarySource *get_val_string_binarysource(BinarySource *in) -{ - strbuf *sb = get_val_string(in); - BinarySource *src = snew(BinarySource); - BinarySource_BARE_INIT(src, sb->u, sb->len); - add_finaliser(finaliser_sfree, src); - return src; -} - -#define GET_CONSUMED_FN(type) \ - typedef TD_val_##type TD_consumed_val_##type; \ - static TD_val_##type get_consumed_val_##type(BinarySource *in) \ - { \ - Value *val = get_value_##type(in); \ - TD_val_##type toret = val->vu_##type; \ - del234(values, val); \ - sfree(val); \ - return toret; \ - } -GET_CONSUMED_FN(hash) -GET_CONSUMED_FN(pcs) - -static void return_int(strbuf *out, intmax_t u) -{ - put_fmt(out, "%"PRIdMAX"\n", u); -} - -static void return_uint(strbuf *out, uintmax_t u) -{ - put_fmt(out, "0x%"PRIXMAX"\n", u); -} - -static void return_boolean(strbuf *out, bool b) -{ - put_fmt(out, "%s\n", b ? "true" : "false"); -} - -static void return_pocklestatus(strbuf *out, PockleStatus status) -{ - switch (status) { - default: - put_fmt(out, "POCKLE_BAD_STATUS_VALUE\n"); - break; - -#define STATUS_CASE(id) \ - case id: \ - put_fmt(out, "%s\n", #id); \ - break; - - POCKLE_STATUSES(STATUS_CASE); - -#undef STATUS_CASE - - } -} - -static void return_mr_result(strbuf *out, struct mr_result result) -{ - if (!result.passed) - put_fmt(out, "failed\n"); - else if (!result.potential_primitive_root) - put_fmt(out, "passed\n"); - else - put_fmt(out, "passed+ppr\n"); -} - -static void return_val_string_asciz_const(strbuf *out, const char *s) -{ - strbuf *sb = strbuf_new(); - put_data(sb, s, strlen(s)); - return_val_string(out, sb); -} - -static void return_val_string_asciz(strbuf *out, char *s) -{ - return_val_string_asciz_const(out, s); - sfree(s); -} - -#define NULLABLE_RETURN_WRAPPER(type_name, c_type) \ - static void return_opt_##type_name(strbuf *out, c_type ptr) \ - { \ - if (!ptr) \ - put_fmt(out, "NULL\n"); \ - else \ - return_##type_name(out, ptr); \ - } - -NULLABLE_RETURN_WRAPPER(val_string, strbuf *) -NULLABLE_RETURN_WRAPPER(val_string_asciz, char *) -NULLABLE_RETURN_WRAPPER(val_string_asciz_const, const char *) -NULLABLE_RETURN_WRAPPER(val_cipher, ssh_cipher *) -NULLABLE_RETURN_WRAPPER(val_mac, ssh2_mac *) -NULLABLE_RETURN_WRAPPER(val_hash, ssh_hash *) -NULLABLE_RETURN_WRAPPER(val_key, ssh_key *) -NULLABLE_RETURN_WRAPPER(val_mpint, mp_int *) -NULLABLE_RETURN_WRAPPER(int16_list, int16_list *) - -static void handle_hello(BinarySource *in, strbuf *out) -{ - put_fmt(out, "hello, world\n"); -} - -static void rsa_free(RSAKey *rsa) -{ - freersakey(rsa); - sfree(rsa); -} - -static void free_value(Value *val) -{ - switch (val->type) { -#define VALTYPE_FREE(n,t,f) case VT_##n: { t v = val->vu_##n; (f); break; } - VALUE_TYPES(VALTYPE_FREE) -#undef VALTYPE_FREE - } - sfree(val); -} - -static void handle_free(BinarySource *in, strbuf *out) -{ - Value *val = get_value(in); - del234(values, val); - free_value(val); -} - -static void handle_newstring(BinarySource *in, strbuf *out) -{ - strbuf *sb = strbuf_new(); - while (get_avail(in)) { - char c = get_byte(in); - if (c == '%') { - char hex[3]; - hex[0] = get_byte(in); - if (hex[0] != '%') { - hex[1] = get_byte(in); - hex[2] = '\0'; - c = strtoul(hex, NULL, 16); - } - } - put_byte(sb, c); - } - return_val_string(out, sb); -} - -static void handle_getstring(BinarySource *in, strbuf *out) -{ - strbuf *sb = get_val_string(in); - for (size_t i = 0; i < sb->len; i++) { - char c = sb->s[i]; - if (c > ' ' && c < 0x7F && c != '%') { - put_byte(out, c); - } else { - put_fmt(out, "%%%02X", 0xFFU & (unsigned)c); - } - } - put_byte(out, '\n'); -} - -static void handle_mp_literal(BinarySource *in, strbuf *out) -{ - ptrlen pl = get_word(in); - char *str = mkstr(pl); - mp_int *mp = mp__from_string_literal(str); - sfree(str); - return_val_mpint(out, mp); -} - -static void handle_mp_dump(BinarySource *in, strbuf *out) -{ - mp_int *mp = get_val_mpint(in); - for (size_t i = mp_max_bytes(mp); i-- > 0 ;) - put_fmt(out, "%02X", mp_get_byte(mp, i)); - put_byte(out, '\n'); -} - -static void handle_checkenum(BinarySource *in, strbuf *out) -{ - ptrlen type = get_word(in); - ptrlen value = get_word(in); - bool ok = false; - - #define BEGIN_ENUM_TYPE(name) \ - if (ptrlen_eq_string(type, #name)) \ - ok = enum_translate_##name(value, NULL); - #define ENUM_VALUE(name, value) - #define END_ENUM_TYPE(name) - #include "testcrypt-enum.h" - #undef BEGIN_ENUM_TYPE - #undef ENUM_VALUE - #undef END_ENUM_TYPE - - put_dataz(out, ok ? "ok\n" : "bad\n"); -} - -static void random_queue(ptrlen pl) -{ - bufchain_add(&random_data_queue, pl.ptr, pl.len); -} - -static size_t random_queue_len(void) -{ - return bufchain_size(&random_data_queue); -} - -static void random_clear(void) -{ - if (test_prng) { - prng_free(test_prng); - test_prng = NULL; - } - - bufchain_clear(&random_data_queue); -} - -static void random_make_prng(const ssh_hashalg *hashalg, ptrlen seed) -{ - random_clear(); - - test_prng = prng_new(hashalg); - prng_seed_begin(test_prng); - put_datapl(test_prng, seed); - prng_seed_finish(test_prng); -} - -mp_int *monty_identity_wrapper(MontyContext *mc) -{ - return mp_copy(monty_identity(mc)); -} - -mp_int *monty_modulus_wrapper(MontyContext *mc) -{ - return mp_copy(monty_modulus(mc)); -} - -strbuf *ssh_hash_digest_wrapper(ssh_hash *h) -{ - strbuf *sb = strbuf_new(); - void *p = strbuf_append(sb, ssh_hash_alg(h)->hlen); - ssh_hash_digest(h, p); - return sb; -} - -strbuf *ssh_hash_final_wrapper(ssh_hash *h) -{ - strbuf *sb = strbuf_new(); - void *p = strbuf_append(sb, ssh_hash_alg(h)->hlen); - ssh_hash_final(h, p); - return sb; -} - -void ssh_cipher_setiv_wrapper(ssh_cipher *c, ptrlen iv) -{ - if (iv.len != ssh_cipher_alg(c)->blksize) - fatal_error("ssh_cipher_setiv: needs exactly %d bytes", - ssh_cipher_alg(c)->blksize); - ssh_cipher_setiv(c, iv.ptr); -} - -void ssh_cipher_setkey_wrapper(ssh_cipher *c, ptrlen key) -{ - if (key.len != ssh_cipher_alg(c)->padded_keybytes) - fatal_error("ssh_cipher_setkey: needs exactly %d bytes", - ssh_cipher_alg(c)->padded_keybytes); - ssh_cipher_setkey(c, key.ptr); -} - -strbuf *ssh_cipher_encrypt_wrapper(ssh_cipher *c, ptrlen input) -{ - if (input.len % ssh_cipher_alg(c)->blksize) - fatal_error("ssh_cipher_encrypt: needs a multiple of %d bytes", - ssh_cipher_alg(c)->blksize); - strbuf *sb = strbuf_dup(input); - ssh_cipher_encrypt(c, sb->u, sb->len); - return sb; -} - -strbuf *ssh_cipher_decrypt_wrapper(ssh_cipher *c, ptrlen input) -{ - if (input.len % ssh_cipher_alg(c)->blksize) - fatal_error("ssh_cipher_decrypt: needs a multiple of %d bytes", - ssh_cipher_alg(c)->blksize); - strbuf *sb = strbuf_dup(input); - ssh_cipher_decrypt(c, sb->u, sb->len); - return sb; -} - -strbuf *ssh_cipher_encrypt_length_wrapper(ssh_cipher *c, ptrlen input, - unsigned long seq) -{ - if (input.len != 4) - fatal_error("ssh_cipher_encrypt_length: needs exactly 4 bytes"); - strbuf *sb = strbuf_dup(input); - ssh_cipher_encrypt_length(c, sb->u, sb->len, seq); - return sb; -} - -strbuf *ssh_cipher_decrypt_length_wrapper(ssh_cipher *c, ptrlen input, - unsigned long seq) -{ - if (input.len != 4) - fatal_error("ssh_cipher_decrypt_length: needs exactly 4 bytes"); - strbuf *sb = strbuf_dup(input); - ssh_cipher_decrypt_length(c, sb->u, sb->len, seq); - return sb; -} - -strbuf *ssh2_mac_genresult_wrapper(ssh2_mac *m) -{ - strbuf *sb = strbuf_new(); - void *u = strbuf_append(sb, ssh2_mac_alg(m)->len); - ssh2_mac_genresult(m, u); - return sb; -} - -ssh_key *ssh_key_base_key_wrapper(ssh_key *key) -{ - /* To avoid having to explain the borrowed reference to Python, - * just clone the key unconditionally */ - return ssh_key_clone(ssh_key_base_key(key)); -} - -void ssh_key_ca_public_blob_wrapper(ssh_key *key, BinarySink *out) -{ - /* Wrap to avoid null-pointer dereference */ - if (!key->vt->is_certificate) - fatal_error("ssh_key_ca_public_blob: needs a certificate"); - ssh_key_ca_public_blob(key, out); -} - -void ssh_key_cert_id_string_wrapper(ssh_key *key, BinarySink *out) -{ - /* Wrap to avoid null-pointer dereference */ - if (!key->vt->is_certificate) - fatal_error("ssh_key_cert_id_string: needs a certificate"); - ssh_key_cert_id_string(key, out); -} - -static bool ssh_key_check_cert_wrapper( - ssh_key *key, bool host, ptrlen principal, uint64_t time, ptrlen optstr, - BinarySink *error) -{ - /* Wrap to avoid null-pointer dereference */ - if (!key->vt->is_certificate) - fatal_error("ssh_key_cert_id_string: needs a certificate"); - - ca_options opts; - opts.permit_rsa_sha1 = true; - opts.permit_rsa_sha256 = true; - opts.permit_rsa_sha512 = true; - - while (optstr.len) { - ptrlen word = ptrlen_get_word(&optstr, ","); - ptrlen key = word, value = PTRLEN_LITERAL(""); - const char *comma = memchr(word.ptr, '=', word.len); - if (comma) { - key.len = comma - (const char *)word.ptr; - value.ptr = comma + 1; - value.len = word.len - key.len - 1; - } - - if (ptrlen_eq_string(key, "permit_rsa_sha1")) - opts.permit_rsa_sha1 = ptrlen_eq_string(value, "true"); - if (ptrlen_eq_string(key, "permit_rsa_sha256")) - opts.permit_rsa_sha256 = ptrlen_eq_string(value, "true"); - if (ptrlen_eq_string(key, "permit_rsa_sha512")) - opts.permit_rsa_sha512 = ptrlen_eq_string(value, "true"); - } - - return ssh_key_check_cert(key, host, principal, time, &opts, error); -} - -bool dh_validate_f_wrapper(dh_ctx *dh, mp_int *f) -{ - return dh_validate_f(dh, f) == NULL; -} - -void ssh_hash_update(ssh_hash *h, ptrlen pl) -{ - put_datapl(h, pl); -} - -void ssh2_mac_update(ssh2_mac *m, ptrlen pl) -{ - put_datapl(m, pl); -} - -static RSAKey *rsa_new(void) -{ - RSAKey *rsa = snew(RSAKey); - memset(rsa, 0, sizeof(RSAKey)); - return rsa; -} - -strbuf *ecdh_key_getkey_wrapper(ecdh_key *ek, ptrlen remoteKey) -{ - /* Fold the boolean return value in C into the string return value - * for this purpose, by returning NULL on failure */ - strbuf *sb = strbuf_new(); - if (!ecdh_key_getkey(ek, remoteKey, BinarySink_UPCAST(sb))) { - strbuf_free(sb); - return NULL; - } - return sb; -} - -static void int16_list_resize(int16_list *list, unsigned p) -{ - list->integers = sresize(list->integers, p, uint16_t); - for (size_t i = list->n; i < p; i++) - list->integers[i] = 0; -} - -#if 0 -static int16_list ntru_ring_to_list_and_free(uint16_t *out, unsigned p) -{ - struct mpint_list mpl; - mpl.n = p; - mpl->integers = snewn(p, mp_int *); - for (unsigned i = 0; i < p; i++) - mpl->integers[i] = mp_from_integer((int16_t)out[i]); - sfree(out); - add_finaliser(finaliser_sfree, mpl->integers); - return mpl; -} -#endif - -int16_list *ntru_ring_multiply_wrapper( - int16_list *a, int16_list *b, unsigned p, unsigned q) -{ - int16_list_resize(a, p); - int16_list_resize(b, p); - int16_list *out = make_int16_list(p); - ntru_ring_multiply(out->integers, a->integers, b->integers, p, q); - return out; -} - -int16_list *ntru_ring_invert_wrapper(int16_list *in, unsigned p, unsigned q) -{ - int16_list_resize(in, p); - int16_list *out = make_int16_list(p); - unsigned success = ntru_ring_invert(out->integers, in->integers, p, q); - if (!success) - return NULL; - return out; -} - -int16_list *ntru_mod3_wrapper(int16_list *in, unsigned p, unsigned q) -{ - int16_list_resize(in, p); - int16_list *out = make_int16_list(p); - ntru_mod3(out->integers, in->integers, p, q); - return out; -} - -int16_list *ntru_round3_wrapper(int16_list *in, unsigned p, unsigned q) -{ - int16_list_resize(in, p); - int16_list *out = make_int16_list(p); - ntru_round3(out->integers, in->integers, p, q); - return out; -} - -int16_list *ntru_bias_wrapper(int16_list *in, unsigned bias, - unsigned p, unsigned q) -{ - int16_list_resize(in, p); - int16_list *out = make_int16_list(p); - ntru_bias(out->integers, in->integers, bias, p, q); - return out; -} - -int16_list *ntru_scale_wrapper(int16_list *in, unsigned scale, - unsigned p, unsigned q) -{ - int16_list_resize(in, p); - int16_list *out = make_int16_list(p); - ntru_scale(out->integers, in->integers, scale, p, q); - return out; -} - -NTRUEncodeSchedule *ntru_encode_schedule_wrapper(int16_list *in) -{ - return ntru_encode_schedule(in->integers, in->n); -} - -void ntru_encode_wrapper(NTRUEncodeSchedule *sched, int16_list *rs, - BinarySink *bs) -{ - ntru_encode(sched, rs->integers, bs); -} - -int16_list *ntru_decode_wrapper(NTRUEncodeSchedule *sched, ptrlen data) -{ - int16_list *out = make_int16_list(ntru_encode_schedule_nvals(sched)); - ntru_decode(sched, out->integers, data); - return out; -} - -int16_list *ntru_gen_short_wrapper(unsigned p, unsigned w) -{ - int16_list *out = make_int16_list(p); - ntru_gen_short(out->integers, p, w); - return out; -} - -int16_list *ntru_pubkey_wrapper(NTRUKeyPair *keypair) -{ - unsigned p = ntru_keypair_p(keypair); - int16_list *out = make_int16_list(p); - memcpy(out->integers, ntru_pubkey(keypair), p*sizeof(uint16_t)); - return out; -} - -int16_list *ntru_encrypt_wrapper(int16_list *plaintext, int16_list *pubkey, - unsigned p, unsigned q) -{ - int16_list *out = make_int16_list(p); - ntru_encrypt(out->integers, plaintext->integers, pubkey->integers, p, q); - return out; -} - -int16_list *ntru_decrypt_wrapper(int16_list *ciphertext, NTRUKeyPair *keypair) -{ - unsigned p = ntru_keypair_p(keypair); - int16_list *out = make_int16_list(p); - ntru_decrypt(out->integers, ciphertext->integers, keypair); - return out; -} - -strbuf *rsa_ssh1_encrypt_wrapper(ptrlen input, RSAKey *key) -{ - /* Fold the boolean return value in C into the string return value - * for this purpose, by returning NULL on failure */ - strbuf *sb = strbuf_new(); - put_datapl(sb, input); - put_padding(sb, key->bytes - input.len, 0); - if (!rsa_ssh1_encrypt(sb->u, input.len, key)) { - strbuf_free(sb); - return NULL; - } - return sb; -} - -strbuf *rsa_ssh1_decrypt_pkcs1_wrapper(mp_int *input, RSAKey *key) -{ - /* Again, return "" on failure */ - strbuf *sb = strbuf_new(); - if (!rsa_ssh1_decrypt_pkcs1(input, key, sb)) - strbuf_clear(sb); - return sb; -} - -strbuf *des_encrypt_xdmauth_wrapper(ptrlen key, ptrlen data) -{ - if (key.len != 7) - fatal_error("des_encrypt_xdmauth: key must be 7 bytes long"); - if (data.len % 8 != 0) - fatal_error("des_encrypt_xdmauth: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_dup(data); - des_encrypt_xdmauth(key.ptr, sb->u, sb->len); - return sb; -} - -strbuf *des_decrypt_xdmauth_wrapper(ptrlen key, ptrlen data) -{ - if (key.len != 7) - fatal_error("des_decrypt_xdmauth: key must be 7 bytes long"); - if (data.len % 8 != 0) - fatal_error("des_decrypt_xdmauth: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_dup(data); - des_decrypt_xdmauth(key.ptr, sb->u, sb->len); - return sb; -} - -strbuf *des3_encrypt_pubkey_wrapper(ptrlen key, ptrlen data) -{ - if (key.len != 16) - fatal_error("des3_encrypt_pubkey: key must be 16 bytes long"); - if (data.len % 8 != 0) - fatal_error("des3_encrypt_pubkey: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_dup(data); - des3_encrypt_pubkey(key.ptr, sb->u, sb->len); - return sb; -} - -strbuf *des3_decrypt_pubkey_wrapper(ptrlen key, ptrlen data) -{ - if (key.len != 16) - fatal_error("des3_decrypt_pubkey: key must be 16 bytes long"); - if (data.len % 8 != 0) - fatal_error("des3_decrypt_pubkey: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_dup(data); - des3_decrypt_pubkey(key.ptr, sb->u, sb->len); - return sb; -} - -strbuf *des3_encrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) -{ - if (key.len != 24) - fatal_error("des3_encrypt_pubkey_ossh: key must be 24 bytes long"); - if (iv.len != 8) - fatal_error("des3_encrypt_pubkey_ossh: iv must be 8 bytes long"); - if (data.len % 8 != 0) - fatal_error("des3_encrypt_pubkey_ossh: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_dup(data); - des3_encrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); - return sb; -} - -strbuf *des3_decrypt_pubkey_ossh_wrapper(ptrlen key, ptrlen iv, ptrlen data) -{ - if (key.len != 24) - fatal_error("des3_decrypt_pubkey_ossh: key must be 24 bytes long"); - if (iv.len != 8) - fatal_error("des3_encrypt_pubkey_ossh: iv must be 8 bytes long"); - if (data.len % 8 != 0) - fatal_error("des3_decrypt_pubkey_ossh: data must be a multiple of 8 bytes"); - strbuf *sb = strbuf_dup(data); - des3_decrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); - return sb; -} - -strbuf *aes256_encrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) -{ - if (key.len != 32) - fatal_error("aes256_encrypt_pubkey: key must be 32 bytes long"); - if (iv.len != 16) - fatal_error("aes256_encrypt_pubkey: iv must be 16 bytes long"); - if (data.len % 16 != 0) - fatal_error("aes256_encrypt_pubkey: data must be a multiple of 16 bytes"); - strbuf *sb = strbuf_dup(data); - aes256_encrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); - return sb; -} - -strbuf *aes256_decrypt_pubkey_wrapper(ptrlen key, ptrlen iv, ptrlen data) -{ - if (key.len != 32) - fatal_error("aes256_decrypt_pubkey: key must be 32 bytes long"); - if (iv.len != 16) - fatal_error("aes256_encrypt_pubkey: iv must be 16 bytes long"); - if (data.len % 16 != 0) - fatal_error("aes256_decrypt_pubkey: data must be a multiple of 16 bytes"); - strbuf *sb = strbuf_dup(data); - aes256_decrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); - return sb; -} - -strbuf *prng_read_wrapper(prng *pr, size_t size) -{ - strbuf *sb = strbuf_new(); - prng_read(pr, strbuf_append(sb, size), size); - return sb; -} - -void prng_seed_update(prng *pr, ptrlen data) -{ - put_datapl(pr, data); -} - -bool crcda_detect(ptrlen packet, ptrlen iv) -{ - if (iv.len != 0 && iv.len != 8) - fatal_error("crcda_detect: iv must be empty or 8 bytes long"); - if (packet.len % 8 != 0) - fatal_error("crcda_detect: packet must be a multiple of 8 bytes"); - struct crcda_ctx *ctx = crcda_make_context(); - bool toret = detect_attack(ctx, packet.ptr, packet.len, - iv.len ? iv.ptr : NULL); - crcda_free_context(ctx); - return toret; -} - -ssh_key *ppk_load_s_wrapper(BinarySource *src, char **comment, - const char *passphrase, const char **errorstr) -{ - ssh2_userkey *uk = ppk_load_s(src, passphrase, errorstr); - if (uk == SSH2_WRONG_PASSPHRASE) { - /* Fudge this special return value */ - *errorstr = "SSH2_WRONG_PASSPHRASE"; - return NULL; - } - if (uk == NULL) - return NULL; - ssh_key *toret = uk->key; - *comment = uk->comment; - sfree(uk); - return toret; -} - -int rsa1_load_s_wrapper(BinarySource *src, RSAKey *rsa, char **comment, - const char *passphrase, const char **errorstr) -{ - int toret = rsa1_load_s(src, rsa, passphrase, errorstr); - *comment = rsa->comment; - rsa->comment = NULL; - return toret; -} - -strbuf *ppk_save_sb_wrapper( - ssh_key *key, const char *comment, const char *passphrase, - unsigned fmt_version, Argon2Flavour flavour, - uint32_t mem, uint32_t passes, uint32_t parallel) -{ - /* - * For repeatable testing purposes, we never want a timing-dependent - * choice of password hashing parameters, so this is easy. - */ - ppk_save_parameters save_params; - memset(&save_params, 0, sizeof(save_params)); - save_params.fmt_version = fmt_version; - save_params.argon2_flavour = flavour; - save_params.argon2_mem = mem; - save_params.argon2_passes_auto = false; - save_params.argon2_passes = passes; - save_params.argon2_parallelism = parallel; - - ssh2_userkey uk; - uk.key = key; - uk.comment = dupstr(comment); - strbuf *toret = ppk_save_sb(&uk, passphrase, &save_params); - sfree(uk.comment); - return toret; -} - -strbuf *rsa1_save_sb_wrapper(RSAKey *key, const char *comment, - const char *passphrase) -{ - key->comment = dupstr(comment); - strbuf *toret = rsa1_save_sb(key, passphrase); - sfree(key->comment); - key->comment = NULL; - return toret; -} - -#define return_void(out, expression) (expression) - -static ProgressReceiver null_progress = { .vt = &null_progress_vt }; - -mp_int *primegen_generate_wrapper( - PrimeGenerationContext *ctx, PrimeCandidateSource *pcs) -{ - return primegen_generate(ctx, pcs, &null_progress); -} - -RSAKey *rsa1_generate(int bits, bool strong, PrimeGenerationContext *pgc) -{ - RSAKey *rsakey = snew(RSAKey); - rsa_generate(rsakey, bits, strong, pgc, &null_progress); - rsakey->comment = NULL; - return rsakey; -} - -ssh_key *rsa_generate_wrapper(int bits, bool strong, - PrimeGenerationContext *pgc) -{ - return &rsa1_generate(bits, strong, pgc)->sshk; -} - -ssh_key *dsa_generate_wrapper(int bits, PrimeGenerationContext *pgc) -{ - struct dsa_key *dsakey = snew(struct dsa_key); - dsa_generate(dsakey, bits, pgc, &null_progress); - return &dsakey->sshk; -} - -ssh_key *ecdsa_generate_wrapper(int bits) -{ - struct ecdsa_key *ek = snew(struct ecdsa_key); - if (!ecdsa_generate(ek, bits)) { - sfree(ek); - return NULL; - } - return &ek->sshk; -} - -ssh_key *eddsa_generate_wrapper(int bits) -{ - struct eddsa_key *ek = snew(struct eddsa_key); - if (!eddsa_generate(ek, bits)) { - sfree(ek); - return NULL; - } - return &ek->sshk; -} - -size_t key_components_count(key_components *kc) { return kc->ncomponents; } -const char *key_components_nth_name(key_components *kc, size_t n) -{ - return (n >= kc->ncomponents ? NULL : - kc->components[n].name); -} -strbuf *key_components_nth_str(key_components *kc, size_t n) -{ - if (n >= kc->ncomponents) - return NULL; - if (kc->components[n].type != KCT_TEXT && - kc->components[n].type != KCT_BINARY) - return NULL; - return strbuf_dup(ptrlen_from_strbuf(kc->components[n].str)); -} -mp_int *key_components_nth_mp(key_components *kc, size_t n) -{ - return (n >= kc->ncomponents ? NULL : - kc->components[n].type != KCT_MPINT ? NULL : - mp_copy(kc->components[n].mp)); -} - -PockleStatus pockle_add_prime_wrapper(Pockle *pockle, mp_int *p, - struct mpint_list mpl, mp_int *witness) -{ - return pockle_add_prime(pockle, p, mpl.integers, mpl.n, witness); -} - -strbuf *argon2_wrapper(Argon2Flavour flavour, uint32_t mem, uint32_t passes, - uint32_t parallel, uint32_t taglen, - ptrlen P, ptrlen S, ptrlen K, ptrlen X) -{ - strbuf *out = strbuf_new(); - argon2(flavour, mem, passes, parallel, taglen, P, S, K, X, out); - return out; -} - -strbuf *openssh_bcrypt_wrapper(ptrlen passphrase, ptrlen salt, - unsigned rounds, unsigned outbytes) -{ - strbuf *out = strbuf_new(); - openssh_bcrypt(passphrase, salt, rounds, - strbuf_append(out, outbytes), outbytes); - return out; -} - -strbuf *get_implementations_commasep(ptrlen alg) -{ - strbuf *out = strbuf_new(); - put_datapl(out, alg); - - if (ptrlen_startswith(alg, PTRLEN_LITERAL("aesgcm"), NULL)) { - put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); - put_fmt(out, ",%.*s_ref_poly", PTRLEN_PRINTF(alg)); -#if HAVE_CLMUL - put_fmt(out, ",%.*s_clmul", PTRLEN_PRINTF(alg)); -#endif -#if HAVE_NEON_PMULL - put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); -#endif - } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("aes"), NULL)) { - put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); -#if HAVE_AES_NI - put_fmt(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); -#endif -#if HAVE_NEON_CRYPTO - put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); -#endif - } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("sha256"), NULL) || - ptrlen_startswith(alg, PTRLEN_LITERAL("sha1"), NULL)) { - put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); -#if HAVE_SHA_NI - put_fmt(out, ",%.*s_ni", PTRLEN_PRINTF(alg)); -#endif -#if HAVE_NEON_CRYPTO - put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); -#endif - } else if (ptrlen_startswith(alg, PTRLEN_LITERAL("sha512"), NULL)) { - put_fmt(out, ",%.*s_sw", PTRLEN_PRINTF(alg)); -#if HAVE_NEON_SHA512 - put_fmt(out, ",%.*s_neon", PTRLEN_PRINTF(alg)); -#endif - } - - return out; -} - -#define OPTIONAL_PTR_FUNC(type) \ - typedef TD_val_##type TD_opt_val_##type; \ - static TD_opt_val_##type get_opt_val_##type(BinarySource *in) { \ - ptrlen word = get_word(in); \ - if (ptrlen_eq_string(word, "NULL")) \ - return NULL; \ - return unwrap_value_##type(lookup_value(word))->vu_##type; \ - } -OPTIONAL_PTR_FUNC(cipher) -OPTIONAL_PTR_FUNC(mpint) -OPTIONAL_PTR_FUNC(string) - -/* - * HERE BE DRAGONS: the horrible C preprocessor business that reads - * testcrypt-func.h and generates a marshalling wrapper for each - * exported function. - * - * In an ideal world, we would start from a specification like this in - * testcrypt-func.h - * - * FUNC(val_foo, example, ARG(val_bar, bar), ARG(uint, n)) - * - * and generate a wrapper function looking like this: - * - * static void handle_example(BinarySource *in, strbuf *out) { - * TD_val_bar bar = get_val_bar(in); - * TD_uint n = get_uint(in); - * return_val_foo(out, example(bar, n)); - * } - * - * which would read the marshalled form of each function argument in - * turn from the input BinarySource via the get_() function - * family defined in this file; assign each argument to a local - * variable; call the underlying C function with all those arguments; - * and then call a function of the return_() family to marshal - * the output value into the output strbuf to be sent to standard - * output. - * - * With a more general macro processor such as m4, or custom code in - * Perl or Python, or a helper program like llvm-tblgen, we could just - * do that directly, reading function specifications from - * testcrypt-func.h and writing out exactly the above. But we don't - * have a fully general macro processor (since everything in that - * category introduces an extra build dependency that's awkward on - * plain Windows, or requires compiling and running a helper program - * which is awkward in a cross-compile). We only have cpp. And in cpp, - * a macro can't expand one of its arguments differently in two parts - * of its own expansion. So we have to be more clever. - * - * In place of the above code, I instead generate three successive - * declarations for each function. In simplified form they would look - * like this: - * - * typedef struct ARGS_example { - * TD_val_bar bar; - * TD_uint n; - * } ARGS_example; - * - * static inline ARGS_example get_args_example(BinarySource *in) { - * ARGS_example args; - * args.bar = get_val_bar(in); - * args.n = get_uint(in); - * return args; - * } - * - * static void handle_example(BinarySource *in, strbuf *out) { - * ARGS_example args = get_args_example(in); - * return_val_foo(out, example(args.bar, args.n)); - * } - * - * Each of these mentions the arguments and their types just _once_, - * so each one can be generated by a single expansion of the FUNC(...) - * specification in testcrypt-func.h, with FUNC and ARG and VOID - * defined to appropriate values. - * - * Or ... *nearly*. In fact, I left out several details there, but - * it's a good starting point to understand the full version. - * - * To begin with, several of the variable names shown above are - * actually named with an ugly leading underscore, to minimise the - * chance of them colliding with real parameter names. (You could - * easily imagine 'out' being the name of a parameter to one of the - * wrapped functions.) Also, we memset the whole structure to zero at - * the start of get_args_example() to avoid compiler warnings about - * uninitialised stuff, and insert a precautionary '(void)args;' in - * handle_example to avoid a similar warning about _unused_ stuff. - * - * The big problem is the commas that have to appear between arguments - * in the final call to the actual C function. Those can't be - * generated by expanding the ARG macro itself, or you'd get one too - * many - either a leading comma or a trailing comma. Trailing commas - * are legal in a Python function call, but unfortunately C is not yet - * so enlightened. (C permits a trailing comma in a struct or array - * initialiser, and is coming round to it in enums, but hasn't yet - * seen the light about function calls or function prototypes.) - * - * So the commas must appear _between_ ARG(...) specifiers. And that - * means they unavoidably appear in _every_ expansion of FUNC() (or - * rather, every expansion that uses the variadic argument list at - * all). Therefore, we need to ensure they're harmless in the other - * two functions as well. - * - * In the get_args_example() function above, there's no real problem. - * The list of assignments can perfectly well be separated by commas - * instead of semicolons, so that it becomes a single expression- - * statement instead of a sequence of them; the comma operator still - * defines a sequence point, so it's fine. - * - * But what about the structure definition of ARGS_example? - * - * To get round that, we fill the structure with pointless extra - * cruft, in the form of an extra 'int' field before and after each - * actually useful argument field. So the real structure definition - * ends up looking more like this: - * - * typedef struct ARGS_example { - * int _predummy_bar; - * TD_val_bar bar; - * int _postdummy_bar, _predummy_n; - * TD_uint n; - * int _postdummy_n; - * } ARGS_example; - * - * Those extra 'int' fields are ignored completely at run time. They - * might cause a runtime space cost if the struct doesn't get - * completely optimised away when get_args_example is inlined into - * handle_example, but even if so, that's OK, this is a test program - * whose memory usage isn't critical. The real point is that, in - * between each pair of real arguments, there's a declaration - * containing *two* int variables, and in between them is the vital - * comma that we need! - * - * So in that pass through testcrypt-func.h, the ARG(type, name) macro - * has to expand to the weird piece of text - * - * _predummy_name; // terminating the previous int declaration - * TD_type name; // declaring the thing we actually wanted - * int _postdummy_name // new declaration ready to see a comma - * - * so that a comma-separated list of pieces of expansion like that - * will fall into just the right form to be the core of the above - * expanded structure definition. Then we just need to put in the - * 'int' after the open brace, and the ';' before the closing brace, - * and we've got everything we need to make it all syntactically legal. - * - * Finally, what if a wrapped function has _no_ arguments? Two out of - * three uses of the argument list here need some kind of special case - * for that. That's why you have to write 'VOID' explicitly in an - * empty argument list in testcrypt-func.h: we make VOID expand to - * whatever is needed to avoid a syntax error in that special case. - */ - -/* - * Workarounds for an awkwardness in Visual Studio's preprocessor, - * which disagrees with everyone else about what happens if you expand - * __VA_ARGS__ into the argument list of another macro. gcc and clang - * will treat the commas expanding from __VA_ARGS__ as argument - * separators, whereas VS will make them all part of a single argument - * to the secondary macro. We want the former behaviour, so we use - * the following workaround to enforce it. - * - * Each of these JUXTAPOSE macros simply places its arguments side by - * side. But the arguments are macro-expanded before JUXTAPOSE is - * called at all, so we can do this: - * - * JUXTAPOSE(macroname, (__VA_ARGS__)) - * -> JUXTAPOSE(macroname, (foo, bar, baz)) - * -> macroname (foo, bar, baz) - * - * and this preliminary expansion causes the commas to be treated - * normally by the time VS gets round to expanding the inner macro. - * - * We need two differently named JUXTAPOSE macros, because we have to - * do this trick twice: once to turn FUNC and FUNC_WRAPPED in - * testcrypt-funcs.h into the underlying common FUNC_INNER, and again - * to expand the final function call. And you can't expand a macro - * inside text expanded from the _same_ macro, so we have to do the - * outer and inner instances of this trick using macros of different - * names. - */ -#define JUXTAPOSE1(first, second) first second -#define JUXTAPOSE2(first, second) first second - -#define FUNC(outtype, fname, ...) \ - JUXTAPOSE1(FUNC_INNER, (outtype, fname, fname, __VA_ARGS__)) -#define FUNC_WRAPPED(outtype, fname, ...) \ - JUXTAPOSE1(FUNC_INNER, (outtype, fname, fname##_wrapper, __VA_ARGS__)) - -#define ARG(type, arg) _predummy_##arg; TD_##type arg; int _postdummy_##arg -#define VOID _voiddummy -#define FUNC_INNER(outtype, fname, realname, ...) \ - typedef struct ARGS_##fname { \ - int __VA_ARGS__; \ - } ARGS_##fname; -#include "testcrypt-func.h" -#undef FUNC_INNER -#undef ARG -#undef VOID - -#define ARG(type, arg) _args.arg = get_##type(_in) -#define VOID ((void)0) -#define FUNC_INNER(outtype, fname, realname, ...) \ - static inline ARGS_##fname get_args_##fname(BinarySource *_in) { \ - ARGS_##fname _args; \ - memset(&_args, 0, sizeof(_args)); \ - __VA_ARGS__; \ - return _args; \ - } -#include "testcrypt-func.h" -#undef FUNC_INNER -#undef ARG -#undef VOID - -#define ARG(type, arg) _args.arg -#define VOID -#define FUNC_INNER(outtype, fname, realname, ...) \ - static void handle_##fname(BinarySource *_in, strbuf *_out) { \ - ARGS_##fname _args = get_args_##fname(_in); \ - (void)_args; /* suppress warning if no actual arguments */ \ - return_##outtype(_out, JUXTAPOSE2(realname, (__VA_ARGS__))); \ - } -#include "testcrypt-func.h" -#undef FUNC_INNER -#undef ARG - -static void process_line(BinarySource *in, strbuf *out) -{ - ptrlen id = get_word(in); - -#define DISPATCH_INTERNAL(cmdname, handler) do { \ - if (ptrlen_eq_string(id, cmdname)) { \ - handler(in, out); \ - return; \ - } \ - } while (0) - -#define DISPATCH_COMMAND(cmd) DISPATCH_INTERNAL(#cmd, handle_##cmd) - DISPATCH_COMMAND(hello); - DISPATCH_COMMAND(free); - DISPATCH_COMMAND(newstring); - DISPATCH_COMMAND(getstring); - DISPATCH_COMMAND(mp_literal); - DISPATCH_COMMAND(mp_dump); - DISPATCH_COMMAND(checkenum); -#undef DISPATCH_COMMAND - -#define FUNC_INNER(outtype, fname, realname, ...) \ - DISPATCH_INTERNAL(#fname,handle_##fname); -#include "testcrypt-func.h" -#undef FUNC_INNER - -#undef DISPATCH_INTERNAL - - fatal_error("command '%.*s': unrecognised", PTRLEN_PRINTF(id)); -} - -static void free_all_values(void) -{ - for (Value *val; (val = delpos234(values, 0)) != NULL ;) - free_value(val); - freetree234(values); -} - -void dputs(const char *buf) -{ - fputs(buf, stderr); -} - -int main(int argc, char **argv) -{ - const char *infile = NULL, *outfile = NULL; - bool doing_opts = true; - - while (--argc > 0) { - char *p = *++argv; - - if (p[0] == '-' && doing_opts) { - if (!strcmp(p, "-o")) { - if (--argc <= 0) { - fprintf(stderr, "'-o' expects a filename\n"); - return 1; - } - outfile = *++argv; - } else if (!strcmp(p, "--")) { - doing_opts = false; - } else if (!strcmp(p, "--help")) { - printf("usage: testcrypt [INFILE] [-o OUTFILE]\n"); - printf(" also: testcrypt --help display this text\n"); - return 0; - } else { - fprintf(stderr, "unknown command line option '%s'\n", p); - return 1; - } - } else if (!infile) { - infile = p; - } else { - fprintf(stderr, "can only handle one input file name\n"); - return 1; - } - } - - FILE *infp = stdin; - if (infile) { - infp = fopen(infile, "r"); - if (!infp) { - fprintf(stderr, "%s: open: %s\n", infile, strerror(errno)); - return 1; - } - } - - FILE *outfp = stdout; - if (outfile) { - outfp = fopen(outfile, "w"); - if (!outfp) { - fprintf(stderr, "%s: open: %s\n", outfile, strerror(errno)); - return 1; - } - } - - values = newtree234(valuecmp); - - atexit(free_all_values); - - for (char *line; (line = chomp(fgetline(infp))) != NULL ;) { - BinarySource src[1]; - BinarySource_BARE_INIT(src, line, strlen(line)); - strbuf *sb = strbuf_new(); - process_line(src, sb); - run_finalisers(sb); - size_t lines = 0; - for (size_t i = 0; i < sb->len; i++) - if (sb->s[i] == '\n') - lines++; - fprintf(outfp, "%"SIZEu"\n%s", lines, sb->s); - fflush(outfp); - strbuf_free(sb); - sfree(line); - } - - if (infp != stdin) - fclose(infp); - if (outfp != stdin) - fclose(outfp); - - return 0; -} diff --git a/test/testcrypt.py b/test/testcrypt.py deleted file mode 100644 index 66f63d5c6..000000000 --- a/test/testcrypt.py +++ /dev/null @@ -1,441 +0,0 @@ -import sys -import os -import numbers -import subprocess -import re -import string -import struct -from binascii import hexlify - -assert sys.version_info[:2] >= (3,0), "This is Python 3 code" - -# Expect to be run from the 'test' subdirectory, one level down from -# the main source -putty_srcdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - -def coerce_to_bytes(arg): - return arg.encode("UTF-8") if isinstance(arg, str) else arg - -class ChildProcessFailure(Exception): - pass - -class ChildProcess(object): - def __init__(self): - self.sp = None - self.debug = None - self.exitstatus = None - self.exception = None - - dbg = os.environ.get("PUTTY_TESTCRYPT_DEBUG") - if dbg is not None: - if dbg == "stderr": - self.debug = sys.stderr - else: - sys.stderr.write("Unknown value '{}' for PUTTY_TESTCRYPT_DEBUG" - " (try 'stderr'\n") - def start(self): - assert self.sp is None - override_command = os.environ.get("PUTTY_TESTCRYPT") - if override_command is None: - cmd = [os.path.join(putty_srcdir, "testcrypt")] - shell = False - else: - cmd = override_command - shell = True - self.sp = subprocess.Popen( - cmd, shell=shell, stdin=subprocess.PIPE, stdout=subprocess.PIPE) - def write_line(self, line): - if self.exception is not None: - # Re-raise our fatal-error exception, if it previously - # occurred in a context where it couldn't be propagated (a - # __del__ method). - raise self.exception - if self.debug is not None: - self.debug.write("send: {}\n".format(line)) - self.sp.stdin.write(line + b"\n") - self.sp.stdin.flush() - def read_line(self): - line = self.sp.stdout.readline() - if len(line) == 0: - self.exception = ChildProcessFailure("received EOF from testcrypt") - raise self.exception - line = line.rstrip(b"\r\n") - if self.debug is not None: - self.debug.write("recv: {}\n".format(line)) - return line - def already_terminated(self): - return self.sp is None and self.exitstatus is not None - def funcall(self, cmd, args): - if self.sp is None: - assert self.exitstatus is None - self.start() - self.write_line(coerce_to_bytes(cmd) + b" " + b" ".join( - coerce_to_bytes(arg) for arg in args)) - argcount = int(self.read_line()) - return [self.read_line() for arg in range(argcount)] - def wait_for_exit(self): - if self.sp is not None: - self.sp.stdin.close() - self.exitstatus = self.sp.wait() - self.sp = None - def check_return_status(self): - self.wait_for_exit() - if self.exitstatus is not None and self.exitstatus != 0: - raise ChildProcessFailure("testcrypt returned exit status {}" - .format(self.exitstatus)) - -childprocess = ChildProcess() - -method_prefixes = { - 'val_wpoint': ['ecc_weierstrass_'], - 'val_mpoint': ['ecc_montgomery_'], - 'val_epoint': ['ecc_edwards_'], - 'val_hash': ['ssh_hash_'], - 'val_mac': ['ssh2_mac_'], - 'val_key': ['ssh_key_'], - 'val_cipher': ['ssh_cipher_'], - 'val_dh': ['dh_'], - 'val_ecdh': ['ssh_ecdhkex_'], - 'val_rsakex': ['ssh_rsakex_'], - 'val_prng': ['prng_'], - 'val_pcs': ['pcs_'], - 'val_pockle': ['pockle_'], - 'val_ntruencodeschedule': ['ntru_encode_schedule_', 'ntru_'], -} -method_lists = {t: [] for t in method_prefixes} - -checked_enum_values = {} - -class Value(object): - def __init__(self, typename, ident): - self._typename = typename - self._ident = ident - for methodname, function in method_lists.get(self._typename, []): - setattr(self, methodname, - (lambda f: lambda *args: f(self, *args))(function)) - def _consumed(self): - self._ident = None - def __repr__(self): - return "Value({!r}, {!r})".format(self._typename, self._ident) - def __del__(self): - if self._ident is not None and not childprocess.already_terminated(): - try: - childprocess.funcall("free", [self._ident]) - except ChildProcessFailure: - # If we see this exception now, we can't do anything - # about it, because exceptions don't propagate out of - # __del__ methods. Squelch it to prevent the annoying - # runtime warning from Python, and the - # 'self.exception' mechanism in the ChildProcess class - # will raise it again at the next opportunity. - # - # (This covers both the case where testcrypt crashes - # _during_ one of these free operations, and the - # silencing of cascade failures when we try to send a - # "free" command to testcrypt after it had already - # crashed for some other reason.) - pass - def __long__(self): - if self._typename != "val_mpint": - raise TypeError("testcrypt values of types other than mpint" - " cannot be converted to integer") - hexval = childprocess.funcall("mp_dump", [self._ident])[0] - return 0 if len(hexval) == 0 else int(hexval, 16) - def __int__(self): - return int(self.__long__()) - -def marshal_string(val): - val = coerce_to_bytes(val) - assert isinstance(val, bytes), "Bad type for val_string input" - return "".join( - chr(b) if (0x20 <= b < 0x7F and b != 0x25) - else "%{:02x}".format(b) - for b in val) - -def make_argword(arg, argtype, fnname, argindex, argname, to_preserve): - typename, consumed = argtype - if typename.startswith("opt_"): - if arg is None: - return "NULL" - typename = typename[4:] - if typename == "val_string": - retwords = childprocess.funcall("newstring", [marshal_string(arg)]) - arg = make_retvals([typename], retwords, unpack_strings=False)[0] - to_preserve.append(arg) - if typename == "val_mpint" and isinstance(arg, numbers.Integral): - retwords = childprocess.funcall("mp_literal", ["0x{:x}".format(arg)]) - arg = make_retvals([typename], retwords)[0] - to_preserve.append(arg) - if isinstance(arg, Value): - if arg._typename != typename: - raise TypeError( - "{}() argument #{:d} ({}) should be {} ({} given)".format( - fnname, argindex, argname, typename, arg._typename)) - ident = arg._ident - if consumed: - arg._consumed() - return ident - if typename == "uint" and isinstance(arg, numbers.Integral): - return "0x{:x}".format(arg) - if typename == "boolean": - return "true" if arg else "false" - if typename in { - "hashalg", "macalg", "keyalg", "cipheralg", - "dh_group", "ecdh_alg", "rsaorder", "primegenpolicy", - "argon2flavour", "fptype", "httpdigesthash"}: - arg = coerce_to_bytes(arg) - if isinstance(arg, bytes) and b" " not in arg: - dictkey = (typename, arg) - if dictkey not in checked_enum_values: - retwords = childprocess.funcall("checkenum", [typename, arg]) - assert len(retwords) == 1 - checked_enum_values[dictkey] = (retwords[0] == b"ok") - if checked_enum_values[dictkey]: - return arg - if typename == "mpint_list": - sublist = [make_argword(len(arg), ("uint", False), - fnname, argindex, argname, to_preserve)] - for val in arg: - sublist.append(make_argword(val, ("val_mpint", False), - fnname, argindex, argname, to_preserve)) - return b" ".join(coerce_to_bytes(sub) for sub in sublist) - if typename == "int16_list": - sublist = [make_argword(len(arg), ("uint", False), - fnname, argindex, argname, to_preserve)] - for val in arg: - sublist.append(make_argword(val & 0xFFFF, ("uint", False), - fnname, argindex, argname, to_preserve)) - return b" ".join(coerce_to_bytes(sub) for sub in sublist) - raise TypeError( - "Can't convert {}() argument #{:d} ({}) to {} (value was {!r})".format( - fnname, argindex, argname, typename, arg)) - -def unpack_string(identifier): - retwords = childprocess.funcall("getstring", [identifier]) - childprocess.funcall("free", [identifier]) - return re.sub(b"%[0-9A-F][0-9A-F]", - lambda m: bytes([int(m.group(0)[1:], 16)]), - retwords[0]) - -def unpack_mp(identifier): - retwords = childprocess.funcall("mp_dump", [identifier]) - childprocess.funcall("free", [identifier]) - return int(retwords[0], 16) - -def make_retval(rettype, word, unpack_strings): - if rettype.startswith("opt_"): - if word == b"NULL": - return None - rettype = rettype[4:] - if rettype == "val_string" and unpack_strings: - return unpack_string(word) - if rettype == "val_keycomponents": - kc = {} - retwords = childprocess.funcall("key_components_count", [word]) - for i in range(int(retwords[0], 0)): - args = [word, "{:d}".format(i)] - retwords = childprocess.funcall("key_components_nth_name", args) - kc_key = unpack_string(retwords[0]) - retwords = childprocess.funcall("key_components_nth_str", args) - if retwords[0] != b"NULL": - kc_value = unpack_string(retwords[0]).decode("ASCII") - else: - retwords = childprocess.funcall("key_components_nth_mp", args) - kc_value = unpack_mp(retwords[0]) - kc[kc_key.decode("ASCII")] = kc_value - childprocess.funcall("free", [word]) - return kc - if rettype.startswith("val_"): - return Value(rettype, word) - elif rettype == "int" or rettype == "uint": - return int(word, 0) - elif rettype == "boolean": - assert word == b"true" or word == b"false" - return word == b"true" - elif rettype in {"pocklestatus", "mr_result"}: - return word.decode("ASCII") - elif rettype == "int16_list": - return list(map(int, word.split(b','))) - raise TypeError("Can't deal with return value {!r} of type {!r}" - .format(word, rettype)) - -def make_retvals(rettypes, retwords, unpack_strings=True): - assert len(rettypes) == len(retwords) # FIXME: better exception - return [make_retval(rettype, word, unpack_strings) - for rettype, word in zip(rettypes, retwords)] - -class Function(object): - def __init__(self, fnname, rettypes, retnames, argtypes, argnames): - self.fnname = fnname - self.rettypes = rettypes - self.retnames = retnames - self.argtypes = argtypes - self.argnames = argnames - def __repr__(self): - return " ({})>".format( - self.fnname, - ", ".join(("consumed " if c else "")+t+" "+n - for (t,c),n in zip(self.argtypes, self.argnames)), - ", ".join((t+" "+n if n is not None else t) - for t,n in zip(self.rettypes, self.retnames)), - ) - def __call__(self, *args): - if len(args) != len(self.argtypes): - raise TypeError( - "{}() takes exactly {} arguments ({} given)".format( - self.fnname, len(self.argtypes), len(args))) - to_preserve = [] - retwords = childprocess.funcall( - self.fnname, [make_argword(args[i], self.argtypes[i], - self.fnname, i, self.argnames[i], - to_preserve) - for i in range(len(args))]) - retvals = make_retvals(self.rettypes, retwords) - if len(retvals) == 0: - return None - if len(retvals) == 1: - return retvals[0] - return tuple(retvals) - -def _lex_testcrypt_header(header): - pat = re.compile( - # Skip any combination of whitespace and comments - '(?:{})*'.format('|'.join(( - '[ \t\n]', # whitespace - '/\\*(?:.|\n)*?\\*/', # C90-style /* ... */ comment, ended eagerly - '//[^\n]*\n', # C99-style comment to end-of-line - ))) + - # And then match a token - '({})'.format('|'.join(( - # Punctuation - '\(', - '\)', - ',', - # Identifier - '[A-Za-z_][A-Za-z0-9_]*', - # End of string - '$', - ))) - ) - - pos = 0 - end = len(header) - while pos < end: - m = pat.match(header, pos) - assert m is not None, ( - "Failed to lex testcrypt-func.h at byte position {:d}".format(pos)) - - pos = m.end() - tok = m.group(1) - if len(tok) == 0: - assert pos == end, ( - "Empty token should only be returned at end of string") - yield tok, m.start(1) - -def _parse_testcrypt_header(tokens): - def is_id(tok): - return tok[0] in string.ascii_letters+"_" - def expect(what, why, eof_ok=False): - tok, pos = next(tokens) - if tok == '' and eof_ok: - return None - if hasattr(what, '__call__'): - description = lambda: "" - ok = what(tok) - elif isinstance(what, set): - description = lambda: " or ".join("'"+x+"' " for x in sorted(what)) - ok = tok in what - else: - description = lambda: "'"+what+"' " - ok = tok == what - if not ok: - sys.exit("testcrypt-func.h:{:d}: expected {}{}".format( - pos, description(), why)) - return tok - - while True: - tok = expect({"FUNC", "FUNC_WRAPPED"}, - "at start of function specification", eof_ok=True) - if tok is None: - break - - expect("(", "after FUNC") - rettype = expect(is_id, "return type") - expect(",", "after return type") - funcname = expect(is_id, "function name") - expect(",", "after function name") - args = [] - firstargkind = expect({"ARG", "VOID"}, "at start of argument list") - if firstargkind == "VOID": - expect(")", "after VOID") - else: - while True: - # Every time we come back to the top of this loop, we've - # just seen 'ARG' - expect("(", "after ARG") - argtype = expect(is_id, "argument type") - expect(",", "after argument type") - argname = expect(is_id, "argument name") - args.append((argtype, argname)) - expect(")", "at end of ARG") - punct = expect({",", ")"}, "after argument") - if punct == ")": - break - expect("ARG", "to begin next argument") - yield funcname, rettype, args - -def _setup(scope): - valprefix = "val_" - outprefix = "out_" - optprefix = "opt_" - consprefix = "consumed_" - - def trim_argtype(arg): - if arg.startswith(optprefix): - return optprefix + trim_argtype(arg[len(optprefix):]) - - if (arg.startswith(valprefix) and - "_" in arg[len(valprefix):]): - # Strip suffixes like val_string_asciz - arg = arg[:arg.index("_", len(valprefix))] - return arg - - with open(os.path.join(putty_srcdir, "test", "testcrypt-func.h")) as f: - header = f.read() - tokens = _lex_testcrypt_header(header) - for function, rettype, arglist in _parse_testcrypt_header(tokens): - rettypes = [] - retnames = [] - if rettype != "void": - rettypes.append(trim_argtype(rettype)) - retnames.append(None) - - argtypes = [] - argnames = [] - argsconsumed = [] - for arg, argname in arglist: - if arg.startswith(outprefix): - rettypes.append(trim_argtype(arg[len(outprefix):])) - retnames.append(argname) - else: - consumed = False - if arg.startswith(consprefix): - arg = arg[len(consprefix):] - consumed = True - arg = trim_argtype(arg) - argtypes.append((arg, consumed)) - argnames.append(argname) - func = Function(function, rettypes, retnames, - argtypes, argnames) - scope[function] = func - if len(argtypes) > 0: - t = argtypes[0][0] - if t in method_prefixes: - for prefix in method_prefixes[t]: - if function.startswith(prefix): - methodname = function[len(prefix):] - method_lists[t].append((methodname, func)) - break - -_setup(globals()) -del _setup diff --git a/test/testsc.c b/test/testsc.c deleted file mode 100644 index d6807c419..000000000 --- a/test/testsc.c +++ /dev/null @@ -1,1946 +0,0 @@ -/* - * testsc: run PuTTY's crypto primitives under instrumentation that - * checks for cache and timing side channels. - * - * The idea is: cryptographic code should avoid leaking secret data - * through timing information, or through traces of its activity left - * in the caches. - * - * (This property is sometimes called 'constant-time', although really - * that's a misnomer. It would be impossible to avoid the execution - * time varying for any number of reasons outside the code's control, - * such as the prior contents of caches and branch predictors, - * temperature-based CPU throttling, system load, etc. And in any case - * you don't _need_ the execution time to be literally constant: you - * just need it to be independent of your secrets. It can vary as much - * as it likes based on anything else.) - * - * To avoid this, you need to ensure that various aspects of the - * code's behaviour do not depend on the secret data. The control - * flow, for a start - no conditional branches based on secrets - and - * also the memory access pattern (no using secret data as an index - * into a lookup table). A couple of other kinds of CPU instruction - * also can't be trusted to run in constant time: we check for - * register-controlled shifts and hardware divisions. (But, again, - * it's perfectly fine to _use_ those instructions in the course of - * crypto code. You just can't use a secret as any time-affecting - * operand.) - * - * This test program works by running the same crypto primitive - * multiple times, with different secret input data. The relevant - * details of each run is logged to a file via the DynamoRIO-based - * instrumentation system living in the subdirectory test/sclog. Then - * we check over all the files and ensure they're identical. - * - * This program itself (testsc) is built by the ordinary PuTTY - * makefiles. But run by itself, it will do nothing useful: it needs - * to be run under DynamoRIO, with the sclog instrumentation library. - * - * Here's an example of how I built it: - * - * Download the DynamoRIO source. I did this by cloning - * https://github.com/DynamoRIO/dynamorio.git, and at the time of - * writing this, 259c182a75ce80112bcad329c97ada8d56ba854d was the head - * commit. - * - * In the DynamoRIO checkout: - * - * mkdir build - * cd build - * cmake -G Ninja .. - * ninja - * - * Now set the shell variable DRBUILD to be the location of the build - * directory you did that in. (Or not, if you prefer, but the example - * build commands below will assume that that's where the DynamoRIO - * libraries, headers and runtime can be found.) - * - * Then, in test/sclog: - * - * cmake -G Ninja -DCMAKE_PREFIX_PATH=$DRBUILD/cmake . - * ninja - * - * Finally, to run the actual test, set SCTMP to some temp directory - * you don't mind filling with large temp files (several GB at a - * time), and in the main PuTTY source directory (assuming that's - * where testsc has been built): - * - * $DRBUILD/bin64/drrun -c test/sclog/libsclog.so -- ./testsc -O $SCTMP - */ - -#include -#include -#include -#include -#include - -#include "defs.h" -#include "putty.h" -#include "ssh.h" -#include "sshkeygen.h" -#include "misc.h" -#include "mpint.h" -#include "crypto/ecc.h" -#include "crypto/ntru.h" - -static NORETURN PRINTF_LIKE(1, 2) void fatal_error(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "testsc: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); - exit(1); -} - -void out_of_memory(void) { fatal_error("out of memory"); } - -/* - * A simple deterministic PRNG, without any of the Fortuna - * complexities, for generating test inputs in a way that's repeatable - * between runs of the program, even if only a subset of test cases is - * run. - */ -static uint64_t random_counter = 0; -static const char *random_seedstr = NULL; -static uint8_t random_buf[MAX_HASH_LEN]; -static size_t random_buf_limit = 0; -static ssh_hash *random_hash; - -static void random_seed(const char *seedstr) -{ - random_seedstr = seedstr; - random_counter = 0; - random_buf_limit = 0; -} - -static void random_advance_counter(void) -{ - ssh_hash_reset(random_hash); - put_asciz(random_hash, random_seedstr); - put_uint64(random_hash, random_counter); - random_counter++; - random_buf_limit = ssh_hash_alg(random_hash)->hlen; - ssh_hash_digest(random_hash, random_buf); -} - -void random_read(void *vbuf, size_t size) -{ - assert(random_seedstr); - uint8_t *buf = (uint8_t *)vbuf; - while (size-- > 0) { - if (random_buf_limit == 0) - random_advance_counter(); - *buf++ = random_buf[random_buf_limit--]; - } -} - -struct random_state { - const char *seedstr; - uint64_t counter; - size_t limit; - uint8_t buf[MAX_HASH_LEN]; -}; - -static struct random_state random_get_state(void) -{ - struct random_state st; - st.seedstr = random_seedstr; - st.counter = random_counter; - st.limit = random_buf_limit; - memcpy(st.buf, random_buf, sizeof(st.buf)); - return st; -} - -static void random_set_state(struct random_state st) -{ - random_seedstr = st.seedstr; - random_counter = st.counter; - random_buf_limit = st.limit; - memcpy(random_buf, st.buf, sizeof(random_buf)); -} - -/* - * Macro that defines a function, and also a volatile function pointer - * pointing to it. Callers indirect through the function pointer - * instead of directly calling the function, to ensure that the - * compiler doesn't try to get clever by eliminating the call - * completely, or inlining it. - * - * This is used to mark functions that DynamoRIO will look for to - * intercept, and also to inhibit inlining and unrolling where they'd - * cause a failure of experimental control in the main test. - */ -#define VOLATILE_WRAPPED_DEFN(qualifier, rettype, fn, params) \ - qualifier rettype fn##_real params; \ - qualifier rettype (*volatile fn) params = fn##_real; \ - qualifier rettype fn##_real params - -VOLATILE_WRAPPED_DEFN(, void, log_to_file, (const char *filename)) -{ - /* - * This function is intercepted by the DynamoRIO side of the - * mechanism. We use it to send instructions to the DR wrapper, - * namely, 'please start logging to this file' or 'please stop - * logging' (if filename == NULL). But we don't have to actually - * do anything in _this_ program - all the functionality is in the - * DR wrapper. - */ -} - -static const char *outdir = NULL; -char *log_filename(const char *basename, size_t index) -{ - return dupprintf("%s/%s.%04"SIZEu, outdir, basename, index); -} - -static char *last_filename; -static const char *test_basename; -static size_t test_index = 0; -void log_start(void) -{ - last_filename = log_filename(test_basename, test_index++); - log_to_file(last_filename); -} -void log_end(void) -{ - log_to_file(NULL); - sfree(last_filename); -} - -static bool test_skipped = false; - -VOLATILE_WRAPPED_DEFN(, intptr_t, dry_run, (void)) -{ - /* - * This is another function intercepted by DynamoRIO. In this - * case, DR overrides this function to return 0 rather than 1, so - * we can use it as a check for whether we're running under - * instrumentation, or whether this is just a dry run which goes - * through the motions but doesn't expect to find any log files - * created. - */ - return 1; -} - -static void mp_random_bits_into(mp_int *r, size_t bits) -{ - mp_int *x = mp_random_bits(bits); - mp_copy_into(r, x); - mp_free(x); -} - -static void mp_random_fill(mp_int *r) -{ - mp_random_bits_into(r, mp_max_bits(r)); -} - -VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) -{ - /* - * looplimit() is the identity function on size_t, but the - * compiler isn't allowed to rely on it being that. I use it to - * make loops in the test functions look less attractive to - * compilers' unrolling heuristics. - */ - return x; -} - -#if HAVE_AES_NI -#define IF_AES_NI(x) x -#else -#define IF_AES_NI(x) -#endif - -#if HAVE_SHA_NI -#define IF_SHA_NI(x) x -#else -#define IF_SHA_NI(x) -#endif - -#if HAVE_CLMUL -#define IF_CLMUL(x) x -#else -#define IF_CLMUL(x) -#endif - -#if HAVE_NEON_CRYPTO -#define IF_NEON_CRYPTO(x) x -#else -#define IF_NEON_CRYPTO(x) -#endif - -#if HAVE_NEON_SHA512 -#define IF_NEON_SHA512(x) x -#else -#define IF_NEON_SHA512(x) -#endif - -#if HAVE_NEON_PMULL -#define IF_NEON_PMULL(x) x -#else -#define IF_NEON_PMULL(x) -#endif - -/* Ciphers that we expect to pass this test. Blowfish and Arcfour are - * intentionally omitted, because we already know they don't. */ -#define CIPHERS(X, Y) \ - X(Y, ssh_3des_ssh1) \ - X(Y, ssh_3des_ssh2_ctr) \ - X(Y, ssh_3des_ssh2) \ - X(Y, ssh_des) \ - X(Y, ssh_des_sshcom_ssh2) \ - X(Y, ssh_aes256_sdctr) \ - X(Y, ssh_aes256_gcm) \ - X(Y, ssh_aes256_cbc) \ - X(Y, ssh_aes192_sdctr) \ - X(Y, ssh_aes192_gcm) \ - X(Y, ssh_aes192_cbc) \ - X(Y, ssh_aes128_sdctr) \ - X(Y, ssh_aes128_gcm) \ - X(Y, ssh_aes128_cbc) \ - X(Y, ssh_aes256_sdctr_sw) \ - X(Y, ssh_aes256_gcm_sw) \ - X(Y, ssh_aes256_cbc_sw) \ - X(Y, ssh_aes192_sdctr_sw) \ - X(Y, ssh_aes192_gcm_sw) \ - X(Y, ssh_aes192_cbc_sw) \ - X(Y, ssh_aes128_sdctr_sw) \ - X(Y, ssh_aes128_gcm_sw) \ - X(Y, ssh_aes128_cbc_sw) \ - IF_AES_NI(X(Y, ssh_aes256_sdctr_ni)) \ - IF_AES_NI(X(Y, ssh_aes256_gcm_ni)) \ - IF_AES_NI(X(Y, ssh_aes256_cbc_ni)) \ - IF_AES_NI(X(Y, ssh_aes192_sdctr_ni)) \ - IF_AES_NI(X(Y, ssh_aes192_gcm_ni)) \ - IF_AES_NI(X(Y, ssh_aes192_cbc_ni)) \ - IF_AES_NI(X(Y, ssh_aes128_sdctr_ni)) \ - IF_AES_NI(X(Y, ssh_aes128_gcm_ni)) \ - IF_AES_NI(X(Y, ssh_aes128_cbc_ni)) \ - IF_NEON_CRYPTO(X(Y, ssh_aes256_sdctr_neon)) \ - IF_NEON_CRYPTO(X(Y, ssh_aes256_gcm_neon)) \ - IF_NEON_CRYPTO(X(Y, ssh_aes256_cbc_neon)) \ - IF_NEON_CRYPTO(X(Y, ssh_aes192_sdctr_neon)) \ - IF_NEON_CRYPTO(X(Y, ssh_aes192_gcm_neon)) \ - IF_NEON_CRYPTO(X(Y, ssh_aes192_cbc_neon)) \ - IF_NEON_CRYPTO(X(Y, ssh_aes128_sdctr_neon)) \ - IF_NEON_CRYPTO(X(Y, ssh_aes128_gcm_neon)) \ - IF_NEON_CRYPTO(X(Y, ssh_aes128_cbc_neon)) \ - X(Y, ssh2_chacha20_poly1305) \ - /* end of list */ - -#define CIPHER_TESTLIST(X, name) X(cipher_ ## name) - -#define SIMPLE_MACS(X, Y) \ - X(Y, ssh_hmac_md5) \ - X(Y, ssh_hmac_sha1) \ - X(Y, ssh_hmac_sha1_buggy) \ - X(Y, ssh_hmac_sha1_96) \ - X(Y, ssh_hmac_sha1_96_buggy) \ - X(Y, ssh_hmac_sha256) \ - X(Y, ssh_hmac_sha512) \ - /* end of list */ - -#define ALL_MACS(X, Y) \ - SIMPLE_MACS(X, Y) \ - X(Y, poly1305) \ - X(Y, aesgcm_sw_sw) \ - X(Y, aesgcm_sw_refpoly) \ - IF_AES_NI(X(Y, aesgcm_ni_sw)) \ - IF_NEON_CRYPTO(X(Y, aesgcm_neon_sw)) \ - IF_CLMUL(X(Y, aesgcm_sw_clmul)) \ - IF_NEON_PMULL(X(Y, aesgcm_sw_neon)) \ - IF_AES_NI(IF_CLMUL(X(Y, aesgcm_ni_clmul))) \ - IF_NEON_CRYPTO(IF_NEON_PMULL(X(Y, aesgcm_neon_neon))) \ - /* end of list */ - -#define MAC_TESTLIST(X, name) X(mac_ ## name) - -#define HASHES(X, Y) \ - X(Y, ssh_md5) \ - X(Y, ssh_sha1) \ - X(Y, ssh_sha1_sw) \ - X(Y, ssh_sha256) \ - X(Y, ssh_sha256_sw) \ - X(Y, ssh_sha384) \ - X(Y, ssh_sha512) \ - X(Y, ssh_sha384_sw) \ - X(Y, ssh_sha512_sw) \ - IF_SHA_NI(X(Y, ssh_sha256_ni)) \ - IF_SHA_NI(X(Y, ssh_sha1_ni)) \ - IF_NEON_CRYPTO(X(Y, ssh_sha256_neon)) \ - IF_NEON_CRYPTO(X(Y, ssh_sha1_neon)) \ - IF_NEON_SHA512(X(Y, ssh_sha384_neon)) \ - IF_NEON_SHA512(X(Y, ssh_sha512_neon)) \ - X(Y, ssh_sha3_224) \ - X(Y, ssh_sha3_256) \ - X(Y, ssh_sha3_384) \ - X(Y, ssh_sha3_512) \ - X(Y, ssh_shake256_114bytes) \ - X(Y, ssh_blake2b) \ - /* end of list */ - -#define HASH_TESTLIST(X, name) X(hash_ ## name) - -#define TESTLIST(X) \ - X(mp_get_nbits) \ - X(mp_from_decimal) \ - X(mp_from_hex) \ - X(mp_get_decimal) \ - X(mp_get_hex) \ - X(mp_cmp_hs) \ - X(mp_cmp_eq) \ - X(mp_min) \ - X(mp_max) \ - X(mp_select_into) \ - X(mp_cond_swap) \ - X(mp_cond_clear) \ - X(mp_add) \ - X(mp_sub) \ - X(mp_mul) \ - X(mp_rshift_safe) \ - X(mp_divmod) \ - X(mp_nthroot) \ - X(mp_modadd) \ - X(mp_modsub) \ - X(mp_modmul) \ - X(mp_modpow) \ - X(mp_invert_mod_2to) \ - X(mp_invert) \ - X(mp_modsqrt) \ - X(ecc_weierstrass_add) \ - X(ecc_weierstrass_double) \ - X(ecc_weierstrass_add_general) \ - X(ecc_weierstrass_multiply) \ - X(ecc_weierstrass_is_identity) \ - X(ecc_weierstrass_get_affine) \ - X(ecc_weierstrass_decompress) \ - X(ecc_montgomery_diff_add) \ - X(ecc_montgomery_double) \ - X(ecc_montgomery_multiply) \ - X(ecc_montgomery_get_affine) \ - X(ecc_edwards_add) \ - X(ecc_edwards_multiply) \ - X(ecc_edwards_eq) \ - X(ecc_edwards_get_affine) \ - X(ecc_edwards_decompress) \ - CIPHERS(CIPHER_TESTLIST, X) \ - ALL_MACS(MAC_TESTLIST, X) \ - HASHES(HASH_TESTLIST, X) \ - X(argon2) \ - X(primegen_probabilistic) \ - X(ntru) \ - /* end of list */ - -static void test_mp_get_nbits(void) -{ - mp_int *z = mp_new(512); - static const size_t bitposns[] = { - 0, 1, 5, 16, 23, 32, 67, 123, 234, 511 - }; - mp_int *prev = mp_from_integer(0); - for (size_t i = 0; i < looplimit(lenof(bitposns)); i++) { - mp_int *x = mp_power_2(bitposns[i]); - mp_add_into(z, x, prev); - mp_free(prev); - prev = x; - log_start(); - mp_get_nbits(z); - log_end(); - } - mp_free(prev); - mp_free(z); -} - -static void test_mp_from_decimal(void) -{ - char dec[64]; - static const size_t starts[] = { 0, 1, 5, 16, 23, 32, 63, 64 }; - for (size_t i = 0; i < looplimit(lenof(starts)); i++) { - memset(dec, '0', lenof(dec)); - for (size_t j = starts[i]; j < lenof(dec); j++) { - uint8_t r[4]; - random_read(r, 4); - dec[j] = '0' + GET_32BIT_MSB_FIRST(r) % 10; - } - log_start(); - mp_int *x = mp_from_decimal_pl(make_ptrlen(dec, lenof(dec))); - log_end(); - mp_free(x); - } -} - -static void test_mp_from_hex(void) -{ - char hex[64]; - static const size_t starts[] = { 0, 1, 5, 16, 23, 32, 63, 64 }; - static const char digits[] = "0123456789abcdefABCDEF"; - for (size_t i = 0; i < looplimit(lenof(starts)); i++) { - memset(hex, '0', lenof(hex)); - for (size_t j = starts[i]; j < lenof(hex); j++) { - uint8_t r[4]; - random_read(r, 4); - hex[j] = digits[GET_32BIT_MSB_FIRST(r) % lenof(digits)]; - } - log_start(); - mp_int *x = mp_from_hex_pl(make_ptrlen(hex, lenof(hex))); - log_end(); - mp_free(x); - } -} - -static void test_mp_string_format(char *(*mp_format)(mp_int *x)) -{ - mp_int *z = mp_new(512); - static const size_t bitposns[] = { - 0, 1, 5, 16, 23, 32, 67, 123, 234, 511 - }; - for (size_t i = 0; i < looplimit(lenof(bitposns)); i++) { - mp_random_bits_into(z, bitposns[i]); - log_start(); - char *formatted = mp_format(z); - log_end(); - sfree(formatted); - } - mp_free(z); -} - -static void test_mp_get_decimal(void) -{ - test_mp_string_format(mp_get_decimal); -} - -static void test_mp_get_hex(void) -{ - test_mp_string_format(mp_get_hex); -} - -static void test_mp_cmp(unsigned (*mp_cmp)(mp_int *a, mp_int *b)) -{ - mp_int *a = mp_new(512), *b = mp_new(512); - static const size_t bitposns[] = { - 0, 1, 5, 16, 23, 32, 67, 123, 234, 511 - }; - for (size_t i = 0; i < looplimit(lenof(bitposns)); i++) { - mp_random_fill(b); - mp_int *x = mp_random_bits(bitposns[i]); - mp_xor_into(a, b, x); - mp_free(x); - log_start(); - mp_cmp(a, b); - log_end(); - } - mp_free(a); - mp_free(b); -} - -static void test_mp_cmp_hs(void) -{ - test_mp_cmp(mp_cmp_hs); -} - -static void test_mp_cmp_eq(void) -{ - test_mp_cmp(mp_cmp_eq); -} - -static void test_mp_minmax( - void (*mp_minmax_into)(mp_int *r, mp_int *x, mp_int *y)) -{ - mp_int *a = mp_new(256), *b = mp_new(256); - for (size_t i = 0; i < looplimit(10); i++) { - uint8_t lens[2]; - random_read(lens, 2); - mp_int *x = mp_random_bits(lens[0]); - mp_copy_into(a, x); - mp_free(x); - mp_int *y = mp_random_bits(lens[1]); - mp_copy_into(a, y); - mp_free(y); - log_start(); - mp_minmax_into(a, a, b); - log_end(); - } - mp_free(a); - mp_free(b); -} - -static void test_mp_max(void) -{ - test_mp_minmax(mp_max_into); -} - -static void test_mp_min(void) -{ - test_mp_minmax(mp_min_into); -} - -static void test_mp_select_into(void) -{ - mp_int *a = mp_random_bits(256); - mp_int *b = mp_random_bits(512); - mp_int *r = mp_new(384); - for (size_t i = 0; i < looplimit(16); i++) { - log_start(); - mp_select_into(r, a, b, i & 1); - log_end(); - } - mp_free(a); - mp_free(b); - mp_free(r); -} - -static void test_mp_cond_swap(void) -{ - mp_int *a = mp_random_bits(512); - mp_int *b = mp_random_bits(512); - for (size_t i = 0; i < looplimit(16); i++) { - log_start(); - mp_cond_swap(a, b, i & 1); - log_end(); - } - mp_free(a); - mp_free(b); -} - -static void test_mp_cond_clear(void) -{ - mp_int *a = mp_random_bits(512); - mp_int *x = mp_copy(a); - for (size_t i = 0; i < looplimit(16); i++) { - mp_copy_into(x, a); - log_start(); - mp_cond_clear(a, i & 1); - log_end(); - } - mp_free(a); - mp_free(x); -} - -static void test_mp_arithmetic(mp_int *(*mp_arith)(mp_int *x, mp_int *y)) -{ - mp_int *a = mp_new(256), *b = mp_new(512); - for (size_t i = 0; i < looplimit(16); i++) { - mp_random_fill(a); - mp_random_fill(b); - log_start(); - mp_int *r = mp_arith(a, b); - log_end(); - mp_free(r); - } - mp_free(a); - mp_free(b); -} - -static void test_mp_add(void) -{ - test_mp_arithmetic(mp_add); -} - -static void test_mp_sub(void) -{ - test_mp_arithmetic(mp_sub); -} - -static void test_mp_mul(void) -{ - test_mp_arithmetic(mp_mul); -} - -static void test_mp_invert(void) -{ - test_mp_arithmetic(mp_invert); -} - -static void test_mp_rshift_safe(void) -{ - mp_int *x = mp_random_bits(256); - - for (size_t i = 0; i < looplimit(mp_max_bits(x)+1); i++) { - log_start(); - mp_int *r = mp_rshift_safe(x, i); - log_end(); - mp_free(r); - } - - mp_free(x); -} - -static void test_mp_divmod(void) -{ - mp_int *n = mp_new(256), *d = mp_new(256); - mp_int *q = mp_new(256), *r = mp_new(256); - - for (size_t i = 0; i < looplimit(32); i++) { - uint8_t sizes[2]; - random_read(sizes, 2); - mp_random_bits_into(n, sizes[0]); - mp_random_bits_into(d, sizes[1]); - log_start(); - mp_divmod_into(n, d, q, r); - log_end(); - } - - mp_free(n); - mp_free(d); - mp_free(q); - mp_free(r); -} - -static void test_mp_nthroot(void) -{ - mp_int *x = mp_new(256), *remainder = mp_new(256); - - for (size_t i = 0; i < looplimit(32); i++) { - uint8_t sizes[1]; - random_read(sizes, 1); - mp_random_bits_into(x, sizes[0]); - log_start(); - mp_free(mp_nthroot(x, 3, remainder)); - log_end(); - } - - mp_free(x); - mp_free(remainder); -} - -static void test_mp_modarith( - mp_int *(*mp_modarith)(mp_int *x, mp_int *y, mp_int *modulus)) -{ - mp_int *base = mp_new(256); - mp_int *exponent = mp_new(256); - mp_int *modulus = mp_new(256); - - for (size_t i = 0; i < looplimit(8); i++) { - mp_random_fill(base); - mp_random_fill(exponent); - mp_random_fill(modulus); - mp_set_bit(modulus, 0, 1); /* we only support odd moduli */ - - log_start(); - mp_int *out = mp_modarith(base, exponent, modulus); - log_end(); - - mp_free(out); - } - - mp_free(base); - mp_free(exponent); - mp_free(modulus); -} - -static void test_mp_modadd(void) -{ - test_mp_modarith(mp_modadd); -} - -static void test_mp_modsub(void) -{ - test_mp_modarith(mp_modsub); -} - -static void test_mp_modmul(void) -{ - test_mp_modarith(mp_modmul); -} - -static void test_mp_modpow(void) -{ - test_mp_modarith(mp_modpow); -} - -static void test_mp_invert_mod_2to(void) -{ - mp_int *x = mp_new(512); - - for (size_t i = 0; i < looplimit(32); i++) { - mp_random_fill(x); - mp_set_bit(x, 0, 1); /* input should be odd */ - - log_start(); - mp_int *out = mp_invert_mod_2to(x, 511); - log_end(); - - mp_free(out); - } - - mp_free(x); -} - -static void test_mp_modsqrt(void) -{ - /* The prime isn't secret in this function (and in any case - * finding a non-square on the fly would be prohibitively - * annoying), so I hardcode a fixed one, selected to have a lot of - * factors of two in p-1 so as to exercise lots of choices in the - * algorithm. */ - mp_int *p = - MP_LITERAL(0xb56a517b206a88c73cfa9ec6f704c7030d18212cace82401); - mp_int *nonsquare = MP_LITERAL(0x5); - size_t bits = mp_max_bits(p); - ModsqrtContext *sc = modsqrt_new(p, nonsquare); - mp_free(p); - mp_free(nonsquare); - - mp_int *x = mp_new(bits); - unsigned success; - - /* Do one initial call to cause the lazily initialised sub-context - * to be set up. This will take a while, but it can't be helped. */ - mp_int *unwanted = mp_modsqrt(sc, x, &success); - mp_free(unwanted); - - for (size_t i = 0; i < looplimit(8); i++) { - mp_random_bits_into(x, bits - 1); - log_start(); - mp_int *out = mp_modsqrt(sc, x, &success); - log_end(); - mp_free(out); - } - - mp_free(x); - modsqrt_free(sc); -} - -static WeierstrassCurve *wcurve(void) -{ - mp_int *p = MP_LITERAL(0xc19337603dc856acf31e01375a696fdf5451); - mp_int *a = MP_LITERAL(0x864946f50eecca4cde7abad4865e34be8f67); - mp_int *b = MP_LITERAL(0x6a5bf56db3a03ba91cfbf3241916c90feeca); - mp_int *nonsquare = mp_from_integer(3); - WeierstrassCurve *wc = ecc_weierstrass_curve(p, a, b, nonsquare); - mp_free(p); - mp_free(a); - mp_free(b); - mp_free(nonsquare); - return wc; -} - -static WeierstrassPoint *wpoint(WeierstrassCurve *wc, size_t index) -{ - mp_int *x = NULL, *y = NULL; - WeierstrassPoint *wp; - switch (index) { - case 0: - break; - case 1: - x = MP_LITERAL(0x12345); - y = MP_LITERAL(0x3c2c799a365b53d003ef37dab65860bf80ae); - break; - case 2: - x = MP_LITERAL(0x4e1c77e3c00f7c3b15869e6a4e5f86b3ee53); - y = MP_LITERAL(0x5bde01693130591400b5c9d257d8325a44a5); - break; - case 3: - x = MP_LITERAL(0xb5f0e722b2f0f7e729f55ba9f15511e3b399); - y = MP_LITERAL(0x033d636b855c931cfe679f0b18db164a0d64); - break; - case 4: - x = MP_LITERAL(0xb5f0e722b2f0f7e729f55ba9f15511e3b399); - y = MP_LITERAL(0xbe55d3f4b86bc38ff4b6622c418e599546ed); - break; - default: - unreachable("only 5 example Weierstrass points defined"); - } - if (x && y) { - wp = ecc_weierstrass_point_new(wc, x, y); - } else { - wp = ecc_weierstrass_point_new_identity(wc); - } - if (x) - mp_free(x); - if (y) - mp_free(y); - return wp; -} - -static void test_ecc_weierstrass_add(void) -{ - WeierstrassCurve *wc = wcurve(); - WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); - WeierstrassPoint *b = ecc_weierstrass_point_new_identity(wc); - for (size_t i = 0; i < looplimit(5); i++) { - for (size_t j = 0; j < looplimit(5); j++) { - if (i == 0 || j == 0 || i == j || - (i==3 && j==4) || (i==4 && j==3)) - continue; /* difficult cases */ - - WeierstrassPoint *A = wpoint(wc, i), *B = wpoint(wc, j); - ecc_weierstrass_point_copy_into(a, A); - ecc_weierstrass_point_copy_into(b, B); - ecc_weierstrass_point_free(A); - ecc_weierstrass_point_free(B); - - log_start(); - WeierstrassPoint *r = ecc_weierstrass_add(a, b); - log_end(); - ecc_weierstrass_point_free(r); - } - } - ecc_weierstrass_point_free(a); - ecc_weierstrass_point_free(b); - ecc_weierstrass_curve_free(wc); -} - -static void test_ecc_weierstrass_double(void) -{ - WeierstrassCurve *wc = wcurve(); - WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); - for (size_t i = 0; i < looplimit(5); i++) { - WeierstrassPoint *A = wpoint(wc, i); - ecc_weierstrass_point_copy_into(a, A); - ecc_weierstrass_point_free(A); - - log_start(); - WeierstrassPoint *r = ecc_weierstrass_double(a); - log_end(); - ecc_weierstrass_point_free(r); - } - ecc_weierstrass_point_free(a); - ecc_weierstrass_curve_free(wc); -} - -static void test_ecc_weierstrass_add_general(void) -{ - WeierstrassCurve *wc = wcurve(); - WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); - WeierstrassPoint *b = ecc_weierstrass_point_new_identity(wc); - for (size_t i = 0; i < looplimit(5); i++) { - for (size_t j = 0; j < looplimit(5); j++) { - WeierstrassPoint *A = wpoint(wc, i), *B = wpoint(wc, j); - ecc_weierstrass_point_copy_into(a, A); - ecc_weierstrass_point_copy_into(b, B); - ecc_weierstrass_point_free(A); - ecc_weierstrass_point_free(B); - - log_start(); - WeierstrassPoint *r = ecc_weierstrass_add_general(a, b); - log_end(); - ecc_weierstrass_point_free(r); - } - } - ecc_weierstrass_point_free(a); - ecc_weierstrass_point_free(b); - ecc_weierstrass_curve_free(wc); -} - -static void test_ecc_weierstrass_multiply(void) -{ - WeierstrassCurve *wc = wcurve(); - WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); - mp_int *exponent = mp_new(56); - for (size_t i = 1; i < looplimit(5); i++) { - WeierstrassPoint *A = wpoint(wc, i); - ecc_weierstrass_point_copy_into(a, A); - ecc_weierstrass_point_free(A); - mp_random_fill(exponent); - - log_start(); - WeierstrassPoint *r = ecc_weierstrass_multiply(a, exponent); - log_end(); - - ecc_weierstrass_point_free(r); - } - ecc_weierstrass_point_free(a); - ecc_weierstrass_curve_free(wc); - mp_free(exponent); -} - -static void test_ecc_weierstrass_is_identity(void) -{ - WeierstrassCurve *wc = wcurve(); - WeierstrassPoint *a = ecc_weierstrass_point_new_identity(wc); - for (size_t i = 1; i < looplimit(5); i++) { - WeierstrassPoint *A = wpoint(wc, i); - ecc_weierstrass_point_copy_into(a, A); - ecc_weierstrass_point_free(A); - - log_start(); - ecc_weierstrass_is_identity(a); - log_end(); - } - ecc_weierstrass_point_free(a); - ecc_weierstrass_curve_free(wc); -} - -static void test_ecc_weierstrass_get_affine(void) -{ - WeierstrassCurve *wc = wcurve(); - WeierstrassPoint *r = ecc_weierstrass_point_new_identity(wc); - for (size_t i = 0; i < looplimit(4); i++) { - WeierstrassPoint *A = wpoint(wc, i), *B = wpoint(wc, i+1); - WeierstrassPoint *R = ecc_weierstrass_add_general(A, B); - ecc_weierstrass_point_copy_into(r, R); - ecc_weierstrass_point_free(A); - ecc_weierstrass_point_free(B); - ecc_weierstrass_point_free(R); - - log_start(); - mp_int *x, *y; - ecc_weierstrass_get_affine(r, &x, &y); - log_end(); - mp_free(x); - mp_free(y); - } - ecc_weierstrass_point_free(r); - ecc_weierstrass_curve_free(wc); -} - -static void test_ecc_weierstrass_decompress(void) -{ - WeierstrassCurve *wc = wcurve(); - - /* As in the mp_modsqrt test, prime the lazy initialisation of the - * ModsqrtContext */ - mp_int *x = mp_new(144); - WeierstrassPoint *a = ecc_weierstrass_point_new_from_x(wc, x, 0); - if (a) /* don't care whether this one succeeded */ - ecc_weierstrass_point_free(a); - - for (size_t p = 0; p < looplimit(2); p++) { - for (size_t i = 1; i < looplimit(5); i++) { - WeierstrassPoint *A = wpoint(wc, i); - mp_int *X; - ecc_weierstrass_get_affine(A, &X, NULL); - mp_copy_into(x, X); - mp_free(X); - ecc_weierstrass_point_free(A); - - log_start(); - WeierstrassPoint *a = ecc_weierstrass_point_new_from_x(wc, x, p); - log_end(); - - ecc_weierstrass_point_free(a); - } - } - mp_free(x); - ecc_weierstrass_curve_free(wc); -} - -static MontgomeryCurve *mcurve(void) -{ - mp_int *p = MP_LITERAL(0xde978eb1db35236a5792e9f0c04d86000659); - mp_int *a = MP_LITERAL(0x799b62a612b1b30e1c23cea6d67b2e33c51a); - mp_int *b = MP_LITERAL(0x944bf9042b56821a8c9e0b49b636c2502b2b); - MontgomeryCurve *mc = ecc_montgomery_curve(p, a, b); - mp_free(p); - mp_free(a); - mp_free(b); - return mc; -} - -static MontgomeryPoint *mpoint(MontgomeryCurve *wc, size_t index) -{ - mp_int *x = NULL; - MontgomeryPoint *mp; - switch (index) { - case 0: - x = MP_LITERAL(31415); - break; - case 1: - x = MP_LITERAL(0x4d352c654c06eecfe19104118857b38398e8); - break; - case 2: - x = MP_LITERAL(0x03fca2a73983bc3434caae3134599cd69cce); - break; - case 3: - x = MP_LITERAL(0xa0fd735ce9b3406498b5f035ee655bda4e15); - break; - case 4: - x = MP_LITERAL(0x7c7f46a00cc286dbe47db39b6d8f5efd920e); - break; - case 5: - x = MP_LITERAL(0x07a6dc30d3b320448e6f8999be417e6b7c6b); - break; - case 6: - x = MP_LITERAL(0x7832da5fc16dfbd358170b2b96896cd3cd06); - break; - default: - unreachable("only 7 example Weierstrass points defined"); - } - mp = ecc_montgomery_point_new(wc, x); - mp_free(x); - return mp; -} - -static void test_ecc_montgomery_diff_add(void) -{ - MontgomeryCurve *wc = mcurve(); - MontgomeryPoint *a = NULL, *b = NULL, *c = NULL; - for (size_t i = 0; i < looplimit(5); i++) { - MontgomeryPoint *A = mpoint(wc, i); - MontgomeryPoint *B = mpoint(wc, i); - MontgomeryPoint *C = mpoint(wc, i); - if (!a) { - a = A; - b = B; - c = C; - } else { - ecc_montgomery_point_copy_into(a, A); - ecc_montgomery_point_copy_into(b, B); - ecc_montgomery_point_copy_into(c, C); - ecc_montgomery_point_free(A); - ecc_montgomery_point_free(B); - ecc_montgomery_point_free(C); - } - - log_start(); - MontgomeryPoint *r = ecc_montgomery_diff_add(b, c, a); - log_end(); - - ecc_montgomery_point_free(r); - } - ecc_montgomery_point_free(a); - ecc_montgomery_point_free(b); - ecc_montgomery_point_free(c); - ecc_montgomery_curve_free(wc); -} - -static void test_ecc_montgomery_double(void) -{ - MontgomeryCurve *wc = mcurve(); - MontgomeryPoint *a = NULL; - for (size_t i = 0; i < looplimit(7); i++) { - MontgomeryPoint *A = mpoint(wc, i); - if (!a) { - a = A; - } else { - ecc_montgomery_point_copy_into(a, A); - ecc_montgomery_point_free(A); - } - - log_start(); - MontgomeryPoint *r = ecc_montgomery_double(a); - log_end(); - - ecc_montgomery_point_free(r); - } - ecc_montgomery_point_free(a); - ecc_montgomery_curve_free(wc); -} - -static void test_ecc_montgomery_multiply(void) -{ - MontgomeryCurve *wc = mcurve(); - MontgomeryPoint *a = NULL; - mp_int *exponent = mp_new(56); - for (size_t i = 0; i < looplimit(7); i++) { - MontgomeryPoint *A = mpoint(wc, i); - if (!a) { - a = A; - } else { - ecc_montgomery_point_copy_into(a, A); - ecc_montgomery_point_free(A); - } - mp_random_fill(exponent); - - log_start(); - MontgomeryPoint *r = ecc_montgomery_multiply(a, exponent); - log_end(); - - ecc_montgomery_point_free(r); - } - ecc_montgomery_point_free(a); - ecc_montgomery_curve_free(wc); - mp_free(exponent); -} - -static void test_ecc_montgomery_get_affine(void) -{ - MontgomeryCurve *wc = mcurve(); - MontgomeryPoint *r = NULL; - for (size_t i = 0; i < looplimit(5); i++) { - MontgomeryPoint *A = mpoint(wc, i); - MontgomeryPoint *B = mpoint(wc, i); - MontgomeryPoint *C = mpoint(wc, i); - MontgomeryPoint *R = ecc_montgomery_diff_add(B, C, A); - ecc_montgomery_point_free(A); - ecc_montgomery_point_free(B); - ecc_montgomery_point_free(C); - if (!r) { - r = R; - } else { - ecc_montgomery_point_copy_into(r, R); - ecc_montgomery_point_free(R); - } - - log_start(); - mp_int *x; - ecc_montgomery_get_affine(r, &x); - log_end(); - - mp_free(x); - } - ecc_montgomery_point_free(r); - ecc_montgomery_curve_free(wc); -} - -static EdwardsCurve *ecurve(void) -{ - mp_int *p = MP_LITERAL(0xfce2dac1704095de0b5c48876c45063cd475); - mp_int *d = MP_LITERAL(0xbd4f77401c3b14ae1742a7d1d367adac8f3e); - mp_int *a = MP_LITERAL(0x51d0845da3fa871aaac4341adea53b861919); - mp_int *nonsquare = mp_from_integer(2); - EdwardsCurve *ec = ecc_edwards_curve(p, d, a, nonsquare); - mp_free(p); - mp_free(d); - mp_free(a); - mp_free(nonsquare); - return ec; -} - -static EdwardsPoint *epoint(EdwardsCurve *wc, size_t index) -{ - mp_int *x, *y; - EdwardsPoint *ep; - switch (index) { - case 0: - x = MP_LITERAL(0x0); - y = MP_LITERAL(0x1); - break; - case 1: - x = MP_LITERAL(0x3d8aef0294a67c1c7e8e185d987716250d7c); - y = MP_LITERAL(0x27184); - break; - case 2: - x = MP_LITERAL(0xf44ed5b8a6debfd3ab24b7874cd2589fd672); - y = MP_LITERAL(0xd635d8d15d367881c8a3af472c8fe487bf40); - break; - case 3: - x = MP_LITERAL(0xde114ecc8b944684415ef81126a07269cd30); - y = MP_LITERAL(0xbe0fd45ff67ebba047ed0ec5a85d22e688a1); - break; - case 4: - x = MP_LITERAL(0x76bd2f90898d271b492c9c20dd7bbfe39fe5); - y = MP_LITERAL(0xbf1c82698b4a5a12c1057631c1ebdc216ae2); - break; - default: - unreachable("only 5 example Edwards points defined"); - } - ep = ecc_edwards_point_new(wc, x, y); - mp_free(x); - mp_free(y); - return ep; -} - -static void test_ecc_edwards_add(void) -{ - EdwardsCurve *ec = ecurve(); - EdwardsPoint *a = NULL, *b = NULL; - for (size_t i = 0; i < looplimit(5); i++) { - for (size_t j = 0; j < looplimit(5); j++) { - EdwardsPoint *A = epoint(ec, i), *B = epoint(ec, j); - if (!a) { - a = A; - b = B; - } else { - ecc_edwards_point_copy_into(a, A); - ecc_edwards_point_copy_into(b, B); - ecc_edwards_point_free(A); - ecc_edwards_point_free(B); - } - - log_start(); - EdwardsPoint *r = ecc_edwards_add(a, b); - log_end(); - - ecc_edwards_point_free(r); - } - } - ecc_edwards_point_free(a); - ecc_edwards_point_free(b); - ecc_edwards_curve_free(ec); -} - -static void test_ecc_edwards_multiply(void) -{ - EdwardsCurve *ec = ecurve(); - EdwardsPoint *a = NULL; - mp_int *exponent = mp_new(56); - for (size_t i = 1; i < looplimit(5); i++) { - EdwardsPoint *A = epoint(ec, i); - if (!a) { - a = A; - } else { - ecc_edwards_point_copy_into(a, A); - ecc_edwards_point_free(A); - } - mp_random_fill(exponent); - - log_start(); - EdwardsPoint *r = ecc_edwards_multiply(a, exponent); - log_end(); - - ecc_edwards_point_free(r); - } - ecc_edwards_point_free(a); - ecc_edwards_curve_free(ec); - mp_free(exponent); -} - -static void test_ecc_edwards_eq(void) -{ - EdwardsCurve *ec = ecurve(); - EdwardsPoint *a = NULL, *b = NULL; - for (size_t i = 0; i < looplimit(5); i++) { - for (size_t j = 0; j < looplimit(5); j++) { - EdwardsPoint *A = epoint(ec, i), *B = epoint(ec, j); - if (!a) { - a = A; - b = B; - } else { - ecc_edwards_point_copy_into(a, A); - ecc_edwards_point_copy_into(b, B); - ecc_edwards_point_free(A); - ecc_edwards_point_free(B); - } - - log_start(); - ecc_edwards_eq(a, b); - log_end(); - } - } - ecc_edwards_point_free(a); - ecc_edwards_point_free(b); - ecc_edwards_curve_free(ec); -} - -static void test_ecc_edwards_get_affine(void) -{ - EdwardsCurve *ec = ecurve(); - EdwardsPoint *r = NULL; - for (size_t i = 0; i < looplimit(4); i++) { - EdwardsPoint *A = epoint(ec, i), *B = epoint(ec, i+1); - EdwardsPoint *R = ecc_edwards_add(A, B); - ecc_edwards_point_free(A); - ecc_edwards_point_free(B); - if (!r) { - r = R; - } else { - ecc_edwards_point_copy_into(r, R); - ecc_edwards_point_free(R); - } - - log_start(); - mp_int *x, *y; - ecc_edwards_get_affine(r, &x, &y); - log_end(); - - mp_free(x); - mp_free(y); - } - ecc_edwards_point_free(r); - ecc_edwards_curve_free(ec); -} - -static void test_ecc_edwards_decompress(void) -{ - EdwardsCurve *ec = ecurve(); - - /* As in the mp_modsqrt test, prime the lazy initialisation of the - * ModsqrtContext */ - mp_int *y = mp_new(144); - EdwardsPoint *a = ecc_edwards_point_new_from_y(ec, y, 0); - if (a) /* don't care whether this one succeeded */ - ecc_edwards_point_free(a); - - for (size_t p = 0; p < looplimit(2); p++) { - for (size_t i = 0; i < looplimit(5); i++) { - EdwardsPoint *A = epoint(ec, i); - mp_int *Y; - ecc_edwards_get_affine(A, NULL, &Y); - mp_copy_into(y, Y); - mp_free(Y); - ecc_edwards_point_free(A); - - log_start(); - EdwardsPoint *a = ecc_edwards_point_new_from_y(ec, y, p); - log_end(); - - ecc_edwards_point_free(a); - } - } - mp_free(y); - ecc_edwards_curve_free(ec); -} - -static void test_cipher(const ssh_cipheralg *calg) -{ - ssh_cipher *c = ssh_cipher_new(calg); - if (!c) { - test_skipped = true; - return; - } - const ssh2_macalg *malg = calg->required_mac; - ssh2_mac *m = NULL; - if (malg) { - m = ssh2_mac_new(malg, c); - if (!m) { - ssh_cipher_free(c); - test_skipped = true; - return; - } - } - - uint8_t *ckey = snewn(calg->padded_keybytes, uint8_t); - uint8_t *civ = snewn(calg->blksize, uint8_t); - uint8_t *mkey = malg ? snewn(malg->keylen, uint8_t) : NULL; - size_t datalen = calg->blksize * 8; - size_t maclen = malg ? malg->len : 0; - uint8_t *data = snewn(datalen + maclen, uint8_t); - size_t lenlen = 4; - uint8_t *lendata = snewn(lenlen, uint8_t); - - for (size_t i = 0; i < looplimit(16); i++) { - random_read(ckey, calg->padded_keybytes); - if (malg) - random_read(mkey, malg->keylen); - random_read(data, datalen); - random_read(lendata, lenlen); - if (i == 0) { - /* Ensure one of our test IVs will cause SDCTR wraparound */ - memset(civ, 0xFF, calg->blksize); - } else { - random_read(civ, calg->blksize); - } - uint8_t seqbuf[4]; - random_read(seqbuf, 4); - uint32_t seq = GET_32BIT_MSB_FIRST(seqbuf); - - log_start(); - ssh_cipher_setkey(c, ckey); - ssh_cipher_setiv(c, civ); - if (m) - ssh2_mac_setkey(m, make_ptrlen(mkey, malg->keylen)); - if (calg->flags & SSH_CIPHER_SEPARATE_LENGTH) - ssh_cipher_encrypt_length(c, data, datalen, seq); - ssh_cipher_encrypt(c, data, datalen); - if (m) { - ssh2_mac_generate(m, data, datalen, seq); - ssh2_mac_verify(m, data, datalen, seq); - } - if (calg->flags & SSH_CIPHER_SEPARATE_LENGTH) - ssh_cipher_decrypt_length(c, data, datalen, seq); - ssh_cipher_decrypt(c, data, datalen); - log_end(); - } - - sfree(ckey); - sfree(civ); - sfree(mkey); - sfree(data); - sfree(lendata); - if (m) - ssh2_mac_free(m); - ssh_cipher_free(c); -} - -#define CIPHER_TESTFN(Y_unused, cipher) \ - static void test_cipher_##cipher(void) { test_cipher(&cipher); } -CIPHERS(CIPHER_TESTFN, Y_unused) - -static void test_mac(const ssh2_macalg *malg, const ssh_cipheralg *calg) -{ - ssh_cipher *c = NULL; - if (calg) { - c = ssh_cipher_new(calg); - if (!c) { - test_skipped = true; - return; - } - } - - ssh2_mac *m = ssh2_mac_new(malg, c); - if (!m) { - test_skipped = true; - if (c) - ssh_cipher_free(c); - return; - } - - size_t ckeylen = calg ? calg->padded_keybytes : 0; - size_t civlen = calg ? calg->blksize : 0; - uint8_t *ckey = snewn(ckeylen, uint8_t); - uint8_t *civ = snewn(civlen, uint8_t); - uint8_t *mkey = snewn(malg->keylen, uint8_t); - size_t datalen = 256; - size_t maclen = malg->len; - uint8_t *data = snewn(datalen + maclen, uint8_t); - - for (size_t i = 0; i < looplimit(16); i++) { - random_read(ckey, ckeylen); - random_read(civ, civlen); - random_read(mkey, malg->keylen); - random_read(data, datalen); - uint8_t seqbuf[4]; - random_read(seqbuf, 4); - uint32_t seq = GET_32BIT_MSB_FIRST(seqbuf); - - log_start(); - if (c) { - ssh_cipher_setkey(c, ckey); - ssh_cipher_setiv(c, civ); - } - ssh2_mac_setkey(m, make_ptrlen(mkey, malg->keylen)); - ssh2_mac_generate(m, data, datalen, seq); - ssh2_mac_verify(m, data, datalen, seq); - log_end(); - } - - sfree(ckey); - sfree(civ); - sfree(mkey); - sfree(data); - ssh2_mac_free(m); - if (c) - ssh_cipher_free(c); -} - -#define MAC_TESTFN(Y_unused, mac) \ - static void test_mac_##mac(void) { test_mac(&mac, NULL); } -SIMPLE_MACS(MAC_TESTFN, Y_unused) - -static void test_mac_poly1305(void) -{ - test_mac(&ssh2_poly1305, &ssh2_chacha20_poly1305); -} - -static void test_mac_aesgcm_sw_sw(void) -{ - test_mac(&ssh2_aesgcm_mac_sw, &ssh_aes128_gcm_sw); -} - -static void test_mac_aesgcm_sw_refpoly(void) -{ - test_mac(&ssh2_aesgcm_mac_ref_poly, &ssh_aes128_gcm_sw); -} - -#if HAVE_AES_NI -static void test_mac_aesgcm_ni_sw(void) -{ - test_mac(&ssh2_aesgcm_mac_sw, &ssh_aes128_gcm_ni); -} -#endif - -#if HAVE_NEON_CRYPTO -static void test_mac_aesgcm_neon_sw(void) -{ - test_mac(&ssh2_aesgcm_mac_sw, &ssh_aes128_gcm_neon); -} -#endif - -#if HAVE_CLMUL -static void test_mac_aesgcm_sw_clmul(void) -{ - test_mac(&ssh2_aesgcm_mac_clmul, &ssh_aes128_gcm_sw); -} -#endif - -#if HAVE_NEON_PMULL -static void test_mac_aesgcm_sw_neon(void) -{ - test_mac(&ssh2_aesgcm_mac_neon, &ssh_aes128_gcm_sw); -} -#endif - -#if HAVE_AES_NI && HAVE_CLMUL -static void test_mac_aesgcm_ni_clmul(void) -{ - test_mac(&ssh2_aesgcm_mac_clmul, &ssh_aes128_gcm_ni); -} -#endif - -#if HAVE_NEON_CRYPTO && HAVE_NEON_PMULL -static void test_mac_aesgcm_neon_neon(void) -{ - test_mac(&ssh2_aesgcm_mac_neon, &ssh_aes128_gcm_neon); -} -#endif - -static void test_hash(const ssh_hashalg *halg) -{ - ssh_hash *h = ssh_hash_new(halg); - if (!h) { - test_skipped = true; - return; - } - - size_t datalen = 256; - uint8_t *data = snewn(datalen, uint8_t); - uint8_t *hash = snewn(halg->hlen, uint8_t); - - for (size_t i = 0; i < looplimit(16); i++) { - random_read(data, datalen); - - log_start(); - put_data(h, data, datalen); - ssh_hash_final(h, hash); - log_end(); - - h = ssh_hash_new(halg); - } - - sfree(data); - sfree(hash); - ssh_hash_free(h); -} - -#define HASH_TESTFN(Y_unused, hash) \ - static void test_hash_##hash(void) { test_hash(&hash); } -HASHES(HASH_TESTFN, Y_unused) - -struct test { - const char *testname; - void (*testfn)(void); -}; - -static void test_argon2(void) -{ - /* - * We can only expect the Argon2i variant to pass this stringent - * test for no data-dependency, because the other two variants of - * Argon2 have _deliberate_ data-dependency. - */ - size_t inlen = 48+16+24+8; - uint8_t *indata = snewn(inlen, uint8_t); - ptrlen password = make_ptrlen(indata, 48); - ptrlen salt = make_ptrlen(indata+48, 16); - ptrlen secret = make_ptrlen(indata+48+16, 24); - ptrlen assoc = make_ptrlen(indata+48+16+24, 8); - - strbuf *outdata = strbuf_new(); - strbuf_append(outdata, 256); - - for (size_t i = 0; i < looplimit(16); i++) { - strbuf_clear(outdata); - random_read(indata, inlen); - - log_start(); - argon2(Argon2i, 32, 2, 2, 144, password, salt, secret, assoc, outdata); - log_end(); - } - - sfree(indata); - strbuf_free(outdata); -} - -static void test_primegen(const PrimeGenerationPolicy *policy) -{ - static ProgressReceiver null_progress = { .vt = &null_progress_vt }; - - PrimeGenerationContext *pgc = primegen_new_context(policy); - - init_smallprimes(); - mp_int *pcopy = mp_new(128); - - for (size_t i = 0; i < looplimit(2); i++) { - while (true) { - random_advance_counter(); - struct random_state st = random_get_state(); - - PrimeCandidateSource *pcs = pcs_new(128); - pcs_set_oneshot(pcs); - pcs_ready(pcs); - mp_int *p = primegen_generate(pgc, pcs, &null_progress); - - if (p) { - mp_copy_into(pcopy, p); - sfree(p); - - random_set_state(st); - - log_start(); - PrimeCandidateSource *pcs = pcs_new(128); - pcs_set_oneshot(pcs); - pcs_ready(pcs); - mp_int *q = primegen_generate(pgc, pcs, &null_progress); - log_end(); - - assert(q); - assert(mp_cmp_eq(pcopy, q)); - mp_free(q); - break; - } - } - } - - mp_free(pcopy); - primegen_free_context(pgc); -} - -static void test_primegen_probabilistic(void) -{ - test_primegen(&primegen_probabilistic); -} - -static void test_ntru(void) -{ - unsigned p = 11, q = 59, w = 3; - uint16_t *pubkey_orig = snewn(p, uint16_t); - uint16_t *pubkey_check = snewn(p, uint16_t); - uint16_t *pubkey = snewn(p, uint16_t); - uint16_t *plaintext = snewn(p, uint16_t); - uint16_t *ciphertext = snewn(p, uint16_t); - - strbuf *buffer = strbuf_new(); - strbuf_append(buffer, 16384); - BinarySource src[1]; - - for (size_t i = 0; i < looplimit(32); i++) { - while (true) { - random_advance_counter(); - struct random_state st = random_get_state(); - - NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w); - - if (keypair) { - memcpy(pubkey_orig, ntru_pubkey(keypair), - p*sizeof(*pubkey_orig)); - ntru_keypair_free(keypair); - - random_set_state(st); - - log_start(); - NTRUKeyPair *keypair = ntru_keygen_attempt(p, q, w); - memcpy(pubkey_check, ntru_pubkey(keypair), - p*sizeof(*pubkey_check)); - - ntru_gen_short(plaintext, p, w); - ntru_encrypt(ciphertext, plaintext, pubkey, p, w); - ntru_decrypt(plaintext, ciphertext, keypair); - - strbuf_clear(buffer); - ntru_encode_pubkey(ntru_pubkey(keypair), p, q, - BinarySink_UPCAST(buffer)); - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(buffer)); - ntru_decode_pubkey(pubkey, p, q, src); - - strbuf_clear(buffer); - ntru_encode_ciphertext(ciphertext, p, q, - BinarySink_UPCAST(buffer)); - BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(buffer)); - ntru_decode_ciphertext(ciphertext, keypair, src); - - strbuf_clear(buffer); - ntru_encode_plaintext(plaintext, p, BinarySink_UPCAST(buffer)); - log_end(); - - ntru_keypair_free(keypair); - - break; - } - - assert(!memcmp(pubkey_orig, pubkey_check, - p*sizeof(*pubkey_check))); - } - } - - sfree(pubkey_orig); - sfree(pubkey_check); - sfree(pubkey); - sfree(plaintext); - sfree(ciphertext); - strbuf_free(buffer); -} - -static const struct test tests[] = { -#define STRUCT_TEST(X) { #X, test_##X }, -TESTLIST(STRUCT_TEST) -#undef STRUCT_TEST -}; - -void dputs(const char *buf) -{ - fputs(buf, stderr); -} - -int main(int argc, char **argv) -{ - bool doing_opts = true; - const char *pname = argv[0]; - uint8_t tests_to_run[lenof(tests)]; - bool keep_outfiles = false; - bool test_names_given = false; - - memset(tests_to_run, 1, sizeof(tests_to_run)); - random_hash = ssh_hash_new(&ssh_sha256); - - while (--argc > 0) { - char *p = *++argv; - - if (p[0] == '-' && doing_opts) { - if (!strcmp(p, "-O")) { - if (--argc <= 0) { - fprintf(stderr, "'-O' expects a directory name\n"); - return 1; - } - outdir = *++argv; - } else if (!strcmp(p, "-k") || !strcmp(p, "--keep")) { - keep_outfiles = true; - } else if (!strcmp(p, "--")) { - doing_opts = false; - } else if (!strcmp(p, "--help")) { - printf(" usage: drrun -c test/sclog/libsclog.so -- " - "%s -O \n", pname); - printf("options: -O " - "put log files in the specified directory\n"); - printf(" -k, --keep " - "do not delete log files for tests that passed\n"); - printf(" also: --help " - "display this text\n"); - return 0; - } else { - fprintf(stderr, "unknown command line option '%s'\n", p); - return 1; - } - } else { - if (!test_names_given) { - test_names_given = true; - memset(tests_to_run, 0, sizeof(tests_to_run)); - } - bool found_one = false; - for (size_t i = 0; i < lenof(tests); i++) { - if (wc_match(p, tests[i].testname)) { - tests_to_run[i] = 1; - found_one = true; - } - } - if (!found_one) { - fprintf(stderr, "no test name matched '%s'\n", p); - return 1; - } - } - } - - bool is_dry_run = dry_run(); - - if (is_dry_run) { - printf("Dry run (DynamoRIO instrumentation not detected)\n"); - } else { - /* Print the address of main() in this run. The idea is that - * if this image is compiled to be position-independent, then - * PC values in the logs won't match the ones you get if you - * disassemble the binary, so it'll be harder to match up the - * log messages to the code. But if you know the address of a - * fixed (and not inlined) function in both worlds, you can - * find out the offset between them. */ - printf("Live run, main = %p\n", (void *)main); - - if (!outdir) { - fprintf(stderr, "expected -O option\n"); - return 1; - } - printf("Will write log files to %s\n", outdir); - } - - size_t nrun = 0, npass = 0; - - for (size_t i = 0; i < lenof(tests); i++) { - bool keep_these_outfiles = true; - - if (!tests_to_run[i]) - continue; - const struct test *test = &tests[i]; - printf("Running test %s ... ", test->testname); - fflush(stdout); - - test_skipped = false; - random_seed(test->testname); - test_basename = test->testname; - test_index = 0; - - test->testfn(); - - if (test_skipped) { - /* Used for e.g. tests of hardware-accelerated crypto when - * the hardware acceleration isn't available */ - printf("skipped\n"); - continue; - } - - nrun++; - - if (is_dry_run) { - printf("dry run done\n"); - continue; /* test files won't exist anyway */ - } - - if (test_index < 2) { - printf("FAIL: test did not generate multiple output files\n"); - goto test_done; - } - - char *firstfile = log_filename(test_basename, 0); - FILE *firstfp = fopen(firstfile, "rb"); - if (!firstfp) { - printf("ERR: %s: open: %s\n", firstfile, strerror(errno)); - goto test_done; - } - for (size_t i = 1; i < test_index; i++) { - char *nextfile = log_filename(test_basename, i); - FILE *nextfp = fopen(nextfile, "rb"); - if (!nextfp) { - printf("ERR: %s: open: %s\n", nextfile, strerror(errno)); - goto test_done; - } - - rewind(firstfp); - char buf1[4096], bufn[4096]; - bool compare_ok = false; - while (true) { - size_t r1 = fread(buf1, 1, sizeof(buf1), firstfp); - size_t rn = fread(bufn, 1, sizeof(bufn), nextfp); - if (r1 != rn) { - printf("FAIL: %s %s: different lengths\n", - firstfile, nextfile); - break; - } - if (r1 == 0) { - if (feof(firstfp) && feof(nextfp)) { - compare_ok = true; - } else { - printf("FAIL: %s %s: error at end of file\n", - firstfile, nextfile); - } - break; - } - if (memcmp(buf1, bufn, r1) != 0) { - printf("FAIL: %s %s: different content\n", - firstfile, nextfile); - break; - } - } - fclose(nextfp); - sfree(nextfile); - if (!compare_ok) { - goto test_done; - } - } - fclose(firstfp); - sfree(firstfile); - - printf("pass\n"); - npass++; - keep_these_outfiles = keep_outfiles; - - test_done: - if (!keep_these_outfiles) { - for (size_t i = 0; i < test_index; i++) { - char *file = log_filename(test_basename, i); - remove(file); - sfree(file); - } - } - } - - ssh_hash_free(random_hash); - - if (npass == nrun) { - printf("All tests passed\n"); - return 0; - } else { - printf("%"SIZEu" tests failed\n", nrun - npass); - return 1; - } -} diff --git a/test/testzlib.c b/test/testzlib.c deleted file mode 100644 index 0ef4ef198..000000000 --- a/test/testzlib.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Main program to compile ssh/zlib.c into a zlib decoding tool. - * - * This is potentially a handy tool in its own right for picking apart - * Zip files or PDFs or PNGs, because it accepts the bare Deflate - * format and the zlib wrapper format, unlike 'zcat' which accepts - * only the gzip wrapper format. - * - * It's also useful as a means for a fuzzer to get reasonably direct - * access to PuTTY's zlib decompressor. - */ - -#include -#include -#include -#include - -#include "defs.h" -#include "ssh.h" - -void out_of_memory(void) -{ - fprintf(stderr, "Out of memory!\n"); - exit(1); -} - -void dputs(const char *buf) -{ - fputs(buf, stderr); -} - -int main(int argc, char **argv) -{ - unsigned char buf[16], *outbuf; - int ret, outlen; - ssh_decompressor *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 if (!strcmp(p, "--help")) { - printf("usage: testzlib decode zlib (RFC1950) data" - " from standard input\n"); - printf(" testzlib -d decode Deflate (RFC1951) data" - " from standard input\n"); - printf(" testzlib --help display this text\n"); - return 0; - } 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 = ssh_decompressor_new(&ssh_zlib); - - if (noheader) { - /* - * Provide missing zlib header if -d was specified. - */ - static const unsigned char ersatz_zlib_header[] = { 0x78, 0x9C }; - ssh_decompressor_decompress( - handle, ersatz_zlib_header, sizeof(ersatz_zlib_header), - &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; - ssh_decompressor_decompress(handle, buf, ret, &outbuf, &outlen); - if (outbuf) { - if (outlen) - fwrite(outbuf, 1, outlen, stdout); - sfree(outbuf); - } else { - fprintf(stderr, "decoding error\n"); - fclose(fp); - return 1; - } - } - - ssh_decompressor_free(handle); - - if (filename) - fclose(fp); - - return 0; -} diff --git a/test/utf8.txt b/test/utf8.txt deleted file mode 100644 index d7e1d7737..000000000 --- a/test/utf8.txt +++ /dev/null @@ -1,23 +0,0 @@ -Test of UTF-8 output in a terminal emulator -‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ - -Some basic Unicode: - ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β), - ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ, ⊥ < a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (A ⇔ B), - -Combining characters: - STARGΛ̊TE SG-1, a = v̇ = r̈, a⃑ ⊥ b⃑ - [----------------------------|------------------------] - ๏ แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช พระปกเกศกองบู๊กู้ขึ้นใหม่ - สิบสองกษัตริย์ก่อนหน้าแลถัดไป สององค์ไซร้โง่เขลาเบาปัญญา - -Wide characters with difficult wrapping: - Here we go then: コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ コンニチハ - -Arabic and bidirectional text: - (من مجمع الزوائد ومنبع الفوائد للهيثمي ، ج 1 ، ص 74-84) - عن جرير رضي الله عنه قال قال رسول الله صلى الله عليه - وسلم: بني الاسلام على خمس شهادة ان لا اله الا الله واقام -Mixed LTR and RTL text: جرير رضي back to LTR. - -East Asian Ambiguous characters: ¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾¼½¾ diff --git a/test/vt100.txt b/test/vt100.txt deleted file mode 100644 index ac2ce841f..000000000 --- a/test/vt100.txt +++ /dev/null @@ -1,16 +0,0 @@ -VT100 line drawing characters, actually using the VT100 escapes -(B)0ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo - -lqqqqqqqqqqpoopqrssrqqqqqqqqqqwqqqqqqqqqqpoopqrssrqqqqqqqqqqk -x x x -x ooh, swirly! x top right corner x -x x x -tqqqqqqqqqqpoopqrssrqqqqqqqqqqnqqqqqqqqqqpoopqrssrqqqqqqqqqqu -x x x -x stuff down here x is quite inane x -x x x -mqqqqqqqqqqpoopqrssrqqqqqqqqqqvqqqqqqqqqqpoopqrssrqqqqqqqqqqj - -(This won't do the right thing in PuTTY's default UTF-8 translation; -you'll just see lqqqk letters. It should work with a character set -like ISO8859-1.) diff --git a/test/windowchange.py b/test/windowchange.py deleted file mode 100644 index 6e5ffb766..000000000 --- a/test/windowchange.py +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/env python3 - -# Interactive test program for assorted ancillary changes to the -# terminal window - title, position, size, z-order, colour palette -# etc. - -import argparse -import sys -import termios -import os -from time import sleep - -def send(s): - sys.stdout.write(s) - sys.stdout.flush() - -def query(s, lastchar=None): - send(s) - - old_attrs = termios.tcgetattr(0) - new_attrs = old_attrs.copy() - new_attrs[3] &= ~(termios.ECHO | termios.ICANON) - new_attrs[6][termios.VMIN] = 0 - new_attrs[6][termios.VTIME] = 1 - try: - termios.tcsetattr(0, termios.TCSANOW, new_attrs) - - s = b"" - while True: - c = os.read(0, 1) - if len(c) == 0: - break - s += c - if lastchar is not None and c[0] == lastchar: - break - return s - - finally: - termios.tcsetattr(0, termios.TCSANOW, old_attrs) - -def pause(prompt): - input(prompt + (" " if len(prompt) > 0 else "") + "--More--") - -def main(): - testnames = [ - "title", - "icon", - "minimise", - "maximise", - "minquery", - "setpalette", - "querypalette", - "winpos", - "setwinsize", - "querywinsize", - "zorder", - "mousereport", - ] - - parser = argparse.ArgumentParser( - description='Test PuTTY\'s ancillary terminal updates') - parser.add_argument("test", choices=testnames, nargs="*", - help='Sub-test to perform (default: all of them)') - args = parser.parse_args() - - if len(args.test) == 0: - dotest = lambda s: True - else: - testset = set(args.test) - dotest = lambda s: s in testset - - if dotest("title"): - send("\033]2;Title test 1\a") - pause("Title test 1.") - send("\033]2;Title test 2\a") - pause("Title test 2.") - - if dotest("icon"): - send("\033]1;Icon-title test 1\a") - pause("Icon-title test 1.") - send("\033]1;Icon-title test 2\a") - pause("Icon-title test 2.") - - if dotest("minimise"): - pause("About to minimise and restore.") - send("\033[2t") - sleep(2) - send("\033[1t") - - if dotest("maximise"): - pause("About to maximise.") - send("\033[9;1t") - pause("About to unmaximise.") - send("\033[9;0t") - - if dotest("minquery"): - pause("Query minimised status.") - s = query("\033[11t") - if s == b"\033[1t": - print("Reply: not minimised") - elif s == b"\033[2t": - print("Reply: minimised") - else: - print("Reply unknown:", repr(s)) - - if dotest("setpalette"): - print("Palette testing:") - teststr = "" - for i in range(8): - teststr += "\033[0;{:d}m{:X}".format(i+30, i) - for i in range(8): - teststr += "\033[1;{:d}m{:X}".format(i+30, i+8) - teststr += "\033[0;39mG\033[1mH\033[0;7mI\033[1mJ" - teststr += "\033[m" - teststr += " " * 9 + "K" + "\b" * 10 - for c in "0123456789ABCDEFGHIJKL": - pause("Base: " + teststr) - send("\033]P" + c + "ff0000") - pause(c + " red: " + teststr) - send("\033]P" + c + "00ff00") - pause(c + " green: " + teststr) - send("\033]P" + c + "0000ff") - pause(c + " blue: " + teststr) - send("\033]R") - - if dotest("querypalette"): - send("\033]R") - nfail = 262 - for i in range(nfail): - s = query("\033]4;{:d};?\a".format(i), 7).decode("ASCII") - prefix, suffix = "\033]4;{:d};rgb:".format(i), "\a" - if s.startswith(prefix) and s.endswith(suffix): - rgb = [int(word, 16) for word in - s[len(prefix):-len(suffix)].split("/")] - if 0 <= i < 8: - j = i - expected_rgb = [0xbbbb * ((j>>n) & 1) for n in range(3)] - elif 0 <= i-8 < 8: - j = i-8 - expected_rgb = [0x5555 + 0xaaaa * ((j>>n) & 1) - for n in range(3)] - elif 0 <= i-16 < 216: - j = i-16 - expected_rgb = [(0 if v==0 else 0x3737+0x2828*v) for v in - (j // 36, j // 6 % 6, j % 6)] - elif 0 <= i-232 < 24: - j = i-232 - expected_rgb = [j * 0x0a0a + 0x0808] * 3 - else: - expected_rgb = [ - [0xbbbb, 0xbbbb, 0xbbbb], [0xffff, 0xffff, 0xffff], - [0x0000, 0x0000, 0x0000], [0x5555, 0x5555, 0x5555], - [0x0000, 0x0000, 0x0000], [0x0000, 0xffff, 0x0000], - ][i-256] - if expected_rgb == rgb: - nfail -= 1 - else: - print(i, "unexpected: {:04x} {:04x} {:04x}".format(*rgb)) - else: - print(i, "bad format:", repr(s)) - print("Query palette: {:d} colours wrong".format(nfail)) - - if dotest("winpos"): - print("Query window position: ", end="") - s = query("\033[13t").decode("ASCII") - prefix, suffix = "\033[3;", "t" - if s.startswith(prefix) and s.endswith(suffix): - x, y = [int(word) for word in - s[len(prefix):-len(suffix)].split(";")] - print("x={:d} y={:d}".format(x, y)) - else: - print("bad format:", repr(s)) - x, y = 50, 50 - - pause("About to move window.") - send("\033[3;{:d};{:d}t".format(x+50, y+50)) - pause("About to move it back again.") - send("\033[3;{:d};{:d}t".format(x, y)) - - if dotest("setwinsize"): - pause("About to DECCOLM to 132 cols.") - send("\033[?3h") - pause("About to DECCOLM to 80 cols.") - send("\033[?3l") - pause("About to DECCOLM to 132 cols again.") - send("\033[?3h") - pause("About to reset the terminal.") - send("\033c") - pause("About to DECSLPP to 30 rows.") - send("\033[30t") - pause("About to DECSLPP to 24 rows.") - send("\033[24t") - pause("About to DECSNLS to 30 rows.") - send("\033[*30|") - pause("About to DECSNLS to 24 rows.") - send("\033[*24|") - pause("About to DECSCPP to 90 cols.") - send("\033[$90|") - pause("About to DECSCPP to 80 cols.") - send("\033[$80|") - pause("About to xterm to 90x30.") - send("\033[8;30;90t") - pause("About to xterm back to 80x24.") - send("\033[8;24;80t") - - if dotest("querywinsize"): - print("Query window size: ", end="") - s = query("\033[14t").decode("ASCII") - prefix, suffix = "\033[4;", "t" - if s.startswith(prefix) and s.endswith(suffix): - h, w = [int(word) for word in - s[len(prefix):-len(suffix)].split(";")] - print("w={:d} h={:d}".format(w, h)) - else: - print("bad format:", repr(s)) - - if dotest("zorder"): - pause("About to lower to bottom and then raise.") - send("\033[6t") - sleep(2) - send("\033[5t") - - if dotest("mousereport"): - send("\033[?1000h") - pause("Mouse reporting on: expect clicks to generate input") - send("\033[?1000l") - pause("Mouse reporting off: expect clicks to select") - -if __name__ == '__main__': - main() diff --git a/timing.c b/timing.c deleted file mode 100644 index 7281634a2..000000000 --- a/timing.c +++ /dev/null @@ -1,222 +0,0 @@ -/* - * 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; -} - -unsigned long timing_last_clock(void) -{ - /* - * Return the last value we stored in 'now'. In particular, - * calling this just after schedule_timer returns the value of - * 'now' that was used to decide when the timer you just set would - * go off. - */ - return now; -} - -/* - * Call to run any timers whose time has reached the present. - * Returns the time (in ticks) expected until the next timer after - * that triggers. - */ -bool 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/tree234.h b/tree234.h deleted file mode 100644 index c7e3f641a..000000000 --- a/tree234.h +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 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); - -/* - * A more general search type still. Use search234_start() to - * initialise one of these state structures; it will fill in - * state->element with an element of the tree, and state->index with - * the index of that element. If you don't like that element, call - * search234_step, with direction == -1 if you want an element earlier - * in the tree, or +1 if you want a later one. - * - * If either function returns state->element == NULL, then you've - * narrowed the search to a point between two adjacent elements, so - * there are no further elements left to return consistent with the - * constraints you've imposed. In this case, state->index tells you - * how many elements come before the point you narrowed down to. After - * this, you mustn't call search234_step again (unless the state - * structure is first reinitialised). - * - * The use of this search system is that you get both the candidate - * element _and_ its index at every stage, so you can use both of them - * to make your decision. Also, you can remember element pointers from - * earlier in the search. - * - * The fields beginning with underscores are private to the - * implementation, and only exposed so that clients can know how much - * space to allocate for the structure as a whole. Don't modify them. - * (Except that it's safe to copy the whole structure.) - */ -typedef struct search234_state { - void *element; - int index; - int _lo, _hi, _last, _base; - void *_node; -} search234_state; -void search234_start(search234_state *state, tree234 *t); -void search234_step(search234_state *state, int direction); - -/* - * 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/unicode/ambiguous_wide_chars.h b/unicode/ambiguous_wide_chars.h deleted file mode 100644 index 63ca9474d..000000000 --- a/unicode/ambiguous_wide_chars.h +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Autogenerated by read_ucd.py from The Unicode Standard 15.0.0 - * - * Identify Unicode characters that are width-ambiguous: some regimes - * regard them as occupying two adjacent character cells in a terminal, - * and others do not. - * - * Used by utils/wcwidth.c. - */ - -{0x00a1, 0x00a1}, -{0x00a4, 0x00a4}, -{0x00a7, 0x00a8}, -{0x00aa, 0x00aa}, -{0x00ad, 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}, -{0x0300, 0x036f}, -{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}, -{0x2189, 0x2189}, -{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}, -{0x261c, 0x261c}, -{0x261e, 0x261e}, -{0x2640, 0x2640}, -{0x2642, 0x2642}, -{0x2660, 0x2661}, -{0x2663, 0x2665}, -{0x2667, 0x266a}, -{0x266c, 0x266d}, -{0x266f, 0x266f}, -{0x269e, 0x269f}, -{0x26bf, 0x26bf}, -{0x26c6, 0x26cd}, -{0x26cf, 0x26d3}, -{0x26d5, 0x26e1}, -{0x26e3, 0x26e3}, -{0x26e8, 0x26e9}, -{0x26eb, 0x26f1}, -{0x26f4, 0x26f4}, -{0x26f6, 0x26f9}, -{0x26fb, 0x26fc}, -{0x26fe, 0x26ff}, -{0x273d, 0x273d}, -{0x2776, 0x277f}, -{0x2b56, 0x2b59}, -{0x3248, 0x324f}, -{0xe000, 0xf8ff}, -{0xfe00, 0xfe0f}, -{0xfffd, 0xfffd}, -{0x1f100, 0x1f10a}, -{0x1f110, 0x1f12d}, -{0x1f130, 0x1f169}, -{0x1f170, 0x1f18d}, -{0x1f18f, 0x1f190}, -{0x1f19b, 0x1f1ac}, -{0xe0100, 0xe01ef}, -{0xf0000, 0xffffd}, -{0x100000, 0x10fffd}, diff --git a/unicode/bidi_brackets.h b/unicode/bidi_brackets.h deleted file mode 100644 index 1a846d496..000000000 --- a/unicode/bidi_brackets.h +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Autogenerated by read_ucd.py from The Unicode Standard 15.0.0 - * - * Identify Unicode characters that count as brackets for the purposes of - * bidirectional text layout. For each one, indicate whether it's an open - * or closed bracket, and identify up to two characters that can act as - * its counterpart. - * - * Used by terminal/bidi.c. - */ - -{0x0028, {0x0029, 0x0000, BT_OPEN}}, -{0x0029, {0x0028, 0x0000, BT_CLOSE}}, -{0x005b, {0x005d, 0x0000, BT_OPEN}}, -{0x005d, {0x005b, 0x0000, BT_CLOSE}}, -{0x007b, {0x007d, 0x0000, BT_OPEN}}, -{0x007d, {0x007b, 0x0000, BT_CLOSE}}, -{0x0f3a, {0x0f3b, 0x0000, BT_OPEN}}, -{0x0f3b, {0x0f3a, 0x0000, BT_CLOSE}}, -{0x0f3c, {0x0f3d, 0x0000, BT_OPEN}}, -{0x0f3d, {0x0f3c, 0x0000, BT_CLOSE}}, -{0x169b, {0x169c, 0x0000, BT_OPEN}}, -{0x169c, {0x169b, 0x0000, BT_CLOSE}}, -{0x2045, {0x2046, 0x0000, BT_OPEN}}, -{0x2046, {0x2045, 0x0000, BT_CLOSE}}, -{0x207d, {0x207e, 0x0000, BT_OPEN}}, -{0x207e, {0x207d, 0x0000, BT_CLOSE}}, -{0x208d, {0x208e, 0x0000, BT_OPEN}}, -{0x208e, {0x208d, 0x0000, BT_CLOSE}}, -{0x2308, {0x2309, 0x0000, BT_OPEN}}, -{0x2309, {0x2308, 0x0000, BT_CLOSE}}, -{0x230a, {0x230b, 0x0000, BT_OPEN}}, -{0x230b, {0x230a, 0x0000, BT_CLOSE}}, -{0x2329, {0x232a, 0x3009, BT_OPEN}}, -{0x232a, {0x2329, 0x3008, BT_CLOSE}}, -{0x2768, {0x2769, 0x0000, BT_OPEN}}, -{0x2769, {0x2768, 0x0000, BT_CLOSE}}, -{0x276a, {0x276b, 0x0000, BT_OPEN}}, -{0x276b, {0x276a, 0x0000, BT_CLOSE}}, -{0x276c, {0x276d, 0x0000, BT_OPEN}}, -{0x276d, {0x276c, 0x0000, BT_CLOSE}}, -{0x276e, {0x276f, 0x0000, BT_OPEN}}, -{0x276f, {0x276e, 0x0000, BT_CLOSE}}, -{0x2770, {0x2771, 0x0000, BT_OPEN}}, -{0x2771, {0x2770, 0x0000, BT_CLOSE}}, -{0x2772, {0x2773, 0x0000, BT_OPEN}}, -{0x2773, {0x2772, 0x0000, BT_CLOSE}}, -{0x2774, {0x2775, 0x0000, BT_OPEN}}, -{0x2775, {0x2774, 0x0000, BT_CLOSE}}, -{0x27c5, {0x27c6, 0x0000, BT_OPEN}}, -{0x27c6, {0x27c5, 0x0000, BT_CLOSE}}, -{0x27e6, {0x27e7, 0x0000, BT_OPEN}}, -{0x27e7, {0x27e6, 0x0000, BT_CLOSE}}, -{0x27e8, {0x27e9, 0x0000, BT_OPEN}}, -{0x27e9, {0x27e8, 0x0000, BT_CLOSE}}, -{0x27ea, {0x27eb, 0x0000, BT_OPEN}}, -{0x27eb, {0x27ea, 0x0000, BT_CLOSE}}, -{0x27ec, {0x27ed, 0x0000, BT_OPEN}}, -{0x27ed, {0x27ec, 0x0000, BT_CLOSE}}, -{0x27ee, {0x27ef, 0x0000, BT_OPEN}}, -{0x27ef, {0x27ee, 0x0000, BT_CLOSE}}, -{0x2983, {0x2984, 0x0000, BT_OPEN}}, -{0x2984, {0x2983, 0x0000, BT_CLOSE}}, -{0x2985, {0x2986, 0x0000, BT_OPEN}}, -{0x2986, {0x2985, 0x0000, BT_CLOSE}}, -{0x2987, {0x2988, 0x0000, BT_OPEN}}, -{0x2988, {0x2987, 0x0000, BT_CLOSE}}, -{0x2989, {0x298a, 0x0000, BT_OPEN}}, -{0x298a, {0x2989, 0x0000, BT_CLOSE}}, -{0x298b, {0x298c, 0x0000, BT_OPEN}}, -{0x298c, {0x298b, 0x0000, BT_CLOSE}}, -{0x298d, {0x2990, 0x0000, BT_OPEN}}, -{0x298e, {0x298f, 0x0000, BT_CLOSE}}, -{0x298f, {0x298e, 0x0000, BT_OPEN}}, -{0x2990, {0x298d, 0x0000, BT_CLOSE}}, -{0x2991, {0x2992, 0x0000, BT_OPEN}}, -{0x2992, {0x2991, 0x0000, BT_CLOSE}}, -{0x2993, {0x2994, 0x0000, BT_OPEN}}, -{0x2994, {0x2993, 0x0000, BT_CLOSE}}, -{0x2995, {0x2996, 0x0000, BT_OPEN}}, -{0x2996, {0x2995, 0x0000, BT_CLOSE}}, -{0x2997, {0x2998, 0x0000, BT_OPEN}}, -{0x2998, {0x2997, 0x0000, BT_CLOSE}}, -{0x29d8, {0x29d9, 0x0000, BT_OPEN}}, -{0x29d9, {0x29d8, 0x0000, BT_CLOSE}}, -{0x29da, {0x29db, 0x0000, BT_OPEN}}, -{0x29db, {0x29da, 0x0000, BT_CLOSE}}, -{0x29fc, {0x29fd, 0x0000, BT_OPEN}}, -{0x29fd, {0x29fc, 0x0000, BT_CLOSE}}, -{0x2e22, {0x2e23, 0x0000, BT_OPEN}}, -{0x2e23, {0x2e22, 0x0000, BT_CLOSE}}, -{0x2e24, {0x2e25, 0x0000, BT_OPEN}}, -{0x2e25, {0x2e24, 0x0000, BT_CLOSE}}, -{0x2e26, {0x2e27, 0x0000, BT_OPEN}}, -{0x2e27, {0x2e26, 0x0000, BT_CLOSE}}, -{0x2e28, {0x2e29, 0x0000, BT_OPEN}}, -{0x2e29, {0x2e28, 0x0000, BT_CLOSE}}, -{0x2e55, {0x2e56, 0x0000, BT_OPEN}}, -{0x2e56, {0x2e55, 0x0000, BT_CLOSE}}, -{0x2e57, {0x2e58, 0x0000, BT_OPEN}}, -{0x2e58, {0x2e57, 0x0000, BT_CLOSE}}, -{0x2e59, {0x2e5a, 0x0000, BT_OPEN}}, -{0x2e5a, {0x2e59, 0x0000, BT_CLOSE}}, -{0x2e5b, {0x2e5c, 0x0000, BT_OPEN}}, -{0x2e5c, {0x2e5b, 0x0000, BT_CLOSE}}, -{0x3008, {0x3009, 0x232a, BT_OPEN}}, -{0x3009, {0x3008, 0x2329, BT_CLOSE}}, -{0x300a, {0x300b, 0x0000, BT_OPEN}}, -{0x300b, {0x300a, 0x0000, BT_CLOSE}}, -{0x300c, {0x300d, 0x0000, BT_OPEN}}, -{0x300d, {0x300c, 0x0000, BT_CLOSE}}, -{0x300e, {0x300f, 0x0000, BT_OPEN}}, -{0x300f, {0x300e, 0x0000, BT_CLOSE}}, -{0x3010, {0x3011, 0x0000, BT_OPEN}}, -{0x3011, {0x3010, 0x0000, BT_CLOSE}}, -{0x3014, {0x3015, 0x0000, BT_OPEN}}, -{0x3015, {0x3014, 0x0000, BT_CLOSE}}, -{0x3016, {0x3017, 0x0000, BT_OPEN}}, -{0x3017, {0x3016, 0x0000, BT_CLOSE}}, -{0x3018, {0x3019, 0x0000, BT_OPEN}}, -{0x3019, {0x3018, 0x0000, BT_CLOSE}}, -{0x301a, {0x301b, 0x0000, BT_OPEN}}, -{0x301b, {0x301a, 0x0000, BT_CLOSE}}, -{0xfe59, {0xfe5a, 0x0000, BT_OPEN}}, -{0xfe5a, {0xfe59, 0x0000, BT_CLOSE}}, -{0xfe5b, {0xfe5c, 0x0000, BT_OPEN}}, -{0xfe5c, {0xfe5b, 0x0000, BT_CLOSE}}, -{0xfe5d, {0xfe5e, 0x0000, BT_OPEN}}, -{0xfe5e, {0xfe5d, 0x0000, BT_CLOSE}}, -{0xff08, {0xff09, 0x0000, BT_OPEN}}, -{0xff09, {0xff08, 0x0000, BT_CLOSE}}, -{0xff3b, {0xff3d, 0x0000, BT_OPEN}}, -{0xff3d, {0xff3b, 0x0000, BT_CLOSE}}, -{0xff5b, {0xff5d, 0x0000, BT_OPEN}}, -{0xff5d, {0xff5b, 0x0000, BT_CLOSE}}, -{0xff5f, {0xff60, 0x0000, BT_OPEN}}, -{0xff60, {0xff5f, 0x0000, BT_CLOSE}}, -{0xff62, {0xff63, 0x0000, BT_OPEN}}, -{0xff63, {0xff62, 0x0000, BT_CLOSE}}, diff --git a/unicode/bidi_mirror.h b/unicode/bidi_mirror.h deleted file mode 100644 index b7c7f6293..000000000 --- a/unicode/bidi_mirror.h +++ /dev/null @@ -1,437 +0,0 @@ -/* - * Autogenerated by read_ucd.py from The Unicode Standard 15.0.0 - * - * Map each Unicode character to its mirrored form when printing right to - * left. - * - * Used by terminal/bidi.c. - */ - -{0x0028, 0x0029}, -{0x0029, 0x0028}, -{0x003c, 0x003e}, -{0x003e, 0x003c}, -{0x005b, 0x005d}, -{0x005d, 0x005b}, -{0x007b, 0x007d}, -{0x007d, 0x007b}, -{0x00ab, 0x00bb}, -{0x00bb, 0x00ab}, -{0x0f3a, 0x0f3b}, -{0x0f3b, 0x0f3a}, -{0x0f3c, 0x0f3d}, -{0x0f3d, 0x0f3c}, -{0x169b, 0x169c}, -{0x169c, 0x169b}, -{0x2039, 0x203a}, -{0x203a, 0x2039}, -{0x2045, 0x2046}, -{0x2046, 0x2045}, -{0x207d, 0x207e}, -{0x207e, 0x207d}, -{0x208d, 0x208e}, -{0x208e, 0x208d}, -{0x2208, 0x220b}, -{0x2209, 0x220c}, -{0x220a, 0x220d}, -{0x220b, 0x2208}, -{0x220c, 0x2209}, -{0x220d, 0x220a}, -{0x2215, 0x29f5}, -{0x221f, 0x2bfe}, -{0x2220, 0x29a3}, -{0x2221, 0x299b}, -{0x2222, 0x29a0}, -{0x2224, 0x2aee}, -{0x223c, 0x223d}, -{0x223d, 0x223c}, -{0x2243, 0x22cd}, -{0x2245, 0x224c}, -{0x224c, 0x2245}, -{0x2252, 0x2253}, -{0x2253, 0x2252}, -{0x2254, 0x2255}, -{0x2255, 0x2254}, -{0x2264, 0x2265}, -{0x2265, 0x2264}, -{0x2266, 0x2267}, -{0x2267, 0x2266}, -{0x2268, 0x2269}, -{0x2269, 0x2268}, -{0x226a, 0x226b}, -{0x226b, 0x226a}, -{0x226e, 0x226f}, -{0x226f, 0x226e}, -{0x2270, 0x2271}, -{0x2271, 0x2270}, -{0x2272, 0x2273}, -{0x2273, 0x2272}, -{0x2274, 0x2275}, -{0x2275, 0x2274}, -{0x2276, 0x2277}, -{0x2277, 0x2276}, -{0x2278, 0x2279}, -{0x2279, 0x2278}, -{0x227a, 0x227b}, -{0x227b, 0x227a}, -{0x227c, 0x227d}, -{0x227d, 0x227c}, -{0x227e, 0x227f}, -{0x227f, 0x227e}, -{0x2280, 0x2281}, -{0x2281, 0x2280}, -{0x2282, 0x2283}, -{0x2283, 0x2282}, -{0x2284, 0x2285}, -{0x2285, 0x2284}, -{0x2286, 0x2287}, -{0x2287, 0x2286}, -{0x2288, 0x2289}, -{0x2289, 0x2288}, -{0x228a, 0x228b}, -{0x228b, 0x228a}, -{0x228f, 0x2290}, -{0x2290, 0x228f}, -{0x2291, 0x2292}, -{0x2292, 0x2291}, -{0x2298, 0x29b8}, -{0x22a2, 0x22a3}, -{0x22a3, 0x22a2}, -{0x22a6, 0x2ade}, -{0x22a8, 0x2ae4}, -{0x22a9, 0x2ae3}, -{0x22ab, 0x2ae5}, -{0x22b0, 0x22b1}, -{0x22b1, 0x22b0}, -{0x22b2, 0x22b3}, -{0x22b3, 0x22b2}, -{0x22b4, 0x22b5}, -{0x22b5, 0x22b4}, -{0x22b6, 0x22b7}, -{0x22b7, 0x22b6}, -{0x22b8, 0x27dc}, -{0x22c9, 0x22ca}, -{0x22ca, 0x22c9}, -{0x22cb, 0x22cc}, -{0x22cc, 0x22cb}, -{0x22cd, 0x2243}, -{0x22d0, 0x22d1}, -{0x22d1, 0x22d0}, -{0x22d6, 0x22d7}, -{0x22d7, 0x22d6}, -{0x22d8, 0x22d9}, -{0x22d9, 0x22d8}, -{0x22da, 0x22db}, -{0x22db, 0x22da}, -{0x22dc, 0x22dd}, -{0x22dd, 0x22dc}, -{0x22de, 0x22df}, -{0x22df, 0x22de}, -{0x22e0, 0x22e1}, -{0x22e1, 0x22e0}, -{0x22e2, 0x22e3}, -{0x22e3, 0x22e2}, -{0x22e4, 0x22e5}, -{0x22e5, 0x22e4}, -{0x22e6, 0x22e7}, -{0x22e7, 0x22e6}, -{0x22e8, 0x22e9}, -{0x22e9, 0x22e8}, -{0x22ea, 0x22eb}, -{0x22eb, 0x22ea}, -{0x22ec, 0x22ed}, -{0x22ed, 0x22ec}, -{0x22f0, 0x22f1}, -{0x22f1, 0x22f0}, -{0x22f2, 0x22fa}, -{0x22f3, 0x22fb}, -{0x22f4, 0x22fc}, -{0x22f6, 0x22fd}, -{0x22f7, 0x22fe}, -{0x22fa, 0x22f2}, -{0x22fb, 0x22f3}, -{0x22fc, 0x22f4}, -{0x22fd, 0x22f6}, -{0x22fe, 0x22f7}, -{0x2308, 0x2309}, -{0x2309, 0x2308}, -{0x230a, 0x230b}, -{0x230b, 0x230a}, -{0x2329, 0x232a}, -{0x232a, 0x2329}, -{0x2768, 0x2769}, -{0x2769, 0x2768}, -{0x276a, 0x276b}, -{0x276b, 0x276a}, -{0x276c, 0x276d}, -{0x276d, 0x276c}, -{0x276e, 0x276f}, -{0x276f, 0x276e}, -{0x2770, 0x2771}, -{0x2771, 0x2770}, -{0x2772, 0x2773}, -{0x2773, 0x2772}, -{0x2774, 0x2775}, -{0x2775, 0x2774}, -{0x27c3, 0x27c4}, -{0x27c4, 0x27c3}, -{0x27c5, 0x27c6}, -{0x27c6, 0x27c5}, -{0x27c8, 0x27c9}, -{0x27c9, 0x27c8}, -{0x27cb, 0x27cd}, -{0x27cd, 0x27cb}, -{0x27d5, 0x27d6}, -{0x27d6, 0x27d5}, -{0x27dc, 0x22b8}, -{0x27dd, 0x27de}, -{0x27de, 0x27dd}, -{0x27e2, 0x27e3}, -{0x27e3, 0x27e2}, -{0x27e4, 0x27e5}, -{0x27e5, 0x27e4}, -{0x27e6, 0x27e7}, -{0x27e7, 0x27e6}, -{0x27e8, 0x27e9}, -{0x27e9, 0x27e8}, -{0x27ea, 0x27eb}, -{0x27eb, 0x27ea}, -{0x27ec, 0x27ed}, -{0x27ed, 0x27ec}, -{0x27ee, 0x27ef}, -{0x27ef, 0x27ee}, -{0x2983, 0x2984}, -{0x2984, 0x2983}, -{0x2985, 0x2986}, -{0x2986, 0x2985}, -{0x2987, 0x2988}, -{0x2988, 0x2987}, -{0x2989, 0x298a}, -{0x298a, 0x2989}, -{0x298b, 0x298c}, -{0x298c, 0x298b}, -{0x298d, 0x2990}, -{0x298e, 0x298f}, -{0x298f, 0x298e}, -{0x2990, 0x298d}, -{0x2991, 0x2992}, -{0x2992, 0x2991}, -{0x2993, 0x2994}, -{0x2994, 0x2993}, -{0x2995, 0x2996}, -{0x2996, 0x2995}, -{0x2997, 0x2998}, -{0x2998, 0x2997}, -{0x299b, 0x2221}, -{0x29a0, 0x2222}, -{0x29a3, 0x2220}, -{0x29a4, 0x29a5}, -{0x29a5, 0x29a4}, -{0x29a8, 0x29a9}, -{0x29a9, 0x29a8}, -{0x29aa, 0x29ab}, -{0x29ab, 0x29aa}, -{0x29ac, 0x29ad}, -{0x29ad, 0x29ac}, -{0x29ae, 0x29af}, -{0x29af, 0x29ae}, -{0x29b8, 0x2298}, -{0x29c0, 0x29c1}, -{0x29c1, 0x29c0}, -{0x29c4, 0x29c5}, -{0x29c5, 0x29c4}, -{0x29cf, 0x29d0}, -{0x29d0, 0x29cf}, -{0x29d1, 0x29d2}, -{0x29d2, 0x29d1}, -{0x29d4, 0x29d5}, -{0x29d5, 0x29d4}, -{0x29d8, 0x29d9}, -{0x29d9, 0x29d8}, -{0x29da, 0x29db}, -{0x29db, 0x29da}, -{0x29e8, 0x29e9}, -{0x29e9, 0x29e8}, -{0x29f5, 0x2215}, -{0x29f8, 0x29f9}, -{0x29f9, 0x29f8}, -{0x29fc, 0x29fd}, -{0x29fd, 0x29fc}, -{0x2a2b, 0x2a2c}, -{0x2a2c, 0x2a2b}, -{0x2a2d, 0x2a2e}, -{0x2a2e, 0x2a2d}, -{0x2a34, 0x2a35}, -{0x2a35, 0x2a34}, -{0x2a3c, 0x2a3d}, -{0x2a3d, 0x2a3c}, -{0x2a64, 0x2a65}, -{0x2a65, 0x2a64}, -{0x2a79, 0x2a7a}, -{0x2a7a, 0x2a79}, -{0x2a7b, 0x2a7c}, -{0x2a7c, 0x2a7b}, -{0x2a7d, 0x2a7e}, -{0x2a7e, 0x2a7d}, -{0x2a7f, 0x2a80}, -{0x2a80, 0x2a7f}, -{0x2a81, 0x2a82}, -{0x2a82, 0x2a81}, -{0x2a83, 0x2a84}, -{0x2a84, 0x2a83}, -{0x2a85, 0x2a86}, -{0x2a86, 0x2a85}, -{0x2a87, 0x2a88}, -{0x2a88, 0x2a87}, -{0x2a89, 0x2a8a}, -{0x2a8a, 0x2a89}, -{0x2a8b, 0x2a8c}, -{0x2a8c, 0x2a8b}, -{0x2a8d, 0x2a8e}, -{0x2a8e, 0x2a8d}, -{0x2a8f, 0x2a90}, -{0x2a90, 0x2a8f}, -{0x2a91, 0x2a92}, -{0x2a92, 0x2a91}, -{0x2a93, 0x2a94}, -{0x2a94, 0x2a93}, -{0x2a95, 0x2a96}, -{0x2a96, 0x2a95}, -{0x2a97, 0x2a98}, -{0x2a98, 0x2a97}, -{0x2a99, 0x2a9a}, -{0x2a9a, 0x2a99}, -{0x2a9b, 0x2a9c}, -{0x2a9c, 0x2a9b}, -{0x2a9d, 0x2a9e}, -{0x2a9e, 0x2a9d}, -{0x2a9f, 0x2aa0}, -{0x2aa0, 0x2a9f}, -{0x2aa1, 0x2aa2}, -{0x2aa2, 0x2aa1}, -{0x2aa6, 0x2aa7}, -{0x2aa7, 0x2aa6}, -{0x2aa8, 0x2aa9}, -{0x2aa9, 0x2aa8}, -{0x2aaa, 0x2aab}, -{0x2aab, 0x2aaa}, -{0x2aac, 0x2aad}, -{0x2aad, 0x2aac}, -{0x2aaf, 0x2ab0}, -{0x2ab0, 0x2aaf}, -{0x2ab1, 0x2ab2}, -{0x2ab2, 0x2ab1}, -{0x2ab3, 0x2ab4}, -{0x2ab4, 0x2ab3}, -{0x2ab5, 0x2ab6}, -{0x2ab6, 0x2ab5}, -{0x2ab7, 0x2ab8}, -{0x2ab8, 0x2ab7}, -{0x2ab9, 0x2aba}, -{0x2aba, 0x2ab9}, -{0x2abb, 0x2abc}, -{0x2abc, 0x2abb}, -{0x2abd, 0x2abe}, -{0x2abe, 0x2abd}, -{0x2abf, 0x2ac0}, -{0x2ac0, 0x2abf}, -{0x2ac1, 0x2ac2}, -{0x2ac2, 0x2ac1}, -{0x2ac3, 0x2ac4}, -{0x2ac4, 0x2ac3}, -{0x2ac5, 0x2ac6}, -{0x2ac6, 0x2ac5}, -{0x2ac7, 0x2ac8}, -{0x2ac8, 0x2ac7}, -{0x2ac9, 0x2aca}, -{0x2aca, 0x2ac9}, -{0x2acb, 0x2acc}, -{0x2acc, 0x2acb}, -{0x2acd, 0x2ace}, -{0x2ace, 0x2acd}, -{0x2acf, 0x2ad0}, -{0x2ad0, 0x2acf}, -{0x2ad1, 0x2ad2}, -{0x2ad2, 0x2ad1}, -{0x2ad3, 0x2ad4}, -{0x2ad4, 0x2ad3}, -{0x2ad5, 0x2ad6}, -{0x2ad6, 0x2ad5}, -{0x2ade, 0x22a6}, -{0x2ae3, 0x22a9}, -{0x2ae4, 0x22a8}, -{0x2ae5, 0x22ab}, -{0x2aec, 0x2aed}, -{0x2aed, 0x2aec}, -{0x2aee, 0x2224}, -{0x2af7, 0x2af8}, -{0x2af8, 0x2af7}, -{0x2af9, 0x2afa}, -{0x2afa, 0x2af9}, -{0x2bfe, 0x221f}, -{0x2e02, 0x2e03}, -{0x2e03, 0x2e02}, -{0x2e04, 0x2e05}, -{0x2e05, 0x2e04}, -{0x2e09, 0x2e0a}, -{0x2e0a, 0x2e09}, -{0x2e0c, 0x2e0d}, -{0x2e0d, 0x2e0c}, -{0x2e1c, 0x2e1d}, -{0x2e1d, 0x2e1c}, -{0x2e20, 0x2e21}, -{0x2e21, 0x2e20}, -{0x2e22, 0x2e23}, -{0x2e23, 0x2e22}, -{0x2e24, 0x2e25}, -{0x2e25, 0x2e24}, -{0x2e26, 0x2e27}, -{0x2e27, 0x2e26}, -{0x2e28, 0x2e29}, -{0x2e29, 0x2e28}, -{0x2e55, 0x2e56}, -{0x2e56, 0x2e55}, -{0x2e57, 0x2e58}, -{0x2e58, 0x2e57}, -{0x2e59, 0x2e5a}, -{0x2e5a, 0x2e59}, -{0x2e5b, 0x2e5c}, -{0x2e5c, 0x2e5b}, -{0x3008, 0x3009}, -{0x3009, 0x3008}, -{0x300a, 0x300b}, -{0x300b, 0x300a}, -{0x300c, 0x300d}, -{0x300d, 0x300c}, -{0x300e, 0x300f}, -{0x300f, 0x300e}, -{0x3010, 0x3011}, -{0x3011, 0x3010}, -{0x3014, 0x3015}, -{0x3015, 0x3014}, -{0x3016, 0x3017}, -{0x3017, 0x3016}, -{0x3018, 0x3019}, -{0x3019, 0x3018}, -{0x301a, 0x301b}, -{0x301b, 0x301a}, -{0xfe59, 0xfe5a}, -{0xfe5a, 0xfe59}, -{0xfe5b, 0xfe5c}, -{0xfe5c, 0xfe5b}, -{0xfe5d, 0xfe5e}, -{0xfe5e, 0xfe5d}, -{0xfe64, 0xfe65}, -{0xfe65, 0xfe64}, -{0xff08, 0xff09}, -{0xff09, 0xff08}, -{0xff1c, 0xff1e}, -{0xff1e, 0xff1c}, -{0xff3b, 0xff3d}, -{0xff3d, 0xff3b}, -{0xff5b, 0xff5d}, -{0xff5d, 0xff5b}, -{0xff5f, 0xff60}, -{0xff60, 0xff5f}, -{0xff62, 0xff63}, -{0xff63, 0xff62}, diff --git a/unicode/bidi_type.h b/unicode/bidi_type.h deleted file mode 100644 index 8feaa0051..000000000 --- a/unicode/bidi_type.h +++ /dev/null @@ -1,1331 +0,0 @@ -/* - * Autogenerated by read_ucd.py from The Unicode Standard 15.0.0 - * - * Bidirectional type of every Unicode character, excluding those with - * type ON. - * - * Used by terminal/bidi.c, whose associated lookup function returns ON - * by default for anything not in this list. - */ - -{0x0000, 0x0008, BN}, -{0x0009, 0x0009, S}, -{0x000a, 0x000a, B}, -{0x000b, 0x000b, S}, -{0x000c, 0x000c, WS}, -{0x000d, 0x000d, B}, -{0x000e, 0x001b, BN}, -{0x001c, 0x001e, B}, -{0x001f, 0x001f, S}, -{0x0020, 0x0020, WS}, -{0x0023, 0x0025, ET}, -{0x002b, 0x002b, ES}, -{0x002c, 0x002c, CS}, -{0x002d, 0x002d, ES}, -{0x002e, 0x002f, CS}, -{0x0030, 0x0039, EN}, -{0x003a, 0x003a, CS}, -{0x0041, 0x005a, L}, -{0x0061, 0x007a, L}, -{0x007f, 0x0084, BN}, -{0x0085, 0x0085, B}, -{0x0086, 0x009f, BN}, -{0x00a0, 0x00a0, CS}, -{0x00a2, 0x00a5, ET}, -{0x00aa, 0x00aa, L}, -{0x00ad, 0x00ad, BN}, -{0x00b0, 0x00b1, ET}, -{0x00b2, 0x00b3, EN}, -{0x00b5, 0x00b5, L}, -{0x00b9, 0x00b9, EN}, -{0x00ba, 0x00ba, L}, -{0x00c0, 0x00d6, L}, -{0x00d8, 0x00f6, L}, -{0x00f8, 0x02b8, L}, -{0x02bb, 0x02c1, L}, -{0x02d0, 0x02d1, L}, -{0x02e0, 0x02e4, L}, -{0x02ee, 0x02ee, L}, -{0x0300, 0x036f, NSM}, -{0x0370, 0x0373, L}, -{0x0376, 0x0377, L}, -{0x037a, 0x037d, L}, -{0x037f, 0x037f, L}, -{0x0386, 0x0386, L}, -{0x0388, 0x038a, L}, -{0x038c, 0x038c, L}, -{0x038e, 0x03a1, L}, -{0x03a3, 0x03f5, L}, -{0x03f7, 0x0482, L}, -{0x0483, 0x0489, NSM}, -{0x048a, 0x052f, L}, -{0x0531, 0x0556, L}, -{0x0559, 0x0589, L}, -{0x058f, 0x058f, ET}, -{0x0591, 0x05bd, NSM}, -{0x05be, 0x05be, R}, -{0x05bf, 0x05bf, NSM}, -{0x05c0, 0x05c0, R}, -{0x05c1, 0x05c2, NSM}, -{0x05c3, 0x05c3, R}, -{0x05c4, 0x05c5, NSM}, -{0x05c6, 0x05c6, R}, -{0x05c7, 0x05c7, NSM}, -{0x05d0, 0x05ea, R}, -{0x05ef, 0x05f4, R}, -{0x0600, 0x0605, AN}, -{0x0608, 0x0608, AL}, -{0x0609, 0x060a, ET}, -{0x060b, 0x060b, AL}, -{0x060c, 0x060c, CS}, -{0x060d, 0x060d, AL}, -{0x0610, 0x061a, NSM}, -{0x061b, 0x064a, AL}, -{0x064b, 0x065f, NSM}, -{0x0660, 0x0669, AN}, -{0x066a, 0x066a, ET}, -{0x066b, 0x066c, AN}, -{0x066d, 0x066f, AL}, -{0x0670, 0x0670, NSM}, -{0x0671, 0x06d5, AL}, -{0x06d6, 0x06dc, NSM}, -{0x06dd, 0x06dd, AN}, -{0x06df, 0x06e4, NSM}, -{0x06e5, 0x06e6, AL}, -{0x06e7, 0x06e8, NSM}, -{0x06ea, 0x06ed, NSM}, -{0x06ee, 0x06ef, AL}, -{0x06f0, 0x06f9, EN}, -{0x06fa, 0x070d, AL}, -{0x070f, 0x0710, AL}, -{0x0711, 0x0711, NSM}, -{0x0712, 0x072f, AL}, -{0x0730, 0x074a, NSM}, -{0x074d, 0x07a5, AL}, -{0x07a6, 0x07b0, NSM}, -{0x07b1, 0x07b1, AL}, -{0x07c0, 0x07ea, R}, -{0x07eb, 0x07f3, NSM}, -{0x07f4, 0x07f5, R}, -{0x07fa, 0x07fa, R}, -{0x07fd, 0x07fd, NSM}, -{0x07fe, 0x0815, R}, -{0x0816, 0x0819, NSM}, -{0x081a, 0x081a, R}, -{0x081b, 0x0823, NSM}, -{0x0824, 0x0824, R}, -{0x0825, 0x0827, NSM}, -{0x0828, 0x0828, R}, -{0x0829, 0x082d, NSM}, -{0x0830, 0x083e, R}, -{0x0840, 0x0858, R}, -{0x0859, 0x085b, NSM}, -{0x085e, 0x085e, R}, -{0x0860, 0x086a, AL}, -{0x0870, 0x088e, AL}, -{0x0890, 0x0891, AN}, -{0x0898, 0x089f, NSM}, -{0x08a0, 0x08c9, AL}, -{0x08ca, 0x08e1, NSM}, -{0x08e2, 0x08e2, AN}, -{0x08e3, 0x0902, NSM}, -{0x0903, 0x0939, L}, -{0x093a, 0x093a, NSM}, -{0x093b, 0x093b, L}, -{0x093c, 0x093c, NSM}, -{0x093d, 0x0940, L}, -{0x0941, 0x0948, NSM}, -{0x0949, 0x094c, L}, -{0x094d, 0x094d, NSM}, -{0x094e, 0x0950, L}, -{0x0951, 0x0957, NSM}, -{0x0958, 0x0961, L}, -{0x0962, 0x0963, NSM}, -{0x0964, 0x0980, L}, -{0x0981, 0x0981, NSM}, -{0x0982, 0x0983, L}, -{0x0985, 0x098c, L}, -{0x098f, 0x0990, L}, -{0x0993, 0x09a8, L}, -{0x09aa, 0x09b0, L}, -{0x09b2, 0x09b2, L}, -{0x09b6, 0x09b9, L}, -{0x09bc, 0x09bc, NSM}, -{0x09bd, 0x09c0, L}, -{0x09c1, 0x09c4, NSM}, -{0x09c7, 0x09c8, L}, -{0x09cb, 0x09cc, L}, -{0x09cd, 0x09cd, NSM}, -{0x09ce, 0x09ce, L}, -{0x09d7, 0x09d7, L}, -{0x09dc, 0x09dd, L}, -{0x09df, 0x09e1, L}, -{0x09e2, 0x09e3, NSM}, -{0x09e6, 0x09f1, L}, -{0x09f2, 0x09f3, ET}, -{0x09f4, 0x09fa, L}, -{0x09fb, 0x09fb, ET}, -{0x09fc, 0x09fd, L}, -{0x09fe, 0x09fe, NSM}, -{0x0a01, 0x0a02, NSM}, -{0x0a03, 0x0a03, L}, -{0x0a05, 0x0a0a, L}, -{0x0a0f, 0x0a10, L}, -{0x0a13, 0x0a28, L}, -{0x0a2a, 0x0a30, L}, -{0x0a32, 0x0a33, L}, -{0x0a35, 0x0a36, L}, -{0x0a38, 0x0a39, L}, -{0x0a3c, 0x0a3c, NSM}, -{0x0a3e, 0x0a40, L}, -{0x0a41, 0x0a42, NSM}, -{0x0a47, 0x0a48, NSM}, -{0x0a4b, 0x0a4d, NSM}, -{0x0a51, 0x0a51, NSM}, -{0x0a59, 0x0a5c, L}, -{0x0a5e, 0x0a5e, L}, -{0x0a66, 0x0a6f, L}, -{0x0a70, 0x0a71, NSM}, -{0x0a72, 0x0a74, L}, -{0x0a75, 0x0a75, NSM}, -{0x0a76, 0x0a76, L}, -{0x0a81, 0x0a82, NSM}, -{0x0a83, 0x0a83, L}, -{0x0a85, 0x0a8d, L}, -{0x0a8f, 0x0a91, L}, -{0x0a93, 0x0aa8, L}, -{0x0aaa, 0x0ab0, L}, -{0x0ab2, 0x0ab3, L}, -{0x0ab5, 0x0ab9, L}, -{0x0abc, 0x0abc, NSM}, -{0x0abd, 0x0ac0, L}, -{0x0ac1, 0x0ac5, NSM}, -{0x0ac7, 0x0ac8, NSM}, -{0x0ac9, 0x0ac9, L}, -{0x0acb, 0x0acc, L}, -{0x0acd, 0x0acd, NSM}, -{0x0ad0, 0x0ad0, L}, -{0x0ae0, 0x0ae1, L}, -{0x0ae2, 0x0ae3, NSM}, -{0x0ae6, 0x0af0, L}, -{0x0af1, 0x0af1, ET}, -{0x0af9, 0x0af9, L}, -{0x0afa, 0x0aff, NSM}, -{0x0b01, 0x0b01, NSM}, -{0x0b02, 0x0b03, L}, -{0x0b05, 0x0b0c, L}, -{0x0b0f, 0x0b10, L}, -{0x0b13, 0x0b28, L}, -{0x0b2a, 0x0b30, L}, -{0x0b32, 0x0b33, L}, -{0x0b35, 0x0b39, L}, -{0x0b3c, 0x0b3c, NSM}, -{0x0b3d, 0x0b3e, L}, -{0x0b3f, 0x0b3f, NSM}, -{0x0b40, 0x0b40, L}, -{0x0b41, 0x0b44, NSM}, -{0x0b47, 0x0b48, L}, -{0x0b4b, 0x0b4c, L}, -{0x0b4d, 0x0b4d, NSM}, -{0x0b55, 0x0b56, NSM}, -{0x0b57, 0x0b57, L}, -{0x0b5c, 0x0b5d, L}, -{0x0b5f, 0x0b61, L}, -{0x0b62, 0x0b63, NSM}, -{0x0b66, 0x0b77, L}, -{0x0b82, 0x0b82, NSM}, -{0x0b83, 0x0b83, L}, -{0x0b85, 0x0b8a, L}, -{0x0b8e, 0x0b90, L}, -{0x0b92, 0x0b95, L}, -{0x0b99, 0x0b9a, L}, -{0x0b9c, 0x0b9c, L}, -{0x0b9e, 0x0b9f, L}, -{0x0ba3, 0x0ba4, L}, -{0x0ba8, 0x0baa, L}, -{0x0bae, 0x0bb9, L}, -{0x0bbe, 0x0bbf, L}, -{0x0bc0, 0x0bc0, NSM}, -{0x0bc1, 0x0bc2, L}, -{0x0bc6, 0x0bc8, L}, -{0x0bca, 0x0bcc, L}, -{0x0bcd, 0x0bcd, NSM}, -{0x0bd0, 0x0bd0, L}, -{0x0bd7, 0x0bd7, L}, -{0x0be6, 0x0bf2, L}, -{0x0bf9, 0x0bf9, ET}, -{0x0c00, 0x0c00, NSM}, -{0x0c01, 0x0c03, L}, -{0x0c04, 0x0c04, NSM}, -{0x0c05, 0x0c0c, L}, -{0x0c0e, 0x0c10, L}, -{0x0c12, 0x0c28, L}, -{0x0c2a, 0x0c39, L}, -{0x0c3c, 0x0c3c, NSM}, -{0x0c3d, 0x0c3d, L}, -{0x0c3e, 0x0c40, NSM}, -{0x0c41, 0x0c44, L}, -{0x0c46, 0x0c48, NSM}, -{0x0c4a, 0x0c4d, NSM}, -{0x0c55, 0x0c56, NSM}, -{0x0c58, 0x0c5a, L}, -{0x0c5d, 0x0c5d, L}, -{0x0c60, 0x0c61, L}, -{0x0c62, 0x0c63, NSM}, -{0x0c66, 0x0c6f, L}, -{0x0c77, 0x0c77, L}, -{0x0c7f, 0x0c80, L}, -{0x0c81, 0x0c81, NSM}, -{0x0c82, 0x0c8c, L}, -{0x0c8e, 0x0c90, L}, -{0x0c92, 0x0ca8, L}, -{0x0caa, 0x0cb3, L}, -{0x0cb5, 0x0cb9, L}, -{0x0cbc, 0x0cbc, NSM}, -{0x0cbd, 0x0cc4, L}, -{0x0cc6, 0x0cc8, L}, -{0x0cca, 0x0ccb, L}, -{0x0ccc, 0x0ccd, NSM}, -{0x0cd5, 0x0cd6, L}, -{0x0cdd, 0x0cde, L}, -{0x0ce0, 0x0ce1, L}, -{0x0ce2, 0x0ce3, NSM}, -{0x0ce6, 0x0cef, L}, -{0x0cf1, 0x0cf3, L}, -{0x0d00, 0x0d01, NSM}, -{0x0d02, 0x0d0c, L}, -{0x0d0e, 0x0d10, L}, -{0x0d12, 0x0d3a, L}, -{0x0d3b, 0x0d3c, NSM}, -{0x0d3d, 0x0d40, L}, -{0x0d41, 0x0d44, NSM}, -{0x0d46, 0x0d48, L}, -{0x0d4a, 0x0d4c, L}, -{0x0d4d, 0x0d4d, NSM}, -{0x0d4e, 0x0d4f, L}, -{0x0d54, 0x0d61, L}, -{0x0d62, 0x0d63, NSM}, -{0x0d66, 0x0d7f, L}, -{0x0d81, 0x0d81, NSM}, -{0x0d82, 0x0d83, L}, -{0x0d85, 0x0d96, L}, -{0x0d9a, 0x0db1, L}, -{0x0db3, 0x0dbb, L}, -{0x0dbd, 0x0dbd, L}, -{0x0dc0, 0x0dc6, L}, -{0x0dca, 0x0dca, NSM}, -{0x0dcf, 0x0dd1, L}, -{0x0dd2, 0x0dd4, NSM}, -{0x0dd6, 0x0dd6, NSM}, -{0x0dd8, 0x0ddf, L}, -{0x0de6, 0x0def, L}, -{0x0df2, 0x0df4, L}, -{0x0e01, 0x0e30, L}, -{0x0e31, 0x0e31, NSM}, -{0x0e32, 0x0e33, L}, -{0x0e34, 0x0e3a, NSM}, -{0x0e3f, 0x0e3f, ET}, -{0x0e40, 0x0e46, L}, -{0x0e47, 0x0e4e, NSM}, -{0x0e4f, 0x0e5b, L}, -{0x0e81, 0x0e82, L}, -{0x0e84, 0x0e84, L}, -{0x0e86, 0x0e8a, L}, -{0x0e8c, 0x0ea3, L}, -{0x0ea5, 0x0ea5, L}, -{0x0ea7, 0x0eb0, L}, -{0x0eb1, 0x0eb1, NSM}, -{0x0eb2, 0x0eb3, L}, -{0x0eb4, 0x0ebc, NSM}, -{0x0ebd, 0x0ebd, L}, -{0x0ec0, 0x0ec4, L}, -{0x0ec6, 0x0ec6, L}, -{0x0ec8, 0x0ece, NSM}, -{0x0ed0, 0x0ed9, L}, -{0x0edc, 0x0edf, L}, -{0x0f00, 0x0f17, L}, -{0x0f18, 0x0f19, NSM}, -{0x0f1a, 0x0f34, L}, -{0x0f35, 0x0f35, NSM}, -{0x0f36, 0x0f36, L}, -{0x0f37, 0x0f37, NSM}, -{0x0f38, 0x0f38, L}, -{0x0f39, 0x0f39, NSM}, -{0x0f3e, 0x0f47, L}, -{0x0f49, 0x0f6c, L}, -{0x0f71, 0x0f7e, NSM}, -{0x0f7f, 0x0f7f, L}, -{0x0f80, 0x0f84, NSM}, -{0x0f85, 0x0f85, L}, -{0x0f86, 0x0f87, NSM}, -{0x0f88, 0x0f8c, L}, -{0x0f8d, 0x0f97, NSM}, -{0x0f99, 0x0fbc, NSM}, -{0x0fbe, 0x0fc5, L}, -{0x0fc6, 0x0fc6, NSM}, -{0x0fc7, 0x0fcc, L}, -{0x0fce, 0x0fda, L}, -{0x1000, 0x102c, L}, -{0x102d, 0x1030, NSM}, -{0x1031, 0x1031, L}, -{0x1032, 0x1037, NSM}, -{0x1038, 0x1038, L}, -{0x1039, 0x103a, NSM}, -{0x103b, 0x103c, L}, -{0x103d, 0x103e, NSM}, -{0x103f, 0x1057, L}, -{0x1058, 0x1059, NSM}, -{0x105a, 0x105d, L}, -{0x105e, 0x1060, NSM}, -{0x1061, 0x1070, L}, -{0x1071, 0x1074, NSM}, -{0x1075, 0x1081, L}, -{0x1082, 0x1082, NSM}, -{0x1083, 0x1084, L}, -{0x1085, 0x1086, NSM}, -{0x1087, 0x108c, L}, -{0x108d, 0x108d, NSM}, -{0x108e, 0x109c, L}, -{0x109d, 0x109d, NSM}, -{0x109e, 0x10c5, L}, -{0x10c7, 0x10c7, L}, -{0x10cd, 0x10cd, L}, -{0x10d0, 0x1248, L}, -{0x124a, 0x124d, L}, -{0x1250, 0x1256, L}, -{0x1258, 0x1258, L}, -{0x125a, 0x125d, L}, -{0x1260, 0x1288, L}, -{0x128a, 0x128d, L}, -{0x1290, 0x12b0, L}, -{0x12b2, 0x12b5, L}, -{0x12b8, 0x12be, L}, -{0x12c0, 0x12c0, L}, -{0x12c2, 0x12c5, L}, -{0x12c8, 0x12d6, L}, -{0x12d8, 0x1310, L}, -{0x1312, 0x1315, L}, -{0x1318, 0x135a, L}, -{0x135d, 0x135f, NSM}, -{0x1360, 0x137c, L}, -{0x1380, 0x138f, L}, -{0x13a0, 0x13f5, L}, -{0x13f8, 0x13fd, L}, -{0x1401, 0x167f, L}, -{0x1680, 0x1680, WS}, -{0x1681, 0x169a, L}, -{0x16a0, 0x16f8, L}, -{0x1700, 0x1711, L}, -{0x1712, 0x1714, NSM}, -{0x1715, 0x1715, L}, -{0x171f, 0x1731, L}, -{0x1732, 0x1733, NSM}, -{0x1734, 0x1736, L}, -{0x1740, 0x1751, L}, -{0x1752, 0x1753, NSM}, -{0x1760, 0x176c, L}, -{0x176e, 0x1770, L}, -{0x1772, 0x1773, NSM}, -{0x1780, 0x17b3, L}, -{0x17b4, 0x17b5, NSM}, -{0x17b6, 0x17b6, L}, -{0x17b7, 0x17bd, NSM}, -{0x17be, 0x17c5, L}, -{0x17c6, 0x17c6, NSM}, -{0x17c7, 0x17c8, L}, -{0x17c9, 0x17d3, NSM}, -{0x17d4, 0x17da, L}, -{0x17db, 0x17db, ET}, -{0x17dc, 0x17dc, L}, -{0x17dd, 0x17dd, NSM}, -{0x17e0, 0x17e9, L}, -{0x180b, 0x180d, NSM}, -{0x180e, 0x180e, BN}, -{0x180f, 0x180f, NSM}, -{0x1810, 0x1819, L}, -{0x1820, 0x1878, L}, -{0x1880, 0x1884, L}, -{0x1885, 0x1886, NSM}, -{0x1887, 0x18a8, L}, -{0x18a9, 0x18a9, NSM}, -{0x18aa, 0x18aa, L}, -{0x18b0, 0x18f5, L}, -{0x1900, 0x191e, L}, -{0x1920, 0x1922, NSM}, -{0x1923, 0x1926, L}, -{0x1927, 0x1928, NSM}, -{0x1929, 0x192b, L}, -{0x1930, 0x1931, L}, -{0x1932, 0x1932, NSM}, -{0x1933, 0x1938, L}, -{0x1939, 0x193b, NSM}, -{0x1946, 0x196d, L}, -{0x1970, 0x1974, L}, -{0x1980, 0x19ab, L}, -{0x19b0, 0x19c9, L}, -{0x19d0, 0x19da, L}, -{0x1a00, 0x1a16, L}, -{0x1a17, 0x1a18, NSM}, -{0x1a19, 0x1a1a, L}, -{0x1a1b, 0x1a1b, NSM}, -{0x1a1e, 0x1a55, L}, -{0x1a56, 0x1a56, NSM}, -{0x1a57, 0x1a57, L}, -{0x1a58, 0x1a5e, NSM}, -{0x1a60, 0x1a60, NSM}, -{0x1a61, 0x1a61, L}, -{0x1a62, 0x1a62, NSM}, -{0x1a63, 0x1a64, L}, -{0x1a65, 0x1a6c, NSM}, -{0x1a6d, 0x1a72, L}, -{0x1a73, 0x1a7c, NSM}, -{0x1a7f, 0x1a7f, NSM}, -{0x1a80, 0x1a89, L}, -{0x1a90, 0x1a99, L}, -{0x1aa0, 0x1aad, L}, -{0x1ab0, 0x1ace, NSM}, -{0x1b00, 0x1b03, NSM}, -{0x1b04, 0x1b33, L}, -{0x1b34, 0x1b34, NSM}, -{0x1b35, 0x1b35, L}, -{0x1b36, 0x1b3a, NSM}, -{0x1b3b, 0x1b3b, L}, -{0x1b3c, 0x1b3c, NSM}, -{0x1b3d, 0x1b41, L}, -{0x1b42, 0x1b42, NSM}, -{0x1b43, 0x1b4c, L}, -{0x1b50, 0x1b6a, L}, -{0x1b6b, 0x1b73, NSM}, -{0x1b74, 0x1b7e, L}, -{0x1b80, 0x1b81, NSM}, -{0x1b82, 0x1ba1, L}, -{0x1ba2, 0x1ba5, NSM}, -{0x1ba6, 0x1ba7, L}, -{0x1ba8, 0x1ba9, NSM}, -{0x1baa, 0x1baa, L}, -{0x1bab, 0x1bad, NSM}, -{0x1bae, 0x1be5, L}, -{0x1be6, 0x1be6, NSM}, -{0x1be7, 0x1be7, L}, -{0x1be8, 0x1be9, NSM}, -{0x1bea, 0x1bec, L}, -{0x1bed, 0x1bed, NSM}, -{0x1bee, 0x1bee, L}, -{0x1bef, 0x1bf1, NSM}, -{0x1bf2, 0x1bf3, L}, -{0x1bfc, 0x1c2b, L}, -{0x1c2c, 0x1c33, NSM}, -{0x1c34, 0x1c35, L}, -{0x1c36, 0x1c37, NSM}, -{0x1c3b, 0x1c49, L}, -{0x1c4d, 0x1c88, L}, -{0x1c90, 0x1cba, L}, -{0x1cbd, 0x1cc7, L}, -{0x1cd0, 0x1cd2, NSM}, -{0x1cd3, 0x1cd3, L}, -{0x1cd4, 0x1ce0, NSM}, -{0x1ce1, 0x1ce1, L}, -{0x1ce2, 0x1ce8, NSM}, -{0x1ce9, 0x1cec, L}, -{0x1ced, 0x1ced, NSM}, -{0x1cee, 0x1cf3, L}, -{0x1cf4, 0x1cf4, NSM}, -{0x1cf5, 0x1cf7, L}, -{0x1cf8, 0x1cf9, NSM}, -{0x1cfa, 0x1cfa, L}, -{0x1d00, 0x1dbf, L}, -{0x1dc0, 0x1dff, NSM}, -{0x1e00, 0x1f15, L}, -{0x1f18, 0x1f1d, L}, -{0x1f20, 0x1f45, L}, -{0x1f48, 0x1f4d, L}, -{0x1f50, 0x1f57, L}, -{0x1f59, 0x1f59, L}, -{0x1f5b, 0x1f5b, L}, -{0x1f5d, 0x1f5d, L}, -{0x1f5f, 0x1f7d, L}, -{0x1f80, 0x1fb4, L}, -{0x1fb6, 0x1fbc, L}, -{0x1fbe, 0x1fbe, L}, -{0x1fc2, 0x1fc4, L}, -{0x1fc6, 0x1fcc, L}, -{0x1fd0, 0x1fd3, L}, -{0x1fd6, 0x1fdb, L}, -{0x1fe0, 0x1fec, L}, -{0x1ff2, 0x1ff4, L}, -{0x1ff6, 0x1ffc, L}, -{0x2000, 0x200a, WS}, -{0x200b, 0x200d, BN}, -{0x200e, 0x200e, L}, -{0x200f, 0x200f, R}, -{0x2028, 0x2028, WS}, -{0x2029, 0x2029, B}, -{0x202a, 0x202a, LRE}, -{0x202b, 0x202b, RLE}, -{0x202c, 0x202c, PDF}, -{0x202d, 0x202d, LRO}, -{0x202e, 0x202e, RLO}, -{0x202f, 0x202f, CS}, -{0x2030, 0x2034, ET}, -{0x2044, 0x2044, CS}, -{0x205f, 0x205f, WS}, -{0x2060, 0x2064, BN}, -{0x2066, 0x2066, LRI}, -{0x2067, 0x2067, RLI}, -{0x2068, 0x2068, FSI}, -{0x2069, 0x2069, PDI}, -{0x206a, 0x206f, BN}, -{0x2070, 0x2070, EN}, -{0x2071, 0x2071, L}, -{0x2074, 0x2079, EN}, -{0x207a, 0x207b, ES}, -{0x207f, 0x207f, L}, -{0x2080, 0x2089, EN}, -{0x208a, 0x208b, ES}, -{0x2090, 0x209c, L}, -{0x20a0, 0x20c0, ET}, -{0x20d0, 0x20f0, NSM}, -{0x2102, 0x2102, L}, -{0x2107, 0x2107, L}, -{0x210a, 0x2113, L}, -{0x2115, 0x2115, L}, -{0x2119, 0x211d, L}, -{0x2124, 0x2124, L}, -{0x2126, 0x2126, L}, -{0x2128, 0x2128, L}, -{0x212a, 0x212d, L}, -{0x212e, 0x212e, ET}, -{0x212f, 0x2139, L}, -{0x213c, 0x213f, L}, -{0x2145, 0x2149, L}, -{0x214e, 0x214f, L}, -{0x2160, 0x2188, L}, -{0x2212, 0x2212, ES}, -{0x2213, 0x2213, ET}, -{0x2336, 0x237a, L}, -{0x2395, 0x2395, L}, -{0x2488, 0x249b, EN}, -{0x249c, 0x24e9, L}, -{0x26ac, 0x26ac, L}, -{0x2800, 0x28ff, L}, -{0x2c00, 0x2ce4, L}, -{0x2ceb, 0x2cee, L}, -{0x2cef, 0x2cf1, NSM}, -{0x2cf2, 0x2cf3, L}, -{0x2d00, 0x2d25, L}, -{0x2d27, 0x2d27, L}, -{0x2d2d, 0x2d2d, L}, -{0x2d30, 0x2d67, L}, -{0x2d6f, 0x2d70, L}, -{0x2d7f, 0x2d7f, NSM}, -{0x2d80, 0x2d96, L}, -{0x2da0, 0x2da6, L}, -{0x2da8, 0x2dae, L}, -{0x2db0, 0x2db6, L}, -{0x2db8, 0x2dbe, L}, -{0x2dc0, 0x2dc6, L}, -{0x2dc8, 0x2dce, L}, -{0x2dd0, 0x2dd6, L}, -{0x2dd8, 0x2dde, L}, -{0x2de0, 0x2dff, NSM}, -{0x3000, 0x3000, WS}, -{0x3005, 0x3007, L}, -{0x3021, 0x3029, L}, -{0x302a, 0x302d, NSM}, -{0x302e, 0x302f, L}, -{0x3031, 0x3035, L}, -{0x3038, 0x303c, L}, -{0x3041, 0x3096, L}, -{0x3099, 0x309a, NSM}, -{0x309d, 0x309f, L}, -{0x30a1, 0x30fa, L}, -{0x30fc, 0x30ff, L}, -{0x3105, 0x312f, L}, -{0x3131, 0x318e, L}, -{0x3190, 0x31bf, L}, -{0x31f0, 0x321c, L}, -{0x3220, 0x324f, L}, -{0x3260, 0x327b, L}, -{0x327f, 0x32b0, L}, -{0x32c0, 0x32cb, L}, -{0x32d0, 0x3376, L}, -{0x337b, 0x33dd, L}, -{0x33e0, 0x33fe, L}, -{0x3400, 0x4dbf, L}, -{0x4e00, 0xa48c, L}, -{0xa4d0, 0xa60c, L}, -{0xa610, 0xa62b, L}, -{0xa640, 0xa66e, L}, -{0xa66f, 0xa672, NSM}, -{0xa674, 0xa67d, NSM}, -{0xa680, 0xa69d, L}, -{0xa69e, 0xa69f, NSM}, -{0xa6a0, 0xa6ef, L}, -{0xa6f0, 0xa6f1, NSM}, -{0xa6f2, 0xa6f7, L}, -{0xa722, 0xa787, L}, -{0xa789, 0xa7ca, L}, -{0xa7d0, 0xa7d1, L}, -{0xa7d3, 0xa7d3, L}, -{0xa7d5, 0xa7d9, L}, -{0xa7f2, 0xa801, L}, -{0xa802, 0xa802, NSM}, -{0xa803, 0xa805, L}, -{0xa806, 0xa806, NSM}, -{0xa807, 0xa80a, L}, -{0xa80b, 0xa80b, NSM}, -{0xa80c, 0xa824, L}, -{0xa825, 0xa826, NSM}, -{0xa827, 0xa827, L}, -{0xa82c, 0xa82c, NSM}, -{0xa830, 0xa837, L}, -{0xa838, 0xa839, ET}, -{0xa840, 0xa873, L}, -{0xa880, 0xa8c3, L}, -{0xa8c4, 0xa8c5, NSM}, -{0xa8ce, 0xa8d9, L}, -{0xa8e0, 0xa8f1, NSM}, -{0xa8f2, 0xa8fe, L}, -{0xa8ff, 0xa8ff, NSM}, -{0xa900, 0xa925, L}, -{0xa926, 0xa92d, NSM}, -{0xa92e, 0xa946, L}, -{0xa947, 0xa951, NSM}, -{0xa952, 0xa953, L}, -{0xa95f, 0xa97c, L}, -{0xa980, 0xa982, NSM}, -{0xa983, 0xa9b2, L}, -{0xa9b3, 0xa9b3, NSM}, -{0xa9b4, 0xa9b5, L}, -{0xa9b6, 0xa9b9, NSM}, -{0xa9ba, 0xa9bb, L}, -{0xa9bc, 0xa9bd, NSM}, -{0xa9be, 0xa9cd, L}, -{0xa9cf, 0xa9d9, L}, -{0xa9de, 0xa9e4, L}, -{0xa9e5, 0xa9e5, NSM}, -{0xa9e6, 0xa9fe, L}, -{0xaa00, 0xaa28, L}, -{0xaa29, 0xaa2e, NSM}, -{0xaa2f, 0xaa30, L}, -{0xaa31, 0xaa32, NSM}, -{0xaa33, 0xaa34, L}, -{0xaa35, 0xaa36, NSM}, -{0xaa40, 0xaa42, L}, -{0xaa43, 0xaa43, NSM}, -{0xaa44, 0xaa4b, L}, -{0xaa4c, 0xaa4c, NSM}, -{0xaa4d, 0xaa4d, L}, -{0xaa50, 0xaa59, L}, -{0xaa5c, 0xaa7b, L}, -{0xaa7c, 0xaa7c, NSM}, -{0xaa7d, 0xaaaf, L}, -{0xaab0, 0xaab0, NSM}, -{0xaab1, 0xaab1, L}, -{0xaab2, 0xaab4, NSM}, -{0xaab5, 0xaab6, L}, -{0xaab7, 0xaab8, NSM}, -{0xaab9, 0xaabd, L}, -{0xaabe, 0xaabf, NSM}, -{0xaac0, 0xaac0, L}, -{0xaac1, 0xaac1, NSM}, -{0xaac2, 0xaac2, L}, -{0xaadb, 0xaaeb, L}, -{0xaaec, 0xaaed, NSM}, -{0xaaee, 0xaaf5, L}, -{0xaaf6, 0xaaf6, NSM}, -{0xab01, 0xab06, L}, -{0xab09, 0xab0e, L}, -{0xab11, 0xab16, L}, -{0xab20, 0xab26, L}, -{0xab28, 0xab2e, L}, -{0xab30, 0xab69, L}, -{0xab70, 0xabe4, L}, -{0xabe5, 0xabe5, NSM}, -{0xabe6, 0xabe7, L}, -{0xabe8, 0xabe8, NSM}, -{0xabe9, 0xabec, L}, -{0xabed, 0xabed, NSM}, -{0xabf0, 0xabf9, L}, -{0xac00, 0xd7a3, L}, -{0xd7b0, 0xd7c6, L}, -{0xd7cb, 0xd7fb, L}, -{0xd800, 0xfa6d, L}, -{0xfa70, 0xfad9, L}, -{0xfb00, 0xfb06, L}, -{0xfb13, 0xfb17, L}, -{0xfb1d, 0xfb1d, R}, -{0xfb1e, 0xfb1e, NSM}, -{0xfb1f, 0xfb28, R}, -{0xfb29, 0xfb29, ES}, -{0xfb2a, 0xfb36, R}, -{0xfb38, 0xfb3c, R}, -{0xfb3e, 0xfb3e, R}, -{0xfb40, 0xfb41, R}, -{0xfb43, 0xfb44, R}, -{0xfb46, 0xfb4f, R}, -{0xfb50, 0xfbc2, AL}, -{0xfbd3, 0xfd3d, AL}, -{0xfd50, 0xfd8f, AL}, -{0xfd92, 0xfdc7, AL}, -{0xfdf0, 0xfdfc, AL}, -{0xfe00, 0xfe0f, NSM}, -{0xfe20, 0xfe2f, NSM}, -{0xfe50, 0xfe50, CS}, -{0xfe52, 0xfe52, CS}, -{0xfe55, 0xfe55, CS}, -{0xfe5f, 0xfe5f, ET}, -{0xfe62, 0xfe63, ES}, -{0xfe69, 0xfe6a, ET}, -{0xfe70, 0xfe74, AL}, -{0xfe76, 0xfefc, AL}, -{0xfeff, 0xfeff, BN}, -{0xff03, 0xff05, ET}, -{0xff0b, 0xff0b, ES}, -{0xff0c, 0xff0c, CS}, -{0xff0d, 0xff0d, ES}, -{0xff0e, 0xff0f, CS}, -{0xff10, 0xff19, EN}, -{0xff1a, 0xff1a, CS}, -{0xff21, 0xff3a, L}, -{0xff41, 0xff5a, L}, -{0xff66, 0xffbe, L}, -{0xffc2, 0xffc7, L}, -{0xffca, 0xffcf, L}, -{0xffd2, 0xffd7, L}, -{0xffda, 0xffdc, L}, -{0xffe0, 0xffe1, ET}, -{0xffe5, 0xffe6, ET}, -{0x10000, 0x1000b, L}, -{0x1000d, 0x10026, L}, -{0x10028, 0x1003a, L}, -{0x1003c, 0x1003d, L}, -{0x1003f, 0x1004d, L}, -{0x10050, 0x1005d, L}, -{0x10080, 0x100fa, L}, -{0x10100, 0x10100, L}, -{0x10102, 0x10102, L}, -{0x10107, 0x10133, L}, -{0x10137, 0x1013f, L}, -{0x1018d, 0x1018e, L}, -{0x101d0, 0x101fc, L}, -{0x101fd, 0x101fd, NSM}, -{0x10280, 0x1029c, L}, -{0x102a0, 0x102d0, L}, -{0x102e0, 0x102e0, NSM}, -{0x102e1, 0x102fb, EN}, -{0x10300, 0x10323, L}, -{0x1032d, 0x1034a, L}, -{0x10350, 0x10375, L}, -{0x10376, 0x1037a, NSM}, -{0x10380, 0x1039d, L}, -{0x1039f, 0x103c3, L}, -{0x103c8, 0x103d5, L}, -{0x10400, 0x1049d, L}, -{0x104a0, 0x104a9, L}, -{0x104b0, 0x104d3, L}, -{0x104d8, 0x104fb, L}, -{0x10500, 0x10527, L}, -{0x10530, 0x10563, L}, -{0x1056f, 0x1057a, L}, -{0x1057c, 0x1058a, L}, -{0x1058c, 0x10592, L}, -{0x10594, 0x10595, L}, -{0x10597, 0x105a1, L}, -{0x105a3, 0x105b1, L}, -{0x105b3, 0x105b9, L}, -{0x105bb, 0x105bc, L}, -{0x10600, 0x10736, L}, -{0x10740, 0x10755, L}, -{0x10760, 0x10767, L}, -{0x10780, 0x10785, L}, -{0x10787, 0x107b0, L}, -{0x107b2, 0x107ba, L}, -{0x10800, 0x10805, R}, -{0x10808, 0x10808, R}, -{0x1080a, 0x10835, R}, -{0x10837, 0x10838, R}, -{0x1083c, 0x1083c, R}, -{0x1083f, 0x10855, R}, -{0x10857, 0x1089e, R}, -{0x108a7, 0x108af, R}, -{0x108e0, 0x108f2, R}, -{0x108f4, 0x108f5, R}, -{0x108fb, 0x1091b, R}, -{0x10920, 0x10939, R}, -{0x1093f, 0x1093f, R}, -{0x10980, 0x109b7, R}, -{0x109bc, 0x109cf, R}, -{0x109d2, 0x10a00, R}, -{0x10a01, 0x10a03, NSM}, -{0x10a05, 0x10a06, NSM}, -{0x10a0c, 0x10a0f, NSM}, -{0x10a10, 0x10a13, R}, -{0x10a15, 0x10a17, R}, -{0x10a19, 0x10a35, R}, -{0x10a38, 0x10a3a, NSM}, -{0x10a3f, 0x10a3f, NSM}, -{0x10a40, 0x10a48, R}, -{0x10a50, 0x10a58, R}, -{0x10a60, 0x10a9f, R}, -{0x10ac0, 0x10ae4, R}, -{0x10ae5, 0x10ae6, NSM}, -{0x10aeb, 0x10af6, R}, -{0x10b00, 0x10b35, R}, -{0x10b40, 0x10b55, R}, -{0x10b58, 0x10b72, R}, -{0x10b78, 0x10b91, R}, -{0x10b99, 0x10b9c, R}, -{0x10ba9, 0x10baf, R}, -{0x10c00, 0x10c48, R}, -{0x10c80, 0x10cb2, R}, -{0x10cc0, 0x10cf2, R}, -{0x10cfa, 0x10cff, R}, -{0x10d00, 0x10d23, AL}, -{0x10d24, 0x10d27, NSM}, -{0x10d30, 0x10d39, AN}, -{0x10e60, 0x10e7e, AN}, -{0x10e80, 0x10ea9, R}, -{0x10eab, 0x10eac, NSM}, -{0x10ead, 0x10ead, R}, -{0x10eb0, 0x10eb1, R}, -{0x10efd, 0x10eff, NSM}, -{0x10f00, 0x10f27, R}, -{0x10f30, 0x10f45, AL}, -{0x10f46, 0x10f50, NSM}, -{0x10f51, 0x10f59, AL}, -{0x10f70, 0x10f81, R}, -{0x10f82, 0x10f85, NSM}, -{0x10f86, 0x10f89, R}, -{0x10fb0, 0x10fcb, R}, -{0x10fe0, 0x10ff6, R}, -{0x11000, 0x11000, L}, -{0x11001, 0x11001, NSM}, -{0x11002, 0x11037, L}, -{0x11038, 0x11046, NSM}, -{0x11047, 0x1104d, L}, -{0x11066, 0x1106f, L}, -{0x11070, 0x11070, NSM}, -{0x11071, 0x11072, L}, -{0x11073, 0x11074, NSM}, -{0x11075, 0x11075, L}, -{0x1107f, 0x11081, NSM}, -{0x11082, 0x110b2, L}, -{0x110b3, 0x110b6, NSM}, -{0x110b7, 0x110b8, L}, -{0x110b9, 0x110ba, NSM}, -{0x110bb, 0x110c1, L}, -{0x110c2, 0x110c2, NSM}, -{0x110cd, 0x110cd, L}, -{0x110d0, 0x110e8, L}, -{0x110f0, 0x110f9, L}, -{0x11100, 0x11102, NSM}, -{0x11103, 0x11126, L}, -{0x11127, 0x1112b, NSM}, -{0x1112c, 0x1112c, L}, -{0x1112d, 0x11134, NSM}, -{0x11136, 0x11147, L}, -{0x11150, 0x11172, L}, -{0x11173, 0x11173, NSM}, -{0x11174, 0x11176, L}, -{0x11180, 0x11181, NSM}, -{0x11182, 0x111b5, L}, -{0x111b6, 0x111be, NSM}, -{0x111bf, 0x111c8, L}, -{0x111c9, 0x111cc, NSM}, -{0x111cd, 0x111ce, L}, -{0x111cf, 0x111cf, NSM}, -{0x111d0, 0x111df, L}, -{0x111e1, 0x111f4, L}, -{0x11200, 0x11211, L}, -{0x11213, 0x1122e, L}, -{0x1122f, 0x11231, NSM}, -{0x11232, 0x11233, L}, -{0x11234, 0x11234, NSM}, -{0x11235, 0x11235, L}, -{0x11236, 0x11237, NSM}, -{0x11238, 0x1123d, L}, -{0x1123e, 0x1123e, NSM}, -{0x1123f, 0x11240, L}, -{0x11241, 0x11241, NSM}, -{0x11280, 0x11286, L}, -{0x11288, 0x11288, L}, -{0x1128a, 0x1128d, L}, -{0x1128f, 0x1129d, L}, -{0x1129f, 0x112a9, L}, -{0x112b0, 0x112de, L}, -{0x112df, 0x112df, NSM}, -{0x112e0, 0x112e2, L}, -{0x112e3, 0x112ea, NSM}, -{0x112f0, 0x112f9, L}, -{0x11300, 0x11301, NSM}, -{0x11302, 0x11303, L}, -{0x11305, 0x1130c, L}, -{0x1130f, 0x11310, L}, -{0x11313, 0x11328, L}, -{0x1132a, 0x11330, L}, -{0x11332, 0x11333, L}, -{0x11335, 0x11339, L}, -{0x1133b, 0x1133c, NSM}, -{0x1133d, 0x1133f, L}, -{0x11340, 0x11340, NSM}, -{0x11341, 0x11344, L}, -{0x11347, 0x11348, L}, -{0x1134b, 0x1134d, L}, -{0x11350, 0x11350, L}, -{0x11357, 0x11357, L}, -{0x1135d, 0x11363, L}, -{0x11366, 0x1136c, NSM}, -{0x11370, 0x11374, NSM}, -{0x11400, 0x11437, L}, -{0x11438, 0x1143f, NSM}, -{0x11440, 0x11441, L}, -{0x11442, 0x11444, NSM}, -{0x11445, 0x11445, L}, -{0x11446, 0x11446, NSM}, -{0x11447, 0x1145b, L}, -{0x1145d, 0x1145d, L}, -{0x1145e, 0x1145e, NSM}, -{0x1145f, 0x11461, L}, -{0x11480, 0x114b2, L}, -{0x114b3, 0x114b8, NSM}, -{0x114b9, 0x114b9, L}, -{0x114ba, 0x114ba, NSM}, -{0x114bb, 0x114be, L}, -{0x114bf, 0x114c0, NSM}, -{0x114c1, 0x114c1, L}, -{0x114c2, 0x114c3, NSM}, -{0x114c4, 0x114c7, L}, -{0x114d0, 0x114d9, L}, -{0x11580, 0x115b1, L}, -{0x115b2, 0x115b5, NSM}, -{0x115b8, 0x115bb, L}, -{0x115bc, 0x115bd, NSM}, -{0x115be, 0x115be, L}, -{0x115bf, 0x115c0, NSM}, -{0x115c1, 0x115db, L}, -{0x115dc, 0x115dd, NSM}, -{0x11600, 0x11632, L}, -{0x11633, 0x1163a, NSM}, -{0x1163b, 0x1163c, L}, -{0x1163d, 0x1163d, NSM}, -{0x1163e, 0x1163e, L}, -{0x1163f, 0x11640, NSM}, -{0x11641, 0x11644, L}, -{0x11650, 0x11659, L}, -{0x11680, 0x116aa, L}, -{0x116ab, 0x116ab, NSM}, -{0x116ac, 0x116ac, L}, -{0x116ad, 0x116ad, NSM}, -{0x116ae, 0x116af, L}, -{0x116b0, 0x116b5, NSM}, -{0x116b6, 0x116b6, L}, -{0x116b7, 0x116b7, NSM}, -{0x116b8, 0x116b9, L}, -{0x116c0, 0x116c9, L}, -{0x11700, 0x1171a, L}, -{0x1171d, 0x1171f, NSM}, -{0x11720, 0x11721, L}, -{0x11722, 0x11725, NSM}, -{0x11726, 0x11726, L}, -{0x11727, 0x1172b, NSM}, -{0x11730, 0x11746, L}, -{0x11800, 0x1182e, L}, -{0x1182f, 0x11837, NSM}, -{0x11838, 0x11838, L}, -{0x11839, 0x1183a, NSM}, -{0x1183b, 0x1183b, L}, -{0x118a0, 0x118f2, L}, -{0x118ff, 0x11906, L}, -{0x11909, 0x11909, L}, -{0x1190c, 0x11913, L}, -{0x11915, 0x11916, L}, -{0x11918, 0x11935, L}, -{0x11937, 0x11938, L}, -{0x1193b, 0x1193c, NSM}, -{0x1193d, 0x1193d, L}, -{0x1193e, 0x1193e, NSM}, -{0x1193f, 0x11942, L}, -{0x11943, 0x11943, NSM}, -{0x11944, 0x11946, L}, -{0x11950, 0x11959, L}, -{0x119a0, 0x119a7, L}, -{0x119aa, 0x119d3, L}, -{0x119d4, 0x119d7, NSM}, -{0x119da, 0x119db, NSM}, -{0x119dc, 0x119df, L}, -{0x119e0, 0x119e0, NSM}, -{0x119e1, 0x119e4, L}, -{0x11a00, 0x11a00, L}, -{0x11a01, 0x11a06, NSM}, -{0x11a07, 0x11a08, L}, -{0x11a09, 0x11a0a, NSM}, -{0x11a0b, 0x11a32, L}, -{0x11a33, 0x11a38, NSM}, -{0x11a39, 0x11a3a, L}, -{0x11a3b, 0x11a3e, NSM}, -{0x11a3f, 0x11a46, L}, -{0x11a47, 0x11a47, NSM}, -{0x11a50, 0x11a50, L}, -{0x11a51, 0x11a56, NSM}, -{0x11a57, 0x11a58, L}, -{0x11a59, 0x11a5b, NSM}, -{0x11a5c, 0x11a89, L}, -{0x11a8a, 0x11a96, NSM}, -{0x11a97, 0x11a97, L}, -{0x11a98, 0x11a99, NSM}, -{0x11a9a, 0x11aa2, L}, -{0x11ab0, 0x11af8, L}, -{0x11b00, 0x11b09, L}, -{0x11c00, 0x11c08, L}, -{0x11c0a, 0x11c2f, L}, -{0x11c30, 0x11c36, NSM}, -{0x11c38, 0x11c3d, NSM}, -{0x11c3e, 0x11c45, L}, -{0x11c50, 0x11c6c, L}, -{0x11c70, 0x11c8f, L}, -{0x11c92, 0x11ca7, NSM}, -{0x11ca9, 0x11ca9, L}, -{0x11caa, 0x11cb0, NSM}, -{0x11cb1, 0x11cb1, L}, -{0x11cb2, 0x11cb3, NSM}, -{0x11cb4, 0x11cb4, L}, -{0x11cb5, 0x11cb6, NSM}, -{0x11d00, 0x11d06, L}, -{0x11d08, 0x11d09, L}, -{0x11d0b, 0x11d30, L}, -{0x11d31, 0x11d36, NSM}, -{0x11d3a, 0x11d3a, NSM}, -{0x11d3c, 0x11d3d, NSM}, -{0x11d3f, 0x11d45, NSM}, -{0x11d46, 0x11d46, L}, -{0x11d47, 0x11d47, NSM}, -{0x11d50, 0x11d59, L}, -{0x11d60, 0x11d65, L}, -{0x11d67, 0x11d68, L}, -{0x11d6a, 0x11d8e, L}, -{0x11d90, 0x11d91, NSM}, -{0x11d93, 0x11d94, L}, -{0x11d95, 0x11d95, NSM}, -{0x11d96, 0x11d96, L}, -{0x11d97, 0x11d97, NSM}, -{0x11d98, 0x11d98, L}, -{0x11da0, 0x11da9, L}, -{0x11ee0, 0x11ef2, L}, -{0x11ef3, 0x11ef4, NSM}, -{0x11ef5, 0x11ef8, L}, -{0x11f00, 0x11f01, NSM}, -{0x11f02, 0x11f10, L}, -{0x11f12, 0x11f35, L}, -{0x11f36, 0x11f3a, NSM}, -{0x11f3e, 0x11f3f, L}, -{0x11f40, 0x11f40, NSM}, -{0x11f41, 0x11f41, L}, -{0x11f42, 0x11f42, NSM}, -{0x11f43, 0x11f59, L}, -{0x11fb0, 0x11fb0, L}, -{0x11fc0, 0x11fd4, L}, -{0x11fdd, 0x11fe0, ET}, -{0x11fff, 0x12399, L}, -{0x12400, 0x1246e, L}, -{0x12470, 0x12474, L}, -{0x12480, 0x12543, L}, -{0x12f90, 0x12ff2, L}, -{0x13000, 0x1343f, L}, -{0x13440, 0x13440, NSM}, -{0x13441, 0x13446, L}, -{0x13447, 0x13455, NSM}, -{0x14400, 0x14646, L}, -{0x16800, 0x16a38, L}, -{0x16a40, 0x16a5e, L}, -{0x16a60, 0x16a69, L}, -{0x16a6e, 0x16abe, L}, -{0x16ac0, 0x16ac9, L}, -{0x16ad0, 0x16aed, L}, -{0x16af0, 0x16af4, NSM}, -{0x16af5, 0x16af5, L}, -{0x16b00, 0x16b2f, L}, -{0x16b30, 0x16b36, NSM}, -{0x16b37, 0x16b45, L}, -{0x16b50, 0x16b59, L}, -{0x16b5b, 0x16b61, L}, -{0x16b63, 0x16b77, L}, -{0x16b7d, 0x16b8f, L}, -{0x16e40, 0x16e9a, L}, -{0x16f00, 0x16f4a, L}, -{0x16f4f, 0x16f4f, NSM}, -{0x16f50, 0x16f87, L}, -{0x16f8f, 0x16f92, NSM}, -{0x16f93, 0x16f9f, L}, -{0x16fe0, 0x16fe1, L}, -{0x16fe3, 0x16fe3, L}, -{0x16fe4, 0x16fe4, NSM}, -{0x16ff0, 0x16ff1, L}, -{0x17000, 0x187f7, L}, -{0x18800, 0x18cd5, L}, -{0x18d00, 0x18d08, L}, -{0x1aff0, 0x1aff3, L}, -{0x1aff5, 0x1affb, L}, -{0x1affd, 0x1affe, L}, -{0x1b000, 0x1b122, L}, -{0x1b132, 0x1b132, L}, -{0x1b150, 0x1b152, L}, -{0x1b155, 0x1b155, L}, -{0x1b164, 0x1b167, L}, -{0x1b170, 0x1b2fb, L}, -{0x1bc00, 0x1bc6a, L}, -{0x1bc70, 0x1bc7c, L}, -{0x1bc80, 0x1bc88, L}, -{0x1bc90, 0x1bc99, L}, -{0x1bc9c, 0x1bc9c, L}, -{0x1bc9d, 0x1bc9e, NSM}, -{0x1bc9f, 0x1bc9f, L}, -{0x1bca0, 0x1bca3, BN}, -{0x1cf00, 0x1cf2d, NSM}, -{0x1cf30, 0x1cf46, NSM}, -{0x1cf50, 0x1cfc3, L}, -{0x1d000, 0x1d0f5, L}, -{0x1d100, 0x1d126, L}, -{0x1d129, 0x1d166, L}, -{0x1d167, 0x1d169, NSM}, -{0x1d16a, 0x1d172, L}, -{0x1d173, 0x1d17a, BN}, -{0x1d17b, 0x1d182, NSM}, -{0x1d183, 0x1d184, L}, -{0x1d185, 0x1d18b, NSM}, -{0x1d18c, 0x1d1a9, L}, -{0x1d1aa, 0x1d1ad, NSM}, -{0x1d1ae, 0x1d1e8, L}, -{0x1d242, 0x1d244, NSM}, -{0x1d2c0, 0x1d2d3, L}, -{0x1d2e0, 0x1d2f3, L}, -{0x1d360, 0x1d378, L}, -{0x1d400, 0x1d454, L}, -{0x1d456, 0x1d49c, L}, -{0x1d49e, 0x1d49f, L}, -{0x1d4a2, 0x1d4a2, L}, -{0x1d4a5, 0x1d4a6, L}, -{0x1d4a9, 0x1d4ac, L}, -{0x1d4ae, 0x1d4b9, L}, -{0x1d4bb, 0x1d4bb, L}, -{0x1d4bd, 0x1d4c3, L}, -{0x1d4c5, 0x1d505, L}, -{0x1d507, 0x1d50a, L}, -{0x1d50d, 0x1d514, L}, -{0x1d516, 0x1d51c, L}, -{0x1d51e, 0x1d539, L}, -{0x1d53b, 0x1d53e, L}, -{0x1d540, 0x1d544, L}, -{0x1d546, 0x1d546, L}, -{0x1d54a, 0x1d550, L}, -{0x1d552, 0x1d6a5, L}, -{0x1d6a8, 0x1d6da, L}, -{0x1d6dc, 0x1d714, L}, -{0x1d716, 0x1d74e, L}, -{0x1d750, 0x1d788, L}, -{0x1d78a, 0x1d7c2, L}, -{0x1d7c4, 0x1d7cb, L}, -{0x1d7ce, 0x1d7ff, EN}, -{0x1d800, 0x1d9ff, L}, -{0x1da00, 0x1da36, NSM}, -{0x1da37, 0x1da3a, L}, -{0x1da3b, 0x1da6c, NSM}, -{0x1da6d, 0x1da74, L}, -{0x1da75, 0x1da75, NSM}, -{0x1da76, 0x1da83, L}, -{0x1da84, 0x1da84, NSM}, -{0x1da85, 0x1da8b, L}, -{0x1da9b, 0x1da9f, NSM}, -{0x1daa1, 0x1daaf, NSM}, -{0x1df00, 0x1df1e, L}, -{0x1df25, 0x1df2a, L}, -{0x1e000, 0x1e006, NSM}, -{0x1e008, 0x1e018, NSM}, -{0x1e01b, 0x1e021, NSM}, -{0x1e023, 0x1e024, NSM}, -{0x1e026, 0x1e02a, NSM}, -{0x1e030, 0x1e06d, L}, -{0x1e08f, 0x1e08f, NSM}, -{0x1e100, 0x1e12c, L}, -{0x1e130, 0x1e136, NSM}, -{0x1e137, 0x1e13d, L}, -{0x1e140, 0x1e149, L}, -{0x1e14e, 0x1e14f, L}, -{0x1e290, 0x1e2ad, L}, -{0x1e2ae, 0x1e2ae, NSM}, -{0x1e2c0, 0x1e2eb, L}, -{0x1e2ec, 0x1e2ef, NSM}, -{0x1e2f0, 0x1e2f9, L}, -{0x1e2ff, 0x1e2ff, ET}, -{0x1e4d0, 0x1e4eb, L}, -{0x1e4ec, 0x1e4ef, NSM}, -{0x1e4f0, 0x1e4f9, L}, -{0x1e7e0, 0x1e7e6, L}, -{0x1e7e8, 0x1e7eb, L}, -{0x1e7ed, 0x1e7ee, L}, -{0x1e7f0, 0x1e7fe, L}, -{0x1e800, 0x1e8c4, R}, -{0x1e8c7, 0x1e8cf, R}, -{0x1e8d0, 0x1e8d6, NSM}, -{0x1e900, 0x1e943, R}, -{0x1e944, 0x1e94a, NSM}, -{0x1e94b, 0x1e94b, R}, -{0x1e950, 0x1e959, R}, -{0x1e95e, 0x1e95f, R}, -{0x1ec71, 0x1ecb4, AL}, -{0x1ed01, 0x1ed3d, AL}, -{0x1ee00, 0x1ee03, AL}, -{0x1ee05, 0x1ee1f, AL}, -{0x1ee21, 0x1ee22, AL}, -{0x1ee24, 0x1ee24, AL}, -{0x1ee27, 0x1ee27, AL}, -{0x1ee29, 0x1ee32, AL}, -{0x1ee34, 0x1ee37, AL}, -{0x1ee39, 0x1ee39, AL}, -{0x1ee3b, 0x1ee3b, AL}, -{0x1ee42, 0x1ee42, AL}, -{0x1ee47, 0x1ee47, AL}, -{0x1ee49, 0x1ee49, AL}, -{0x1ee4b, 0x1ee4b, AL}, -{0x1ee4d, 0x1ee4f, AL}, -{0x1ee51, 0x1ee52, AL}, -{0x1ee54, 0x1ee54, AL}, -{0x1ee57, 0x1ee57, AL}, -{0x1ee59, 0x1ee59, AL}, -{0x1ee5b, 0x1ee5b, AL}, -{0x1ee5d, 0x1ee5d, AL}, -{0x1ee5f, 0x1ee5f, AL}, -{0x1ee61, 0x1ee62, AL}, -{0x1ee64, 0x1ee64, AL}, -{0x1ee67, 0x1ee6a, AL}, -{0x1ee6c, 0x1ee72, AL}, -{0x1ee74, 0x1ee77, AL}, -{0x1ee79, 0x1ee7c, AL}, -{0x1ee7e, 0x1ee7e, AL}, -{0x1ee80, 0x1ee89, AL}, -{0x1ee8b, 0x1ee9b, AL}, -{0x1eea1, 0x1eea3, AL}, -{0x1eea5, 0x1eea9, AL}, -{0x1eeab, 0x1eebb, AL}, -{0x1f100, 0x1f10a, EN}, -{0x1f110, 0x1f12e, L}, -{0x1f130, 0x1f169, L}, -{0x1f170, 0x1f1ac, L}, -{0x1f1e6, 0x1f202, L}, -{0x1f210, 0x1f23b, L}, -{0x1f240, 0x1f248, L}, -{0x1f250, 0x1f251, L}, -{0x1fbf0, 0x1fbf9, EN}, -{0x20000, 0x2a6df, L}, -{0x2a700, 0x2b739, L}, -{0x2b740, 0x2b81d, L}, -{0x2b820, 0x2cea1, L}, -{0x2ceb0, 0x2ebe0, L}, -{0x2f800, 0x2fa1d, L}, -{0x30000, 0x3134a, L}, -{0x31350, 0x323af, L}, -{0xe0001, 0xe0001, BN}, -{0xe0020, 0xe007f, BN}, -{0xe0100, 0xe01ef, NSM}, -{0xf0000, 0xffffd, L}, -{0x100000, 0x10fffd, L}, diff --git a/unicode/canonical_comp.h b/unicode/canonical_comp.h deleted file mode 100644 index 3813418eb..000000000 --- a/unicode/canonical_comp.h +++ /dev/null @@ -1,950 +0,0 @@ -/* - * Autogenerated by read_ucd.py from The Unicode Standard 15.0.0 - * - * List the pairs of Unicode characters that canonically recompose to a - * single character in NFC. - * - * Used by utils/unicode-norm.c. - */ - -{0x003c, 0x0338, 0x226e}, -{0x003d, 0x0338, 0x2260}, -{0x003e, 0x0338, 0x226f}, -{0x0041, 0x0300, 0x00c0}, -{0x0041, 0x0301, 0x00c1}, -{0x0041, 0x0302, 0x00c2}, -{0x0041, 0x0303, 0x00c3}, -{0x0041, 0x0304, 0x0100}, -{0x0041, 0x0306, 0x0102}, -{0x0041, 0x0307, 0x0226}, -{0x0041, 0x0308, 0x00c4}, -{0x0041, 0x0309, 0x1ea2}, -{0x0041, 0x030a, 0x00c5}, -{0x0041, 0x030c, 0x01cd}, -{0x0041, 0x030f, 0x0200}, -{0x0041, 0x0311, 0x0202}, -{0x0041, 0x0323, 0x1ea0}, -{0x0041, 0x0325, 0x1e00}, -{0x0041, 0x0328, 0x0104}, -{0x0042, 0x0307, 0x1e02}, -{0x0042, 0x0323, 0x1e04}, -{0x0042, 0x0331, 0x1e06}, -{0x0043, 0x0301, 0x0106}, -{0x0043, 0x0302, 0x0108}, -{0x0043, 0x0307, 0x010a}, -{0x0043, 0x030c, 0x010c}, -{0x0043, 0x0327, 0x00c7}, -{0x0044, 0x0307, 0x1e0a}, -{0x0044, 0x030c, 0x010e}, -{0x0044, 0x0323, 0x1e0c}, -{0x0044, 0x0327, 0x1e10}, -{0x0044, 0x032d, 0x1e12}, -{0x0044, 0x0331, 0x1e0e}, -{0x0045, 0x0300, 0x00c8}, -{0x0045, 0x0301, 0x00c9}, -{0x0045, 0x0302, 0x00ca}, -{0x0045, 0x0303, 0x1ebc}, -{0x0045, 0x0304, 0x0112}, -{0x0045, 0x0306, 0x0114}, -{0x0045, 0x0307, 0x0116}, -{0x0045, 0x0308, 0x00cb}, -{0x0045, 0x0309, 0x1eba}, -{0x0045, 0x030c, 0x011a}, -{0x0045, 0x030f, 0x0204}, -{0x0045, 0x0311, 0x0206}, -{0x0045, 0x0323, 0x1eb8}, -{0x0045, 0x0327, 0x0228}, -{0x0045, 0x0328, 0x0118}, -{0x0045, 0x032d, 0x1e18}, -{0x0045, 0x0330, 0x1e1a}, -{0x0046, 0x0307, 0x1e1e}, -{0x0047, 0x0301, 0x01f4}, -{0x0047, 0x0302, 0x011c}, -{0x0047, 0x0304, 0x1e20}, -{0x0047, 0x0306, 0x011e}, -{0x0047, 0x0307, 0x0120}, -{0x0047, 0x030c, 0x01e6}, -{0x0047, 0x0327, 0x0122}, -{0x0048, 0x0302, 0x0124}, -{0x0048, 0x0307, 0x1e22}, -{0x0048, 0x0308, 0x1e26}, -{0x0048, 0x030c, 0x021e}, -{0x0048, 0x0323, 0x1e24}, -{0x0048, 0x0327, 0x1e28}, -{0x0048, 0x032e, 0x1e2a}, -{0x0049, 0x0300, 0x00cc}, -{0x0049, 0x0301, 0x00cd}, -{0x0049, 0x0302, 0x00ce}, -{0x0049, 0x0303, 0x0128}, -{0x0049, 0x0304, 0x012a}, -{0x0049, 0x0306, 0x012c}, -{0x0049, 0x0307, 0x0130}, -{0x0049, 0x0308, 0x00cf}, -{0x0049, 0x0309, 0x1ec8}, -{0x0049, 0x030c, 0x01cf}, -{0x0049, 0x030f, 0x0208}, -{0x0049, 0x0311, 0x020a}, -{0x0049, 0x0323, 0x1eca}, -{0x0049, 0x0328, 0x012e}, -{0x0049, 0x0330, 0x1e2c}, -{0x004a, 0x0302, 0x0134}, -{0x004b, 0x0301, 0x1e30}, -{0x004b, 0x030c, 0x01e8}, -{0x004b, 0x0323, 0x1e32}, -{0x004b, 0x0327, 0x0136}, -{0x004b, 0x0331, 0x1e34}, -{0x004c, 0x0301, 0x0139}, -{0x004c, 0x030c, 0x013d}, -{0x004c, 0x0323, 0x1e36}, -{0x004c, 0x0327, 0x013b}, -{0x004c, 0x032d, 0x1e3c}, -{0x004c, 0x0331, 0x1e3a}, -{0x004d, 0x0301, 0x1e3e}, -{0x004d, 0x0307, 0x1e40}, -{0x004d, 0x0323, 0x1e42}, -{0x004e, 0x0300, 0x01f8}, -{0x004e, 0x0301, 0x0143}, -{0x004e, 0x0303, 0x00d1}, -{0x004e, 0x0307, 0x1e44}, -{0x004e, 0x030c, 0x0147}, -{0x004e, 0x0323, 0x1e46}, -{0x004e, 0x0327, 0x0145}, -{0x004e, 0x032d, 0x1e4a}, -{0x004e, 0x0331, 0x1e48}, -{0x004f, 0x0300, 0x00d2}, -{0x004f, 0x0301, 0x00d3}, -{0x004f, 0x0302, 0x00d4}, -{0x004f, 0x0303, 0x00d5}, -{0x004f, 0x0304, 0x014c}, -{0x004f, 0x0306, 0x014e}, -{0x004f, 0x0307, 0x022e}, -{0x004f, 0x0308, 0x00d6}, -{0x004f, 0x0309, 0x1ece}, -{0x004f, 0x030b, 0x0150}, -{0x004f, 0x030c, 0x01d1}, -{0x004f, 0x030f, 0x020c}, -{0x004f, 0x0311, 0x020e}, -{0x004f, 0x031b, 0x01a0}, -{0x004f, 0x0323, 0x1ecc}, -{0x004f, 0x0328, 0x01ea}, -{0x0050, 0x0301, 0x1e54}, -{0x0050, 0x0307, 0x1e56}, -{0x0052, 0x0301, 0x0154}, -{0x0052, 0x0307, 0x1e58}, -{0x0052, 0x030c, 0x0158}, -{0x0052, 0x030f, 0x0210}, -{0x0052, 0x0311, 0x0212}, -{0x0052, 0x0323, 0x1e5a}, -{0x0052, 0x0327, 0x0156}, -{0x0052, 0x0331, 0x1e5e}, -{0x0053, 0x0301, 0x015a}, -{0x0053, 0x0302, 0x015c}, -{0x0053, 0x0307, 0x1e60}, -{0x0053, 0x030c, 0x0160}, -{0x0053, 0x0323, 0x1e62}, -{0x0053, 0x0326, 0x0218}, -{0x0053, 0x0327, 0x015e}, -{0x0054, 0x0307, 0x1e6a}, -{0x0054, 0x030c, 0x0164}, -{0x0054, 0x0323, 0x1e6c}, -{0x0054, 0x0326, 0x021a}, -{0x0054, 0x0327, 0x0162}, -{0x0054, 0x032d, 0x1e70}, -{0x0054, 0x0331, 0x1e6e}, -{0x0055, 0x0300, 0x00d9}, -{0x0055, 0x0301, 0x00da}, -{0x0055, 0x0302, 0x00db}, -{0x0055, 0x0303, 0x0168}, -{0x0055, 0x0304, 0x016a}, -{0x0055, 0x0306, 0x016c}, -{0x0055, 0x0308, 0x00dc}, -{0x0055, 0x0309, 0x1ee6}, -{0x0055, 0x030a, 0x016e}, -{0x0055, 0x030b, 0x0170}, -{0x0055, 0x030c, 0x01d3}, -{0x0055, 0x030f, 0x0214}, -{0x0055, 0x0311, 0x0216}, -{0x0055, 0x031b, 0x01af}, -{0x0055, 0x0323, 0x1ee4}, -{0x0055, 0x0324, 0x1e72}, -{0x0055, 0x0328, 0x0172}, -{0x0055, 0x032d, 0x1e76}, -{0x0055, 0x0330, 0x1e74}, -{0x0056, 0x0303, 0x1e7c}, -{0x0056, 0x0323, 0x1e7e}, -{0x0057, 0x0300, 0x1e80}, -{0x0057, 0x0301, 0x1e82}, -{0x0057, 0x0302, 0x0174}, -{0x0057, 0x0307, 0x1e86}, -{0x0057, 0x0308, 0x1e84}, -{0x0057, 0x0323, 0x1e88}, -{0x0058, 0x0307, 0x1e8a}, -{0x0058, 0x0308, 0x1e8c}, -{0x0059, 0x0300, 0x1ef2}, -{0x0059, 0x0301, 0x00dd}, -{0x0059, 0x0302, 0x0176}, -{0x0059, 0x0303, 0x1ef8}, -{0x0059, 0x0304, 0x0232}, -{0x0059, 0x0307, 0x1e8e}, -{0x0059, 0x0308, 0x0178}, -{0x0059, 0x0309, 0x1ef6}, -{0x0059, 0x0323, 0x1ef4}, -{0x005a, 0x0301, 0x0179}, -{0x005a, 0x0302, 0x1e90}, -{0x005a, 0x0307, 0x017b}, -{0x005a, 0x030c, 0x017d}, -{0x005a, 0x0323, 0x1e92}, -{0x005a, 0x0331, 0x1e94}, -{0x0061, 0x0300, 0x00e0}, -{0x0061, 0x0301, 0x00e1}, -{0x0061, 0x0302, 0x00e2}, -{0x0061, 0x0303, 0x00e3}, -{0x0061, 0x0304, 0x0101}, -{0x0061, 0x0306, 0x0103}, -{0x0061, 0x0307, 0x0227}, -{0x0061, 0x0308, 0x00e4}, -{0x0061, 0x0309, 0x1ea3}, -{0x0061, 0x030a, 0x00e5}, -{0x0061, 0x030c, 0x01ce}, -{0x0061, 0x030f, 0x0201}, -{0x0061, 0x0311, 0x0203}, -{0x0061, 0x0323, 0x1ea1}, -{0x0061, 0x0325, 0x1e01}, -{0x0061, 0x0328, 0x0105}, -{0x0062, 0x0307, 0x1e03}, -{0x0062, 0x0323, 0x1e05}, -{0x0062, 0x0331, 0x1e07}, -{0x0063, 0x0301, 0x0107}, -{0x0063, 0x0302, 0x0109}, -{0x0063, 0x0307, 0x010b}, -{0x0063, 0x030c, 0x010d}, -{0x0063, 0x0327, 0x00e7}, -{0x0064, 0x0307, 0x1e0b}, -{0x0064, 0x030c, 0x010f}, -{0x0064, 0x0323, 0x1e0d}, -{0x0064, 0x0327, 0x1e11}, -{0x0064, 0x032d, 0x1e13}, -{0x0064, 0x0331, 0x1e0f}, -{0x0065, 0x0300, 0x00e8}, -{0x0065, 0x0301, 0x00e9}, -{0x0065, 0x0302, 0x00ea}, -{0x0065, 0x0303, 0x1ebd}, -{0x0065, 0x0304, 0x0113}, -{0x0065, 0x0306, 0x0115}, -{0x0065, 0x0307, 0x0117}, -{0x0065, 0x0308, 0x00eb}, -{0x0065, 0x0309, 0x1ebb}, -{0x0065, 0x030c, 0x011b}, -{0x0065, 0x030f, 0x0205}, -{0x0065, 0x0311, 0x0207}, -{0x0065, 0x0323, 0x1eb9}, -{0x0065, 0x0327, 0x0229}, -{0x0065, 0x0328, 0x0119}, -{0x0065, 0x032d, 0x1e19}, -{0x0065, 0x0330, 0x1e1b}, -{0x0066, 0x0307, 0x1e1f}, -{0x0067, 0x0301, 0x01f5}, -{0x0067, 0x0302, 0x011d}, -{0x0067, 0x0304, 0x1e21}, -{0x0067, 0x0306, 0x011f}, -{0x0067, 0x0307, 0x0121}, -{0x0067, 0x030c, 0x01e7}, -{0x0067, 0x0327, 0x0123}, -{0x0068, 0x0302, 0x0125}, -{0x0068, 0x0307, 0x1e23}, -{0x0068, 0x0308, 0x1e27}, -{0x0068, 0x030c, 0x021f}, -{0x0068, 0x0323, 0x1e25}, -{0x0068, 0x0327, 0x1e29}, -{0x0068, 0x032e, 0x1e2b}, -{0x0068, 0x0331, 0x1e96}, -{0x0069, 0x0300, 0x00ec}, -{0x0069, 0x0301, 0x00ed}, -{0x0069, 0x0302, 0x00ee}, -{0x0069, 0x0303, 0x0129}, -{0x0069, 0x0304, 0x012b}, -{0x0069, 0x0306, 0x012d}, -{0x0069, 0x0308, 0x00ef}, -{0x0069, 0x0309, 0x1ec9}, -{0x0069, 0x030c, 0x01d0}, -{0x0069, 0x030f, 0x0209}, -{0x0069, 0x0311, 0x020b}, -{0x0069, 0x0323, 0x1ecb}, -{0x0069, 0x0328, 0x012f}, -{0x0069, 0x0330, 0x1e2d}, -{0x006a, 0x0302, 0x0135}, -{0x006a, 0x030c, 0x01f0}, -{0x006b, 0x0301, 0x1e31}, -{0x006b, 0x030c, 0x01e9}, -{0x006b, 0x0323, 0x1e33}, -{0x006b, 0x0327, 0x0137}, -{0x006b, 0x0331, 0x1e35}, -{0x006c, 0x0301, 0x013a}, -{0x006c, 0x030c, 0x013e}, -{0x006c, 0x0323, 0x1e37}, -{0x006c, 0x0327, 0x013c}, -{0x006c, 0x032d, 0x1e3d}, -{0x006c, 0x0331, 0x1e3b}, -{0x006d, 0x0301, 0x1e3f}, -{0x006d, 0x0307, 0x1e41}, -{0x006d, 0x0323, 0x1e43}, -{0x006e, 0x0300, 0x01f9}, -{0x006e, 0x0301, 0x0144}, -{0x006e, 0x0303, 0x00f1}, -{0x006e, 0x0307, 0x1e45}, -{0x006e, 0x030c, 0x0148}, -{0x006e, 0x0323, 0x1e47}, -{0x006e, 0x0327, 0x0146}, -{0x006e, 0x032d, 0x1e4b}, -{0x006e, 0x0331, 0x1e49}, -{0x006f, 0x0300, 0x00f2}, -{0x006f, 0x0301, 0x00f3}, -{0x006f, 0x0302, 0x00f4}, -{0x006f, 0x0303, 0x00f5}, -{0x006f, 0x0304, 0x014d}, -{0x006f, 0x0306, 0x014f}, -{0x006f, 0x0307, 0x022f}, -{0x006f, 0x0308, 0x00f6}, -{0x006f, 0x0309, 0x1ecf}, -{0x006f, 0x030b, 0x0151}, -{0x006f, 0x030c, 0x01d2}, -{0x006f, 0x030f, 0x020d}, -{0x006f, 0x0311, 0x020f}, -{0x006f, 0x031b, 0x01a1}, -{0x006f, 0x0323, 0x1ecd}, -{0x006f, 0x0328, 0x01eb}, -{0x0070, 0x0301, 0x1e55}, -{0x0070, 0x0307, 0x1e57}, -{0x0072, 0x0301, 0x0155}, -{0x0072, 0x0307, 0x1e59}, -{0x0072, 0x030c, 0x0159}, -{0x0072, 0x030f, 0x0211}, -{0x0072, 0x0311, 0x0213}, -{0x0072, 0x0323, 0x1e5b}, -{0x0072, 0x0327, 0x0157}, -{0x0072, 0x0331, 0x1e5f}, -{0x0073, 0x0301, 0x015b}, -{0x0073, 0x0302, 0x015d}, -{0x0073, 0x0307, 0x1e61}, -{0x0073, 0x030c, 0x0161}, -{0x0073, 0x0323, 0x1e63}, -{0x0073, 0x0326, 0x0219}, -{0x0073, 0x0327, 0x015f}, -{0x0074, 0x0307, 0x1e6b}, -{0x0074, 0x0308, 0x1e97}, -{0x0074, 0x030c, 0x0165}, -{0x0074, 0x0323, 0x1e6d}, -{0x0074, 0x0326, 0x021b}, -{0x0074, 0x0327, 0x0163}, -{0x0074, 0x032d, 0x1e71}, -{0x0074, 0x0331, 0x1e6f}, -{0x0075, 0x0300, 0x00f9}, -{0x0075, 0x0301, 0x00fa}, -{0x0075, 0x0302, 0x00fb}, -{0x0075, 0x0303, 0x0169}, -{0x0075, 0x0304, 0x016b}, -{0x0075, 0x0306, 0x016d}, -{0x0075, 0x0308, 0x00fc}, -{0x0075, 0x0309, 0x1ee7}, -{0x0075, 0x030a, 0x016f}, -{0x0075, 0x030b, 0x0171}, -{0x0075, 0x030c, 0x01d4}, -{0x0075, 0x030f, 0x0215}, -{0x0075, 0x0311, 0x0217}, -{0x0075, 0x031b, 0x01b0}, -{0x0075, 0x0323, 0x1ee5}, -{0x0075, 0x0324, 0x1e73}, -{0x0075, 0x0328, 0x0173}, -{0x0075, 0x032d, 0x1e77}, -{0x0075, 0x0330, 0x1e75}, -{0x0076, 0x0303, 0x1e7d}, -{0x0076, 0x0323, 0x1e7f}, -{0x0077, 0x0300, 0x1e81}, -{0x0077, 0x0301, 0x1e83}, -{0x0077, 0x0302, 0x0175}, -{0x0077, 0x0307, 0x1e87}, -{0x0077, 0x0308, 0x1e85}, -{0x0077, 0x030a, 0x1e98}, -{0x0077, 0x0323, 0x1e89}, -{0x0078, 0x0307, 0x1e8b}, -{0x0078, 0x0308, 0x1e8d}, -{0x0079, 0x0300, 0x1ef3}, -{0x0079, 0x0301, 0x00fd}, -{0x0079, 0x0302, 0x0177}, -{0x0079, 0x0303, 0x1ef9}, -{0x0079, 0x0304, 0x0233}, -{0x0079, 0x0307, 0x1e8f}, -{0x0079, 0x0308, 0x00ff}, -{0x0079, 0x0309, 0x1ef7}, -{0x0079, 0x030a, 0x1e99}, -{0x0079, 0x0323, 0x1ef5}, -{0x007a, 0x0301, 0x017a}, -{0x007a, 0x0302, 0x1e91}, -{0x007a, 0x0307, 0x017c}, -{0x007a, 0x030c, 0x017e}, -{0x007a, 0x0323, 0x1e93}, -{0x007a, 0x0331, 0x1e95}, -{0x00a8, 0x0300, 0x1fed}, -{0x00a8, 0x0301, 0x0385}, -{0x00a8, 0x0342, 0x1fc1}, -{0x00c2, 0x0300, 0x1ea6}, -{0x00c2, 0x0301, 0x1ea4}, -{0x00c2, 0x0303, 0x1eaa}, -{0x00c2, 0x0309, 0x1ea8}, -{0x00c4, 0x0304, 0x01de}, -{0x00c5, 0x0301, 0x01fa}, -{0x00c6, 0x0301, 0x01fc}, -{0x00c6, 0x0304, 0x01e2}, -{0x00c7, 0x0301, 0x1e08}, -{0x00ca, 0x0300, 0x1ec0}, -{0x00ca, 0x0301, 0x1ebe}, -{0x00ca, 0x0303, 0x1ec4}, -{0x00ca, 0x0309, 0x1ec2}, -{0x00cf, 0x0301, 0x1e2e}, -{0x00d4, 0x0300, 0x1ed2}, -{0x00d4, 0x0301, 0x1ed0}, -{0x00d4, 0x0303, 0x1ed6}, -{0x00d4, 0x0309, 0x1ed4}, -{0x00d5, 0x0301, 0x1e4c}, -{0x00d5, 0x0304, 0x022c}, -{0x00d5, 0x0308, 0x1e4e}, -{0x00d6, 0x0304, 0x022a}, -{0x00d8, 0x0301, 0x01fe}, -{0x00dc, 0x0300, 0x01db}, -{0x00dc, 0x0301, 0x01d7}, -{0x00dc, 0x0304, 0x01d5}, -{0x00dc, 0x030c, 0x01d9}, -{0x00e2, 0x0300, 0x1ea7}, -{0x00e2, 0x0301, 0x1ea5}, -{0x00e2, 0x0303, 0x1eab}, -{0x00e2, 0x0309, 0x1ea9}, -{0x00e4, 0x0304, 0x01df}, -{0x00e5, 0x0301, 0x01fb}, -{0x00e6, 0x0301, 0x01fd}, -{0x00e6, 0x0304, 0x01e3}, -{0x00e7, 0x0301, 0x1e09}, -{0x00ea, 0x0300, 0x1ec1}, -{0x00ea, 0x0301, 0x1ebf}, -{0x00ea, 0x0303, 0x1ec5}, -{0x00ea, 0x0309, 0x1ec3}, -{0x00ef, 0x0301, 0x1e2f}, -{0x00f4, 0x0300, 0x1ed3}, -{0x00f4, 0x0301, 0x1ed1}, -{0x00f4, 0x0303, 0x1ed7}, -{0x00f4, 0x0309, 0x1ed5}, -{0x00f5, 0x0301, 0x1e4d}, -{0x00f5, 0x0304, 0x022d}, -{0x00f5, 0x0308, 0x1e4f}, -{0x00f6, 0x0304, 0x022b}, -{0x00f8, 0x0301, 0x01ff}, -{0x00fc, 0x0300, 0x01dc}, -{0x00fc, 0x0301, 0x01d8}, -{0x00fc, 0x0304, 0x01d6}, -{0x00fc, 0x030c, 0x01da}, -{0x0102, 0x0300, 0x1eb0}, -{0x0102, 0x0301, 0x1eae}, -{0x0102, 0x0303, 0x1eb4}, -{0x0102, 0x0309, 0x1eb2}, -{0x0103, 0x0300, 0x1eb1}, -{0x0103, 0x0301, 0x1eaf}, -{0x0103, 0x0303, 0x1eb5}, -{0x0103, 0x0309, 0x1eb3}, -{0x0112, 0x0300, 0x1e14}, -{0x0112, 0x0301, 0x1e16}, -{0x0113, 0x0300, 0x1e15}, -{0x0113, 0x0301, 0x1e17}, -{0x014c, 0x0300, 0x1e50}, -{0x014c, 0x0301, 0x1e52}, -{0x014d, 0x0300, 0x1e51}, -{0x014d, 0x0301, 0x1e53}, -{0x015a, 0x0307, 0x1e64}, -{0x015b, 0x0307, 0x1e65}, -{0x0160, 0x0307, 0x1e66}, -{0x0161, 0x0307, 0x1e67}, -{0x0168, 0x0301, 0x1e78}, -{0x0169, 0x0301, 0x1e79}, -{0x016a, 0x0308, 0x1e7a}, -{0x016b, 0x0308, 0x1e7b}, -{0x017f, 0x0307, 0x1e9b}, -{0x01a0, 0x0300, 0x1edc}, -{0x01a0, 0x0301, 0x1eda}, -{0x01a0, 0x0303, 0x1ee0}, -{0x01a0, 0x0309, 0x1ede}, -{0x01a0, 0x0323, 0x1ee2}, -{0x01a1, 0x0300, 0x1edd}, -{0x01a1, 0x0301, 0x1edb}, -{0x01a1, 0x0303, 0x1ee1}, -{0x01a1, 0x0309, 0x1edf}, -{0x01a1, 0x0323, 0x1ee3}, -{0x01af, 0x0300, 0x1eea}, -{0x01af, 0x0301, 0x1ee8}, -{0x01af, 0x0303, 0x1eee}, -{0x01af, 0x0309, 0x1eec}, -{0x01af, 0x0323, 0x1ef0}, -{0x01b0, 0x0300, 0x1eeb}, -{0x01b0, 0x0301, 0x1ee9}, -{0x01b0, 0x0303, 0x1eef}, -{0x01b0, 0x0309, 0x1eed}, -{0x01b0, 0x0323, 0x1ef1}, -{0x01b7, 0x030c, 0x01ee}, -{0x01ea, 0x0304, 0x01ec}, -{0x01eb, 0x0304, 0x01ed}, -{0x0226, 0x0304, 0x01e0}, -{0x0227, 0x0304, 0x01e1}, -{0x0228, 0x0306, 0x1e1c}, -{0x0229, 0x0306, 0x1e1d}, -{0x022e, 0x0304, 0x0230}, -{0x022f, 0x0304, 0x0231}, -{0x0292, 0x030c, 0x01ef}, -{0x0391, 0x0300, 0x1fba}, -{0x0391, 0x0301, 0x0386}, -{0x0391, 0x0304, 0x1fb9}, -{0x0391, 0x0306, 0x1fb8}, -{0x0391, 0x0313, 0x1f08}, -{0x0391, 0x0314, 0x1f09}, -{0x0391, 0x0345, 0x1fbc}, -{0x0395, 0x0300, 0x1fc8}, -{0x0395, 0x0301, 0x0388}, -{0x0395, 0x0313, 0x1f18}, -{0x0395, 0x0314, 0x1f19}, -{0x0397, 0x0300, 0x1fca}, -{0x0397, 0x0301, 0x0389}, -{0x0397, 0x0313, 0x1f28}, -{0x0397, 0x0314, 0x1f29}, -{0x0397, 0x0345, 0x1fcc}, -{0x0399, 0x0300, 0x1fda}, -{0x0399, 0x0301, 0x038a}, -{0x0399, 0x0304, 0x1fd9}, -{0x0399, 0x0306, 0x1fd8}, -{0x0399, 0x0308, 0x03aa}, -{0x0399, 0x0313, 0x1f38}, -{0x0399, 0x0314, 0x1f39}, -{0x039f, 0x0300, 0x1ff8}, -{0x039f, 0x0301, 0x038c}, -{0x039f, 0x0313, 0x1f48}, -{0x039f, 0x0314, 0x1f49}, -{0x03a1, 0x0314, 0x1fec}, -{0x03a5, 0x0300, 0x1fea}, -{0x03a5, 0x0301, 0x038e}, -{0x03a5, 0x0304, 0x1fe9}, -{0x03a5, 0x0306, 0x1fe8}, -{0x03a5, 0x0308, 0x03ab}, -{0x03a5, 0x0314, 0x1f59}, -{0x03a9, 0x0300, 0x1ffa}, -{0x03a9, 0x0301, 0x038f}, -{0x03a9, 0x0313, 0x1f68}, -{0x03a9, 0x0314, 0x1f69}, -{0x03a9, 0x0345, 0x1ffc}, -{0x03ac, 0x0345, 0x1fb4}, -{0x03ae, 0x0345, 0x1fc4}, -{0x03b1, 0x0300, 0x1f70}, -{0x03b1, 0x0301, 0x03ac}, -{0x03b1, 0x0304, 0x1fb1}, -{0x03b1, 0x0306, 0x1fb0}, -{0x03b1, 0x0313, 0x1f00}, -{0x03b1, 0x0314, 0x1f01}, -{0x03b1, 0x0342, 0x1fb6}, -{0x03b1, 0x0345, 0x1fb3}, -{0x03b5, 0x0300, 0x1f72}, -{0x03b5, 0x0301, 0x03ad}, -{0x03b5, 0x0313, 0x1f10}, -{0x03b5, 0x0314, 0x1f11}, -{0x03b7, 0x0300, 0x1f74}, -{0x03b7, 0x0301, 0x03ae}, -{0x03b7, 0x0313, 0x1f20}, -{0x03b7, 0x0314, 0x1f21}, -{0x03b7, 0x0342, 0x1fc6}, -{0x03b7, 0x0345, 0x1fc3}, -{0x03b9, 0x0300, 0x1f76}, -{0x03b9, 0x0301, 0x03af}, -{0x03b9, 0x0304, 0x1fd1}, -{0x03b9, 0x0306, 0x1fd0}, -{0x03b9, 0x0308, 0x03ca}, -{0x03b9, 0x0313, 0x1f30}, -{0x03b9, 0x0314, 0x1f31}, -{0x03b9, 0x0342, 0x1fd6}, -{0x03bf, 0x0300, 0x1f78}, -{0x03bf, 0x0301, 0x03cc}, -{0x03bf, 0x0313, 0x1f40}, -{0x03bf, 0x0314, 0x1f41}, -{0x03c1, 0x0313, 0x1fe4}, -{0x03c1, 0x0314, 0x1fe5}, -{0x03c5, 0x0300, 0x1f7a}, -{0x03c5, 0x0301, 0x03cd}, -{0x03c5, 0x0304, 0x1fe1}, -{0x03c5, 0x0306, 0x1fe0}, -{0x03c5, 0x0308, 0x03cb}, -{0x03c5, 0x0313, 0x1f50}, -{0x03c5, 0x0314, 0x1f51}, -{0x03c5, 0x0342, 0x1fe6}, -{0x03c9, 0x0300, 0x1f7c}, -{0x03c9, 0x0301, 0x03ce}, -{0x03c9, 0x0313, 0x1f60}, -{0x03c9, 0x0314, 0x1f61}, -{0x03c9, 0x0342, 0x1ff6}, -{0x03c9, 0x0345, 0x1ff3}, -{0x03ca, 0x0300, 0x1fd2}, -{0x03ca, 0x0301, 0x0390}, -{0x03ca, 0x0342, 0x1fd7}, -{0x03cb, 0x0300, 0x1fe2}, -{0x03cb, 0x0301, 0x03b0}, -{0x03cb, 0x0342, 0x1fe7}, -{0x03ce, 0x0345, 0x1ff4}, -{0x03d2, 0x0301, 0x03d3}, -{0x03d2, 0x0308, 0x03d4}, -{0x0406, 0x0308, 0x0407}, -{0x0410, 0x0306, 0x04d0}, -{0x0410, 0x0308, 0x04d2}, -{0x0413, 0x0301, 0x0403}, -{0x0415, 0x0300, 0x0400}, -{0x0415, 0x0306, 0x04d6}, -{0x0415, 0x0308, 0x0401}, -{0x0416, 0x0306, 0x04c1}, -{0x0416, 0x0308, 0x04dc}, -{0x0417, 0x0308, 0x04de}, -{0x0418, 0x0300, 0x040d}, -{0x0418, 0x0304, 0x04e2}, -{0x0418, 0x0306, 0x0419}, -{0x0418, 0x0308, 0x04e4}, -{0x041a, 0x0301, 0x040c}, -{0x041e, 0x0308, 0x04e6}, -{0x0423, 0x0304, 0x04ee}, -{0x0423, 0x0306, 0x040e}, -{0x0423, 0x0308, 0x04f0}, -{0x0423, 0x030b, 0x04f2}, -{0x0427, 0x0308, 0x04f4}, -{0x042b, 0x0308, 0x04f8}, -{0x042d, 0x0308, 0x04ec}, -{0x0430, 0x0306, 0x04d1}, -{0x0430, 0x0308, 0x04d3}, -{0x0433, 0x0301, 0x0453}, -{0x0435, 0x0300, 0x0450}, -{0x0435, 0x0306, 0x04d7}, -{0x0435, 0x0308, 0x0451}, -{0x0436, 0x0306, 0x04c2}, -{0x0436, 0x0308, 0x04dd}, -{0x0437, 0x0308, 0x04df}, -{0x0438, 0x0300, 0x045d}, -{0x0438, 0x0304, 0x04e3}, -{0x0438, 0x0306, 0x0439}, -{0x0438, 0x0308, 0x04e5}, -{0x043a, 0x0301, 0x045c}, -{0x043e, 0x0308, 0x04e7}, -{0x0443, 0x0304, 0x04ef}, -{0x0443, 0x0306, 0x045e}, -{0x0443, 0x0308, 0x04f1}, -{0x0443, 0x030b, 0x04f3}, -{0x0447, 0x0308, 0x04f5}, -{0x044b, 0x0308, 0x04f9}, -{0x044d, 0x0308, 0x04ed}, -{0x0456, 0x0308, 0x0457}, -{0x0474, 0x030f, 0x0476}, -{0x0475, 0x030f, 0x0477}, -{0x04d8, 0x0308, 0x04da}, -{0x04d9, 0x0308, 0x04db}, -{0x04e8, 0x0308, 0x04ea}, -{0x04e9, 0x0308, 0x04eb}, -{0x0627, 0x0653, 0x0622}, -{0x0627, 0x0654, 0x0623}, -{0x0627, 0x0655, 0x0625}, -{0x0648, 0x0654, 0x0624}, -{0x064a, 0x0654, 0x0626}, -{0x06c1, 0x0654, 0x06c2}, -{0x06d2, 0x0654, 0x06d3}, -{0x06d5, 0x0654, 0x06c0}, -{0x0928, 0x093c, 0x0929}, -{0x0930, 0x093c, 0x0931}, -{0x0933, 0x093c, 0x0934}, -{0x09c7, 0x09be, 0x09cb}, -{0x09c7, 0x09d7, 0x09cc}, -{0x0b47, 0x0b3e, 0x0b4b}, -{0x0b47, 0x0b56, 0x0b48}, -{0x0b47, 0x0b57, 0x0b4c}, -{0x0b92, 0x0bd7, 0x0b94}, -{0x0bc6, 0x0bbe, 0x0bca}, -{0x0bc6, 0x0bd7, 0x0bcc}, -{0x0bc7, 0x0bbe, 0x0bcb}, -{0x0c46, 0x0c56, 0x0c48}, -{0x0cbf, 0x0cd5, 0x0cc0}, -{0x0cc6, 0x0cc2, 0x0cca}, -{0x0cc6, 0x0cd5, 0x0cc7}, -{0x0cc6, 0x0cd6, 0x0cc8}, -{0x0cca, 0x0cd5, 0x0ccb}, -{0x0d46, 0x0d3e, 0x0d4a}, -{0x0d46, 0x0d57, 0x0d4c}, -{0x0d47, 0x0d3e, 0x0d4b}, -{0x0dd9, 0x0dca, 0x0dda}, -{0x0dd9, 0x0dcf, 0x0ddc}, -{0x0dd9, 0x0ddf, 0x0dde}, -{0x0ddc, 0x0dca, 0x0ddd}, -{0x1025, 0x102e, 0x1026}, -{0x1b05, 0x1b35, 0x1b06}, -{0x1b07, 0x1b35, 0x1b08}, -{0x1b09, 0x1b35, 0x1b0a}, -{0x1b0b, 0x1b35, 0x1b0c}, -{0x1b0d, 0x1b35, 0x1b0e}, -{0x1b11, 0x1b35, 0x1b12}, -{0x1b3a, 0x1b35, 0x1b3b}, -{0x1b3c, 0x1b35, 0x1b3d}, -{0x1b3e, 0x1b35, 0x1b40}, -{0x1b3f, 0x1b35, 0x1b41}, -{0x1b42, 0x1b35, 0x1b43}, -{0x1e36, 0x0304, 0x1e38}, -{0x1e37, 0x0304, 0x1e39}, -{0x1e5a, 0x0304, 0x1e5c}, -{0x1e5b, 0x0304, 0x1e5d}, -{0x1e62, 0x0307, 0x1e68}, -{0x1e63, 0x0307, 0x1e69}, -{0x1ea0, 0x0302, 0x1eac}, -{0x1ea0, 0x0306, 0x1eb6}, -{0x1ea1, 0x0302, 0x1ead}, -{0x1ea1, 0x0306, 0x1eb7}, -{0x1eb8, 0x0302, 0x1ec6}, -{0x1eb9, 0x0302, 0x1ec7}, -{0x1ecc, 0x0302, 0x1ed8}, -{0x1ecd, 0x0302, 0x1ed9}, -{0x1f00, 0x0300, 0x1f02}, -{0x1f00, 0x0301, 0x1f04}, -{0x1f00, 0x0342, 0x1f06}, -{0x1f00, 0x0345, 0x1f80}, -{0x1f01, 0x0300, 0x1f03}, -{0x1f01, 0x0301, 0x1f05}, -{0x1f01, 0x0342, 0x1f07}, -{0x1f01, 0x0345, 0x1f81}, -{0x1f02, 0x0345, 0x1f82}, -{0x1f03, 0x0345, 0x1f83}, -{0x1f04, 0x0345, 0x1f84}, -{0x1f05, 0x0345, 0x1f85}, -{0x1f06, 0x0345, 0x1f86}, -{0x1f07, 0x0345, 0x1f87}, -{0x1f08, 0x0300, 0x1f0a}, -{0x1f08, 0x0301, 0x1f0c}, -{0x1f08, 0x0342, 0x1f0e}, -{0x1f08, 0x0345, 0x1f88}, -{0x1f09, 0x0300, 0x1f0b}, -{0x1f09, 0x0301, 0x1f0d}, -{0x1f09, 0x0342, 0x1f0f}, -{0x1f09, 0x0345, 0x1f89}, -{0x1f0a, 0x0345, 0x1f8a}, -{0x1f0b, 0x0345, 0x1f8b}, -{0x1f0c, 0x0345, 0x1f8c}, -{0x1f0d, 0x0345, 0x1f8d}, -{0x1f0e, 0x0345, 0x1f8e}, -{0x1f0f, 0x0345, 0x1f8f}, -{0x1f10, 0x0300, 0x1f12}, -{0x1f10, 0x0301, 0x1f14}, -{0x1f11, 0x0300, 0x1f13}, -{0x1f11, 0x0301, 0x1f15}, -{0x1f18, 0x0300, 0x1f1a}, -{0x1f18, 0x0301, 0x1f1c}, -{0x1f19, 0x0300, 0x1f1b}, -{0x1f19, 0x0301, 0x1f1d}, -{0x1f20, 0x0300, 0x1f22}, -{0x1f20, 0x0301, 0x1f24}, -{0x1f20, 0x0342, 0x1f26}, -{0x1f20, 0x0345, 0x1f90}, -{0x1f21, 0x0300, 0x1f23}, -{0x1f21, 0x0301, 0x1f25}, -{0x1f21, 0x0342, 0x1f27}, -{0x1f21, 0x0345, 0x1f91}, -{0x1f22, 0x0345, 0x1f92}, -{0x1f23, 0x0345, 0x1f93}, -{0x1f24, 0x0345, 0x1f94}, -{0x1f25, 0x0345, 0x1f95}, -{0x1f26, 0x0345, 0x1f96}, -{0x1f27, 0x0345, 0x1f97}, -{0x1f28, 0x0300, 0x1f2a}, -{0x1f28, 0x0301, 0x1f2c}, -{0x1f28, 0x0342, 0x1f2e}, -{0x1f28, 0x0345, 0x1f98}, -{0x1f29, 0x0300, 0x1f2b}, -{0x1f29, 0x0301, 0x1f2d}, -{0x1f29, 0x0342, 0x1f2f}, -{0x1f29, 0x0345, 0x1f99}, -{0x1f2a, 0x0345, 0x1f9a}, -{0x1f2b, 0x0345, 0x1f9b}, -{0x1f2c, 0x0345, 0x1f9c}, -{0x1f2d, 0x0345, 0x1f9d}, -{0x1f2e, 0x0345, 0x1f9e}, -{0x1f2f, 0x0345, 0x1f9f}, -{0x1f30, 0x0300, 0x1f32}, -{0x1f30, 0x0301, 0x1f34}, -{0x1f30, 0x0342, 0x1f36}, -{0x1f31, 0x0300, 0x1f33}, -{0x1f31, 0x0301, 0x1f35}, -{0x1f31, 0x0342, 0x1f37}, -{0x1f38, 0x0300, 0x1f3a}, -{0x1f38, 0x0301, 0x1f3c}, -{0x1f38, 0x0342, 0x1f3e}, -{0x1f39, 0x0300, 0x1f3b}, -{0x1f39, 0x0301, 0x1f3d}, -{0x1f39, 0x0342, 0x1f3f}, -{0x1f40, 0x0300, 0x1f42}, -{0x1f40, 0x0301, 0x1f44}, -{0x1f41, 0x0300, 0x1f43}, -{0x1f41, 0x0301, 0x1f45}, -{0x1f48, 0x0300, 0x1f4a}, -{0x1f48, 0x0301, 0x1f4c}, -{0x1f49, 0x0300, 0x1f4b}, -{0x1f49, 0x0301, 0x1f4d}, -{0x1f50, 0x0300, 0x1f52}, -{0x1f50, 0x0301, 0x1f54}, -{0x1f50, 0x0342, 0x1f56}, -{0x1f51, 0x0300, 0x1f53}, -{0x1f51, 0x0301, 0x1f55}, -{0x1f51, 0x0342, 0x1f57}, -{0x1f59, 0x0300, 0x1f5b}, -{0x1f59, 0x0301, 0x1f5d}, -{0x1f59, 0x0342, 0x1f5f}, -{0x1f60, 0x0300, 0x1f62}, -{0x1f60, 0x0301, 0x1f64}, -{0x1f60, 0x0342, 0x1f66}, -{0x1f60, 0x0345, 0x1fa0}, -{0x1f61, 0x0300, 0x1f63}, -{0x1f61, 0x0301, 0x1f65}, -{0x1f61, 0x0342, 0x1f67}, -{0x1f61, 0x0345, 0x1fa1}, -{0x1f62, 0x0345, 0x1fa2}, -{0x1f63, 0x0345, 0x1fa3}, -{0x1f64, 0x0345, 0x1fa4}, -{0x1f65, 0x0345, 0x1fa5}, -{0x1f66, 0x0345, 0x1fa6}, -{0x1f67, 0x0345, 0x1fa7}, -{0x1f68, 0x0300, 0x1f6a}, -{0x1f68, 0x0301, 0x1f6c}, -{0x1f68, 0x0342, 0x1f6e}, -{0x1f68, 0x0345, 0x1fa8}, -{0x1f69, 0x0300, 0x1f6b}, -{0x1f69, 0x0301, 0x1f6d}, -{0x1f69, 0x0342, 0x1f6f}, -{0x1f69, 0x0345, 0x1fa9}, -{0x1f6a, 0x0345, 0x1faa}, -{0x1f6b, 0x0345, 0x1fab}, -{0x1f6c, 0x0345, 0x1fac}, -{0x1f6d, 0x0345, 0x1fad}, -{0x1f6e, 0x0345, 0x1fae}, -{0x1f6f, 0x0345, 0x1faf}, -{0x1f70, 0x0345, 0x1fb2}, -{0x1f74, 0x0345, 0x1fc2}, -{0x1f7c, 0x0345, 0x1ff2}, -{0x1fb6, 0x0345, 0x1fb7}, -{0x1fbf, 0x0300, 0x1fcd}, -{0x1fbf, 0x0301, 0x1fce}, -{0x1fbf, 0x0342, 0x1fcf}, -{0x1fc6, 0x0345, 0x1fc7}, -{0x1ff6, 0x0345, 0x1ff7}, -{0x1ffe, 0x0300, 0x1fdd}, -{0x1ffe, 0x0301, 0x1fde}, -{0x1ffe, 0x0342, 0x1fdf}, -{0x2190, 0x0338, 0x219a}, -{0x2192, 0x0338, 0x219b}, -{0x2194, 0x0338, 0x21ae}, -{0x21d0, 0x0338, 0x21cd}, -{0x21d2, 0x0338, 0x21cf}, -{0x21d4, 0x0338, 0x21ce}, -{0x2203, 0x0338, 0x2204}, -{0x2208, 0x0338, 0x2209}, -{0x220b, 0x0338, 0x220c}, -{0x2223, 0x0338, 0x2224}, -{0x2225, 0x0338, 0x2226}, -{0x223c, 0x0338, 0x2241}, -{0x2243, 0x0338, 0x2244}, -{0x2245, 0x0338, 0x2247}, -{0x2248, 0x0338, 0x2249}, -{0x224d, 0x0338, 0x226d}, -{0x2261, 0x0338, 0x2262}, -{0x2264, 0x0338, 0x2270}, -{0x2265, 0x0338, 0x2271}, -{0x2272, 0x0338, 0x2274}, -{0x2273, 0x0338, 0x2275}, -{0x2276, 0x0338, 0x2278}, -{0x2277, 0x0338, 0x2279}, -{0x227a, 0x0338, 0x2280}, -{0x227b, 0x0338, 0x2281}, -{0x227c, 0x0338, 0x22e0}, -{0x227d, 0x0338, 0x22e1}, -{0x2282, 0x0338, 0x2284}, -{0x2283, 0x0338, 0x2285}, -{0x2286, 0x0338, 0x2288}, -{0x2287, 0x0338, 0x2289}, -{0x2291, 0x0338, 0x22e2}, -{0x2292, 0x0338, 0x22e3}, -{0x22a2, 0x0338, 0x22ac}, -{0x22a8, 0x0338, 0x22ad}, -{0x22a9, 0x0338, 0x22ae}, -{0x22ab, 0x0338, 0x22af}, -{0x22b2, 0x0338, 0x22ea}, -{0x22b3, 0x0338, 0x22eb}, -{0x22b4, 0x0338, 0x22ec}, -{0x22b5, 0x0338, 0x22ed}, -{0x3046, 0x3099, 0x3094}, -{0x304b, 0x3099, 0x304c}, -{0x304d, 0x3099, 0x304e}, -{0x304f, 0x3099, 0x3050}, -{0x3051, 0x3099, 0x3052}, -{0x3053, 0x3099, 0x3054}, -{0x3055, 0x3099, 0x3056}, -{0x3057, 0x3099, 0x3058}, -{0x3059, 0x3099, 0x305a}, -{0x305b, 0x3099, 0x305c}, -{0x305d, 0x3099, 0x305e}, -{0x305f, 0x3099, 0x3060}, -{0x3061, 0x3099, 0x3062}, -{0x3064, 0x3099, 0x3065}, -{0x3066, 0x3099, 0x3067}, -{0x3068, 0x3099, 0x3069}, -{0x306f, 0x3099, 0x3070}, -{0x306f, 0x309a, 0x3071}, -{0x3072, 0x3099, 0x3073}, -{0x3072, 0x309a, 0x3074}, -{0x3075, 0x3099, 0x3076}, -{0x3075, 0x309a, 0x3077}, -{0x3078, 0x3099, 0x3079}, -{0x3078, 0x309a, 0x307a}, -{0x307b, 0x3099, 0x307c}, -{0x307b, 0x309a, 0x307d}, -{0x309d, 0x3099, 0x309e}, -{0x30a6, 0x3099, 0x30f4}, -{0x30ab, 0x3099, 0x30ac}, -{0x30ad, 0x3099, 0x30ae}, -{0x30af, 0x3099, 0x30b0}, -{0x30b1, 0x3099, 0x30b2}, -{0x30b3, 0x3099, 0x30b4}, -{0x30b5, 0x3099, 0x30b6}, -{0x30b7, 0x3099, 0x30b8}, -{0x30b9, 0x3099, 0x30ba}, -{0x30bb, 0x3099, 0x30bc}, -{0x30bd, 0x3099, 0x30be}, -{0x30bf, 0x3099, 0x30c0}, -{0x30c1, 0x3099, 0x30c2}, -{0x30c4, 0x3099, 0x30c5}, -{0x30c6, 0x3099, 0x30c7}, -{0x30c8, 0x3099, 0x30c9}, -{0x30cf, 0x3099, 0x30d0}, -{0x30cf, 0x309a, 0x30d1}, -{0x30d2, 0x3099, 0x30d3}, -{0x30d2, 0x309a, 0x30d4}, -{0x30d5, 0x3099, 0x30d6}, -{0x30d5, 0x309a, 0x30d7}, -{0x30d8, 0x3099, 0x30d9}, -{0x30d8, 0x309a, 0x30da}, -{0x30db, 0x3099, 0x30dc}, -{0x30db, 0x309a, 0x30dd}, -{0x30ef, 0x3099, 0x30f7}, -{0x30f0, 0x3099, 0x30f8}, -{0x30f1, 0x3099, 0x30f9}, -{0x30f2, 0x3099, 0x30fa}, -{0x30fd, 0x3099, 0x30fe}, -{0x11099, 0x110ba, 0x1109a}, -{0x1109b, 0x110ba, 0x1109c}, -{0x110a5, 0x110ba, 0x110ab}, -{0x11131, 0x11127, 0x1112e}, -{0x11132, 0x11127, 0x1112f}, -{0x11347, 0x1133e, 0x1134b}, -{0x11347, 0x11357, 0x1134c}, -{0x114b9, 0x114b0, 0x114bc}, -{0x114b9, 0x114ba, 0x114bb}, -{0x114b9, 0x114bd, 0x114be}, -{0x115b8, 0x115af, 0x115ba}, -{0x115b9, 0x115af, 0x115bb}, -{0x11935, 0x11930, 0x11938}, diff --git a/unicode/canonical_decomp.h b/unicode/canonical_decomp.h deleted file mode 100644 index 1544e738b..000000000 --- a/unicode/canonical_decomp.h +++ /dev/null @@ -1,2071 +0,0 @@ -/* - * Autogenerated by read_ucd.py from The Unicode Standard 15.0.0 - * - * List the canonical decomposition of every Unicode character that has - * one. This consists of up to two characters, but those may need - * decomposition in turn. - * - * Used by utils/unicode-norm.c. - */ - -{0x00c0, 0x0041, 0x0300}, -{0x00c1, 0x0041, 0x0301}, -{0x00c2, 0x0041, 0x0302}, -{0x00c3, 0x0041, 0x0303}, -{0x00c4, 0x0041, 0x0308}, -{0x00c5, 0x0041, 0x030a}, -{0x00c7, 0x0043, 0x0327}, -{0x00c8, 0x0045, 0x0300}, -{0x00c9, 0x0045, 0x0301}, -{0x00ca, 0x0045, 0x0302}, -{0x00cb, 0x0045, 0x0308}, -{0x00cc, 0x0049, 0x0300}, -{0x00cd, 0x0049, 0x0301}, -{0x00ce, 0x0049, 0x0302}, -{0x00cf, 0x0049, 0x0308}, -{0x00d1, 0x004e, 0x0303}, -{0x00d2, 0x004f, 0x0300}, -{0x00d3, 0x004f, 0x0301}, -{0x00d4, 0x004f, 0x0302}, -{0x00d5, 0x004f, 0x0303}, -{0x00d6, 0x004f, 0x0308}, -{0x00d9, 0x0055, 0x0300}, -{0x00da, 0x0055, 0x0301}, -{0x00db, 0x0055, 0x0302}, -{0x00dc, 0x0055, 0x0308}, -{0x00dd, 0x0059, 0x0301}, -{0x00e0, 0x0061, 0x0300}, -{0x00e1, 0x0061, 0x0301}, -{0x00e2, 0x0061, 0x0302}, -{0x00e3, 0x0061, 0x0303}, -{0x00e4, 0x0061, 0x0308}, -{0x00e5, 0x0061, 0x030a}, -{0x00e7, 0x0063, 0x0327}, -{0x00e8, 0x0065, 0x0300}, -{0x00e9, 0x0065, 0x0301}, -{0x00ea, 0x0065, 0x0302}, -{0x00eb, 0x0065, 0x0308}, -{0x00ec, 0x0069, 0x0300}, -{0x00ed, 0x0069, 0x0301}, -{0x00ee, 0x0069, 0x0302}, -{0x00ef, 0x0069, 0x0308}, -{0x00f1, 0x006e, 0x0303}, -{0x00f2, 0x006f, 0x0300}, -{0x00f3, 0x006f, 0x0301}, -{0x00f4, 0x006f, 0x0302}, -{0x00f5, 0x006f, 0x0303}, -{0x00f6, 0x006f, 0x0308}, -{0x00f9, 0x0075, 0x0300}, -{0x00fa, 0x0075, 0x0301}, -{0x00fb, 0x0075, 0x0302}, -{0x00fc, 0x0075, 0x0308}, -{0x00fd, 0x0079, 0x0301}, -{0x00ff, 0x0079, 0x0308}, -{0x0100, 0x0041, 0x0304}, -{0x0101, 0x0061, 0x0304}, -{0x0102, 0x0041, 0x0306}, -{0x0103, 0x0061, 0x0306}, -{0x0104, 0x0041, 0x0328}, -{0x0105, 0x0061, 0x0328}, -{0x0106, 0x0043, 0x0301}, -{0x0107, 0x0063, 0x0301}, -{0x0108, 0x0043, 0x0302}, -{0x0109, 0x0063, 0x0302}, -{0x010a, 0x0043, 0x0307}, -{0x010b, 0x0063, 0x0307}, -{0x010c, 0x0043, 0x030c}, -{0x010d, 0x0063, 0x030c}, -{0x010e, 0x0044, 0x030c}, -{0x010f, 0x0064, 0x030c}, -{0x0112, 0x0045, 0x0304}, -{0x0113, 0x0065, 0x0304}, -{0x0114, 0x0045, 0x0306}, -{0x0115, 0x0065, 0x0306}, -{0x0116, 0x0045, 0x0307}, -{0x0117, 0x0065, 0x0307}, -{0x0118, 0x0045, 0x0328}, -{0x0119, 0x0065, 0x0328}, -{0x011a, 0x0045, 0x030c}, -{0x011b, 0x0065, 0x030c}, -{0x011c, 0x0047, 0x0302}, -{0x011d, 0x0067, 0x0302}, -{0x011e, 0x0047, 0x0306}, -{0x011f, 0x0067, 0x0306}, -{0x0120, 0x0047, 0x0307}, -{0x0121, 0x0067, 0x0307}, -{0x0122, 0x0047, 0x0327}, -{0x0123, 0x0067, 0x0327}, -{0x0124, 0x0048, 0x0302}, -{0x0125, 0x0068, 0x0302}, -{0x0128, 0x0049, 0x0303}, -{0x0129, 0x0069, 0x0303}, -{0x012a, 0x0049, 0x0304}, -{0x012b, 0x0069, 0x0304}, -{0x012c, 0x0049, 0x0306}, -{0x012d, 0x0069, 0x0306}, -{0x012e, 0x0049, 0x0328}, -{0x012f, 0x0069, 0x0328}, -{0x0130, 0x0049, 0x0307}, -{0x0134, 0x004a, 0x0302}, -{0x0135, 0x006a, 0x0302}, -{0x0136, 0x004b, 0x0327}, -{0x0137, 0x006b, 0x0327}, -{0x0139, 0x004c, 0x0301}, -{0x013a, 0x006c, 0x0301}, -{0x013b, 0x004c, 0x0327}, -{0x013c, 0x006c, 0x0327}, -{0x013d, 0x004c, 0x030c}, -{0x013e, 0x006c, 0x030c}, -{0x0143, 0x004e, 0x0301}, -{0x0144, 0x006e, 0x0301}, -{0x0145, 0x004e, 0x0327}, -{0x0146, 0x006e, 0x0327}, -{0x0147, 0x004e, 0x030c}, -{0x0148, 0x006e, 0x030c}, -{0x014c, 0x004f, 0x0304}, -{0x014d, 0x006f, 0x0304}, -{0x014e, 0x004f, 0x0306}, -{0x014f, 0x006f, 0x0306}, -{0x0150, 0x004f, 0x030b}, -{0x0151, 0x006f, 0x030b}, -{0x0154, 0x0052, 0x0301}, -{0x0155, 0x0072, 0x0301}, -{0x0156, 0x0052, 0x0327}, -{0x0157, 0x0072, 0x0327}, -{0x0158, 0x0052, 0x030c}, -{0x0159, 0x0072, 0x030c}, -{0x015a, 0x0053, 0x0301}, -{0x015b, 0x0073, 0x0301}, -{0x015c, 0x0053, 0x0302}, -{0x015d, 0x0073, 0x0302}, -{0x015e, 0x0053, 0x0327}, -{0x015f, 0x0073, 0x0327}, -{0x0160, 0x0053, 0x030c}, -{0x0161, 0x0073, 0x030c}, -{0x0162, 0x0054, 0x0327}, -{0x0163, 0x0074, 0x0327}, -{0x0164, 0x0054, 0x030c}, -{0x0165, 0x0074, 0x030c}, -{0x0168, 0x0055, 0x0303}, -{0x0169, 0x0075, 0x0303}, -{0x016a, 0x0055, 0x0304}, -{0x016b, 0x0075, 0x0304}, -{0x016c, 0x0055, 0x0306}, -{0x016d, 0x0075, 0x0306}, -{0x016e, 0x0055, 0x030a}, -{0x016f, 0x0075, 0x030a}, -{0x0170, 0x0055, 0x030b}, -{0x0171, 0x0075, 0x030b}, -{0x0172, 0x0055, 0x0328}, -{0x0173, 0x0075, 0x0328}, -{0x0174, 0x0057, 0x0302}, -{0x0175, 0x0077, 0x0302}, -{0x0176, 0x0059, 0x0302}, -{0x0177, 0x0079, 0x0302}, -{0x0178, 0x0059, 0x0308}, -{0x0179, 0x005a, 0x0301}, -{0x017a, 0x007a, 0x0301}, -{0x017b, 0x005a, 0x0307}, -{0x017c, 0x007a, 0x0307}, -{0x017d, 0x005a, 0x030c}, -{0x017e, 0x007a, 0x030c}, -{0x01a0, 0x004f, 0x031b}, -{0x01a1, 0x006f, 0x031b}, -{0x01af, 0x0055, 0x031b}, -{0x01b0, 0x0075, 0x031b}, -{0x01cd, 0x0041, 0x030c}, -{0x01ce, 0x0061, 0x030c}, -{0x01cf, 0x0049, 0x030c}, -{0x01d0, 0x0069, 0x030c}, -{0x01d1, 0x004f, 0x030c}, -{0x01d2, 0x006f, 0x030c}, -{0x01d3, 0x0055, 0x030c}, -{0x01d4, 0x0075, 0x030c}, -{0x01d5, 0x00dc, 0x0304}, -{0x01d6, 0x00fc, 0x0304}, -{0x01d7, 0x00dc, 0x0301}, -{0x01d8, 0x00fc, 0x0301}, -{0x01d9, 0x00dc, 0x030c}, -{0x01da, 0x00fc, 0x030c}, -{0x01db, 0x00dc, 0x0300}, -{0x01dc, 0x00fc, 0x0300}, -{0x01de, 0x00c4, 0x0304}, -{0x01df, 0x00e4, 0x0304}, -{0x01e0, 0x0226, 0x0304}, -{0x01e1, 0x0227, 0x0304}, -{0x01e2, 0x00c6, 0x0304}, -{0x01e3, 0x00e6, 0x0304}, -{0x01e6, 0x0047, 0x030c}, -{0x01e7, 0x0067, 0x030c}, -{0x01e8, 0x004b, 0x030c}, -{0x01e9, 0x006b, 0x030c}, -{0x01ea, 0x004f, 0x0328}, -{0x01eb, 0x006f, 0x0328}, -{0x01ec, 0x01ea, 0x0304}, -{0x01ed, 0x01eb, 0x0304}, -{0x01ee, 0x01b7, 0x030c}, -{0x01ef, 0x0292, 0x030c}, -{0x01f0, 0x006a, 0x030c}, -{0x01f4, 0x0047, 0x0301}, -{0x01f5, 0x0067, 0x0301}, -{0x01f8, 0x004e, 0x0300}, -{0x01f9, 0x006e, 0x0300}, -{0x01fa, 0x00c5, 0x0301}, -{0x01fb, 0x00e5, 0x0301}, -{0x01fc, 0x00c6, 0x0301}, -{0x01fd, 0x00e6, 0x0301}, -{0x01fe, 0x00d8, 0x0301}, -{0x01ff, 0x00f8, 0x0301}, -{0x0200, 0x0041, 0x030f}, -{0x0201, 0x0061, 0x030f}, -{0x0202, 0x0041, 0x0311}, -{0x0203, 0x0061, 0x0311}, -{0x0204, 0x0045, 0x030f}, -{0x0205, 0x0065, 0x030f}, -{0x0206, 0x0045, 0x0311}, -{0x0207, 0x0065, 0x0311}, -{0x0208, 0x0049, 0x030f}, -{0x0209, 0x0069, 0x030f}, -{0x020a, 0x0049, 0x0311}, -{0x020b, 0x0069, 0x0311}, -{0x020c, 0x004f, 0x030f}, -{0x020d, 0x006f, 0x030f}, -{0x020e, 0x004f, 0x0311}, -{0x020f, 0x006f, 0x0311}, -{0x0210, 0x0052, 0x030f}, -{0x0211, 0x0072, 0x030f}, -{0x0212, 0x0052, 0x0311}, -{0x0213, 0x0072, 0x0311}, -{0x0214, 0x0055, 0x030f}, -{0x0215, 0x0075, 0x030f}, -{0x0216, 0x0055, 0x0311}, -{0x0217, 0x0075, 0x0311}, -{0x0218, 0x0053, 0x0326}, -{0x0219, 0x0073, 0x0326}, -{0x021a, 0x0054, 0x0326}, -{0x021b, 0x0074, 0x0326}, -{0x021e, 0x0048, 0x030c}, -{0x021f, 0x0068, 0x030c}, -{0x0226, 0x0041, 0x0307}, -{0x0227, 0x0061, 0x0307}, -{0x0228, 0x0045, 0x0327}, -{0x0229, 0x0065, 0x0327}, -{0x022a, 0x00d6, 0x0304}, -{0x022b, 0x00f6, 0x0304}, -{0x022c, 0x00d5, 0x0304}, -{0x022d, 0x00f5, 0x0304}, -{0x022e, 0x004f, 0x0307}, -{0x022f, 0x006f, 0x0307}, -{0x0230, 0x022e, 0x0304}, -{0x0231, 0x022f, 0x0304}, -{0x0232, 0x0059, 0x0304}, -{0x0233, 0x0079, 0x0304}, -{0x0340, 0x0300, 0}, -{0x0341, 0x0301, 0}, -{0x0343, 0x0313, 0}, -{0x0344, 0x0308, 0x0301}, -{0x0374, 0x02b9, 0}, -{0x037e, 0x003b, 0}, -{0x0385, 0x00a8, 0x0301}, -{0x0386, 0x0391, 0x0301}, -{0x0387, 0x00b7, 0}, -{0x0388, 0x0395, 0x0301}, -{0x0389, 0x0397, 0x0301}, -{0x038a, 0x0399, 0x0301}, -{0x038c, 0x039f, 0x0301}, -{0x038e, 0x03a5, 0x0301}, -{0x038f, 0x03a9, 0x0301}, -{0x0390, 0x03ca, 0x0301}, -{0x03aa, 0x0399, 0x0308}, -{0x03ab, 0x03a5, 0x0308}, -{0x03ac, 0x03b1, 0x0301}, -{0x03ad, 0x03b5, 0x0301}, -{0x03ae, 0x03b7, 0x0301}, -{0x03af, 0x03b9, 0x0301}, -{0x03b0, 0x03cb, 0x0301}, -{0x03ca, 0x03b9, 0x0308}, -{0x03cb, 0x03c5, 0x0308}, -{0x03cc, 0x03bf, 0x0301}, -{0x03cd, 0x03c5, 0x0301}, -{0x03ce, 0x03c9, 0x0301}, -{0x03d3, 0x03d2, 0x0301}, -{0x03d4, 0x03d2, 0x0308}, -{0x0400, 0x0415, 0x0300}, -{0x0401, 0x0415, 0x0308}, -{0x0403, 0x0413, 0x0301}, -{0x0407, 0x0406, 0x0308}, -{0x040c, 0x041a, 0x0301}, -{0x040d, 0x0418, 0x0300}, -{0x040e, 0x0423, 0x0306}, -{0x0419, 0x0418, 0x0306}, -{0x0439, 0x0438, 0x0306}, -{0x0450, 0x0435, 0x0300}, -{0x0451, 0x0435, 0x0308}, -{0x0453, 0x0433, 0x0301}, -{0x0457, 0x0456, 0x0308}, -{0x045c, 0x043a, 0x0301}, -{0x045d, 0x0438, 0x0300}, -{0x045e, 0x0443, 0x0306}, -{0x0476, 0x0474, 0x030f}, -{0x0477, 0x0475, 0x030f}, -{0x04c1, 0x0416, 0x0306}, -{0x04c2, 0x0436, 0x0306}, -{0x04d0, 0x0410, 0x0306}, -{0x04d1, 0x0430, 0x0306}, -{0x04d2, 0x0410, 0x0308}, -{0x04d3, 0x0430, 0x0308}, -{0x04d6, 0x0415, 0x0306}, -{0x04d7, 0x0435, 0x0306}, -{0x04da, 0x04d8, 0x0308}, -{0x04db, 0x04d9, 0x0308}, -{0x04dc, 0x0416, 0x0308}, -{0x04dd, 0x0436, 0x0308}, -{0x04de, 0x0417, 0x0308}, -{0x04df, 0x0437, 0x0308}, -{0x04e2, 0x0418, 0x0304}, -{0x04e3, 0x0438, 0x0304}, -{0x04e4, 0x0418, 0x0308}, -{0x04e5, 0x0438, 0x0308}, -{0x04e6, 0x041e, 0x0308}, -{0x04e7, 0x043e, 0x0308}, -{0x04ea, 0x04e8, 0x0308}, -{0x04eb, 0x04e9, 0x0308}, -{0x04ec, 0x042d, 0x0308}, -{0x04ed, 0x044d, 0x0308}, -{0x04ee, 0x0423, 0x0304}, -{0x04ef, 0x0443, 0x0304}, -{0x04f0, 0x0423, 0x0308}, -{0x04f1, 0x0443, 0x0308}, -{0x04f2, 0x0423, 0x030b}, -{0x04f3, 0x0443, 0x030b}, -{0x04f4, 0x0427, 0x0308}, -{0x04f5, 0x0447, 0x0308}, -{0x04f8, 0x042b, 0x0308}, -{0x04f9, 0x044b, 0x0308}, -{0x0622, 0x0627, 0x0653}, -{0x0623, 0x0627, 0x0654}, -{0x0624, 0x0648, 0x0654}, -{0x0625, 0x0627, 0x0655}, -{0x0626, 0x064a, 0x0654}, -{0x06c0, 0x06d5, 0x0654}, -{0x06c2, 0x06c1, 0x0654}, -{0x06d3, 0x06d2, 0x0654}, -{0x0929, 0x0928, 0x093c}, -{0x0931, 0x0930, 0x093c}, -{0x0934, 0x0933, 0x093c}, -{0x0958, 0x0915, 0x093c}, -{0x0959, 0x0916, 0x093c}, -{0x095a, 0x0917, 0x093c}, -{0x095b, 0x091c, 0x093c}, -{0x095c, 0x0921, 0x093c}, -{0x095d, 0x0922, 0x093c}, -{0x095e, 0x092b, 0x093c}, -{0x095f, 0x092f, 0x093c}, -{0x09cb, 0x09c7, 0x09be}, -{0x09cc, 0x09c7, 0x09d7}, -{0x09dc, 0x09a1, 0x09bc}, -{0x09dd, 0x09a2, 0x09bc}, -{0x09df, 0x09af, 0x09bc}, -{0x0a33, 0x0a32, 0x0a3c}, -{0x0a36, 0x0a38, 0x0a3c}, -{0x0a59, 0x0a16, 0x0a3c}, -{0x0a5a, 0x0a17, 0x0a3c}, -{0x0a5b, 0x0a1c, 0x0a3c}, -{0x0a5e, 0x0a2b, 0x0a3c}, -{0x0b48, 0x0b47, 0x0b56}, -{0x0b4b, 0x0b47, 0x0b3e}, -{0x0b4c, 0x0b47, 0x0b57}, -{0x0b5c, 0x0b21, 0x0b3c}, -{0x0b5d, 0x0b22, 0x0b3c}, -{0x0b94, 0x0b92, 0x0bd7}, -{0x0bca, 0x0bc6, 0x0bbe}, -{0x0bcb, 0x0bc7, 0x0bbe}, -{0x0bcc, 0x0bc6, 0x0bd7}, -{0x0c48, 0x0c46, 0x0c56}, -{0x0cc0, 0x0cbf, 0x0cd5}, -{0x0cc7, 0x0cc6, 0x0cd5}, -{0x0cc8, 0x0cc6, 0x0cd6}, -{0x0cca, 0x0cc6, 0x0cc2}, -{0x0ccb, 0x0cca, 0x0cd5}, -{0x0d4a, 0x0d46, 0x0d3e}, -{0x0d4b, 0x0d47, 0x0d3e}, -{0x0d4c, 0x0d46, 0x0d57}, -{0x0dda, 0x0dd9, 0x0dca}, -{0x0ddc, 0x0dd9, 0x0dcf}, -{0x0ddd, 0x0ddc, 0x0dca}, -{0x0dde, 0x0dd9, 0x0ddf}, -{0x0f43, 0x0f42, 0x0fb7}, -{0x0f4d, 0x0f4c, 0x0fb7}, -{0x0f52, 0x0f51, 0x0fb7}, -{0x0f57, 0x0f56, 0x0fb7}, -{0x0f5c, 0x0f5b, 0x0fb7}, -{0x0f69, 0x0f40, 0x0fb5}, -{0x0f73, 0x0f71, 0x0f72}, -{0x0f75, 0x0f71, 0x0f74}, -{0x0f76, 0x0fb2, 0x0f80}, -{0x0f78, 0x0fb3, 0x0f80}, -{0x0f81, 0x0f71, 0x0f80}, -{0x0f93, 0x0f92, 0x0fb7}, -{0x0f9d, 0x0f9c, 0x0fb7}, -{0x0fa2, 0x0fa1, 0x0fb7}, -{0x0fa7, 0x0fa6, 0x0fb7}, -{0x0fac, 0x0fab, 0x0fb7}, -{0x0fb9, 0x0f90, 0x0fb5}, -{0x1026, 0x1025, 0x102e}, -{0x1b06, 0x1b05, 0x1b35}, -{0x1b08, 0x1b07, 0x1b35}, -{0x1b0a, 0x1b09, 0x1b35}, -{0x1b0c, 0x1b0b, 0x1b35}, -{0x1b0e, 0x1b0d, 0x1b35}, -{0x1b12, 0x1b11, 0x1b35}, -{0x1b3b, 0x1b3a, 0x1b35}, -{0x1b3d, 0x1b3c, 0x1b35}, -{0x1b40, 0x1b3e, 0x1b35}, -{0x1b41, 0x1b3f, 0x1b35}, -{0x1b43, 0x1b42, 0x1b35}, -{0x1e00, 0x0041, 0x0325}, -{0x1e01, 0x0061, 0x0325}, -{0x1e02, 0x0042, 0x0307}, -{0x1e03, 0x0062, 0x0307}, -{0x1e04, 0x0042, 0x0323}, -{0x1e05, 0x0062, 0x0323}, -{0x1e06, 0x0042, 0x0331}, -{0x1e07, 0x0062, 0x0331}, -{0x1e08, 0x00c7, 0x0301}, -{0x1e09, 0x00e7, 0x0301}, -{0x1e0a, 0x0044, 0x0307}, -{0x1e0b, 0x0064, 0x0307}, -{0x1e0c, 0x0044, 0x0323}, -{0x1e0d, 0x0064, 0x0323}, -{0x1e0e, 0x0044, 0x0331}, -{0x1e0f, 0x0064, 0x0331}, -{0x1e10, 0x0044, 0x0327}, -{0x1e11, 0x0064, 0x0327}, -{0x1e12, 0x0044, 0x032d}, -{0x1e13, 0x0064, 0x032d}, -{0x1e14, 0x0112, 0x0300}, -{0x1e15, 0x0113, 0x0300}, -{0x1e16, 0x0112, 0x0301}, -{0x1e17, 0x0113, 0x0301}, -{0x1e18, 0x0045, 0x032d}, -{0x1e19, 0x0065, 0x032d}, -{0x1e1a, 0x0045, 0x0330}, -{0x1e1b, 0x0065, 0x0330}, -{0x1e1c, 0x0228, 0x0306}, -{0x1e1d, 0x0229, 0x0306}, -{0x1e1e, 0x0046, 0x0307}, -{0x1e1f, 0x0066, 0x0307}, -{0x1e20, 0x0047, 0x0304}, -{0x1e21, 0x0067, 0x0304}, -{0x1e22, 0x0048, 0x0307}, -{0x1e23, 0x0068, 0x0307}, -{0x1e24, 0x0048, 0x0323}, -{0x1e25, 0x0068, 0x0323}, -{0x1e26, 0x0048, 0x0308}, -{0x1e27, 0x0068, 0x0308}, -{0x1e28, 0x0048, 0x0327}, -{0x1e29, 0x0068, 0x0327}, -{0x1e2a, 0x0048, 0x032e}, -{0x1e2b, 0x0068, 0x032e}, -{0x1e2c, 0x0049, 0x0330}, -{0x1e2d, 0x0069, 0x0330}, -{0x1e2e, 0x00cf, 0x0301}, -{0x1e2f, 0x00ef, 0x0301}, -{0x1e30, 0x004b, 0x0301}, -{0x1e31, 0x006b, 0x0301}, -{0x1e32, 0x004b, 0x0323}, -{0x1e33, 0x006b, 0x0323}, -{0x1e34, 0x004b, 0x0331}, -{0x1e35, 0x006b, 0x0331}, -{0x1e36, 0x004c, 0x0323}, -{0x1e37, 0x006c, 0x0323}, -{0x1e38, 0x1e36, 0x0304}, -{0x1e39, 0x1e37, 0x0304}, -{0x1e3a, 0x004c, 0x0331}, -{0x1e3b, 0x006c, 0x0331}, -{0x1e3c, 0x004c, 0x032d}, -{0x1e3d, 0x006c, 0x032d}, -{0x1e3e, 0x004d, 0x0301}, -{0x1e3f, 0x006d, 0x0301}, -{0x1e40, 0x004d, 0x0307}, -{0x1e41, 0x006d, 0x0307}, -{0x1e42, 0x004d, 0x0323}, -{0x1e43, 0x006d, 0x0323}, -{0x1e44, 0x004e, 0x0307}, -{0x1e45, 0x006e, 0x0307}, -{0x1e46, 0x004e, 0x0323}, -{0x1e47, 0x006e, 0x0323}, -{0x1e48, 0x004e, 0x0331}, -{0x1e49, 0x006e, 0x0331}, -{0x1e4a, 0x004e, 0x032d}, -{0x1e4b, 0x006e, 0x032d}, -{0x1e4c, 0x00d5, 0x0301}, -{0x1e4d, 0x00f5, 0x0301}, -{0x1e4e, 0x00d5, 0x0308}, -{0x1e4f, 0x00f5, 0x0308}, -{0x1e50, 0x014c, 0x0300}, -{0x1e51, 0x014d, 0x0300}, -{0x1e52, 0x014c, 0x0301}, -{0x1e53, 0x014d, 0x0301}, -{0x1e54, 0x0050, 0x0301}, -{0x1e55, 0x0070, 0x0301}, -{0x1e56, 0x0050, 0x0307}, -{0x1e57, 0x0070, 0x0307}, -{0x1e58, 0x0052, 0x0307}, -{0x1e59, 0x0072, 0x0307}, -{0x1e5a, 0x0052, 0x0323}, -{0x1e5b, 0x0072, 0x0323}, -{0x1e5c, 0x1e5a, 0x0304}, -{0x1e5d, 0x1e5b, 0x0304}, -{0x1e5e, 0x0052, 0x0331}, -{0x1e5f, 0x0072, 0x0331}, -{0x1e60, 0x0053, 0x0307}, -{0x1e61, 0x0073, 0x0307}, -{0x1e62, 0x0053, 0x0323}, -{0x1e63, 0x0073, 0x0323}, -{0x1e64, 0x015a, 0x0307}, -{0x1e65, 0x015b, 0x0307}, -{0x1e66, 0x0160, 0x0307}, -{0x1e67, 0x0161, 0x0307}, -{0x1e68, 0x1e62, 0x0307}, -{0x1e69, 0x1e63, 0x0307}, -{0x1e6a, 0x0054, 0x0307}, -{0x1e6b, 0x0074, 0x0307}, -{0x1e6c, 0x0054, 0x0323}, -{0x1e6d, 0x0074, 0x0323}, -{0x1e6e, 0x0054, 0x0331}, -{0x1e6f, 0x0074, 0x0331}, -{0x1e70, 0x0054, 0x032d}, -{0x1e71, 0x0074, 0x032d}, -{0x1e72, 0x0055, 0x0324}, -{0x1e73, 0x0075, 0x0324}, -{0x1e74, 0x0055, 0x0330}, -{0x1e75, 0x0075, 0x0330}, -{0x1e76, 0x0055, 0x032d}, -{0x1e77, 0x0075, 0x032d}, -{0x1e78, 0x0168, 0x0301}, -{0x1e79, 0x0169, 0x0301}, -{0x1e7a, 0x016a, 0x0308}, -{0x1e7b, 0x016b, 0x0308}, -{0x1e7c, 0x0056, 0x0303}, -{0x1e7d, 0x0076, 0x0303}, -{0x1e7e, 0x0056, 0x0323}, -{0x1e7f, 0x0076, 0x0323}, -{0x1e80, 0x0057, 0x0300}, -{0x1e81, 0x0077, 0x0300}, -{0x1e82, 0x0057, 0x0301}, -{0x1e83, 0x0077, 0x0301}, -{0x1e84, 0x0057, 0x0308}, -{0x1e85, 0x0077, 0x0308}, -{0x1e86, 0x0057, 0x0307}, -{0x1e87, 0x0077, 0x0307}, -{0x1e88, 0x0057, 0x0323}, -{0x1e89, 0x0077, 0x0323}, -{0x1e8a, 0x0058, 0x0307}, -{0x1e8b, 0x0078, 0x0307}, -{0x1e8c, 0x0058, 0x0308}, -{0x1e8d, 0x0078, 0x0308}, -{0x1e8e, 0x0059, 0x0307}, -{0x1e8f, 0x0079, 0x0307}, -{0x1e90, 0x005a, 0x0302}, -{0x1e91, 0x007a, 0x0302}, -{0x1e92, 0x005a, 0x0323}, -{0x1e93, 0x007a, 0x0323}, -{0x1e94, 0x005a, 0x0331}, -{0x1e95, 0x007a, 0x0331}, -{0x1e96, 0x0068, 0x0331}, -{0x1e97, 0x0074, 0x0308}, -{0x1e98, 0x0077, 0x030a}, -{0x1e99, 0x0079, 0x030a}, -{0x1e9b, 0x017f, 0x0307}, -{0x1ea0, 0x0041, 0x0323}, -{0x1ea1, 0x0061, 0x0323}, -{0x1ea2, 0x0041, 0x0309}, -{0x1ea3, 0x0061, 0x0309}, -{0x1ea4, 0x00c2, 0x0301}, -{0x1ea5, 0x00e2, 0x0301}, -{0x1ea6, 0x00c2, 0x0300}, -{0x1ea7, 0x00e2, 0x0300}, -{0x1ea8, 0x00c2, 0x0309}, -{0x1ea9, 0x00e2, 0x0309}, -{0x1eaa, 0x00c2, 0x0303}, -{0x1eab, 0x00e2, 0x0303}, -{0x1eac, 0x1ea0, 0x0302}, -{0x1ead, 0x1ea1, 0x0302}, -{0x1eae, 0x0102, 0x0301}, -{0x1eaf, 0x0103, 0x0301}, -{0x1eb0, 0x0102, 0x0300}, -{0x1eb1, 0x0103, 0x0300}, -{0x1eb2, 0x0102, 0x0309}, -{0x1eb3, 0x0103, 0x0309}, -{0x1eb4, 0x0102, 0x0303}, -{0x1eb5, 0x0103, 0x0303}, -{0x1eb6, 0x1ea0, 0x0306}, -{0x1eb7, 0x1ea1, 0x0306}, -{0x1eb8, 0x0045, 0x0323}, -{0x1eb9, 0x0065, 0x0323}, -{0x1eba, 0x0045, 0x0309}, -{0x1ebb, 0x0065, 0x0309}, -{0x1ebc, 0x0045, 0x0303}, -{0x1ebd, 0x0065, 0x0303}, -{0x1ebe, 0x00ca, 0x0301}, -{0x1ebf, 0x00ea, 0x0301}, -{0x1ec0, 0x00ca, 0x0300}, -{0x1ec1, 0x00ea, 0x0300}, -{0x1ec2, 0x00ca, 0x0309}, -{0x1ec3, 0x00ea, 0x0309}, -{0x1ec4, 0x00ca, 0x0303}, -{0x1ec5, 0x00ea, 0x0303}, -{0x1ec6, 0x1eb8, 0x0302}, -{0x1ec7, 0x1eb9, 0x0302}, -{0x1ec8, 0x0049, 0x0309}, -{0x1ec9, 0x0069, 0x0309}, -{0x1eca, 0x0049, 0x0323}, -{0x1ecb, 0x0069, 0x0323}, -{0x1ecc, 0x004f, 0x0323}, -{0x1ecd, 0x006f, 0x0323}, -{0x1ece, 0x004f, 0x0309}, -{0x1ecf, 0x006f, 0x0309}, -{0x1ed0, 0x00d4, 0x0301}, -{0x1ed1, 0x00f4, 0x0301}, -{0x1ed2, 0x00d4, 0x0300}, -{0x1ed3, 0x00f4, 0x0300}, -{0x1ed4, 0x00d4, 0x0309}, -{0x1ed5, 0x00f4, 0x0309}, -{0x1ed6, 0x00d4, 0x0303}, -{0x1ed7, 0x00f4, 0x0303}, -{0x1ed8, 0x1ecc, 0x0302}, -{0x1ed9, 0x1ecd, 0x0302}, -{0x1eda, 0x01a0, 0x0301}, -{0x1edb, 0x01a1, 0x0301}, -{0x1edc, 0x01a0, 0x0300}, -{0x1edd, 0x01a1, 0x0300}, -{0x1ede, 0x01a0, 0x0309}, -{0x1edf, 0x01a1, 0x0309}, -{0x1ee0, 0x01a0, 0x0303}, -{0x1ee1, 0x01a1, 0x0303}, -{0x1ee2, 0x01a0, 0x0323}, -{0x1ee3, 0x01a1, 0x0323}, -{0x1ee4, 0x0055, 0x0323}, -{0x1ee5, 0x0075, 0x0323}, -{0x1ee6, 0x0055, 0x0309}, -{0x1ee7, 0x0075, 0x0309}, -{0x1ee8, 0x01af, 0x0301}, -{0x1ee9, 0x01b0, 0x0301}, -{0x1eea, 0x01af, 0x0300}, -{0x1eeb, 0x01b0, 0x0300}, -{0x1eec, 0x01af, 0x0309}, -{0x1eed, 0x01b0, 0x0309}, -{0x1eee, 0x01af, 0x0303}, -{0x1eef, 0x01b0, 0x0303}, -{0x1ef0, 0x01af, 0x0323}, -{0x1ef1, 0x01b0, 0x0323}, -{0x1ef2, 0x0059, 0x0300}, -{0x1ef3, 0x0079, 0x0300}, -{0x1ef4, 0x0059, 0x0323}, -{0x1ef5, 0x0079, 0x0323}, -{0x1ef6, 0x0059, 0x0309}, -{0x1ef7, 0x0079, 0x0309}, -{0x1ef8, 0x0059, 0x0303}, -{0x1ef9, 0x0079, 0x0303}, -{0x1f00, 0x03b1, 0x0313}, -{0x1f01, 0x03b1, 0x0314}, -{0x1f02, 0x1f00, 0x0300}, -{0x1f03, 0x1f01, 0x0300}, -{0x1f04, 0x1f00, 0x0301}, -{0x1f05, 0x1f01, 0x0301}, -{0x1f06, 0x1f00, 0x0342}, -{0x1f07, 0x1f01, 0x0342}, -{0x1f08, 0x0391, 0x0313}, -{0x1f09, 0x0391, 0x0314}, -{0x1f0a, 0x1f08, 0x0300}, -{0x1f0b, 0x1f09, 0x0300}, -{0x1f0c, 0x1f08, 0x0301}, -{0x1f0d, 0x1f09, 0x0301}, -{0x1f0e, 0x1f08, 0x0342}, -{0x1f0f, 0x1f09, 0x0342}, -{0x1f10, 0x03b5, 0x0313}, -{0x1f11, 0x03b5, 0x0314}, -{0x1f12, 0x1f10, 0x0300}, -{0x1f13, 0x1f11, 0x0300}, -{0x1f14, 0x1f10, 0x0301}, -{0x1f15, 0x1f11, 0x0301}, -{0x1f18, 0x0395, 0x0313}, -{0x1f19, 0x0395, 0x0314}, -{0x1f1a, 0x1f18, 0x0300}, -{0x1f1b, 0x1f19, 0x0300}, -{0x1f1c, 0x1f18, 0x0301}, -{0x1f1d, 0x1f19, 0x0301}, -{0x1f20, 0x03b7, 0x0313}, -{0x1f21, 0x03b7, 0x0314}, -{0x1f22, 0x1f20, 0x0300}, -{0x1f23, 0x1f21, 0x0300}, -{0x1f24, 0x1f20, 0x0301}, -{0x1f25, 0x1f21, 0x0301}, -{0x1f26, 0x1f20, 0x0342}, -{0x1f27, 0x1f21, 0x0342}, -{0x1f28, 0x0397, 0x0313}, -{0x1f29, 0x0397, 0x0314}, -{0x1f2a, 0x1f28, 0x0300}, -{0x1f2b, 0x1f29, 0x0300}, -{0x1f2c, 0x1f28, 0x0301}, -{0x1f2d, 0x1f29, 0x0301}, -{0x1f2e, 0x1f28, 0x0342}, -{0x1f2f, 0x1f29, 0x0342}, -{0x1f30, 0x03b9, 0x0313}, -{0x1f31, 0x03b9, 0x0314}, -{0x1f32, 0x1f30, 0x0300}, -{0x1f33, 0x1f31, 0x0300}, -{0x1f34, 0x1f30, 0x0301}, -{0x1f35, 0x1f31, 0x0301}, -{0x1f36, 0x1f30, 0x0342}, -{0x1f37, 0x1f31, 0x0342}, -{0x1f38, 0x0399, 0x0313}, -{0x1f39, 0x0399, 0x0314}, -{0x1f3a, 0x1f38, 0x0300}, -{0x1f3b, 0x1f39, 0x0300}, -{0x1f3c, 0x1f38, 0x0301}, -{0x1f3d, 0x1f39, 0x0301}, -{0x1f3e, 0x1f38, 0x0342}, -{0x1f3f, 0x1f39, 0x0342}, -{0x1f40, 0x03bf, 0x0313}, -{0x1f41, 0x03bf, 0x0314}, -{0x1f42, 0x1f40, 0x0300}, -{0x1f43, 0x1f41, 0x0300}, -{0x1f44, 0x1f40, 0x0301}, -{0x1f45, 0x1f41, 0x0301}, -{0x1f48, 0x039f, 0x0313}, -{0x1f49, 0x039f, 0x0314}, -{0x1f4a, 0x1f48, 0x0300}, -{0x1f4b, 0x1f49, 0x0300}, -{0x1f4c, 0x1f48, 0x0301}, -{0x1f4d, 0x1f49, 0x0301}, -{0x1f50, 0x03c5, 0x0313}, -{0x1f51, 0x03c5, 0x0314}, -{0x1f52, 0x1f50, 0x0300}, -{0x1f53, 0x1f51, 0x0300}, -{0x1f54, 0x1f50, 0x0301}, -{0x1f55, 0x1f51, 0x0301}, -{0x1f56, 0x1f50, 0x0342}, -{0x1f57, 0x1f51, 0x0342}, -{0x1f59, 0x03a5, 0x0314}, -{0x1f5b, 0x1f59, 0x0300}, -{0x1f5d, 0x1f59, 0x0301}, -{0x1f5f, 0x1f59, 0x0342}, -{0x1f60, 0x03c9, 0x0313}, -{0x1f61, 0x03c9, 0x0314}, -{0x1f62, 0x1f60, 0x0300}, -{0x1f63, 0x1f61, 0x0300}, -{0x1f64, 0x1f60, 0x0301}, -{0x1f65, 0x1f61, 0x0301}, -{0x1f66, 0x1f60, 0x0342}, -{0x1f67, 0x1f61, 0x0342}, -{0x1f68, 0x03a9, 0x0313}, -{0x1f69, 0x03a9, 0x0314}, -{0x1f6a, 0x1f68, 0x0300}, -{0x1f6b, 0x1f69, 0x0300}, -{0x1f6c, 0x1f68, 0x0301}, -{0x1f6d, 0x1f69, 0x0301}, -{0x1f6e, 0x1f68, 0x0342}, -{0x1f6f, 0x1f69, 0x0342}, -{0x1f70, 0x03b1, 0x0300}, -{0x1f71, 0x03ac, 0}, -{0x1f72, 0x03b5, 0x0300}, -{0x1f73, 0x03ad, 0}, -{0x1f74, 0x03b7, 0x0300}, -{0x1f75, 0x03ae, 0}, -{0x1f76, 0x03b9, 0x0300}, -{0x1f77, 0x03af, 0}, -{0x1f78, 0x03bf, 0x0300}, -{0x1f79, 0x03cc, 0}, -{0x1f7a, 0x03c5, 0x0300}, -{0x1f7b, 0x03cd, 0}, -{0x1f7c, 0x03c9, 0x0300}, -{0x1f7d, 0x03ce, 0}, -{0x1f80, 0x1f00, 0x0345}, -{0x1f81, 0x1f01, 0x0345}, -{0x1f82, 0x1f02, 0x0345}, -{0x1f83, 0x1f03, 0x0345}, -{0x1f84, 0x1f04, 0x0345}, -{0x1f85, 0x1f05, 0x0345}, -{0x1f86, 0x1f06, 0x0345}, -{0x1f87, 0x1f07, 0x0345}, -{0x1f88, 0x1f08, 0x0345}, -{0x1f89, 0x1f09, 0x0345}, -{0x1f8a, 0x1f0a, 0x0345}, -{0x1f8b, 0x1f0b, 0x0345}, -{0x1f8c, 0x1f0c, 0x0345}, -{0x1f8d, 0x1f0d, 0x0345}, -{0x1f8e, 0x1f0e, 0x0345}, -{0x1f8f, 0x1f0f, 0x0345}, -{0x1f90, 0x1f20, 0x0345}, -{0x1f91, 0x1f21, 0x0345}, -{0x1f92, 0x1f22, 0x0345}, -{0x1f93, 0x1f23, 0x0345}, -{0x1f94, 0x1f24, 0x0345}, -{0x1f95, 0x1f25, 0x0345}, -{0x1f96, 0x1f26, 0x0345}, -{0x1f97, 0x1f27, 0x0345}, -{0x1f98, 0x1f28, 0x0345}, -{0x1f99, 0x1f29, 0x0345}, -{0x1f9a, 0x1f2a, 0x0345}, -{0x1f9b, 0x1f2b, 0x0345}, -{0x1f9c, 0x1f2c, 0x0345}, -{0x1f9d, 0x1f2d, 0x0345}, -{0x1f9e, 0x1f2e, 0x0345}, -{0x1f9f, 0x1f2f, 0x0345}, -{0x1fa0, 0x1f60, 0x0345}, -{0x1fa1, 0x1f61, 0x0345}, -{0x1fa2, 0x1f62, 0x0345}, -{0x1fa3, 0x1f63, 0x0345}, -{0x1fa4, 0x1f64, 0x0345}, -{0x1fa5, 0x1f65, 0x0345}, -{0x1fa6, 0x1f66, 0x0345}, -{0x1fa7, 0x1f67, 0x0345}, -{0x1fa8, 0x1f68, 0x0345}, -{0x1fa9, 0x1f69, 0x0345}, -{0x1faa, 0x1f6a, 0x0345}, -{0x1fab, 0x1f6b, 0x0345}, -{0x1fac, 0x1f6c, 0x0345}, -{0x1fad, 0x1f6d, 0x0345}, -{0x1fae, 0x1f6e, 0x0345}, -{0x1faf, 0x1f6f, 0x0345}, -{0x1fb0, 0x03b1, 0x0306}, -{0x1fb1, 0x03b1, 0x0304}, -{0x1fb2, 0x1f70, 0x0345}, -{0x1fb3, 0x03b1, 0x0345}, -{0x1fb4, 0x03ac, 0x0345}, -{0x1fb6, 0x03b1, 0x0342}, -{0x1fb7, 0x1fb6, 0x0345}, -{0x1fb8, 0x0391, 0x0306}, -{0x1fb9, 0x0391, 0x0304}, -{0x1fba, 0x0391, 0x0300}, -{0x1fbb, 0x0386, 0}, -{0x1fbc, 0x0391, 0x0345}, -{0x1fbe, 0x03b9, 0}, -{0x1fc1, 0x00a8, 0x0342}, -{0x1fc2, 0x1f74, 0x0345}, -{0x1fc3, 0x03b7, 0x0345}, -{0x1fc4, 0x03ae, 0x0345}, -{0x1fc6, 0x03b7, 0x0342}, -{0x1fc7, 0x1fc6, 0x0345}, -{0x1fc8, 0x0395, 0x0300}, -{0x1fc9, 0x0388, 0}, -{0x1fca, 0x0397, 0x0300}, -{0x1fcb, 0x0389, 0}, -{0x1fcc, 0x0397, 0x0345}, -{0x1fcd, 0x1fbf, 0x0300}, -{0x1fce, 0x1fbf, 0x0301}, -{0x1fcf, 0x1fbf, 0x0342}, -{0x1fd0, 0x03b9, 0x0306}, -{0x1fd1, 0x03b9, 0x0304}, -{0x1fd2, 0x03ca, 0x0300}, -{0x1fd3, 0x0390, 0}, -{0x1fd6, 0x03b9, 0x0342}, -{0x1fd7, 0x03ca, 0x0342}, -{0x1fd8, 0x0399, 0x0306}, -{0x1fd9, 0x0399, 0x0304}, -{0x1fda, 0x0399, 0x0300}, -{0x1fdb, 0x038a, 0}, -{0x1fdd, 0x1ffe, 0x0300}, -{0x1fde, 0x1ffe, 0x0301}, -{0x1fdf, 0x1ffe, 0x0342}, -{0x1fe0, 0x03c5, 0x0306}, -{0x1fe1, 0x03c5, 0x0304}, -{0x1fe2, 0x03cb, 0x0300}, -{0x1fe3, 0x03b0, 0}, -{0x1fe4, 0x03c1, 0x0313}, -{0x1fe5, 0x03c1, 0x0314}, -{0x1fe6, 0x03c5, 0x0342}, -{0x1fe7, 0x03cb, 0x0342}, -{0x1fe8, 0x03a5, 0x0306}, -{0x1fe9, 0x03a5, 0x0304}, -{0x1fea, 0x03a5, 0x0300}, -{0x1feb, 0x038e, 0}, -{0x1fec, 0x03a1, 0x0314}, -{0x1fed, 0x00a8, 0x0300}, -{0x1fee, 0x0385, 0}, -{0x1fef, 0x0060, 0}, -{0x1ff2, 0x1f7c, 0x0345}, -{0x1ff3, 0x03c9, 0x0345}, -{0x1ff4, 0x03ce, 0x0345}, -{0x1ff6, 0x03c9, 0x0342}, -{0x1ff7, 0x1ff6, 0x0345}, -{0x1ff8, 0x039f, 0x0300}, -{0x1ff9, 0x038c, 0}, -{0x1ffa, 0x03a9, 0x0300}, -{0x1ffb, 0x038f, 0}, -{0x1ffc, 0x03a9, 0x0345}, -{0x1ffd, 0x00b4, 0}, -{0x2000, 0x2002, 0}, -{0x2001, 0x2003, 0}, -{0x2126, 0x03a9, 0}, -{0x212a, 0x004b, 0}, -{0x212b, 0x00c5, 0}, -{0x219a, 0x2190, 0x0338}, -{0x219b, 0x2192, 0x0338}, -{0x21ae, 0x2194, 0x0338}, -{0x21cd, 0x21d0, 0x0338}, -{0x21ce, 0x21d4, 0x0338}, -{0x21cf, 0x21d2, 0x0338}, -{0x2204, 0x2203, 0x0338}, -{0x2209, 0x2208, 0x0338}, -{0x220c, 0x220b, 0x0338}, -{0x2224, 0x2223, 0x0338}, -{0x2226, 0x2225, 0x0338}, -{0x2241, 0x223c, 0x0338}, -{0x2244, 0x2243, 0x0338}, -{0x2247, 0x2245, 0x0338}, -{0x2249, 0x2248, 0x0338}, -{0x2260, 0x003d, 0x0338}, -{0x2262, 0x2261, 0x0338}, -{0x226d, 0x224d, 0x0338}, -{0x226e, 0x003c, 0x0338}, -{0x226f, 0x003e, 0x0338}, -{0x2270, 0x2264, 0x0338}, -{0x2271, 0x2265, 0x0338}, -{0x2274, 0x2272, 0x0338}, -{0x2275, 0x2273, 0x0338}, -{0x2278, 0x2276, 0x0338}, -{0x2279, 0x2277, 0x0338}, -{0x2280, 0x227a, 0x0338}, -{0x2281, 0x227b, 0x0338}, -{0x2284, 0x2282, 0x0338}, -{0x2285, 0x2283, 0x0338}, -{0x2288, 0x2286, 0x0338}, -{0x2289, 0x2287, 0x0338}, -{0x22ac, 0x22a2, 0x0338}, -{0x22ad, 0x22a8, 0x0338}, -{0x22ae, 0x22a9, 0x0338}, -{0x22af, 0x22ab, 0x0338}, -{0x22e0, 0x227c, 0x0338}, -{0x22e1, 0x227d, 0x0338}, -{0x22e2, 0x2291, 0x0338}, -{0x22e3, 0x2292, 0x0338}, -{0x22ea, 0x22b2, 0x0338}, -{0x22eb, 0x22b3, 0x0338}, -{0x22ec, 0x22b4, 0x0338}, -{0x22ed, 0x22b5, 0x0338}, -{0x2329, 0x3008, 0}, -{0x232a, 0x3009, 0}, -{0x2adc, 0x2add, 0x0338}, -{0x304c, 0x304b, 0x3099}, -{0x304e, 0x304d, 0x3099}, -{0x3050, 0x304f, 0x3099}, -{0x3052, 0x3051, 0x3099}, -{0x3054, 0x3053, 0x3099}, -{0x3056, 0x3055, 0x3099}, -{0x3058, 0x3057, 0x3099}, -{0x305a, 0x3059, 0x3099}, -{0x305c, 0x305b, 0x3099}, -{0x305e, 0x305d, 0x3099}, -{0x3060, 0x305f, 0x3099}, -{0x3062, 0x3061, 0x3099}, -{0x3065, 0x3064, 0x3099}, -{0x3067, 0x3066, 0x3099}, -{0x3069, 0x3068, 0x3099}, -{0x3070, 0x306f, 0x3099}, -{0x3071, 0x306f, 0x309a}, -{0x3073, 0x3072, 0x3099}, -{0x3074, 0x3072, 0x309a}, -{0x3076, 0x3075, 0x3099}, -{0x3077, 0x3075, 0x309a}, -{0x3079, 0x3078, 0x3099}, -{0x307a, 0x3078, 0x309a}, -{0x307c, 0x307b, 0x3099}, -{0x307d, 0x307b, 0x309a}, -{0x3094, 0x3046, 0x3099}, -{0x309e, 0x309d, 0x3099}, -{0x30ac, 0x30ab, 0x3099}, -{0x30ae, 0x30ad, 0x3099}, -{0x30b0, 0x30af, 0x3099}, -{0x30b2, 0x30b1, 0x3099}, -{0x30b4, 0x30b3, 0x3099}, -{0x30b6, 0x30b5, 0x3099}, -{0x30b8, 0x30b7, 0x3099}, -{0x30ba, 0x30b9, 0x3099}, -{0x30bc, 0x30bb, 0x3099}, -{0x30be, 0x30bd, 0x3099}, -{0x30c0, 0x30bf, 0x3099}, -{0x30c2, 0x30c1, 0x3099}, -{0x30c5, 0x30c4, 0x3099}, -{0x30c7, 0x30c6, 0x3099}, -{0x30c9, 0x30c8, 0x3099}, -{0x30d0, 0x30cf, 0x3099}, -{0x30d1, 0x30cf, 0x309a}, -{0x30d3, 0x30d2, 0x3099}, -{0x30d4, 0x30d2, 0x309a}, -{0x30d6, 0x30d5, 0x3099}, -{0x30d7, 0x30d5, 0x309a}, -{0x30d9, 0x30d8, 0x3099}, -{0x30da, 0x30d8, 0x309a}, -{0x30dc, 0x30db, 0x3099}, -{0x30dd, 0x30db, 0x309a}, -{0x30f4, 0x30a6, 0x3099}, -{0x30f7, 0x30ef, 0x3099}, -{0x30f8, 0x30f0, 0x3099}, -{0x30f9, 0x30f1, 0x3099}, -{0x30fa, 0x30f2, 0x3099}, -{0x30fe, 0x30fd, 0x3099}, -{0xf900, 0x8c48, 0}, -{0xf901, 0x66f4, 0}, -{0xf902, 0x8eca, 0}, -{0xf903, 0x8cc8, 0}, -{0xf904, 0x6ed1, 0}, -{0xf905, 0x4e32, 0}, -{0xf906, 0x53e5, 0}, -{0xf907, 0x9f9c, 0}, -{0xf908, 0x9f9c, 0}, -{0xf909, 0x5951, 0}, -{0xf90a, 0x91d1, 0}, -{0xf90b, 0x5587, 0}, -{0xf90c, 0x5948, 0}, -{0xf90d, 0x61f6, 0}, -{0xf90e, 0x7669, 0}, -{0xf90f, 0x7f85, 0}, -{0xf910, 0x863f, 0}, -{0xf911, 0x87ba, 0}, -{0xf912, 0x88f8, 0}, -{0xf913, 0x908f, 0}, -{0xf914, 0x6a02, 0}, -{0xf915, 0x6d1b, 0}, -{0xf916, 0x70d9, 0}, -{0xf917, 0x73de, 0}, -{0xf918, 0x843d, 0}, -{0xf919, 0x916a, 0}, -{0xf91a, 0x99f1, 0}, -{0xf91b, 0x4e82, 0}, -{0xf91c, 0x5375, 0}, -{0xf91d, 0x6b04, 0}, -{0xf91e, 0x721b, 0}, -{0xf91f, 0x862d, 0}, -{0xf920, 0x9e1e, 0}, -{0xf921, 0x5d50, 0}, -{0xf922, 0x6feb, 0}, -{0xf923, 0x85cd, 0}, -{0xf924, 0x8964, 0}, -{0xf925, 0x62c9, 0}, -{0xf926, 0x81d8, 0}, -{0xf927, 0x881f, 0}, -{0xf928, 0x5eca, 0}, -{0xf929, 0x6717, 0}, -{0xf92a, 0x6d6a, 0}, -{0xf92b, 0x72fc, 0}, -{0xf92c, 0x90ce, 0}, -{0xf92d, 0x4f86, 0}, -{0xf92e, 0x51b7, 0}, -{0xf92f, 0x52de, 0}, -{0xf930, 0x64c4, 0}, -{0xf931, 0x6ad3, 0}, -{0xf932, 0x7210, 0}, -{0xf933, 0x76e7, 0}, -{0xf934, 0x8001, 0}, -{0xf935, 0x8606, 0}, -{0xf936, 0x865c, 0}, -{0xf937, 0x8def, 0}, -{0xf938, 0x9732, 0}, -{0xf939, 0x9b6f, 0}, -{0xf93a, 0x9dfa, 0}, -{0xf93b, 0x788c, 0}, -{0xf93c, 0x797f, 0}, -{0xf93d, 0x7da0, 0}, -{0xf93e, 0x83c9, 0}, -{0xf93f, 0x9304, 0}, -{0xf940, 0x9e7f, 0}, -{0xf941, 0x8ad6, 0}, -{0xf942, 0x58df, 0}, -{0xf943, 0x5f04, 0}, -{0xf944, 0x7c60, 0}, -{0xf945, 0x807e, 0}, -{0xf946, 0x7262, 0}, -{0xf947, 0x78ca, 0}, -{0xf948, 0x8cc2, 0}, -{0xf949, 0x96f7, 0}, -{0xf94a, 0x58d8, 0}, -{0xf94b, 0x5c62, 0}, -{0xf94c, 0x6a13, 0}, -{0xf94d, 0x6dda, 0}, -{0xf94e, 0x6f0f, 0}, -{0xf94f, 0x7d2f, 0}, -{0xf950, 0x7e37, 0}, -{0xf951, 0x964b, 0}, -{0xf952, 0x52d2, 0}, -{0xf953, 0x808b, 0}, -{0xf954, 0x51dc, 0}, -{0xf955, 0x51cc, 0}, -{0xf956, 0x7a1c, 0}, -{0xf957, 0x7dbe, 0}, -{0xf958, 0x83f1, 0}, -{0xf959, 0x9675, 0}, -{0xf95a, 0x8b80, 0}, -{0xf95b, 0x62cf, 0}, -{0xf95c, 0x6a02, 0}, -{0xf95d, 0x8afe, 0}, -{0xf95e, 0x4e39, 0}, -{0xf95f, 0x5be7, 0}, -{0xf960, 0x6012, 0}, -{0xf961, 0x7387, 0}, -{0xf962, 0x7570, 0}, -{0xf963, 0x5317, 0}, -{0xf964, 0x78fb, 0}, -{0xf965, 0x4fbf, 0}, -{0xf966, 0x5fa9, 0}, -{0xf967, 0x4e0d, 0}, -{0xf968, 0x6ccc, 0}, -{0xf969, 0x6578, 0}, -{0xf96a, 0x7d22, 0}, -{0xf96b, 0x53c3, 0}, -{0xf96c, 0x585e, 0}, -{0xf96d, 0x7701, 0}, -{0xf96e, 0x8449, 0}, -{0xf96f, 0x8aaa, 0}, -{0xf970, 0x6bba, 0}, -{0xf971, 0x8fb0, 0}, -{0xf972, 0x6c88, 0}, -{0xf973, 0x62fe, 0}, -{0xf974, 0x82e5, 0}, -{0xf975, 0x63a0, 0}, -{0xf976, 0x7565, 0}, -{0xf977, 0x4eae, 0}, -{0xf978, 0x5169, 0}, -{0xf979, 0x51c9, 0}, -{0xf97a, 0x6881, 0}, -{0xf97b, 0x7ce7, 0}, -{0xf97c, 0x826f, 0}, -{0xf97d, 0x8ad2, 0}, -{0xf97e, 0x91cf, 0}, -{0xf97f, 0x52f5, 0}, -{0xf980, 0x5442, 0}, -{0xf981, 0x5973, 0}, -{0xf982, 0x5eec, 0}, -{0xf983, 0x65c5, 0}, -{0xf984, 0x6ffe, 0}, -{0xf985, 0x792a, 0}, -{0xf986, 0x95ad, 0}, -{0xf987, 0x9a6a, 0}, -{0xf988, 0x9e97, 0}, -{0xf989, 0x9ece, 0}, -{0xf98a, 0x529b, 0}, -{0xf98b, 0x66c6, 0}, -{0xf98c, 0x6b77, 0}, -{0xf98d, 0x8f62, 0}, -{0xf98e, 0x5e74, 0}, -{0xf98f, 0x6190, 0}, -{0xf990, 0x6200, 0}, -{0xf991, 0x649a, 0}, -{0xf992, 0x6f23, 0}, -{0xf993, 0x7149, 0}, -{0xf994, 0x7489, 0}, -{0xf995, 0x79ca, 0}, -{0xf996, 0x7df4, 0}, -{0xf997, 0x806f, 0}, -{0xf998, 0x8f26, 0}, -{0xf999, 0x84ee, 0}, -{0xf99a, 0x9023, 0}, -{0xf99b, 0x934a, 0}, -{0xf99c, 0x5217, 0}, -{0xf99d, 0x52a3, 0}, -{0xf99e, 0x54bd, 0}, -{0xf99f, 0x70c8, 0}, -{0xf9a0, 0x88c2, 0}, -{0xf9a1, 0x8aaa, 0}, -{0xf9a2, 0x5ec9, 0}, -{0xf9a3, 0x5ff5, 0}, -{0xf9a4, 0x637b, 0}, -{0xf9a5, 0x6bae, 0}, -{0xf9a6, 0x7c3e, 0}, -{0xf9a7, 0x7375, 0}, -{0xf9a8, 0x4ee4, 0}, -{0xf9a9, 0x56f9, 0}, -{0xf9aa, 0x5be7, 0}, -{0xf9ab, 0x5dba, 0}, -{0xf9ac, 0x601c, 0}, -{0xf9ad, 0x73b2, 0}, -{0xf9ae, 0x7469, 0}, -{0xf9af, 0x7f9a, 0}, -{0xf9b0, 0x8046, 0}, -{0xf9b1, 0x9234, 0}, -{0xf9b2, 0x96f6, 0}, -{0xf9b3, 0x9748, 0}, -{0xf9b4, 0x9818, 0}, -{0xf9b5, 0x4f8b, 0}, -{0xf9b6, 0x79ae, 0}, -{0xf9b7, 0x91b4, 0}, -{0xf9b8, 0x96b8, 0}, -{0xf9b9, 0x60e1, 0}, -{0xf9ba, 0x4e86, 0}, -{0xf9bb, 0x50da, 0}, -{0xf9bc, 0x5bee, 0}, -{0xf9bd, 0x5c3f, 0}, -{0xf9be, 0x6599, 0}, -{0xf9bf, 0x6a02, 0}, -{0xf9c0, 0x71ce, 0}, -{0xf9c1, 0x7642, 0}, -{0xf9c2, 0x84fc, 0}, -{0xf9c3, 0x907c, 0}, -{0xf9c4, 0x9f8d, 0}, -{0xf9c5, 0x6688, 0}, -{0xf9c6, 0x962e, 0}, -{0xf9c7, 0x5289, 0}, -{0xf9c8, 0x677b, 0}, -{0xf9c9, 0x67f3, 0}, -{0xf9ca, 0x6d41, 0}, -{0xf9cb, 0x6e9c, 0}, -{0xf9cc, 0x7409, 0}, -{0xf9cd, 0x7559, 0}, -{0xf9ce, 0x786b, 0}, -{0xf9cf, 0x7d10, 0}, -{0xf9d0, 0x985e, 0}, -{0xf9d1, 0x516d, 0}, -{0xf9d2, 0x622e, 0}, -{0xf9d3, 0x9678, 0}, -{0xf9d4, 0x502b, 0}, -{0xf9d5, 0x5d19, 0}, -{0xf9d6, 0x6dea, 0}, -{0xf9d7, 0x8f2a, 0}, -{0xf9d8, 0x5f8b, 0}, -{0xf9d9, 0x6144, 0}, -{0xf9da, 0x6817, 0}, -{0xf9db, 0x7387, 0}, -{0xf9dc, 0x9686, 0}, -{0xf9dd, 0x5229, 0}, -{0xf9de, 0x540f, 0}, -{0xf9df, 0x5c65, 0}, -{0xf9e0, 0x6613, 0}, -{0xf9e1, 0x674e, 0}, -{0xf9e2, 0x68a8, 0}, -{0xf9e3, 0x6ce5, 0}, -{0xf9e4, 0x7406, 0}, -{0xf9e5, 0x75e2, 0}, -{0xf9e6, 0x7f79, 0}, -{0xf9e7, 0x88cf, 0}, -{0xf9e8, 0x88e1, 0}, -{0xf9e9, 0x91cc, 0}, -{0xf9ea, 0x96e2, 0}, -{0xf9eb, 0x533f, 0}, -{0xf9ec, 0x6eba, 0}, -{0xf9ed, 0x541d, 0}, -{0xf9ee, 0x71d0, 0}, -{0xf9ef, 0x7498, 0}, -{0xf9f0, 0x85fa, 0}, -{0xf9f1, 0x96a3, 0}, -{0xf9f2, 0x9c57, 0}, -{0xf9f3, 0x9e9f, 0}, -{0xf9f4, 0x6797, 0}, -{0xf9f5, 0x6dcb, 0}, -{0xf9f6, 0x81e8, 0}, -{0xf9f7, 0x7acb, 0}, -{0xf9f8, 0x7b20, 0}, -{0xf9f9, 0x7c92, 0}, -{0xf9fa, 0x72c0, 0}, -{0xf9fb, 0x7099, 0}, -{0xf9fc, 0x8b58, 0}, -{0xf9fd, 0x4ec0, 0}, -{0xf9fe, 0x8336, 0}, -{0xf9ff, 0x523a, 0}, -{0xfa00, 0x5207, 0}, -{0xfa01, 0x5ea6, 0}, -{0xfa02, 0x62d3, 0}, -{0xfa03, 0x7cd6, 0}, -{0xfa04, 0x5b85, 0}, -{0xfa05, 0x6d1e, 0}, -{0xfa06, 0x66b4, 0}, -{0xfa07, 0x8f3b, 0}, -{0xfa08, 0x884c, 0}, -{0xfa09, 0x964d, 0}, -{0xfa0a, 0x898b, 0}, -{0xfa0b, 0x5ed3, 0}, -{0xfa0c, 0x5140, 0}, -{0xfa0d, 0x55c0, 0}, -{0xfa10, 0x585a, 0}, -{0xfa12, 0x6674, 0}, -{0xfa15, 0x51de, 0}, -{0xfa16, 0x732a, 0}, -{0xfa17, 0x76ca, 0}, -{0xfa18, 0x793c, 0}, -{0xfa19, 0x795e, 0}, -{0xfa1a, 0x7965, 0}, -{0xfa1b, 0x798f, 0}, -{0xfa1c, 0x9756, 0}, -{0xfa1d, 0x7cbe, 0}, -{0xfa1e, 0x7fbd, 0}, -{0xfa20, 0x8612, 0}, -{0xfa22, 0x8af8, 0}, -{0xfa25, 0x9038, 0}, -{0xfa26, 0x90fd, 0}, -{0xfa2a, 0x98ef, 0}, -{0xfa2b, 0x98fc, 0}, -{0xfa2c, 0x9928, 0}, -{0xfa2d, 0x9db4, 0}, -{0xfa2e, 0x90de, 0}, -{0xfa2f, 0x96b7, 0}, -{0xfa30, 0x4fae, 0}, -{0xfa31, 0x50e7, 0}, -{0xfa32, 0x514d, 0}, -{0xfa33, 0x52c9, 0}, -{0xfa34, 0x52e4, 0}, -{0xfa35, 0x5351, 0}, -{0xfa36, 0x559d, 0}, -{0xfa37, 0x5606, 0}, -{0xfa38, 0x5668, 0}, -{0xfa39, 0x5840, 0}, -{0xfa3a, 0x58a8, 0}, -{0xfa3b, 0x5c64, 0}, -{0xfa3c, 0x5c6e, 0}, -{0xfa3d, 0x6094, 0}, -{0xfa3e, 0x6168, 0}, -{0xfa3f, 0x618e, 0}, -{0xfa40, 0x61f2, 0}, -{0xfa41, 0x654f, 0}, -{0xfa42, 0x65e2, 0}, -{0xfa43, 0x6691, 0}, -{0xfa44, 0x6885, 0}, -{0xfa45, 0x6d77, 0}, -{0xfa46, 0x6e1a, 0}, -{0xfa47, 0x6f22, 0}, -{0xfa48, 0x716e, 0}, -{0xfa49, 0x722b, 0}, -{0xfa4a, 0x7422, 0}, -{0xfa4b, 0x7891, 0}, -{0xfa4c, 0x793e, 0}, -{0xfa4d, 0x7949, 0}, -{0xfa4e, 0x7948, 0}, -{0xfa4f, 0x7950, 0}, -{0xfa50, 0x7956, 0}, -{0xfa51, 0x795d, 0}, -{0xfa52, 0x798d, 0}, -{0xfa53, 0x798e, 0}, -{0xfa54, 0x7a40, 0}, -{0xfa55, 0x7a81, 0}, -{0xfa56, 0x7bc0, 0}, -{0xfa57, 0x7df4, 0}, -{0xfa58, 0x7e09, 0}, -{0xfa59, 0x7e41, 0}, -{0xfa5a, 0x7f72, 0}, -{0xfa5b, 0x8005, 0}, -{0xfa5c, 0x81ed, 0}, -{0xfa5d, 0x8279, 0}, -{0xfa5e, 0x8279, 0}, -{0xfa5f, 0x8457, 0}, -{0xfa60, 0x8910, 0}, -{0xfa61, 0x8996, 0}, -{0xfa62, 0x8b01, 0}, -{0xfa63, 0x8b39, 0}, -{0xfa64, 0x8cd3, 0}, -{0xfa65, 0x8d08, 0}, -{0xfa66, 0x8fb6, 0}, -{0xfa67, 0x9038, 0}, -{0xfa68, 0x96e3, 0}, -{0xfa69, 0x97ff, 0}, -{0xfa6a, 0x983b, 0}, -{0xfa6b, 0x6075, 0}, -{0xfa6c, 0x242ee, 0}, -{0xfa6d, 0x8218, 0}, -{0xfa70, 0x4e26, 0}, -{0xfa71, 0x51b5, 0}, -{0xfa72, 0x5168, 0}, -{0xfa73, 0x4f80, 0}, -{0xfa74, 0x5145, 0}, -{0xfa75, 0x5180, 0}, -{0xfa76, 0x52c7, 0}, -{0xfa77, 0x52fa, 0}, -{0xfa78, 0x559d, 0}, -{0xfa79, 0x5555, 0}, -{0xfa7a, 0x5599, 0}, -{0xfa7b, 0x55e2, 0}, -{0xfa7c, 0x585a, 0}, -{0xfa7d, 0x58b3, 0}, -{0xfa7e, 0x5944, 0}, -{0xfa7f, 0x5954, 0}, -{0xfa80, 0x5a62, 0}, -{0xfa81, 0x5b28, 0}, -{0xfa82, 0x5ed2, 0}, -{0xfa83, 0x5ed9, 0}, -{0xfa84, 0x5f69, 0}, -{0xfa85, 0x5fad, 0}, -{0xfa86, 0x60d8, 0}, -{0xfa87, 0x614e, 0}, -{0xfa88, 0x6108, 0}, -{0xfa89, 0x618e, 0}, -{0xfa8a, 0x6160, 0}, -{0xfa8b, 0x61f2, 0}, -{0xfa8c, 0x6234, 0}, -{0xfa8d, 0x63c4, 0}, -{0xfa8e, 0x641c, 0}, -{0xfa8f, 0x6452, 0}, -{0xfa90, 0x6556, 0}, -{0xfa91, 0x6674, 0}, -{0xfa92, 0x6717, 0}, -{0xfa93, 0x671b, 0}, -{0xfa94, 0x6756, 0}, -{0xfa95, 0x6b79, 0}, -{0xfa96, 0x6bba, 0}, -{0xfa97, 0x6d41, 0}, -{0xfa98, 0x6edb, 0}, -{0xfa99, 0x6ecb, 0}, -{0xfa9a, 0x6f22, 0}, -{0xfa9b, 0x701e, 0}, -{0xfa9c, 0x716e, 0}, -{0xfa9d, 0x77a7, 0}, -{0xfa9e, 0x7235, 0}, -{0xfa9f, 0x72af, 0}, -{0xfaa0, 0x732a, 0}, -{0xfaa1, 0x7471, 0}, -{0xfaa2, 0x7506, 0}, -{0xfaa3, 0x753b, 0}, -{0xfaa4, 0x761d, 0}, -{0xfaa5, 0x761f, 0}, -{0xfaa6, 0x76ca, 0}, -{0xfaa7, 0x76db, 0}, -{0xfaa8, 0x76f4, 0}, -{0xfaa9, 0x774a, 0}, -{0xfaaa, 0x7740, 0}, -{0xfaab, 0x78cc, 0}, -{0xfaac, 0x7ab1, 0}, -{0xfaad, 0x7bc0, 0}, -{0xfaae, 0x7c7b, 0}, -{0xfaaf, 0x7d5b, 0}, -{0xfab0, 0x7df4, 0}, -{0xfab1, 0x7f3e, 0}, -{0xfab2, 0x8005, 0}, -{0xfab3, 0x8352, 0}, -{0xfab4, 0x83ef, 0}, -{0xfab5, 0x8779, 0}, -{0xfab6, 0x8941, 0}, -{0xfab7, 0x8986, 0}, -{0xfab8, 0x8996, 0}, -{0xfab9, 0x8abf, 0}, -{0xfaba, 0x8af8, 0}, -{0xfabb, 0x8acb, 0}, -{0xfabc, 0x8b01, 0}, -{0xfabd, 0x8afe, 0}, -{0xfabe, 0x8aed, 0}, -{0xfabf, 0x8b39, 0}, -{0xfac0, 0x8b8a, 0}, -{0xfac1, 0x8d08, 0}, -{0xfac2, 0x8f38, 0}, -{0xfac3, 0x9072, 0}, -{0xfac4, 0x9199, 0}, -{0xfac5, 0x9276, 0}, -{0xfac6, 0x967c, 0}, -{0xfac7, 0x96e3, 0}, -{0xfac8, 0x9756, 0}, -{0xfac9, 0x97db, 0}, -{0xfaca, 0x97ff, 0}, -{0xfacb, 0x980b, 0}, -{0xfacc, 0x983b, 0}, -{0xfacd, 0x9b12, 0}, -{0xface, 0x9f9c, 0}, -{0xfacf, 0x2284a, 0}, -{0xfad0, 0x22844, 0}, -{0xfad1, 0x233d5, 0}, -{0xfad2, 0x3b9d, 0}, -{0xfad3, 0x4018, 0}, -{0xfad4, 0x4039, 0}, -{0xfad5, 0x25249, 0}, -{0xfad6, 0x25cd0, 0}, -{0xfad7, 0x27ed3, 0}, -{0xfad8, 0x9f43, 0}, -{0xfad9, 0x9f8e, 0}, -{0xfb1d, 0x05d9, 0x05b4}, -{0xfb1f, 0x05f2, 0x05b7}, -{0xfb2a, 0x05e9, 0x05c1}, -{0xfb2b, 0x05e9, 0x05c2}, -{0xfb2c, 0xfb49, 0x05c1}, -{0xfb2d, 0xfb49, 0x05c2}, -{0xfb2e, 0x05d0, 0x05b7}, -{0xfb2f, 0x05d0, 0x05b8}, -{0xfb30, 0x05d0, 0x05bc}, -{0xfb31, 0x05d1, 0x05bc}, -{0xfb32, 0x05d2, 0x05bc}, -{0xfb33, 0x05d3, 0x05bc}, -{0xfb34, 0x05d4, 0x05bc}, -{0xfb35, 0x05d5, 0x05bc}, -{0xfb36, 0x05d6, 0x05bc}, -{0xfb38, 0x05d8, 0x05bc}, -{0xfb39, 0x05d9, 0x05bc}, -{0xfb3a, 0x05da, 0x05bc}, -{0xfb3b, 0x05db, 0x05bc}, -{0xfb3c, 0x05dc, 0x05bc}, -{0xfb3e, 0x05de, 0x05bc}, -{0xfb40, 0x05e0, 0x05bc}, -{0xfb41, 0x05e1, 0x05bc}, -{0xfb43, 0x05e3, 0x05bc}, -{0xfb44, 0x05e4, 0x05bc}, -{0xfb46, 0x05e6, 0x05bc}, -{0xfb47, 0x05e7, 0x05bc}, -{0xfb48, 0x05e8, 0x05bc}, -{0xfb49, 0x05e9, 0x05bc}, -{0xfb4a, 0x05ea, 0x05bc}, -{0xfb4b, 0x05d5, 0x05b9}, -{0xfb4c, 0x05d1, 0x05bf}, -{0xfb4d, 0x05db, 0x05bf}, -{0xfb4e, 0x05e4, 0x05bf}, -{0x1109a, 0x11099, 0x110ba}, -{0x1109c, 0x1109b, 0x110ba}, -{0x110ab, 0x110a5, 0x110ba}, -{0x1112e, 0x11131, 0x11127}, -{0x1112f, 0x11132, 0x11127}, -{0x1134b, 0x11347, 0x1133e}, -{0x1134c, 0x11347, 0x11357}, -{0x114bb, 0x114b9, 0x114ba}, -{0x114bc, 0x114b9, 0x114b0}, -{0x114be, 0x114b9, 0x114bd}, -{0x115ba, 0x115b8, 0x115af}, -{0x115bb, 0x115b9, 0x115af}, -{0x11938, 0x11935, 0x11930}, -{0x1d15e, 0x1d157, 0x1d165}, -{0x1d15f, 0x1d158, 0x1d165}, -{0x1d160, 0x1d15f, 0x1d16e}, -{0x1d161, 0x1d15f, 0x1d16f}, -{0x1d162, 0x1d15f, 0x1d170}, -{0x1d163, 0x1d15f, 0x1d171}, -{0x1d164, 0x1d15f, 0x1d172}, -{0x1d1bb, 0x1d1b9, 0x1d165}, -{0x1d1bc, 0x1d1ba, 0x1d165}, -{0x1d1bd, 0x1d1bb, 0x1d16e}, -{0x1d1be, 0x1d1bc, 0x1d16e}, -{0x1d1bf, 0x1d1bb, 0x1d16f}, -{0x1d1c0, 0x1d1bc, 0x1d16f}, -{0x2f800, 0x4e3d, 0}, -{0x2f801, 0x4e38, 0}, -{0x2f802, 0x4e41, 0}, -{0x2f803, 0x20122, 0}, -{0x2f804, 0x4f60, 0}, -{0x2f805, 0x4fae, 0}, -{0x2f806, 0x4fbb, 0}, -{0x2f807, 0x5002, 0}, -{0x2f808, 0x507a, 0}, -{0x2f809, 0x5099, 0}, -{0x2f80a, 0x50e7, 0}, -{0x2f80b, 0x50cf, 0}, -{0x2f80c, 0x349e, 0}, -{0x2f80d, 0x2063a, 0}, -{0x2f80e, 0x514d, 0}, -{0x2f80f, 0x5154, 0}, -{0x2f810, 0x5164, 0}, -{0x2f811, 0x5177, 0}, -{0x2f812, 0x2051c, 0}, -{0x2f813, 0x34b9, 0}, -{0x2f814, 0x5167, 0}, -{0x2f815, 0x518d, 0}, -{0x2f816, 0x2054b, 0}, -{0x2f817, 0x5197, 0}, -{0x2f818, 0x51a4, 0}, -{0x2f819, 0x4ecc, 0}, -{0x2f81a, 0x51ac, 0}, -{0x2f81b, 0x51b5, 0}, -{0x2f81c, 0x291df, 0}, -{0x2f81d, 0x51f5, 0}, -{0x2f81e, 0x5203, 0}, -{0x2f81f, 0x34df, 0}, -{0x2f820, 0x523b, 0}, -{0x2f821, 0x5246, 0}, -{0x2f822, 0x5272, 0}, -{0x2f823, 0x5277, 0}, -{0x2f824, 0x3515, 0}, -{0x2f825, 0x52c7, 0}, -{0x2f826, 0x52c9, 0}, -{0x2f827, 0x52e4, 0}, -{0x2f828, 0x52fa, 0}, -{0x2f829, 0x5305, 0}, -{0x2f82a, 0x5306, 0}, -{0x2f82b, 0x5317, 0}, -{0x2f82c, 0x5349, 0}, -{0x2f82d, 0x5351, 0}, -{0x2f82e, 0x535a, 0}, -{0x2f82f, 0x5373, 0}, -{0x2f830, 0x537d, 0}, -{0x2f831, 0x537f, 0}, -{0x2f832, 0x537f, 0}, -{0x2f833, 0x537f, 0}, -{0x2f834, 0x20a2c, 0}, -{0x2f835, 0x7070, 0}, -{0x2f836, 0x53ca, 0}, -{0x2f837, 0x53df, 0}, -{0x2f838, 0x20b63, 0}, -{0x2f839, 0x53eb, 0}, -{0x2f83a, 0x53f1, 0}, -{0x2f83b, 0x5406, 0}, -{0x2f83c, 0x549e, 0}, -{0x2f83d, 0x5438, 0}, -{0x2f83e, 0x5448, 0}, -{0x2f83f, 0x5468, 0}, -{0x2f840, 0x54a2, 0}, -{0x2f841, 0x54f6, 0}, -{0x2f842, 0x5510, 0}, -{0x2f843, 0x5553, 0}, -{0x2f844, 0x5563, 0}, -{0x2f845, 0x5584, 0}, -{0x2f846, 0x5584, 0}, -{0x2f847, 0x5599, 0}, -{0x2f848, 0x55ab, 0}, -{0x2f849, 0x55b3, 0}, -{0x2f84a, 0x55c2, 0}, -{0x2f84b, 0x5716, 0}, -{0x2f84c, 0x5606, 0}, -{0x2f84d, 0x5717, 0}, -{0x2f84e, 0x5651, 0}, -{0x2f84f, 0x5674, 0}, -{0x2f850, 0x5207, 0}, -{0x2f851, 0x58ee, 0}, -{0x2f852, 0x57ce, 0}, -{0x2f853, 0x57f4, 0}, -{0x2f854, 0x580d, 0}, -{0x2f855, 0x578b, 0}, -{0x2f856, 0x5832, 0}, -{0x2f857, 0x5831, 0}, -{0x2f858, 0x58ac, 0}, -{0x2f859, 0x214e4, 0}, -{0x2f85a, 0x58f2, 0}, -{0x2f85b, 0x58f7, 0}, -{0x2f85c, 0x5906, 0}, -{0x2f85d, 0x591a, 0}, -{0x2f85e, 0x5922, 0}, -{0x2f85f, 0x5962, 0}, -{0x2f860, 0x216a8, 0}, -{0x2f861, 0x216ea, 0}, -{0x2f862, 0x59ec, 0}, -{0x2f863, 0x5a1b, 0}, -{0x2f864, 0x5a27, 0}, -{0x2f865, 0x59d8, 0}, -{0x2f866, 0x5a66, 0}, -{0x2f867, 0x36ee, 0}, -{0x2f868, 0x36fc, 0}, -{0x2f869, 0x5b08, 0}, -{0x2f86a, 0x5b3e, 0}, -{0x2f86b, 0x5b3e, 0}, -{0x2f86c, 0x219c8, 0}, -{0x2f86d, 0x5bc3, 0}, -{0x2f86e, 0x5bd8, 0}, -{0x2f86f, 0x5be7, 0}, -{0x2f870, 0x5bf3, 0}, -{0x2f871, 0x21b18, 0}, -{0x2f872, 0x5bff, 0}, -{0x2f873, 0x5c06, 0}, -{0x2f874, 0x5f53, 0}, -{0x2f875, 0x5c22, 0}, -{0x2f876, 0x3781, 0}, -{0x2f877, 0x5c60, 0}, -{0x2f878, 0x5c6e, 0}, -{0x2f879, 0x5cc0, 0}, -{0x2f87a, 0x5c8d, 0}, -{0x2f87b, 0x21de4, 0}, -{0x2f87c, 0x5d43, 0}, -{0x2f87d, 0x21de6, 0}, -{0x2f87e, 0x5d6e, 0}, -{0x2f87f, 0x5d6b, 0}, -{0x2f880, 0x5d7c, 0}, -{0x2f881, 0x5de1, 0}, -{0x2f882, 0x5de2, 0}, -{0x2f883, 0x382f, 0}, -{0x2f884, 0x5dfd, 0}, -{0x2f885, 0x5e28, 0}, -{0x2f886, 0x5e3d, 0}, -{0x2f887, 0x5e69, 0}, -{0x2f888, 0x3862, 0}, -{0x2f889, 0x22183, 0}, -{0x2f88a, 0x387c, 0}, -{0x2f88b, 0x5eb0, 0}, -{0x2f88c, 0x5eb3, 0}, -{0x2f88d, 0x5eb6, 0}, -{0x2f88e, 0x5eca, 0}, -{0x2f88f, 0x2a392, 0}, -{0x2f890, 0x5efe, 0}, -{0x2f891, 0x22331, 0}, -{0x2f892, 0x22331, 0}, -{0x2f893, 0x8201, 0}, -{0x2f894, 0x5f22, 0}, -{0x2f895, 0x5f22, 0}, -{0x2f896, 0x38c7, 0}, -{0x2f897, 0x232b8, 0}, -{0x2f898, 0x261da, 0}, -{0x2f899, 0x5f62, 0}, -{0x2f89a, 0x5f6b, 0}, -{0x2f89b, 0x38e3, 0}, -{0x2f89c, 0x5f9a, 0}, -{0x2f89d, 0x5fcd, 0}, -{0x2f89e, 0x5fd7, 0}, -{0x2f89f, 0x5ff9, 0}, -{0x2f8a0, 0x6081, 0}, -{0x2f8a1, 0x393a, 0}, -{0x2f8a2, 0x391c, 0}, -{0x2f8a3, 0x6094, 0}, -{0x2f8a4, 0x226d4, 0}, -{0x2f8a5, 0x60c7, 0}, -{0x2f8a6, 0x6148, 0}, -{0x2f8a7, 0x614c, 0}, -{0x2f8a8, 0x614e, 0}, -{0x2f8a9, 0x614c, 0}, -{0x2f8aa, 0x617a, 0}, -{0x2f8ab, 0x618e, 0}, -{0x2f8ac, 0x61b2, 0}, -{0x2f8ad, 0x61a4, 0}, -{0x2f8ae, 0x61af, 0}, -{0x2f8af, 0x61de, 0}, -{0x2f8b0, 0x61f2, 0}, -{0x2f8b1, 0x61f6, 0}, -{0x2f8b2, 0x6210, 0}, -{0x2f8b3, 0x621b, 0}, -{0x2f8b4, 0x625d, 0}, -{0x2f8b5, 0x62b1, 0}, -{0x2f8b6, 0x62d4, 0}, -{0x2f8b7, 0x6350, 0}, -{0x2f8b8, 0x22b0c, 0}, -{0x2f8b9, 0x633d, 0}, -{0x2f8ba, 0x62fc, 0}, -{0x2f8bb, 0x6368, 0}, -{0x2f8bc, 0x6383, 0}, -{0x2f8bd, 0x63e4, 0}, -{0x2f8be, 0x22bf1, 0}, -{0x2f8bf, 0x6422, 0}, -{0x2f8c0, 0x63c5, 0}, -{0x2f8c1, 0x63a9, 0}, -{0x2f8c2, 0x3a2e, 0}, -{0x2f8c3, 0x6469, 0}, -{0x2f8c4, 0x647e, 0}, -{0x2f8c5, 0x649d, 0}, -{0x2f8c6, 0x6477, 0}, -{0x2f8c7, 0x3a6c, 0}, -{0x2f8c8, 0x654f, 0}, -{0x2f8c9, 0x656c, 0}, -{0x2f8ca, 0x2300a, 0}, -{0x2f8cb, 0x65e3, 0}, -{0x2f8cc, 0x66f8, 0}, -{0x2f8cd, 0x6649, 0}, -{0x2f8ce, 0x3b19, 0}, -{0x2f8cf, 0x6691, 0}, -{0x2f8d0, 0x3b08, 0}, -{0x2f8d1, 0x3ae4, 0}, -{0x2f8d2, 0x5192, 0}, -{0x2f8d3, 0x5195, 0}, -{0x2f8d4, 0x6700, 0}, -{0x2f8d5, 0x669c, 0}, -{0x2f8d6, 0x80ad, 0}, -{0x2f8d7, 0x43d9, 0}, -{0x2f8d8, 0x6717, 0}, -{0x2f8d9, 0x671b, 0}, -{0x2f8da, 0x6721, 0}, -{0x2f8db, 0x675e, 0}, -{0x2f8dc, 0x6753, 0}, -{0x2f8dd, 0x233c3, 0}, -{0x2f8de, 0x3b49, 0}, -{0x2f8df, 0x67fa, 0}, -{0x2f8e0, 0x6785, 0}, -{0x2f8e1, 0x6852, 0}, -{0x2f8e2, 0x6885, 0}, -{0x2f8e3, 0x2346d, 0}, -{0x2f8e4, 0x688e, 0}, -{0x2f8e5, 0x681f, 0}, -{0x2f8e6, 0x6914, 0}, -{0x2f8e7, 0x3b9d, 0}, -{0x2f8e8, 0x6942, 0}, -{0x2f8e9, 0x69a3, 0}, -{0x2f8ea, 0x69ea, 0}, -{0x2f8eb, 0x6aa8, 0}, -{0x2f8ec, 0x236a3, 0}, -{0x2f8ed, 0x6adb, 0}, -{0x2f8ee, 0x3c18, 0}, -{0x2f8ef, 0x6b21, 0}, -{0x2f8f0, 0x238a7, 0}, -{0x2f8f1, 0x6b54, 0}, -{0x2f8f2, 0x3c4e, 0}, -{0x2f8f3, 0x6b72, 0}, -{0x2f8f4, 0x6b9f, 0}, -{0x2f8f5, 0x6bba, 0}, -{0x2f8f6, 0x6bbb, 0}, -{0x2f8f7, 0x23a8d, 0}, -{0x2f8f8, 0x21d0b, 0}, -{0x2f8f9, 0x23afa, 0}, -{0x2f8fa, 0x6c4e, 0}, -{0x2f8fb, 0x23cbc, 0}, -{0x2f8fc, 0x6cbf, 0}, -{0x2f8fd, 0x6ccd, 0}, -{0x2f8fe, 0x6c67, 0}, -{0x2f8ff, 0x6d16, 0}, -{0x2f900, 0x6d3e, 0}, -{0x2f901, 0x6d77, 0}, -{0x2f902, 0x6d41, 0}, -{0x2f903, 0x6d69, 0}, -{0x2f904, 0x6d78, 0}, -{0x2f905, 0x6d85, 0}, -{0x2f906, 0x23d1e, 0}, -{0x2f907, 0x6d34, 0}, -{0x2f908, 0x6e2f, 0}, -{0x2f909, 0x6e6e, 0}, -{0x2f90a, 0x3d33, 0}, -{0x2f90b, 0x6ecb, 0}, -{0x2f90c, 0x6ec7, 0}, -{0x2f90d, 0x23ed1, 0}, -{0x2f90e, 0x6df9, 0}, -{0x2f90f, 0x6f6e, 0}, -{0x2f910, 0x23f5e, 0}, -{0x2f911, 0x23f8e, 0}, -{0x2f912, 0x6fc6, 0}, -{0x2f913, 0x7039, 0}, -{0x2f914, 0x701e, 0}, -{0x2f915, 0x701b, 0}, -{0x2f916, 0x3d96, 0}, -{0x2f917, 0x704a, 0}, -{0x2f918, 0x707d, 0}, -{0x2f919, 0x7077, 0}, -{0x2f91a, 0x70ad, 0}, -{0x2f91b, 0x20525, 0}, -{0x2f91c, 0x7145, 0}, -{0x2f91d, 0x24263, 0}, -{0x2f91e, 0x719c, 0}, -{0x2f91f, 0x243ab, 0}, -{0x2f920, 0x7228, 0}, -{0x2f921, 0x7235, 0}, -{0x2f922, 0x7250, 0}, -{0x2f923, 0x24608, 0}, -{0x2f924, 0x7280, 0}, -{0x2f925, 0x7295, 0}, -{0x2f926, 0x24735, 0}, -{0x2f927, 0x24814, 0}, -{0x2f928, 0x737a, 0}, -{0x2f929, 0x738b, 0}, -{0x2f92a, 0x3eac, 0}, -{0x2f92b, 0x73a5, 0}, -{0x2f92c, 0x3eb8, 0}, -{0x2f92d, 0x3eb8, 0}, -{0x2f92e, 0x7447, 0}, -{0x2f92f, 0x745c, 0}, -{0x2f930, 0x7471, 0}, -{0x2f931, 0x7485, 0}, -{0x2f932, 0x74ca, 0}, -{0x2f933, 0x3f1b, 0}, -{0x2f934, 0x7524, 0}, -{0x2f935, 0x24c36, 0}, -{0x2f936, 0x753e, 0}, -{0x2f937, 0x24c92, 0}, -{0x2f938, 0x7570, 0}, -{0x2f939, 0x2219f, 0}, -{0x2f93a, 0x7610, 0}, -{0x2f93b, 0x24fa1, 0}, -{0x2f93c, 0x24fb8, 0}, -{0x2f93d, 0x25044, 0}, -{0x2f93e, 0x3ffc, 0}, -{0x2f93f, 0x4008, 0}, -{0x2f940, 0x76f4, 0}, -{0x2f941, 0x250f3, 0}, -{0x2f942, 0x250f2, 0}, -{0x2f943, 0x25119, 0}, -{0x2f944, 0x25133, 0}, -{0x2f945, 0x771e, 0}, -{0x2f946, 0x771f, 0}, -{0x2f947, 0x771f, 0}, -{0x2f948, 0x774a, 0}, -{0x2f949, 0x4039, 0}, -{0x2f94a, 0x778b, 0}, -{0x2f94b, 0x4046, 0}, -{0x2f94c, 0x4096, 0}, -{0x2f94d, 0x2541d, 0}, -{0x2f94e, 0x784e, 0}, -{0x2f94f, 0x788c, 0}, -{0x2f950, 0x78cc, 0}, -{0x2f951, 0x40e3, 0}, -{0x2f952, 0x25626, 0}, -{0x2f953, 0x7956, 0}, -{0x2f954, 0x2569a, 0}, -{0x2f955, 0x256c5, 0}, -{0x2f956, 0x798f, 0}, -{0x2f957, 0x79eb, 0}, -{0x2f958, 0x412f, 0}, -{0x2f959, 0x7a40, 0}, -{0x2f95a, 0x7a4a, 0}, -{0x2f95b, 0x7a4f, 0}, -{0x2f95c, 0x2597c, 0}, -{0x2f95d, 0x25aa7, 0}, -{0x2f95e, 0x25aa7, 0}, -{0x2f95f, 0x7aee, 0}, -{0x2f960, 0x4202, 0}, -{0x2f961, 0x25bab, 0}, -{0x2f962, 0x7bc6, 0}, -{0x2f963, 0x7bc9, 0}, -{0x2f964, 0x4227, 0}, -{0x2f965, 0x25c80, 0}, -{0x2f966, 0x7cd2, 0}, -{0x2f967, 0x42a0, 0}, -{0x2f968, 0x7ce8, 0}, -{0x2f969, 0x7ce3, 0}, -{0x2f96a, 0x7d00, 0}, -{0x2f96b, 0x25f86, 0}, -{0x2f96c, 0x7d63, 0}, -{0x2f96d, 0x4301, 0}, -{0x2f96e, 0x7dc7, 0}, -{0x2f96f, 0x7e02, 0}, -{0x2f970, 0x7e45, 0}, -{0x2f971, 0x4334, 0}, -{0x2f972, 0x26228, 0}, -{0x2f973, 0x26247, 0}, -{0x2f974, 0x4359, 0}, -{0x2f975, 0x262d9, 0}, -{0x2f976, 0x7f7a, 0}, -{0x2f977, 0x2633e, 0}, -{0x2f978, 0x7f95, 0}, -{0x2f979, 0x7ffa, 0}, -{0x2f97a, 0x8005, 0}, -{0x2f97b, 0x264da, 0}, -{0x2f97c, 0x26523, 0}, -{0x2f97d, 0x8060, 0}, -{0x2f97e, 0x265a8, 0}, -{0x2f97f, 0x8070, 0}, -{0x2f980, 0x2335f, 0}, -{0x2f981, 0x43d5, 0}, -{0x2f982, 0x80b2, 0}, -{0x2f983, 0x8103, 0}, -{0x2f984, 0x440b, 0}, -{0x2f985, 0x813e, 0}, -{0x2f986, 0x5ab5, 0}, -{0x2f987, 0x267a7, 0}, -{0x2f988, 0x267b5, 0}, -{0x2f989, 0x23393, 0}, -{0x2f98a, 0x2339c, 0}, -{0x2f98b, 0x8201, 0}, -{0x2f98c, 0x8204, 0}, -{0x2f98d, 0x8f9e, 0}, -{0x2f98e, 0x446b, 0}, -{0x2f98f, 0x8291, 0}, -{0x2f990, 0x828b, 0}, -{0x2f991, 0x829d, 0}, -{0x2f992, 0x52b3, 0}, -{0x2f993, 0x82b1, 0}, -{0x2f994, 0x82b3, 0}, -{0x2f995, 0x82bd, 0}, -{0x2f996, 0x82e6, 0}, -{0x2f997, 0x26b3c, 0}, -{0x2f998, 0x82e5, 0}, -{0x2f999, 0x831d, 0}, -{0x2f99a, 0x8363, 0}, -{0x2f99b, 0x83ad, 0}, -{0x2f99c, 0x8323, 0}, -{0x2f99d, 0x83bd, 0}, -{0x2f99e, 0x83e7, 0}, -{0x2f99f, 0x8457, 0}, -{0x2f9a0, 0x8353, 0}, -{0x2f9a1, 0x83ca, 0}, -{0x2f9a2, 0x83cc, 0}, -{0x2f9a3, 0x83dc, 0}, -{0x2f9a4, 0x26c36, 0}, -{0x2f9a5, 0x26d6b, 0}, -{0x2f9a6, 0x26cd5, 0}, -{0x2f9a7, 0x452b, 0}, -{0x2f9a8, 0x84f1, 0}, -{0x2f9a9, 0x84f3, 0}, -{0x2f9aa, 0x8516, 0}, -{0x2f9ab, 0x273ca, 0}, -{0x2f9ac, 0x8564, 0}, -{0x2f9ad, 0x26f2c, 0}, -{0x2f9ae, 0x455d, 0}, -{0x2f9af, 0x4561, 0}, -{0x2f9b0, 0x26fb1, 0}, -{0x2f9b1, 0x270d2, 0}, -{0x2f9b2, 0x456b, 0}, -{0x2f9b3, 0x8650, 0}, -{0x2f9b4, 0x865c, 0}, -{0x2f9b5, 0x8667, 0}, -{0x2f9b6, 0x8669, 0}, -{0x2f9b7, 0x86a9, 0}, -{0x2f9b8, 0x8688, 0}, -{0x2f9b9, 0x870e, 0}, -{0x2f9ba, 0x86e2, 0}, -{0x2f9bb, 0x8779, 0}, -{0x2f9bc, 0x8728, 0}, -{0x2f9bd, 0x876b, 0}, -{0x2f9be, 0x8786, 0}, -{0x2f9bf, 0x45d7, 0}, -{0x2f9c0, 0x87e1, 0}, -{0x2f9c1, 0x8801, 0}, -{0x2f9c2, 0x45f9, 0}, -{0x2f9c3, 0x8860, 0}, -{0x2f9c4, 0x8863, 0}, -{0x2f9c5, 0x27667, 0}, -{0x2f9c6, 0x88d7, 0}, -{0x2f9c7, 0x88de, 0}, -{0x2f9c8, 0x4635, 0}, -{0x2f9c9, 0x88fa, 0}, -{0x2f9ca, 0x34bb, 0}, -{0x2f9cb, 0x278ae, 0}, -{0x2f9cc, 0x27966, 0}, -{0x2f9cd, 0x46be, 0}, -{0x2f9ce, 0x46c7, 0}, -{0x2f9cf, 0x8aa0, 0}, -{0x2f9d0, 0x8aed, 0}, -{0x2f9d1, 0x8b8a, 0}, -{0x2f9d2, 0x8c55, 0}, -{0x2f9d3, 0x27ca8, 0}, -{0x2f9d4, 0x8cab, 0}, -{0x2f9d5, 0x8cc1, 0}, -{0x2f9d6, 0x8d1b, 0}, -{0x2f9d7, 0x8d77, 0}, -{0x2f9d8, 0x27f2f, 0}, -{0x2f9d9, 0x20804, 0}, -{0x2f9da, 0x8dcb, 0}, -{0x2f9db, 0x8dbc, 0}, -{0x2f9dc, 0x8df0, 0}, -{0x2f9dd, 0x208de, 0}, -{0x2f9de, 0x8ed4, 0}, -{0x2f9df, 0x8f38, 0}, -{0x2f9e0, 0x285d2, 0}, -{0x2f9e1, 0x285ed, 0}, -{0x2f9e2, 0x9094, 0}, -{0x2f9e3, 0x90f1, 0}, -{0x2f9e4, 0x9111, 0}, -{0x2f9e5, 0x2872e, 0}, -{0x2f9e6, 0x911b, 0}, -{0x2f9e7, 0x9238, 0}, -{0x2f9e8, 0x92d7, 0}, -{0x2f9e9, 0x92d8, 0}, -{0x2f9ea, 0x927c, 0}, -{0x2f9eb, 0x93f9, 0}, -{0x2f9ec, 0x9415, 0}, -{0x2f9ed, 0x28bfa, 0}, -{0x2f9ee, 0x958b, 0}, -{0x2f9ef, 0x4995, 0}, -{0x2f9f0, 0x95b7, 0}, -{0x2f9f1, 0x28d77, 0}, -{0x2f9f2, 0x49e6, 0}, -{0x2f9f3, 0x96c3, 0}, -{0x2f9f4, 0x5db2, 0}, -{0x2f9f5, 0x9723, 0}, -{0x2f9f6, 0x29145, 0}, -{0x2f9f7, 0x2921a, 0}, -{0x2f9f8, 0x4a6e, 0}, -{0x2f9f9, 0x4a76, 0}, -{0x2f9fa, 0x97e0, 0}, -{0x2f9fb, 0x2940a, 0}, -{0x2f9fc, 0x4ab2, 0}, -{0x2f9fd, 0x29496, 0}, -{0x2f9fe, 0x980b, 0}, -{0x2f9ff, 0x980b, 0}, -{0x2fa00, 0x9829, 0}, -{0x2fa01, 0x295b6, 0}, -{0x2fa02, 0x98e2, 0}, -{0x2fa03, 0x4b33, 0}, -{0x2fa04, 0x9929, 0}, -{0x2fa05, 0x99a7, 0}, -{0x2fa06, 0x99c2, 0}, -{0x2fa07, 0x99fe, 0}, -{0x2fa08, 0x4bce, 0}, -{0x2fa09, 0x29b30, 0}, -{0x2fa0a, 0x9b12, 0}, -{0x2fa0b, 0x9c40, 0}, -{0x2fa0c, 0x9cfd, 0}, -{0x2fa0d, 0x4cce, 0}, -{0x2fa0e, 0x4ced, 0}, -{0x2fa0f, 0x9d67, 0}, -{0x2fa10, 0x2a0ce, 0}, -{0x2fa11, 0x4cf8, 0}, -{0x2fa12, 0x2a105, 0}, -{0x2fa13, 0x2a20e, 0}, -{0x2fa14, 0x2a291, 0}, -{0x2fa15, 0x9ebb, 0}, -{0x2fa16, 0x4d56, 0}, -{0x2fa17, 0x9ef9, 0}, -{0x2fa18, 0x9efe, 0}, -{0x2fa19, 0x9f05, 0}, -{0x2fa1a, 0x9f0f, 0}, -{0x2fa1b, 0x9f16, 0}, -{0x2fa1c, 0x9f3b, 0}, -{0x2fa1d, 0x2a600, 0}, diff --git a/unicode/combining_classes.h b/unicode/combining_classes.h deleted file mode 100644 index a2262bc34..000000000 --- a/unicode/combining_classes.h +++ /dev/null @@ -1,398 +0,0 @@ -/* - * Autogenerated by read_ucd.py from The Unicode Standard 15.0.0 - * - * List the canonical combining class of each Unicode character, if it is - * not zero. This controls how combining marks can be reordered by the - * Unicode normalisation algorithms. - * - * Used by utils/unicode-norm.c. - */ - -{0x0300, 0x0314, 230}, -{0x0315, 0x0315, 232}, -{0x0316, 0x0319, 220}, -{0x031a, 0x031a, 232}, -{0x031b, 0x031b, 216}, -{0x031c, 0x0320, 220}, -{0x0321, 0x0322, 202}, -{0x0323, 0x0326, 220}, -{0x0327, 0x0328, 202}, -{0x0329, 0x0333, 220}, -{0x0334, 0x0338, 1}, -{0x0339, 0x033c, 220}, -{0x033d, 0x0344, 230}, -{0x0345, 0x0345, 240}, -{0x0346, 0x0346, 230}, -{0x0347, 0x0349, 220}, -{0x034a, 0x034c, 230}, -{0x034d, 0x034e, 220}, -{0x0350, 0x0352, 230}, -{0x0353, 0x0356, 220}, -{0x0357, 0x0357, 230}, -{0x0358, 0x0358, 232}, -{0x0359, 0x035a, 220}, -{0x035b, 0x035b, 230}, -{0x035c, 0x035c, 233}, -{0x035d, 0x035e, 234}, -{0x035f, 0x035f, 233}, -{0x0360, 0x0361, 234}, -{0x0362, 0x0362, 233}, -{0x0363, 0x036f, 230}, -{0x0483, 0x0487, 230}, -{0x0591, 0x0591, 220}, -{0x0592, 0x0595, 230}, -{0x0596, 0x0596, 220}, -{0x0597, 0x0599, 230}, -{0x059a, 0x059a, 222}, -{0x059b, 0x059b, 220}, -{0x059c, 0x05a1, 230}, -{0x05a2, 0x05a7, 220}, -{0x05a8, 0x05a9, 230}, -{0x05aa, 0x05aa, 220}, -{0x05ab, 0x05ac, 230}, -{0x05ad, 0x05ad, 222}, -{0x05ae, 0x05ae, 228}, -{0x05af, 0x05af, 230}, -{0x05b0, 0x05b0, 10}, -{0x05b1, 0x05b1, 11}, -{0x05b2, 0x05b2, 12}, -{0x05b3, 0x05b3, 13}, -{0x05b4, 0x05b4, 14}, -{0x05b5, 0x05b5, 15}, -{0x05b6, 0x05b6, 16}, -{0x05b7, 0x05b7, 17}, -{0x05b8, 0x05b8, 18}, -{0x05b9, 0x05ba, 19}, -{0x05bb, 0x05bb, 20}, -{0x05bc, 0x05bc, 21}, -{0x05bd, 0x05bd, 22}, -{0x05bf, 0x05bf, 23}, -{0x05c1, 0x05c1, 24}, -{0x05c2, 0x05c2, 25}, -{0x05c4, 0x05c4, 230}, -{0x05c5, 0x05c5, 220}, -{0x05c7, 0x05c7, 18}, -{0x0610, 0x0617, 230}, -{0x0618, 0x0618, 30}, -{0x0619, 0x0619, 31}, -{0x061a, 0x061a, 32}, -{0x064b, 0x064b, 27}, -{0x064c, 0x064c, 28}, -{0x064d, 0x064d, 29}, -{0x064e, 0x064e, 30}, -{0x064f, 0x064f, 31}, -{0x0650, 0x0650, 32}, -{0x0651, 0x0651, 33}, -{0x0652, 0x0652, 34}, -{0x0653, 0x0654, 230}, -{0x0655, 0x0656, 220}, -{0x0657, 0x065b, 230}, -{0x065c, 0x065c, 220}, -{0x065d, 0x065e, 230}, -{0x065f, 0x065f, 220}, -{0x0670, 0x0670, 35}, -{0x06d6, 0x06dc, 230}, -{0x06df, 0x06e2, 230}, -{0x06e3, 0x06e3, 220}, -{0x06e4, 0x06e4, 230}, -{0x06e7, 0x06e8, 230}, -{0x06ea, 0x06ea, 220}, -{0x06eb, 0x06ec, 230}, -{0x06ed, 0x06ed, 220}, -{0x0711, 0x0711, 36}, -{0x0730, 0x0730, 230}, -{0x0731, 0x0731, 220}, -{0x0732, 0x0733, 230}, -{0x0734, 0x0734, 220}, -{0x0735, 0x0736, 230}, -{0x0737, 0x0739, 220}, -{0x073a, 0x073a, 230}, -{0x073b, 0x073c, 220}, -{0x073d, 0x073d, 230}, -{0x073e, 0x073e, 220}, -{0x073f, 0x0741, 230}, -{0x0742, 0x0742, 220}, -{0x0743, 0x0743, 230}, -{0x0744, 0x0744, 220}, -{0x0745, 0x0745, 230}, -{0x0746, 0x0746, 220}, -{0x0747, 0x0747, 230}, -{0x0748, 0x0748, 220}, -{0x0749, 0x074a, 230}, -{0x07eb, 0x07f1, 230}, -{0x07f2, 0x07f2, 220}, -{0x07f3, 0x07f3, 230}, -{0x07fd, 0x07fd, 220}, -{0x0816, 0x0819, 230}, -{0x081b, 0x0823, 230}, -{0x0825, 0x0827, 230}, -{0x0829, 0x082d, 230}, -{0x0859, 0x085b, 220}, -{0x0898, 0x0898, 230}, -{0x0899, 0x089b, 220}, -{0x089c, 0x089f, 230}, -{0x08ca, 0x08ce, 230}, -{0x08cf, 0x08d3, 220}, -{0x08d4, 0x08e1, 230}, -{0x08e3, 0x08e3, 220}, -{0x08e4, 0x08e5, 230}, -{0x08e6, 0x08e6, 220}, -{0x08e7, 0x08e8, 230}, -{0x08e9, 0x08e9, 220}, -{0x08ea, 0x08ec, 230}, -{0x08ed, 0x08ef, 220}, -{0x08f0, 0x08f0, 27}, -{0x08f1, 0x08f1, 28}, -{0x08f2, 0x08f2, 29}, -{0x08f3, 0x08f5, 230}, -{0x08f6, 0x08f6, 220}, -{0x08f7, 0x08f8, 230}, -{0x08f9, 0x08fa, 220}, -{0x08fb, 0x08ff, 230}, -{0x093c, 0x093c, 7}, -{0x094d, 0x094d, 9}, -{0x0951, 0x0951, 230}, -{0x0952, 0x0952, 220}, -{0x0953, 0x0954, 230}, -{0x09bc, 0x09bc, 7}, -{0x09cd, 0x09cd, 9}, -{0x09fe, 0x09fe, 230}, -{0x0a3c, 0x0a3c, 7}, -{0x0a4d, 0x0a4d, 9}, -{0x0abc, 0x0abc, 7}, -{0x0acd, 0x0acd, 9}, -{0x0b3c, 0x0b3c, 7}, -{0x0b4d, 0x0b4d, 9}, -{0x0bcd, 0x0bcd, 9}, -{0x0c3c, 0x0c3c, 7}, -{0x0c4d, 0x0c4d, 9}, -{0x0c55, 0x0c55, 84}, -{0x0c56, 0x0c56, 91}, -{0x0cbc, 0x0cbc, 7}, -{0x0ccd, 0x0ccd, 9}, -{0x0d3b, 0x0d3c, 9}, -{0x0d4d, 0x0d4d, 9}, -{0x0dca, 0x0dca, 9}, -{0x0e38, 0x0e39, 103}, -{0x0e3a, 0x0e3a, 9}, -{0x0e48, 0x0e4b, 107}, -{0x0eb8, 0x0eb9, 118}, -{0x0eba, 0x0eba, 9}, -{0x0ec8, 0x0ecb, 122}, -{0x0f18, 0x0f19, 220}, -{0x0f35, 0x0f35, 220}, -{0x0f37, 0x0f37, 220}, -{0x0f39, 0x0f39, 216}, -{0x0f71, 0x0f71, 129}, -{0x0f72, 0x0f72, 130}, -{0x0f74, 0x0f74, 132}, -{0x0f7a, 0x0f7d, 130}, -{0x0f80, 0x0f80, 130}, -{0x0f82, 0x0f83, 230}, -{0x0f84, 0x0f84, 9}, -{0x0f86, 0x0f87, 230}, -{0x0fc6, 0x0fc6, 220}, -{0x1037, 0x1037, 7}, -{0x1039, 0x103a, 9}, -{0x108d, 0x108d, 220}, -{0x135d, 0x135f, 230}, -{0x1714, 0x1715, 9}, -{0x1734, 0x1734, 9}, -{0x17d2, 0x17d2, 9}, -{0x17dd, 0x17dd, 230}, -{0x18a9, 0x18a9, 228}, -{0x1939, 0x1939, 222}, -{0x193a, 0x193a, 230}, -{0x193b, 0x193b, 220}, -{0x1a17, 0x1a17, 230}, -{0x1a18, 0x1a18, 220}, -{0x1a60, 0x1a60, 9}, -{0x1a75, 0x1a7c, 230}, -{0x1a7f, 0x1a7f, 220}, -{0x1ab0, 0x1ab4, 230}, -{0x1ab5, 0x1aba, 220}, -{0x1abb, 0x1abc, 230}, -{0x1abd, 0x1abd, 220}, -{0x1abf, 0x1ac0, 220}, -{0x1ac1, 0x1ac2, 230}, -{0x1ac3, 0x1ac4, 220}, -{0x1ac5, 0x1ac9, 230}, -{0x1aca, 0x1aca, 220}, -{0x1acb, 0x1ace, 230}, -{0x1b34, 0x1b34, 7}, -{0x1b44, 0x1b44, 9}, -{0x1b6b, 0x1b6b, 230}, -{0x1b6c, 0x1b6c, 220}, -{0x1b6d, 0x1b73, 230}, -{0x1baa, 0x1bab, 9}, -{0x1be6, 0x1be6, 7}, -{0x1bf2, 0x1bf3, 9}, -{0x1c37, 0x1c37, 7}, -{0x1cd0, 0x1cd2, 230}, -{0x1cd4, 0x1cd4, 1}, -{0x1cd5, 0x1cd9, 220}, -{0x1cda, 0x1cdb, 230}, -{0x1cdc, 0x1cdf, 220}, -{0x1ce0, 0x1ce0, 230}, -{0x1ce2, 0x1ce8, 1}, -{0x1ced, 0x1ced, 220}, -{0x1cf4, 0x1cf4, 230}, -{0x1cf8, 0x1cf9, 230}, -{0x1dc0, 0x1dc1, 230}, -{0x1dc2, 0x1dc2, 220}, -{0x1dc3, 0x1dc9, 230}, -{0x1dca, 0x1dca, 220}, -{0x1dcb, 0x1dcc, 230}, -{0x1dcd, 0x1dcd, 234}, -{0x1dce, 0x1dce, 214}, -{0x1dcf, 0x1dcf, 220}, -{0x1dd0, 0x1dd0, 202}, -{0x1dd1, 0x1df5, 230}, -{0x1df6, 0x1df6, 232}, -{0x1df7, 0x1df8, 228}, -{0x1df9, 0x1df9, 220}, -{0x1dfa, 0x1dfa, 218}, -{0x1dfb, 0x1dfb, 230}, -{0x1dfc, 0x1dfc, 233}, -{0x1dfd, 0x1dfd, 220}, -{0x1dfe, 0x1dfe, 230}, -{0x1dff, 0x1dff, 220}, -{0x20d0, 0x20d1, 230}, -{0x20d2, 0x20d3, 1}, -{0x20d4, 0x20d7, 230}, -{0x20d8, 0x20da, 1}, -{0x20db, 0x20dc, 230}, -{0x20e1, 0x20e1, 230}, -{0x20e5, 0x20e6, 1}, -{0x20e7, 0x20e7, 230}, -{0x20e8, 0x20e8, 220}, -{0x20e9, 0x20e9, 230}, -{0x20ea, 0x20eb, 1}, -{0x20ec, 0x20ef, 220}, -{0x20f0, 0x20f0, 230}, -{0x2cef, 0x2cf1, 230}, -{0x2d7f, 0x2d7f, 9}, -{0x2de0, 0x2dff, 230}, -{0x302a, 0x302a, 218}, -{0x302b, 0x302b, 228}, -{0x302c, 0x302c, 232}, -{0x302d, 0x302d, 222}, -{0x302e, 0x302f, 224}, -{0x3099, 0x309a, 8}, -{0xa66f, 0xa66f, 230}, -{0xa674, 0xa67d, 230}, -{0xa69e, 0xa69f, 230}, -{0xa6f0, 0xa6f1, 230}, -{0xa806, 0xa806, 9}, -{0xa82c, 0xa82c, 9}, -{0xa8c4, 0xa8c4, 9}, -{0xa8e0, 0xa8f1, 230}, -{0xa92b, 0xa92d, 220}, -{0xa953, 0xa953, 9}, -{0xa9b3, 0xa9b3, 7}, -{0xa9c0, 0xa9c0, 9}, -{0xaab0, 0xaab0, 230}, -{0xaab2, 0xaab3, 230}, -{0xaab4, 0xaab4, 220}, -{0xaab7, 0xaab8, 230}, -{0xaabe, 0xaabf, 230}, -{0xaac1, 0xaac1, 230}, -{0xaaf6, 0xaaf6, 9}, -{0xabed, 0xabed, 9}, -{0xfb1e, 0xfb1e, 26}, -{0xfe20, 0xfe26, 230}, -{0xfe27, 0xfe2d, 220}, -{0xfe2e, 0xfe2f, 230}, -{0x101fd, 0x101fd, 220}, -{0x102e0, 0x102e0, 220}, -{0x10376, 0x1037a, 230}, -{0x10a0d, 0x10a0d, 220}, -{0x10a0f, 0x10a0f, 230}, -{0x10a38, 0x10a38, 230}, -{0x10a39, 0x10a39, 1}, -{0x10a3a, 0x10a3a, 220}, -{0x10a3f, 0x10a3f, 9}, -{0x10ae5, 0x10ae5, 230}, -{0x10ae6, 0x10ae6, 220}, -{0x10d24, 0x10d27, 230}, -{0x10eab, 0x10eac, 230}, -{0x10efd, 0x10eff, 220}, -{0x10f46, 0x10f47, 220}, -{0x10f48, 0x10f4a, 230}, -{0x10f4b, 0x10f4b, 220}, -{0x10f4c, 0x10f4c, 230}, -{0x10f4d, 0x10f50, 220}, -{0x10f82, 0x10f82, 230}, -{0x10f83, 0x10f83, 220}, -{0x10f84, 0x10f84, 230}, -{0x10f85, 0x10f85, 220}, -{0x11046, 0x11046, 9}, -{0x11070, 0x11070, 9}, -{0x1107f, 0x1107f, 9}, -{0x110b9, 0x110b9, 9}, -{0x110ba, 0x110ba, 7}, -{0x11100, 0x11102, 230}, -{0x11133, 0x11134, 9}, -{0x11173, 0x11173, 7}, -{0x111c0, 0x111c0, 9}, -{0x111ca, 0x111ca, 7}, -{0x11235, 0x11235, 9}, -{0x11236, 0x11236, 7}, -{0x112e9, 0x112e9, 7}, -{0x112ea, 0x112ea, 9}, -{0x1133b, 0x1133c, 7}, -{0x1134d, 0x1134d, 9}, -{0x11366, 0x1136c, 230}, -{0x11370, 0x11374, 230}, -{0x11442, 0x11442, 9}, -{0x11446, 0x11446, 7}, -{0x1145e, 0x1145e, 230}, -{0x114c2, 0x114c2, 9}, -{0x114c3, 0x114c3, 7}, -{0x115bf, 0x115bf, 9}, -{0x115c0, 0x115c0, 7}, -{0x1163f, 0x1163f, 9}, -{0x116b6, 0x116b6, 9}, -{0x116b7, 0x116b7, 7}, -{0x1172b, 0x1172b, 9}, -{0x11839, 0x11839, 9}, -{0x1183a, 0x1183a, 7}, -{0x1193d, 0x1193e, 9}, -{0x11943, 0x11943, 7}, -{0x119e0, 0x119e0, 9}, -{0x11a34, 0x11a34, 9}, -{0x11a47, 0x11a47, 9}, -{0x11a99, 0x11a99, 9}, -{0x11c3f, 0x11c3f, 9}, -{0x11d42, 0x11d42, 7}, -{0x11d44, 0x11d45, 9}, -{0x11d97, 0x11d97, 9}, -{0x11f41, 0x11f42, 9}, -{0x16af0, 0x16af4, 1}, -{0x16b30, 0x16b36, 230}, -{0x16ff0, 0x16ff1, 6}, -{0x1bc9e, 0x1bc9e, 1}, -{0x1d165, 0x1d166, 216}, -{0x1d167, 0x1d169, 1}, -{0x1d16d, 0x1d16d, 226}, -{0x1d16e, 0x1d172, 216}, -{0x1d17b, 0x1d182, 220}, -{0x1d185, 0x1d189, 230}, -{0x1d18a, 0x1d18b, 220}, -{0x1d1aa, 0x1d1ad, 230}, -{0x1d242, 0x1d244, 230}, -{0x1e000, 0x1e006, 230}, -{0x1e008, 0x1e018, 230}, -{0x1e01b, 0x1e021, 230}, -{0x1e023, 0x1e024, 230}, -{0x1e026, 0x1e02a, 230}, -{0x1e08f, 0x1e08f, 230}, -{0x1e130, 0x1e136, 230}, -{0x1e2ae, 0x1e2ae, 230}, -{0x1e2ec, 0x1e2ef, 230}, -{0x1e4ec, 0x1e4ed, 232}, -{0x1e4ee, 0x1e4ee, 220}, -{0x1e4ef, 0x1e4ef, 230}, -{0x1e8d0, 0x1e8d6, 220}, -{0x1e944, 0x1e949, 230}, -{0x1e94a, 0x1e94a, 7}, diff --git a/unicode/known_chars.h b/unicode/known_chars.h deleted file mode 100644 index de1b313c0..000000000 --- a/unicode/known_chars.h +++ /dev/null @@ -1,716 +0,0 @@ -/* - * Autogenerated by read_ucd.py from The Unicode Standard 15.0.0 - * - * List the Unicode code points that are known to this version of the - * standard at all. - * - * Used by utils/unicode-known.c. - */ - -{0x0000, 0x0377}, -{0x037a, 0x037f}, -{0x0384, 0x038a}, -{0x038c, 0x038c}, -{0x038e, 0x03a1}, -{0x03a3, 0x052f}, -{0x0531, 0x0556}, -{0x0559, 0x058a}, -{0x058d, 0x058f}, -{0x0591, 0x05c7}, -{0x05d0, 0x05ea}, -{0x05ef, 0x05f4}, -{0x0600, 0x070d}, -{0x070f, 0x074a}, -{0x074d, 0x07b1}, -{0x07c0, 0x07fa}, -{0x07fd, 0x082d}, -{0x0830, 0x083e}, -{0x0840, 0x085b}, -{0x085e, 0x085e}, -{0x0860, 0x086a}, -{0x0870, 0x088e}, -{0x0890, 0x0891}, -{0x0898, 0x0983}, -{0x0985, 0x098c}, -{0x098f, 0x0990}, -{0x0993, 0x09a8}, -{0x09aa, 0x09b0}, -{0x09b2, 0x09b2}, -{0x09b6, 0x09b9}, -{0x09bc, 0x09c4}, -{0x09c7, 0x09c8}, -{0x09cb, 0x09ce}, -{0x09d7, 0x09d7}, -{0x09dc, 0x09dd}, -{0x09df, 0x09e3}, -{0x09e6, 0x09fe}, -{0x0a01, 0x0a03}, -{0x0a05, 0x0a0a}, -{0x0a0f, 0x0a10}, -{0x0a13, 0x0a28}, -{0x0a2a, 0x0a30}, -{0x0a32, 0x0a33}, -{0x0a35, 0x0a36}, -{0x0a38, 0x0a39}, -{0x0a3c, 0x0a3c}, -{0x0a3e, 0x0a42}, -{0x0a47, 0x0a48}, -{0x0a4b, 0x0a4d}, -{0x0a51, 0x0a51}, -{0x0a59, 0x0a5c}, -{0x0a5e, 0x0a5e}, -{0x0a66, 0x0a76}, -{0x0a81, 0x0a83}, -{0x0a85, 0x0a8d}, -{0x0a8f, 0x0a91}, -{0x0a93, 0x0aa8}, -{0x0aaa, 0x0ab0}, -{0x0ab2, 0x0ab3}, -{0x0ab5, 0x0ab9}, -{0x0abc, 0x0ac5}, -{0x0ac7, 0x0ac9}, -{0x0acb, 0x0acd}, -{0x0ad0, 0x0ad0}, -{0x0ae0, 0x0ae3}, -{0x0ae6, 0x0af1}, -{0x0af9, 0x0aff}, -{0x0b01, 0x0b03}, -{0x0b05, 0x0b0c}, -{0x0b0f, 0x0b10}, -{0x0b13, 0x0b28}, -{0x0b2a, 0x0b30}, -{0x0b32, 0x0b33}, -{0x0b35, 0x0b39}, -{0x0b3c, 0x0b44}, -{0x0b47, 0x0b48}, -{0x0b4b, 0x0b4d}, -{0x0b55, 0x0b57}, -{0x0b5c, 0x0b5d}, -{0x0b5f, 0x0b63}, -{0x0b66, 0x0b77}, -{0x0b82, 0x0b83}, -{0x0b85, 0x0b8a}, -{0x0b8e, 0x0b90}, -{0x0b92, 0x0b95}, -{0x0b99, 0x0b9a}, -{0x0b9c, 0x0b9c}, -{0x0b9e, 0x0b9f}, -{0x0ba3, 0x0ba4}, -{0x0ba8, 0x0baa}, -{0x0bae, 0x0bb9}, -{0x0bbe, 0x0bc2}, -{0x0bc6, 0x0bc8}, -{0x0bca, 0x0bcd}, -{0x0bd0, 0x0bd0}, -{0x0bd7, 0x0bd7}, -{0x0be6, 0x0bfa}, -{0x0c00, 0x0c0c}, -{0x0c0e, 0x0c10}, -{0x0c12, 0x0c28}, -{0x0c2a, 0x0c39}, -{0x0c3c, 0x0c44}, -{0x0c46, 0x0c48}, -{0x0c4a, 0x0c4d}, -{0x0c55, 0x0c56}, -{0x0c58, 0x0c5a}, -{0x0c5d, 0x0c5d}, -{0x0c60, 0x0c63}, -{0x0c66, 0x0c6f}, -{0x0c77, 0x0c8c}, -{0x0c8e, 0x0c90}, -{0x0c92, 0x0ca8}, -{0x0caa, 0x0cb3}, -{0x0cb5, 0x0cb9}, -{0x0cbc, 0x0cc4}, -{0x0cc6, 0x0cc8}, -{0x0cca, 0x0ccd}, -{0x0cd5, 0x0cd6}, -{0x0cdd, 0x0cde}, -{0x0ce0, 0x0ce3}, -{0x0ce6, 0x0cef}, -{0x0cf1, 0x0cf3}, -{0x0d00, 0x0d0c}, -{0x0d0e, 0x0d10}, -{0x0d12, 0x0d44}, -{0x0d46, 0x0d48}, -{0x0d4a, 0x0d4f}, -{0x0d54, 0x0d63}, -{0x0d66, 0x0d7f}, -{0x0d81, 0x0d83}, -{0x0d85, 0x0d96}, -{0x0d9a, 0x0db1}, -{0x0db3, 0x0dbb}, -{0x0dbd, 0x0dbd}, -{0x0dc0, 0x0dc6}, -{0x0dca, 0x0dca}, -{0x0dcf, 0x0dd4}, -{0x0dd6, 0x0dd6}, -{0x0dd8, 0x0ddf}, -{0x0de6, 0x0def}, -{0x0df2, 0x0df4}, -{0x0e01, 0x0e3a}, -{0x0e3f, 0x0e5b}, -{0x0e81, 0x0e82}, -{0x0e84, 0x0e84}, -{0x0e86, 0x0e8a}, -{0x0e8c, 0x0ea3}, -{0x0ea5, 0x0ea5}, -{0x0ea7, 0x0ebd}, -{0x0ec0, 0x0ec4}, -{0x0ec6, 0x0ec6}, -{0x0ec8, 0x0ece}, -{0x0ed0, 0x0ed9}, -{0x0edc, 0x0edf}, -{0x0f00, 0x0f47}, -{0x0f49, 0x0f6c}, -{0x0f71, 0x0f97}, -{0x0f99, 0x0fbc}, -{0x0fbe, 0x0fcc}, -{0x0fce, 0x0fda}, -{0x1000, 0x10c5}, -{0x10c7, 0x10c7}, -{0x10cd, 0x10cd}, -{0x10d0, 0x1248}, -{0x124a, 0x124d}, -{0x1250, 0x1256}, -{0x1258, 0x1258}, -{0x125a, 0x125d}, -{0x1260, 0x1288}, -{0x128a, 0x128d}, -{0x1290, 0x12b0}, -{0x12b2, 0x12b5}, -{0x12b8, 0x12be}, -{0x12c0, 0x12c0}, -{0x12c2, 0x12c5}, -{0x12c8, 0x12d6}, -{0x12d8, 0x1310}, -{0x1312, 0x1315}, -{0x1318, 0x135a}, -{0x135d, 0x137c}, -{0x1380, 0x1399}, -{0x13a0, 0x13f5}, -{0x13f8, 0x13fd}, -{0x1400, 0x169c}, -{0x16a0, 0x16f8}, -{0x1700, 0x1715}, -{0x171f, 0x1736}, -{0x1740, 0x1753}, -{0x1760, 0x176c}, -{0x176e, 0x1770}, -{0x1772, 0x1773}, -{0x1780, 0x17dd}, -{0x17e0, 0x17e9}, -{0x17f0, 0x17f9}, -{0x1800, 0x1819}, -{0x1820, 0x1878}, -{0x1880, 0x18aa}, -{0x18b0, 0x18f5}, -{0x1900, 0x191e}, -{0x1920, 0x192b}, -{0x1930, 0x193b}, -{0x1940, 0x1940}, -{0x1944, 0x196d}, -{0x1970, 0x1974}, -{0x1980, 0x19ab}, -{0x19b0, 0x19c9}, -{0x19d0, 0x19da}, -{0x19de, 0x1a1b}, -{0x1a1e, 0x1a5e}, -{0x1a60, 0x1a7c}, -{0x1a7f, 0x1a89}, -{0x1a90, 0x1a99}, -{0x1aa0, 0x1aad}, -{0x1ab0, 0x1ace}, -{0x1b00, 0x1b4c}, -{0x1b50, 0x1b7e}, -{0x1b80, 0x1bf3}, -{0x1bfc, 0x1c37}, -{0x1c3b, 0x1c49}, -{0x1c4d, 0x1c88}, -{0x1c90, 0x1cba}, -{0x1cbd, 0x1cc7}, -{0x1cd0, 0x1cfa}, -{0x1d00, 0x1f15}, -{0x1f18, 0x1f1d}, -{0x1f20, 0x1f45}, -{0x1f48, 0x1f4d}, -{0x1f50, 0x1f57}, -{0x1f59, 0x1f59}, -{0x1f5b, 0x1f5b}, -{0x1f5d, 0x1f5d}, -{0x1f5f, 0x1f7d}, -{0x1f80, 0x1fb4}, -{0x1fb6, 0x1fc4}, -{0x1fc6, 0x1fd3}, -{0x1fd6, 0x1fdb}, -{0x1fdd, 0x1fef}, -{0x1ff2, 0x1ff4}, -{0x1ff6, 0x1ffe}, -{0x2000, 0x2064}, -{0x2066, 0x2071}, -{0x2074, 0x208e}, -{0x2090, 0x209c}, -{0x20a0, 0x20c0}, -{0x20d0, 0x20f0}, -{0x2100, 0x218b}, -{0x2190, 0x2426}, -{0x2440, 0x244a}, -{0x2460, 0x2b73}, -{0x2b76, 0x2b95}, -{0x2b97, 0x2cf3}, -{0x2cf9, 0x2d25}, -{0x2d27, 0x2d27}, -{0x2d2d, 0x2d2d}, -{0x2d30, 0x2d67}, -{0x2d6f, 0x2d70}, -{0x2d7f, 0x2d96}, -{0x2da0, 0x2da6}, -{0x2da8, 0x2dae}, -{0x2db0, 0x2db6}, -{0x2db8, 0x2dbe}, -{0x2dc0, 0x2dc6}, -{0x2dc8, 0x2dce}, -{0x2dd0, 0x2dd6}, -{0x2dd8, 0x2dde}, -{0x2de0, 0x2e5d}, -{0x2e80, 0x2e99}, -{0x2e9b, 0x2ef3}, -{0x2f00, 0x2fd5}, -{0x2ff0, 0x2ffb}, -{0x3000, 0x303f}, -{0x3041, 0x3096}, -{0x3099, 0x30ff}, -{0x3105, 0x312f}, -{0x3131, 0x318e}, -{0x3190, 0x31e3}, -{0x31f0, 0x321e}, -{0x3220, 0xa48c}, -{0xa490, 0xa4c6}, -{0xa4d0, 0xa62b}, -{0xa640, 0xa6f7}, -{0xa700, 0xa7ca}, -{0xa7d0, 0xa7d1}, -{0xa7d3, 0xa7d3}, -{0xa7d5, 0xa7d9}, -{0xa7f2, 0xa82c}, -{0xa830, 0xa839}, -{0xa840, 0xa877}, -{0xa880, 0xa8c5}, -{0xa8ce, 0xa8d9}, -{0xa8e0, 0xa953}, -{0xa95f, 0xa97c}, -{0xa980, 0xa9cd}, -{0xa9cf, 0xa9d9}, -{0xa9de, 0xa9fe}, -{0xaa00, 0xaa36}, -{0xaa40, 0xaa4d}, -{0xaa50, 0xaa59}, -{0xaa5c, 0xaac2}, -{0xaadb, 0xaaf6}, -{0xab01, 0xab06}, -{0xab09, 0xab0e}, -{0xab11, 0xab16}, -{0xab20, 0xab26}, -{0xab28, 0xab2e}, -{0xab30, 0xab6b}, -{0xab70, 0xabed}, -{0xabf0, 0xabf9}, -{0xac00, 0xd7a3}, -{0xd7b0, 0xd7c6}, -{0xd7cb, 0xd7fb}, -{0xd800, 0xfa6d}, -{0xfa70, 0xfad9}, -{0xfb00, 0xfb06}, -{0xfb13, 0xfb17}, -{0xfb1d, 0xfb36}, -{0xfb38, 0xfb3c}, -{0xfb3e, 0xfb3e}, -{0xfb40, 0xfb41}, -{0xfb43, 0xfb44}, -{0xfb46, 0xfbc2}, -{0xfbd3, 0xfd8f}, -{0xfd92, 0xfdc7}, -{0xfdcf, 0xfdcf}, -{0xfdf0, 0xfe19}, -{0xfe20, 0xfe52}, -{0xfe54, 0xfe66}, -{0xfe68, 0xfe6b}, -{0xfe70, 0xfe74}, -{0xfe76, 0xfefc}, -{0xfeff, 0xfeff}, -{0xff01, 0xffbe}, -{0xffc2, 0xffc7}, -{0xffca, 0xffcf}, -{0xffd2, 0xffd7}, -{0xffda, 0xffdc}, -{0xffe0, 0xffe6}, -{0xffe8, 0xffee}, -{0xfff9, 0xfffd}, -{0x10000, 0x1000b}, -{0x1000d, 0x10026}, -{0x10028, 0x1003a}, -{0x1003c, 0x1003d}, -{0x1003f, 0x1004d}, -{0x10050, 0x1005d}, -{0x10080, 0x100fa}, -{0x10100, 0x10102}, -{0x10107, 0x10133}, -{0x10137, 0x1018e}, -{0x10190, 0x1019c}, -{0x101a0, 0x101a0}, -{0x101d0, 0x101fd}, -{0x10280, 0x1029c}, -{0x102a0, 0x102d0}, -{0x102e0, 0x102fb}, -{0x10300, 0x10323}, -{0x1032d, 0x1034a}, -{0x10350, 0x1037a}, -{0x10380, 0x1039d}, -{0x1039f, 0x103c3}, -{0x103c8, 0x103d5}, -{0x10400, 0x1049d}, -{0x104a0, 0x104a9}, -{0x104b0, 0x104d3}, -{0x104d8, 0x104fb}, -{0x10500, 0x10527}, -{0x10530, 0x10563}, -{0x1056f, 0x1057a}, -{0x1057c, 0x1058a}, -{0x1058c, 0x10592}, -{0x10594, 0x10595}, -{0x10597, 0x105a1}, -{0x105a3, 0x105b1}, -{0x105b3, 0x105b9}, -{0x105bb, 0x105bc}, -{0x10600, 0x10736}, -{0x10740, 0x10755}, -{0x10760, 0x10767}, -{0x10780, 0x10785}, -{0x10787, 0x107b0}, -{0x107b2, 0x107ba}, -{0x10800, 0x10805}, -{0x10808, 0x10808}, -{0x1080a, 0x10835}, -{0x10837, 0x10838}, -{0x1083c, 0x1083c}, -{0x1083f, 0x10855}, -{0x10857, 0x1089e}, -{0x108a7, 0x108af}, -{0x108e0, 0x108f2}, -{0x108f4, 0x108f5}, -{0x108fb, 0x1091b}, -{0x1091f, 0x10939}, -{0x1093f, 0x1093f}, -{0x10980, 0x109b7}, -{0x109bc, 0x109cf}, -{0x109d2, 0x10a03}, -{0x10a05, 0x10a06}, -{0x10a0c, 0x10a13}, -{0x10a15, 0x10a17}, -{0x10a19, 0x10a35}, -{0x10a38, 0x10a3a}, -{0x10a3f, 0x10a48}, -{0x10a50, 0x10a58}, -{0x10a60, 0x10a9f}, -{0x10ac0, 0x10ae6}, -{0x10aeb, 0x10af6}, -{0x10b00, 0x10b35}, -{0x10b39, 0x10b55}, -{0x10b58, 0x10b72}, -{0x10b78, 0x10b91}, -{0x10b99, 0x10b9c}, -{0x10ba9, 0x10baf}, -{0x10c00, 0x10c48}, -{0x10c80, 0x10cb2}, -{0x10cc0, 0x10cf2}, -{0x10cfa, 0x10d27}, -{0x10d30, 0x10d39}, -{0x10e60, 0x10e7e}, -{0x10e80, 0x10ea9}, -{0x10eab, 0x10ead}, -{0x10eb0, 0x10eb1}, -{0x10efd, 0x10f27}, -{0x10f30, 0x10f59}, -{0x10f70, 0x10f89}, -{0x10fb0, 0x10fcb}, -{0x10fe0, 0x10ff6}, -{0x11000, 0x1104d}, -{0x11052, 0x11075}, -{0x1107f, 0x110c2}, -{0x110cd, 0x110cd}, -{0x110d0, 0x110e8}, -{0x110f0, 0x110f9}, -{0x11100, 0x11134}, -{0x11136, 0x11147}, -{0x11150, 0x11176}, -{0x11180, 0x111df}, -{0x111e1, 0x111f4}, -{0x11200, 0x11211}, -{0x11213, 0x11241}, -{0x11280, 0x11286}, -{0x11288, 0x11288}, -{0x1128a, 0x1128d}, -{0x1128f, 0x1129d}, -{0x1129f, 0x112a9}, -{0x112b0, 0x112ea}, -{0x112f0, 0x112f9}, -{0x11300, 0x11303}, -{0x11305, 0x1130c}, -{0x1130f, 0x11310}, -{0x11313, 0x11328}, -{0x1132a, 0x11330}, -{0x11332, 0x11333}, -{0x11335, 0x11339}, -{0x1133b, 0x11344}, -{0x11347, 0x11348}, -{0x1134b, 0x1134d}, -{0x11350, 0x11350}, -{0x11357, 0x11357}, -{0x1135d, 0x11363}, -{0x11366, 0x1136c}, -{0x11370, 0x11374}, -{0x11400, 0x1145b}, -{0x1145d, 0x11461}, -{0x11480, 0x114c7}, -{0x114d0, 0x114d9}, -{0x11580, 0x115b5}, -{0x115b8, 0x115dd}, -{0x11600, 0x11644}, -{0x11650, 0x11659}, -{0x11660, 0x1166c}, -{0x11680, 0x116b9}, -{0x116c0, 0x116c9}, -{0x11700, 0x1171a}, -{0x1171d, 0x1172b}, -{0x11730, 0x11746}, -{0x11800, 0x1183b}, -{0x118a0, 0x118f2}, -{0x118ff, 0x11906}, -{0x11909, 0x11909}, -{0x1190c, 0x11913}, -{0x11915, 0x11916}, -{0x11918, 0x11935}, -{0x11937, 0x11938}, -{0x1193b, 0x11946}, -{0x11950, 0x11959}, -{0x119a0, 0x119a7}, -{0x119aa, 0x119d7}, -{0x119da, 0x119e4}, -{0x11a00, 0x11a47}, -{0x11a50, 0x11aa2}, -{0x11ab0, 0x11af8}, -{0x11b00, 0x11b09}, -{0x11c00, 0x11c08}, -{0x11c0a, 0x11c36}, -{0x11c38, 0x11c45}, -{0x11c50, 0x11c6c}, -{0x11c70, 0x11c8f}, -{0x11c92, 0x11ca7}, -{0x11ca9, 0x11cb6}, -{0x11d00, 0x11d06}, -{0x11d08, 0x11d09}, -{0x11d0b, 0x11d36}, -{0x11d3a, 0x11d3a}, -{0x11d3c, 0x11d3d}, -{0x11d3f, 0x11d47}, -{0x11d50, 0x11d59}, -{0x11d60, 0x11d65}, -{0x11d67, 0x11d68}, -{0x11d6a, 0x11d8e}, -{0x11d90, 0x11d91}, -{0x11d93, 0x11d98}, -{0x11da0, 0x11da9}, -{0x11ee0, 0x11ef8}, -{0x11f00, 0x11f10}, -{0x11f12, 0x11f3a}, -{0x11f3e, 0x11f59}, -{0x11fb0, 0x11fb0}, -{0x11fc0, 0x11ff1}, -{0x11fff, 0x12399}, -{0x12400, 0x1246e}, -{0x12470, 0x12474}, -{0x12480, 0x12543}, -{0x12f90, 0x12ff2}, -{0x13000, 0x13455}, -{0x14400, 0x14646}, -{0x16800, 0x16a38}, -{0x16a40, 0x16a5e}, -{0x16a60, 0x16a69}, -{0x16a6e, 0x16abe}, -{0x16ac0, 0x16ac9}, -{0x16ad0, 0x16aed}, -{0x16af0, 0x16af5}, -{0x16b00, 0x16b45}, -{0x16b50, 0x16b59}, -{0x16b5b, 0x16b61}, -{0x16b63, 0x16b77}, -{0x16b7d, 0x16b8f}, -{0x16e40, 0x16e9a}, -{0x16f00, 0x16f4a}, -{0x16f4f, 0x16f87}, -{0x16f8f, 0x16f9f}, -{0x16fe0, 0x16fe4}, -{0x16ff0, 0x16ff1}, -{0x17000, 0x187f7}, -{0x18800, 0x18cd5}, -{0x18d00, 0x18d08}, -{0x1aff0, 0x1aff3}, -{0x1aff5, 0x1affb}, -{0x1affd, 0x1affe}, -{0x1b000, 0x1b122}, -{0x1b132, 0x1b132}, -{0x1b150, 0x1b152}, -{0x1b155, 0x1b155}, -{0x1b164, 0x1b167}, -{0x1b170, 0x1b2fb}, -{0x1bc00, 0x1bc6a}, -{0x1bc70, 0x1bc7c}, -{0x1bc80, 0x1bc88}, -{0x1bc90, 0x1bc99}, -{0x1bc9c, 0x1bca3}, -{0x1cf00, 0x1cf2d}, -{0x1cf30, 0x1cf46}, -{0x1cf50, 0x1cfc3}, -{0x1d000, 0x1d0f5}, -{0x1d100, 0x1d126}, -{0x1d129, 0x1d1ea}, -{0x1d200, 0x1d245}, -{0x1d2c0, 0x1d2d3}, -{0x1d2e0, 0x1d2f3}, -{0x1d300, 0x1d356}, -{0x1d360, 0x1d378}, -{0x1d400, 0x1d454}, -{0x1d456, 0x1d49c}, -{0x1d49e, 0x1d49f}, -{0x1d4a2, 0x1d4a2}, -{0x1d4a5, 0x1d4a6}, -{0x1d4a9, 0x1d4ac}, -{0x1d4ae, 0x1d4b9}, -{0x1d4bb, 0x1d4bb}, -{0x1d4bd, 0x1d4c3}, -{0x1d4c5, 0x1d505}, -{0x1d507, 0x1d50a}, -{0x1d50d, 0x1d514}, -{0x1d516, 0x1d51c}, -{0x1d51e, 0x1d539}, -{0x1d53b, 0x1d53e}, -{0x1d540, 0x1d544}, -{0x1d546, 0x1d546}, -{0x1d54a, 0x1d550}, -{0x1d552, 0x1d6a5}, -{0x1d6a8, 0x1d7cb}, -{0x1d7ce, 0x1da8b}, -{0x1da9b, 0x1da9f}, -{0x1daa1, 0x1daaf}, -{0x1df00, 0x1df1e}, -{0x1df25, 0x1df2a}, -{0x1e000, 0x1e006}, -{0x1e008, 0x1e018}, -{0x1e01b, 0x1e021}, -{0x1e023, 0x1e024}, -{0x1e026, 0x1e02a}, -{0x1e030, 0x1e06d}, -{0x1e08f, 0x1e08f}, -{0x1e100, 0x1e12c}, -{0x1e130, 0x1e13d}, -{0x1e140, 0x1e149}, -{0x1e14e, 0x1e14f}, -{0x1e290, 0x1e2ae}, -{0x1e2c0, 0x1e2f9}, -{0x1e2ff, 0x1e2ff}, -{0x1e4d0, 0x1e4f9}, -{0x1e7e0, 0x1e7e6}, -{0x1e7e8, 0x1e7eb}, -{0x1e7ed, 0x1e7ee}, -{0x1e7f0, 0x1e7fe}, -{0x1e800, 0x1e8c4}, -{0x1e8c7, 0x1e8d6}, -{0x1e900, 0x1e94b}, -{0x1e950, 0x1e959}, -{0x1e95e, 0x1e95f}, -{0x1ec71, 0x1ecb4}, -{0x1ed01, 0x1ed3d}, -{0x1ee00, 0x1ee03}, -{0x1ee05, 0x1ee1f}, -{0x1ee21, 0x1ee22}, -{0x1ee24, 0x1ee24}, -{0x1ee27, 0x1ee27}, -{0x1ee29, 0x1ee32}, -{0x1ee34, 0x1ee37}, -{0x1ee39, 0x1ee39}, -{0x1ee3b, 0x1ee3b}, -{0x1ee42, 0x1ee42}, -{0x1ee47, 0x1ee47}, -{0x1ee49, 0x1ee49}, -{0x1ee4b, 0x1ee4b}, -{0x1ee4d, 0x1ee4f}, -{0x1ee51, 0x1ee52}, -{0x1ee54, 0x1ee54}, -{0x1ee57, 0x1ee57}, -{0x1ee59, 0x1ee59}, -{0x1ee5b, 0x1ee5b}, -{0x1ee5d, 0x1ee5d}, -{0x1ee5f, 0x1ee5f}, -{0x1ee61, 0x1ee62}, -{0x1ee64, 0x1ee64}, -{0x1ee67, 0x1ee6a}, -{0x1ee6c, 0x1ee72}, -{0x1ee74, 0x1ee77}, -{0x1ee79, 0x1ee7c}, -{0x1ee7e, 0x1ee7e}, -{0x1ee80, 0x1ee89}, -{0x1ee8b, 0x1ee9b}, -{0x1eea1, 0x1eea3}, -{0x1eea5, 0x1eea9}, -{0x1eeab, 0x1eebb}, -{0x1eef0, 0x1eef1}, -{0x1f000, 0x1f02b}, -{0x1f030, 0x1f093}, -{0x1f0a0, 0x1f0ae}, -{0x1f0b1, 0x1f0bf}, -{0x1f0c1, 0x1f0cf}, -{0x1f0d1, 0x1f0f5}, -{0x1f100, 0x1f1ad}, -{0x1f1e6, 0x1f202}, -{0x1f210, 0x1f23b}, -{0x1f240, 0x1f248}, -{0x1f250, 0x1f251}, -{0x1f260, 0x1f265}, -{0x1f300, 0x1f6d7}, -{0x1f6dc, 0x1f6ec}, -{0x1f6f0, 0x1f6fc}, -{0x1f700, 0x1f776}, -{0x1f77b, 0x1f7d9}, -{0x1f7e0, 0x1f7eb}, -{0x1f7f0, 0x1f7f0}, -{0x1f800, 0x1f80b}, -{0x1f810, 0x1f847}, -{0x1f850, 0x1f859}, -{0x1f860, 0x1f887}, -{0x1f890, 0x1f8ad}, -{0x1f8b0, 0x1f8b1}, -{0x1f900, 0x1fa53}, -{0x1fa60, 0x1fa6d}, -{0x1fa70, 0x1fa7c}, -{0x1fa80, 0x1fa88}, -{0x1fa90, 0x1fabd}, -{0x1fabf, 0x1fac5}, -{0x1face, 0x1fadb}, -{0x1fae0, 0x1fae8}, -{0x1faf0, 0x1faf8}, -{0x1fb00, 0x1fb92}, -{0x1fb94, 0x1fbca}, -{0x1fbf0, 0x1fbf9}, -{0x20000, 0x2a6df}, -{0x2a700, 0x2b739}, -{0x2b740, 0x2b81d}, -{0x2b820, 0x2cea1}, -{0x2ceb0, 0x2ebe0}, -{0x2f800, 0x2fa1d}, -{0x30000, 0x3134a}, -{0x31350, 0x323af}, -{0xe0001, 0xe0001}, -{0xe0020, 0xe007f}, -{0xe0100, 0xe01ef}, -{0xf0000, 0xffffd}, -{0x100000, 0x10fffd}, diff --git a/unicode/nonspacing_chars.h b/unicode/nonspacing_chars.h deleted file mode 100644 index 147275940..000000000 --- a/unicode/nonspacing_chars.h +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Autogenerated by read_ucd.py from The Unicode Standard 15.0.0 - * - * Identify Unicode characters that occupy no character cells of a - * terminal. - * - * Used by utils/wcwidth.c. - */ - -{0x0300, 0x036f}, -{0x0483, 0x0489}, -{0x0591, 0x05bd}, -{0x05bf, 0x05bf}, -{0x05c1, 0x05c2}, -{0x05c4, 0x05c5}, -{0x05c7, 0x05c7}, -{0x0600, 0x0605}, -{0x0610, 0x061a}, -{0x061c, 0x061c}, -{0x064b, 0x065f}, -{0x0670, 0x0670}, -{0x06d6, 0x06dd}, -{0x06df, 0x06e4}, -{0x06e7, 0x06e8}, -{0x06ea, 0x06ed}, -{0x070f, 0x070f}, -{0x0711, 0x0711}, -{0x0730, 0x074a}, -{0x07a6, 0x07b0}, -{0x07eb, 0x07f3}, -{0x07fd, 0x07fd}, -{0x0816, 0x0819}, -{0x081b, 0x0823}, -{0x0825, 0x0827}, -{0x0829, 0x082d}, -{0x0859, 0x085b}, -{0x0890, 0x0891}, -{0x0898, 0x089f}, -{0x08ca, 0x0902}, -{0x093a, 0x093a}, -{0x093c, 0x093c}, -{0x0941, 0x0948}, -{0x094d, 0x094d}, -{0x0951, 0x0957}, -{0x0962, 0x0963}, -{0x0981, 0x0981}, -{0x09bc, 0x09bc}, -{0x09c1, 0x09c4}, -{0x09cd, 0x09cd}, -{0x09e2, 0x09e3}, -{0x09fe, 0x09fe}, -{0x0a01, 0x0a02}, -{0x0a3c, 0x0a3c}, -{0x0a41, 0x0a42}, -{0x0a47, 0x0a48}, -{0x0a4b, 0x0a4d}, -{0x0a51, 0x0a51}, -{0x0a70, 0x0a71}, -{0x0a75, 0x0a75}, -{0x0a81, 0x0a82}, -{0x0abc, 0x0abc}, -{0x0ac1, 0x0ac5}, -{0x0ac7, 0x0ac8}, -{0x0acd, 0x0acd}, -{0x0ae2, 0x0ae3}, -{0x0afa, 0x0aff}, -{0x0b01, 0x0b01}, -{0x0b3c, 0x0b3c}, -{0x0b3f, 0x0b3f}, -{0x0b41, 0x0b44}, -{0x0b4d, 0x0b4d}, -{0x0b55, 0x0b56}, -{0x0b62, 0x0b63}, -{0x0b82, 0x0b82}, -{0x0bc0, 0x0bc0}, -{0x0bcd, 0x0bcd}, -{0x0c00, 0x0c00}, -{0x0c04, 0x0c04}, -{0x0c3c, 0x0c3c}, -{0x0c3e, 0x0c40}, -{0x0c46, 0x0c48}, -{0x0c4a, 0x0c4d}, -{0x0c55, 0x0c56}, -{0x0c62, 0x0c63}, -{0x0c81, 0x0c81}, -{0x0cbc, 0x0cbc}, -{0x0cbf, 0x0cbf}, -{0x0cc6, 0x0cc6}, -{0x0ccc, 0x0ccd}, -{0x0ce2, 0x0ce3}, -{0x0d00, 0x0d01}, -{0x0d3b, 0x0d3c}, -{0x0d41, 0x0d44}, -{0x0d4d, 0x0d4d}, -{0x0d62, 0x0d63}, -{0x0d81, 0x0d81}, -{0x0dca, 0x0dca}, -{0x0dd2, 0x0dd4}, -{0x0dd6, 0x0dd6}, -{0x0e31, 0x0e31}, -{0x0e34, 0x0e3a}, -{0x0e47, 0x0e4e}, -{0x0eb1, 0x0eb1}, -{0x0eb4, 0x0ebc}, -{0x0ec8, 0x0ece}, -{0x0f18, 0x0f19}, -{0x0f35, 0x0f35}, -{0x0f37, 0x0f37}, -{0x0f39, 0x0f39}, -{0x0f71, 0x0f7e}, -{0x0f80, 0x0f84}, -{0x0f86, 0x0f87}, -{0x0f8d, 0x0f97}, -{0x0f99, 0x0fbc}, -{0x0fc6, 0x0fc6}, -{0x102d, 0x1030}, -{0x1032, 0x1037}, -{0x1039, 0x103a}, -{0x103d, 0x103e}, -{0x1058, 0x1059}, -{0x105e, 0x1060}, -{0x1071, 0x1074}, -{0x1082, 0x1082}, -{0x1085, 0x1086}, -{0x108d, 0x108d}, -{0x109d, 0x109d}, -{0x1160, 0x11ff}, -{0x135d, 0x135f}, -{0x1712, 0x1714}, -{0x1732, 0x1733}, -{0x1752, 0x1753}, -{0x1772, 0x1773}, -{0x17b4, 0x17b5}, -{0x17b7, 0x17bd}, -{0x17c6, 0x17c6}, -{0x17c9, 0x17d3}, -{0x17dd, 0x17dd}, -{0x180b, 0x180f}, -{0x1885, 0x1886}, -{0x18a9, 0x18a9}, -{0x1920, 0x1922}, -{0x1927, 0x1928}, -{0x1932, 0x1932}, -{0x1939, 0x193b}, -{0x1a17, 0x1a18}, -{0x1a1b, 0x1a1b}, -{0x1a56, 0x1a56}, -{0x1a58, 0x1a5e}, -{0x1a60, 0x1a60}, -{0x1a62, 0x1a62}, -{0x1a65, 0x1a6c}, -{0x1a73, 0x1a7c}, -{0x1a7f, 0x1a7f}, -{0x1ab0, 0x1ace}, -{0x1b00, 0x1b03}, -{0x1b34, 0x1b34}, -{0x1b36, 0x1b3a}, -{0x1b3c, 0x1b3c}, -{0x1b42, 0x1b42}, -{0x1b6b, 0x1b73}, -{0x1b80, 0x1b81}, -{0x1ba2, 0x1ba5}, -{0x1ba8, 0x1ba9}, -{0x1bab, 0x1bad}, -{0x1be6, 0x1be6}, -{0x1be8, 0x1be9}, -{0x1bed, 0x1bed}, -{0x1bef, 0x1bf1}, -{0x1c2c, 0x1c33}, -{0x1c36, 0x1c37}, -{0x1cd0, 0x1cd2}, -{0x1cd4, 0x1ce0}, -{0x1ce2, 0x1ce8}, -{0x1ced, 0x1ced}, -{0x1cf4, 0x1cf4}, -{0x1cf8, 0x1cf9}, -{0x1dc0, 0x1dff}, -{0x200b, 0x200f}, -{0x202a, 0x202e}, -{0x2060, 0x2064}, -{0x2066, 0x206f}, -{0x20d0, 0x20f0}, -{0x2cef, 0x2cf1}, -{0x2d7f, 0x2d7f}, -{0x2de0, 0x2dff}, -{0x302a, 0x302d}, -{0x3099, 0x309a}, -{0xa66f, 0xa672}, -{0xa674, 0xa67d}, -{0xa69e, 0xa69f}, -{0xa6f0, 0xa6f1}, -{0xa802, 0xa802}, -{0xa806, 0xa806}, -{0xa80b, 0xa80b}, -{0xa825, 0xa826}, -{0xa82c, 0xa82c}, -{0xa8c4, 0xa8c5}, -{0xa8e0, 0xa8f1}, -{0xa8ff, 0xa8ff}, -{0xa926, 0xa92d}, -{0xa947, 0xa951}, -{0xa980, 0xa982}, -{0xa9b3, 0xa9b3}, -{0xa9b6, 0xa9b9}, -{0xa9bc, 0xa9bd}, -{0xa9e5, 0xa9e5}, -{0xaa29, 0xaa2e}, -{0xaa31, 0xaa32}, -{0xaa35, 0xaa36}, -{0xaa43, 0xaa43}, -{0xaa4c, 0xaa4c}, -{0xaa7c, 0xaa7c}, -{0xaab0, 0xaab0}, -{0xaab2, 0xaab4}, -{0xaab7, 0xaab8}, -{0xaabe, 0xaabf}, -{0xaac1, 0xaac1}, -{0xaaec, 0xaaed}, -{0xaaf6, 0xaaf6}, -{0xabe5, 0xabe5}, -{0xabe8, 0xabe8}, -{0xabed, 0xabed}, -{0xfb1e, 0xfb1e}, -{0xfe00, 0xfe0f}, -{0xfe20, 0xfe2f}, -{0xfeff, 0xfeff}, -{0xfff9, 0xfffb}, -{0x101fd, 0x101fd}, -{0x102e0, 0x102e0}, -{0x10376, 0x1037a}, -{0x10a01, 0x10a03}, -{0x10a05, 0x10a06}, -{0x10a0c, 0x10a0f}, -{0x10a38, 0x10a3a}, -{0x10a3f, 0x10a3f}, -{0x10ae5, 0x10ae6}, -{0x10d24, 0x10d27}, -{0x10eab, 0x10eac}, -{0x10efd, 0x10eff}, -{0x10f46, 0x10f50}, -{0x10f82, 0x10f85}, -{0x11001, 0x11001}, -{0x11038, 0x11046}, -{0x11070, 0x11070}, -{0x11073, 0x11074}, -{0x1107f, 0x11081}, -{0x110b3, 0x110b6}, -{0x110b9, 0x110ba}, -{0x110bd, 0x110bd}, -{0x110c2, 0x110c2}, -{0x110cd, 0x110cd}, -{0x11100, 0x11102}, -{0x11127, 0x1112b}, -{0x1112d, 0x11134}, -{0x11173, 0x11173}, -{0x11180, 0x11181}, -{0x111b6, 0x111be}, -{0x111c9, 0x111cc}, -{0x111cf, 0x111cf}, -{0x1122f, 0x11231}, -{0x11234, 0x11234}, -{0x11236, 0x11237}, -{0x1123e, 0x1123e}, -{0x11241, 0x11241}, -{0x112df, 0x112df}, -{0x112e3, 0x112ea}, -{0x11300, 0x11301}, -{0x1133b, 0x1133c}, -{0x11340, 0x11340}, -{0x11366, 0x1136c}, -{0x11370, 0x11374}, -{0x11438, 0x1143f}, -{0x11442, 0x11444}, -{0x11446, 0x11446}, -{0x1145e, 0x1145e}, -{0x114b3, 0x114b8}, -{0x114ba, 0x114ba}, -{0x114bf, 0x114c0}, -{0x114c2, 0x114c3}, -{0x115b2, 0x115b5}, -{0x115bc, 0x115bd}, -{0x115bf, 0x115c0}, -{0x115dc, 0x115dd}, -{0x11633, 0x1163a}, -{0x1163d, 0x1163d}, -{0x1163f, 0x11640}, -{0x116ab, 0x116ab}, -{0x116ad, 0x116ad}, -{0x116b0, 0x116b5}, -{0x116b7, 0x116b7}, -{0x1171d, 0x1171f}, -{0x11722, 0x11725}, -{0x11727, 0x1172b}, -{0x1182f, 0x11837}, -{0x11839, 0x1183a}, -{0x1193b, 0x1193c}, -{0x1193e, 0x1193e}, -{0x11943, 0x11943}, -{0x119d4, 0x119d7}, -{0x119da, 0x119db}, -{0x119e0, 0x119e0}, -{0x11a01, 0x11a0a}, -{0x11a33, 0x11a38}, -{0x11a3b, 0x11a3e}, -{0x11a47, 0x11a47}, -{0x11a51, 0x11a56}, -{0x11a59, 0x11a5b}, -{0x11a8a, 0x11a96}, -{0x11a98, 0x11a99}, -{0x11c30, 0x11c36}, -{0x11c38, 0x11c3d}, -{0x11c3f, 0x11c3f}, -{0x11c92, 0x11ca7}, -{0x11caa, 0x11cb0}, -{0x11cb2, 0x11cb3}, -{0x11cb5, 0x11cb6}, -{0x11d31, 0x11d36}, -{0x11d3a, 0x11d3a}, -{0x11d3c, 0x11d3d}, -{0x11d3f, 0x11d45}, -{0x11d47, 0x11d47}, -{0x11d90, 0x11d91}, -{0x11d95, 0x11d95}, -{0x11d97, 0x11d97}, -{0x11ef3, 0x11ef4}, -{0x11f00, 0x11f01}, -{0x11f36, 0x11f3a}, -{0x11f40, 0x11f40}, -{0x11f42, 0x11f42}, -{0x13430, 0x13440}, -{0x13447, 0x13455}, -{0x16af0, 0x16af4}, -{0x16b30, 0x16b36}, -{0x16f4f, 0x16f4f}, -{0x16f8f, 0x16f92}, -{0x16fe4, 0x16fe4}, -{0x1bc9d, 0x1bc9e}, -{0x1bca0, 0x1bca3}, -{0x1cf00, 0x1cf2d}, -{0x1cf30, 0x1cf46}, -{0x1d167, 0x1d169}, -{0x1d173, 0x1d182}, -{0x1d185, 0x1d18b}, -{0x1d1aa, 0x1d1ad}, -{0x1d242, 0x1d244}, -{0x1da00, 0x1da36}, -{0x1da3b, 0x1da6c}, -{0x1da75, 0x1da75}, -{0x1da84, 0x1da84}, -{0x1da9b, 0x1da9f}, -{0x1daa1, 0x1daaf}, -{0x1e000, 0x1e006}, -{0x1e008, 0x1e018}, -{0x1e01b, 0x1e021}, -{0x1e023, 0x1e024}, -{0x1e026, 0x1e02a}, -{0x1e08f, 0x1e08f}, -{0x1e130, 0x1e136}, -{0x1e2ae, 0x1e2ae}, -{0x1e2ec, 0x1e2ef}, -{0x1e4ec, 0x1e4ef}, -{0x1e8d0, 0x1e8d6}, -{0x1e944, 0x1e94a}, -{0xe0001, 0xe0001}, -{0xe0020, 0xe007f}, -{0xe0100, 0xe01ef}, diff --git a/unicode/read_ucd.py b/unicode/read_ucd.py deleted file mode 100644 index ba9f0f311..000000000 --- a/unicode/read_ucd.py +++ /dev/null @@ -1,510 +0,0 @@ -#!/usr/bin/env python3 - -# Tool to read various files from the Unicode character database and -# generate headers containing derived arrays and lookup tables needed -# by PuTTY. -# -# The aim is to have this be a single tool which you can easily re-run -# against a new version of Unicode, simply by pointing it at an -# appropriate UCD.zip or a directory containing the same files -# unpacked. - -import argparse -import collections -import io -import os -import string -import sys -import zipfile - -UCDRecord = collections.namedtuple('UCDRecord', [ - 'c', - 'General_Category', - 'Canonical_Combining_Class', - 'Bidi_Class', - 'Decomposition_Type', - 'Decomposition_Mapping', -]) - -def to_ranges(iterable): - """Collect together adjacent ranges in a list of (key, value) pairs. - - The input iterable should deliver a sequence of (key, value) pairs - in which the keys are integers in sorted order. The output is a - sequence of tuples with structure ((start, end), value), each - indicating that all the keys [start, start+1, ..., end] go with - that value. - """ - start = end = val = None - - for k, v in iterable: - if (k-1, v) == (end, val): - end = k - else: - if start is not None: - yield (start, end), val - start, end, val = k, k, v - - if start is not None: - yield (start, end), val - -def map_to_ranges(m): - """Convert an integer-keyed map into a list of (range, value) pairs.""" - yield from to_ranges(sorted(m.items())) - -def set_to_ranges(s): - """Convert a set into a list of ranges.""" - for r, _ in to_ranges((x, None) for x in sorted(s)): - yield r - -def lines(iterable, keep_comments=False): - """Deliver the lines of a Unicode data file. - - The input iterable should yield raw lines of the file: for - example, it can be the file handle itself. The output values have - their newlines removed, comments and trailing spaces deleted, and - blank lines discarded. - """ - for line in iter(iterable): - line = line.rstrip("\r\n") - if not keep_comments: - line = line.split("#", 1)[0] - line = line.rstrip(" \t") - if line == "": - continue - yield line - -class Main: - def run(self): - "Parse arguments and generate all the output files." - - parser = argparse.ArgumentParser( - description='Build UCD-derived source files.') - parser.add_argument("ucd", help="UCD to work from, either UCD.zip or " - "a directory full of unpacked files.") - args = parser.parse_args() - - if os.path.isdir(args.ucd): - ucd_dir = args.ucd - self.open_ucd_file = lambda filename: ( - open(os.path.join(ucd_dir, filename))) - else: - ucd_zip = zipfile.ZipFile(args.ucd) - self.open_ucd_file = lambda filename: ( - io.TextIOWrapper(ucd_zip.open(filename))) - - self.find_unicode_version() - - with open("version.h", "w") as fh: - self.write_version_header(fh) - with open("bidi_type.h", "w") as fh: - self.write_bidi_type_table(fh) - with open("bidi_mirror.h", "w") as fh: - self.write_bidi_mirroring_table(fh) - with open("bidi_brackets.h", "w") as fh: - self.write_bidi_brackets_table(fh) - with open("nonspacing_chars.h", "w") as fh: - self.write_nonspacing_chars_list(fh) - with open("wide_chars.h", "w") as fh: - self.write_wide_chars_list(fh) - with open("ambiguous_wide_chars.h", "w") as fh: - self.write_ambiguous_wide_chars_list(fh) - with open("known_chars.h", "w") as fh: - self.write_known_chars_table(fh) - with open("combining_classes.h", "w") as fh: - self.write_combining_class_table(fh) - with open("canonical_decomp.h", "w") as fh: - self.write_canonical_decomp_table(fh) - with open("canonical_comp.h", "w") as fh: - self.write_canonical_comp_table(fh) - - def find_unicode_version(self): - """Find out the version of Unicode. - - This is read from the top of NamesList.txt, which has the - closest thing to a machine-readable statement of the version - number that I found in the whole collection of files. - """ - with self.open_ucd_file("NamesList.txt") as fh: - for line in lines(fh): - if line.startswith("@@@\t"): - self.unicode_version_full = line[4:] - self.unicode_version_short = " ".join( - w for w in self.unicode_version_full.split(" ") - if any(c in string.digits for c in w)) - return - - @property - def UnicodeData(self): - """Records from UnicodeData.txt. - - Each yielded item is a UCDRecord tuple. - """ - with self.open_ucd_file("UnicodeData.txt") as fh: - for line in lines(fh): - # Split up the line into its raw fields. - ( - codepoint, name, category, cclass, bidiclass, decomp, - num6, num7, num8, bidimirrored, obsolete_unicode1_name, - obsolete_comment, uppercase, lowercase, titlecase, - ) = line.split(";") - - # By default, we expect that this record describes - # just one code point. - codepoints = [int(codepoint, 16)] - - # Spot the special markers where consecutive lines say - # and , indicating that the - # entire range of code points in between are treated - # the same. If so, we replace 'codepoints' with a - # range object. - if "<" in name: - assert name.startswith("<") and name.endswith(">"), ( - "Confusing < in character name: {!r}".format(line)) - name_pieces = [piece.strip(" \t") for piece in - name.lstrip("<").rstrip(">").split(",")] - if "First" in name_pieces: - assert isinstance(codepoints, list) - prev_line_was_first = True - prev_codepoint = codepoints[0] - continue - elif "Last" in name_pieces: - assert prev_line_was_first - codepoints = range(prev_codepoint, codepoints[0]+1) - del prev_codepoint - prev_line_was_first = False - - # Decode some of the raw fields into more cooked - # forms. - cclass = int(cclass) - - # Separate the decomposition field into decomposition - # type and mapping. - if decomp == "": - dtype = decomp = None - elif "<" not in decomp: - dtype = 'canonical' - else: - assert decomp.startswith("<") - dtype, decomp = decomp[1:].split(">", 1) - decomp = decomp.lstrip(" ") - # And decode the mapping part from hex strings to integers. - if decomp is not None: - decomp = [int(w, 16) for w in decomp.split(" ")] - - # And yield a UCDRecord for each code point in our - # range. - for codepoint in codepoints: - yield UCDRecord( - c=codepoint, - General_Category=category, - Canonical_Combining_Class=cclass, - Bidi_Class=bidiclass, - Decomposition_Type=dtype, - Decomposition_Mapping=decomp, - ) - - @property - def BidiMirroring(self): - """Parsed character pairs from BidiMirroring.txt. - - Each yielded tuple is a pair of Unicode code points. - """ - with self.open_ucd_file("BidiMirroring.txt") as fh: - for line in lines(fh): - cs1, cs2 = line.split(";") - c1 = int(cs1, 16) - c2 = int(cs2, 16) - yield c1, c2 - - @property - def BidiBrackets(self): - """Bracket pairs from BidiBrackets.txt. - - Each yielded tuple is a pair of Unicode code points, followed - by either 'o', 'c' or 'n' to indicate whether the first one is - an open or closing parenthesis or neither. - """ - with self.open_ucd_file("BidiBrackets.txt") as fh: - for line in lines(fh): - cs1, cs2, kind = line.split(";") - c1 = int(cs1, 16) - c2 = int(cs2, 16) - kind = kind.strip(" \t") - yield c1, c2, kind - - @property - def EastAsianWidth(self): - """East Asian width types from EastAsianWidth.txt. - - Each yielded tuple is (code point, width type). - """ - with self.open_ucd_file("EastAsianWidth.txt") as fh: - for line in lines(fh): - fields = line.split(";") - if ".." in fields[0]: - start, end = [int(s, 16) for s in fields[0].split("..")] - cs = range(start, end+1) - else: - cs = [int(fields[0], 16)] - for c in cs: - yield c, fields[1] - - @property - def CompositionExclusions(self): - """Composition exclusions from CompositionExclusions.txt. - - Each yielded item is just a code point. - """ - with self.open_ucd_file("CompositionExclusions.txt") as fh: - for line in lines(fh): - yield int(line, 16) - - def write_file_header_comment(self, fh, description): - print("/*", file=fh) - print(" * Autogenerated by read_ucd.py from", - self.unicode_version_full, file=fh) - print(" *", file=fh) - for line in description.strip("\n").split("\n"): - print(" *" + (" " if line != "" else "") + line, file=fh) - print(" */", file=fh) - print(file=fh) - - def write_version_header(self, fh): - self.write_file_header_comment(fh, """ - -String literals giving the currently supported version of Unicode. -Useful for error messages and 'about' boxes. - -""") - assert all(0x20 <= ord(c) < 0x7F and c != '"' - for c in self.unicode_version_full) - - print("#define UNICODE_VERSION_FULL \"{}\"".format( - self.unicode_version_full), file=fh) - print("#define UNICODE_VERSION_SHORT \"{}\"".format( - self.unicode_version_short), file=fh) - - def write_bidi_type_table(self, fh): - self.write_file_header_comment(fh, """ - -Bidirectional type of every Unicode character, excluding those with -type ON. - -Used by terminal/bidi.c, whose associated lookup function returns ON -by default for anything not in this list. - -""") - types = {} - - for rec in self.UnicodeData: - if rec.Bidi_Class != "ON": - types[rec.c] = rec.Bidi_Class - - for (start, end), t in map_to_ranges(types): - print(f"{{0x{start:04x}, 0x{end:04x}, {t}}},", file=fh) - - def write_bidi_mirroring_table(self, fh): - self.write_file_header_comment(fh, """ - -Map each Unicode character to its mirrored form when printing right to -left. - -Used by terminal/bidi.c. - -""") - bidi_mirror = {} - for c1, c2 in self.BidiMirroring: - assert bidi_mirror.get(c1, c2) == c2, f"Clash at {c1:%04X}" - bidi_mirror[c1] = c2 - assert bidi_mirror.get(c2, c1) == c1, f"Clash at {c2:%04X}" - bidi_mirror[c2] = c1 - - for c1, c2 in sorted(bidi_mirror.items()): - print("{{0x{:04x}, 0x{:04x}}},".format(c1, c2), file=fh) - - def write_bidi_brackets_table(self, fh): - self.write_file_header_comment(fh, """ - -Identify Unicode characters that count as brackets for the purposes of -bidirectional text layout. For each one, indicate whether it's an open -or closed bracket, and identify up to two characters that can act as -its counterpart. - -Used by terminal/bidi.c. - -""") - bracket_map = {} - for c1, c2, kind in self.BidiBrackets: - bracket_map[c1] = kind, c2 - - equivalents = {} - for rec in self.UnicodeData: - if (rec.Decomposition_Type == 'canonical' and - len(rec.Decomposition_Mapping) == 1): - c = rec.c - c2 = rec.Decomposition_Mapping[0] - equivalents[c] = c2 - equivalents[c2] = c - - for src, (kind, dst) in sorted(bracket_map.items()): - dsteq = equivalents.get(dst, 0) - # UCD claims there's an 'n' kind possible, but as of UCD - # 14, no instances of it exist - enumval = {'o': 'BT_OPEN', 'c': 'BT_CLOSE'}[kind] - print("{{0x{:04x}, {{0x{:04x}, 0x{:04x}, {}}}}},".format( - src, dst, dsteq, enumval), file=fh) - - def write_nonspacing_chars_list(self, fh): - self.write_file_header_comment(fh, """ - -Identify Unicode characters that occupy no character cells of a -terminal. - -Used by utils/wcwidth.c. - -""") - cs = set() - - for rec in self.UnicodeData: - nonspacing = rec.General_Category in {"Me", "Mn", "Cf"} - if rec.c == 0xAD: - # In typography this is a SOFT HYPHEN and counts as - # discardable. But it's also an ISO 8859-1 printing - # character, and all of those occupy one character - # cell in a terminal. - nonspacing = False - if 0x1160 <= rec.c <= 0x11FF: - # Medial (vowel) and final (consonant) jamo for - # decomposed Hangul characters. These are regarded as - # non-spacing on the grounds that they compose with - # the preceding initial consonant. - nonspacing = True - if nonspacing: - cs.add(rec.c) - - for start, end in set_to_ranges(cs): - print(f"{{0x{start:04x}, 0x{end:04x}}},", file=fh) - - def write_width_table(self, fh, accept): - cs = set() - - for c, wid in self.EastAsianWidth: - if wid in accept: - cs.add(c) - - for start, end in set_to_ranges(cs): - print(f"{{0x{start:04x}, 0x{end:04x}}},", file=fh) - - def write_wide_chars_list(self, fh): - self.write_file_header_comment(fh, """ - -Identify Unicode characters that occupy two adjacent character cells -in a terminal. - -Used by utils/wcwidth.c. - -""") - self.write_width_table(fh, {'W', 'F'}) - - def write_ambiguous_wide_chars_list(self, fh): - self.write_file_header_comment(fh, """ - -Identify Unicode characters that are width-ambiguous: some regimes -regard them as occupying two adjacent character cells in a terminal, -and others do not. - -Used by utils/wcwidth.c. - -""") - self.write_width_table(fh, {'A'}) - - def write_known_chars_table(self, fh): - self.write_file_header_comment(fh, """ - -List the Unicode code points that are known to this version of the -standard at all. - -Used by utils/unicode-known.c. - -""") - chars = set(rec.c for rec in self.UnicodeData) - - for start, end in set_to_ranges(chars): - print(f"{{0x{start:04x}, 0x{end:04x}}},", file=fh) - - def write_combining_class_table(self, fh): - self.write_file_header_comment(fh, """ - -List the canonical combining class of each Unicode character, if it is -not zero. This controls how combining marks can be reordered by the -Unicode normalisation algorithms. - -Used by utils/unicode-norm.c. - -""") - cclasses = {} - - for rec in self.UnicodeData: - cc = rec.Canonical_Combining_Class - if cc != 0: - cclasses[rec.c] = cc - - for (start, end), cclass in map_to_ranges(cclasses): - print(f"{{0x{start:04x}, 0x{end:04x}, {cclass:d}}},", file=fh) - - def write_canonical_decomp_table(self, fh): - self.write_file_header_comment(fh, """ - -List the canonical decomposition of every Unicode character that has -one. This consists of up to two characters, but those may need -decomposition in turn. - -Used by utils/unicode-norm.c. - -""") - decomps = {} - - for rec in self.UnicodeData: - if rec.Decomposition_Type != 'canonical': - continue - # Fill in a zero code point as the second character, if - # it's only one character long - decomps[rec.c] = (rec.Decomposition_Mapping + [0])[:2] - - for c, (d1, d2) in sorted(decomps.items()): - d2s = f"0x{d2:04x}" if d2 else "0" - print(f"{{0x{c:04x}, 0x{d1:04x}, {d2s}}},", file=fh) - - def write_canonical_comp_table(self, fh): - self.write_file_header_comment(fh, """ - -List the pairs of Unicode characters that canonically recompose to a -single character in NFC. - -Used by utils/unicode-norm.c. - -""") - exclusions = set(self.CompositionExclusions) - nonstarters = set(rec.c for rec in self.UnicodeData - if rec.Canonical_Combining_Class != 0) - - decomps = {} - - for rec in self.UnicodeData: - if rec.Decomposition_Type != 'canonical': - continue # we don't want compatibility decompositions - if len(rec.Decomposition_Mapping) != 2: - continue # we don't want singletons either - if rec.c in exclusions: - continue # we don't want anything explicitly excluded - if (rec.c in nonstarters or - rec.Decomposition_Mapping[0] in nonstarters): - continue # we don't want non-starter decompositions - decomps[tuple(rec.Decomposition_Mapping)] = rec.c - - for (d0, d1), c in sorted(decomps.items()): - print(f"{{0x{d0:04x}, 0x{d1:04x}, 0x{c:04x}}},", file=fh) - -if __name__ == '__main__': - Main().run() diff --git a/unicode/version.h b/unicode/version.h deleted file mode 100644 index ef511991c..000000000 --- a/unicode/version.h +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Autogenerated by read_ucd.py from The Unicode Standard 15.0.0 - * - * String literals giving the currently supported version of Unicode. - * Useful for error messages and 'about' boxes. - */ - -#define UNICODE_VERSION_FULL "The Unicode Standard 15.0.0" -#define UNICODE_VERSION_SHORT "15.0.0" diff --git a/unicode/wide_chars.h b/unicode/wide_chars.h deleted file mode 100644 index bac8ea39e..000000000 --- a/unicode/wide_chars.h +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Autogenerated by read_ucd.py from The Unicode Standard 15.0.0 - * - * Identify Unicode characters that occupy two adjacent character cells - * in a terminal. - * - * Used by utils/wcwidth.c. - */ - -{0x1100, 0x115f}, -{0x231a, 0x231b}, -{0x2329, 0x232a}, -{0x23e9, 0x23ec}, -{0x23f0, 0x23f0}, -{0x23f3, 0x23f3}, -{0x25fd, 0x25fe}, -{0x2614, 0x2615}, -{0x2648, 0x2653}, -{0x267f, 0x267f}, -{0x2693, 0x2693}, -{0x26a1, 0x26a1}, -{0x26aa, 0x26ab}, -{0x26bd, 0x26be}, -{0x26c4, 0x26c5}, -{0x26ce, 0x26ce}, -{0x26d4, 0x26d4}, -{0x26ea, 0x26ea}, -{0x26f2, 0x26f3}, -{0x26f5, 0x26f5}, -{0x26fa, 0x26fa}, -{0x26fd, 0x26fd}, -{0x2705, 0x2705}, -{0x270a, 0x270b}, -{0x2728, 0x2728}, -{0x274c, 0x274c}, -{0x274e, 0x274e}, -{0x2753, 0x2755}, -{0x2757, 0x2757}, -{0x2795, 0x2797}, -{0x27b0, 0x27b0}, -{0x27bf, 0x27bf}, -{0x2b1b, 0x2b1c}, -{0x2b50, 0x2b50}, -{0x2b55, 0x2b55}, -{0x2e80, 0x2e99}, -{0x2e9b, 0x2ef3}, -{0x2f00, 0x2fd5}, -{0x2ff0, 0x2ffb}, -{0x3000, 0x303e}, -{0x3041, 0x3096}, -{0x3099, 0x30ff}, -{0x3105, 0x312f}, -{0x3131, 0x318e}, -{0x3190, 0x31e3}, -{0x31f0, 0x321e}, -{0x3220, 0x3247}, -{0x3250, 0x4dbf}, -{0x4e00, 0xa48c}, -{0xa490, 0xa4c6}, -{0xa960, 0xa97c}, -{0xac00, 0xd7a3}, -{0xf900, 0xfaff}, -{0xfe10, 0xfe19}, -{0xfe30, 0xfe52}, -{0xfe54, 0xfe66}, -{0xfe68, 0xfe6b}, -{0xff01, 0xff60}, -{0xffe0, 0xffe6}, -{0x16fe0, 0x16fe4}, -{0x16ff0, 0x16ff1}, -{0x17000, 0x187f7}, -{0x18800, 0x18cd5}, -{0x18d00, 0x18d08}, -{0x1aff0, 0x1aff3}, -{0x1aff5, 0x1affb}, -{0x1affd, 0x1affe}, -{0x1b000, 0x1b122}, -{0x1b132, 0x1b132}, -{0x1b150, 0x1b152}, -{0x1b155, 0x1b155}, -{0x1b164, 0x1b167}, -{0x1b170, 0x1b2fb}, -{0x1f004, 0x1f004}, -{0x1f0cf, 0x1f0cf}, -{0x1f18e, 0x1f18e}, -{0x1f191, 0x1f19a}, -{0x1f200, 0x1f202}, -{0x1f210, 0x1f23b}, -{0x1f240, 0x1f248}, -{0x1f250, 0x1f251}, -{0x1f260, 0x1f265}, -{0x1f300, 0x1f320}, -{0x1f32d, 0x1f335}, -{0x1f337, 0x1f37c}, -{0x1f37e, 0x1f393}, -{0x1f3a0, 0x1f3ca}, -{0x1f3cf, 0x1f3d3}, -{0x1f3e0, 0x1f3f0}, -{0x1f3f4, 0x1f3f4}, -{0x1f3f8, 0x1f43e}, -{0x1f440, 0x1f440}, -{0x1f442, 0x1f4fc}, -{0x1f4ff, 0x1f53d}, -{0x1f54b, 0x1f54e}, -{0x1f550, 0x1f567}, -{0x1f57a, 0x1f57a}, -{0x1f595, 0x1f596}, -{0x1f5a4, 0x1f5a4}, -{0x1f5fb, 0x1f64f}, -{0x1f680, 0x1f6c5}, -{0x1f6cc, 0x1f6cc}, -{0x1f6d0, 0x1f6d2}, -{0x1f6d5, 0x1f6d7}, -{0x1f6dc, 0x1f6df}, -{0x1f6eb, 0x1f6ec}, -{0x1f6f4, 0x1f6fc}, -{0x1f7e0, 0x1f7eb}, -{0x1f7f0, 0x1f7f0}, -{0x1f90c, 0x1f93a}, -{0x1f93c, 0x1f945}, -{0x1f947, 0x1f9ff}, -{0x1fa70, 0x1fa7c}, -{0x1fa80, 0x1fa88}, -{0x1fa90, 0x1fabd}, -{0x1fabf, 0x1fac5}, -{0x1face, 0x1fadb}, -{0x1fae0, 0x1fae8}, -{0x1faf0, 0x1faf8}, -{0x20000, 0x2fffd}, -{0x30000, 0x3fffd}, diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt deleted file mode 100644 index 9b4f53ac3..000000000 --- a/unix/CMakeLists.txt +++ /dev/null @@ -1,248 +0,0 @@ -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) - -add_sources_from_current_dir(utils - utils/arm_arch_queries.c - utils/block_signal.c - utils/cloexec.c - utils/dputs.c - utils/filename.c - utils/fontspec.c - utils/getticks.c - utils/get_username.c - utils/keysym_to_unicode.c - utils/make_dir_and_check_ours.c - utils/make_dir_path.c - utils/make_spr_sw_abort_errno.c - utils/nonblock.c - utils/open_for_write_would_lose_data.c - utils/pgp_fingerprints.c - utils/pollwrap.c - utils/signal.c - utils/x11_ignore_error.c - # We want the ISO C implementation of ltime(), because we don't have - # a local better alternative - ../utils/ltime.c) -# Compiled icon pixmap files -add_library(puttyxpms STATIC - putty-xpm.c - putty-config-xpm.c) -add_library(ptermxpms STATIC - pterm-xpm.c - pterm-config-xpm.c) -add_sources_from_current_dir(eventloop - cliloop.c uxsel.c) -add_sources_from_current_dir(console - console.c) -add_sources_from_current_dir(settings - storage.c) -add_sources_from_current_dir(network - network.c fd-socket.c agent-socket.c peerinfo.c local-proxy.c x11.c) -add_sources_from_current_dir(sshcommon - noise.c) -add_sources_from_current_dir(sshclient - gss.c agent-client.c sharing.c) -add_sources_from_current_dir(sshserver - sftpserver.c procnet.c) -add_sources_from_current_dir(sftpclient - sftp.c) -add_sources_from_current_dir(otherbackends - serial.c) -add_sources_from_current_dir(agent - agent-client.c) - -add_executable(fuzzterm - ${CMAKE_SOURCE_DIR}/test/fuzzterm.c - ${CMAKE_SOURCE_DIR}/stubs/no-print.c - unicode.c - no-gtk.c - $) -be_list(fuzzterm FuZZterm) -add_dependencies(fuzzterm generated_licence_h) -target_link_libraries(fuzzterm - guiterminal eventloop charset settings utils) - -add_executable(osxlaunch - osxlaunch.c) - -add_sources_from_current_dir(plink no-gtk.c) -add_sources_from_current_dir(pscp no-gtk.c) -add_sources_from_current_dir(psftp no-gtk.c) -add_sources_from_current_dir(psocks no-gtk.c) - -add_executable(psusan - psusan.c - ${CMAKE_SOURCE_DIR}/stubs/no-gss.c - ${CMAKE_SOURCE_DIR}/ssh/scpserver.c - no-gtk.c - pty.c) -be_list(psusan psusan) -target_link_libraries(psusan - eventloop sshserver keygen settings network crypto utils) -installed_program(psusan) - -add_library(puttygen-common OBJECT - ${CMAKE_SOURCE_DIR}/stubs/no-timing.c - keygen-noise.c - no-gtk.c - noise.c - storage.c - ${CMAKE_SOURCE_DIR}/sshpubk.c - ${CMAKE_SOURCE_DIR}/sshrand.c) - -add_executable(puttygen - ${CMAKE_SOURCE_DIR}/cmdgen.c - $) -target_link_libraries(puttygen keygen console crypto utils) -installed_program(puttygen) - -add_executable(cgtest - ${CMAKE_SOURCE_DIR}/cgtest.c - $) -target_link_libraries(cgtest keygen console crypto utils) - -add_executable(testsc - ${CMAKE_SOURCE_DIR}/test/testsc.c) -target_link_libraries(testsc keygen crypto utils) - -add_executable(testzlib - ${CMAKE_SOURCE_DIR}/test/testzlib.c - ${CMAKE_SOURCE_DIR}/ssh/zlib.c) -target_link_libraries(testzlib utils) - -add_executable(uppity - uppity.c - ${CMAKE_SOURCE_DIR}/ssh/scpserver.c - no-gtk.c - pty.c - ${CMAKE_SOURCE_DIR}/stubs/no-gss.c) -be_list(uppity Uppity) -target_link_libraries(uppity - eventloop sshserver keygen settings network crypto utils) - -if(GTK_FOUND) - add_sources_from_current_dir(utils - utils/align_label_left.c - utils/buildinfo_gtk_version.c - utils/get_label_text_dimensions.c - utils/get_x11_display.c - utils/our_dialog.c - utils/string_width.c - columns.c) - add_sources_from_current_dir(guiterminal - window.c unifont.c dialog.c config-gtk.c gtk-common.c config-unix.c unicode.c printing.c) - add_dependencies(guiterminal generated_licence_h) # dialog.c uses licence.h - - add_executable(pterm - pterm.c - main-gtk-simple.c - ${CMAKE_SOURCE_DIR}/stubs/no-gss.c - ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c - ${CMAKE_SOURCE_DIR}/stubs/no-console.c - ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c - pty.c) - be_list(pterm pterm) - target_link_libraries(pterm - guiterminal eventloop settings charset utils ptermxpms - ${GTK_LIBRARIES} ${X11_LIBRARIES}) - installed_program(pterm) - - if(GTK_VERSION GREATER_EQUAL 3) - add_executable(ptermapp - pterm.c - main-gtk-application.c - ${CMAKE_SOURCE_DIR}/stubs/no-cmdline.c - ${CMAKE_SOURCE_DIR}/stubs/no-gss.c - ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c - ${CMAKE_SOURCE_DIR}/stubs/no-console.c - ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c - pty.c) - be_list(ptermapp pterm) - target_link_libraries(ptermapp - guiterminal eventloop settings charset utils ptermxpms - ${GTK_LIBRARIES} ${X11_LIBRARIES}) - endif() - - add_executable(putty - putty.c - main-gtk-simple.c - ${CMAKE_SOURCE_DIR}/stubs/no-console.c) - be_list(putty PuTTY SSH SERIAL OTHERBACKENDS) - target_link_libraries(putty - guiterminal eventloop sshclient otherbackends settings - network crypto charset utils puttyxpms - ${GTK_LIBRARIES} ${X11_LIBRARIES}) - set_target_properties(putty - PROPERTIES LINK_INTERFACE_MULTIPLICITY 2) - installed_program(putty) - - if(GTK_VERSION GREATER_EQUAL 3) - add_executable(puttyapp - putty.c - main-gtk-application.c - ${CMAKE_SOURCE_DIR}/stubs/no-cmdline.c - ${CMAKE_SOURCE_DIR}/stubs/no-console.c) - be_list(puttyapp PuTTY SSH SERIAL OTHERBACKENDS) - target_link_libraries(puttyapp - guiterminal eventloop sshclient otherbackends settings - network crypto charset utils puttyxpms - ${GTK_LIBRARIES} ${X11_LIBRARIES}) - endif() - - add_executable(puttytel - putty.c - main-gtk-simple.c - ${CMAKE_SOURCE_DIR}/stubs/no-gss.c - ${CMAKE_SOURCE_DIR}/stubs/no-ca-config.c - ${CMAKE_SOURCE_DIR}/stubs/no-console.c - ${CMAKE_SOURCE_DIR}/stubs/no-rand.c - ${CMAKE_SOURCE_DIR}/proxy/nocproxy.c - ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c) - be_list(puttytel PuTTYtel SERIAL OTHERBACKENDS) - target_link_libraries(puttytel - guiterminal eventloop otherbackends settings network charset utils - puttyxpms - ${GTK_LIBRARIES} ${X11_LIBRARIES}) - - add_executable(test_lineedit - ${CMAKE_SOURCE_DIR}/test/test_lineedit.c - ${CMAKE_SOURCE_DIR}/stubs/no-gss.c - ${CMAKE_SOURCE_DIR}/stubs/no-logging.c - ${CMAKE_SOURCE_DIR}/stubs/no-printing.c - ${CMAKE_SOURCE_DIR}/stubs/no-storage.c - ${CMAKE_SOURCE_DIR}/stubs/no-timing.c) - target_link_libraries(test_lineedit - guiterminal settings eventloop charset utils ${platform_libraries}) - - add_executable(test_terminal - ${CMAKE_SOURCE_DIR}/test/test_terminal.c - ${CMAKE_SOURCE_DIR}/stubs/no-gss.c - ${CMAKE_SOURCE_DIR}/stubs/no-storage.c - ${CMAKE_SOURCE_DIR}/stubs/no-timing.c) - target_link_libraries(test_terminal - guiterminal settings eventloop charset utils ${platform_libraries}) -endif() - -# Pageant is built whether we have GTK or not; in its absence we -# degrade to a version that doesn't provide the GTK askpass. -if(GTK_FOUND) - set(pageant_conditional_sources askpass.c) - set(pageant_libs ${GTK_LIBRARIES}) -else() - set(pageant_conditional_sources noaskpass.c no-gtk.c) - set(pageant_libs) -endif() -add_executable(pageant - pageant.c - ${CMAKE_SOURCE_DIR}/stubs/no-gss.c - x11.c - noise.c - ${CMAKE_SOURCE_DIR}/ssh/x11fwd.c - ${CMAKE_SOURCE_DIR}/proxy/nosshproxy.c - ${pageant_conditional_sources}) -be_list(pageant Pageant) -target_link_libraries(pageant - eventloop console agent settings network crypto utils - ${pageant_libs}) -installed_program(pageant) - -add_sources_from_current_dir(test_conf stubs/no-uxsel.c) diff --git a/unix/agent-client.c b/unix/agent-client.c deleted file mode 100644 index 6d7a36623..000000000 --- a/unix/agent-client.c +++ /dev/null @@ -1,226 +0,0 @@ -/* - * SSH agent client code. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "misc.h" -#include "tree234.h" -#include "puttymem.h" - -bool agent_exists(void) -{ - const char *p = getenv("SSH_AUTH_SOCK"); - if (p && *p) - return true; - return false; -} - -static tree234 *agent_pending_queries; -struct agent_pending_query { - 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) -{ - agent_pending_query *a = (agent_pending_query *) av; - agent_pending_query *b = (agent_pending_query *) 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; - agent_pending_query *b = (agent_pending_query *) bv; - if (afd < b->fd) - return -1; - if (afd > b->fd) - return +1; - return 0; -} - -/* - * Attempt to read from an agent socket fd. Returns false if the - * expected response is as yet incomplete; returns true if it's either - * complete (conn->retbuf non-NULL and filled with something useful) - * or has failed totally (conn->retbuf is NULL). - */ -static bool agent_try_read(agent_pending_query *conn) -{ - int ret; - - ret = read(conn->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; - return true; - } - conn->retlen += ret; - if (conn->retsize == 4 && conn->retlen == 4) { - conn->retsize = toint(GET_32BIT_MSB_FIRST(conn->retbuf) + 4); - if (conn->retsize <= 0) { - conn->retbuf = NULL; - conn->retlen = 0; - return true; /* way too large */ - } - assert(conn->retbuf == conn->sizebuf); - conn->retbuf = snewn(conn->retsize, char); - memcpy(conn->retbuf, conn->sizebuf, 4); - } - - if (conn->retlen < conn->retsize) - return false; /* more data to come */ - - return true; -} - -void agent_cancel_query(agent_pending_query *conn) -{ - uxsel_del(conn->fd); - close(conn->fd); - del234(agent_pending_queries, conn); - if (conn->retbuf && conn->retbuf != conn->sizebuf) - sfree(conn->retbuf); - sfree(conn); -} - -static void agent_select_result(int fd, int event) -{ - agent_pending_query *conn; - - assert(event == SELECT_R); /* not selecting for anything but R */ - - conn = find234(agent_pending_queries, &fd, agent_connfind); - if (!conn) { - uxsel_del(fd); - return; - } - - if (!agent_try_read(conn)) - return; /* more data to come */ - - /* - * We have now completed the agent query. Do the callback. - */ - conn->callback(conn->callback_ctx, conn->retbuf, conn->retlen); - /* Null out conn->retbuf, since ownership of that buffer has - * passed to the callback. */ - conn->retbuf = NULL; - agent_cancel_query(conn); -} - -static const char *agent_socket_path(void) -{ - return getenv("SSH_AUTH_SOCK"); -} - -Socket *agent_connect(Plug *plug) -{ - const char *path = agent_socket_path(); - if (!path) - return new_error_socket_fmt(plug, "SSH_AUTH_SOCK not set"); - return sk_new(unix_sock_addr(path), 0, false, false, false, false, plug); -} - -agent_pending_query *agent_query( - strbuf *query, void **out, int *outlen, - void (*callback)(void *, void *, int), void *callback_ctx) -{ - const char *name; - int sock; - struct sockaddr_un addr; - int done; - agent_pending_query *conn; - - name = agent_socket_path(); - if (!name || strlen(name) >= sizeof(addr.sun_path)) - 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; - strcpy(addr.sun_path, name); - if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - close(sock); - goto failure; - } - - strbuf_finalise_agent_query(query); - - for (done = 0; done < query->len ;) { - int ret = write(sock, query->s + done, - query->len - done); - if (ret <= 0) { - close(sock); - goto failure; - } - done += ret; - } - - conn = snew(agent_pending_query); - conn->fd = sock; - conn->retbuf = conn->sizebuf; - conn->retsize = 4; - conn->retlen = 0; - conn->callback = callback; - conn->callback_ctx = callback_ctx; - - if (!callback) { - /* - * Bodge to permit making deliberately synchronous agent - * requests. Used by Unix Pageant in command-line client mode, - * which is legit because it really is true that no other part - * of the program is trying to get anything useful done - * simultaneously. But this special case shouldn't be used in - * any more general program. - */ - no_nonblock(conn->fd); - while (!agent_try_read(conn)) - /* empty loop body */; - - *out = conn->retbuf; - *outlen = conn->retlen; - sfree(conn); - return NULL; - } - - /* - * Otherwise do it properly: add conn to the tree of agent - * connections currently in flight, return 0 to indicate that the - * response hasn't been received yet, and call the callback when - * select_result comes back to us. - */ - if (!agent_pending_queries) - agent_pending_queries = newtree234(agent_conncmp); - add234(agent_pending_queries, conn); - - uxsel_set(sock, SELECT_R, agent_select_result); - return conn; - - failure: - *out = NULL; - *outlen = 0; - return NULL; -} diff --git a/unix/agent-socket.c b/unix/agent-socket.c deleted file mode 100644 index 7e6187c60..000000000 --- a/unix/agent-socket.c +++ /dev/null @@ -1,91 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include "putty.h" -#include "ssh.h" -#include "misc.h" -#include "pageant.h" - -Socket *platform_make_agent_socket( - Plug *plug, const char *dirprefix, char **error, char **name) -{ - char *username, *socketdir, *socketname, *errw; - const char *err; - Socket *sock; - - *name = NULL; - - username = get_username(); - socketdir = dupprintf("%s.%s", dirprefix, username); - sfree(username); - - assert(*socketdir == '/'); - if ((errw = make_dir_and_check_ours(socketdir)) != NULL) { - *error = dupprintf("%s: %s\n", socketdir, errw); - sfree(errw); - sfree(socketdir); - return NULL; - } - - socketname = dupprintf("%s/pageant.%d", socketdir, (int)getpid()); - sock = new_unix_listener(unix_sock_addr(socketname), plug); - if ((err = sk_socket_error(sock)) != NULL) { - *error = dupprintf("%s: %s\n", socketname, err); - sk_close(sock); - sfree(socketname); - rmdir(socketdir); - sfree(socketdir); - return NULL; - } - - /* - * Spawn a subprocess which will try to reliably delete our socket - * and its containing directory when we terminate, in case we die - * unexpectedly. - */ - { - int cleanup_pipe[2]; - pid_t pid; - - /* Don't worry if pipe or fork fails; it's not _that_ critical. */ - if (!pipe(cleanup_pipe)) { - if ((pid = fork()) == 0) { - int buf[1024]; - /* - * Our parent process holds the writing end of - * this pipe, and writes nothing to it. Hence, - * we expect read() to return EOF as soon as - * that process terminates. - */ - - close(0); - close(1); - close(2); - - setpgid(0, 0); - close(cleanup_pipe[1]); - while (read(cleanup_pipe[0], buf, sizeof(buf)) > 0); - unlink(socketname); - rmdir(socketdir); - _exit(0); - } else if (pid < 0) { - close(cleanup_pipe[0]); - close(cleanup_pipe[1]); - } else { - close(cleanup_pipe[0]); - cloexec(cleanup_pipe[1]); - } - } - } - - *name = socketname; - *error = NULL; - sfree(socketdir); - return sock; -} diff --git a/unix/askpass.c b/unix/askpass.c deleted file mode 100644 index a1143a8f4..000000000 --- a/unix/askpass.c +++ /dev/null @@ -1,637 +0,0 @@ -/* - * GTK implementation of a GUI password/passphrase prompt. - */ - -#include -#include -#include - -#include - -#include -#include -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#include "defs.h" -#include "unifont.h" -#include "gtkcompat.h" -#include "gtkmisc.h" - -#include "putty.h" -#include "ssh.h" -#include "misc.h" - -#define N_DRAWING_AREAS 3 - -struct drawing_area_ctx { - GtkWidget *area; -#ifndef DRAW_DEFAULT_CAIRO - GdkColor *cols; -#endif - int width, height; - enum { NOT_CURRENT, CURRENT, GREYED_OUT } state; -}; - -struct askpass_ctx { - GtkWidget *dialog, *promptlabel; - struct drawing_area_ctx drawingareas[N_DRAWING_AREAS]; - int active_area; -#if GTK_CHECK_VERSION(2,0,0) - GtkIMContext *imc; -#endif -#ifndef DRAW_DEFAULT_CAIRO - GdkColormap *colmap; - GdkColor cols[3]; -#endif - char *error_message; /* if we finish without a passphrase */ - strbuf *passphrase; /* if we finish with one */ -#if GTK_CHECK_VERSION(3,20,0) - GdkSeat *seat; /* for gdk_seat_grab */ -#elif GTK_CHECK_VERSION(3,0,0) - GdkDevice *keyboard; /* for gdk_device_grab */ -#endif - - int nattempts; -}; - -static prng *keypress_prng = NULL; -static void feed_keypress_prng(void *data, int size) -{ - put_data(keypress_prng, data, size); -} -void random_add_noise(NoiseSourceId source, const void *noise, int length) -{ - if (keypress_prng) - prng_add_entropy(keypress_prng, source, make_ptrlen(noise, length)); -} -static void setup_keypress_prng(void) -{ - keypress_prng = prng_new(&ssh_sha256); - prng_seed_begin(keypress_prng); - noise_get_heavy(feed_keypress_prng); - prng_seed_finish(keypress_prng); -} -static void cleanup_keypress_prng(void) -{ - prng_free(keypress_prng); -} -static uint64_t keypress_prng_value(void) -{ - /* - * Don't actually put the passphrase keystrokes themselves into - * the PRNG; that doesn't seem like the course of wisdom when - * that's precisely what the information displayed on the screen - * is trying _not_ to be correlated to. - */ - noise_ultralight(NOISE_SOURCE_KEY, 0); - uint8_t data[8]; - prng_read(keypress_prng, data, 8); - return GET_64BIT_MSB_FIRST(data); -} -static int choose_new_area(int prev_area) -{ - int reduced = keypress_prng_value() % (N_DRAWING_AREAS - 1); - return (prev_area + 1 + reduced) % N_DRAWING_AREAS; -} - -static void visually_acknowledge_keypress(struct askpass_ctx *ctx) -{ - int new_active = choose_new_area(ctx->active_area); - ctx->drawingareas[ctx->active_area].state = NOT_CURRENT; - gtk_widget_queue_draw(ctx->drawingareas[ctx->active_area].area); - ctx->drawingareas[new_active].state = CURRENT; - gtk_widget_queue_draw(ctx->drawingareas[new_active].area); - ctx->active_area = new_active; -} - -static size_t last_char_start(struct askpass_ctx *ctx) -{ - /* - * GTK always encodes in UTF-8, so we can do this in a fixed way. - */ - assert(ctx->passphrase->len > 0); - size_t i = ctx->passphrase->len - 1; - while ((unsigned)(ctx->passphrase->u[i] - 0x80) < 0x40) { - if (i == 0) - break; - i--; - } - return i; -} - -static void add_text_to_passphrase(struct askpass_ctx *ctx, gchar *str) -{ - put_datapl(ctx->passphrase, ptrlen_from_asciz(str)); - visually_acknowledge_keypress(ctx); -} - -static void cancel_askpass(struct askpass_ctx *ctx, const char *msg) -{ - strbuf_free(ctx->passphrase); - ctx->passphrase = NULL; - ctx->error_message = dupstr(msg); - gtk_main_quit(); -} - -static gboolean askpass_dialog_closed(GtkWidget *widget, GdkEvent *event, - gpointer data) -{ - struct askpass_ctx *ctx = (struct askpass_ctx *)data; - cancel_askpass(ctx, "passphrase input cancelled"); - /* Don't destroy dialog yet, so gtk_askpass_cleanup() can do its work */ - return true; -} - -static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) -{ - struct askpass_ctx *ctx = (struct askpass_ctx *)data; - - if (event->keyval == GDK_KEY_Return && - event->type == GDK_KEY_PRESS) { - gtk_main_quit(); - } else if (event->keyval == GDK_KEY_Escape && - event->type == GDK_KEY_PRESS) { - cancel_askpass(ctx, "passphrase input cancelled"); - } else { -#if GTK_CHECK_VERSION(2,0,0) - if (gtk_im_context_filter_keypress(ctx->imc, event)) - return true; -#endif - - if (event->type == GDK_KEY_PRESS) { - if (!strcmp(event->string, "\x15")) { - /* Ctrl-U. Wipe out the whole line */ - strbuf_clear(ctx->passphrase); - visually_acknowledge_keypress(ctx); - } else if (!strcmp(event->string, "\x17")) { - /* Ctrl-W. Delete back to the last space->nonspace - * boundary. We interpret 'space' in a really simple - * way (mimicking terminal drivers), and don't attempt - * to second-guess exciting Unicode space - * characters. */ - while (ctx->passphrase->len > 0) { - char deleted, prior; - size_t newlen = last_char_start(ctx); - deleted = ctx->passphrase->s[newlen]; - strbuf_shrink_to(ctx->passphrase, newlen); - prior = (ctx->passphrase->len == 0 ? ' ' : - ctx->passphrase->s[ctx->passphrase->len-1]); - if (!g_ascii_isspace(deleted) && g_ascii_isspace(prior)) - break; - } - visually_acknowledge_keypress(ctx); - } else if (event->keyval == GDK_KEY_BackSpace) { - /* Backspace. Delete one character. */ - if (ctx->passphrase->len > 0) - strbuf_shrink_to(ctx->passphrase, last_char_start(ctx)); - visually_acknowledge_keypress(ctx); -#if !GTK_CHECK_VERSION(2,0,0) - } else if (event->string[0]) { - add_text_to_passphrase(ctx, event->string); -#endif - } - } - } - return true; -} - -#if GTK_CHECK_VERSION(2,0,0) -static void input_method_commit_event(GtkIMContext *imc, gchar *str, - gpointer data) -{ - struct askpass_ctx *ctx = (struct askpass_ctx *)data; - add_text_to_passphrase(ctx, str); -} -#endif - -static gint configure_area(GtkWidget *widget, GdkEventConfigure *event, - gpointer data) -{ - struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data; - ctx->width = event->width; - ctx->height = event->height; - gtk_widget_queue_draw(widget); - return true; -} - -#ifdef DRAW_DEFAULT_CAIRO -static void askpass_redraw_cairo(cairo_t *cr, struct drawing_area_ctx *ctx) -{ - double rgbval = (ctx->state == CURRENT ? 0 : - ctx->state == NOT_CURRENT ? 1 : 0.5); - cairo_set_source_rgb(cr, rgbval, rgbval, rgbval); - cairo_paint(cr); -} -#else -static void askpass_redraw_gdk(GdkWindow *win, struct drawing_area_ctx *ctx) -{ - GdkGC *gc = gdk_gc_new(win); - gdk_gc_set_foreground(gc, &ctx->cols[ctx->state]); - gdk_draw_rectangle(win, gc, true, 0, 0, ctx->width, ctx->height); - gdk_gc_unref(gc); -} -#endif - -#if GTK_CHECK_VERSION(3,0,0) -static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) -{ - struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data; - askpass_redraw_cairo(cr, ctx); - return true; -} -#else -static gint expose_area(GtkWidget *widget, GdkEventExpose *event, - gpointer data) -{ - struct drawing_area_ctx *ctx = (struct drawing_area_ctx *)data; - -#ifdef DRAW_DEFAULT_CAIRO - cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(ctx->area)); - askpass_redraw_cairo(cr, ctx); - cairo_destroy(cr); -#else - askpass_redraw_gdk(gtk_widget_get_window(ctx->area), ctx); -#endif - - return true; -} -#endif - -static gboolean try_grab_keyboard(gpointer vctx) -{ - struct askpass_ctx *ctx = (struct askpass_ctx *)vctx; - int i, ret; - -#if GTK_CHECK_VERSION(3,20,0) - /* - * Grabbing the keyboard in GTK 3.20 requires the new notion of - * GdkSeat. - */ - GdkSeat *seat; - GdkWindow *gdkw = gtk_widget_get_window(ctx->dialog); - if (!GDK_IS_WINDOW(gdkw) || !gdk_window_is_visible(gdkw)) - goto fail; - - seat = gdk_display_get_default_seat( - gtk_widget_get_display(ctx->dialog)); - if (!seat) - goto fail; - - ctx->seat = seat; - ret = gdk_seat_grab(seat, gdkw, GDK_SEAT_CAPABILITY_KEYBOARD, - true, NULL, NULL, NULL, NULL); - - /* - * For some reason GDK 3.22 hides the GDK window as a side effect - * of a failed grab. I've no idea why. But if we're going to retry - * the grab, then we need to unhide it again or else we'll just - * get GDK_GRAB_NOT_VIEWABLE on every subsequent attempt. - */ - if (ret != GDK_GRAB_SUCCESS) - gdk_window_show(gdkw); - -#elif GTK_CHECK_VERSION(3,0,0) - /* - * And it has to be done differently again prior to GTK 3.20. - */ - GdkDeviceManager *dm; - GdkDevice *pointer, *keyboard; - - dm = gdk_display_get_device_manager( - gtk_widget_get_display(ctx->dialog)); - if (!dm) - goto fail; - - pointer = gdk_device_manager_get_client_pointer(dm); - if (!pointer) - goto fail; - keyboard = gdk_device_get_associated_device(pointer); - if (!keyboard) - goto fail; - if (gdk_device_get_source(keyboard) != GDK_SOURCE_KEYBOARD) - goto fail; - - ctx->keyboard = keyboard; - ret = gdk_device_grab(ctx->keyboard, - gtk_widget_get_window(ctx->dialog), - GDK_OWNERSHIP_NONE, - true, - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, - NULL, - GDK_CURRENT_TIME); -#else - /* - * It's much simpler in GTK 1 and 2! - */ - ret = gdk_keyboard_grab(gtk_widget_get_window(ctx->dialog), - false, GDK_CURRENT_TIME); -#endif - if (ret != GDK_GRAB_SUCCESS) - goto fail; - - /* - * Now that we've got the keyboard grab, connect up our keyboard - * handlers. - */ -#if GTK_CHECK_VERSION(2,0,0) - g_signal_connect(G_OBJECT(ctx->imc), "commit", - G_CALLBACK(input_method_commit_event), ctx); -#endif - g_signal_connect(G_OBJECT(ctx->dialog), "key_press_event", - G_CALLBACK(key_event), ctx); - g_signal_connect(G_OBJECT(ctx->dialog), "key_release_event", - G_CALLBACK(key_event), ctx); -#if GTK_CHECK_VERSION(2,0,0) - gtk_im_context_set_client_window(ctx->imc, - gtk_widget_get_window(ctx->dialog)); -#endif - - /* - * And repaint the key-acknowledgment drawing areas as not greyed - * out. - */ - ctx->active_area = keypress_prng_value() % N_DRAWING_AREAS; - for (i = 0; i < N_DRAWING_AREAS; i++) { - ctx->drawingareas[i].state = - (i == ctx->active_area ? CURRENT : NOT_CURRENT); - gtk_widget_queue_draw(ctx->drawingareas[i].area); - } - - return false; - - fail: - /* - * If we didn't get the grab, reschedule ourself on a timer to try - * again later. - * - * We have to do this rather than just trying once, because there - * is at least one important situation in which the grab may fail - * the first time: any user who is launching an add-key operation - * off some kind of window manager hotkey will almost by - * definition be running this script with a keyboard grab already - * active, namely the one-key grab that the WM (or whatever) uses - * to detect presses of the hotkey. So at the very least we have - * to give the user time to release that key. - */ - if (++ctx->nattempts >= 4) { - cancel_askpass(ctx, "unable to grab keyboard after 5 seconds"); - } else { - g_timeout_add(1000/8, try_grab_keyboard, ctx); - } - return false; -} - -void realize(GtkWidget *widget, gpointer vctx) -{ - struct askpass_ctx *ctx = (struct askpass_ctx *)vctx; - - gtk_grab_add(ctx->dialog); - - /* - * Schedule the first attempt at the keyboard grab. - */ - ctx->nattempts = 0; -#if GTK_CHECK_VERSION(3,20,0) - ctx->seat = NULL; -#elif GTK_CHECK_VERSION(3,0,0) - ctx->keyboard = NULL; -#endif - - g_idle_add(try_grab_keyboard, ctx); -} - -static const char *gtk_askpass_setup(struct askpass_ctx *ctx, - const char *window_title, - const char *prompt_text) -{ - int i; - GtkBox *action_area; - - ctx->passphrase = strbuf_new_nm(); - - /* - * Create widgets. - */ - ctx->dialog = our_dialog_new(); - gtk_window_set_title(GTK_WINDOW(ctx->dialog), window_title); - gtk_window_set_position(GTK_WINDOW(ctx->dialog), GTK_WIN_POS_CENTER); - g_signal_connect(G_OBJECT(ctx->dialog), "delete-event", - G_CALLBACK(askpass_dialog_closed), ctx); - ctx->promptlabel = gtk_label_new(prompt_text); - align_label_left(GTK_LABEL(ctx->promptlabel)); - gtk_widget_show(ctx->promptlabel); - gtk_label_set_line_wrap(GTK_LABEL(ctx->promptlabel), true); -#if GTK_CHECK_VERSION(3,0,0) - gtk_label_set_width_chars(GTK_LABEL(ctx->promptlabel), 48); -#endif - int margin = string_width("MM"); -#if GTK_CHECK_VERSION(3,12,0) - gtk_widget_set_margin_start(ctx->promptlabel, margin); - gtk_widget_set_margin_end(ctx->promptlabel, margin); -#else - gtk_misc_set_padding(GTK_MISC(ctx->promptlabel), margin, 0); -#endif - our_dialog_add_to_content_area(GTK_WINDOW(ctx->dialog), - ctx->promptlabel, true, true, 0); -#if GTK_CHECK_VERSION(2,0,0) - ctx->imc = gtk_im_multicontext_new(); -#endif -#ifndef DRAW_DEFAULT_CAIRO - { - gboolean success[2]; - ctx->colmap = gdk_colormap_get_system(); - ctx->cols[0].red = ctx->cols[0].green = ctx->cols[0].blue = 0xFFFF; - ctx->cols[1].red = ctx->cols[1].green = ctx->cols[1].blue = 0; - ctx->cols[2].red = ctx->cols[2].green = ctx->cols[2].blue = 0x8000; - gdk_colormap_alloc_colors(ctx->colmap, ctx->cols, 2, - false, true, success); - if (!success[0] || !success[1]) - return "unable to allocate colours"; - } -#endif - - action_area = our_dialog_make_action_hbox(GTK_WINDOW(ctx->dialog)); - - for (i = 0; i < N_DRAWING_AREAS; i++) { - ctx->drawingareas[i].area = gtk_drawing_area_new(); -#ifndef DRAW_DEFAULT_CAIRO - ctx->drawingareas[i].cols = ctx->cols; -#endif - ctx->drawingareas[i].state = GREYED_OUT; - ctx->drawingareas[i].width = ctx->drawingareas[i].height = 0; - /* It would be nice to choose this size in some more - * context-sensitive way, like measuring the size of some - * piece of template text. */ - gtk_widget_set_size_request(ctx->drawingareas[i].area, 32, 32); - gtk_box_pack_end(action_area, ctx->drawingareas[i].area, - true, true, 5); - g_signal_connect(G_OBJECT(ctx->drawingareas[i].area), - "configure_event", - G_CALLBACK(configure_area), - &ctx->drawingareas[i]); -#if GTK_CHECK_VERSION(3,0,0) - g_signal_connect(G_OBJECT(ctx->drawingareas[i].area), - "draw", - G_CALLBACK(draw_area), - &ctx->drawingareas[i]); -#else - g_signal_connect(G_OBJECT(ctx->drawingareas[i].area), - "expose_event", - G_CALLBACK(expose_area), - &ctx->drawingareas[i]); -#endif - -#if GTK_CHECK_VERSION(3,0,0) - g_object_set(G_OBJECT(ctx->drawingareas[i].area), - "margin-bottom", 8, (const char *)NULL); -#endif - - gtk_widget_show(ctx->drawingareas[i].area); - } - ctx->active_area = -1; - - /* - * Arrange to receive key events. We don't really need to worry - * from a UI perspective about which widget gets the events, as - * long as we know which it is so we can catch them. So we'll pick - * the prompt label at random, and we'll use gtk_grab_add to - * ensure key events go to it. - */ - gtk_widget_set_sensitive(ctx->dialog, true); - -#if GTK_CHECK_VERSION(2,0,0) - gtk_window_set_keep_above(GTK_WINDOW(ctx->dialog), true); -#endif - - /* - * Wait for the key-receiving widget to actually be created, in - * order to call gtk_grab_add on it. - */ - g_signal_connect(G_OBJECT(ctx->dialog), "realize", - G_CALLBACK(realize), ctx); - - /* - * Show the window. - */ - gtk_widget_show(ctx->dialog); - - return NULL; -} - -static void gtk_askpass_cleanup(struct askpass_ctx *ctx) -{ -#if GTK_CHECK_VERSION(3,20,0) - if (ctx->seat) - gdk_seat_ungrab(ctx->seat); -#elif GTK_CHECK_VERSION(3,0,0) - if (ctx->keyboard) - gdk_device_ungrab(ctx->keyboard, GDK_CURRENT_TIME); -#else - gdk_keyboard_ungrab(GDK_CURRENT_TIME); -#endif - gtk_grab_remove(ctx->promptlabel); - - gtk_widget_destroy(ctx->dialog); -} - -static bool setup_gtk(const char *display) -{ - static bool gtk_initialised = false; - int argc; - char *real_argv[3]; - char **argv = real_argv; - bool ret; - - if (gtk_initialised) - return true; - - argc = 0; - argv[argc++] = dupstr("dummy"); - argv[argc++] = dupprintf("--display=%s", display); - argv[argc] = NULL; - ret = gtk_init_check(&argc, &argv); - while (argc > 0) - sfree(argv[--argc]); - - gtk_initialised = ret; - return ret; -} - -const bool buildinfo_gtk_relevant = true; - -char *gtk_askpass_main(const char *display, const char *wintitle, - const char *prompt, bool *success) -{ - struct askpass_ctx ctx[1]; - const char *err; - - ctx->passphrase = NULL; - ctx->error_message = NULL; - - /* In case gtk_init hasn't been called yet by the program */ - if (!setup_gtk(display)) { - *success = false; - return dupstr("unable to initialise GTK"); - } - - if ((err = gtk_askpass_setup(ctx, wintitle, prompt)) != NULL) { - *success = false; - return dupprintf("%s", err); - } - setup_keypress_prng(); - gtk_main(); - cleanup_keypress_prng(); - gtk_askpass_cleanup(ctx); - - if (ctx->passphrase) { - *success = true; - return strbuf_to_str(ctx->passphrase); - } else { - *success = false; - return ctx->error_message; - } -} - -#ifdef TEST_ASKPASS -void modalfatalbox(const 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); -} - -int main(int argc, char **argv) -{ - bool success; - int exitcode; - char *ret; - - gtk_init(&argc, &argv); - - if (argc != 2) { - success = false; - ret = dupprintf("usage: %s ", argv[0]); - } else { - ret = gtk_askpass_main(NULL, "Enter passphrase", argv[1], &success); - } - - if (!success) { - fputs(ret, stderr); - fputc('\n', stderr); - exitcode = 1; - } else { - fputs(ret, stdout); - fputc('\n', stdout); - exitcode = 0; - } - - smemclr(ret, strlen(ret)); - return exitcode; -} -#endif diff --git a/unix/cliloop.c b/unix/cliloop.c deleted file mode 100644 index 23a808da2..000000000 --- a/unix/cliloop.c +++ /dev/null @@ -1,125 +0,0 @@ -#include - -#include "putty.h" - -void cli_main_loop(cliloop_pw_setup_t pw_setup, - cliloop_pw_check_t pw_check, - cliloop_continue_t cont, void *ctx) -{ - unsigned long now = GETTICKCOUNT(); - - int *fdlist = NULL; - size_t fdsize = 0; - - pollwrapper *pw = pollwrap_new(); - - while (true) { - int rwx; - int ret; - int fdstate; - unsigned long next; - - pollwrap_clear(pw); - - if (!pw_setup(ctx, pw)) - break; /* our client signalled emergency exit */ - - /* Count the currently active fds. */ - size_t nfds = 0; - for (int fd = first_fd(&fdstate, &rwx); fd >= 0; - fd = next_fd(&fdstate, &rwx)) - nfds++; - - /* Expand the fdlist buffer if necessary. */ - sgrowarray(fdlist, fdsize, nfds); - - /* - * Add all currently open uxsel fds to pw, and store them in - * fdlist as well. - */ - size_t fdcount = 0; - for (int fd = first_fd(&fdstate, &rwx); fd >= 0; - fd = next_fd(&fdstate, &rwx)) { - fdlist[fdcount++] = fd; - pollwrap_add_fd_rwx(pw, fd, rwx); - } - - if (toplevel_callback_pending()) { - ret = pollwrap_poll_instant(pw); - } else if (run_timers(now, &next)) { - do { - unsigned long then; - long ticks; - - then = now; - now = GETTICKCOUNT(); - if (now - then > next - then) - ticks = 0; - else - ticks = next - now; - - bool overflow = false; - if (ticks > INT_MAX) { - ticks = INT_MAX; - overflow = true; - } - - ret = pollwrap_poll_timeout(pw, ticks); - if (ret == 0 && !overflow) - now = next; - else - now = GETTICKCOUNT(); - } while (ret < 0 && errno == EINTR); - } else { - ret = pollwrap_poll_endless(pw); - } - - if (ret < 0 && errno == EINTR) - continue; - - if (ret < 0) { - perror("poll"); - exit(1); - } - - bool found_fd = (ret > 0); - - for (size_t i = 0; i < fdcount; i++) { - int fd = fdlist[i]; - int rwx = pollwrap_get_fd_rwx(pw, fd); - /* - * We must process exceptional notifications before - * ordinary readability ones, or we may go straight - * past the urgent marker. - */ - if (rwx & SELECT_X) - select_result(fd, SELECT_X); - if (rwx & SELECT_R) - select_result(fd, SELECT_R); - if (rwx & SELECT_W) - select_result(fd, SELECT_W); - } - - pw_check(ctx, pw); - - bool ran_callback = run_toplevel_callbacks(); - - if (!cont(ctx, found_fd, ran_callback)) - break; - } - - pollwrap_free(pw); - sfree(fdlist); -} - -bool cliloop_no_pw_setup(void *ctx, pollwrapper *pw) { return true; } -void cliloop_no_pw_check(void *ctx, pollwrapper *pw) {} -bool cliloop_always_continue(void *ctx, bool fd, bool cb) { return true; } - -/* - * Any application using this main loop doesn't need to do anything - * when uxsel adds or removes an fd, because we synchronously re-check - * the current list every time we go round the main loop above. - */ -uxsel_id *uxsel_input_add(int fd, int rwx) { return NULL; } -void uxsel_input_remove(uxsel_id *id) { } diff --git a/unix/columns.c b/unix/columns.c deleted file mode 100644 index 3dbed9c38..000000000 --- a/unix/columns.c +++ /dev/null @@ -1,1276 +0,0 @@ -/* - * columns.c - implementation of the `Columns' GTK layout container. - */ - -#include -#include -#include "defs.h" -#include "gtkcompat.h" -#include "columns.h" - -#if GTK_CHECK_VERSION(2,0,0) -/* The "focus" method lives in GtkWidget from GTK 2 onwards, but it - * was in GtkContainer in GTK 1 */ -#define FOCUS_METHOD_SUPERCLASS GtkWidget -#define FOCUS_METHOD_LOCATION widget_class /* used in columns_init */ -#define CHILD_FOCUS(cont, dir) gtk_widget_child_focus(GTK_WIDGET(cont), dir) -#else -#define FOCUS_METHOD_SUPERCLASS GtkContainer -#define FOCUS_METHOD_LOCATION container_class -#define CHILD_FOCUS(cont, dir) gtk_container_focus(GTK_CONTAINER(cont), dir) -#endif - -static void columns_init(Columns *cols); -static void columns_class_init(ColumnsClass *klass); -#if !GTK_CHECK_VERSION(2,0,0) -static void columns_finalize(GtkObject *object); -#else -static void columns_finalize(GObject *object); -#endif -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); -static gint columns_focus(FOCUS_METHOD_SUPERCLASS *container, - GtkDirectionType dir); -static GType columns_child_type(GtkContainer *container); -#if GTK_CHECK_VERSION(3,0,0) -static void columns_get_preferred_width(GtkWidget *widget, - gint *min, gint *nat); -static void columns_get_preferred_height(GtkWidget *widget, - gint *min, gint *nat); -static void columns_get_preferred_width_for_height(GtkWidget *widget, - gint height, - gint *min, gint *nat); -static void columns_get_preferred_height_for_width(GtkWidget *widget, - gint width, - gint *min, gint *nat); -#else -static void columns_size_request(GtkWidget *widget, GtkRequisition *req); -#endif -static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc); - -static GtkContainerClass *parent_class = NULL; - -#if !GTK_CHECK_VERSION(2,0,0) -GType columns_get_type(void) -{ - static GType 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 - -static gint (*columns_inherited_focus)(FOCUS_METHOD_SUPERCLASS *container, - GtkDirectionType direction); - -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 - - object_class->finalize = columns_finalize; - 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 -#if GTK_CHECK_VERSION(3,0,0) - widget_class->get_preferred_width = columns_get_preferred_width; - widget_class->get_preferred_height = columns_get_preferred_height; - widget_class->get_preferred_width_for_height = - columns_get_preferred_width_for_height; - widget_class->get_preferred_height_for_width = - columns_get_preferred_height_for_width; -#else - widget_class->size_request = columns_size_request; -#endif - 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; - - /* Save the previous value of this method. */ - if (!columns_inherited_focus) - columns_inherited_focus = FOCUS_METHOD_LOCATION->focus; - FOCUS_METHOD_LOCATION->focus = columns_focus; -} - -static void columns_init(Columns *cols) -{ - gtk_widget_set_has_window(GTK_WIDGET(cols), false); - - cols->children = NULL; - cols->spacing = 0; -} - -static void columns_child_free(gpointer vchild) -{ - ColumnsChild *child = (ColumnsChild *)vchild; - if (child->percentages) - g_free(child->percentages); - g_free(child); -} - -static void columns_finalize( -#if !GTK_CHECK_VERSION(2,0,0) - GtkObject *object -#else - GObject *object -#endif - ) -{ - Columns *cols; - - g_return_if_fail(object != NULL); - g_return_if_fail(IS_COLUMNS(object)); - - cols = COLUMNS(object); - -#if !GTK_CHECK_VERSION(2,0,0) - { - GList *node; - for (node = cols->children; node; node = node->next) - if (node->data) - columns_child_free(node->data); - } - g_list_free(cols->children); -#else - g_list_free_full(cols->children, columns_child_free); -#endif - - cols->children = NULL; - -#if !GTK_CHECK_VERSION(2,0,0) - GTK_OBJECT_CLASS(parent_class)->finalize(object); -#else - G_OBJECT_CLASS(parent_class)->finalize(object); -#endif -} - -/* - * 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_mapped(GTK_WIDGET(cols), true); - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - if (child->widget && - gtk_widget_get_visible(child->widget) && - !gtk_widget_get_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_set_mapped(GTK_WIDGET(cols), false); - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - if (child->widget && - gtk_widget_get_visible(child->widget) && - gtk_widget_get_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; - - 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; - - bool need_layout = false; - if (gtk_widget_get_visible(widget)) - need_layout = true; - gtk_widget_unparent(widget); - cols->children = g_list_remove_link(cols->children, children); - g_list_free(children); - - /* Unlink this widget from its valign list, and if anything - * else on the list is still visible, ensure we recompute our - * layout */ - for (ColumnsChild *ch = child->valign_next; ch != child; - ch = ch->valign_next) - if (gtk_widget_get_visible(ch->widget)) - need_layout = true; - child->valign_next->valign_prev = child->valign_prev; - child->valign_prev->valign_next = child->valign_next; - - if (cols->vexpand == child) - cols->vexpand = NULL; - - g_free(child); - if (need_layout) - 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); - 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 GType 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(gtk_widget_get_parent(child) == NULL); - - childdata = g_new(ColumnsChild, 1); - childdata->widget = child; - childdata->colstart = colstart; - childdata->colspan = colspan; - childdata->force_left = false; - childdata->valign_next = childdata; - childdata->valign_prev = childdata; - childdata->percentages = NULL; - - 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_widget_get_realized(GTK_WIDGET(cols))) - gtk_widget_realize(child); - - if (gtk_widget_get_visible(GTK_WIDGET(cols)) && - gtk_widget_get_visible(child)) { - if (gtk_widget_get_mapped(GTK_WIDGET(cols))) - gtk_widget_map(child); - gtk_widget_queue_resize(child); - } -} - -static ColumnsChild *columns_find_child(Columns *cols, GtkWidget *widget) -{ - GList *children; - ColumnsChild *child; - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - - if (child->widget == widget) - return child; - } - - return NULL; -} - -void columns_force_left_align(Columns *cols, GtkWidget *widget) -{ - ColumnsChild *child; - - g_return_if_fail(cols != NULL); - g_return_if_fail(IS_COLUMNS(cols)); - g_return_if_fail(widget != NULL); - - child = columns_find_child(cols, widget); - g_return_if_fail(child != NULL); - - child->force_left = true; - if (gtk_widget_get_visible(widget)) - gtk_widget_queue_resize(GTK_WIDGET(cols)); -} - -void columns_align_next_to(Columns *cols, GtkWidget *cw1, GtkWidget *cw2) -{ - ColumnsChild *child1, *child2; - - g_return_if_fail(cols != NULL); - g_return_if_fail(IS_COLUMNS(cols)); - g_return_if_fail(cw1 != NULL); - g_return_if_fail(cw2 != NULL); - - child1 = columns_find_child(cols, cw1); - g_return_if_fail(child1 != NULL); - child2 = columns_find_child(cols, cw2); - g_return_if_fail(child2 != NULL); - - ColumnsChild *child1prev = child1->valign_prev; - ColumnsChild *child2prev = child2->valign_prev; - child1prev->valign_next = child2; - child2->valign_prev = child1prev; - child2prev->valign_next = child1; - child1->valign_prev = child2prev; - - if (gtk_widget_get_visible(cw1) || gtk_widget_get_visible(cw2)) - gtk_widget_queue_resize(GTK_WIDGET(cols)); -} - -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); - break; - } -} - -void columns_vexpand(Columns *cols, GtkWidget *widget) -{ - g_return_if_fail(cols != NULL); - g_return_if_fail(IS_COLUMNS(cols)); - g_return_if_fail(widget != NULL); - - ColumnsChild *child = columns_find_child(cols, widget); - g_return_if_fail(child != NULL); - - cols->vexpand = child; -} - -/* - * Override GtkContainer's focus movement so the user can - * explicitly specify the tab order. - */ -static gint columns_focus(FOCUS_METHOD_SUPERCLASS *super, GtkDirectionType dir) -{ - Columns *cols; - GList *pos; - GtkWidget *focuschild; - - g_return_val_if_fail(super != NULL, false); - g_return_val_if_fail(IS_COLUMNS(super), false); - - cols = COLUMNS(super); - - if (!gtk_widget_is_drawable(GTK_WIDGET(cols)) || - !gtk_widget_is_sensitive(GTK_WIDGET(cols))) - return false; - - if (!gtk_widget_get_can_focus(GTK_WIDGET(cols)) && - (dir == GTK_DIR_TAB_FORWARD || dir == GTK_DIR_TAB_BACKWARD)) { - - focuschild = gtk_container_get_focus_child(GTK_CONTAINER(cols)); - gtk_container_set_focus_child(GTK_CONTAINER(cols), 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_is_drawable(child) && - GTK_IS_CONTAINER(child) && - !gtk_widget_has_focus(child)) { - if (CHILD_FOCUS(child, dir)) - return true; - } - } - } else if (gtk_widget_is_drawable(child)) { - if (GTK_IS_CONTAINER(child)) { - if (CHILD_FOCUS(child, dir)) - return true; - } else if (gtk_widget_get_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(super, dir); -} - -/* - * Underlying parts of the layout algorithm, to compute the Columns - * container's width or height given the widths or heights of its - * children. These will be called in various ways with different - * notions of width and height in use, so we abstract them out and - * pass them a 'get width' or 'get height' function pointer. - */ - -typedef gint (*widget_dim_fn_t)(ColumnsChild *child); - -static gint columns_compute_width(Columns *cols, widget_dim_fn_t get_width) -{ - ColumnsChild *child; - GList *children; - gint i, ncols, colspan, retwidth, childwidth; - const gint *percentages; - static const gint onecol[] = { 100 }; - -#ifdef COLUMNS_WIDTH_DIAGNOSTICS - printf("compute_width(%p): start\n", cols); -#endif - - retwidth = 0; - - ncols = 1; - percentages = onecol; - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - - if (!child->widget) { - /* Column reconfiguration. */ - ncols = child->ncols; - percentages = child->percentages; - continue; - } - - /* Only take visible widgets into account. */ - if (!gtk_widget_get_visible(child->widget)) - continue; - - childwidth = get_width(child); - colspan = child->colspan ? child->colspan : ncols-child->colstart; - assert(colspan > 0); - -#ifdef COLUMNS_WIDTH_DIAGNOSTICS - printf("compute_width(%p): ", cols); - if (GTK_IS_LABEL(child->widget)) - printf("label %p '%s' wrap=%s: ", child->widget, - gtk_label_get_text(GTK_LABEL(child->widget)), - (gtk_label_get_line_wrap(GTK_LABEL(child->widget)) - ? "true" : "false")); - else - printf("widget %p: ", child->widget); - { - gint min, nat; - gtk_widget_get_preferred_width(child->widget, &min, &nat); - printf("minwidth=%d natwidth=%d ", min, nat); - } - printf("thiswidth=%d span=%d\n", childwidth, colspan); -#endif - - /* - * To compute width: we know that childwidth + 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 = childwidth + cols->spacing; - /* - * Since childwidth is (at least sometimes) 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; -#ifdef COLUMNS_WIDTH_DIAGNOSTICS - printf("compute_width(%p): after %p, thiswid=%d fullwid=%d\n", - cols, child->widget, thiswid, fullwid); -#endif - - /* - * 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 (retwidth < fullwid - cols->spacing) - retwidth = fullwid - cols->spacing; - } - } - - retwidth += 2*gtk_container_get_border_width(GTK_CONTAINER(cols)); - -#ifdef COLUMNS_WIDTH_DIAGNOSTICS - printf("compute_width(%p): done, returning %d\n", cols, retwidth); -#endif - - return retwidth; -} - -static void columns_alloc_horiz(Columns *cols, gint ourwidth, - widget_dim_fn_t get_width) -{ - ColumnsChild *child; - GList *children; - gint i, ncols, colspan, border, *colxpos, childwidth; - - border = gtk_container_get_border_width(GTK_CONTAINER(cols)); - - ncols = 1; - /* 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] = ourwidth - 2*border + cols->spacing; - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - - if (!child->widget) { - gint percent; - - /* Column reconfiguration. */ - ncols = child->ncols; - colxpos = g_renew(gint, colxpos, ncols + 1); - colxpos[0] = 0; - percent = 0; - for (i = 0; i < ncols; i++) { - percent += child->percentages[i]; - colxpos[i+1] = (((ourwidth - 2*border) + cols->spacing) - * percent / 100); - } - continue; - } - - /* Only take visible widgets into account. */ - if (!gtk_widget_get_visible(child->widget)) - continue; - - childwidth = get_width(child); - 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. - */ - child->x = colxpos[child->colstart]; - if (child->force_left) - child->w = childwidth; - else - child->w = (colxpos[child->colstart+colspan] - - colxpos[child->colstart] - cols->spacing); - } - - g_free(colxpos); -} - -static gint columns_compute_height(Columns *cols, widget_dim_fn_t get_height) -{ - ColumnsChild *child; - GList *children; - gint i, ncols, colspan, *colypos, retheight, childheight; - - retheight = cols->spacing; - - ncols = 1; - colypos = g_new(gint, 1); - colypos[0] = 0; - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - - if (!child->widget) { - /* Column reconfiguration. */ - for (i = 1; i < ncols; i++) { - if (colypos[0] < colypos[i]) - colypos[0] = colypos[i]; - } - ncols = child->ncols; - 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_get_visible(child->widget)) - continue; - - childheight = get_height(child); - for (ColumnsChild *ch = child->valign_next; ch != child; - ch = ch->valign_next) { - gint childheight2 = get_height(ch); - if (childheight < childheight2) - childheight = childheight2; - } - colspan = child->colspan ? child->colspan : ncols-child->colstart; - - /* - * 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 childheight 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 height. - */ - { - int topy, boty; - - topy = 0; - for (i = 0; i < colspan; i++) { - if (topy < colypos[child->colstart+i]) - topy = colypos[child->colstart+i]; - } - boty = topy + childheight + cols->spacing; - for (i = 0; i < colspan; i++) { - colypos[child->colstart+i] = boty; - } - - if (retheight < boty - cols->spacing) - retheight = boty - cols->spacing; - } - } - - retheight += 2*gtk_container_get_border_width(GTK_CONTAINER(cols)); - - g_free(colypos); - - return retheight; -} - -static void columns_alloc_vert(Columns *cols, gint ourheight, - widget_dim_fn_t get_height) -{ - ColumnsChild *child; - GList *children; - gint i, ncols, colspan, *colypos, realheight, fakeheight, vexpand_extra; - - if (cols->vexpand) { - gint minheight = columns_compute_height(cols, get_height); - vexpand_extra = ourheight - minheight; - } else { - vexpand_extra = 0; - } - - ncols = 1; - /* 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) - child->visited = false; - - /* - * Main layout loop. In this loop, vertically aligned controls are - * only half dealt with: we assign each one enough _height_ to - * match the others in its group, but we don't adjust its y - * coordinates yet. - */ - for (children = cols->children; - children && (child = children->data); - children = children->next) { - if (!child->widget) { - /* Column reconfiguration. */ - for (i = 1; i < ncols; i++) { - if (colypos[0] < colypos[i]) - colypos[0] = colypos[i]; - } - ncols = child->ncols; - 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_get_visible(child->widget)) - continue; - - int ymin = 0; - - realheight = get_height(child); - if (child == cols->vexpand) - realheight += vexpand_extra; - fakeheight = realheight; - for (ColumnsChild *ch = child->valign_next; ch != child; - ch = ch->valign_next) { - gint childheight2 = get_height(ch); - if (fakeheight < childheight2) - fakeheight = childheight2; - if (ch->visited && ymin < ch->y) - ymin = ch->y; - } - colspan = child->colspan ? child->colspan : ncols-child->colstart; - - /* - * 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 = ymin; - for (i = 0; i < colspan; i++) { - if (topy < colypos[child->colstart+i]) - topy = colypos[child->colstart+i]; - } - child->y = topy; - child->h = realheight; - child->visited = true; - boty = topy + fakeheight + cols->spacing; - for (i = 0; i < colspan; i++) { - colypos[child->colstart+i] = boty; - } - } - } - - /* - * Now make a separate pass that deals with vertical alignment by - * moving controls downwards based on the difference between their - * own height and the largest height of anything in their group. - */ - for (children = cols->children; - children && (child = children->data); - children = children->next) { - if (!child->widget) - continue; - if (!gtk_widget_get_visible(child->widget)) - continue; - - fakeheight = realheight = child->h; - for (ColumnsChild *ch = child->valign_next; ch != child; - ch = ch->valign_next) { - if (fakeheight < ch->h) - fakeheight = ch->h; - } - child->y += fakeheight/2 - realheight/2; - } - - g_free(colypos); -} - -/* - * 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. - */ - -#if !GTK_CHECK_VERSION(3,0,0) - -static gint columns_gtk2_get_width(ColumnsChild *child) -{ - GtkRequisition creq; - gtk_widget_size_request(child->widget, &creq); - return creq.width; -} - -static gint columns_gtk2_get_height(ColumnsChild *child) -{ - GtkRequisition creq; - gtk_widget_size_request(child->widget, &creq); - return creq.height; -} - -static void columns_size_request(GtkWidget *widget, GtkRequisition *req) -{ - Columns *cols; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - g_return_if_fail(req != NULL); - - cols = COLUMNS(widget); - - req->width = columns_compute_width(cols, columns_gtk2_get_width); - req->height = columns_compute_height(cols, columns_gtk2_get_height); -} - -static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc) -{ - Columns *cols; - ColumnsChild *child; - GList *children; - gint border; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - g_return_if_fail(alloc != NULL); - - cols = COLUMNS(widget); - gtk_widget_set_allocation(widget, alloc); - - border = gtk_container_get_border_width(GTK_CONTAINER(cols)); - - columns_alloc_horiz(cols, alloc->width, columns_gtk2_get_width); - columns_alloc_vert(cols, alloc->height, columns_gtk2_get_height); - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - if (child->widget && gtk_widget_get_visible(child->widget)) { - GtkAllocation call; - call.x = alloc->x + border + child->x; - call.y = alloc->y + border + child->y; - call.width = child->w; - call.height = child->h; - gtk_widget_size_allocate(child->widget, &call); - } - } -} - -#else /* GTK_CHECK_VERSION(3,0,0) */ - -static gint columns_gtk3_get_min_width(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_width(child->widget, &ret, NULL); - return ret; -} - -static gint columns_gtk3_get_nat_width(ColumnsChild *child) -{ - gint ret; - - if ((GTK_IS_LABEL(child->widget) && - gtk_label_get_line_wrap(GTK_LABEL(child->widget))) || - GTK_IS_ENTRY(child->widget)) { - /* - * We treat wrapping GtkLabels as a special case in this - * layout class, because the whole point of those is that I - * _don't_ want them to take up extra horizontal space for - * long text, but instead to wrap it to whatever size is used - * by the rest of the layout. - * - * GtkEntry gets similar treatment, because in OS X GTK I've - * found that it requests a natural width regardless of the - * output of gtk_entry_set_width_chars. - */ - gtk_widget_get_preferred_width(child->widget, &ret, NULL); - } else { - gtk_widget_get_preferred_width(child->widget, NULL, &ret); - } - return ret; -} - -static gint columns_gtk3_get_minfh_width(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_width_for_height(child->widget, child->h, - &ret, NULL); - return ret; -} - -static gint columns_gtk3_get_natfh_width(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_width_for_height(child->widget, child->h, - NULL, &ret); - return ret; -} - -static gint columns_gtk3_get_min_height(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_height(child->widget, &ret, NULL); - return ret; -} - -static gint columns_gtk3_get_nat_height(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_height(child->widget, NULL, &ret); - return ret; -} - -static gint columns_gtk3_get_minfw_height(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_height_for_width(child->widget, child->w, - &ret, NULL); - return ret; -} - -static gint columns_gtk3_get_natfw_height(ColumnsChild *child) -{ - gint ret; - gtk_widget_get_preferred_height_for_width(child->widget, child->w, - NULL, &ret); - return ret; -} - -static void columns_get_preferred_width(GtkWidget *widget, - gint *min, gint *nat) -{ - Columns *cols; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - - cols = COLUMNS(widget); - - if (min) - *min = columns_compute_width(cols, columns_gtk3_get_min_width); - if (nat) - *nat = columns_compute_width(cols, columns_gtk3_get_nat_width); -} - -static void columns_get_preferred_height(GtkWidget *widget, - gint *min, gint *nat) -{ - Columns *cols; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - - cols = COLUMNS(widget); - - if (min) - *min = columns_compute_height(cols, columns_gtk3_get_min_height); - if (nat) - *nat = columns_compute_height(cols, columns_gtk3_get_nat_height); -} - -static void columns_get_preferred_width_for_height(GtkWidget *widget, - gint height, - gint *min, gint *nat) -{ - Columns *cols; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - - cols = COLUMNS(widget); - - /* FIXME: which one should the get-height function here be? */ - columns_alloc_vert(cols, height, columns_gtk3_get_nat_height); - - if (min) - *min = columns_compute_width(cols, columns_gtk3_get_minfh_width); - if (nat) - *nat = columns_compute_width(cols, columns_gtk3_get_natfh_width); -} - -static void columns_get_preferred_height_for_width(GtkWidget *widget, - gint width, - gint *min, gint *nat) -{ - Columns *cols; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - - cols = COLUMNS(widget); - - /* FIXME: which one should the get-height function here be? */ - columns_alloc_horiz(cols, width, columns_gtk3_get_nat_width); - - if (min) - *min = columns_compute_height(cols, columns_gtk3_get_minfw_height); - if (nat) - *nat = columns_compute_height(cols, columns_gtk3_get_natfw_height); -} - -static void columns_size_allocate(GtkWidget *widget, GtkAllocation *alloc) -{ - Columns *cols; - ColumnsChild *child; - GList *children; - gint border; - - g_return_if_fail(widget != NULL); - g_return_if_fail(IS_COLUMNS(widget)); - g_return_if_fail(alloc != NULL); - - cols = COLUMNS(widget); - gtk_widget_set_allocation(widget, alloc); - - border = gtk_container_get_border_width(GTK_CONTAINER(cols)); - - columns_alloc_horiz(cols, alloc->width, columns_gtk3_get_min_width); - columns_alloc_vert(cols, alloc->height, columns_gtk3_get_minfw_height); - - for (children = cols->children; - children && (child = children->data); - children = children->next) { - if (child->widget && gtk_widget_get_visible(child->widget)) { - GtkAllocation call; - call.x = alloc->x + border + child->x; - call.y = alloc->y + border + child->y; - call.width = child->w; - call.height = child->h; - gtk_widget_size_allocate(child->widget, &call); - } - } -} - -#endif diff --git a/unix/columns.h b/unix/columns.h deleted file mode 100644 index bca306b4c..000000000 --- a/unix/columns.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * columns.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) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_COLUMNS, Columns)) -#define COLUMNS_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_COLUMNS, ColumnsClass)) -#define IS_COLUMNS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_COLUMNS)) -#define IS_COLUMNS_CLASS(klass) (G_TYPE_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 */ - ColumnsChild *vexpand; - 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; - bool force_left; /* for recalcitrant GtkLabels */ - /* Otherwise, this entry represents a change in the column setup. */ - gint ncols; - gint *percentages; - gint x, y, w, h; /* used during an individual size computation */ - - /* Circularly linked list of children that are vertically aligned - * with each other. */ - ColumnsChild *valign_next, *valign_prev; - - /* Temporary space used within some methods */ - bool visited; -}; - -GType 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); -void columns_align_next_to(Columns *cols, GtkWidget *ch1, GtkWidget *ch2); -void columns_vexpand(Columns *cols, GtkWidget *child); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* COLUMNS_H */ diff --git a/unix/config-gtk.c b/unix/config-gtk.c deleted file mode 100644 index be70f5cbd..000000000 --- a/unix/config-gtk.c +++ /dev/null @@ -1,160 +0,0 @@ -/* - * config-gtk.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(dlgcontrol *ctrl, dlgparam *dlg, - void *data, int event) -{ - if (event == EVENT_ACTION) { - about_box(ctrl->context.p); - } -} - -void gtk_setup_config_box(struct controlbox *b, bool midsession, void *win) -{ - struct controlset *s, *s2; - dlgcontrol *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->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->type == CTRL_CHECKBOX && - c->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(dlgcontrol *)); - 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), ED_INT); - - /* - * 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)); - -#ifdef OSX_META_KEY_CONFIG - /* - * On OS X, there are multiple reasonable opinions about whether - * Option or Command (or both, or neither) should act as a Meta - * key, or whether they should have their normal OS functions. - */ - s = ctrl_getset(b, "Terminal/Keyboard", "meta", - "Choose the Meta key:"); - ctrl_checkbox(s, "Option key acts as Meta", 'p', - HELPCTX(no_help), - conf_checkbox_handler, I(CONF_osx_option_meta)); - ctrl_checkbox(s, "Command key acts as Meta", 'm', - HELPCTX(no_help), - conf_checkbox_handler, I(CONF_osx_command_meta)); -#endif - - 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), ED_STR); - } -} diff --git a/unix/config-unix.c b/unix/config-unix.c deleted file mode 100644 index 179d8a9c0..000000000 --- a/unix/config-unix.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * config-unix.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, bool midsession, int protocol) -{ - struct controlset *s; - dlgcontrol *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]->type == CTRL_EDITBOX); - s->ctrls[0]->editbox.has_list = false; - - /* - * Unix supports a local-command proxy. - */ - 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->type == CTRL_LISTBOX && - c->handler == proxy_type_handler) { - c->context.i |= PROXY_UI_FLAG_LOCAL; - break; - } - } - } -} diff --git a/unix/console.c b/unix/console.c deleted file mode 100644 index f3d983098..000000000 --- a/unix/console.c +++ /dev/null @@ -1,607 +0,0 @@ -/* - * unix/console.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" -#include "console.h" - -static struct termios orig_termios_stderr; -static bool 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 (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); -} - -void cleanup_exit(int code) -{ - /* - * Clean up. - */ - sk_cleanup(); - random_save_seed(); - exit(code); -} - -void console_print_error_msg(const char *prefix, const char *msg) -{ - struct termios cf; - premsg(&cf); - fputs(prefix, stderr); - fputs(": ", stderr); - fputs(msg, stderr); - fputc('\n', stderr); - fflush(stderr); - postmsg(&cf); -} - -/* - * 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 poll and then trying again; all other - * errors (including errors from poll) are returned to the caller. - */ -static int block_and_read(int fd, void *buf, size_t len) -{ - int ret; - pollwrapper *pw = pollwrap_new(); - - while ((ret = read(fd, buf, len)) < 0 && ( -#ifdef EAGAIN - (errno == EAGAIN) || -#endif -#ifdef EWOULDBLOCK - (errno == EWOULDBLOCK) || -#endif - false)) { - - pollwrap_clear(pw); - pollwrap_add_fd_rwx(pw, fd, SELECT_R); - do { - ret = pollwrap_poll_endless(pw); - } while (ret < 0 && errno == EINTR); - assert(ret != 0); - if (ret < 0) { - pollwrap_free(pw); - return ret; - } - assert(pollwrap_check_fd_rwx(pw, fd, SELECT_R)); - } - - pollwrap_free(pw); - return ret; -} - -/* - * Helper function to print the message from a SeatDialogText. Returns - * the final prompt to print on the input line, or NULL if a - * batch-mode abort is needed. In the latter case it will have printed - * the abort text already. - */ -static const char *console_print_seatdialogtext(SeatDialogText *text) -{ - const char *prompt = NULL; - stdio_sink errsink[1]; - stdio_sink_init(errsink, stderr); - - for (SeatDialogTextItem *item = text->items, - *end = item+text->nitems; item < end; item++) { - switch (item->type) { - case SDT_PARA: - wordwrap(BinarySink_UPCAST(errsink), - ptrlen_from_asciz(item->text), 60); - fputc('\n', stderr); - break; - case SDT_DISPLAY: - fprintf(stderr, " %s\n", item->text); - break; - case SDT_SCARY_HEADING: - /* Can't change font size or weight in this context */ - fprintf(stderr, "%s\n", item->text); - break; - case SDT_BATCH_ABORT: - if (console_batch_mode) { - fprintf(stderr, "%s\n", item->text); - fflush(stderr); - return NULL; - } - break; - case SDT_PROMPT: - prompt = item->text; - break; - default: - break; - } - } - - assert(prompt); /* something in the SeatDialogText should have set this */ - return prompt; -} - -SeatPromptResult console_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - char line[32]; - struct termios cf; - - premsg(&cf); - - const char *prompt = console_print_seatdialogtext(text); - if (!prompt) { - postmsg(&cf); - return SPR_SW_ABORT("Cannot confirm a host key in batch mode"); - } - - while (true) { - fprintf(stderr, - "%s (y/n, Return cancels connection, i for more info) ", - prompt); - 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] == 'i' || line[0] == 'I') { - for (SeatDialogTextItem *item = text->items, - *end = item+text->nitems; item < end; item++) { - switch (item->type) { - case SDT_MORE_INFO_KEY: - fprintf(stderr, "%s", item->text); - break; - case SDT_MORE_INFO_VALUE_SHORT: - fprintf(stderr, ": %s\n", item->text); - break; - case SDT_MORE_INFO_VALUE_BLOB: - fprintf(stderr, ":\n%s\n", item->text); - break; - default: - break; - } - } - } else { - break; - } - } - - /* In case of misplaced reflexes from another program, also recognise 'q' - * as 'abandon connection rather than trust this key' */ - if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n' && - line[0] != 'q' && line[0] != 'Q') { - if (line[0] == 'y' || line[0] == 'Y') - store_host_key(seat, host, port, keytype, keystr); - postmsg(&cf); - return SPR_OK; - } else { - fputs(console_abandoned_msg, stderr); - postmsg(&cf); - return SPR_USER_ABORT; - } -} - -SeatPromptResult console_confirm_weak_crypto_primitive( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - char line[32]; - struct termios cf; - - premsg(&cf); - - const char *prompt = console_print_seatdialogtext(text); - if (!prompt) { - postmsg(&cf); - return SPR_SW_ABORT("Cannot confirm a weak crypto primitive " - "in batch mode"); - } - - fprintf(stderr, "%s (y/n) ", prompt); - 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 SPR_OK; - } else { - fputs(console_abandoned_msg, stderr); - postmsg(&cf); - return SPR_USER_ABORT; - } -} - -SeatPromptResult console_confirm_weak_cached_hostkey( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - char line[32]; - struct termios cf; - - premsg(&cf); - - const char *prompt = console_print_seatdialogtext(text); - if (!prompt) { - postmsg(&cf); - return SPR_SW_ABORT("Cannot confirm a weak cached host key " - "in batch mode"); - } - - fprintf(stderr, "%s (y/n) ", prompt); - 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 SPR_OK; - } else { - fputs(console_abandoned_msg, stderr); - postmsg(&cf); - return SPR_USER_ABORT; - } -} - -/* - * 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 console_askappend(LogPolicy *lp, 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; -} - -bool console_antispoof_prompt = true; - -void console_set_trust_status(Seat *seat, bool trusted) -{ - /* Do nothing in response to a change of trust status, because - * there's nothing we can do in a console environment. However, - * the query function below will make a fiddly decision about - * whether to tell the backend to enable fallback handling. */ -} - -bool console_can_set_trust_status(Seat *seat) -{ - if (console_batch_mode) { - /* - * In batch mode, we don't need to worry about the server - * mimicking our interactive authentication, because the user - * already knows not to expect any. - */ - return true; - } - - return false; -} - -bool console_has_mixed_input_stream(Seat *seat) -{ - if (!is_interactive() || !console_antispoof_prompt) { - /* - * If standard input isn't connected to a terminal, then even - * if the server did send a spoof authentication prompt, the - * user couldn't respond to it via the terminal anyway. - * - * We also pretend this is true if the user has purposely - * disabled the antispoof prompt. - */ - return false; - } - - return true; -} - -/* - * 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_logging_error(LogPolicy *lp, const char *string) -{ - /* Errors setting up logging are considered important, so they're - * displayed to standard error even when not in verbose mode */ - struct termios cf; - premsg(&cf); - fprintf(stderr, "%s\n", string); - fflush(stderr); - postmsg(&cf); -} - - -void console_eventlog(LogPolicy *lp, const char *string) -{ - /* Ordinary Event Log entries are displayed in the same way as - * logging errors, but only in verbose mode */ - if (lp_verbose(lp)) - console_logging_error(lp, string); -} - -StripCtrlChars *console_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) -{ - return stripctrl_new(bs_out, false, 0); -} - -/* - * 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_write(FILE *outfp, ptrlen data) -{ - fwrite(data.ptr, 1, data.len, outfp); - fflush(outfp); -} - -SeatPromptResult console_get_userpass_input(prompts_t *p) -{ - 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 SPR_SW_ABORT("Cannot answer interactive prompts " - "in batch mode"); - - console_open(&outfp, &infd); - - /* - * Preamble. - */ - /* We only print the `name' caption if we have to... */ - if (p->name_reqd && p->name) { - ptrlen plname = ptrlen_from_asciz(p->name); - console_write(outfp, plname); - if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) - console_write(outfp, PTRLEN_LITERAL("\n")); - } - /* ...but we always print any `instruction'. */ - if (p->instruction) { - ptrlen plinst = ptrlen_from_asciz(p->instruction); - console_write(outfp, plinst); - if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) - console_write(outfp, PTRLEN_LITERAL("\n")); - } - - for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { - - struct termios oldmode, newmode; - 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_write(outfp, ptrlen_from_asciz(pr->prompt)); - - bool failed = false; - SeatPromptResult spr; - while (1) { - size_t toread = 65536; - size_t prev_result_len = pr->result->len; - void *ptr = strbuf_append(pr->result, toread); - int ret = read(infd, ptr, toread); - - if (ret == 0) { - /* Regard EOF on the terminal as a deliberate user-abort */ - failed = true; - spr = SPR_USER_ABORT; - break; - } - - if (ret < 0) { - /* Any other failure to read from the terminal is treated as - * an unexpected error and reported to the user. */ - failed = true; - spr = make_spr_sw_abort_errno( - "Error reading from terminal", errno); - break; - } - - strbuf_shrink_to(pr->result, prev_result_len + ret); - if (strbuf_chomp(pr->result, '\n')) - break; - } - - tcsetattr(infd, TCSANOW, &oldmode); - - if (!pr->echo) - console_write(outfp, PTRLEN_LITERAL("\n")); - - if (failed) { - console_close(outfp, infd); - return spr; - } - } - - console_close(outfp, infd); - - return SPR_OK; -} - -bool is_interactive(void) -{ - return isatty(0); -} - -bool console_set_stdio_prompts(bool newvalue) -{ - /* Sending prompts to stdio in place of /dev/tty is not supported - * in the Unix tools. It's only supported on Windows because of - * years of history making it likely someone was depending on it. */ - return false; -} - -bool set_legacy_charset_handling(bool newvalue) -{ - /* This probably _will_ need to be supported, but isn't yet. */ - return false; -} - -/* - * X11-forwarding-related things suitable for console. - */ - -char *platform_get_x_display(void) { - return dupstr(getenv("DISPLAY")); -} diff --git a/unix/dialog.c b/unix/dialog.c deleted file mode 100644 index 6c70aaa75..000000000 --- a/unix/dialog.c +++ /dev/null @@ -1,4398 +0,0 @@ -/* - * dialog.c - GTK implementation of the PuTTY configuration box. - */ - -#include -#include -#include -#include - -#include -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "gtkcompat.h" -#include "columns.h" -#include "unifont.h" -#include "gtkmisc.h" - -#ifndef NOT_X_WINDOWS -#include -#include -#include -#include "x11misc.h" -#endif - -#include "storage.h" -#include "dialog.h" -#include "tree234.h" -#include "licence.h" -#include "ssh.h" - -#if GTK_CHECK_VERSION(2,0,0) -/* Decide which of GtkFileChooserDialog and GtkFileSelection to use */ -#define USE_GTK_FILE_CHOOSER_DIALOG -#endif - -struct Shortcut { - GtkWidget *widget; - struct uctrl *uc; - int action; -}; - -struct Shortcuts { - struct Shortcut sc[128]; -}; - -struct selparam; - -struct uctrl { - dlgcontrol *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 */ - struct selparam *sp; /* which switchable pane of the box we're in */ - guint textsig; - int nclicks; - const char *textvalue; /* temporary, for button-only file selectors */ -}; - -struct dlgparam { - tree234 *byctrl, *bywidget; - void *data; - struct { - unsigned char r, g, b; /* 0-255 */ - bool ok; - } coloursel_result; - /* `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; - dlgcontrol *currfocus, *lastfocus; -#if !GTK_CHECK_VERSION(2,0,0) - GtkWidget *currtreeitem, **treeitems; - int ntreeitems; -#else - size_t nselparams; - struct selparam **selparams; -#endif - struct selparam *curr_panel; - struct controlbox *ctrlbox; - int retval; - post_dialog_fn_t after; - void *afterctx; -}; -#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 -#if GTK_CHECK_VERSION(3,0,0) -static void colourchoose_response(GtkDialog *dialog, - gint response_id, gpointer data); -#else -static void coloursel_ok(GtkButton *button, gpointer data); -static void coloursel_cancel(GtkButton *button, gpointer data); -#endif -static void dlgparam_destroy(GtkWidget *widget, gpointer data); -static 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) -{ - dlgcontrol *a = (dlgcontrol *)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->curr_panel = NULL; - 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, dlgcontrol *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 = gtk_widget_get_parent(w); - } while (w); - return ret; -} - -dlgcontrol *dlg_last_focused(dlgcontrol *ctrl, dlgparam *dp) -{ - if (dp->currfocus != ctrl) - return dp->currfocus; - else - return dp->lastfocus; -} - -void dlg_radiobutton_set(dlgcontrol *ctrl, dlgparam *dp, int which) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->type == CTRL_RADIO); - assert(uc->buttons != NULL); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->buttons[which]), true); -} - -int dlg_radiobutton_get(dlgcontrol *ctrl, dlgparam *dp) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - int i; - - assert(uc->ctrl->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(dlgcontrol *ctrl, dlgparam *dp, bool checked) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->type == CTRL_CHECKBOX); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(uc->toplevel), checked); -} - -bool dlg_checkbox_get(dlgcontrol *ctrl, dlgparam *dp) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->type == CTRL_CHECKBOX); - return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(uc->toplevel)); -} - -void dlg_editbox_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - GtkWidget *entry; - char *tmpstring; - assert(uc->ctrl->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(dlgcontrol *ctrl, dlgparam *dp) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->type == CTRL_EDITBOX); - -#if GTK_CHECK_VERSION(2,4,0) - if (uc->combo) { - return dupstr(gtk_entry_get_text( - GTK_ENTRY(gtk_bin_get_child(GTK_BIN(uc->combo))))); - } -#endif - - if (uc->entry) { - return dupstr(gtk_entry_get_text(GTK_ENTRY(uc->entry))); - } - - unreachable("bad control type in editbox_get"); -} - -void dlg_editbox_select_range(dlgcontrol *ctrl, dlgparam *dp, - size_t start, size_t len) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->type == CTRL_EDITBOX); - - GtkWidget *entry = NULL; - -#if GTK_CHECK_VERSION(2,4,0) - if (uc->combo) - entry = gtk_bin_get_child(GTK_BIN(uc->combo)); -#endif - - if (uc->entry) - entry = uc->entry; - - assert(entry && "we should have a GtkEntry one way or another"); - - gtk_editable_select_region(GTK_EDITABLE(entry), start, start + len); -} - -#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(dlgcontrol *ctrl, dlgparam *dp) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - - assert(uc->ctrl->type == CTRL_EDITBOX || - uc->ctrl->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 - unreachable("bad control type in listbox_clear"); -} - -void dlg_listbox_del(dlgcontrol *ctrl, dlgparam *dp, int index) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - - assert(uc->ctrl->type == CTRL_EDITBOX || - uc->ctrl->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 - unreachable("bad control type in listbox_del"); -} - -void dlg_listbox_add(dlgcontrol *ctrl, dlgparam *dp, char const *text) -{ - dlg_listbox_addwithid(ctrl, dp, 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(dlgcontrol *ctrl, dlgparam *dp, - char const *text, int id) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - - assert(uc->ctrl->type == CTRL_EDITBOX || - uc->ctrl->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); - - g_object_set_data(G_OBJECT(menuitem), "user-data", - GINT_TO_POINTER(id)); - g_signal_connect(G_OBJECT(menuitem), "activate", - G_CALLBACK(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); - - g_object_set_data(G_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) { - g_signal_connect(G_OBJECT(listitem), "key_press_event", - G_CALLBACK(listitem_multi_key), uc->adj); - } else { - g_signal_connect(G_OBJECT(listitem), "key_press_event", - G_CALLBACK(listitem_single_key), uc->adj); - } - g_signal_connect(G_OBJECT(listitem), "focus_in_event", - G_CALLBACK(widget_focus), dp); - g_signal_connect(G_OBJECT(listitem), "button_press_event", - G_CALLBACK(listitem_button_press), dp); - g_signal_connect(G_OBJECT(listitem), "button_release_event", - G_CALLBACK(listitem_button_release), dp); - g_object_set_data(G_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->type == CTRL_LISTBOX ? ctrl->listbox.ncols : 1); - cols = cols ? cols : 1; - for (i = 0; i < cols; i++) { - int collen = strcspn(text, "\t"); - char *tmpstr = mkstr(make_ptrlen(text, collen)); - gtk_list_store_set(uc->listmodel, &iter, i+1, tmpstr, -1); - sfree(tmpstr); - text += collen; - if (*text) text++; - } - goto done; - } -#endif - unreachable("bad control type in listbox_addwithid"); - done: - dp->flags &= ~FLAG_UPDATING_COMBO_LIST; -} - -int dlg_listbox_getid(dlgcontrol *ctrl, dlgparam *dp, int index) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - - assert(uc->ctrl->type == CTRL_EDITBOX || - uc->ctrl->type == CTRL_LISTBOX); - -#if !GTK_CHECK_VERSION(2,4,0) - if (uc->menu || uc->list) { - GList *children; - GObject *item; - - children = gtk_container_children(GTK_CONTAINER(uc->menu ? uc->menu : - uc->list)); - item = G_OBJECT(g_list_nth_data(children, index)); - g_list_free(children); - - return GPOINTER_TO_INT(g_object_get_data(G_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 - unreachable("bad control type in listbox_getid"); - return -1; /* placate dataflow analysis */ -} - -/* dlg_listbox_index returns <0 if no single element is selected. */ -int dlg_listbox_index(dlgcontrol *ctrl, dlgparam *dp) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - - assert(uc->ctrl->type == CTRL_EDITBOX || - uc->ctrl->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 - unreachable("bad control type in listbox_index"); - return -1; /* placate dataflow analysis */ -} - -bool dlg_listbox_issel(dlgcontrol *ctrl, dlgparam *dp, int index) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - - assert(uc->ctrl->type == CTRL_EDITBOX || - uc->ctrl->type == CTRL_LISTBOX); - -#if !GTK_CHECK_VERSION(2,4,0) - if (uc->menu || uc->list) { - GList *children; - GtkWidget *item, *activeitem; - - assert(uc->ctrl->type == CTRL_EDITBOX || - uc->ctrl->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; - bool 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 - unreachable("bad control type in listbox_issel"); - return false; /* placate dataflow analysis */ -} - -void dlg_listbox_select(dlgcontrol *ctrl, dlgparam *dp, int index) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - - assert(uc->ctrl->type == CTRL_EDITBOX || - uc->ctrl->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) { - bool 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 - unreachable("bad control type in listbox_select"); -} - -void dlg_text_set(dlgcontrol *ctrl, dlgparam *dp, char const *text) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - - assert(uc->ctrl->type == CTRL_TEXT); - assert(uc->text != NULL); - - gtk_label_set_text(GTK_LABEL(uc->text), text); -} - -void dlg_label_change(dlgcontrol *ctrl, dlgparam *dp, char const *text) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - - switch (uc->ctrl->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: - if (uc->label) { - 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: - unreachable("bad control type in label_change"); - } -} - -void dlg_filesel_set(dlgcontrol *ctrl, dlgparam *dp, Filename *fn) -{ - 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->type == CTRL_FILESELECT); - assert(uc->entry != NULL); - gtk_entry_set_text(GTK_ENTRY(uc->entry), duppath); - sfree(duppath); -} - -Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->type == CTRL_FILESELECT); - if (!uc->entry) { - assert(uc->textvalue); - return filename_from_str(uc->textvalue); - } else { - return filename_from_str(gtk_entry_get_text(GTK_ENTRY(uc->entry))); - } -} - -void dlg_fontsel_set(dlgcontrol *ctrl, dlgparam *dp, FontSpec *fs) -{ - 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->type == CTRL_FONTSELECT); - assert(uc->entry != NULL); - gtk_entry_set_text(GTK_ENTRY(uc->entry), dupname); - sfree(dupname); -} - -FontSpec *dlg_fontsel_get(dlgcontrol *ctrl, dlgparam *dp) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - assert(uc->ctrl->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(dlgcontrol *ctrl, dlgparam *dp) -{ - /* - * Apparently we can't do this at all in GTK. GtkCList supports - * freeze and thaw, but not GtkList. Bah. - */ -} - -void dlg_update_done(dlgcontrol *ctrl, dlgparam *dp) -{ - /* - * Apparently we can't do this at all in GTK. GtkCList supports - * freeze and thaw, but not GtkList. Bah. - */ -} - -void dlg_set_focus(dlgcontrol *ctrl, dlgparam *dp) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - - switch (ctrl->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. - */ - for (int 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 - unreachable("bad control type in set_focus"); - } -} - -/* - * 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(dlgparam *dp) -{ - gdk_display_beep(gdk_display_get_default()); -} - -static void set_transient_window_pos(GtkWidget *parent, GtkWidget *child) -{ -#if !GTK_CHECK_VERSION(2,0,0) - 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_get_window(GTK_WIDGET(parent)), &x, &y); - gdk_window_get_size(gtk_widget_get_window(GTK_WIDGET(parent)), &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); -#endif -} - -void trivial_post_dialog_fn(void *vctx, int result) -{ -} - -void dlg_error_msg(dlgparam *dp, const char *msg) -{ - create_message_box( - dp->window, "Error", msg, - string_width("Some sort of text about a config-box error message"), - false, &buttons_ok, trivial_post_dialog_fn, NULL); -} - -/* - * 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(dlgparam *dp, int value) -{ - dp->retval = value; - gtk_widget_destroy(dp->window); -} - -void dlg_refresh(dlgcontrol *ctrl, dlgparam *dp) -{ - struct uctrl *uc; - - if (ctrl) { - if (ctrl->handler != NULL) - ctrl->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->handler != NULL) - uc->ctrl->handler(uc->ctrl, dp, - dp->data, EVENT_REFRESH); - } - } -} - -void dlg_coloursel_start(dlgcontrol *ctrl, dlgparam *dp, int r, int g, int b) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - -#if GTK_CHECK_VERSION(3,0,0) - GtkWidget *coloursel = - gtk_color_chooser_dialog_new("Select a colour", - GTK_WINDOW(dp->window)); - gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(coloursel), false); -#else - GtkWidget *okbutton, *cancelbutton; - GtkWidget *coloursel = - gtk_color_selection_dialog_new("Select a colour"); - GtkColorSelectionDialog *ccs = GTK_COLOR_SELECTION_DIALOG(coloursel); - GtkColorSelection *cs = GTK_COLOR_SELECTION( - gtk_color_selection_dialog_get_color_selection(ccs)); - gtk_color_selection_set_has_opacity_control(cs, false); -#endif - - dp->coloursel_result.ok = false; - - gtk_window_set_modal(GTK_WINDOW(coloursel), true); - -#if GTK_CHECK_VERSION(3,0,0) - { - GdkRGBA rgba; - rgba.red = r / 255.0; - rgba.green = g / 255.0; - rgba.blue = b / 255.0; - rgba.alpha = 1.0; /* fully opaque! */ - gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(coloursel), &rgba); - } -#elif GTK_CHECK_VERSION(2,0,0) - { - GdkColor col; - col.red = r * 0x0101; - col.green = g * 0x0101; - col.blue = b * 0x0101; - gtk_color_selection_set_current_color(cs, &col); - } -#else - { - gdouble cvals[4]; - 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(cs, cvals); - } -#endif - - g_object_set_data(G_OBJECT(coloursel), "user-data", (gpointer)uc); - -#if GTK_CHECK_VERSION(3,0,0) - g_signal_connect(G_OBJECT(coloursel), "response", - G_CALLBACK(colourchoose_response), (gpointer)dp); -#else - -#if GTK_CHECK_VERSION(2,0,0) - g_object_get(G_OBJECT(ccs), - "ok-button", &okbutton, - "cancel-button", &cancelbutton, - (const char *)NULL); -#else - okbutton = ccs->ok_button; - cancelbutton = ccs->cancel_button; -#endif - g_object_set_data(G_OBJECT(okbutton), "user-data", - (gpointer)coloursel); - g_object_set_data(G_OBJECT(cancelbutton), "user-data", - (gpointer)coloursel); - g_signal_connect(G_OBJECT(okbutton), "clicked", - G_CALLBACK(coloursel_ok), (gpointer)dp); - g_signal_connect(G_OBJECT(cancelbutton), "clicked", - G_CALLBACK(coloursel_cancel), (gpointer)dp); - g_signal_connect_swapped(G_OBJECT(okbutton), "clicked", - G_CALLBACK(gtk_widget_destroy), - (gpointer)coloursel); - g_signal_connect_swapped(G_OBJECT(cancelbutton), "clicked", - G_CALLBACK(gtk_widget_destroy), - (gpointer)coloursel); -#endif - gtk_widget_show(coloursel); -} - -bool dlg_coloursel_results(dlgcontrol *ctrl, dlgparam *dp, - int *r, int *g, int *b) -{ - if (dp->coloursel_result.ok) { - *r = dp->coloursel_result.r; - *g = dp->coloursel_result.g; - *b = dp->coloursel_result.b; - return true; - } else - return false; -} - -/* ---------------------------------------------------------------------- - * 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); - dlgcontrol *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->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->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. - */ - GtkWidget *parent = gtk_widget_get_parent(widget); - if (event->keyval == GDK_KEY_Return && parent != NULL) { - gboolean return_val; - g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event"); - g_signal_emit_by_name(G_OBJECT(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->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->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, bool 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; - - g_signal_stop_emission_by_name(G_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->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->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_display_beep(gdk_display_get_default()); - 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->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->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->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->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 = g_object_get_data(G_OBJECT(menushell), "user-data"); - struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(optmenu)); - uc->ctrl->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->handler(uc->ctrl, dp, dp->data, EVENT_SELCHANGE); -} - -#endif /* !GTK_CHECK_VERSION(2,4,0) */ - -static void filechoose_emit_value(struct dlgparam *dp, struct uctrl *uc, - const char *name) -{ - if (uc->entry) { - gtk_entry_set_text(GTK_ENTRY(uc->entry), name); - } else { - uc->textvalue = name; - uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_ACTION); - uc->textvalue = NULL; - } -} - -#ifdef USE_GTK_FILE_CHOOSER_DIALOG -static void filechoose_response(GtkDialog *dialog, gint response, - gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data"); - if (response == GTK_RESPONSE_ACCEPT) { - gchar *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - filechoose_emit_value(dp, uc, name); - g_free(name); - } - gtk_widget_destroy(GTK_WIDGET(dialog)); -} -#else -static void filesel_ok(GtkButton *button, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - gpointer filesel = g_object_get_data(G_OBJECT(button), "user-data"); - struct uctrl *uc = g_object_get_data(G_OBJECT(filesel), "user-data"); - const char *name = gtk_file_selection_get_filename( - GTK_FILE_SELECTION(filesel)); - filechoose_emit_value(dp, uc, name); -} -#endif - -static void fontsel_ok(GtkButton *button, gpointer data) -{ - /* struct dlgparam *dp = (struct dlgparam *)data; */ - -#if !GTK_CHECK_VERSION(2,0,0) - - gpointer fontsel = g_object_get_data(G_OBJECT(button), "user-data"); - struct uctrl *uc = g_object_get_data(G_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 *)g_object_get_data( - G_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 -} - -#if GTK_CHECK_VERSION(3,0,0) - -static void colourchoose_response(GtkDialog *dialog, - gint response_id, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - struct uctrl *uc = g_object_get_data(G_OBJECT(dialog), "user-data"); - - if (response_id == GTK_RESPONSE_OK) { - GdkRGBA rgba; - gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &rgba); - dp->coloursel_result.r = (int) (255 * rgba.red); - dp->coloursel_result.g = (int) (255 * rgba.green); - dp->coloursel_result.b = (int) (255 * rgba.blue); - dp->coloursel_result.ok = true; - } else { - dp->coloursel_result.ok = false; - } - - uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); - - gtk_widget_destroy(GTK_WIDGET(dialog)); -} - -#else /* GTK 1/2 coloursel response handlers */ - -static void coloursel_ok(GtkButton *button, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data"); - struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data"); - -#if GTK_CHECK_VERSION(2,0,0) - { - GtkColorSelection *cs = GTK_COLOR_SELECTION( - gtk_color_selection_dialog_get_color_selection( - GTK_COLOR_SELECTION_DIALOG(coloursel))); - GdkColor col; - gtk_color_selection_get_current_color(cs, &col); - dp->coloursel_result.r = col.red / 0x0100; - dp->coloursel_result.g = col.green / 0x0100; - dp->coloursel_result.b = col.blue / 0x0100; - } -#else - { - GtkColorSelection *cs = GTK_COLOR_SELECTION( - gtk_color_selection_dialog_get_color_selection( - GTK_COLOR_SELECTION_DIALOG(coloursel))); - gdouble cvals[4]; - gtk_color_selection_get_color(cs, 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]); - } -#endif - dp->coloursel_result.ok = true; - uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); -} - -static void coloursel_cancel(GtkButton *button, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - gpointer coloursel = g_object_get_data(G_OBJECT(button), "user-data"); - struct uctrl *uc = g_object_get_data(G_OBJECT(coloursel), "user-data"); - dp->coloursel_result.ok = false; - uc->ctrl->handler(uc->ctrl, dp, dp->data, EVENT_CALLBACK); -} - -#endif /* end of coloursel response handlers */ - -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->type == CTRL_FILESELECT) { -#ifdef USE_GTK_FILE_CHOOSER_DIALOG - GtkWidget *filechoose = gtk_file_chooser_dialog_new( - uc->ctrl->fileselect.title, GTK_WINDOW(dp->window), - (uc->ctrl->fileselect.for_writing ? - GTK_FILE_CHOOSER_ACTION_SAVE : - GTK_FILE_CHOOSER_ACTION_OPEN), - STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL, - STANDARD_OPEN_LABEL, GTK_RESPONSE_ACCEPT, - (const gchar *)NULL); - gtk_window_set_modal(GTK_WINDOW(filechoose), true); - g_object_set_data(G_OBJECT(filechoose), "user-data", (gpointer)uc); - g_signal_connect(G_OBJECT(filechoose), "response", - G_CALLBACK(filechoose_response), (gpointer)dp); - gtk_widget_show(filechoose); -#else - GtkWidget *filesel = - gtk_file_selection_new(uc->ctrl->fileselect.title); - gtk_window_set_modal(GTK_WINDOW(filesel), true); - g_object_set_data( - G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data", - (gpointer)filesel); - g_object_set_data(G_OBJECT(filesel), "user-data", (gpointer)uc); - g_signal_connect( - G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", - G_CALLBACK(filesel_ok), (gpointer)dp); - g_signal_connect_swapped( - G_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked", - G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); - g_signal_connect_swapped( - G_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked", - G_CALLBACK(gtk_widget_destroy), (gpointer)filesel); - gtk_widget_show(filesel); -#endif - } - - if (uc->ctrl->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 = get_x11_display(); - Atom fontprop = XInternAtom(disp, "FONT", False); - unsigned long ret; - - assert(disp); /* this is GTK1! */ - - 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); - } - } - g_object_set_data( - G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "user-data", (gpointer)fontsel); - g_object_set_data(G_OBJECT(fontsel), "user-data", (gpointer)uc); - g_signal_connect( - G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "clicked", G_CALLBACK(fontsel_ok), (gpointer)dp); - g_signal_connect_swapped( - G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->ok_button), - "clicked", G_CALLBACK(gtk_widget_destroy), - (gpointer)fontsel); - g_signal_connect_swapped( - G_OBJECT(GTK_FONT_SELECTION_DIALOG(fontsel)->cancel_button), - "clicked", G_CALLBACK(gtk_widget_destroy), - (gpointer)fontsel); - gtk_widget_show(fontsel); - -#else /* !GTK_CHECK_VERSION(2,0,0) */ - - /* - * Use the unifontsel code provided in unifont.c. - */ - - unifontsel *fontsel = unifontsel_new("Select a font"); - - gtk_window_set_modal(fontsel->window, true); - unifontsel_set_name(fontsel, fontname); - - g_object_set_data(G_OBJECT(fontsel->ok_button), - "user-data", (gpointer)fontsel); - fontsel->user_data = uc; - g_signal_connect(G_OBJECT(fontsel->ok_button), "clicked", - G_CALLBACK(fontsel_ok), (gpointer)dp); - g_signal_connect_swapped(G_OBJECT(fontsel->ok_button), "clicked", - G_CALLBACK(unifontsel_destroy), - (gpointer)fontsel); - g_signal_connect_swapped(G_OBJECT(fontsel->cancel_button),"clicked", - G_CALLBACK(unifontsel_destroy), - (gpointer)fontsel); - - gtk_widget_show(GTK_WIDGET(fontsel->window)); - -#endif /* !GTK_CHECK_VERSION(2,0,0) */ - - } -} - -#if !GTK_CHECK_VERSION(3,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_size_request(uc->text, alloc->width, -1); - gtk_label_set_text(GTK_LABEL(uc->text), uc->ctrl->label); - g_signal_handler_disconnect(G_OBJECT(uc->text), uc->textsig); -} -#endif - -/* ---------------------------------------------------------------------- - * 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 selparam *sp, struct Shortcuts *scs, - struct controlset *s, GtkWindow *win) -{ - Columns *cols; - GtkWidget *ret; - int i; - - if (!s->boxname) { - /* This controlset is a panel title. */ - assert(s->boxtitle); - 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++) { - dlgcontrol *ctrl = s->ctrls[i]; - struct uctrl *uc; - bool left = false; - GtkWidget *w = NULL; - - switch (ctrl->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->sp = sp; - 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->type) { - case CTRL_BUTTON: - w = gtk_button_new_with_label(ctrl->label); - if (win) { - gtk_widget_set_can_default(w, true); - if (ctrl->button.isdefault) - gtk_window_set_default(win, w); - if (ctrl->button.iscancel) - dp->cancelbutton = w; - } - g_signal_connect(G_OBJECT(w), "clicked", - G_CALLBACK(button_clicked), dp); - g_signal_connect(G_OBJECT(w), "focus_in_event", - G_CALLBACK(widget_focus), dp); - shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)), - ctrl->button.shortcut, SHORTCUT_UCTRL, uc); - break; - case CTRL_CHECKBOX: - w = gtk_check_button_new_with_label(ctrl->label); - g_signal_connect(G_OBJECT(w), "toggled", - G_CALLBACK(button_toggled), dp); - g_signal_connect(G_OBJECT(w), "focus_in_event", - G_CALLBACK(widget_focus), dp); - shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)), - 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->label) { - GtkWidget *label = gtk_label_new(ctrl->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_get_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); - g_signal_connect(G_OBJECT(b), "toggled", - G_CALLBACK(button_toggled), dp); - g_signal_connect(G_OBJECT(b), "focus_in_event", - G_CALLBACK(widget_focus), dp); - if (ctrl->radio.shortcuts) { - shortcut_add(scs, gtk_bin_get_child(GTK_BIN(b)), - ctrl->radio.shortcuts[i], - SHORTCUT_UCTRL, uc); - } - } - break; - } - case CTRL_EDITBOX: { - 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_new_with_model_and_entry( - GTK_TREE_MODEL(uc->listmodel)); - g_object_set(G_OBJECT(w), "entry-text-column", 1, - (const char *)NULL); - /* 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; - } - g_signal_connect(G_OBJECT(signalobject), "changed", - G_CALLBACK(editbox_changed), dp); - g_signal_connect(G_OBJECT(signalobject), "key_press_event", - G_CALLBACK(editbox_key), dp); - g_signal_connect(G_OBJECT(signalobject), "focus_in_event", - G_CALLBACK(widget_focus), dp); - g_signal_connect(G_OBJECT(signalobject), "focus_out_event", - G_CALLBACK(editbox_lostfocus), dp); - g_signal_connect(G_OBJECT(signalobject), "focus_out_event", - G_CALLBACK(editbox_lostfocus), dp); - -#if !GTK_CHECK_VERSION(3,0,0) - /* - * 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. - */ - { - GtkRequisition req; - gtk_widget_size_request(w, &req); - gtk_widget_set_size_request(w, 10, req.height); - } -#else - /* - * In GTK 3, this is still true, but there's a special - * method for GtkEntry in particular to fix it. - */ - if (GTK_IS_ENTRY(w)) - gtk_entry_set_width_chars(GTK_ENTRY(w), 1); -#endif - - if (ctrl->label) { - GtkWidget *label; - - label = gtk_label_new(ctrl->label); - - shortcut_add(scs, label, ctrl->editbox.shortcut, - SHORTCUT_FOCUS, uc->entry); - - if (ctrl->editbox.percentwidth == 100) { - columns_add(cols, label, - COLUMN_START(ctrl->column), - COLUMN_SPAN(ctrl->column)); - columns_force_left_align(cols, label); - } else { - GtkWidget *container = columns_new(4); - 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); - columns_align_next_to(COLUMNS(container), label, w); - gtk_widget_show(w); - w = container; - } - - gtk_widget_show(label); - uc->label = label; - } - break; - } - case CTRL_FILESELECT: - case CTRL_FONTSELECT: { - GtkWidget *ww; - - bool just_button = (ctrl->type == CTRL_FILESELECT && - ctrl->fileselect.just_button); - - if (!just_button) { - const char *browsebtn = - (ctrl->type == CTRL_FILESELECT ? - "Browse..." : "Change..."); - - gint percentages[] = { 75, 25 }; - w = columns_new(4); - columns_set_cols(COLUMNS(w), 2, percentages); - - if (ctrl->label) { - ww = gtk_label_new(ctrl->label); - columns_add(COLUMNS(w), ww, 0, 2); - columns_force_left_align(COLUMNS(w), ww); - gtk_widget_show(ww); - shortcut_add(scs, ww, - (ctrl->type == CTRL_FILESELECT ? - ctrl->fileselect.shortcut : - ctrl->fontselect.shortcut), - SHORTCUT_UCTRL, uc); - uc->label = ww; - } - - uc->entry = ww = gtk_entry_new(); -#if !GTK_CHECK_VERSION(3,0,0) - { - GtkRequisition req; - gtk_widget_size_request(ww, &req); - gtk_widget_set_size_request(ww, 10, req.height); - } -#else - gtk_entry_set_width_chars(GTK_ENTRY(ww), 1); -#endif - 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); - - columns_align_next_to(COLUMNS(w), uc->entry, uc->button); - - g_signal_connect(G_OBJECT(uc->entry), "key_press_event", - G_CALLBACK(editbox_key), dp); - g_signal_connect(G_OBJECT(uc->entry), "changed", - G_CALLBACK(editbox_changed), dp); - g_signal_connect(G_OBJECT(uc->entry), "focus_in_event", - G_CALLBACK(widget_focus), dp); - } else { - uc->button = w = gtk_button_new_with_label(ctrl->label); - shortcut_add(scs, gtk_bin_get_child(GTK_BIN(w)), - ctrl->fileselect.shortcut, SHORTCUT_UCTRL, uc); - gtk_widget_show(w); - - } - g_signal_connect(G_OBJECT(uc->button), "focus_in_event", - G_CALLBACK(widget_focus), dp); - g_signal_connect(G_OBJECT(uc->button), "clicked", - G_CALLBACK(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); - g_object_set_data(G_OBJECT(uc->menu), "user-data", - (gpointer)uc->optmenu); - g_signal_connect(G_OBJECT(uc->optmenu), "focus_in_event", - G_CALLBACK(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); - - g_signal_connect(G_OBJECT(w), "focus_in_event", - G_CALLBACK(widget_focus), 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); - g_signal_connect(G_OBJECT(uc->list), "selection-changed", - G_CALLBACK(list_selchange), dp); - g_signal_connect(G_OBJECT(uc->list), "focus_in_event", - G_CALLBACK(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_size_request( - 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); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(draglist_up), dp); - g_signal_connect(G_OBJECT(button), "focus_in_event", - G_CALLBACK(widget_focus), dp); - button = gtk_button_new_with_label("Down"); - columns_add(COLUMNS(cols), button, 1, 1); - gtk_widget_show(button); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(draglist_down), dp); - g_signal_connect(G_OBJECT(button), "focus_in_event", - G_CALLBACK(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; - g_signal_connect(G_OBJECT(w), "row-activated", - G_CALLBACK(listbox_doubleclick), dp); - g_signal_connect(G_OBJECT(w), "focus_in_event", - G_CALLBACK(widget_focus), 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) { - g_object_set(G_OBJECT(cellrend), - "ellipsize", PANGO_ELLIPSIZE_END, - "ellipsize-set", true, - (const char *)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->label) { - GtkWidget *label, *container; - - label = gtk_label_new(ctrl->label); -#if GTK_CHECK_VERSION(3,0,0) - gtk_label_set_width_chars(GTK_LABEL(label), 3); -#endif - - shortcut_add(scs, label, ctrl->listbox.shortcut, - SHORTCUT_UCTRL, uc); - - 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); - columns_align_next_to(COLUMNS(container), label, w); - } - gtk_widget_show(label); - gtk_widget_show(w); - - w = container; - uc->label = label; - } - - break; - case CTRL_TEXT: -#if !GTK_CHECK_VERSION(3,0,0) - /* - * Wrapping text widgets don't sit well with the GTK2 - * 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"); - uc->textsig = - g_signal_connect(G_OBJECT(w), "size-allocate", - G_CALLBACK(label_sizealloc), dp); -#else - /* - * In GTK3, this is all fixed, because the main aim of the - * new 'height-for-width' geometry management is to make - * wrapping labels behave sensibly. So now we can just do - * the obvious thing. - */ - uc->text = w = gtk_label_new(uc->ctrl->label); -#endif -#if GTK_CHECK_VERSION(2,0,0) - gtk_label_set_selectable(GTK_LABEL(w), true); - gtk_widget_set_can_focus(w, false); -#endif - align_label_left(GTK_LABEL(w)); - gtk_label_set_line_wrap(GTK_LABEL(w), ctrl->text.wrap); - if (!ctrl->text.wrap) { - gtk_widget_show(uc->text); - w = gtk_scrolled_window_new(NULL, NULL); - gtk_container_set_border_width(GTK_CONTAINER(w), 0); - gtk_container_add(GTK_CONTAINER(w), uc->text); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), - GTK_POLICY_AUTOMATIC, - GTK_POLICY_NEVER); -#if GTK_CHECK_VERSION(2,0,0) - gtk_widget_set_can_focus(w, false); -#endif - } - break; - } - - assert(w != NULL); - - columns_add(cols, w, - COLUMN_START(ctrl->column), - COLUMN_SPAN(ctrl->column)); - if (left) - columns_force_left_align(cols, w); - if (ctrl->align_next_to) { - struct uctrl *uc2 = dlg_find_byctrl( - dp, ctrl->align_next_to); - assert(uc2); - columns_align_next_to(cols, w, uc2->toplevel); - -#if GTK_CHECK_VERSION(3, 10, 0) - /* Slightly nicer to align baselines than just vertically - * centring, where the option is available */ - gtk_widget_set_valign(w, GTK_ALIGN_BASELINE); - gtk_widget_set_valign(uc2->toplevel, GTK_ALIGN_BASELINE); -#endif - } - 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_current_page(sp->panels, page_num); - - sp->dp->curr_panel = sp; - 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); - - sp->dp->curr_panel = sp; - dlg_refresh(NULL, sp->dp); - - sp->dp->shortcuts = &sp->shortcuts; - sp->dp->currtreeitem = sp->treeitem; -} -#endif - -bool dlg_is_visible(dlgcontrol *ctrl, dlgparam *dp) -{ - struct uctrl *uc = dlg_find_byctrl(dp, ctrl); - /* - * A control is visible if it belongs to _no_ notebook page (i.e. - * it's one of the config-box-global buttons like Load or About), - * or if it belongs to the currently selected page. - */ - return uc->sp == NULL || uc->sp == dp->curr_panel; -} - -#if !GTK_CHECK_VERSION(2,0,0) -static bool 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; - - g_signal_stop_emission_by_name(G_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 - -gint win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - - if (event->keyval == GDK_KEY_Escape && dp->cancelbutton) { - g_signal_emit_by_name(G_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->type) { - case CTRL_CHECKBOX: - case CTRL_BUTTON: - /* Check boxes and buttons get the focus _and_ get toggled. */ - gtk_widget_grab_focus(sc->uc->toplevel); - g_signal_emit_by_name(G_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. */ - g_signal_emit_by_name(G_OBJECT(sc->uc->button), "clicked"); - if (sc->uc->entry) - 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]); - g_signal_emit_by_name( - G_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; - g_signal_emit_by_name(G_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 - unreachable("bad listbox type in win_key_press"); - } - break; - } - } - - return false; -} - -#if !GTK_CHECK_VERSION(2,0,0) -gint 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]; - bool 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; - } - } - } - } - g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event"); - if (j >= 0) { - g_signal_emit_by_name(G_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) { - g_signal_stop_emission_by_name(G_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) { - g_signal_stop_emission_by_name(G_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); - const gchar *currstr; - gchar *pattern; - int i; - -#if !GTK_CHECK_VERSION(2,0,0) - { - gchar *currstr_nonconst; - gtk_label_get(label, &currstr_nonconst); - currstr = currstr_nonconst; - } -#else - currstr = gtk_label_get_text(label); -#endif - - for (i = 0; currstr[i]; i++) - if (tolower((unsigned char)currstr[i]) == chr) { - pattern = dupprintf("%*s_", i, ""); - gtk_label_set_pattern(label, pattern); - 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 || action == SHORTCUT_TREE) { - 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); -} - -static 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); - g_object_ref_sink(G_OBJECT(listitem)); - return req.height; -#else - int height; - GtkCellRenderer *cr = gtk_cell_renderer_text_new(); -#if GTK_CHECK_VERSION(3,0,0) - { - GtkRequisition req; - /* - * Since none of my list items wraps in this GUI, no - * interesting width-for-height behaviour should be happening, - * so I don't think it should matter here whether I ask for - * the minimum or natural height. - */ - gtk_cell_renderer_get_preferred_size(cr, w, &req, NULL); - height = req.height; - } -#else - gtk_cell_renderer_get_size(cr, w, NULL, NULL, NULL, NULL, &height); -#endif - g_object_ref(G_OBJECT(cr)); - g_object_ref_sink(G_OBJECT(cr)); - g_object_unref(G_OBJECT(cr)); - return height; -#endif -} - -#if GTK_CHECK_VERSION(2,0,0) -void initial_treeview_collapse(struct dlgparam *dp, GtkWidget *tree) -{ - /* - * Collapse the deeper branches of the treeview into the state we - * like them to start off in. See comment below in do_config_box. - */ - int i; - for (i = 0; i < dp->nselparams; i++) - if (dp->selparams[i]->depth >= 2) - gtk_tree_view_collapse_row(GTK_TREE_VIEW(tree), - dp->selparams[i]->treepath); -} -#endif - -#if GTK_CHECK_VERSION(3,0,0) -void treeview_map_event(GtkWidget *tree, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - GtkAllocation alloc; - gtk_widget_get_allocation(tree, &alloc); - gtk_widget_set_size_request(tree, alloc.width, -1); - initial_treeview_collapse(dp, tree); -} -#endif - -GtkWidget *create_config_box(const char *title, Conf *conf, - bool midsession, int protcfginfo, - post_dialog_fn_t after, void *afterctx) -{ - GtkWidget *window, *hbox, *vbox, *cols, *label, - *tree, *treescroll, *panels, *panelvbox; - int index, level, protocol; - 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; - size_t nselparams = 0, selparamsize = 0; - - dp = snew(struct dlgparam); - dp->after = after; - dp->afterctx = afterctx; - - dlg_init(dp); - - for (index = 0; index < lenof(scs.sc); index++) { - scs.sc[index].action = SHORTCUT_EMPTY; - } - - window = our_dialog_new(); - - dp->ctrlbox = ctrl_new_box(); - protocol = conf_get_int(conf, CONF_protocol); - setup_config_box(dp->ctrlbox, midsession, protocol, protcfginfo); - unix_setup_config_box(dp->ctrlbox, midsession, protocol); - gtk_setup_config_box(dp->ctrlbox, midsession, window); - - gtk_window_set_title(GTK_WINDOW(window), title); - hbox = gtk_hbox_new(false, 4); - our_dialog_add_to_content_area(GTK_WINDOW(window), 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); - g_signal_connect(G_OBJECT(tree), "focus", G_CALLBACK(tree_focus), dp); -#endif - g_signal_connect(G_OBJECT(tree), "focus_in_event", - G_CALLBACK(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 < dp->ctrlbox->nctrlsets; index++) { - struct controlset *s = dp->ctrlbox->ctrlsets[index]; - GtkWidget *w; - - if (!*s->pathname) { - w = layout_ctrls(dp, NULL, &scs, s, GTK_WINDOW(window)); - - our_dialog_set_action_area(GTK_WINDOW(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 - bool 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); - - struct selparam *sp = snew(struct selparam); - - if (first) { - gint page_num; - - page_num = gtk_notebook_page_num(GTK_NOTEBOOK(panels), - panelvbox); - gtk_notebook_set_current_page(GTK_NOTEBOOK(panels), - page_num); - - dp->curr_panel = sp; - } - - sgrowarray(selparams, selparamsize, nselparams); - selparams[nselparams] = sp; - sp->dp = dp; - sp->panels = GTK_NOTEBOOK(panels); - sp->panel = panelvbox; - sp->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; - - sp->depth = j; - if (j > 0) { - sp->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), - sp->treepath, false); - } else { - sp->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; - - g_signal_connect(G_OBJECT(treeitem), "key_press_event", - G_CALLBACK(tree_key_press), dp); - g_signal_connect(G_OBJECT(treeitem), "focus_in_event", - G_CALLBACK(widget_focus), dp); - - gtk_widget_show(treeitem); - - if (first) - gtk_tree_select_child(GTK_TREE(tree), treeitem); - sp->treeitem = treeitem; -#endif - - level = j+1; - nselparams++; - } - - w = layout_ctrls(dp, selparams[nselparams-1], - &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) - /* - * 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, and then when the - * collapsed branches are expanded later, the tree view will - * jarringly change size. - * - * So instead we start with everything expanded; then, once the - * tree view has computed its resulting width requirement, we - * collapse the relevant rows, but force the width to be the value - * we just retrieved. This arranges that the tree view is wide - * enough to have all branches expanded without further resizing. - */ - - dp->nselparams = nselparams; - dp->selparams = selparams; - -#if !GTK_CHECK_VERSION(3,0,0) - { - /* - * In GTK2, we can just do the job right now. - */ - GtkRequisition req; - gtk_widget_size_request(tree, &req); - initial_treeview_collapse(dp, tree); - gtk_widget_set_size_request(tree, req.width, -1); - } -#else - /* - * But in GTK3, we have to wait until the widget is about to be - * mapped, because the size computation won't have been done yet. - */ - g_signal_connect(G_OBJECT(tree), "map", - G_CALLBACK(treeview_map_event), dp); -#endif /* GTK 2 vs 3 */ -#endif /* GTK 2+ vs 1 */ - -#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++) { - g_signal_connect(G_OBJECT(selparams[index]->treeitem), "select", - G_CALLBACK(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 = -1; - dp->window = window; - - 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 < dp->ctrlbox->nctrlsets; index++) { - struct controlset *s = dp->ctrlbox->ctrlsets[index]; - bool done = false; - int j; - - if (*s->pathname) { - for (j = 0; j < s->ncontrols; j++) - if (s->ctrls[j]->type != CTRL_TABDELAY && - s->ctrls[j]->type != CTRL_COLUMNS && - s->ctrls[j]->type != CTRL_TEXT) { - dlg_set_focus(s->ctrls[j], dp); - dp->lastfocus = s->ctrls[j]; - done = true; - break; - } - } - if (done) - break; - } - - g_signal_connect(G_OBJECT(window), "destroy", - G_CALLBACK(dlgparam_destroy), dp); - g_signal_connect(G_OBJECT(window), "key_press_event", - G_CALLBACK(win_key_press), dp); - - return window; -} - -static void dlgparam_destroy(GtkWidget *widget, gpointer data) -{ - struct dlgparam *dp = (struct dlgparam *)data; - dp->after(dp->afterctx, dp->retval); - dlg_cleanup(dp); - ctrl_free_box(dp->ctrlbox); -#if GTK_CHECK_VERSION(2,0,0) - if (dp->selparams) { - for (size_t i = 0; i < dp->nselparams; i++) { - if (dp->selparams[i]->treepath) - gtk_tree_path_free(dp->selparams[i]->treepath); - sfree(dp->selparams[i]); - } - sfree(dp->selparams); - } -#endif - sfree(dp); -} - -static void messagebox_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - if (event == EVENT_ACTION) - dlg_end(dp, ctrl->context.i); -} - -static const struct message_box_button button_array_yn[] = { - {"Yes", 'y', +1, 1}, - {"No", 'n', -1, 0}, -}; -const struct message_box_buttons buttons_yn = { - button_array_yn, lenof(button_array_yn), -}; -static const struct message_box_button button_array_ok[] = { - {"OK", 'o', 1, 1}, -}; -const struct message_box_buttons buttons_ok = { - button_array_ok, lenof(button_array_ok), -}; - -static GtkWidget *create_message_box_general( - GtkWidget *parentwin, const char *title, const char *msg, int minwid, - bool selectable, const struct message_box_buttons *buttons, - post_dialog_fn_t after, void *afterctx, - GtkWidget *(*action_postproc)(GtkWidget *, void *), void *postproc_ctx) -{ - GtkWidget *window, *w0, *w1; - struct controlset *s0, *s1; - dlgcontrol *c, *textctrl; - struct dlgparam *dp; - struct Shortcuts scs; - int i, index, ncols, min_type; - - dp = snew(struct dlgparam); - dp->after = after; - dp->afterctx = afterctx; - - dlg_init(dp); - - for (index = 0; index < lenof(scs.sc); index++) { - scs.sc[index].action = SHORTCUT_EMPTY; - } - - dp->ctrlbox = ctrl_new_box(); - - /* - * Count up the number of buttons and find out what kinds there - * are. - */ - ncols = 0; - min_type = +1; - for (i = 0; i < buttons->nbuttons; i++) { - const struct message_box_button *button = &buttons->buttons[i]; - ncols++; - if (min_type > button->type) - min_type = button->type; - assert(button->value >= 0); /* <0 means no return value available */ - } - - s0 = ctrl_getset(dp->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; - index = 0; - for (i = 0; i < buttons->nbuttons; i++) { - const struct message_box_button *button = &buttons->buttons[i]; - c = ctrl_pushbutton(s0, button->title, button->shortcut, - HELPCTX(no_help), messagebox_handler, - I(button->value)); - c->column = index++; - if (button->type > 0) - c->button.isdefault = true; - - /* We always arrange that _some_ button is labelled as - * 'iscancel', so that pressing Escape will always cause - * win_key_press to do something. The button we choose is - * whichever has the smallest type value: this means that real - * cancel buttons (labelled -1) will be picked if one is - * there, or in cases where the options are yes/no (1,0) then - * no will be picked, and if there's only one option (a box - * that really is just showing a _message_ and not even asking - * a question) then that will be picked. */ - if (button->type == min_type) - c->button.iscancel = true; - } - - s1 = ctrl_getset(dp->ctrlbox, "x", "", ""); - textctrl = ctrl_text(s1, msg, HELPCTX(no_help)); - - window = our_dialog_new(); - gtk_window_set_title(GTK_WINDOW(window), title); - w0 = layout_ctrls(dp, NULL, &scs, s0, GTK_WINDOW(window)); - if (action_postproc) - w0 = action_postproc(w0, postproc_ctx); - our_dialog_set_action_area(GTK_WINDOW(window), w0); - gtk_widget_show(w0); - w1 = layout_ctrls(dp, NULL, &scs, s1, GTK_WINDOW(window)); - gtk_container_set_border_width(GTK_CONTAINER(w1), 10); - gtk_widget_set_size_request(w1, minwid+20, -1); - our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0); - gtk_widget_show(w1); - - dp->shortcuts = &scs; - dp->lastfocus = NULL; - dp->retval = 0; - dp->window = window; - - 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); - -#if GTK_CHECK_VERSION(2,0,0) - if (selectable) { - /* - * GTK selectable labels have a habit of selecting their - * entire contents when they gain focus. As far as I can see, - * an individual GtkLabel has no way to turn this off - source - * diving suggests that the only configurable option for it is - * "gtk-label-select-on-focus" in the cross-application - * GtkSettings, and there's no per-label or even - * per-application override. - * - * It's ugly to have text in a message box start up all - * selected, and also it interferes with any PRIMARY selection - * you might already have had. So for this purpose we'd prefer - * that the text doesn't _start off_ selected, but it should - * be selectable later. - * - * So we make the label selectable _now_, after the widget is - * shown and the focus has already gone wherever it's going. - */ - struct uctrl *uc = dlg_find_byctrl(dp, textctrl); - gtk_label_select_region(GTK_LABEL(uc->text), 0, 0); - gtk_label_set_selectable(GTK_LABEL(uc->text), true); - } -#else - (void)textctrl; /* placate warning */ -#endif - -#if !GTK_CHECK_VERSION(2,0,0) - dp->currtreeitem = NULL; - dp->treeitems = NULL; -#else - dp->selparams = NULL; -#endif - - g_signal_connect(G_OBJECT(window), "destroy", - G_CALLBACK(dlgparam_destroy), dp); - g_signal_connect(G_OBJECT(window), "key_press_event", - G_CALLBACK(win_key_press), dp); - - return window; -} - -GtkWidget *create_message_box( - GtkWidget *parentwin, const char *title, const char *msg, int minwid, - bool selectable, const struct message_box_buttons *buttons, - post_dialog_fn_t after, void *afterctx) -{ - return create_message_box_general( - parentwin, title, msg, minwid, selectable, buttons, after, afterctx, - NULL /* action_postproc */, NULL /* postproc_ctx */); -} - -struct confirm_ssh_host_key_dialog_ctx { - char *host; - int port; - char *keytype; - char *keystr; - char *more_info; - void (*callback)(void *callback_ctx, SeatPromptResult result); - void *callback_ctx; - Seat *seat; - - GtkWidget *main_dialog; - GtkWidget *more_info_dialog; -}; - -static void confirm_ssh_host_key_result_callback(void *vctx, int result) -{ - struct confirm_ssh_host_key_dialog_ctx *ctx = - (struct confirm_ssh_host_key_dialog_ctx *)vctx; - - if (result >= 0) { - SeatPromptResult logical_result; - - /* - * Convert the dialog-box return value (one of three - * possibilities) into the return value we pass back to the SSH - * code (one of only two possibilities, because the SSH code - * doesn't care whether we saved the host key or not). - */ - if (result == 2) { - store_host_key(ctx->seat, ctx->host, ctx->port, - ctx->keytype, ctx->keystr); - logical_result = SPR_OK; - } else if (result == 1) { - logical_result = SPR_OK; - } else { - logical_result = SPR_USER_ABORT; - } - - ctx->callback(ctx->callback_ctx, logical_result); - } - - /* - * Clean up this context structure, whether or not a result was - * ever actually delivered from the dialog box. - */ - unregister_dialog(ctx->seat, DIALOG_SLOT_NETWORK_PROMPT); - - if (ctx->more_info_dialog) - gtk_widget_destroy(ctx->more_info_dialog); - - sfree(ctx->host); - sfree(ctx->keytype); - sfree(ctx->keystr); - sfree(ctx->more_info); - sfree(ctx); -} - -static GtkWidget *add_more_info_button(GtkWidget *w, void *vctx) -{ - GtkWidget *box = gtk_hbox_new(false, 10); - gtk_widget_show(box); - gtk_box_pack_end(GTK_BOX(box), w, false, true, 0); - GtkWidget *button = gtk_button_new_with_label("More info..."); - gtk_widget_show(button); - gtk_box_pack_start(GTK_BOX(box), button, false, true, 0); - *(GtkWidget **)vctx = button; - return box; -} - -static void more_info_closed(void *vctx, int result) -{ - struct confirm_ssh_host_key_dialog_ctx *ctx = - (struct confirm_ssh_host_key_dialog_ctx *)vctx; - - ctx->more_info_dialog = NULL; -} - -static void more_info_button_clicked(GtkButton *button, gpointer vctx) -{ - struct confirm_ssh_host_key_dialog_ctx *ctx = - (struct confirm_ssh_host_key_dialog_ctx *)vctx; - - if (ctx->more_info_dialog) - return; - - ctx->more_info_dialog = create_message_box( - ctx->main_dialog, "Host key information", ctx->more_info, - string_width("SHA256 fingerprint: ecdsa-sha2-nistp521 521 " - "abcdefghkmnopqrsuvwxyzABCDEFGHJKLMNOPQRSTUW"), true, - &buttons_ok, more_info_closed, ctx); -} - -const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat) -{ - static const SeatDialogPromptDescriptions descs = { - .hk_accept_action = "press \"Accept\"", - .hk_connect_once_action = "press \"Connect Once\"", - .hk_cancel_action = "press \"Cancel\"", - .hk_cancel_action_Participle = "Pressing \"Cancel\"", - .weak_accept_action = "press \"Yes\"", - .weak_cancel_action = "press \"No\"", - }; - return &descs; -} - -/* - * Format a SeatDialogText into a strbuf, also adjusting the box width - * to cope with displayed text. Returns the dialog box title. - */ -static const char *gtk_format_seatdialogtext( - SeatDialogText *text, strbuf *dlg_text, int *width) -{ - const char *dlg_title = NULL; - - for (SeatDialogTextItem *item = text->items, - *end = item + text->nitems; item < end; item++) { - switch (item->type) { - case SDT_PARA: - put_fmt(dlg_text, "%s\n\n", item->text); - break; - case SDT_DISPLAY: { - put_fmt(dlg_text, "%s\n\n", item->text); - int thiswidth = string_width(item->text); - if (*width < thiswidth) - *width = thiswidth; - break; - } - case SDT_SCARY_HEADING: - /* Can't change font size or weight in this context */ - put_fmt(dlg_text, "%s\n\n", item->text); - break; - case SDT_TITLE: - dlg_title = item->text; - break; - default: - break; - } - } - - /* - * Trim trailing newlines. - */ - while (strbuf_chomp(dlg_text, '\n')); - - return dlg_title; -} - -SeatPromptResult gtk_seat_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - static const struct message_box_button button_array_hostkey[] = { - {"Accept", 'a', 0, 2}, - {"Connect Once", 'o', 0, 1}, - {"Cancel", 'c', -1, 0}, - }; - static const struct message_box_buttons buttons_hostkey = { - button_array_hostkey, lenof(button_array_hostkey), - }; - - int width = string_width("default dialog width determination string"); - strbuf *dlg_text = strbuf_new(); - const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width); - - GtkWidget *mainwin, *msgbox; - - struct confirm_ssh_host_key_dialog_ctx *result_ctx = - snew(struct confirm_ssh_host_key_dialog_ctx); - result_ctx->callback = callback; - result_ctx->callback_ctx = ctx; - result_ctx->host = dupstr(host); - result_ctx->port = port; - result_ctx->keytype = dupstr(keytype); - result_ctx->keystr = dupstr(keystr); - result_ctx->seat = seat; - - mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); - GtkWidget *more_info_button = NULL; - msgbox = create_message_box_general( - mainwin, dlg_title, dlg_text->s, width, true, - &buttons_hostkey, confirm_ssh_host_key_result_callback, result_ctx, - add_more_info_button, &more_info_button); - - result_ctx->main_dialog = msgbox; - result_ctx->more_info_dialog = NULL; - - strbuf *moreinfo = strbuf_new(); - for (SeatDialogTextItem *item = text->items, - *end = item + text->nitems; item < end; item++) { - switch (item->type) { - case SDT_MORE_INFO_KEY: - put_fmt(moreinfo, "%s", item->text); - break; - case SDT_MORE_INFO_VALUE_SHORT: - put_fmt(moreinfo, ": %s\n", item->text); - break; - case SDT_MORE_INFO_VALUE_BLOB: - /* We have to manually wrap the public key, or else the GtkLabel - * will resize itself to accommodate the longest word, which will - * lead to a hilariously wide message box. */ - put_byte(moreinfo, ':'); - for (const char *p = item->text, *q = p + strlen(p); p < q ;) { - size_t linelen = q-p; - if (linelen > 72) - linelen = 72; - put_byte(moreinfo, '\n'); - put_data(moreinfo, p, linelen); - p += linelen; - } - put_byte(moreinfo, '\n'); - break; - default: - break; - } - } - result_ctx->more_info = strbuf_to_str(moreinfo); - - g_signal_connect(G_OBJECT(more_info_button), "clicked", - G_CALLBACK(more_info_button_clicked), result_ctx); - - register_dialog(seat, DIALOG_SLOT_NETWORK_PROMPT, msgbox); - - strbuf_free(dlg_text); - - return SPR_INCOMPLETE; /* dialog still in progress */ -} - -struct simple_prompt_result_spr_ctx { - void (*callback)(void *callback_ctx, SeatPromptResult spr); - void *callback_ctx; - Seat *seat; - enum DialogSlot dialog_slot; -}; - -static void simple_prompt_result_spr_callback(void *vctx, int result) -{ - struct simple_prompt_result_spr_ctx *ctx = - (struct simple_prompt_result_spr_ctx *)vctx; - - unregister_dialog(ctx->seat, ctx->dialog_slot); - - if (result == 0) - ctx->callback(ctx->callback_ctx, SPR_USER_ABORT); - else if (result > 0) - ctx->callback(ctx->callback_ctx, SPR_OK); - /* if <0, we're cleaning up for some other reason */ - - /* - * Clean up this context structure, whether or not a result was - * ever actually delivered from the dialog box. - */ - sfree(ctx); -} - -/* - * Ask whether the selected algorithm is acceptable (since it was - * below the configured 'warn' threshold). - */ -SeatPromptResult gtk_seat_confirm_weak_crypto_primitive( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - struct simple_prompt_result_spr_ctx *result_ctx; - GtkWidget *mainwin, *msgbox; - - int width = string_width("Reasonably long line of text " - "as a width template"); - strbuf *dlg_text = strbuf_new(); - const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width); - - result_ctx = snew(struct simple_prompt_result_spr_ctx); - result_ctx->callback = callback; - result_ctx->callback_ctx = ctx; - result_ctx->seat = seat; - result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT; - - mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); - msgbox = create_message_box( - mainwin, dlg_title, dlg_text->s, width, false, - &buttons_yn, simple_prompt_result_spr_callback, result_ctx); - register_dialog(seat, result_ctx->dialog_slot, msgbox); - - strbuf_free(dlg_text); - - return SPR_INCOMPLETE; -} - -SeatPromptResult gtk_seat_confirm_weak_cached_hostkey( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - struct simple_prompt_result_spr_ctx *result_ctx; - GtkWidget *mainwin, *msgbox; - - int width = string_width("is ecdsa-nistp521, which is below the configured" - " warning threshold."); - strbuf *dlg_text = strbuf_new(); - const char *dlg_title = gtk_format_seatdialogtext(text, dlg_text, &width); - - result_ctx = snew(struct simple_prompt_result_spr_ctx); - result_ctx->callback = callback; - result_ctx->callback_ctx = ctx; - result_ctx->seat = seat; - result_ctx->dialog_slot = DIALOG_SLOT_NETWORK_PROMPT; - - mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); - msgbox = create_message_box( - mainwin, dlg_title, dlg_text->s, width, false, - &buttons_yn, simple_prompt_result_spr_callback, result_ctx); - register_dialog(seat, result_ctx->dialog_slot, msgbox); - - strbuf_free(dlg_text); - - return SPR_INCOMPLETE; -} - -void old_keyfile_warning(void) -{ - /* - * This should never happen on Unix. We hope. - */ -} - -void nonfatal_message_box(void *window, const char *msg) -{ - char *title = dupcat(appname, " Error"); - create_message_box( - window, title, msg, - string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), - false, &buttons_ok, trivial_post_dialog_fn, NULL); - sfree(title); -} - -void nonfatal(const 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_window_destroyed(GtkWidget *widget, gpointer data) -{ - aboutbox = NULL; -} - -static void about_close_clicked(GtkButton *button, gpointer data) -{ - gtk_widget_destroy(aboutbox); - aboutbox = NULL; -} - -static void about_key_press(GtkWidget *widget, GdkEventKey *event, - gpointer data) -{ - if (event->keyval == GDK_KEY_Escape && aboutbox) { - gtk_widget_destroy(aboutbox); - aboutbox = NULL; - } -} - -static void licence_clicked(GtkButton *button, gpointer data) -{ - char *title; - - title = dupcat(appname, " Licence"); - assert(aboutbox != NULL); - create_message_box(aboutbox, title, LICENCE_TEXT("\n\n"), - string_width("LONGISH LINE OF TEXT SO THE LICENCE" - " BOX ISN'T EXCESSIVELY TALL AND THIN"), - true, &buttons_ok, trivial_post_dialog_fn, NULL); - sfree(title); -} - -void about_box(void *window) -{ - GtkWidget *w; - GtkBox *action_area; - char *title; - - if (aboutbox) { - gtk_widget_grab_focus(aboutbox); - return; - } - - aboutbox = our_dialog_new(); - gtk_container_set_border_width(GTK_CONTAINER(aboutbox), 10); - title = dupcat("About ", appname); - gtk_window_set_title(GTK_WINDOW(aboutbox), title); - sfree(title); - - g_signal_connect(G_OBJECT(aboutbox), "destroy", - G_CALLBACK(about_window_destroyed), NULL); - - w = gtk_button_new_with_label("Close"); - gtk_widget_set_can_default(w, true); - gtk_window_set_default(GTK_WINDOW(aboutbox), w); - action_area = our_dialog_make_action_hbox(GTK_WINDOW(aboutbox)); - gtk_box_pack_end(action_area, w, false, false, 0); - g_signal_connect(G_OBJECT(w), "clicked", - G_CALLBACK(about_close_clicked), NULL); - gtk_widget_show(w); - - w = gtk_button_new_with_label("View Licence"); - gtk_widget_set_can_default(w, true); - gtk_box_pack_end(action_area, w, false, false, 0); - g_signal_connect(G_OBJECT(w), "clicked", - G_CALLBACK(licence_clicked), NULL); - gtk_widget_show(w); - - { - char *buildinfo_text = buildinfo("\n"); - char *label_text = dupprintf( - "%s\n\n%s\n\n%s\n\n%s", - appname, ver, buildinfo_text, - "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); - } - our_dialog_add_to_content_area(GTK_WINDOW(aboutbox), w, false, false, 0); -#if GTK_CHECK_VERSION(2,0,0) - /* - * Same precautions against initial select-all as in - * create_message_box(). - */ - gtk_widget_grab_focus(w); - gtk_label_select_region(GTK_LABEL(w), 0, 0); -#endif - gtk_widget_show(w); - - g_signal_connect(G_OBJECT(aboutbox), "key_press_event", - G_CALLBACK(about_key_press), NULL); - - set_transient_window_pos(GTK_WIDGET(window), aboutbox); - if (window) - 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); -} - -#define LOGEVENT_INITIAL_MAX 128 -#define LOGEVENT_CIRCULAR_MAX 128 - -struct eventlog_stuff { - GtkWidget *parentwin, *window; - struct controlbox *eventbox; - struct Shortcuts scs; - struct dlgparam dp; - dlgcontrol *listctrl; - char **events_initial; - char **events_circular; - int ninitial, ncircular, circular_first; - strbuf *seldata; - int sellen; - bool ignore_selchange; -}; - -static void eventlog_destroy(GtkWidget *widget, gpointer data) -{ - eventlog_stuff *es = (eventlog_stuff *)data; - - es->window = NULL; - dlg_cleanup(&es->dp); - ctrl_free_box(es->eventbox); -} -static void eventlog_ok_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - if (event == EVENT_ACTION) - dlg_end(dp, 0); -} -static void eventlog_list_handler(dlgcontrol *ctrl, dlgparam *dp, - void *data, int event) -{ - eventlog_stuff *es = (eventlog_stuff *)data; - - if (event == EVENT_REFRESH) { - int i; - - dlg_update_start(ctrl, dp); - dlg_listbox_clear(ctrl, dp); - for (i = 0; i < es->ninitial; i++) { - dlg_listbox_add(ctrl, dp, es->events_initial[i]); - } - for (i = 0; i < es->ncircular; i++) { - dlg_listbox_add(ctrl, dp, es->events_circular[(es->circular_first + i) % LOGEVENT_CIRCULAR_MAX]); - } - dlg_update_done(ctrl, dp); - } else if (event == EVENT_SELCHANGE) { - int i; - - /* - * 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. - */ - strbuf_clear(es->seldata); - for (i = 0; i < es->ninitial; i++) { - if (dlg_listbox_issel(ctrl, dp, i)) - put_fmt(es->seldata, "%s\n", es->events_initial[i]); - } - for (i = 0; i < es->ncircular; i++) { - if (dlg_listbox_issel(ctrl, dp, es->ninitial + i)) { - int j = (es->circular_first + i) % LOGEVENT_CIRCULAR_MAX; - put_fmt(es->seldata, "%s\n", es->events_circular[j]); - } - } - - if (gtk_selection_owner_set(es->window, GDK_SELECTION_PRIMARY, - GDK_CURRENT_TIME)) { - 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) -{ - eventlog_stuff *es = (eventlog_stuff *)data; - - gtk_selection_data_set(seldata, gtk_selection_data_get_target(seldata), 8, - es->seldata->u, es->seldata->len); -} - -gint eventlog_selection_clear(GtkWidget *widget, GdkEventSelection *seldata, - gpointer data) -{ - eventlog_stuff *es = (eventlog_stuff *)data; - struct uctrl *uc; - - /* - * Deselect everything in the list box. - */ - uc = dlg_find_byctrl(&es->dp, es->listctrl); - es->ignore_selchange = true; -#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 = false; - - return true; -} - -void showeventlog(eventlog_stuff *es, void *parentwin) -{ - GtkWidget *window, *w0, *w1; - GtkWidget *parent = GTK_WIDGET(parentwin); - struct controlset *s0, *s1; - dlgcontrol *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->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 = our_dialog_new(); - title = dupcat(appname, " Event Log"); - gtk_window_set_title(GTK_WINDOW(window), title); - sfree(title); - w0 = layout_ctrls(&es->dp, NULL, &es->scs, s0, GTK_WINDOW(window)); - our_dialog_set_action_area(GTK_WINDOW(window), w0); - gtk_widget_show(w0); - w1 = layout_ctrls(&es->dp, NULL, &es->scs, s1, GTK_WINDOW(window)); - gtk_container_set_border_width(GTK_CONTAINER(w1), 10); - gtk_widget_set_size_request( - w1, 20 + string_width("LINE OF TEXT GIVING WIDTH OF EVENT LOG IS " - "QUITE LONG 'COS SSH LOG ENTRIES ARE WIDE"), - -1); - our_dialog_add_to_content_area(GTK_WINDOW(window), w1, true, true, 0); - { - struct uctrl *uc = dlg_find_byctrl(&es->dp, es->listctrl); - columns_vexpand(COLUMNS(w1), uc->toplevel); - } - 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); - - g_signal_connect(G_OBJECT(window), "destroy", - G_CALLBACK(eventlog_destroy), es); - g_signal_connect(G_OBJECT(window), "key_press_event", - G_CALLBACK(win_key_press), &es->dp); - g_signal_connect(G_OBJECT(window), "selection_get", - G_CALLBACK(eventlog_selection_get), es); - g_signal_connect(G_OBJECT(window), "selection_clear_event", - G_CALLBACK(eventlog_selection_clear), es); -} - -eventlog_stuff *eventlogstuff_new(void) -{ - eventlog_stuff *es = snew(eventlog_stuff); - memset(es, 0, sizeof(*es)); - es->seldata = strbuf_new(); - return es; -} - -void eventlogstuff_free(eventlog_stuff *es) -{ - int i; - - if (es->events_initial) { - for (i = 0; i < LOGEVENT_INITIAL_MAX; i++) - sfree(es->events_initial[i]); - sfree(es->events_initial); - } - if (es->events_circular) { - for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++) - sfree(es->events_circular[i]); - sfree(es->events_circular); - } - strbuf_free(es->seldata); - - sfree(es); -} - -void logevent_dlg(eventlog_stuff *es, const char *string) -{ - char timebuf[40]; - struct tm tm; - char **location; - size_t i; - - if (es->ninitial == 0) { - es->events_initial = sresize(es->events_initial, LOGEVENT_INITIAL_MAX, char *); - for (i = 0; i < LOGEVENT_INITIAL_MAX; i++) - es->events_initial[i] = NULL; - es->events_circular = sresize(es->events_circular, LOGEVENT_CIRCULAR_MAX, char *); - for (i = 0; i < LOGEVENT_CIRCULAR_MAX; i++) - es->events_circular[i] = NULL; - } - - if (es->ninitial < LOGEVENT_INITIAL_MAX) - location = &es->events_initial[es->ninitial]; - else - location = &es->events_circular[(es->circular_first + es->ncircular) % LOGEVENT_CIRCULAR_MAX]; - - tm=ltime(); - strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm); - - sfree(*location); - *location = dupcat(timebuf, string); - if (es->window) { - dlg_listbox_add(es->listctrl, &es->dp, *location); - } - if (es->ninitial < LOGEVENT_INITIAL_MAX) { - es->ninitial++; - } else if (es->ncircular < LOGEVENT_CIRCULAR_MAX) { - es->ncircular++; - } else if (es->ncircular == LOGEVENT_CIRCULAR_MAX) { - es->circular_first = (es->circular_first + 1) % LOGEVENT_CIRCULAR_MAX; - sfree(es->events_circular[es->circular_first]); - es->events_circular[es->circular_first] = dupstr(".."); - } -} - -struct simple_prompt_result_int_ctx { - void (*callback)(void *callback_ctx, int result); - void *callback_ctx; - Seat *seat; - enum DialogSlot dialog_slot; -}; - -static void simple_prompt_result_int_callback(void *vctx, int result) -{ - struct simple_prompt_result_int_ctx *ctx = - (struct simple_prompt_result_int_ctx *)vctx; - - unregister_dialog(ctx->seat, ctx->dialog_slot); - - if (result >= 0) - ctx->callback(ctx->callback_ctx, result); - - /* - * Clean up this context structure, whether or not a result was - * ever actually delivered from the dialog box. - */ - sfree(ctx); -} - -int gtkdlg_askappend(Seat *seat, 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."; - static const struct message_box_button button_array_append[] = { - {"Overwrite", 'o', 1, 2}, - {"Append", 'a', 0, 1}, - {"Disable", 'd', -1, 0}, - }; - static const struct message_box_buttons buttons_append = { - button_array_append, lenof(button_array_append), - }; - - char *message; - char *mbtitle; - struct simple_prompt_result_int_ctx *result_ctx; - GtkWidget *mainwin, *msgbox; - - message = dupprintf(msgtemplate, FILENAME_MAX, filename->path); - mbtitle = dupprintf("%s Log to File", appname); - - result_ctx = snew(struct simple_prompt_result_int_ctx); - result_ctx->callback = callback; - result_ctx->callback_ctx = ctx; - result_ctx->seat = seat; - result_ctx->dialog_slot = DIALOG_SLOT_LOGFILE_PROMPT; - - mainwin = GTK_WIDGET(gtk_seat_get_window(seat)); - msgbox = create_message_box( - mainwin, mbtitle, message, - string_width("LINE OF TEXT SUITABLE FOR THE ASKAPPEND WIDTH"), - false, &buttons_append, simple_prompt_result_int_callback, result_ctx); - register_dialog(seat, result_ctx->dialog_slot, msgbox); - - sfree(message); - sfree(mbtitle); - - return -1; /* dialog still in progress */ -} - -struct ca_config_box { - GtkWidget *window; - struct controlbox *cb; - struct Shortcuts scs; - bool quit_main; - dlgparam dp; -}; -static struct ca_config_box *cacfg; /* one of these, cross-instance */ - -static void cacfg_destroy(GtkWidget *widget, gpointer data) -{ - cacfg->window = NULL; - dlg_cleanup(&cacfg->dp); - ctrl_free_box(cacfg->cb); - cacfg->cb = NULL; - if (cacfg->quit_main) - gtk_main_quit(); -} -static void make_ca_config_box(GtkWidget *spawning_window) -{ - if (!cacfg) { - cacfg = snew(struct ca_config_box); - memset(cacfg, 0, sizeof(*cacfg)); - } - - if (cacfg->window) { - /* This dialog box is already displayed; re-focus it */ - gtk_widget_grab_focus(cacfg->window); - return; - } - - dlg_init(&cacfg->dp); - for (size_t i = 0; i < lenof(cacfg->scs.sc); i++) { - cacfg->scs.sc[i].action = SHORTCUT_EMPTY; - } - - cacfg->cb = ctrl_new_box(); - setup_ca_config_box(cacfg->cb); - - cacfg->window = our_dialog_new(); - gtk_window_set_title(GTK_WINDOW(cacfg->window), - "PuTTY trusted host certification authorities"); - gtk_widget_set_size_request( - cacfg->window, string_width( - "ecdsa-sha2-nistp256 256 SHA256:hsO5a8MYGzBoa2gW5" - "dLV2vl7bTnCPjw64x3kLkz6BY8"), -1); - - /* Set up everything else */ - for (int i = 0; i < cacfg->cb->nctrlsets; i++) { - struct controlset *s = cacfg->cb->ctrlsets[i]; - GtkWidget *w = layout_ctrls(&cacfg->dp, NULL, &cacfg->scs, s, - GTK_WINDOW(cacfg->window)); - gtk_container_set_border_width(GTK_CONTAINER(w), 10); - gtk_widget_show(w); - - if (!*s->pathname) { - our_dialog_set_action_area(GTK_WINDOW(cacfg->window), w); - } else { - our_dialog_add_to_content_area(GTK_WINDOW(cacfg->window), w, - true, true, 0); - } - } - - cacfg->dp.data = cacfg; - cacfg->dp.shortcuts = &cacfg->scs; - cacfg->dp.lastfocus = NULL; - cacfg->dp.retval = 0; - cacfg->dp.window = cacfg->window; - - dlg_refresh(NULL, &cacfg->dp); - - if (spawning_window) { - set_transient_window_pos(spawning_window, cacfg->window); - } else { - gtk_window_set_position(GTK_WINDOW(cacfg->window), GTK_WIN_POS_CENTER); - } - gtk_widget_show(cacfg->window); - - g_signal_connect(G_OBJECT(cacfg->window), "destroy", - G_CALLBACK(cacfg_destroy), NULL); - g_signal_connect(G_OBJECT(cacfg->window), "key_press_event", - G_CALLBACK(win_key_press), &cacfg->dp); -} - -void show_ca_config_box(dlgparam *dp) -{ - make_ca_config_box(dp ? dp->window : NULL); -} - -void show_ca_config_box_synchronously(void) -{ - make_ca_config_box(NULL); - cacfg->quit_main = true; - gtk_main(); -} diff --git a/unix/fd-socket.c b/unix/fd-socket.c deleted file mode 100644 index 9758a17bc..000000000 --- a/unix/fd-socket.c +++ /dev/null @@ -1,417 +0,0 @@ -/* - * fd-socket.c: implementation of Socket that just talks to two - * existing input and output file descriptors. - */ - -#include -#include -#include -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" - -typedef struct FdSocket { - int outfd, infd, inerrfd; /* >= 0 if socket is open */ - DeferredSocketOpener *opener; /* non-NULL if not opened yet */ - - bufchain pending_output_data; - bufchain pending_input_data; - ProxyStderrBuf psb; - enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; - - int pending_error; - - SockAddr *addr; - int port; - Plug *plug; - - Socket sock; -} FdSocket; - -static void fdsocket_select_result_input(int fd, int event); -static void fdsocket_select_result_output(int fd, int event); -static void fdsocket_select_result_input_error(int fd, int event); - -/* - * Trees to look up the fds in. - */ -static tree234 *fdsocket_by_outfd; -static tree234 *fdsocket_by_infd; -static tree234 *fdsocket_by_inerrfd; - -static int fdsocket_infd_cmp(void *av, void *bv) -{ - FdSocket *a = (FdSocket *)av; - FdSocket *b = (FdSocket *)bv; - if (a->infd < b->infd) - return -1; - if (a->infd > b->infd) - return +1; - return 0; -} -static int fdsocket_infd_find(void *av, void *bv) -{ - int a = *(int *)av; - FdSocket *b = (FdSocket *)bv; - if (a < b->infd) - return -1; - if (a > b->infd) - return +1; - return 0; -} -static int fdsocket_inerrfd_cmp(void *av, void *bv) -{ - FdSocket *a = (FdSocket *)av; - FdSocket *b = (FdSocket *)bv; - if (a->inerrfd < b->inerrfd) - return -1; - if (a->inerrfd > b->inerrfd) - return +1; - return 0; -} -static int fdsocket_inerrfd_find(void *av, void *bv) -{ - int a = *(int *)av; - FdSocket *b = (FdSocket *)bv; - if (a < b->inerrfd) - return -1; - if (a > b->inerrfd) - return +1; - return 0; -} -static int fdsocket_outfd_cmp(void *av, void *bv) -{ - FdSocket *a = (FdSocket *)av; - FdSocket *b = (FdSocket *)bv; - if (a->outfd < b->outfd) - return -1; - if (a->outfd > b->outfd) - return +1; - return 0; -} -static int fdsocket_outfd_find(void *av, void *bv) -{ - int a = *(int *)av; - FdSocket *b = (FdSocket *)bv; - if (a < b->outfd) - return -1; - if (a > b->outfd) - return +1; - return 0; -} - -static Plug *fdsocket_plug(Socket *s, Plug *p) -{ - FdSocket *fds = container_of(s, FdSocket, sock); - Plug *ret = fds->plug; - if (p) - fds->plug = p; - return ret; -} - -static void fdsocket_close(Socket *s) -{ - FdSocket *fds = container_of(s, FdSocket, sock); - - if (fds->opener) - deferred_socket_opener_free(fds->opener); - - if (fds->outfd >= 0) { - del234(fdsocket_by_outfd, fds); - uxsel_del(fds->outfd); - close(fds->outfd); - } - - if (fds->infd >= 0) { - del234(fdsocket_by_infd, fds); - uxsel_del(fds->infd); - close(fds->infd); - } - - if (fds->inerrfd >= 0) { - del234(fdsocket_by_inerrfd, fds); - uxsel_del(fds->inerrfd); - close(fds->inerrfd); - } - - bufchain_clear(&fds->pending_input_data); - bufchain_clear(&fds->pending_output_data); - - if (fds->addr) - sk_addr_free(fds->addr); - - delete_callbacks_for_context(fds); - - sfree(fds); -} - -static void fdsocket_error_callback(void *vs) -{ - FdSocket *fds = (FdSocket *)vs; - - /* - * Just in case other socket work has caused this socket to vanish - * or become somehow non-erroneous before this callback arrived... - */ - if (!fds->pending_error) - return; - - /* - * An error has occurred on this socket. Pass it to the plug. - */ - plug_closing_errno(fds->plug, fds->pending_error); -} - -static int fdsocket_try_send(FdSocket *fds) -{ - int sent = 0; - - if (fds->opener) - return sent; - - while (bufchain_size(&fds->pending_output_data) > 0) { - ssize_t ret; - - ptrlen data = bufchain_prefix(&fds->pending_output_data); - ret = write(fds->outfd, data.ptr, data.len); - noise_ultralight(NOISE_SOURCE_IOID, ret); - if (ret < 0 && errno != EWOULDBLOCK) { - if (!fds->pending_error) { - fds->pending_error = errno; - queue_toplevel_callback(fdsocket_error_callback, fds); - } - return 0; - } else if (ret <= 0) { - break; - } else { - bufchain_consume(&fds->pending_output_data, ret); - sent += ret; - } - } - - if (fds->outgoingeof == EOF_PENDING) { - del234(fdsocket_by_outfd, fds); - close(fds->outfd); - uxsel_del(fds->outfd); - fds->outfd = -1; - fds->outgoingeof = EOF_SENT; - } - - if (bufchain_size(&fds->pending_output_data) == 0) - uxsel_del(fds->outfd); - else - uxsel_set(fds->outfd, SELECT_W, fdsocket_select_result_output); - - return sent; -} - -static size_t fdsocket_write(Socket *s, const void *data, size_t len) -{ - FdSocket *fds = container_of(s, FdSocket, sock); - - assert(fds->outgoingeof == EOF_NO); - - bufchain_add(&fds->pending_output_data, data, len); - - fdsocket_try_send(fds); - - return bufchain_size(&fds->pending_output_data); -} - -static size_t fdsocket_write_oob(Socket *s, const void *data, size_t len) -{ - /* - * oob data is treated as inband; nasty, but nothing really - * better we can do - */ - return fdsocket_write(s, data, len); -} - -static void fdsocket_write_eof(Socket *s) -{ - FdSocket *fds = container_of(s, FdSocket, sock); - - assert(fds->outgoingeof == EOF_NO); - fds->outgoingeof = EOF_PENDING; - - fdsocket_try_send(fds); -} - -static void fdsocket_set_frozen(Socket *s, bool is_frozen) -{ - FdSocket *fds = container_of(s, FdSocket, sock); - - if (fds->infd < 0) - return; - - if (is_frozen) - uxsel_del(fds->infd); - else - uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input); -} - -static const char *fdsocket_socket_error(Socket *s) -{ - return NULL; -} - -static void fdsocket_select_result_input(int fd, int event) -{ - FdSocket *fds; - char buf[20480]; - int retd; - - if (!(fds = find234(fdsocket_by_infd, &fd, fdsocket_infd_find))) - return; - - retd = read(fds->infd, buf, sizeof(buf)); - if (retd > 0) { - plug_receive(fds->plug, 0, buf, retd); - } else { - del234(fdsocket_by_infd, fds); - uxsel_del(fds->infd); - close(fds->infd); - fds->infd = -1; - - if (retd < 0) { - plug_closing_errno(fds->plug, errno); - } else { - plug_closing_normal(fds->plug); - } - } -} - -static void fdsocket_select_result_output(int fd, int event) -{ - FdSocket *fds; - - if (!(fds = find234(fdsocket_by_outfd, &fd, fdsocket_outfd_find))) - return; - - if (fdsocket_try_send(fds)) - plug_sent(fds->plug, bufchain_size(&fds->pending_output_data)); -} - -static void fdsocket_select_result_input_error(int fd, int event) -{ - FdSocket *fds; - char buf[20480]; - int retd; - - if (!(fds = find234(fdsocket_by_inerrfd, &fd, fdsocket_inerrfd_find))) - return; - - retd = read(fd, buf, sizeof(buf)); - if (retd > 0) { - log_proxy_stderr(fds->plug, &fds->psb, buf, retd); - } else { - del234(fdsocket_by_inerrfd, fds); - uxsel_del(fds->inerrfd); - close(fds->inerrfd); - fds->inerrfd = -1; - } -} - -static const SocketVtable FdSocket_sockvt = { - .plug = fdsocket_plug, - .close = fdsocket_close, - .write = fdsocket_write, - .write_oob = fdsocket_write_oob, - .write_eof = fdsocket_write_eof, - .set_frozen = fdsocket_set_frozen, - .socket_error = fdsocket_socket_error, - .peer_info = NULL, -}; - -static void fdsocket_connect_success_callback(void *ctx) -{ - FdSocket *fds = (FdSocket *)ctx; - plug_log(fds->plug, PLUGLOG_CONNECT_SUCCESS, fds->addr, fds->port, - NULL, 0); -} - -void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd) -{ - FdSocket *fds = container_of(s, FdSocket, sock); - assert(fds->sock.vt == &FdSocket_sockvt); - - if (fds->opener) { - deferred_socket_opener_free(fds->opener); - fds->opener = NULL; - } - - fds->infd = infd; - fds->outfd = outfd; - fds->inerrfd = inerrfd; - - if (fds->outfd >= 0) { - if (!fdsocket_by_outfd) - fdsocket_by_outfd = newtree234(fdsocket_outfd_cmp); - add234(fdsocket_by_outfd, fds); - } - - if (fds->infd >= 0) { - if (!fdsocket_by_infd) - fdsocket_by_infd = newtree234(fdsocket_infd_cmp); - add234(fdsocket_by_infd, fds); - uxsel_set(fds->infd, SELECT_R, fdsocket_select_result_input); - } - - if (fds->inerrfd >= 0) { - assert(fds->inerrfd != fds->infd); - if (!fdsocket_by_inerrfd) - fdsocket_by_inerrfd = newtree234(fdsocket_inerrfd_cmp); - add234(fdsocket_by_inerrfd, fds); - uxsel_set(fds->inerrfd, SELECT_R, fdsocket_select_result_input_error); - } - - queue_toplevel_callback(fdsocket_connect_success_callback, fds); -} - -void fd_socket_set_psb_prefix(Socket *s, const char *prefix) -{ - FdSocket *fds = container_of(s, FdSocket, sock); - assert(fds->sock.vt == &FdSocket_sockvt); - psb_set_prefix(&fds->psb, prefix); -} - -static FdSocket *make_fd_socket_internal(SockAddr *addr, int port, Plug *plug) -{ - FdSocket *fds; - - fds = snew(FdSocket); - fds->sock.vt = &FdSocket_sockvt; - fds->addr = addr; - fds->port = port; - fds->plug = plug; - fds->outgoingeof = EOF_NO; - fds->pending_error = 0; - - fds->opener = NULL; - fds->infd = fds->outfd = fds->inerrfd = -1; - - bufchain_init(&fds->pending_input_data); - bufchain_init(&fds->pending_output_data); - psb_init(&fds->psb); - - return fds; -} - -Socket *make_fd_socket(int infd, int outfd, int inerrfd, - SockAddr *addr, int port, Plug *plug) -{ - FdSocket *fds = make_fd_socket_internal(addr, port, plug); - setup_fd_socket(&fds->sock, infd, outfd, inerrfd); - return &fds->sock; -} - -Socket *make_deferred_fd_socket(DeferredSocketOpener *opener, - SockAddr *addr, int port, Plug *plug) -{ - FdSocket *fds = make_fd_socket_internal(addr, port, plug); - fds->opener = opener; - return &fds->sock; -} diff --git a/unix/gss.c b/unix/gss.c deleted file mode 100644 index bd599fcc4..000000000 --- a/unix/gss.c +++ /dev/null @@ -1,176 +0,0 @@ -#include "putty.h" -#ifndef NO_GSSAPI -#include "ssh/pgssapi.h" -#include "ssh/gss.h" -#include "ssh/gssc.h" - -/* Unix code to set up the GSSAPI library list. */ - -#if !defined NO_LIBDL && !defined STATIC_GSSAPI && !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(verify_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); - BIND_GSS_FN(acquire_cred); - BIND_GSS_FN(inquire_cred_by_mech); - -#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].id = 0; - 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(verify_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); - BIND_GSS_FN(acquire_cred); - BIND_GSS_FN(inquire_cred_by_mech); - -#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/unix/gtk-common.c b/unix/gtk-common.c deleted file mode 100644 index 9d48e882a..000000000 --- a/unix/gtk-common.c +++ /dev/null @@ -1,243 +0,0 @@ -/* - * gtk-common.c: machinery in the GTK front end which is common to all - * programs that run a session in a terminal window, and also common - * across all _sessions_ rather than specific to one session. (Timers, - * uxsel etc.) - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#if GTK_CHECK_VERSION(2,0,0) -#include -#endif - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "terminal.h" -#include "gtkcompat.h" -#include "unifont.h" -#include "gtkmisc.h" - -#ifndef NOT_X_WINDOWS -#include -#include -#include -#include -#endif - -#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 - -/* ---------------------------------------------------------------------- - * File descriptors and uxsel. - */ - -struct uxsel_id { -#if GTK_CHECK_VERSION(2,0,0) - GIOChannel *chan; - guint watch_id; -#else - int id; -#endif -}; - -#if GTK_CHECK_VERSION(2,0,0) -gboolean fd_input_func(GIOChannel *source, GIOCondition condition, - gpointer data) -{ - int sourcefd = g_io_channel_unix_get_fd(source); - /* - * We must process exceptional notifications before ordinary - * readability ones, or we may go straight past the urgent - * marker. - */ - if (condition & G_IO_PRI) - select_result(sourcefd, SELECT_X); - if (condition & (G_IO_IN | G_IO_HUP)) - select_result(sourcefd, SELECT_R); - if (condition & G_IO_OUT) - select_result(sourcefd, SELECT_W); - - run_toplevel_callbacks(); - - return true; -} -#else -void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) -{ - if (condition & GDK_INPUT_EXCEPTION) - select_result(sourcefd, SELECT_X); - if (condition & GDK_INPUT_READ) - select_result(sourcefd, SELECT_R); - if (condition & GDK_INPUT_WRITE) - select_result(sourcefd, SELECT_W); - - run_toplevel_callbacks(); -} -#endif - -uxsel_id *uxsel_input_add(int fd, int rwx) { - uxsel_id *id = snew(uxsel_id); - -#if GTK_CHECK_VERSION(2,0,0) - int flags = 0; - if (rwx & SELECT_R) flags |= G_IO_IN | G_IO_HUP; - if (rwx & SELECT_W) flags |= G_IO_OUT; - if (rwx & SELECT_X) flags |= G_IO_PRI; - id->chan = g_io_channel_unix_new(fd); - g_io_channel_set_encoding(id->chan, NULL, NULL); - id->watch_id = g_io_add_watch_full(id->chan, GDK_PRIORITY_REDRAW+1, flags, - fd_input_func, NULL, NULL); -#else - int flags = 0; - if (rwx & SELECT_R) flags |= GDK_INPUT_READ; - if (rwx & SELECT_W) flags |= GDK_INPUT_WRITE; - if (rwx & SELECT_X) flags |= GDK_INPUT_EXCEPTION; - assert(flags); - id->id = gdk_input_add(fd, flags, fd_input_func, NULL); -#endif - - return id; -} - -void uxsel_input_remove(uxsel_id *id) { -#if GTK_CHECK_VERSION(2,0,0) - g_source_remove(id->watch_id); - g_io_channel_unref(id->chan); -#else - gdk_input_remove(id->id); -#endif - sfree(id); -} - -/* ---------------------------------------------------------------------- - * Timers. - */ - -static guint timer_id = 0; - -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) { - g_source_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 = g_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) - g_source_remove(timer_id); - - ticks = next - GETTICKCOUNT(); - if (ticks <= 0) - ticks = 1; /* just in case */ - - timer_id = g_timeout_add(ticks, timer_trigger, LONG_TO_GPOINTER(next)); -} - -/* ---------------------------------------------------------------------- - * Toplevel callbacks. - */ - -static guint toplevel_callback_idle_id; -static bool idle_fn_scheduled; - -static void notify_toplevel_callback(void *); - -static gint idle_toplevel_callback_func(gpointer data) -{ - 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() && idle_fn_scheduled) { - g_source_remove(toplevel_callback_idle_id); - idle_fn_scheduled = false; - } - - return true; -} - -static void notify_toplevel_callback(void *vctx) -{ - if (!idle_fn_scheduled) { - toplevel_callback_idle_id = - g_idle_add(idle_toplevel_callback_func, NULL); - idle_fn_scheduled = true; - } -} - -/* ---------------------------------------------------------------------- - * Setup function. The real main program must call this. - */ - -void gtkcomm_setup(void) -{ - uxsel_init(); - request_callback_notifications(notify_toplevel_callback, NULL); -} diff --git a/unix/gtkcompat.h b/unix/gtkcompat.h deleted file mode 100644 index 6ab5c809f..000000000 --- a/unix/gtkcompat.h +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Header file to make compatibility with older GTK versions less - * painful, by #defining various things that are pure spelling changes - * between GTK1 and GTK2, or between 2 and 3. - */ - -#if !GTK_CHECK_VERSION(2,0,0) - -#include -#include - -/* Helper macro used in definitions below. Note that although it - * *expands* w and flag twice, it does not *evaluate* them twice - * because the evaluations are in exclusive branches of ?:. So it's - * a side-effect-safe macro. */ -#define gtk1_widget_set_unset_flag(w, flag, b) \ - ((b) ? GTK_WIDGET_SET_FLAGS(w, flag) : GTK_WIDGET_UNSET_FLAGS(w, flag)) - -#define GType GtkType -#define GObject GtkObject -#define G_CALLBACK(x) GTK_SIGNAL_FUNC(x) -#define G_OBJECT(x) GTK_OBJECT(x) -#define G_TYPE_CHECK_INSTANCE_TYPE GTK_CHECK_TYPE -#define G_TYPE_CHECK_INSTANCE_CAST GTK_CHECK_CAST -#define G_TYPE_CHECK_CLASS_TYPE GTK_CHECK_CLASS_TYPE -#define G_TYPE_CHECK_CLASS_CAST GTK_CHECK_CLASS_CAST - -#define g_ascii_isspace(x) (isspace((unsigned char)(x))) -#define g_signal_connect gtk_signal_connect -#define g_signal_connect_swapped gtk_signal_connect_object -#define g_signal_stop_emission_by_name gtk_signal_emit_stop_by_name -#define g_signal_emit_by_name gtk_signal_emit_by_name -#define g_signal_handler_disconnect gtk_signal_disconnect -#define g_object_get_data gtk_object_get_data -#define g_object_set_data gtk_object_set_data -#define g_object_set_data_full gtk_object_set_data_full -#define g_object_ref_sink(x) do { \ - gtk_object_ref(x); \ - gtk_object_sink(x); \ - } while (0) - -#define GDK_GRAB_SUCCESS GrabSuccess - -#define GDK_WINDOW_XID GDK_WINDOW_XWINDOW - -#define gtk_widget_set_size_request gtk_widget_set_usize -#define gtk_radio_button_get_group gtk_radio_button_group -#define gtk_notebook_set_current_page gtk_notebook_set_page -#define gtk_color_selection_set_has_opacity_control \ - gtk_color_selection_set_opacity - -#define gtk_dialog_get_content_area(dlg) ((dlg)->vbox) -#define gtk_dialog_get_action_area(dlg) ((dlg)->action_area) -#define gtk_dialog_set_can_default(dlg) ((dlg)->action_area) -#define gtk_widget_get_window(w) ((w)->window) -#define gtk_widget_get_parent(w) ((w)->parent) -#define gtk_widget_set_allocation(w, a) ((w)->allocation = *(a)) -#define gtk_container_get_border_width(c) ((c)->border_width) -#define gtk_container_get_focus_child(c) ((c)->focus_child) -#define gtk_bin_get_child(b) ((b)->child) -#define gtk_color_selection_dialog_get_color_selection(cs) ((cs)->colorsel) -#define gtk_selection_data_get_target(sd) ((sd)->target) -#define gtk_selection_data_get_data_type(sd) ((sd)->type) -#define gtk_selection_data_get_data(sd) ((sd)->data) -#define gtk_selection_data_get_length(sd) ((sd)->length) -#define gtk_selection_data_get_format(sd) ((sd)->format) -#define gtk_adjustment_set_lower(a, val) ((a)->lower = (val)) -#define gtk_adjustment_set_upper(a, val) ((a)->upper = (val)) -#define gtk_adjustment_set_page_size(a, val) ((a)->page_size = (val)) -#define gtk_adjustment_set_page_increment(a, val) ((a)->page_increment = (val)) -#define gtk_adjustment_set_step_increment(a, val) ((a)->step_increment = (val)) -#define gtk_adjustment_get_value(a) ((a)->value) -#define gtk_selection_data_get_selection(a) ((a)->selection) -#define gdk_display_beep(disp) gdk_beep() - -#define gtk_widget_set_has_window(w, b) \ - gtk1_widget_set_unset_flag(w, GTK_NO_WINDOW, !(b)) -#define gtk_widget_set_mapped(w, b) \ - gtk1_widget_set_unset_flag(w, GTK_MAPPED, (b)) -#define gtk_widget_set_can_default(w, b) \ - gtk1_widget_set_unset_flag(w, GTK_CAN_DEFAULT, (b)) -#define gtk_widget_get_visible(w) GTK_WIDGET_VISIBLE(w) -#define gtk_widget_get_mapped(w) GTK_WIDGET_MAPPED(w) -#define gtk_widget_get_realized(w) GTK_WIDGET_REALIZED(w) -#define gtk_widget_get_state(w) GTK_WIDGET_STATE(w) -#define gtk_widget_get_can_focus(w) GTK_WIDGET_CAN_FOCUS(w) -#define gtk_widget_is_drawable(w) GTK_WIDGET_DRAWABLE(w) -#define gtk_widget_is_sensitive(w) GTK_WIDGET_IS_SENSITIVE(w) -#define gtk_widget_has_focus(w) GTK_WIDGET_HAS_FOCUS(w) - -/* This is a bit of a bodge because it relies on us only calling this - * macro as GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), so under - * GTK1 it makes sense to omit the contained function call and just - * return the GDK default display. */ -#define GDK_DISPLAY_XDISPLAY(x) GDK_DISPLAY() - -#define GDK_KEY_C ('C') -#define GDK_KEY_V ('V') -#define GDK_KEY_c ('c') -#define GDK_KEY_v ('v') - -#endif /* 2.0 */ - -#if !GTK_CHECK_VERSION(2,22,0) - -#define gdk_visual_get_depth(v) ((v)->depth) - -#endif /* 2.22 */ - -#if !GTK_CHECK_VERSION(2,24,0) - -#define GDK_KEY_Alt_L GDK_Alt_L -#define GDK_KEY_Alt_R GDK_Alt_R -#define GDK_KEY_BackSpace GDK_BackSpace -#define GDK_KEY_Begin GDK_Begin -#define GDK_KEY_Break GDK_Break -#define GDK_KEY_Delete GDK_Delete -#define GDK_KEY_Down GDK_Down -#define GDK_KEY_End GDK_End -#define GDK_KEY_Escape GDK_Escape -#define GDK_KEY_F10 GDK_F10 -#define GDK_KEY_F11 GDK_F11 -#define GDK_KEY_F12 GDK_F12 -#define GDK_KEY_F13 GDK_F13 -#define GDK_KEY_F14 GDK_F14 -#define GDK_KEY_F15 GDK_F15 -#define GDK_KEY_F16 GDK_F16 -#define GDK_KEY_F17 GDK_F17 -#define GDK_KEY_F18 GDK_F18 -#define GDK_KEY_F19 GDK_F19 -#define GDK_KEY_F1 GDK_F1 -#define GDK_KEY_F20 GDK_F20 -#define GDK_KEY_F2 GDK_F2 -#define GDK_KEY_F3 GDK_F3 -#define GDK_KEY_F4 GDK_F4 -#define GDK_KEY_F5 GDK_F5 -#define GDK_KEY_F6 GDK_F6 -#define GDK_KEY_F7 GDK_F7 -#define GDK_KEY_F8 GDK_F8 -#define GDK_KEY_F9 GDK_F9 -#define GDK_KEY_Home GDK_Home -#define GDK_KEY_Insert GDK_Insert -#define GDK_KEY_ISO_Left_Tab GDK_ISO_Left_Tab -#define GDK_KEY_KP_0 GDK_KP_0 -#define GDK_KEY_KP_1 GDK_KP_1 -#define GDK_KEY_KP_2 GDK_KP_2 -#define GDK_KEY_KP_3 GDK_KP_3 -#define GDK_KEY_KP_4 GDK_KP_4 -#define GDK_KEY_KP_5 GDK_KP_5 -#define GDK_KEY_KP_6 GDK_KP_6 -#define GDK_KEY_KP_7 GDK_KP_7 -#define GDK_KEY_KP_8 GDK_KP_8 -#define GDK_KEY_KP_9 GDK_KP_9 -#define GDK_KEY_KP_Add GDK_KP_Add -#define GDK_KEY_KP_Begin GDK_KP_Begin -#define GDK_KEY_KP_Decimal GDK_KP_Decimal -#define GDK_KEY_KP_Delete GDK_KP_Delete -#define GDK_KEY_KP_Divide GDK_KP_Divide -#define GDK_KEY_KP_Down GDK_KP_Down -#define GDK_KEY_KP_End GDK_KP_End -#define GDK_KEY_KP_Enter GDK_KP_Enter -#define GDK_KEY_KP_Home GDK_KP_Home -#define GDK_KEY_KP_Insert GDK_KP_Insert -#define GDK_KEY_KP_Left GDK_KP_Left -#define GDK_KEY_KP_Multiply GDK_KP_Multiply -#define GDK_KEY_KP_Page_Down GDK_KP_Page_Down -#define GDK_KEY_KP_Page_Up GDK_KP_Page_Up -#define GDK_KEY_KP_Right GDK_KP_Right -#define GDK_KEY_KP_Subtract GDK_KP_Subtract -#define GDK_KEY_KP_Up GDK_KP_Up -#define GDK_KEY_Left GDK_Left -#define GDK_KEY_Meta_L GDK_Meta_L -#define GDK_KEY_Meta_R GDK_Meta_R -#define GDK_KEY_Num_Lock GDK_Num_Lock -#define GDK_KEY_Page_Down GDK_Page_Down -#define GDK_KEY_Page_Up GDK_Page_Up -#define GDK_KEY_Return GDK_Return -#define GDK_KEY_Right GDK_Right -#define GDK_KEY_Tab GDK_Tab -#define GDK_KEY_Up GDK_Up -#define GDK_KEY_greater GDK_greater -#define GDK_KEY_less GDK_less - -#define gdk_window_get_screen(w) gdk_drawable_get_screen(w) -#define gtk_combo_box_new_with_model_and_entry(t) gtk_combo_box_entry_new_with_model(t, 1) - -#endif /* 2.24 */ - -#if !GTK_CHECK_VERSION(3,0,0) -#define GDK_IS_X11_WINDOW(window) (1) -#endif - -#if GTK_CHECK_VERSION(3,0,0) -#define STANDARD_OK_LABEL "_OK" -#define STANDARD_OPEN_LABEL "_Open" -#define STANDARD_CANCEL_LABEL "_Cancel" -#else -#define STANDARD_OK_LABEL GTK_STOCK_OK -#define STANDARD_OPEN_LABEL GTK_STOCK_OPEN -#define STANDARD_CANCEL_LABEL GTK_STOCK_CANCEL -#endif - -#if GTK_CHECK_VERSION(3,0,0) -#define gtk_hseparator_new() gtk_separator_new(GTK_ORIENTATION_HORIZONTAL) -/* Fortunately, my hboxes and vboxes never actually set homogeneous to - * true, so I can just wrap these deprecated constructors with a macro - * without also having to arrange a call to gtk_box_set_homogeneous. */ -#define gtk_hbox_new(homogeneous, spacing) \ - gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing) -#define gtk_vbox_new(homogeneous, spacing) \ - gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing) -#define gtk_vscrollbar_new(adjust) \ - gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, adjust) - -#define gdk_get_display() gdk_display_get_name(gdk_display_get_default()) - -#define gdk_cursor_new(cur) \ - gdk_cursor_new_for_display(gdk_display_get_default(), cur) - -#endif /* 3.0 */ diff --git a/unix/gtkmisc.h b/unix/gtkmisc.h deleted file mode 100644 index 3d2d1f36d..000000000 --- a/unix/gtkmisc.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Miscellaneous helper functions for GTK. - */ - -#ifndef PUTTY_GTKMISC_H -#define PUTTY_GTKMISC_H - -int string_width(const char *text); -void get_label_text_dimensions(const char *text, int *width, int *height); - -void align_label_left(GtkLabel *label); - -GtkWidget *our_dialog_new(void); -void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w, - bool expand, bool fill, guint padding); -void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w); -GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg); - -#endif /* PUTTY_GTKMISC_H */ diff --git a/unix/keygen-noise.c b/unix/keygen-noise.c deleted file mode 100644 index ab7de68d5..000000000 --- a/unix/keygen-noise.c +++ /dev/null @@ -1,64 +0,0 @@ -/* - * keygen-noise.c: Unix implementation of get_heavy_noise() from cmdgen.c. - */ - -#include -#include - -#include -#include - -#include "putty.h" - -char *get_random_data(int len, const char *device) -{ - char *buf = snewn(len, char); - int fd; - int ngot, ret; - - if (!device) { - static const char *const default_devices[] = { - "/dev/urandom", "/dev/random" - }; - size_t i; - - for (i = 0; i < lenof(default_devices); i++) { - if (access(default_devices[i], R_OK) == 0) { - device = default_devices[i]; - break; - } - } - - if (!device) { - sfree(buf); - fprintf(stderr, "puttygen: cannot find a readable " - "random number source; use --random-device\n"); - return NULL; - } - } - - fd = open(device, O_RDONLY); - if (fd < 0) { - sfree(buf); - fprintf(stderr, "puttygen: %s: open: %s\n", - device, strerror(errno)); - return NULL; - } - - ngot = 0; - while (ngot < len) { - ret = read(fd, buf+ngot, len-ngot); - if (ret < 0) { - close(fd); - sfree(buf); - fprintf(stderr, "puttygen: %s: read: %s\n", - device, strerror(errno)); - return NULL; - } - ngot += ret; - } - - close(fd); - - return buf; -} diff --git a/unix/local-proxy.c b/unix/local-proxy.c deleted file mode 100644 index 157f9207c..000000000 --- a/unix/local-proxy.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * local-proxy.c: Unix implementation of platform_new_connection(), - * supporting an OpenSSH-like proxy command. - */ - -#include -#include -#include -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "proxy/proxy.h" - -char *platform_setup_local_proxy(Socket *socket, const char *cmd) -{ - /* - * Create the pipes to the proxy command, and spawn the proxy - * command process. - */ - int to_cmd_pipe[2], from_cmd_pipe[2], cmd_err_pipe[2]; - if (pipe(to_cmd_pipe) < 0 || - pipe(from_cmd_pipe) < 0 || - pipe(cmd_err_pipe) < 0) { - return dupprintf("pipe: %s", strerror(errno)); - } - cloexec(to_cmd_pipe[1]); - cloexec(from_cmd_pipe[0]); - cloexec(cmd_err_pipe[0]); - - int pid = fork(); - 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]); - dup2(cmd_err_pipe[1], 2); - noncloexec(0); - noncloexec(1); - execl("/bin/sh", "sh", "-c", cmd, (void *)NULL); - _exit(255); - } - - if (pid < 0) { - return dupprintf("fork: %s", strerror(errno)); - } - - close(to_cmd_pipe[0]); - close(from_cmd_pipe[1]); - close(cmd_err_pipe[1]); - - setup_fd_socket(socket, from_cmd_pipe[0], to_cmd_pipe[1], cmd_err_pipe[0]); - - return NULL; -} - -Socket *platform_new_connection(SockAddr *addr, const char *hostname, - int port, bool privport, - bool oobinline, bool nodelay, bool keepalive, - Plug *plug, Conf *conf, Interactor *itr) -{ - switch (conf_get_int(conf, CONF_proxy_type)) { - case PROXY_CMD: { - DeferredSocketOpener *opener = local_proxy_opener( - addr, port, plug, conf, itr); - Socket *socket = make_deferred_fd_socket(opener, addr, port, plug); - local_proxy_opener_set_socket(opener, socket); - return socket; - } - - case PROXY_FUZZ: { - char *cmd = format_telnet_command(addr, port, conf, NULL); - int outfd = open("/dev/null", O_WRONLY); - if (outfd == -1) { - sfree(cmd); - return new_error_socket_fmt( - plug, "/dev/null: %s", strerror(errno)); - } - int infd = open(cmd, O_RDONLY); - if (infd == -1) { - Socket *toret = new_error_socket_fmt( - plug, "%s: %s", cmd, strerror(errno)); - sfree(cmd); - close(outfd); - return toret; - } - sfree(cmd); - return make_fd_socket(infd, outfd, -1, addr, port, plug); - } - - default: - return NULL; - } -} - -Socket *platform_start_subprocess(const char *cmd, Plug *plug, - const char *prefix) -{ - Socket *socket = make_deferred_fd_socket( - null_deferred_socket_opener(), - sk_nonamelookup(""), 0, plug); - char *err = platform_setup_local_proxy(socket, cmd); - fd_socket_set_psb_prefix(socket, prefix); - - if (err) { - sk_close(socket); - socket = new_error_socket_fmt(plug, "%s", err); - sfree(err); - } - - return socket; -} diff --git a/unix/main-gtk-application.c b/unix/main-gtk-application.c deleted file mode 100644 index 963c93fca..000000000 --- a/unix/main-gtk-application.c +++ /dev/null @@ -1,326 +0,0 @@ -/* - * main-gtk-application.c: a top-level front end to GUI PuTTY and - * pterm, using GtkApplication. Suitable for OS X. Currently - * unfinished. - * - * (You could run it on ordinary Linux GTK too, in principle, but I - * don't think it would be particularly useful to do so, even once - * it's fully working.) - */ - -/* - -Building this for OS X is currently broken, because the new -CMake-based build system doesn't support it yet. Probably what needs -doing is to add it back in to unix/CMakeLists.txt under a condition -like if(CMAKE_SYSTEM_NAME MATCHES "Darwin"). - -*/ - -/* - -TODO list for a sensible GTK3 PuTTY/pterm on OS X: - -Still to do on the application menu bar: items that have to vary with -context or user action (saved sessions and mid-session special -commands), and disabling/enabling the main actions in parallel with -their counterparts in the Ctrl-rightclick context menu. - -Mouse wheel events and trackpad scrolling gestures don't work quite -right in the terminal drawing area. This seems to be a combination of -two things, neither of which I completely understand yet. Firstly, on -OS X GTK my trackpad seems to generate GDK scroll events for which -gdk_event_get_scroll_deltas returns integers rather than integer -multiples of 1/30, so we end up scrolling by very large amounts; -secondly, the window doesn't seem to receive a GTK "draw" event until -after the entire scroll gesture is complete, which means we don't get -constant visual feedback on how much we're scrolling by. - -There doesn't seem to be a resize handle on terminal windows. Then -again, they do seem to _be_ resizable; the handle just isn't shown. -Perhaps that's a feature (certainly in a scrollbarless configuration -the handle gets in the way of the bottom right character cell in the -terminal itself), but it would be nice to at least understand _why_ it -happens and perhaps include an option to put it back again. - -A slight oddity with menus that pop up directly under the mouse -pointer: mousing over the menu items doesn't highlight them initially, -but if I mouse off the menu and back on (without un-popping-it-up) -then suddenly that does work. I don't know if this is something I can -fix, though; it might very well be a quirk of the underlying GTK. - -Does OS X have a standard system of online help that I could tie into? - -Need to work out what if anything we can do with Pageant on OS X. -Perhaps it's too much bother and we should just talk to the -system-provided SSH agent? Or perhaps not. - -Nice-to-have: a custom right-click menu from the application's dock -tile, listing the saved sessions for quick launch. As far as I know -there's nothing built in to GtkApplication that can produce this, but -it's possible we might be able to drop a piece of native Cocoa code in -under ifdef, substituting an application delegate of our own which -forwards all methods we're not interested in to the GTK-provided one? - -At the point where this becomes polished enough to publish pre-built, -I suppose I'll have to look into OS X code signing. -https://wiki.gnome.org/Projects/GTK%2B/OSX/Bundling has some links. - - */ - -#include -#include - -#include - -#include - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "gtkmisc.h" - -char *x_get_default(const char *key) { return NULL; } - -const bool buildinfo_gtk_relevant = true; - -#if !GTK_CHECK_VERSION(3,0,0) -#error This front end only works in GTK 3 -#endif - -static void startup(GApplication *app, gpointer user_data) -{ - GMenu *menubar, *menu, *section; - - menubar = g_menu_new(); - - menu = g_menu_new(); - g_menu_append_submenu(menubar, "File", G_MENU_MODEL(menu)); - - section = g_menu_new(); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_menu_append(section, "New Window", "app.newwin"); - - menu = g_menu_new(); - g_menu_append_submenu(menubar, "Edit", G_MENU_MODEL(menu)); - - section = g_menu_new(); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_menu_append(section, "Copy", "win.copy"); - g_menu_append(section, "Paste", "win.paste"); - g_menu_append(section, "Copy All", "win.copyall"); - - menu = g_menu_new(); - g_menu_append_submenu(menubar, "Window", G_MENU_MODEL(menu)); - - section = g_menu_new(); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_menu_append(section, "Restart Session", "win.restart"); - g_menu_append(section, "Duplicate Session", "win.duplicate"); - - section = g_menu_new(); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_menu_append(section, "Change Settings", "win.changesettings"); - - if (use_event_log) { - section = g_menu_new(); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_menu_append(section, "Event Log", "win.eventlog"); - } - - section = g_menu_new(); - g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); - g_menu_append(section, "Clear Scrollback", "win.clearscrollback"); - g_menu_append(section, "Reset Terminal", "win.resetterm"); - -#if GTK_CHECK_VERSION(3,12,0) -#define SET_ACCEL(app, command, accel) do \ - { \ - static const char *const accels[] = { accel, NULL }; \ - gtk_application_set_accels_for_action( \ - GTK_APPLICATION(app), command, accels); \ - } while (0) -#else - /* The Gtk function used above was new in 3.12; the one below - * was deprecated from 3.14. */ -#define SET_ACCEL(app, command, accel) \ - gtk_application_add_accelerator(GTK_APPLICATION(app), accel, \ - command, NULL) -#endif - - SET_ACCEL(app, "app.newwin", "n"); - SET_ACCEL(app, "win.copy", "c"); - SET_ACCEL(app, "win.paste", "v"); - -#undef SET_ACCEL - - gtk_application_set_menubar(GTK_APPLICATION(app), - G_MENU_MODEL(menubar)); -} - -#define WIN_ACTION_LIST(X) \ - X("copy", MA_COPY) \ - X("paste", MA_PASTE) \ - X("copyall", MA_COPY_ALL) \ - X("duplicate", MA_DUPLICATE_SESSION) \ - X("restart", MA_RESTART_SESSION) \ - X("changesettings", MA_CHANGE_SETTINGS) \ - X("clearscrollback", MA_CLEAR_SCROLLBACK) \ - X("resetterm", MA_RESET_TERMINAL) \ - X("eventlog", MA_EVENT_LOG) \ - /* end of list */ - -#define WIN_ACTION_CALLBACK(name, id) \ -static void win_action_cb_ ## id(GSimpleAction *a, GVariant *p, gpointer d) \ -{ app_menu_action(d, id); } -WIN_ACTION_LIST(WIN_ACTION_CALLBACK) -#undef WIN_ACTION_CALLBACK - -static const GActionEntry win_actions[] = { -#define WIN_ACTION_ENTRY(name, id) { name, win_action_cb_ ## id }, -WIN_ACTION_LIST(WIN_ACTION_ENTRY) -#undef WIN_ACTION_ENTRY -}; - -static GtkApplication *app; -GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) -{ - GtkWidget *win = gtk_application_window_new(app); - g_action_map_add_action_entries(G_ACTION_MAP(win), - win_actions, - G_N_ELEMENTS(win_actions), - frontend); - return win; -} - -void launch_duplicate_session(Conf *conf) -{ - assert(!dup_check_launchable || conf_launchable(conf)); - g_application_hold(G_APPLICATION(app)); - new_session_window(conf_copy(conf), NULL); -} - -void session_window_closed(void) -{ - g_application_release(G_APPLICATION(app)); -} - -static void post_initial_config_box(void *vctx, int result) -{ - Conf *conf = (Conf *)vctx; - - if (result > 0) { - new_session_window(conf, NULL); - } else if (result == 0) { - conf_free(conf); - g_application_release(G_APPLICATION(app)); - } -} - -void launch_saved_session(const char *str) -{ - Conf *conf = conf_new(); - do_defaults(str, conf); - - g_application_hold(G_APPLICATION(app)); - - if (!conf_launchable(conf)) { - initial_config_box(conf, post_initial_config_box, conf); - } else { - new_session_window(conf, NULL); - } -} - -void launch_new_session(void) -{ - /* Same as launch_saved_session except that we pass NULL to - * do_defaults. */ - launch_saved_session(NULL); -} - -void new_app_win(GtkApplication *app) -{ - launch_new_session(); -} - -static void window_setup_error_callback(void *vctx, int result) -{ - g_application_release(G_APPLICATION(app)); -} - -void window_setup_error(const char *errmsg) -{ - create_message_box(NULL, "Error creating session window", errmsg, - string_width("Some sort of fiddly error message that " - "might be technical"), - true, &buttons_ok, window_setup_error_callback, NULL); -} - -static void activate(GApplication *app, - gpointer user_data) -{ - new_app_win(GTK_APPLICATION(app)); -} - -static void newwin_cb(GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - new_app_win(GTK_APPLICATION(user_data)); -} - -static void quit_cb(GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - g_application_quit(G_APPLICATION(user_data)); -} - -static void about_cb(GSimpleAction *action, - GVariant *parameter, - gpointer user_data) -{ - about_box(NULL); -} - -static const GActionEntry app_actions[] = { - { "newwin", newwin_cb }, - { "about", about_cb }, - { "quit", quit_cb }, -}; - -int main(int argc, char **argv) -{ - int status; - - /* Call the function in ux{putty,pterm}.c to do app-type - * specific setup */ - setup(false); /* false means we are not a one-session process */ - - if (argc > 1) { - pty_osx_envrestore_prefix = argv[--argc]; - } - - { - const char *home = getenv("HOME"); - if (home) { - if (chdir(home)) {} - } - } - - gtkcomm_setup(); - - app = gtk_application_new("org.tartarus.projects.putty.macputty", - G_APPLICATION_FLAGS_NONE); - g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); - g_signal_connect(app, "startup", G_CALLBACK(startup), NULL); - g_action_map_add_action_entries(G_ACTION_MAP(app), - app_actions, - G_N_ELEMENTS(app_actions), - app); - - status = g_application_run(G_APPLICATION(app), argc, argv); - g_object_unref(app); - - return status; -} diff --git a/unix/main-gtk-simple.c b/unix/main-gtk-simple.c deleted file mode 100644 index f477dfff3..000000000 --- a/unix/main-gtk-simple.c +++ /dev/null @@ -1,679 +0,0 @@ -/* - * main-gtk-simple.c: the common main-program code between the - * straight-up Unix PuTTY and pterm, which they do not share with the - * multi-session main-gtk-application.c. - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#if GTK_CHECK_VERSION(2,0,0) -#include -#endif - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "terminal.h" -#include "gtkcompat.h" -#include "unifont.h" -#include "gtkmisc.h" - -#ifndef NOT_X_WINDOWS -#include -#include -#include -#include -#include "x11misc.h" -#endif - -static char *progname, **gtkargvstart; -static int ngtkargs; - -static const char *app_name = "pterm"; - -char *x_get_default(const char *key) -{ -#ifndef NOT_X_WINDOWS - Display *disp; - if ((disp = get_x11_display()) == NULL) - return NULL; - return XGetDefault(disp, app_name, key); -#else - return NULL; -#endif -} - -void fork_and_exec_self(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 += ngtkargs; - while (va_arg(ap, char *) != NULL) - n++; - va_end(ap); - - args = snewn(n, char *); - args[0] = progname; - args[n-1] = NULL; - for (i = 0; i < ngtkargs; i++) - args[i+1] = 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(progname, args); - perror("exec"); - _exit(127); - - } else { - int status; - sfree(args); - waitpid(pid, &status, 0); - } - -} - -void launch_duplicate_session(Conf *conf) -{ - /* - * 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; - strbuf *serialised; - char option[80]; - int pipefd[2]; - - if (pipe(pipefd) < 0) { - perror("pipe"); - return; - } - - serialised = strbuf_new(); - - conf_serialise(BinarySink_UPCAST(serialised), conf); - if (use_pty_argv && pty_argv) - for (i = 0; pty_argv[i]; i++) - put_asciz(serialised, pty_argv[i]); - - sprintf(option, "---[%d,%zu]", pipefd[0], serialised->len); - noncloexec(pipefd[0]); - fork_and_exec_self(pipefd[1], option, NULL); - close(pipefd[0]); - - i = ret = 0; - while (i < serialised->len && - (ret = write(pipefd[1], serialised->s + i, - serialised->len - i)) > 0) - i += ret; - if (ret < 0) - perror("write to pipe"); - close(pipefd[1]); - strbuf_free(serialised); -} - -void launch_new_session(void) -{ - fork_and_exec_self(-1, NULL); -} - -void launch_saved_session(const char *str) -{ - fork_and_exec_self(-1, "-load", str, NULL); -} - -int read_dupsession_data(Conf *conf, char *arg) -{ - int fd, i, ret, size; - char *data; - BinarySource src[1]; - - 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); - } - - BinarySource_BARE_INIT(src, data, size); - if (!conf_deserialise(conf, src)) { - fprintf(stderr, "%s: malformed Duplicate Session data\n", appname); - exit(1); - } - if (use_pty_argv) { - int pty_argc = 0; - size_t argv_startpos = src->pos; - - while (get_asciz(src), !get_err(src)) - pty_argc++; - - src->err = BSE_NO_ERROR; - - if (pty_argc > 0) { - src->pos = argv_startpos; - - pty_argv = snewn(pty_argc + 1, char *); - pty_argv[pty_argc] = NULL; - for (i = 0; i < pty_argc; i++) - pty_argv[i] = dupstr(get_asciz(src)); - } - } - - if (get_err(src) || get_avail(src) > 0) { - fprintf(stderr, "%s: malformed Duplicate Session data\n", appname); - exit(1); - } - - sfree(data); - return 0; -} - -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) { - char *buildinfo_text = buildinfo("\n"); - if (fprintf(fp, "%s: %s\n%s\n", appname, ver, buildinfo_text) < 0 || - fflush(fp) < 0) { - perror("output error"); - exit(1); - } - sfree(buildinfo_text); -} - -static const char *geometry_string; - -void cmdline_error(const 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); -} - -void window_setup_error(const char *errmsg) -{ - fprintf(stderr, "%s: %s\n", appname, errmsg); - exit(1); -} - -bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) -{ - bool err = false; - 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. I use the alternative if (1) - * {...} else ((void)0). - */ -#define EXPECTS_ARG if (1) { \ - if (--argc <= 0) { \ - err = true; \ - fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ - continue; \ - } else \ - val = *++argv; \ - } else ((void)0) -#define SECOND_PASS_ONLY if (1) { \ - if (!do_everything) \ - continue; \ - } else ((void)0) - - while (--argc > 0) { - const 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")) { - EXPECTS_ARG; - SECOND_PASS_ONLY; - geometry_string = val; - } 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")) { - EXPECTS_ARG; - SECOND_PASS_ONLY; - - { -#if GTK_CHECK_VERSION(3,0,0) - GdkRGBA rgba; - bool success = gdk_rgba_parse(&rgba, val); -#else - GdkColor col; - bool success = gdk_color_parse(val, &col); -#endif - - if (!success) { - err = true; - fprintf(stderr, "%s: unable to parse colour \"%s\"\n", - appname, val); - } else { -#if GTK_CHECK_VERSION(3,0,0) - int r = rgba.red * 255; - int g = rgba.green * 255; - int b = rgba.blue * 255; -#else - int r = col.red / 256; - int g = col.green / 256; - int b = col.blue / 256; -#endif - - 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, r); - conf_set_int_int(conf, CONF_colours, index*3+1, g); - conf_set_int_int(conf, CONF_colours, index*3+2, b); - } - } - - } 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 = true, 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_bool(conf, CONF_stamp_utmp, false); - - } else if (!strcmp(p, "-ut")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_stamp_utmp, true); - - } else if (!strcmp(p, "-ls-") || !strcmp(p, "+ls")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_login_shell, false); - - } else if (!strcmp(p, "-ls")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_login_shell, true); - - } else if (!strcmp(p, "-nethack")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_nethack_keypad, true); - - } else if (!strcmp(p, "-sb-") || !strcmp(p, "+sb")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_scrollbar, false); - - } else if (!strcmp(p, "-sb")) { - SECOND_PASS_ONLY; - conf_set_bool(conf, CONF_scrollbar, true); - - } else if (!strcmp(p, "-name")) { - EXPECTS_ARG; - app_name = val; - - } else if (!strcmp(p, "-xrm")) { - EXPECTS_ARG; - provide_xrm_string(val, appname); - - } 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 (has_ca_config_box && - (!strcmp(p, "-host-ca") || !strcmp(p, "--host-ca") || - !strcmp(p, "-host_ca") || !strcmp(p, "--host_ca"))) { - show_ca_config_box_synchronously(); - exit(0); - - } else if (p[0] != '-') { - /* Non-option arguments not handled by cmdline.c are errors. */ - if (do_everything) { - err = true; - fprintf(stderr, "%s: unexpected non-option argument '%s'\n", - appname, p); - } - - } else { - err = true; - fprintf(stderr, "%s: unrecognized option '%s'\n", appname, p); - } - } - - return err; -} - -GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend) -{ - return gtk_window_new(GTK_WINDOW_TOPLEVEL); -} - -const bool buildinfo_gtk_relevant = true; - -struct post_initial_config_box_ctx { - Conf *conf; - const char *geometry_string; -}; - -static void post_initial_config_box(void *vctx, int result) -{ - struct post_initial_config_box_ctx ctx = - *(struct post_initial_config_box_ctx *)vctx; - sfree(vctx); - - if (result > 0) { - new_session_window(ctx.conf, ctx.geometry_string); - } else { - /* In this main(), which only runs one session in total, a - * negative result from the initial config box means we simply - * terminate. */ - conf_free(ctx.conf); - gtk_main_quit(); - } -} - -void session_window_closed(void) -{ - gtk_main_quit(); -} - -int main(int argc, char **argv) -{ - Conf *conf; - bool need_config_box; - - setlocale(LC_CTYPE, ""); - - /* Call the function in ux{putty,pterm}.c to do app-type - * specific setup */ - setup(true); /* true means we are a one-session process */ - - progname = argv[0]; - - /* - * Copy the original argv before letting gtk_init fiddle with - * it. It will be required later. - */ - { - int i, oldargc; - gtkargvstart = snewn(argc-1, char *); - for (i = 1; i < argc; i++) - gtkargvstart[i-1] = dupstr(argv[i]); - oldargc = argc; - gtk_init(&argc, &argv); - ngtkargs = oldargc - argc; - } - - conf = conf_new(); - - gtkcomm_setup(); - - /* - * 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. However, we'll have to - * unblock it again when pterm forks. - */ - block_signal(SIGPIPE, true); - - if (argc > 1 && !strncmp(argv[1], "---", 3)) { - read_dupsession_data(conf, argv[1]); - /* Splatter this argument so it doesn't clutter a ps listing */ - smemclr(argv[1], strlen(argv[1])); - - assert(!dup_check_launchable || conf_launchable(conf)); - need_config_box = false; - } else { - if (do_cmdline(argc, argv, false, conf)) - exit(1); /* pre-defaults pass to get -class */ - do_defaults(NULL, conf); - if (do_cmdline(argc, argv, true, conf)) - exit(1); /* post-defaults, do everything */ - - cmdline_run_saved(conf); - - if (cmdline_tooltype & TOOLTYPE_HOST_ARG) - need_config_box = !cmdline_host_ok(conf); - else - need_config_box = false; - } - - if (need_config_box) { - /* - * Put up the initial config box, which will pass the provided - * parameters (with conf updated) to new_session_window() when - * (if) the user selects Open. Or it might close without - * creating a session window, if the user selects Cancel. Or - * it might just create the session window immediately if this - * is a pterm-style app which doesn't have an initial config - * box at all. - */ - struct post_initial_config_box_ctx *ctx = - snew(struct post_initial_config_box_ctx); - ctx->conf = conf; - ctx->geometry_string = geometry_string; - initial_config_box(conf, post_initial_config_box, ctx); - } else { - /* - * No initial config needed; just create the session window - * now. - */ - new_session_window(conf, geometry_string); - } - - gtk_main(); - - return 0; -} diff --git a/unix/network.c b/unix/network.c deleted file mode 100644 index 207047db0..000000000 --- a/unix/network.c +++ /dev/null @@ -1,1755 +0,0 @@ -/* - * Unix networking abstraction. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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; -}; - -/* - * 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; -}; - -typedef struct NetSocket NetSocket; -struct NetSocket { - const char *error; - int s; - Plug *plug; - bufchain output_data; - bool connected; /* irrelevant for listening sockets */ - bool writable; - bool frozen; /* this causes readability notifications to be ignored */ - bool localhost_only; /* for listening sockets */ - char oobdata[1]; - size_t sending_oob; - bool oobpending; /* is there OOB data available to read? */ - bool oobinline; - enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; - bool incomingeof; - int pending_error; /* in case send() returns error */ - bool listener; - bool nodelay, keepalive; /* for connect()-type sockets */ - bool privport; - int 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. - */ - NetSocket *parent, *child; - - Socket sock; -}; - -struct SockAddr { - 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(NetSocket *s); - -static int cmpfortree(void *av, void *bv) -{ - NetSocket *a = (NetSocket *) av, *b = (NetSocket *) 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) -{ - NetSocket *b = (NetSocket *) 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) -{ - NetSocket *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) -{ - *canonicalname = NULL; - - if (host[0] == '/') { - *canonicalname = dupstr(host); - return unix_sock_addr(host); - } - - SockAddr *addr = snew(SockAddr); - memset(addr, 0, sizeof(SockAddr)); - addr->superfamily = UNRESOLVED; - addr->refcount = 1; - -#ifndef NO_IPV6 - /* - * Use getaddrinfo, as long as it's available. This should handle - * both IPv4 and IPv6 address literals, and hostnames, in one - * unified API. - */ - { - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : - address_family == ADDRTYPE_IPV6 ? AF_INET6 : - AF_UNSPEC); - hints.ai_flags = AI_CANONNAME; - hints.ai_socktype = SOCK_STREAM; - - /* strip [] on IPv6 address literals */ - char *trimmed_host = host_strduptrim(host); - int err = getaddrinfo(trimmed_host, NULL, &hints, &addr->ais); - sfree(trimmed_host); - - if (addr->ais) { - addr->superfamily = IP; - if (addr->ais->ai_canonname) - *canonicalname = dupstr(addr->ais->ai_canonname); - else - *canonicalname = dupstr(host); - } else { - addr->error = gai_strerror(err); - } - return addr; - } - -#else - /* - * Failing that (if IPv6 support was not compiled in), try the - * old-fashioned approach, which is to start by manually checking - * for an IPv4 literal and then use gethostbyname. - */ - unsigned long a = inet_addr(host); - if (a != (unsigned long) INADDR_NONE) { - addr->addresses = snew(unsigned long); - addr->naddresses = 1; - addr->addresses[0] = ntohl(a); - addr->superfamily = IP; - *canonicalname = dupstr(host); - return addr; - } - - struct hostent *h = gethostbyname(host); - if (h) { - addr->superfamily = IP; - - size_t n; - for (n = 0; h->h_addr_list[n]; n++); - addr->addresses = snewn(n, unsigned long); - addr->naddresses = n; - for (n = 0; n < addr->naddresses; n++) { - uint32_t a; - memcpy(&a, h->h_addr_list[n], sizeof(a)); - addr->addresses[n] = ntohl(a); - } - - *canonicalname = dupstr(h->h_name); - } else { - addr->error = hstrerror(h_errno); - } - return addr; -#endif -} - -SockAddr *sk_nonamelookup(const char *host) -{ - SockAddr *addr = snew(SockAddr); - addr->error = NULL; - addr->superfamily = UNRESOLVED; - strncpy(addr->hostname, host, lenof(addr->hostname)); - addr->hostname[lenof(addr->hostname)-1] = '\0'; -#ifndef NO_IPV6 - addr->ais = NULL; -#else - ret->addresses = NULL; -#endif - addr->refcount = 1; - return addr; -} - -static bool 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 - } -} - -/* - * This constructs a SockAddr that points at one specific sub-address - * of a parent SockAddr. The returned SockAddr does not own all its - * own memory: it points into the old one's data structures, so it - * MUST NOT be used after the old one is freed, and it MUST NOT be - * passed to sk_addr_free. (The latter is why it's returned by value - * rather than dynamically allocated - that should clue in anyone - * writing a call to it that something is weird about it.) - */ -static SockAddr sk_extractaddr_tmp( - SockAddr *addr, const SockAddrStep *step) -{ - SockAddr toret; - toret = *addr; /* structure copy */ - toret.refcount = 1; - - if (addr->superfamily == IP) { -#ifndef NO_IPV6 - toret.ais = step->ai; -#else - assert(SOCKADDR_FAMILY(addr, *step) == AF_INET); - toret.addresses += step->curraddr; -#endif - } - - return toret; -} - -bool sk_addr_needs_port(SockAddr *addr) -{ - if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) { - return false; - } else { - return true; - } -} - -bool 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 bool 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; - } -} - -bool sk_address_is_local(SockAddr *addr) -{ - if (addr->superfamily == UNRESOLVED) - return false; /* we don't know; assume not */ - else if (addr->superfamily == UNIX) - return true; - 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 - } -} - -bool 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 - unreachable("bad address family in sk_addrcopy"); -#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_net_plug(Socket *sock, Plug *p) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - Plug *ret = s->plug; - if (p) - s->plug = p; - return ret; -} - -static void sk_net_close(Socket *s); -static size_t sk_net_write(Socket *s, const void *data, size_t len); -static size_t sk_net_write_oob(Socket *s, const void *data, size_t len); -static void sk_net_write_eof(Socket *s); -static void sk_net_set_frozen(Socket *s, bool is_frozen); -static SocketPeerInfo *sk_net_peer_info(Socket *s); -static const char *sk_net_socket_error(Socket *s); - -static const SocketVtable NetSocket_sockvt = { - .plug = sk_net_plug, - .close = sk_net_close, - .write = sk_net_write, - .write_oob = sk_net_write_oob, - .write_eof = sk_net_write_eof, - .set_frozen = sk_net_set_frozen, - .socket_error = sk_net_socket_error, - .peer_info = sk_net_peer_info, -}; - -static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug) -{ - int sockfd = ctx.i; - NetSocket *s; - - /* - * Create NetSocket structure. - */ - s = snew(NetSocket); - s->sock.vt = &NetSocket_sockvt; - s->error = NULL; - s->plug = plug; - bufchain_init(&s->output_data); - s->writable = true; /* to start with */ - s->sending_oob = 0; - s->frozen = true; - s->localhost_only = false; /* unused, but best init anyway */ - s->pending_error = 0; - s->oobpending = false; - s->outgoingeof = EOF_NO; - s->incomingeof = false; - s->listener = false; - s->parent = s->child = NULL; - s->addr = NULL; - s->connected = true; - - s->s = sockfd; - - if (s->s < 0) { - s->error = strerror(errno); - return &s->sock; - } - - s->oobinline = false; - - uxsel_tell(s); - add234(sktree, s); - - return &s->sock; -} - -static int try_connect(NetSocket *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); - - { - SockAddr thisaddr = sk_extractaddr_tmp( - sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_TRYING, - &thisaddr, 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 = 1; - if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE, - (void *) &b, sizeof(b)) < 0) { - err = errno; - close(s); - goto ret; - } - } - - if (sock->nodelay && family != AF_UNIX) { - int b = 1; - if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, - (void *) &b, sizeof(b)) < 0) { - err = errno; - close(s); - goto ret; - } - } - - if (sock->keepalive) { - int b = 1; - 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(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: - unreachable("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 = true; - sock->writable = true; - - SockAddr thisaddr = sk_extractaddr_tmp(sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_SUCCESS, - &thisaddr, sock->port, NULL, 0); - } - - uxsel_tell(sock); - - ret: - - /* - * No matter what happened, put the socket back in the tree. - */ - add234(sktree, sock); - - if (err) { - SockAddr thisaddr = sk_extractaddr_tmp( - sock->addr, &sock->step); - plug_log(sock->plug, PLUGLOG_CONNECT_FAILED, - &thisaddr, sock->port, strerror(err), err); - } - return err; -} - -Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, - bool nodelay, bool keepalive, Plug *plug) -{ - NetSocket *s; - int err; - - /* - * Create NetSocket structure. - */ - s = snew(NetSocket); - s->sock.vt = &NetSocket_sockvt; - s->error = NULL; - s->plug = plug; - bufchain_init(&s->output_data); - s->connected = false; /* to start with */ - s->writable = false; /* to start with */ - s->sending_oob = 0; - s->frozen = false; - s->localhost_only = false; /* unused, but best init anyway */ - s->pending_error = 0; - s->parent = s->child = NULL; - s->oobpending = false; - s->outgoingeof = EOF_NO; - s->incomingeof = false; - s->listener = false; - s->addr = addr; - START_STEP(s->addr, s->step); - s->s = -1; - s->oobinline = oobinline; - s->nodelay = nodelay; - s->keepalive = keepalive; - s->privport = privport; - s->port = port; - - do { - err = try_connect(s); - } while (err && sk_nextaddr(s->addr, &s->step)); - - if (err) - s->error = strerror(err); - - return &s->sock; -} - -Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, - bool local_host_only, int orig_address_family) -{ - int fd; -#ifndef NO_IPV6 - struct addrinfo hints, *ai = NULL; - char portstr[6]; -#endif - union sockaddr_union u; - union sockaddr_union *addr; - int addrlen; - NetSocket *s; - int retcode; - int address_family; - int on = 1; - - /* - * Create NetSocket structure. - */ - s = snew(NetSocket); - s->sock.vt = &NetSocket_sockvt; - s->error = NULL; - s->plug = plug; - bufchain_init(&s->output_data); - s->writable = false; /* to start with */ - s->sending_oob = 0; - s->frozen = false; - s->localhost_only = local_host_only; - s->pending_error = 0; - s->parent = s->child = NULL; - s->oobpending = false; - s->outgoingeof = EOF_NO; - s->incomingeof = false; - s->listener = true; - s->addr = NULL; - s->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. - */ - fd = socket(address_family, SOCK_STREAM, 0); - -#ifndef NO_IPV6 - /* If the host doesn't support IPv6 try fallback to IPv4. */ - if (fd < 0 && address_family == AF_INET6) { - address_family = AF_INET; - fd = socket(address_family, SOCK_STREAM, 0); - } -#endif - - if (fd < 0) { - s->error = strerror(errno); - return &s->sock; - } - - cloexec(fd); - - s->oobinline = false; - - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, - (const char *)&on, sizeof(on)) < 0) { - s->error = strerror(errno); - close(fd); - return &s->sock; - } - - 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(fd, &addr->sa, addrlen); - -#ifndef NO_IPV6 - if (ai) - freeaddrinfo(ai); -#endif - - if (retcode < 0) { - close(fd); - s->error = strerror(errno); - return &s->sock; - } - - if (listen(fd, SOMAXCONN) < 0) { - close(fd); - s->error = strerror(errno); - return &s->sock; - } - -#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) { - NetSocket *other; - - other = container_of( - sk_newlistener(srcaddr, port, plug, - local_host_only, ADDRTYPE_IPV4), - NetSocket, sock); - - if (other) { - if (!other->error) { - other->parent = s; - s->child = other; - } else { - /* If we couldn't create a listening socket on IPv4 as well - * as IPv6, we must return an error overall. */ - close(fd); - sfree(s); - return &other->sock; - } - } - } -#endif - - s->s = fd; - - uxsel_tell(s); - add234(sktree, s); - - return &s->sock; -} - -static void sk_net_close(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - - if (s->child) - sk_net_close(&s->child->sock); - - bufchain_clear(&s->output_data); - - del234(sktree, s); - if (s->s >= 0) { - uxsel_del(s->s); - close(s->s); - } - if (s->addr) - sk_addr_free(s->addr); - delete_callbacks_for_context(s); - sfree(s); -} - -void *sk_getxdmdata(Socket *sock, int *lenp) -{ - NetSocket *s; - union sockaddr_union u; - socklen_t addrlen; - char *buf; - static unsigned int unix_addr = 0xFFFFFFFF; - - /* - * We must check that this socket really _is_ a NetSocket before - * downcasting it. - */ - if (sock->vt != &NetSocket_sockvt) - return NULL; /* failure */ - s = container_of(sock, NetSocket, sock); - - 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; -} - -void plug_closing_errno(Plug *plug, int error) -{ - PlugCloseType type = PLUGCLOSE_ERROR; - if (error == EPIPE) - type = PLUGCLOSE_BROKEN_PIPE; - plug_closing(plug, type, strerror(error)); -} - -/* - * Deal with socket errors detected in try_send(). - */ -static void socket_error_callback(void *vs) -{ - NetSocket *s = (NetSocket *)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_errno(s->plug, s->pending_error); -} - -/* - * The function which tries to send on a socket once it's deemed - * writable. - */ -void try_send(NetSocket *s) -{ - while (s->sending_oob || bufchain_size(&s->output_data) > 0) { - int nsent; - int err; - const void *data; - size_t len; - int urgentflag; - - if (s->sending_oob) { - urgentflag = MSG_OOB; - len = s->sending_oob; - data = &s->oobdata; - } else { - urgentflag = 0; - ptrlen bufdata = bufchain_prefix(&s->output_data); - data = bufdata.ptr; - len = bufdata.len; - } - nsent = send(s->s, data, len, urgentflag); - noise_ultralight(NOISE_SOURCE_IOLEN, 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 size_t sk_net_write(Socket *sock, const void *buf, size_t len) -{ - NetSocket *s = container_of(sock, NetSocket, 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 size_t sk_net_write_oob(Socket *sock, const void *buf, size_t len) -{ - NetSocket *s = container_of(sock, NetSocket, 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_net_write_eof(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, 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 void net_select_result(int fd, int event) -{ - int ret; - char buf[20480]; /* nice big buffer for plenty of speed */ - NetSocket *s; - bool atmark = true; - - /* Find the Socket structure */ - s = find234(sktree, &fd, cmpforsearch); - if (!s) - return; /* boggle */ - - noise_ultralight(NOISE_SOURCE_IOID, fd); - - switch (event) { - case SELECT_X: /* 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(NOISE_SOURCE_IOLEN, ret); - if (ret == 0) { - plug_closing_error(s->plug, "Internal networking trouble"); - } else if (ret < 0) { - plug_closing_errno(s->plug, errno); - } 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; - } - 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 SELECT_R: /* 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_net_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) { - int atmark_from_ioctl; - if (ioctl(s->s, SIOCATMARK, &atmark_from_ioctl) == 0) { - atmark = atmark_from_ioctl; - if (atmark) - s->oobpending = false; /* clear this indicator */ - } - } else - atmark = true; - - ret = recv(s->s, buf, s->oobpending ? 1 : sizeof(buf), 0); - noise_ultralight(NOISE_SOURCE_IOLEN, ret); - if (ret < 0) { - if (errno == EWOULDBLOCK) { - break; - } - } - if (ret < 0) { - plug_closing_errno(s->plug, errno); - } else if (0 == ret) { - s->incomingeof = true; /* stop trying to read now */ - uxsel_tell(s); - plug_closing_normal(s->plug); - } 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; - } - plug_receive(s->plug, atmark ? 0 : 1, buf, ret); - } - break; - case SELECT_W: /* writable */ - if (!s->connected) { - /* - * select/poll reports a socket as _writable_ when an - * asynchronous connect() attempt either completes or - * fails. So first we must find out which. - */ - { - int err; - socklen_t errlen = sizeof(err); - char *errmsg = NULL; - if (getsockopt(s->s, SOL_SOCKET, SO_ERROR, &err, &errlen)<0) { - errmsg = dupprintf("getsockopt(SO_ERROR): %s", - strerror(errno)); - err = errno; /* got to put something in here */ - } else if (err != 0) { - errmsg = dupstr(strerror(err)); - } - if (errmsg) { - /* - * The asynchronous connection attempt failed. - * Report the problem via plug_log, and try again - * with the next candidate address, if we have - * more than one. - */ - SockAddr thisaddr; - assert(s->addr); - - thisaddr = sk_extractaddr_tmp(s->addr, &s->step); - plug_log(s->plug, PLUGLOG_CONNECT_FAILED, - &thisaddr, s->port, errmsg, err); - - while (err && s->addr && sk_nextaddr(s->addr, &s->step)) { - err = try_connect(s); - } - if (err) { - plug_closing_errno(s->plug, err); - return; /* socket is now presumably defunct */ - } - if (!s->connected) - return; /* another async attempt in progress */ - } else { - /* - * The connection attempt succeeded. - */ - SockAddr thisaddr = sk_extractaddr_tmp(s->addr, &s->step); - plug_log(s->plug, PLUGLOG_CONNECT_SUCCESS, - &thisaddr, s->port, NULL, 0); - } - } - - /* - * If we get here, we've managed to make a connection. - */ - if (s->addr) { - sk_addr_free(s->addr); - s->addr = NULL; - } - s->connected = true; - s->writable = true; - uxsel_tell(s); - } else { - size_t bufsize_before, bufsize_after; - s->writable = true; - 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; - } -} - -/* - * 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_net_socket_error(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - return s->error; -} - -static void sk_net_set_frozen(Socket *sock, bool is_frozen) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - if (s->frozen == is_frozen) - return; - s->frozen = is_frozen; - uxsel_tell(s); -} - -static SocketPeerInfo *sk_net_peer_info(Socket *sock) -{ - NetSocket *s = container_of(sock, NetSocket, sock); - union sockaddr_union addr; - socklen_t addrlen = sizeof(addr); -#ifndef NO_IPV6 - char buf[INET6_ADDRSTRLEN]; -#endif - SocketPeerInfo *pi; - - if (getpeername(s->s, &addr.sa, &addrlen) < 0) - return NULL; - - pi = snew(SocketPeerInfo); - pi->addressfamily = ADDRTYPE_UNSPEC; - pi->addr_text = NULL; - pi->port = -1; - pi->log_text = NULL; - - if (addr.storage.ss_family == AF_INET) { - pi->addressfamily = ADDRTYPE_IPV4; - memcpy(pi->addr_bin.ipv4, &addr.sin.sin_addr, 4); - pi->port = ntohs(addr.sin.sin_port); - pi->addr_text = dupstr(inet_ntoa(addr.sin.sin_addr)); - pi->log_text = dupprintf("%s:%d", pi->addr_text, pi->port); - -#ifndef NO_IPV6 - } else if (addr.storage.ss_family == AF_INET6) { - pi->addressfamily = ADDRTYPE_IPV6; - memcpy(pi->addr_bin.ipv6, &addr.sin6.sin6_addr, 16); - pi->port = ntohs(addr.sin6.sin6_port); - pi->addr_text = dupstr( - inet_ntop(AF_INET6, &addr.sin6.sin6_addr, buf, sizeof(buf))); - pi->log_text = dupprintf("[%s]:%d", pi->addr_text, pi->port); -#endif - - } else if (addr.storage.ss_family == AF_UNIX) { - pi->addressfamily = ADDRTYPE_LOCAL; - - /* - * For Unix sockets, the source address is unlikely to be - * helpful, so we leave addr_txt NULL (and we certainly can't - * fill in port, obviously). Instead, we try SO_PEERCRED and - * try to get the source pid, and put that in the log text. - */ - 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); - pi->log_text = dupprintf("pid %d (%s:%s)", pid, - pw ? pw->pw_name : uidbuf, - gr ? gr->gr_name : gidbuf); - } - } else { - sfree(pi); - return NULL; - } - - return pi; -} - -int sk_net_get_fd(Socket *sock) -{ - /* This function is not fully general: it only works on NetSocket */ - if (sock->vt != &NetSocket_sockvt) - return -1; /* failure */ - NetSocket *s = container_of(sock, NetSocket, sock); - return s->s; -} - -static void uxsel_tell(NetSocket *s) -{ - int rwx = 0; - if (!s->pending_error) { - if (s->listener) { - rwx |= SELECT_R; /* read == accept */ - } else { - if (!s->connected) - rwx |= SELECT_W; /* write == connect */ - if (s->connected && !s->frozen && !s->incomingeof) - rwx |= SELECT_R | SELECT_X; - if (bufchain_size(&s->output_data)) - rwx |= SELECT_W; - } - } - uxsel_set(s->s, rwx, net_select_result); -} - -int net_service_lookup(const char *service) -{ - struct servent *se; - se = getservbyname(service, NULL); - if (se != NULL) - return ntohs(se->s_port); - else - return 0; -} - -char *get_hostname(void) -{ - size_t size = 0; - char *hostname = NULL; - do { - sgrowarray(hostname, size, size); - if ((gethostname(hostname, size) < 0) && (errno != ENAMETOOLONG)) { - sfree(hostname); - hostname = NULL; - break; - } - } while (strlen(hostname) >= size-1); - return hostname; -} - -SockAddr *platform_get_x11_unix_address(const char *sockpath, int displaynum) -{ - SockAddr *addr = snew(SockAddr); - int n; - - memset(addr, 0, sizeof *addr); - addr->superfamily = UNIX; - /* - * In special circumstances (notably Mac OS X Leopard), we'll - * have been passed an explicit Unix socket path. - */ - if (sockpath) { - n = snprintf(addr->hostname, sizeof addr->hostname, - "%s", sockpath); - } else { - n = snprintf(addr->hostname, sizeof addr->hostname, - "%s%d", X11_UNIX_PATH, displaynum); - } - - if (n < 0) - addr->error = "snprintf failed"; - else if (n >= sizeof addr->hostname) - addr->error = "X11 UNIX name too long"; - -#ifndef NO_IPV6 - addr->ais = NULL; -#else - ret->addresses = NULL; - ret->naddresses = 0; -#endif - addr->refcount = 1; - return addr; -} - -SockAddr *unix_sock_addr(const char *path) -{ - SockAddr *addr = snew(SockAddr); - int n; - - memset(addr, 0, sizeof *addr); - addr->superfamily = UNIX; - n = snprintf(addr->hostname, sizeof addr->hostname, "%s", path); - - if (n < 0) - addr->error = "snprintf failed"; - else if (n >= sizeof addr->hostname || - n >= sizeof(((struct sockaddr_un *)0)->sun_path)) - addr->error = "socket pathname too long"; - -#ifndef NO_IPV6 - addr->ais = NULL; -#else - ret->addresses = NULL; - ret->naddresses = 0; -#endif - addr->refcount = 1; - return addr; -} - -Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug) -{ - int fd; - union sockaddr_union u; - union sockaddr_union *addr; - int addrlen; - NetSocket *s; - int retcode; - - /* - * Create NetSocket structure. - */ - s = snew(NetSocket); - s->sock.vt = &NetSocket_sockvt; - s->error = NULL; - s->plug = plug; - bufchain_init(&s->output_data); - s->writable = false; /* to start with */ - s->sending_oob = 0; - s->frozen = false; - s->localhost_only = true; - s->pending_error = 0; - s->parent = s->child = NULL; - s->oobpending = false; - s->outgoingeof = EOF_NO; - s->incomingeof = false; - s->listener = true; - s->addr = listenaddr; - s->s = -1; - - assert(listenaddr->superfamily == UNIX); - - /* - * Open socket. - */ - fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) { - s->error = strerror(errno); - return &s->sock; - } - - cloexec(fd); - - s->oobinline = false; - - memset(&u, '\0', sizeof(u)); - u.su.sun_family = AF_UNIX; -#if __GNUC__ >= 8 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wstringop-truncation" -#endif // __GNUC__ >= 8 - strncpy(u.su.sun_path, listenaddr->hostname, sizeof(u.su.sun_path)-1); -#if __GNUC__ >= 8 -# pragma GCC diagnostic pop -#endif // __GNUC__ >= 8 - addr = &u; - addrlen = sizeof(u.su); - - if (unlink(u.su.sun_path) < 0 && errno != ENOENT) { - close(fd); - s->error = strerror(errno); - return &s->sock; - } - - retcode = bind(fd, &addr->sa, addrlen); - if (retcode < 0) { - close(fd); - s->error = strerror(errno); - return &s->sock; - } - - if (listen(fd, SOMAXCONN) < 0) { - close(fd); - s->error = strerror(errno); - return &s->sock; - } - - s->s = fd; - - uxsel_tell(s); - add234(sktree, s); - - return &s->sock; -} diff --git a/unix/no-gtk.c b/unix/no-gtk.c deleted file mode 100644 index 12565a1f7..000000000 --- a/unix/no-gtk.c +++ /dev/null @@ -1,11 +0,0 @@ -/* - * no-gtk.c: link into non-GUI Unix programs so that they can tell - * buildinfo about a lack of GTK. - */ - -#include "putty.h" - -char *buildinfo_gtk_version(void) -{ - return NULL; -} diff --git a/unix/noaskpass.c b/unix/noaskpass.c deleted file mode 100644 index de646c55f..000000000 --- a/unix/noaskpass.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Dummy (lack-of-)implementation of a GUI password/passphrase prompt. - */ - -#include "putty.h" - -void random_add_noise(NoiseSourceId source, const void *noise, int length) -{ - /* We have no keypress_prng here, so no need to implement this */ -} - -const bool buildinfo_gtk_relevant = false; - -char *gtk_askpass_main(const char *display, const char *wintitle, - const char *prompt, bool *success) -{ - *success = false; - return dupstr("this Pageant was built without GTK"); -} diff --git a/unix/noise.c b/unix/noise.c deleted file mode 100644 index 0fbf8c4db..000000000 --- a/unix/noise.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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 bool read_dev_urandom(char *buf, int len) -{ - int fd; - int ngot, ret; - - fd = open("/dev/urandom", O_RDONLY); - if (fd < 0) - return false; - - ngot = 0; - while (ngot < len) { - ret = read(fd, buf+ngot, len-ngot); - if (ret < 0) { - close(fd); - return false; - } - ngot += ret; - } - - close(fd); - - return true; -} - -/* - * 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; - bool got_dev_urandom = false; - - if (read_dev_urandom(buf, 32)) { - got_dev_urandom = true; - 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); -} - -/* - * 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(NOISE_SOURCE_MEMINFO, buf, ret); - close(fd); - } - if ((fd = open("/proc/stat", O_RDONLY)) >= 0) { - while ( (ret = read(fd, buf, sizeof(buf))) > 0) - random_add_noise(NOISE_SOURCE_STAT, buf, ret); - close(fd); - } - getrusage(RUSAGE_SELF, &rusage); - random_add_noise(NOISE_SOURCE_RUSAGE, &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(NoiseSourceId id, unsigned long data) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - random_add_noise(NOISE_SOURCE_TIME, &tv, sizeof(tv)); - random_add_noise(id, &data, sizeof(data)); -} - -uint64_t prng_reseed_time_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return tv.tv_sec * 1000 + tv.tv_usec / 1000; -} diff --git a/unix/osxlaunch.c b/unix/osxlaunch.c deleted file mode 100644 index d560df938..000000000 --- a/unix/osxlaunch.c +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Launcher program for OS X application bundles of PuTTY. - */ - -/* - * The 'gtk-mac-bundler' utility arranges to build an OS X application - * bundle containing a program compiled against the Quartz GTK - * backend. It does this by including all the necessary GTK shared - * libraries and data files inside the bundle as well as the binary. - * - * But the GTK program won't start up unless all those shared - * libraries etc are already pointed to by environment variables like - * GTK_PATH and PANGO_LIBDIR and things like that, which won't be set - * up when the bundle is launched. - * - * Hence, gtk-mac-bundler expects to install the program in the bundle - * under a name like 'Contents/MacOS/Program-bin'; and the file called - * 'Contents/MacOS/Program', which is the one actually executed when - * the bundle is launched, is a wrapper script that sets up the - * environment before running the actual GTK-using program. - * - * In our case, however, that's not good enough. pterm will want to - * launch subprocesses with general-purpose shell sessions in them, - * and those subprocesses _won't_ want the random stuff dumped in the - * environment by the gtk-mac-bundler standard wrapper script. So I - * have to provide my own wrapper, which has a more complicated job: - * not only setting up the environment for the GTK app, but also - * preserving all details of the _previous_ environment, so that when - * pterm forks off a subprocess to run in a terminal session, it can - * restore the environment that was in force before the wrapper - * started messing about. This source file implements that wrapper, - * and does it in C so as to make string processing more reliable and - * less annoying. - * - * My strategy for saving the old environment is to pick a prefix - * that's unused by anything currently in the environment; let's - * suppose it's "P" for this discussion. Any environment variable I - * overwrite, say "VAR", I will either set "PsVAR=old value", or - * "PuVAR=" ("s" and "u" for "set" and "unset"). Then I pass the - * prefix itself as a command-line argument to the main GTK - * application binary, which then knows how to restore the original - * environment in pterm subprocesses. - */ - -#include -#include -#include -#include -#include - -#if !defined __APPLE__ && !defined TEST_COMPILE_ON_LINUX -/* When we're not compiling for OS X, it's easier to just turn this - * program into a trivial hello-world by ifdef in the source than it - * is to remove it in the makefile edifice. */ -int main(int argc, char **argv) -{ - fprintf(stderr, "launcher does nothing on non-OSX platforms\n"); - return 1; -} -#else /* __APPLE__ */ - -#include -#include - -#ifdef __APPLE__ -#include -#else -/* For Linux, a bodge to let as much of this code still run as - * possible, so that you can run it under friendly debugging tools - * like valgrind. */ -int _NSGetExecutablePath(char *out, uint32_t *outlen) -{ - static const char toret[] = "/proc/self/exe"; - if (out != NULL && *outlen < sizeof(toret)) - return -1; - *outlen = sizeof(toret); - if (out) - memcpy(out, toret, sizeof(toret)); - return 0; -} -#endif - -/* ---------------------------------------------------------------------- - * Find an alphabetic prefix unused by any environment variable name. - */ - -/* - * This linked-list based system is a bit overkill, but I enjoy an - * algorithmic challenge. We essentially do an incremental radix sort - * of all the existing environment variable names: initially divide - * them into 26 buckets by their first letter (discarding those that - * don't have a letter at that position), then subdivide each bucket - * in turn into 26 sub-buckets, and so on. We maintain each bucket as - * a linked list, and link their heads together into a secondary list - * that functions as a queue (meaning that we go breadth-first, - * processing all the buckets of a given depth before moving on to the - * next depth down). At any stage, if we find one of our 26 - * sub-buckets is empty, that's our unused prefix. - * - * The running time is O(number of strings * length of output), and I - * doubt it's possible to do better. - */ - -#define FANOUT 26 -int char_index(int ch) -{ - if (ch >= 'A' && ch <= 'Z') - return ch - 'A'; - else if (ch >= 'a' && ch <= 'z') - return ch - 'a'; - else - return -1; -} - -struct bucket { - int prefixlen; - struct bucket *next_bucket; - struct node *first_node; -}; - -struct node { - const char *string; - int len, prefixlen; - struct node *next; -}; - -struct node *new_node(struct node *prev_head, const char *string, int len) -{ - struct node *ret = (struct node *)malloc(sizeof(struct node)); - - if (!ret) { - fprintf(stderr, "out of memory\n"); - exit(1); - } - - ret->next = prev_head; - ret->string = string; - ret->len = len; - - return ret; -} - -char *get_unused_env_prefix(void) -{ - struct bucket *qhead, *qtail; - extern char **environ; - char **e; - - qhead = (struct bucket *)malloc(sizeof(struct bucket)); - if (!qhead) { - fprintf(stderr, "out of memory\n"); - exit(1); - } - qhead->prefixlen = 0; - qhead->first_node = NULL; - qhead->next_bucket = NULL; - for (e = environ; *e; e++) - qhead->first_node = new_node(qhead->first_node, *e, strcspn(*e, "=")); - - qtail = qhead; - while (1) { - struct bucket *buckets[FANOUT]; - struct node *bucketnode; - int i, index; - - for (i = 0; i < FANOUT; i++) { - buckets[i] = (struct bucket *)malloc(sizeof(struct bucket)); - if (!buckets[i]) { - fprintf(stderr, "out of memory\n"); - exit(1); - } - buckets[i]->prefixlen = qhead->prefixlen + 1; - buckets[i]->first_node = NULL; - qtail->next_bucket = buckets[i]; - qtail = buckets[i]; - } - qtail->next_bucket = NULL; - - bucketnode = qhead->first_node; - while (bucketnode) { - struct node *node = bucketnode; - bucketnode = bucketnode->next; - - if (node->len <= qhead->prefixlen) - continue; - index = char_index(node->string[qhead->prefixlen]); - if (!(index >= 0 && index < FANOUT)) - continue; - node->prefixlen++; - node->next = buckets[index]->first_node; - buckets[index]->first_node = node; - } - - for (i = 0; i < FANOUT; i++) { - if (!buckets[i]->first_node) { - char *ret = malloc(qhead->prefixlen + 2); - if (!ret) { - fprintf(stderr, "out of memory\n"); - exit(1); - } - memcpy(ret, qhead->first_node->string, qhead->prefixlen); - ret[qhead->prefixlen] = i + 'A'; - ret[qhead->prefixlen + 1] = '\0'; - - /* This would be where we freed everything, if we - * didn't know it didn't matter because we were - * imminently going to exec another program */ - return ret; - } - } - - qhead = qhead->next_bucket; - } -} - -/* ---------------------------------------------------------------------- - * Get the pathname of this executable, so we can locate the rest of - * the app bundle relative to it. - */ - -/* - * There are several ways to try to retrieve the pathname to the - * running executable: - * - * (a) Declare main() as taking four arguments int main(int argc, char - * **argv, char **envp, char **apple); and look at apple[0]. - * - * (b) Use sysctl(KERN_PROCARGS) to get the process arguments for the - * current pid. This involves two steps: - * - sysctl(mib, 2, &argmax, &argmax_size, NULL, 0) - * + mib is an array[2] of int containing - * { CTL_KERN, KERN_ARGMAX } - * + argmax is an int - * + argmax_size is a size_t initialised to sizeof(argmax) - * + returns in argmax the amount of memory you need for the next - * call. - * - sysctl(mib, 3, procargs, &procargs_size, NULL, 0) - * + mib is an array[3] of int containing - * { CTL_KERN, KERN_PROCARGS, current pid } - * + procargs is a buffer of size 'argmax' - * + procargs_size is a size_t initialised to argmax - * + returns in the procargs buffer a collection of - * zero-terminated strings of which the first is the program - * name. - * - * (c) Call _NSGetExecutablePath, once to find out the needed buffer - * size and again to fetch the actual path. - * - * (d) Use Objective-C and Cocoa and call - * [[[NSProcessInfo processInfo] arguments] objectAtIndex: 0]. - * - * So, how do those work in various cases? Experiments show: - * - * - if you run the program as 'binary' (or whatever you called it) - * and rely on the shell to search your PATH, all four methods - * return a sensible-looking absolute pathname. - * - * - if you run the program as './binary', (a) and (b) return just - * "./binary", which has a particularly bad race condition if you - * try to convert it into an absolute pathname using realpath(3). - * (c) returns "/full/path/to/./binary", which still needs - * realpath(3)ing to get rid of that ".", but at least it's - * _trying_ to be fully qualified. (d) returns - * "/full/path/to/binary" - full marks! - * + Similar applies if you run it via a more interesting relative - * path such as one with a ".." in: (c) gives you an absolute - * path containing a ".." element, whereas (d) has sorted that - * out. - * - * - if you run the program via a path with a symlink on, _none_ of - * these options successfully returns a path without the symlink. - * - * That last point suggests that even (d) is not a perfect solution on - * its own, and you'll have to realpath() whatever you get back from - * it regardless. - * - * And (d) is extra inconvenient because it returns an NSString, which - * is implicitly Unicode, so it's not clear how you turn that back - * into a char * representing a correct Unix pathname (what charset - * should you interpret it in?). Also because you have to bring in all - * of ObjC and Cocoa, which for a low-level Unix API client like this - * seems like overkill. - * - * So my conclusion is that (c) is most practical for these purposes. - */ - -char *get_program_path(void) -{ - char *our_path; - uint32_t pathlen = 0; - _NSGetExecutablePath(NULL, &pathlen); - our_path = malloc(pathlen); - if (!our_path) { - fprintf(stderr, "out of memory\n"); - exit(1); - } - if (_NSGetExecutablePath(our_path, &pathlen)) { - fprintf(stderr, "unable to get launcher executable path\n"); - exit(1); - } - - /* OS X guarantees to malloc the return value if we pass NULL */ - char *our_real_path = realpath(our_path, NULL); - if (!our_real_path) { - fprintf(stderr, "realpath failed\n"); - exit(1); - } - - free(our_path); - return our_real_path; -} - -/* ---------------------------------------------------------------------- - * Wrapper on dirname(3) which mallocs its return value to whatever - * size is needed. - */ - -char *dirname_wrapper(const char *path) -{ - char *path_copy = malloc(strlen(path) + 1); - if (!path_copy) { - fprintf(stderr, "out of memory\n"); - exit(1); - } - strcpy(path_copy, path); - char *ret_orig = dirname(path_copy); - char *ret = malloc(strlen(ret_orig) + 1); - if (!ret) { - fprintf(stderr, "out of memory\n"); - exit(1); - } - strcpy(ret, ret_orig); - free(path_copy); - return ret; -} - -/* ---------------------------------------------------------------------- - * mallocing string concatenation function. - */ - -char *alloc_cat(const char *str1, const char *str2) -{ - int len1 = strlen(str1), len2 = strlen(str2); - char *ret = malloc(len1 + len2 + 1); - if (!ret) { - fprintf(stderr, "out of memory\n"); - exit(1); - } - strcpy(ret, str1); - strcpy(ret + len1, str2); - return ret; -} - -/* ---------------------------------------------------------------------- - * Overwrite an environment variable, preserving the old one for the - * real app to restore. - */ -void setenv_wrap(const char *name, const char *value) -{ -#ifdef DEBUG_OSXLAUNCH - printf("setenv(\"%s\",\"%s\")\n", name, value); -#endif - setenv(name, value, 1); -} - -void unsetenv_wrap(const char *name) -{ -#ifdef DEBUG_OSXLAUNCH - printf("unsetenv(\"%s\")\n", name); -#endif - unsetenv(name); -} - -char *prefix, *prefixset, *prefixunset; -void overwrite_env(const char *name, const char *value) -{ - const char *oldvalue = getenv(name); - if (oldvalue) { - setenv_wrap(alloc_cat(prefixset, name), oldvalue); - } else { - setenv_wrap(alloc_cat(prefixunset, name), ""); - } - if (value) - setenv_wrap(name, value); - else - unsetenv_wrap(name); -} - -/* ---------------------------------------------------------------------- - * Main program. - */ - -int main(int argc, char **argv) -{ - prefix = get_unused_env_prefix(); - prefixset = alloc_cat(prefix, "s"); - prefixunset = alloc_cat(prefix, "u"); - -#ifdef DEBUG_OSXLAUNCH - printf("Environment prefixes: main=\"%s\", set=\"%s\", unset=\"%s\"\n", - prefix, prefixset, prefixunset); -#endif - - char *prog_path = get_program_path(); // /Contents/MacOS/ - char *macos = dirname_wrapper(prog_path); // /Contents/MacOS - char *contents = dirname_wrapper(macos); // /Contents -// char *bundle = dirname_wrapper(contents); // - char *resources = alloc_cat(contents, "/Resources"); -// char *bin = alloc_cat(resources, "/bin"); - char *etc = alloc_cat(resources, "/etc"); - char *lib = alloc_cat(resources, "/lib"); - char *share = alloc_cat(resources, "/share"); - char *xdg = alloc_cat(etc, "/xdg"); -// char *gtkrc = alloc_cat(etc, "/gtk-2.0/gtkrc"); - char *locale = alloc_cat(share, "/locale"); - char *realbin = alloc_cat(prog_path, "-bin"); - -// overwrite_env("DYLD_LIBRARY_PATH", lib); - overwrite_env("XDG_CONFIG_DIRS", xdg); - overwrite_env("XDG_DATA_DIRS", share); - overwrite_env("GTK_DATA_PREFIX", resources); - overwrite_env("GTK_EXE_PREFIX", resources); - overwrite_env("GTK_PATH", resources); - overwrite_env("PANGO_LIBDIR", lib); - overwrite_env("PANGO_SYSCONFDIR", etc); - overwrite_env("I18NDIR", locale); - overwrite_env("LANG", NULL); - overwrite_env("LC_MESSAGES", NULL); - overwrite_env("LC_MONETARY", NULL); - overwrite_env("LC_COLLATE", NULL); - - char **new_argv = malloc((argc + 16) * sizeof(const char *)); - if (!new_argv) { - fprintf(stderr, "out of memory\n"); - exit(1); - } - int j = 0; - new_argv[j++] = realbin; -#ifdef DEBUG_OSXLAUNCH - printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]); -#endif - { - int i = 1; - if (i < argc && !strncmp(argv[i], "-psn_", 5)) - i++; - - for (; i < argc; i++) { - new_argv[j++] = argv[i]; -#ifdef DEBUG_OSXLAUNCH - printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]); -#endif - } - } - new_argv[j++] = prefix; -#ifdef DEBUG_OSXLAUNCH - printf("argv[%d] = \"%s\"\n", j-1, new_argv[j-1]); -#endif - new_argv[j++] = NULL; - -#ifdef DEBUG_OSXLAUNCH - printf("executing \"%s\"\n", realbin); -#endif - execv(realbin, new_argv); - perror("execv"); - free(new_argv); - free(contents); - free(macos); - return 127; -} - -#endif /* __APPLE__ */ diff --git a/unix/pageant.c b/unix/pageant.c deleted file mode 100644 index c77abbac3..000000000 --- a/unix/pageant.c +++ /dev/null @@ -1,1532 +0,0 @@ -/* - * Unix Pageant, more or less similar to ssh-agent. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "misc.h" -#include "pageant.h" - -void cmdline_error(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - console_print_error_msg_fmt_v("pageant", fmt, ap); - va_end(ap); - exit(1); -} - -static void setup_sigchld_handler(void); - -typedef enum RuntimePromptType { - RTPROMPT_UNAVAILABLE, - RTPROMPT_DEBUG, - RTPROMPT_GUI, -} RuntimePromptType; - -static const char *progname; - -struct uxpgnt_client { - FILE *logfp; - strbuf *prompt_buf; - RuntimePromptType prompt_type; - bool prompt_active; - PageantClientDialogId *dlgid; - int passphrase_fd; - int termination_pid; - - PageantListenerClient plc; -}; - -static void uxpgnt_log(PageantListenerClient *plc, const char *fmt, va_list ap) -{ - struct uxpgnt_client *upc = container_of(plc, struct uxpgnt_client, plc); - - if (!upc->logfp) - return; - - fprintf(upc->logfp, "pageant: "); - vfprintf(upc->logfp, fmt, ap); - fprintf(upc->logfp, "\n"); -} - -static int make_pipe_to_askpass(const char *msg) -{ - int pipefds[2]; - - setup_sigchld_handler(); - - if (pipe(pipefds) < 0) - return -1; - - pid_t pid = fork(); - if (pid < 0) { - close(pipefds[0]); - close(pipefds[1]); - return -1; - } - - if (pid == 0) { - const char *args[5] = { - progname, "--gui-prompt", "--askpass", msg, NULL - }; - - dup2(pipefds[1], 1); - cloexec(pipefds[0]); - cloexec(pipefds[1]); - - /* - * See comment in fork_and_exec_self() in main-gtk-simple.c. - */ - execv("/proc/self/exe", (char **)args); - execvp(progname, (char **)args); - perror("exec"); - _exit(127); - } - - close(pipefds[1]); - return pipefds[0]; -} - -static bool uxpgnt_ask_passphrase( - PageantListenerClient *plc, PageantClientDialogId *dlgid, - const char *comment) -{ - struct uxpgnt_client *upc = container_of(plc, struct uxpgnt_client, plc); - - assert(!upc->dlgid); /* Pageant core should be serialising requests */ - - char *msg = dupprintf( - "A client of Pageant wants to use the following encrypted key:\n" - "%s\n" - "If you intended this, enter the passphrase to decrypt the key.", - comment); - - switch (upc->prompt_type) { - case RTPROMPT_UNAVAILABLE: - sfree(msg); - return false; - - case RTPROMPT_GUI: - upc->passphrase_fd = make_pipe_to_askpass(msg); - sfree(msg); - if (upc->passphrase_fd < 0) - return false; /* something went wrong */ - break; - - case RTPROMPT_DEBUG: - fprintf(upc->logfp, "pageant passphrase request: %s\n", msg); - sfree(msg); - break; - } - - upc->prompt_active = true; - upc->dlgid = dlgid; - return true; -} - -static void passphrase_done(struct uxpgnt_client *upc, bool success) -{ - PageantClientDialogId *dlgid = upc->dlgid; - upc->dlgid = NULL; - upc->prompt_active = false; - - if (upc->logfp) - fprintf(upc->logfp, "pageant passphrase response: %s\n", - success ? "success" : "failure"); - - if (success) - pageant_passphrase_request_success( - dlgid, ptrlen_from_strbuf(upc->prompt_buf)); - else - pageant_passphrase_request_refused(dlgid); - - strbuf_free(upc->prompt_buf); - upc->prompt_buf = strbuf_new_nm(); -} - -static const PageantListenerClientVtable uxpgnt_vtable = { - .log = uxpgnt_log, - .ask_passphrase = uxpgnt_ask_passphrase, -}; - -/* - * More stubs. - */ -void random_save_seed(void) {} -void random_destroy_seed(void) {} -char *platform_default_s(const char *name) { return NULL; } -bool platform_default_b(const char *name, bool def) { return def; } -int platform_default_i(const char *name, int def) { return def; } -FontSpec *platform_default_fontspec(const char *name) { return fontspec_new_default(); } -Filename *platform_default_filename(const char *name) { return filename_from_str(""); } -char *x_get_default(const char *key) { return NULL; } - -/* - * Short description of parameters. - */ -static void usage(void) -{ - printf("Pageant: SSH agent\n"); - printf("%s\n", ver); - printf("Usage: pageant [[--encrypted] key files]\n"); - printf(" pageant [[--encrypted] key files] --exec [args]\n"); - printf(" pageant -a [--encrypted] [key files]\n"); - printf(" pageant -d [key identifiers]\n"); - printf(" pageant -D\n"); - printf(" pageant -r [key identifiers]\n"); - printf(" pageant -R\n"); - printf(" pageant --public [key identifiers]\n"); - printf(" pageant ( --public-openssh | -L ) [key identifiers]\n"); - printf(" pageant -l [-E fptype]\n"); - printf("Lifetime options, for running Pageant as an agent:\n"); - printf(" -X run with the lifetime of the X server\n"); - printf(" -T run with the lifetime of the controlling tty\n"); - printf(" --permanent run permanently\n"); - printf(" --debug run in debugging mode, without forking\n"); - printf(" --exec run with the lifetime of that command\n"); - printf("Client options, for talking to an existing agent:\n"); - printf(" -a add key(s) to the existing agent\n"); - printf(" -l list currently loaded key fingerprints and comments\n"); - printf(" --public print public keys in RFC 4716 format\n"); - printf(" --public-openssh, -L print public keys in OpenSSH format\n"); - printf(" -d delete key(s) from the agent\n"); - printf(" -D delete all keys from the agent\n"); - printf(" -r re-encrypt keys in the agent (forget cleartext)\n"); - printf(" -R re-encrypt all possible keys in the agent\n"); - printf("Other options:\n"); - printf(" -v verbose mode (in agent mode)\n"); - printf(" -s -c force POSIX or C shell syntax (in agent mode)\n"); - printf(" --symlink path create symlink to socket (in agent mode)\n"); - printf(" --encrypted when adding keys, don't decrypt\n"); - printf(" -E alg, --fptype alg fingerprint type for -l (sha256, md5)\n"); - printf(" --tty-prompt force tty-based passphrase prompt\n"); - printf(" --gui-prompt force GUI-based passphrase prompt\n"); - printf(" --askpass behave like a standalone askpass program\n"); - exit(1); -} - -static void version(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("pageant: %s\n%s\n", ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); -} - -void keylist_update(void) -{ - /* Nothing needs doing in Unix Pageant */ -} - -#define PAGEANT_DIR_PREFIX "/tmp/pageant" - -static bool time_to_die = false; - -/* - * These functions are part of the plug for our connection to the X - * display, so they do get called. They needn't actually do anything, - * except that x11_closing has to signal back to the main loop that - * it's time to terminate. - */ -static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) {} -static void x11_receive(Plug *plug, int urgent, const char *data, size_t len) {} -static void x11_sent(Plug *plug, size_t bufsize) {} -static void x11_closing(Plug *plug, PlugCloseType type, const char *error_msg) -{ - time_to_die = true; -} -struct X11Connection { - Plug plug; -}; - -static char *socketname; -static enum { SHELL_AUTO, SHELL_SH, SHELL_CSH } shell_type = SHELL_AUTO; -void pageant_print_env(int pid) -{ - if (shell_type == SHELL_AUTO) { - /* Same policy as OpenSSH: if $SHELL ends in "csh" then assume - * it's csh-shaped. */ - const char *shell = getenv("SHELL"); - if (shell && strlen(shell) >= 3 && - !strcmp(shell + strlen(shell) - 3, "csh")) - shell_type = SHELL_CSH; - else - shell_type = SHELL_SH; - } - - /* - * These shell snippets could usefully pay some attention to - * escaping of interesting characters. I don't think it causes a - * problem at the moment, because the pathnames we use are so - * utterly boring, but it's a lurking bug waiting to happen once - * a bit more flexibility turns up. - */ - - switch (shell_type) { - case SHELL_SH: - printf("SSH_AUTH_SOCK=%s; export SSH_AUTH_SOCK;\n" - "SSH_AGENT_PID=%d; export SSH_AGENT_PID;\n", - socketname, pid); - break; - case SHELL_CSH: - printf("setenv SSH_AUTH_SOCK %s;\n" - "setenv SSH_AGENT_PID %d;\n", - socketname, pid); - break; - case SHELL_AUTO: - unreachable("SHELL_AUTO should have been eliminated by now"); - break; - } -} - -void pageant_fork_and_print_env(bool retain_tty) -{ - pid_t pid = fork(); - if (pid == -1) { - perror("fork"); - exit(1); - } else if (pid != 0) { - pageant_print_env(pid); - exit(0); - } - - /* - * Having forked off, we now daemonise ourselves as best we can. - * It's good practice in general to setsid() ourself out of any - * process group we didn't want to be part of, and to chdir("/") - * to avoid holding any directories open that we don't need in - * case someone wants to umount them; also, we should definitely - * close standard output (because it will very likely be pointing - * at a pipe from which some parent process is trying to read our - * environment variable dump, so if we hold open another copy of - * it then that process will never finish reading). We close - * standard input too on general principles, but not standard - * error, since we might need to shout a panicky error message - * down that one. - */ - if (chdir("/") < 0) { - /* should there be an error condition, nothing we can do about - * it anyway */ - } - close(0); - close(1); - if (retain_tty) { - /* Get out of our previous process group, to avoid being - * blasted by passing signals. But keep our controlling tty, - * so we can keep checking to see if we still have one. */ -#if HAVE_NULLARY_SETPGRP - setpgrp(); -#elif HAVE_BINARY_SETPGRP - setpgrp(0, 0); -#endif - } else { - /* Do that, but also leave our entire session and detach from - * the controlling tty (if any). */ - setsid(); - } -} - -static int signalpipe[2] = { -1, -1 }; - -static void sigchld(int signum) -{ - if (write(signalpipe[1], "x", 1) <= 0) - /* not much we can do about it */; -} - -static void setup_sigchld_handler(void) -{ - if (signalpipe[0] >= 0) - return; - - /* - * Set up the pipe we'll use to tell us about SIGCHLD. - */ - if (pipe(signalpipe) < 0) { - perror("pipe"); - exit(1); - } - putty_signal(SIGCHLD, sigchld); -} - -#define TTY_LIFE_POLL_INTERVAL (TICKSPERSEC * 30) -static void *dummy_timer_ctx; -static void tty_life_timer(void *ctx, unsigned long now) -{ - schedule_timer(TTY_LIFE_POLL_INTERVAL, tty_life_timer, &dummy_timer_ctx); -} - -typedef enum { - KEYACT_AGENT_LOAD, - KEYACT_AGENT_LOAD_ENCRYPTED, - KEYACT_CLIENT_BASE, - KEYACT_CLIENT_ADD = KEYACT_CLIENT_BASE, - KEYACT_CLIENT_ADD_ENCRYPTED, - KEYACT_CLIENT_DEL, - KEYACT_CLIENT_DEL_ALL, - KEYACT_CLIENT_LIST, - KEYACT_CLIENT_PUBLIC_OPENSSH, - KEYACT_CLIENT_PUBLIC, - KEYACT_CLIENT_SIGN, - KEYACT_CLIENT_REENCRYPT, - KEYACT_CLIENT_REENCRYPT_ALL, -} keyact; -struct cmdline_key_action { - struct cmdline_key_action *next; - keyact action; - const char *filename; -}; - -bool is_agent_action(keyact action) -{ - return action < KEYACT_CLIENT_BASE; -} - -static struct cmdline_key_action *keyact_head = NULL, *keyact_tail = NULL; -static uint32_t sign_flags = 0; - -void add_keyact(keyact action, const char *filename) -{ - struct cmdline_key_action *a = snew(struct cmdline_key_action); - a->action = action; - a->filename = filename; - a->next = NULL; - if (keyact_tail) - keyact_tail->next = a; - else - keyact_head = a; - keyact_tail = a; -} - -bool have_controlling_tty(void) -{ - int fd = open("/dev/tty", O_RDONLY); - if (fd < 0) { - if (errno != ENXIO) { - perror("/dev/tty: open"); - exit(1); - } - return false; - } else { - close(fd); - return true; - } -} - -static char **exec_args = NULL; -static enum { - LIFE_UNSPEC, LIFE_X11, LIFE_TTY, LIFE_DEBUG, LIFE_PERM, LIFE_EXEC -} life = LIFE_UNSPEC; -static const char *display = NULL; -static enum { - PROMPT_UNSPEC, PROMPT_TTY, PROMPT_GUI -} prompt_type = PROMPT_UNSPEC; -static FingerprintType key_list_fptype = SSH_FPTYPE_DEFAULT; - -static char *askpass_tty(const char *prompt) -{ - prompts_t *p = new_prompts(); - p->to_server = false; - p->from_server = false; - p->name = dupstr("Pageant passphrase prompt"); - add_prompt(p, dupcat(prompt, ": "), false); - SeatPromptResult spr = console_get_userpass_input(p); - assert(spr.kind != SPRK_INCOMPLETE); - - if (spr.kind == SPRK_USER_ABORT) { - free_prompts(p); - return NULL; - } else if (spr.kind == SPRK_SW_ABORT) { - free_prompts(p); - char *err = spr_get_error_message(spr); - fprintf(stderr, "pageant: unable to read passphrase: %s", err); - sfree(err); - return NULL; - } else { - char *passphrase = prompt_get_result(p->prompts[0]); - free_prompts(p); - return passphrase; - } -} - -static char *askpass_gui(const char *prompt) -{ - char *passphrase; - bool success; - - passphrase = gtk_askpass_main( - display, "Pageant passphrase prompt", prompt, &success); - if (!success) { - /* return value is error message */ - fprintf(stderr, "%s\n", passphrase); - sfree(passphrase); - passphrase = NULL; - } - return passphrase; -} - -static char *askpass(const char *prompt) -{ - if (prompt_type == PROMPT_TTY) { - if (!have_controlling_tty()) { - fprintf(stderr, "no controlling terminal available " - "for passphrase prompt\n"); - return NULL; - } - return askpass_tty(prompt); - } - - if (prompt_type == PROMPT_GUI) { - if (!display) { - fprintf(stderr, "no graphical display available " - "for passphrase prompt\n"); - return NULL; - } - return askpass_gui(prompt); - } - - if (have_controlling_tty()) { - return askpass_tty(prompt); - } else if (display) { - return askpass_gui(prompt); - } else { - fprintf(stderr, "no way to read a passphrase without tty or " - "X display\n"); - return NULL; - } -} - -static bool unix_add_keyfile(const char *filename_str, bool add_encrypted) -{ - Filename *filename = filename_from_str(filename_str); - int status; - bool ret; - char *err; - - ret = true; - - /* - * Try without a passphrase. - */ - status = pageant_add_keyfile(filename, NULL, &err, add_encrypted); - if (status == PAGEANT_ACTION_OK) { - goto cleanup; - } else if (status == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: %s: %s\n", filename_str, err); - ret = false; - goto cleanup; - } - - /* - * And now try prompting for a passphrase. - */ - while (1) { - char *prompt = dupprintf( - "Enter passphrase to load key '%s'", err); - char *passphrase = askpass(prompt); - sfree(err); - sfree(prompt); - err = NULL; - if (!passphrase) - break; - - status = pageant_add_keyfile(filename, passphrase, &err, - add_encrypted); - - smemclr(passphrase, strlen(passphrase)); - sfree(passphrase); - passphrase = NULL; - - if (status == PAGEANT_ACTION_OK) { - goto cleanup; - } else if (status == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: %s: %s\n", filename_str, err); - ret = false; - goto cleanup; - } - } - - cleanup: - sfree(err); - filename_free(filename); - return ret; -} - -void key_list_callback(void *ctx, char **fingerprints, const char *comment, - uint32_t ext_flags, struct pageant_pubkey *key) -{ - const char *mode = ""; - if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) - mode = " (encrypted)"; - else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) - mode = " (re-encryptable)"; - - FingerprintType this_type = - ssh2_pick_fingerprint(fingerprints, key_list_fptype); - printf("%s %s%s\n", fingerprints[this_type], comment, mode); -} - -struct key_find_ctx { - const char *string; - bool match_fp, match_comment; - bool match_fptypes[SSH_N_FPTYPES]; - struct pageant_pubkey *found; - int nfound; -}; - -static bool match_fingerprint_string( - const char *string_orig, char **fingerprints, - const struct key_find_ctx *ctx) -{ - const char *hash; - - for (unsigned fptype = 0; fptype < SSH_N_FPTYPES; fptype++) { - if (!ctx->match_fptypes[fptype]) - continue; - - const char *fingerprint = fingerprints[fptype]; - if (!fingerprint) - continue; - - /* Find the hash in the fingerprint string. It'll be the word - * at the end. */ - hash = strrchr(fingerprint, ' '); - assert(hash); - hash++; - - const char *string = string_orig; - bool case_sensitive; - const char *ignore_chars = ""; - - switch (fptype) { - case SSH_FPTYPE_MD5: - case SSH_FPTYPE_MD5_CERT: - /* MD5 fingerprints are in hex, so disregard case differences. */ - case_sensitive = false; - /* And we don't really need to force the user to type the - * colons in between the digits, which are always the - * same. */ - ignore_chars = ":"; - break; - case SSH_FPTYPE_SHA256: - case SSH_FPTYPE_SHA256_CERT: - /* Skip over the "SHA256:" prefix, which we don't really - * want to force the user to type. On the other hand, - * tolerate it on the input string. */ - assert(strstartswith(hash, "SHA256:")); - hash += 7; - if (strstartswith(string, "SHA256:")) - string += 7; - /* SHA256 fingerprints are base64, which is intrinsically - * case sensitive. */ - case_sensitive = true; - break; - } - - /* Now see if the search string is a prefix of the full hash, - * neglecting colons and (where appropriate) case differences. */ - while (1) { - string += strspn(string, ignore_chars); - hash += strspn(hash, ignore_chars); - if (!*string) - return true; - char sc = *string, hc = *hash; - if (!case_sensitive) { - sc = tolower((unsigned char)sc); - hc = tolower((unsigned char)hc); - } - if (sc != hc) - break; - string++; - hash++; - } - } - - return false; -} - -void key_find_callback(void *vctx, char **fingerprints, - const char *comment, uint32_t ext_flags, - struct pageant_pubkey *key) -{ - struct key_find_ctx *ctx = (struct key_find_ctx *)vctx; - - if ((ctx->match_comment && !strcmp(ctx->string, comment)) || - (ctx->match_fp && match_fingerprint_string(ctx->string, fingerprints, - ctx))) { - if (!ctx->found) - ctx->found = pageant_pubkey_copy(key); - ctx->nfound++; - } -} - -struct pageant_pubkey *find_key(const char *string, char **retstr) -{ - struct key_find_ctx ctx[1]; - struct pageant_pubkey key_in, *key_ret; - bool try_file = true, try_fp = true, try_comment = true; - bool file_errors = false; - bool try_all_fptypes = true; - FingerprintType fptype = SSH_FPTYPE_DEFAULT; - - /* - * Trim off disambiguating prefixes telling us how to interpret - * the provided string. - */ - if (!strncmp(string, "file:", 5)) { - string += 5; - try_fp = false; - try_comment = false; - file_errors = true; /* also report failure to load the file */ - } else if (!strncmp(string, "comment:", 8)) { - string += 8; - try_file = false; - try_fp = false; - } else if (!strncmp(string, "fp:", 3)) { - string += 3; - try_file = false; - try_comment = false; - } else if (!strncmp(string, "fingerprint:", 12)) { - string += 12; - try_file = false; - try_comment = false; - } else if (!strnicmp(string, "md5:", 4)) { - string += 4; - try_file = false; - try_comment = false; - try_all_fptypes = false; - fptype = SSH_FPTYPE_MD5; - } else if (!strncmp(string, "sha256:", 7)) { - string += 7; - try_file = false; - try_comment = false; - try_all_fptypes = false; - fptype = SSH_FPTYPE_SHA256; - } else if (!strnicmp(string, "md5-cert:", 9)) { - string += 9; - try_file = false; - try_comment = false; - try_all_fptypes = false; - fptype = SSH_FPTYPE_MD5_CERT; - } else if (!strncmp(string, "sha256-cert:", 12)) { - string += 12; - try_file = false; - try_comment = false; - try_all_fptypes = false; - fptype = SSH_FPTYPE_SHA256_CERT; - } - - /* - * Try interpreting the string as a key file name. - */ - if (try_file) { - Filename *fn = filename_from_str(string); - int keytype = key_type(fn); - if (keytype == SSH_KEYTYPE_SSH1 || - keytype == SSH_KEYTYPE_SSH1_PUBLIC) { - const char *error; - - key_in.blob = strbuf_new(); - if (!rsa1_loadpub_f(fn, BinarySink_UPCAST(key_in.blob), - NULL, &error)) { - strbuf_free(key_in.blob); - key_in.blob = NULL; - if (file_errors) { - *retstr = dupprintf("unable to load file '%s': %s", - string, error); - filename_free(fn); - return NULL; - } - } else { - /* - * If we've successfully loaded the file, stop here - we - * already have a key blob and need not go to the agent to - * list things. - */ - key_in.ssh_version = 1; - key_in.comment = NULL; - key_ret = pageant_pubkey_copy(&key_in); - strbuf_free(key_in.blob); - key_in.blob = NULL; - filename_free(fn); - return key_ret; - } - } else if (keytype == SSH_KEYTYPE_SSH2 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { - const char *error; - - key_in.blob = strbuf_new(); - if (!ppk_loadpub_f(fn, NULL, BinarySink_UPCAST(key_in.blob), - NULL, &error)) { - strbuf_free(key_in.blob); - key_in.blob = NULL; - if (file_errors) { - *retstr = dupprintf("unable to load file '%s': %s", - string, error); - filename_free(fn); - return NULL; - } - } else { - /* - * If we've successfully loaded the file, stop here - we - * already have a key blob and need not go to the agent to - * list things. - */ - key_in.ssh_version = 2; - key_in.comment = NULL; - key_ret = pageant_pubkey_copy(&key_in); - strbuf_free(key_in.blob); - key_in.blob = NULL; - filename_free(fn); - return key_ret; - } - } else { - if (file_errors) { - *retstr = dupprintf("unable to load key file '%s': %s", - string, key_type_to_str(keytype)); - filename_free(fn); - return NULL; - } - } - filename_free(fn); - } - - /* - * Failing that, go through the keys in the agent, and match - * against fingerprints and comments as appropriate. - */ - ctx->string = string; - ctx->match_fp = try_fp; - ctx->match_comment = try_comment; - for (unsigned i = 0; i < SSH_N_FPTYPES; i++) - ctx->match_fptypes[i] = (try_all_fptypes || i == fptype); - ctx->found = NULL; - ctx->nfound = 0; - if (pageant_enum_keys(key_find_callback, ctx, retstr) == - PAGEANT_ACTION_FAILURE) - return NULL; - - if (ctx->nfound == 0) { - *retstr = dupstr("no key matched"); - assert(!ctx->found); - return NULL; - } else if (ctx->nfound > 1) { - *retstr = dupstr("multiple keys matched"); - assert(ctx->found); - pageant_pubkey_free(ctx->found); - return NULL; - } - - assert(ctx->found); - return ctx->found; -} - -void run_client(void) -{ - const struct cmdline_key_action *act; - struct pageant_pubkey *key; - bool errors = false; - char *retstr; - LoadedFile *message = lf_new(AGENT_MAX_MSGLEN); - bool message_loaded = false, message_ok = false; - strbuf *signature = strbuf_new(); - - if (!agent_exists()) { - fprintf(stderr, "pageant: no agent running to talk to\n"); - exit(1); - } - - for (act = keyact_head; act; act = act->next) { - switch (act->action) { - case KEYACT_CLIENT_ADD: - case KEYACT_CLIENT_ADD_ENCRYPTED: - if (!unix_add_keyfile(act->filename, - act->action == KEYACT_CLIENT_ADD_ENCRYPTED)) - errors = true; - break; - case KEYACT_CLIENT_LIST: - if (pageant_enum_keys(key_list_callback, NULL, &retstr) == - PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: listing keys: %s\n", retstr); - sfree(retstr); - errors = true; - } - break; - case KEYACT_CLIENT_DEL: - key = NULL; - if (!(key = find_key(act->filename, &retstr)) || - pageant_delete_key(key, &retstr) == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: deleting key '%s': %s\n", - act->filename, retstr); - sfree(retstr); - errors = true; - } - if (key) - pageant_pubkey_free(key); - break; - case KEYACT_CLIENT_REENCRYPT: - key = NULL; - if (!(key = find_key(act->filename, &retstr)) || - pageant_reencrypt_key(key, &retstr) == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: re-encrypting key '%s': %s\n", - act->filename, retstr); - sfree(retstr); - errors = true; - } - if (key) - pageant_pubkey_free(key); - break; - case KEYACT_CLIENT_PUBLIC_OPENSSH: - case KEYACT_CLIENT_PUBLIC: - key = NULL; - if (!(key = find_key(act->filename, &retstr))) { - fprintf(stderr, "pageant: finding key '%s': %s\n", - act->filename, retstr); - sfree(retstr); - errors = true; - } else { - FILE *fp = stdout; /* FIXME: add a -o option? */ - - if (key->ssh_version == 1) { - BinarySource src[1]; - RSAKey rkey; - - BinarySource_BARE_INIT(src, key->blob->u, key->blob->len); - memset(&rkey, 0, sizeof(rkey)); - rkey.comment = dupstr(key->comment); - get_rsa_ssh1_pub(src, &rkey, RSA_SSH1_EXPONENT_FIRST); - ssh1_write_pubkey(fp, &rkey); - freersakey(&rkey); - } else { - ssh2_write_pubkey(fp, key->comment, - key->blob->u, - key->blob->len, - (act->action == KEYACT_CLIENT_PUBLIC ? - SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 : - SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH)); - } - pageant_pubkey_free(key); - } - break; - case KEYACT_CLIENT_DEL_ALL: - if (pageant_delete_all_keys(&retstr) == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: deleting all keys: %s\n", retstr); - sfree(retstr); - errors = true; - } - break; - case KEYACT_CLIENT_REENCRYPT_ALL: { - int status = pageant_reencrypt_all_keys(&retstr); - if (status == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: re-encrypting all keys: " - "%s\n", retstr); - sfree(retstr); - errors = true; - } else if (status == PAGEANT_ACTION_WARNING) { - fprintf(stderr, "pageant: re-encrypting all keys: " - "warning: %s\n", retstr); - sfree(retstr); - } - break; - } - case KEYACT_CLIENT_SIGN: - key = NULL; - if (!message_loaded) { - message_loaded = true; - switch(lf_load_fp(message, stdin)) { - case LF_TOO_BIG: - fprintf(stderr, "pageant: message to sign is too big\n"); - errors = true; - break; - case LF_ERROR: - fprintf(stderr, "pageant: reading message to sign: %s\n", - strerror(errno)); - errors = true; - break; - case LF_OK: - message_ok = true; - break; - } - } - if (!message_ok) - break; - strbuf_clear(signature); - if (!(key = find_key(act->filename, &retstr)) || - pageant_sign(key, ptrlen_from_lf(message), signature, - sign_flags, &retstr) == PAGEANT_ACTION_FAILURE) { - fprintf(stderr, "pageant: signing with key '%s': %s\n", - act->filename, retstr); - sfree(retstr); - errors = true; - } else { - fwrite(signature->s, 1, signature->len, stdout); - } - if (key) - pageant_pubkey_free(key); - break; - default: - unreachable("Invalid client action found"); - } - } - - lf_free(message); - strbuf_free(signature); - - if (errors) - exit(1); -} - -static const PlugVtable X11Connection_plugvt = { - .log = x11_log, - .closing = x11_closing, - .receive = x11_receive, - .sent = x11_sent, -}; - - -static bool agent_loop_pw_setup(void *vctx, pollwrapper *pw) -{ - struct uxpgnt_client *upc = (struct uxpgnt_client *)vctx; - - if (signalpipe[0] >= 0) { - pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R); - } - - if (upc->prompt_active) - pollwrap_add_fd_rwx(pw, upc->passphrase_fd, SELECT_R); - - return true; -} - -static void agent_loop_pw_check(void *vctx, pollwrapper *pw) -{ - struct uxpgnt_client *upc = (struct uxpgnt_client *)vctx; - - if (life == LIFE_TTY) { - /* - * Every time we wake up (whether it was due to tty_timer - * elapsing or for any other reason), poll to see if we still - * have a controlling terminal. If we don't, then our - * containing tty session has ended, so it's time to clean up - * and leave. - */ - if (!have_controlling_tty()) { - time_to_die = true; - return; - } - } - - if (signalpipe[0] >= 0 && - pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) { - char c[1]; - if (read(signalpipe[0], c, 1) <= 0) - /* ignore error */; - /* ignore its value; it'll be `x' */ - while (1) { - int status; - pid_t pid; - pid = waitpid(-1, &status, WNOHANG); - if (pid <= 0) - break; - if (pid == upc->termination_pid) - time_to_die = true; - } - } - - if (upc->prompt_active && - pollwrap_check_fd_rwx(pw, upc->passphrase_fd, SELECT_R)) { - char c; - int retd = read(upc->passphrase_fd, &c, 1); - - switch (upc->prompt_type) { - case RTPROMPT_GUI: - if (retd <= 0) { - close(upc->passphrase_fd); - upc->passphrase_fd = -1; - bool ok = (retd == 0); - if (!strbuf_chomp(upc->prompt_buf, '\n')) - ok = false; - passphrase_done(upc, ok); - } else { - put_byte(upc->prompt_buf, c); - } - break; - case RTPROMPT_DEBUG: - if (retd <= 0) { - passphrase_done(upc, false); - /* Now never try to read from stdin again */ - upc->prompt_type = RTPROMPT_UNAVAILABLE; - break; - } - - switch (c) { - case '\n': - case '\r': - passphrase_done(upc, true); - break; - case '\004': - passphrase_done(upc, false); - break; - case '\b': - case '\177': - strbuf_shrink_by(upc->prompt_buf, 1); - break; - case '\025': - strbuf_clear(upc->prompt_buf); - break; - default: - put_byte(upc->prompt_buf, c); - break; - } - break; - case RTPROMPT_UNAVAILABLE: - unreachable("Should never have started a prompt at all"); - } - } -} - -static bool agent_loop_continue(void *vctx, bool fd, bool cb) -{ - return !time_to_die; -} - -void run_agent(FILE *logfp, const char *symlink_path) -{ - const char *err; - char *errw; - struct pageant_listen_state *pl; - Plug *pl_plug; - Socket *sock; - bool errors = false; - Conf *conf; - const struct cmdline_key_action *act; - - pageant_init(); - - /* - * Start by loading any keys provided on the command line. - */ - for (act = keyact_head; act; act = act->next) { - assert(act->action == KEYACT_AGENT_LOAD || - act->action == KEYACT_AGENT_LOAD_ENCRYPTED); - if (!unix_add_keyfile(act->filename, - act->action == KEYACT_AGENT_LOAD_ENCRYPTED)) - errors = true; - } - if (errors) - exit(1); - - /* - * Set up a listening socket and run Pageant on it. - */ - struct uxpgnt_client upc[1]; - memset(upc, 0, sizeof(upc)); - upc->plc.vt = &uxpgnt_vtable; - upc->logfp = logfp; - upc->passphrase_fd = -1; - upc->termination_pid = -1; - upc->prompt_buf = strbuf_new_nm(); - upc->prompt_type = display ? RTPROMPT_GUI : RTPROMPT_UNAVAILABLE; - pl = pageant_listener_new(&pl_plug, &upc->plc); - sock = platform_make_agent_socket(pl_plug, PAGEANT_DIR_PREFIX, - &errw, &socketname); - if (!sock) { - fprintf(stderr, "pageant: %s\n", errw); - sfree(errw); - exit(1); - } - pageant_listener_got_socket(pl, sock); - - if (symlink_path) { - /* - * Try to make a symlink to the Unix socket, in a location of - * the user's choosing. - * - * If the link already exists, we want to replace it. There - * are two ways we could do this: either make it under another - * name and then rename it over the top, or remove the old - * link first. The former is what 'ln -sf' does, on the - * grounds that it's more atomic. But I think in this case, - * where the expected use case is that the previous agent has - * long since shut down, atomicity isn't a critical concern - * compared to not accidentally overwriting some non-symlink - * that might have important data in it! - */ - struct stat st; - if (lstat(symlink_path, &st) == 0 && S_ISLNK(st.st_mode)) - unlink(symlink_path); - if (symlink(socketname, symlink_path) < 0) - fprintf(stderr, "pageant: making symlink %s: %s\n", - symlink_path, strerror(errno)); - } - - conf = conf_new(); - conf_set_int(conf, CONF_proxy_type, PROXY_NONE); - - /* - * Lifetime preparations. - */ - if (life == LIFE_X11) { - struct X11Display *disp; - void *greeting; - int greetinglen; - Socket *s; - struct X11Connection *conn; - char *x11_setup_err; - - if (!display) { - fprintf(stderr, "pageant: no DISPLAY for -X mode\n"); - exit(1); - } - disp = x11_setup_display(display, conf, &x11_setup_err); - if (!disp) { - fprintf(stderr, "pageant: unable to connect to X server: %s\n", - x11_setup_err); - sfree(x11_setup_err); - exit(1); - } - - conn = snew(struct X11Connection); - conn->plug.vt = &X11Connection_plugvt; - s = new_connection(sk_addr_dup(disp->addr), - disp->realhost, disp->port, - false, true, false, false, &conn->plug, conf, - NULL); - if ((err = sk_socket_error(s)) != NULL) { - fprintf(stderr, "pageant: unable to connect to X server: %s", err); - exit(1); - } - greeting = x11_make_greeting('B', 11, 0, disp->localauthproto, - disp->localauthdata, - disp->localauthdatalen, - NULL, 0, &greetinglen); - sk_write(s, greeting, greetinglen); - smemclr(greeting, greetinglen); - sfree(greeting); - - pageant_fork_and_print_env(false); - } else if (life == LIFE_TTY) { - schedule_timer(TTY_LIFE_POLL_INTERVAL, - tty_life_timer, &dummy_timer_ctx); - pageant_fork_and_print_env(true); - } else if (life == LIFE_PERM) { - pageant_fork_and_print_env(false); - } else if (life == LIFE_DEBUG) { - pageant_print_env(getpid()); - upc->logfp = stdout; - - struct termios orig_termios; - upc->passphrase_fd = fileno(stdin); - if (tcgetattr(upc->passphrase_fd, &orig_termios) == 0) { - struct termios new_termios = orig_termios; - new_termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON); - - /* - * Try to set up a watchdog process that will restore - * termios if we crash or are killed. If successful, turn - * off echo, for runtime passphrase prompts. - */ - int pipefd[2]; - if (pipe(pipefd) == 0) { - pid_t pid = fork(); - if (pid == 0) { - tcsetattr(upc->passphrase_fd, TCSADRAIN, &new_termios); - close(pipefd[1]); - char buf[4096]; - while (read(pipefd[0], buf, sizeof(buf)) > 0); - tcsetattr(upc->passphrase_fd, TCSADRAIN, &new_termios); - _exit(0); - } else if (pid > 0) { - upc->prompt_type = RTPROMPT_DEBUG; - } - - close(pipefd[0]); - if (pid < 0) - close(pipefd[1]); - } - } - } else if (life == LIFE_EXEC) { - pid_t agentpid, pid; - - agentpid = getpid(); - setup_sigchld_handler(); - - pid = fork(); - if (pid < 0) { - perror("fork"); - exit(1); - } else if (pid == 0) { - setenv("SSH_AUTH_SOCK", socketname, true); - setenv("SSH_AGENT_PID", dupprintf("%d", (int)agentpid), true); - execvp(exec_args[0], exec_args); - perror("exec"); - _exit(127); - } else { - upc->termination_pid = pid; - } - } - - if (!upc->logfp) - upc->plc.suppress_logging = true; - - cli_main_loop(agent_loop_pw_setup, agent_loop_pw_check, - agent_loop_continue, upc); - - /* - * Before terminating, clean up our Unix socket file if possible. - */ - if (unlink(socketname) < 0) { - fprintf(stderr, "pageant: %s: %s\n", socketname, strerror(errno)); - exit(1); - } - - strbuf_free(upc->prompt_buf); - conf_free(conf); -} - -int main(int argc, char **argv) -{ - bool doing_opts = true; - keyact curr_keyact = KEYACT_AGENT_LOAD; - const char *standalone_askpass_prompt = NULL; - const char *symlink_path = NULL; - FILE *logfp = NULL; - - progname = argv[0]; - - /* - * Process the command line. - */ - while (--argc > 0) { - char *p = *++argv; - if (*p == '-' && doing_opts) { - if (!strcmp(p, "-V") || !strcmp(p, "--version")) { - version(); - } else if (!strcmp(p, "--help")) { - usage(); - exit(0); - } else if (!strcmp(p, "-v")) { - logfp = stderr; - } else if (!strcmp(p, "-a")) { - curr_keyact = KEYACT_CLIENT_ADD; - } else if (!strcmp(p, "-d")) { - curr_keyact = KEYACT_CLIENT_DEL; - } else if (!strcmp(p, "-r")) { - curr_keyact = KEYACT_CLIENT_REENCRYPT; - } else if (!strcmp(p, "-s")) { - shell_type = SHELL_SH; - } else if (!strcmp(p, "-c")) { - shell_type = SHELL_CSH; - } else if (!strcmp(p, "-D")) { - add_keyact(KEYACT_CLIENT_DEL_ALL, NULL); - } else if (!strcmp(p, "-R")) { - add_keyact(KEYACT_CLIENT_REENCRYPT_ALL, NULL); - } else if (!strcmp(p, "-l")) { - add_keyact(KEYACT_CLIENT_LIST, NULL); - } else if (!strcmp(p, "--public")) { - curr_keyact = KEYACT_CLIENT_PUBLIC; - } else if (!strcmp(p, "--public-openssh") || !strcmp(p, "-L")) { - curr_keyact = KEYACT_CLIENT_PUBLIC_OPENSSH; - } else if (!strcmp(p, "-X")) { - life = LIFE_X11; - } else if (!strcmp(p, "-T")) { - life = LIFE_TTY; - } else if (!strcmp(p, "--no-decrypt") || - !strcmp(p, "-no-decrypt") || - !strcmp(p, "--no_decrypt") || - !strcmp(p, "-no_decrypt") || - !strcmp(p, "--nodecrypt") || - !strcmp(p, "-nodecrypt") || - !strcmp(p, "--encrypted") || - !strcmp(p, "-encrypted")) { - if (curr_keyact == KEYACT_AGENT_LOAD) - curr_keyact = KEYACT_AGENT_LOAD_ENCRYPTED; - else if (curr_keyact == KEYACT_CLIENT_ADD) - curr_keyact = KEYACT_CLIENT_ADD_ENCRYPTED; - else { - fprintf(stderr, "pageant: unexpected -E while not adding " - "keys\n"); - exit(1); - } - } else if (!strcmp(p, "--debug")) { - life = LIFE_DEBUG; - } else if (!strcmp(p, "--test-sign")) { - curr_keyact = KEYACT_CLIENT_SIGN; - sign_flags = 0; - } else if (strstartswith(p, "--test-sign-with-flags=")) { - curr_keyact = KEYACT_CLIENT_SIGN; - sign_flags = atoi(p + strlen("--test-sign-with-flags=")); - } else if (!strcmp(p, "--permanent")) { - life = LIFE_PERM; - } else if (!strcmp(p, "--exec")) { - life = LIFE_EXEC; - /* Now all subsequent arguments go to the exec command. */ - if (--argc > 0) { - exec_args = ++argv; - argc = 0; /* force end of option processing */ - } else { - fprintf(stderr, "pageant: expected a command " - "after --exec\n"); - exit(1); - } - } else if (!strcmp(p, "--tty-prompt")) { - prompt_type = PROMPT_TTY; - } else if (!strcmp(p, "--gui-prompt")) { - prompt_type = PROMPT_GUI; - } else if (!strcmp(p, "--askpass")) { - if (--argc > 0) { - standalone_askpass_prompt = *++argv; - } else { - fprintf(stderr, "pageant: expected a prompt message " - "after --askpass\n"); - exit(1); - } - } else if (!strcmp(p, "--symlink")) { - if (--argc > 0) { - symlink_path = *++argv; - } else { - fprintf(stderr, "pageant: expected a pathname " - "after --symlink\n"); - exit(1); - } - } else if (!strcmp(p, "-E") || !strcmp(p, "--fptype")) { - const char *keyword; - if (--argc > 0) { - keyword = *++argv; - } else { - fprintf(stderr, "pageant: expected a type string " - "after %s\n", p); - exit(1); - } - if (!strcmp(keyword, "md5")) - key_list_fptype = SSH_FPTYPE_MD5; - else if (!strcmp(keyword, "sha256")) - key_list_fptype = SSH_FPTYPE_SHA256; - else if (!strcmp(keyword, "md5-cert")) - key_list_fptype = SSH_FPTYPE_MD5_CERT; - else if (!strcmp(keyword, "sha256-cert")) - key_list_fptype = SSH_FPTYPE_SHA256_CERT; - else { - fprintf(stderr, "pageant: unknown fingerprint type `%s'\n", - keyword); - exit(1); - } - } else if (!strcmp(p, "--")) { - doing_opts = false; - } else { - fprintf(stderr, "pageant: unrecognised option '%s'\n", p); - exit(1); - } - } else { - /* - * Non-option arguments (apart from those after --exec, - * which are treated specially above) are interpreted as - * the names of private key files to either add or delete - * from an agent. - */ - add_keyact(curr_keyact, p); - } - } - - if (life == LIFE_EXEC && !exec_args) { - fprintf(stderr, "pageant: expected a command with --exec\n"); - exit(1); - } - - if (!display) { - display = getenv("DISPLAY"); - if (display && !*display) - display = NULL; - } - - /* - * Deal with standalone-askpass mode. - */ - if (standalone_askpass_prompt) { - char *passphrase = askpass(standalone_askpass_prompt); - - if (!passphrase) - return 1; - - puts(passphrase); - fflush(stdout); - - smemclr(passphrase, strlen(passphrase)); - sfree(passphrase); - return 0; - } - - /* - * Block SIGPIPE, so that we'll get EPIPE individually on - * particular network connections that go wrong. - */ - putty_signal(SIGPIPE, SIG_IGN); - - sk_init(); - uxsel_init(); - - /* - * Now distinguish our two main running modes. Either we're - * actually starting up an agent, in which case we should have a - * lifetime mode, and no key actions of KEYACT_CLIENT_* type; or - * else we're contacting an existing agent to add or remove keys, - * in which case we should have no lifetime mode, and no key - * actions of KEYACT_AGENT_* type. - */ - { - bool has_agent_actions = false; - bool has_client_actions = false; - bool has_lifetime = false; - const struct cmdline_key_action *act; - - for (act = keyact_head; act; act = act->next) { - if (is_agent_action(act->action)) - has_agent_actions = true; - else - has_client_actions = true; - } - if (life != LIFE_UNSPEC) - has_lifetime = true; - - if (has_lifetime && has_client_actions) { - fprintf(stderr, "pageant: client key actions (-a, -d, -D, -r, -R, " - "-l, -L) do not go with an agent lifetime option\n"); - exit(1); - } - if (!has_lifetime && has_agent_actions) { - fprintf(stderr, "pageant: expected an agent lifetime option with" - " bare key file arguments\n"); - exit(1); - } - if (!has_lifetime && !has_client_actions) { - fprintf(stderr, "pageant: expected an agent lifetime option" - " or a client key action\n"); - exit(1); - } - - if (has_lifetime) { - run_agent(logfp, symlink_path); - } else if (has_client_actions) { - run_client(); - } - } - - return 0; -} diff --git a/unix/peerinfo.c b/unix/peerinfo.c deleted file mode 100644 index 11f032913..000000000 --- a/unix/peerinfo.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Unix: wrapper for getsockopt(SO_PEERCRED), conditionalised on - * appropriate autoconfery. - */ - -#if HAVE_CMAKE_H -#include "cmake.h" -#endif - -#if HAVE_SO_PEERCRED -#define _GNU_SOURCE -#include -#endif - -#include - -#include "putty.h" - -bool so_peercred(int fd, int *pid, int *uid, int *gid) -{ -#if 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/unix/platform.h b/unix/platform.h deleted file mode 100644 index 7b9a7ccf7..000000000 --- a/unix/platform.h +++ /dev/null @@ -1,480 +0,0 @@ -/* - * unix/platform.h: Unix-specific inter-module stuff. - */ - -#ifndef PUTTY_UNIX_PLATFORM_H -#define PUTTY_UNIX_PLATFORM_H - -#include /* for FILENAME_MAX */ -#include /* C99 int types */ -#ifndef NO_LIBDL -#include /* Dynamic library loading */ -#endif /* NO_LIBDL */ -#include "charset.h" -#include /* for mode_t */ - -#ifdef OSX_GTK -/* - * Assorted tweaks to various parts of the GTK front end which all - * need to be enabled when compiling on OS X. Because I might need the - * same tweaks on other systems in future, I don't want to - * conditionalise all of them on OSX_GTK directly, so instead, each - * one has its own name and we enable them all centrally here if - * OSX_GTK is defined at configure time. - */ -#define NOT_X_WINDOWS /* of course, all the X11 stuff should be disabled */ -#define NO_PTY_PRE_INIT /* OS X gets very huffy if we try to set[ug]id */ -#define SET_NONBLOCK_VIA_OPENPT /* work around missing fcntl functionality */ -#define OSX_META_KEY_CONFIG /* two possible Meta keys to choose from */ -/* this potential one of the Meta keys needs manual handling */ -#define META_MANUAL_MASK (GDK_MOD1_MASK) -#define JUST_USE_GTK_CLIPBOARD_UTF8 /* low-level gdk_selection_* fails */ - -#define BUILDINFO_PLATFORM_GTK "OS X (GTK)" -#define BUILDINFO_GTK - -#elif defined NOT_X_WINDOWS - -#define BUILDINFO_PLATFORM_GTK "Unix (pure GTK)" -#define BUILDINFO_GTK - -#else - -#define BUILDINFO_PLATFORM_GTK "Unix (GTK + X11)" -#define BUILDINFO_GTK - -#endif - -/* BUILDINFO_PLATFORM varies its expansion between the GTK and - * pure-CLI utilities, so that Unix Plink, PSFTP etc don't announce - * themselves incongruously as having something to do with GTK. */ -#define BUILDINFO_PLATFORM_CLI "Unix" -extern const bool buildinfo_gtk_relevant; -#define BUILDINFO_PLATFORM (buildinfo_gtk_relevant ? \ - BUILDINFO_PLATFORM_GTK : BUILDINFO_PLATFORM_CLI) - -char *buildinfo_gtk_version(void); - -struct Filename { - char *path; -}; -FILE *f_open(const struct Filename *, char const *, bool); - -#ifndef SUPERSEDE_FONTSPEC_FOR_TESTING -struct FontSpec { - char *name; /* may be "" to indicate no selected font at all */ -}; -struct FontSpec *fontspec_new(const char *name); -#endif - -extern const struct BackendVtable pty_backend; - -#define BROKEN_PIPE_ERROR_CODE EPIPE /* used in ssh/sharing.c */ - -/* - * 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. - */ -typedef void *HelpCtx; -#define NULL_HELPCTX ((HelpCtx)NULL) -#define HELPCTX(x) NULL - -typedef const char *FILESELECT_FILTER_TYPE; -#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); -#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 - -#define PLATFORM_CLIPBOARDS(X) \ - X(CLIP_PRIMARY, "X11 primary selection") \ - X(CLIP_CLIPBOARD, "XDG clipboard") \ - X(CLIP_CUSTOM_1, "") \ - X(CLIP_CUSTOM_2, "") \ - X(CLIP_CUSTOM_3, "") \ - /* end of list */ - -#ifdef OSX_GTK -/* OS X has no PRIMARY selection */ -#define MOUSE_SELECT_CLIPBOARD CLIP_NULL -#define MOUSE_PASTE_CLIPBOARD CLIP_LOCAL -#define CLIPNAME_IMPLICIT "Last selected text" -#define CLIPNAME_EXPLICIT "System clipboard" -#define CLIPNAME_EXPLICIT_OBJECT "system clipboard" -/* These defaults are the ones that more or less comply with the OS X - * Human Interface Guidelines, i.e. copy/paste to the system clipboard - * is _not_ implicit but requires a specific UI action. This is at - * odds with all other PuTTY front ends' defaults, but on OS X there - * is no multi-decade precedent for PuTTY working the other way. */ -#define CLIPUI_DEFAULT_AUTOCOPY false -#define CLIPUI_DEFAULT_MOUSE CLIPUI_IMPLICIT -#define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT -#define MENU_CLIPBOARD CLIP_CLIPBOARD -#define COPYALL_CLIPBOARDS CLIP_CLIPBOARD -#else -#define MOUSE_SELECT_CLIPBOARD CLIP_PRIMARY -#define MOUSE_PASTE_CLIPBOARD CLIP_PRIMARY -#define CLIPNAME_IMPLICIT "PRIMARY" -#define CLIPNAME_EXPLICIT "CLIPBOARD" -#define CLIPNAME_EXPLICIT_OBJECT "CLIPBOARD" -/* These defaults are the ones Unix PuTTY has historically had since - * it was first thought of in 2002 */ -#define CLIPUI_DEFAULT_AUTOCOPY false -#define CLIPUI_DEFAULT_MOUSE CLIPUI_IMPLICIT -#define CLIPUI_DEFAULT_INS CLIPUI_IMPLICIT -#define MENU_CLIPBOARD CLIP_CLIPBOARD -#define COPYALL_CLIPBOARDS CLIP_PRIMARY, CLIP_CLIPBOARD -/* X11 supports arbitrary named clipboards */ -#define NAMED_CLIPBOARDS -#endif - -/* The per-session frontend structure managed by window.c */ -typedef struct GtkFrontend GtkFrontend; - -/* Callback when a dialog box finishes, and a no-op implementation of it */ -typedef void (*post_dialog_fn_t)(void *ctx, int result); -void trivial_post_dialog_fn(void *vctx, int result); - -/* Start up a session window, with or without a preliminary config box */ -void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx); -void new_session_window(Conf *conf, const char *geometry_string); - -/* Defined in main-gtk-*.c */ -void launch_duplicate_session(Conf *conf); -void launch_new_session(void); -void launch_saved_session(const char *str); -void session_window_closed(void); -void window_setup_error(const char *errmsg); -#ifdef MAY_REFER_TO_GTK_IN_HEADERS -GtkWidget *make_gtk_toplevel_window(GtkFrontend *frontend); -#endif - -const struct BackendVtable *select_backend(Conf *conf); - -/* Defined in gtk-common.c */ -void gtkcomm_setup(void); - -/* Used to pass application-menu operations from - * main-gtk-application.c to window.c */ -enum MenuAction { - MA_COPY, MA_PASTE, MA_COPY_ALL, MA_DUPLICATE_SESSION, - MA_RESTART_SESSION, MA_CHANGE_SETTINGS, MA_CLEAR_SCROLLBACK, - MA_RESET_TERMINAL, MA_EVENT_LOG -}; -void app_menu_action(GtkFrontend *frontend, enum MenuAction); - -/* Arrays of pixmap data used for GTK window icons. (main_icon is for - * the process's main window; cfg_icon is the modified icon used for - * its config box.) */ -extern const char *const *const main_icon[]; -extern const char *const *const cfg_icon[]; -extern const int n_main_icon, n_cfg_icon; - -/* Things dialog.c needs from window.c */ -#ifdef MAY_REFER_TO_GTK_IN_HEADERS -enum DialogSlot { - DIALOG_SLOT_RECONFIGURE, - DIALOG_SLOT_NETWORK_PROMPT, - DIALOG_SLOT_LOGFILE_PROMPT, - DIALOG_SLOT_WARN_ON_CLOSE, - DIALOG_SLOT_CONNECTION_FATAL, - DIALOG_SLOT_LIMIT /* must remain last */ -}; -GtkWidget *gtk_seat_get_window(Seat *seat); -void register_dialog(Seat *seat, enum DialogSlot slot, GtkWidget *dialog); -void unregister_dialog(Seat *seat, enum DialogSlot slot); -void set_window_icon(GtkWidget *window, const char *const *const *icon, - int n_icon); -extern GdkAtom compound_text_atom; -#endif - -/* Things window.c needs from dialog.c */ -#ifdef MAY_REFER_TO_GTK_IN_HEADERS -GtkWidget *create_config_box(const char *title, Conf *conf, - bool midsession, int protcfginfo, - post_dialog_fn_t after, void *afterctx); -#endif -void nonfatal_message_box(void *window, const char *msg); -void about_box(void *window); -typedef struct eventlog_stuff eventlog_stuff; -eventlog_stuff *eventlogstuff_new(void); -void eventlogstuff_free(eventlog_stuff *); -void showeventlog(eventlog_stuff *estuff, void *parentwin); -void logevent_dlg(eventlog_stuff *estuff, const char *string); -int gtkdlg_askappend(Seat *seat, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx); -SeatPromptResult gtk_seat_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); -SeatPromptResult gtk_seat_confirm_weak_crypto_primitive( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); -SeatPromptResult gtk_seat_confirm_weak_cached_hostkey( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx); -const SeatDialogPromptDescriptions *gtk_seat_prompt_descriptions(Seat *seat); -#ifdef MAY_REFER_TO_GTK_IN_HEADERS -struct message_box_button { - const char *title; - char shortcut; - int type; /* more negative means more appropriate to be the Esc action */ - int value; /* message box's return value if this is pressed */ -}; -struct message_box_buttons { - const struct message_box_button *buttons; - int nbuttons; -}; -extern const struct message_box_buttons buttons_yn, buttons_ok; -GtkWidget *create_message_box( - GtkWidget *parentwin, const char *title, const char *msg, int minwid, - bool selectable, const struct message_box_buttons *buttons, - post_dialog_fn_t after, void *afterctx); -#endif -void show_ca_config_box_synchronously(void); - -/* window.c needs this special function in utils */ -int keysym_to_unicode(int keysym); - -/* Things storage.c needs from window.c */ -char *x_get_default(const char *key); - -/* Things storage.c provides to window.c */ -void provide_xrm_string(const char *string, const char *progname); - -/* Function that main-gtk-*.c needs from {pterm,putty}.c. Does - * early process setup that varies between applications (e.g. - * pty_pre_init or sk_init), and is passed a boolean by the caller - * indicating whether this is an OS X style multi-session monolithic - * process or an ordinary Unix one-shot. */ -void setup(bool single_session_in_this_process); - -/* - * Per-application constants that affect behaviour of shared modules. - */ -/* Do we need an Event Log menu item? (yes for PuTTY, no for pterm) */ -extern const bool use_event_log; -/* Do we need a New Session menu item? (yes for PuTTY, no for pterm) */ -extern const bool new_session; -/* Do we need a Saved Sessions menu item? (yes for PuTTY, no for pterm) */ -extern const bool saved_sessions; -/* When we Duplicate Session, do we need to double-check that the Conf - * is in a launchable state? (no for pterm, because conf_launchable - * returns an irrelevant answer, since we'll force use of the pty - * backend which ignores all the relevant settings) */ -extern const bool dup_check_launchable; -/* In the Duplicate Session serialised data, do we send/receive an - * argv array after the main Conf? (yes for pterm, no for PuTTY) */ -extern const bool use_pty_argv; - -/* - * OS X environment munging: this is the prefix we expect to find on - * environment variable names that were changed by osxlaunch. - * Extracted from the command line of the OS X pterm main binary, and - * used in pty.c to restore the original environment before - * launching its subprocess. - */ -extern char *pty_osx_envrestore_prefix; - -/* Things provided by console.c */ -struct termios; -void stderr_tty_init(void); /* call at startup if stderr might be a tty */ -void premsg(struct termios *); -void postmsg(struct termios *); - -/* The interface used by uxsel.c */ -typedef struct uxsel_id uxsel_id; -void uxsel_init(void); -typedef void (*uxsel_callback_fn)(int fd, int event); -void uxsel_set(int fd, int rwx, uxsel_callback_fn callback); -void uxsel_del(int fd); -enum { SELECT_R = 1, SELECT_W = 2, SELECT_X = 4 }; -void 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 */ -uxsel_id *uxsel_input_add(int fd, int rwx); /* returns an id */ -void uxsel_input_remove(uxsel_id *id); - -/* config-unix.c */ -struct controlbox; -void unix_setup_config_box( - struct controlbox *b, bool midsession, int protocol); - -/* config-gtk.c */ -void gtk_setup_config_box( - struct controlbox *b, bool 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 CP_437 CS_CP437 /* used for test suites */ -#define CP_ISO8859_1 CS_ISO8859_1 /* used for test suites */ - -#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, bool block_it); - -/* utils */ -void cloexec(int); -void noncloexec(int); -bool nonblock(int); -bool no_nonblock(int); -char *make_dir_and_check_ours(const char *dirname); -char *make_dir_path(const char *path, mode_t mode); - -/* - * Exports from unicode.c. - */ -bool init_ucs(struct unicode_data *ucsdata, char *line_codepage, - bool utf8_override, int font_charset, int vtmode); - -/* - * Spare functions exported directly from network.c. - */ -void *sk_getxdmdata(Socket *sock, int *lenp); -int sk_net_get_fd(Socket *sock); -SockAddr *unix_sock_addr(const char *path); -Socket *new_unix_listener(SockAddr *listenaddr, Plug *plug); - -/* - * 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 serial.c. - */ -extern const struct BackendVtable serial_backend; - -/* - * peerinfo.c, wrapping getsockopt(SO_PEERCRED). - */ -bool so_peercred(int fd, int *pid, int *uid, int *gid); - -/* - * fd-socket.c. - */ -Socket *make_fd_socket(int infd, int outfd, int inerrfd, - SockAddr *addr, int port, Plug *plug); -Socket *make_deferred_fd_socket(DeferredSocketOpener *opener, - SockAddr *addr, int port, Plug *plug); -void setup_fd_socket(Socket *s, int infd, int outfd, int inerrfd); -void fd_socket_set_psb_prefix(Socket *s, const char *prefix); - -/* - * Default font setting, which can vary depending on NOT_X_WINDOWS. - */ -#ifdef NOT_X_WINDOWS -#define DEFAULT_GTK_FONT "client:Monospace 12" -#else -#define DEFAULT_GTK_FONT "server:fixed" -#endif - -/* - * pty.c. - */ -void pty_pre_init(void); /* pty+utmp setup before dropping privilege */ -/* Pass in the argv[] for an instance of the pty backend created by - * the standard vtable constructor. Only called from (non-OSX) pterm, - * which will construct exactly one such instance, and initialises - * this from the command line. */ -extern char **pty_argv; - -/* - * askpass.c. - */ -char *gtk_askpass_main(const char *display, const char *wintitle, - const char *prompt, bool *success); - -/* - * procnet.c. - */ -bool socket_peer_is_same_user(int fd); -static inline bool sk_peer_trusted(Socket *sock) -{ - int fd = sk_net_get_fd(sock); - return fd >= 0 && socket_peer_is_same_user(fd); -} - -/* - * sftpserver.c. - */ -extern const SftpServerVtable unix_live_sftpserver_vt; - -/* - * utils/pollwrap.c. - */ -typedef struct pollwrapper pollwrapper; -pollwrapper *pollwrap_new(void); -void pollwrap_free(pollwrapper *pw); -void pollwrap_clear(pollwrapper *pw); -void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events); -void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx); -int pollwrap_poll_instant(pollwrapper *pw); -int pollwrap_poll_endless(pollwrapper *pw); -int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds); -int pollwrap_get_fd_events(pollwrapper *pw, int fd); -int pollwrap_get_fd_rwx(pollwrapper *pw, int fd); -static inline bool pollwrap_check_fd_rwx(pollwrapper *pw, int fd, int rwx) -{ - return (pollwrap_get_fd_rwx(pw, fd) & rwx) != 0; -} - -/* - * cliloop.c. - */ -typedef bool (*cliloop_pw_setup_t)(void *ctx, pollwrapper *pw); -typedef void (*cliloop_pw_check_t)(void *ctx, pollwrapper *pw); -typedef bool (*cliloop_continue_t)(void *ctx, bool found_any_fd, - bool ran_any_callback); - -void cli_main_loop(cliloop_pw_setup_t pw_setup, - cliloop_pw_check_t pw_check, - cliloop_continue_t cont, void *ctx); - -bool cliloop_no_pw_setup(void *ctx, pollwrapper *pw); -void cliloop_no_pw_check(void *ctx, pollwrapper *pw); -bool cliloop_always_continue(void *ctx, bool, bool); - -/* network.c: network error reporting helper taking an OS error code */ -void plug_closing_errno(Plug *plug, int error); - -SeatPromptResult make_spr_sw_abort_errno(const char *prefix, int errno_value); - -#endif /* PUTTY_UNIX_PLATFORM_H */ diff --git a/unix/plink.c b/unix/plink.c deleted file mode 100644 index 76f4c936e..000000000 --- a/unix/plink.c +++ /dev/null @@ -1,983 +0,0 @@ -/* - * PLink - a command-line (stdin/stdout) variant of PuTTY. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "storage.h" -#include "tree234.h" - -#define MAX_STDIN_BACKLOG 4096 - -static LogContext *logctx; - -static struct termios orig_termios; - -void cmdline_error(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - console_print_error_msg_fmt_v("plink", fmt, ap); - va_end(ap); - exit(1); -} - -static bool local_tty = false; /* do we have a local tty? */ - -static Backend *backend; -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; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} - -FontSpec *platform_default_fontspec(const char *name) -{ - return fontspec_new_default(); -} - -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 */ -} -static void plink_echoedit_update(Seat *seat, bool echo, bool 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 the echoedit_update() method, 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); -} - -static char *plink_get_ttymode(Seat *seat, 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) { \ - bool 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 -#if defined(IUTF8) - GET_BOOL("IUTF8", IUTF8, c_iflag, ); -#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); -} - -static bufchain stdout_data, stderr_data; -static bufchain_sink stdout_bcs, stderr_bcs; -static StripCtrlChars *stdout_scc, *stderr_scc; -static BinarySink *stdout_bs, *stderr_bs; - -static enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; - -static size_t output_backlog(void) -{ - return bufchain_size(&stdout_data) + bufchain_size(&stderr_data); -} - -void try_output(bool is_stderr) -{ - bufchain *chain = (is_stderr ? &stderr_data : &stdout_data); - int fd = (is_stderr ? STDERR_FILENO : STDOUT_FILENO); - ssize_t ret; - - if (bufchain_size(chain) > 0) { - bool prev_nonblock = nonblock(fd); - ptrlen senddata; - do { - senddata = bufchain_prefix(chain); - ret = write(fd, senddata.ptr, senddata.len); - if (ret > 0) - bufchain_consume(chain, ret); - } while (ret == senddata.len && bufchain_size(chain) != 0); - if (!prev_nonblock) - no_nonblock(fd); - if (ret < 0 && errno != EAGAIN) { - perror(is_stderr ? "stderr: write" : "stdout: write"); - exit(1); - } - - backend_unthrottle(backend, output_backlog()); - } - if (outgoingeof == EOF_PENDING && bufchain_size(&stdout_data) == 0) { - close(STDOUT_FILENO); - outgoingeof = EOF_SENT; - } -} - -static size_t plink_output( - Seat *seat, SeatOutputType type, const void *data, size_t len) -{ - bool is_stderr = type != SEAT_OUTPUT_STDOUT; - assert(is_stderr || outgoingeof == EOF_NO); - - BinarySink *bs = is_stderr ? stderr_bs : stdout_bs; - put_data(bs, data, len); - - try_output(is_stderr); - return output_backlog(); -} - -static bool plink_eof(Seat *seat) -{ - assert(outgoingeof == EOF_NO); - outgoingeof = EOF_PENDING; - try_output(false); - return false; /* do not respond to incoming EOF with outgoing */ -} - -static SeatPromptResult plink_get_userpass_input(Seat *seat, prompts_t *p) -{ - /* Plink doesn't support Restart Session, so we can just have a - * single static cmdline_get_passwd_input_state that's never reset */ - static cmdline_get_passwd_input_state cmdline_state = - CMDLINE_GET_PASSWD_INPUT_STATE_INIT; - - SeatPromptResult spr; - spr = cmdline_get_passwd_input(p, &cmdline_state, false); - if (spr.kind == SPRK_INCOMPLETE) - spr = console_get_userpass_input(p); - return spr; -} - -static bool plink_seat_interactive(Seat *seat) -{ - return (!*conf_get_str(conf, CONF_remote_cmd) && - !*conf_get_str(conf, CONF_remote_cmd2) && - !*conf_get_str(conf, CONF_ssh_nc_host)); -} - -static const SeatVtable plink_seat_vt = { - .output = plink_output, - .eof = plink_eof, - .sent = nullseat_sent, - .banner = nullseat_banner_to_stderr, - .get_userpass_input = plink_get_userpass_input, - .notify_session_started = nullseat_notify_session_started, - .notify_remote_exit = nullseat_notify_remote_exit, - .notify_remote_disconnect = nullseat_notify_remote_disconnect, - .connection_fatal = console_connection_fatal, - .nonfatal = console_nonfatal, - .update_specials_menu = nullseat_update_specials_menu, - .get_ttymode = plink_get_ttymode, - .set_busy_status = nullseat_set_busy_status, - .confirm_ssh_host_key = console_confirm_ssh_host_key, - .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, - .prompt_descriptions = console_prompt_descriptions, - .is_utf8 = nullseat_is_never_utf8, - .echoedit_update = plink_echoedit_update, - .get_x_display = nullseat_get_x_display, - .get_windowid = nullseat_get_windowid, - .get_window_pixel_size = nullseat_get_window_pixel_size, - .stripctrl_new = console_stripctrl_new, - .set_trust_status = console_set_trust_status, - .can_set_trust_status = console_can_set_trust_status, - .has_mixed_input_stream = console_has_mixed_input_stream, - .verbose = cmdline_seat_verbose, - .interactive = plink_seat_interactive, - .get_cursor_position = nullseat_get_cursor_position, -}; -static Seat plink_seat[1] = {{ &plink_seat_vt }}; - -/* - * 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; - backend_send(backend, p, q - p); - p = q; - } - break; - case FF: - if (*p == '\xff') { - backend_send(backend, p, 1); - p++; - state = NORMAL; - } else if (*p == '\0') { - p++; - state = FF00; - } else abort(); - break; - case FF00: - if (*p == '\0') { - backend_special(backend, SS_BRK, 0); - } 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; - backend_send(backend, p, 1); - } - } else { - /* INPCK not set. Assume we got a parity error. */ - backend_send(backend, p, 1); - } - } - p++; - state = NORMAL; - } - } -} - -static int signalpipe[2]; - -void sigwinch(int signum) -{ - if (write(signalpipe[1], "x", 1) <= 0) - /* not much we can do about it */; -} - -/* - * 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(" -ssh-connection\n"); - printf(" force use of the bare ssh-connection 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(" -proxycmd command\n"); - printf(" use 'command' as local proxy\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(" -pwfile file login with password read from specified file\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 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(" -no-trivial-auth\n"); - printf(" disconnect if SSH authentication succeeds trivially\n"); - printf(" -noshare disable use of connection sharing\n"); - printf(" -share enable use of connection sharing\n"); - printf(" -hostkey keyid\n"); - printf(" manually specify a host key (may be repeated)\n"); - printf(" -sanitise-stderr, -sanitise-stdout, " - "-no-sanitise-stderr, -no-sanitise-stdout\n"); - printf(" do/don't strip control chars from standard " - "output/error\n"); - printf(" -no-antispoof omit anti-spoofing prompt after " - "authentication\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"); - printf(" -logoverwrite\n"); - printf(" -logappend\n"); - printf(" control what happens when a log file already exists\n"); - printf(" -shareexists\n"); - printf(" test whether a connection-sharing upstream exists\n"); - exit(1); -} - -static void version(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("plink: %s\n%s\n", ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); -} - -void frontend_net_error_pending(void) {} - -const bool share_can_be_downstream = true; -const bool share_can_be_upstream = true; - -const bool buildinfo_gtk_relevant = false; - -const unsigned cmdline_tooltype = - TOOLTYPE_HOST_ARG | - TOOLTYPE_HOST_ARG_CAN_BE_SESSION | - TOOLTYPE_HOST_ARG_PROTOCOL_PREFIX | - TOOLTYPE_HOST_ARG_FROM_LAUNCHABLE_LOAD; - -static bool seen_stdin_eof = false; - -static bool plink_pw_setup(void *vctx, pollwrapper *pw) -{ - pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R); - - if (!seen_stdin_eof && - backend_connected(backend) && - backend_sendok(backend) && - backend_sendbuffer(backend) < MAX_STDIN_BACKLOG) { - /* If we're OK to send, then try to read from stdin. */ - pollwrap_add_fd_rwx(pw, STDIN_FILENO, SELECT_R); - } - - if (bufchain_size(&stdout_data) > 0) { - /* If we have data for stdout, try to write to stdout. */ - pollwrap_add_fd_rwx(pw, STDOUT_FILENO, SELECT_W); - } - - if (bufchain_size(&stderr_data) > 0) { - /* If we have data for stderr, try to write to stderr. */ - pollwrap_add_fd_rwx(pw, STDERR_FILENO, SELECT_W); - } - - return true; -} - -static void plink_pw_check(void *vctx, pollwrapper *pw) -{ - if (pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) { - 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) - backend_size(backend, size.ws_col, size.ws_row); - } - - if (pollwrap_check_fd_rwx(pw, STDIN_FILENO, SELECT_R)) { - char buf[4096]; - int ret; - - if (backend_connected(backend)) { - ret = read(STDIN_FILENO, buf, sizeof(buf)); - noise_ultralight(NOISE_SOURCE_IOLEN, ret); - if (ret < 0) { - perror("stdin: read"); - exit(1); - } else if (ret == 0) { - backend_special(backend, SS_EOF, 0); - seen_stdin_eof = true; - } else { - if (local_tty) - from_tty(buf, ret); - else - backend_send(backend, buf, ret); - } - } - } - - if (pollwrap_check_fd_rwx(pw, STDOUT_FILENO, SELECT_W)) - try_output(false); - - if (pollwrap_check_fd_rwx(pw, STDERR_FILENO, SELECT_W)) - try_output(true); -} - -static bool plink_continue(void *vctx, bool found_any_fd, - bool ran_any_callback) -{ - if (!backend_connected(backend) && - bufchain_size(&stdout_data) == 0 && bufchain_size(&stderr_data) == 0) - return false; /* terminate main loop */ - return true; -} - -int main(int argc, char **argv) -{ - int exitcode; - bool errors; - enum TriState sanitise_stdout = AUTO, sanitise_stderr = AUTO; - bool use_subsystem = false; - bool just_test_share_exists = false; - struct winsize size; - const struct BackendVtable *backvt; - - /* - * Initialise port and protocol to sensible defaults. (These - * will be overridden by more or less anything.) - */ - settings_set_default_protocol(PROT_SSH); - settings_set_default_port(22); - - bufchain_init(&stdout_data); - bufchain_init(&stderr_data); - bufchain_sink_init(&stdout_bcs, &stdout_data); - bufchain_sink_init(&stderr_bcs, &stderr_data); - stdout_bs = BinarySink_UPCAST(&stdout_bcs); - stderr_bs = BinarySink_UPCAST(&stderr_bcs); - outgoingeof = EOF_NO; - - stderr_tty_init(); - /* - * Process the command line. - */ - conf = conf_new(); - do_defaults(NULL, conf); - settings_set_default_protocol(conf_get_int(conf, CONF_protocol)); - settings_set_default_port(conf_get_int(conf, CONF_port)); - errors = false; - { - /* - * Override the default protocol if PLINK_PROTOCOL is set. - */ - char *p = getenv("PLINK_PROTOCOL"); - if (p) { - const struct BackendVtable *vt = backend_vt_from_name(p); - if (vt) { - settings_set_default_protocol(vt->protocol); - settings_set_default_port(vt->default_port); - conf_set_int(conf, CONF_protocol, vt->protocol); - conf_set_int(conf, CONF_port, vt->default_port); - } - } - } - while (--argc) { - char *p = *++argv; - 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 = true; - } else if (ret == 2) { - --argc, ++argv; - } else if (ret == 1) { - continue; - } else if (!strcmp(p, "-s")) { - /* Save status to write to conf later. */ - use_subsystem = true; - } 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 = true; - } else { - --argc; - /* Explicitly pass "plink" in place of appname for - * error reporting purposes. appname will have been - * set by be_list.c to something more generic, probably - * "PuTTY". */ - provide_xrm_string(*++argv, "plink"); - } - } else if (!strcmp(p, "-shareexists")) { - just_test_share_exists = true; - } else if (!strcmp(p, "-fuzznet")) { - conf_set_int(conf, CONF_proxy_type, PROXY_FUZZ); - conf_set_str(conf, CONF_proxy_telnet_command, "%host"); - } else if (!strcmp(p, "-sanitise-stdout") || - !strcmp(p, "-sanitize-stdout")) { - sanitise_stdout = FORCE_ON; - } else if (!strcmp(p, "-no-sanitise-stdout") || - !strcmp(p, "-no-sanitize-stdout")) { - sanitise_stdout = FORCE_OFF; - } else if (!strcmp(p, "-sanitise-stderr") || - !strcmp(p, "-sanitize-stderr")) { - sanitise_stderr = FORCE_ON; - } else if (!strcmp(p, "-no-sanitise-stderr") || - !strcmp(p, "-no-sanitize-stderr")) { - sanitise_stderr = FORCE_OFF; - } else if (!strcmp(p, "-no-antispoof")) { - console_antispoof_prompt = false; - } else if (*p != '-') { - strbuf *cmdbuf = strbuf_new(); - - while (argc > 0) { - if (cmdbuf->len > 0) - put_byte(cmdbuf, ' '); /* add space separator */ - put_dataz(cmdbuf, p); - if (--argc > 0) - p = *++argv; - } - - conf_set_str(conf, CONF_remote_cmd, cmdbuf->s); - conf_set_str(conf, CONF_remote_cmd2, ""); - conf_set_bool(conf, CONF_nopty, true); /* command => no tty */ - - strbuf_free(cmdbuf); - break; /* done with cmdline */ - } else { - fprintf(stderr, "plink: unknown option \"%s\"\n", p); - errors = true; - } - } - - if (errors) - return 1; - - if (!cmdline_host_ok(conf)) { - usage(); - } - - prepare_session(conf); - - /* - * 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_bool(conf, CONF_ssh_subsys, true); - - /* - * Select protocol. This is farmed out into a table in a - * separate file to enable an ssh-free variant. - */ - backvt = backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); - if (!backvt) { - fprintf(stderr, - "Internal fault: Unsupported protocol found\n"); - return 1; - } - - if (backvt->flags & BACKEND_NEEDS_TERMINAL) { - fprintf(stderr, - "Plink doesn't support %s, which needs terminal emulation\n", - backvt->displayname_lc); - return 1; - } - - /* - * 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); - } - /* We don't want the signal handler to block if the pipe's full. */ - nonblock(signalpipe[0]); - nonblock(signalpipe[1]); - cloexec(signalpipe[0]); - cloexec(signalpipe[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); - } - - /* - * Decide whether to sanitise control sequences out of standard - * output and standard error. - * - * If we weren't given a command-line override, we do this if (a) - * the fd in question is pointing at a terminal, and (b) we aren't - * trying to allocate a terminal as part of the session. - * - * (Rationale: the risk of control sequences is that they cause - * confusion when sent to a local terminal, so if there isn't one, - * no problem. Also, if we allocate a remote terminal, then we - * sent a terminal type, i.e. we told it what kind of escape - * sequences we _like_, i.e. we were expecting to receive some.) - */ - if (sanitise_stdout == FORCE_ON || - (sanitise_stdout == AUTO && isatty(STDOUT_FILENO) && - conf_get_bool(conf, CONF_nopty))) { - stdout_scc = stripctrl_new(stdout_bs, true, L'\0'); - stdout_bs = BinarySink_UPCAST(stdout_scc); - } - if (sanitise_stderr == FORCE_ON || - (sanitise_stderr == AUTO && isatty(STDERR_FILENO) && - conf_get_bool(conf, CONF_nopty))) { - stderr_scc = stripctrl_new(stderr_bs, true, L'\0'); - stderr_bs = BinarySink_UPCAST(stderr_scc); - } - - sk_init(); - uxsel_init(); - - /* - * 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_bool(conf, CONF_x11_forward) && - !conf_get_bool(conf, CONF_agentfwd) && - !conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) - conf_set_bool(conf, CONF_ssh_simple, true); - - if (just_test_share_exists) { - if (!backvt->test_for_upstream) { - fprintf(stderr, "Connection sharing not supported for this " - "connection type (%s)'\n", backvt->displayname_lc); - return 1; - } - if (backvt->test_for_upstream(conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), conf)) - return 0; - else - return 1; - } - - /* - * Start up the connection. - */ - logctx = log_init(console_cli_logpolicy, conf); - { - char *error, *realhost; - /* nodelay is only useful if stdin is a terminal device */ - bool nodelay = conf_get_bool(conf, CONF_tcp_nodelay) && isatty(0); - - /* This is a good place for a fuzzer to fork us. */ -#ifdef __AFL_HAVE_MANUAL_CONTROL - __AFL_INIT(); -#endif - - error = backend_init(backvt, plink_seat, &backend, logctx, conf, - conf_get_str(conf, CONF_host), - conf_get_int(conf, CONF_port), - &realhost, nodelay, - conf_get_bool(conf, CONF_tcp_keepalives)); - if (error) { - fprintf(stderr, "Unable to open connection:\n%s\n", error); - sfree(error); - return 1; - } - ldisc_create(conf, NULL, backend, plink_seat); - sfree(realhost); - } - - /* - * 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); - seat_echoedit_update(plink_seat, 1, 1); - - cli_main_loop(plink_pw_setup, plink_pw_check, plink_continue, NULL); - - exitcode = backend_exitcode(backend); - 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/unix/printing.c b/unix/printing.c deleted file mode 100644 index 7f26ce002..000000000 --- a/unix/printing.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Printing interface for PuTTY. - */ - -#include -#include -#include "putty.h" - -struct printer_job_tag { - FILE *fp; -}; - -printer_job *printer_start_job(char *printer) -{ - printer_job *pj = snew(printer_job); - /* - * On Unix, we treat the printer string as the name of a - * command to pipe to - typically lpr, of course. - */ - pj->fp = popen(printer, "w"); - if (!pj->fp) { - sfree(pj); - pj = NULL; - } - return pj; -} - -void printer_job_data(printer_job *pj, const void *data, size_t 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 config-unix.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/unix/procnet.c b/unix/procnet.c deleted file mode 100644 index 093739567..000000000 --- a/unix/procnet.c +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Locally authenticate a TCP socket via /proc/net. - * - * Obviously, if a TCP connection comes from a different host, there's - * no way to find out the identity of the thing at the other end (or - * even really to assign that concept a meaning) except by the usual - * method of speaking a protocol over the socket itself which involves - * some form of (preferably cryptographic) authentication exchange. - * - * But if the connection comes from localhost, then on at least some - * operating systems, you can do better. On Linux, /proc/net/tcp and - * /proc/net/tcp6 list the full set of active TCP connection - * endpoints, and they list an owning uid for each one. So once you've - * accepted a connection to a listening socket and found that the - * other end of it is a localhost address, you can look up the _other_ - * endpoint in the right one of those files, and find out which uid - * owns it. - */ - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "misc.h" - -static ptrlen get_space_separated_field(ptrlen *string) -{ - const char *p = string->ptr, *end = p + string->len; - - while (p < end && isspace((unsigned char)*p)) - p++; - if (p == end) - return PTRLEN_LITERAL(""); - - const char *start = p; - while (p < end && !isspace((unsigned char)*p)) - p++; - *string = make_ptrlen(p, end - p); - return make_ptrlen(start, p - start); -} - -enum { GOT_LOCAL_UID = 1, GOT_REMOTE_UID = 2 }; - -/* - * Open a file formatted like /proc/net/tcp{,6}, and search it for - * both ends of a particular connection. - * - * The operands 'local' and 'remote' give the expected string - * representations of the local and remote addresses of the connection - * we're looking for. - * - * Return value is the bitwise OR of 1 if we found the local end of - * the connection and 2 if we found the remote. Each output uid_t - * parameter is filled in iff the corresponding bit is set in the - * return value. - */ -static int lookup_uids_in_procnet_file( - const char *path, ptrlen local, ptrlen remote, - uid_t *local_uid, uid_t *remote_uid) -{ - FILE *fp = NULL; - int toret = 0; - ptrlen line, field; - - enum { GF_LOCAL = 1, GF_REMOTE = 2, GF_UID = 4 }; - - fp = fopen(path, "r"); - if (!fp) - goto out; - - /* Expected indices of fields in /proc/net/tcp* */ - const int LOCAL_ADDR_INDEX = 1; - const int REMOTE_ADDR_INDEX = 2; - const int UID_INDEX = 7; - - for (char *linez; (linez = chomp(fgetline(fp))) != NULL ;) { - line = ptrlen_from_asciz(linez); - int gotfields = 0; - ptrlen local_addr = PTRLEN_LITERAL(""); - ptrlen remote_addr = PTRLEN_LITERAL(""); - long uid = -1; - - for (int i = 0; (field = get_space_separated_field(&line)).len != 0; - i++) { - - if (i == LOCAL_ADDR_INDEX) { - gotfields |= GF_LOCAL; - local_addr = field; - } else if (i == REMOTE_ADDR_INDEX) { - gotfields |= GF_REMOTE; - remote_addr = field; - } else if (i == UID_INDEX) { - uid = 0; - for (const char *p = field.ptr, *end = p + field.len; - p < end; p++) { - if (!isdigit((unsigned char)*p)) { - uid = -1; - break; - } - int dval = *p - '0'; - if (uid > LONG_MAX/10) { - uid = -1; - break; - } - uid *= 10; - if (uid > LONG_MAX - dval) { - uid = -1; - break; - } - uid += dval; - } - - gotfields |= GF_UID; - } - } - - if (gotfields == (GF_LOCAL | GF_REMOTE | GF_UID)) { - if (ptrlen_eq_ptrlen(local_addr, local) && - ptrlen_eq_ptrlen(remote_addr, remote)) { - *local_uid = uid; - toret |= GOT_LOCAL_UID; - } - if (ptrlen_eq_ptrlen(local_addr, remote) && - ptrlen_eq_ptrlen(remote_addr, local)) { - *remote_uid = uid; - toret |= GOT_REMOTE_UID; - } - } - - sfree(linez); - } - - fclose(fp); - fp = NULL; - - out: - if (fp) - fclose(fp); - return toret; -} - -static const char *procnet_path(int family) -{ - switch (family) { - case AF_INET: return "/proc/net/tcp"; - case AF_INET6: return "/proc/net/tcp6"; - default: return NULL; - } -} - -static char *format_sockaddr(const void *addr, int family) -{ - if (family == AF_INET) { - const struct sockaddr_in *a = (const struct sockaddr_in *)addr; - assert(a->sin_family == family); - /* Linux /proc/net formats the IP address native-endian, so we - * don't use ntohl */ - return dupprintf("%08X:%04X", a->sin_addr.s_addr, ntohs(a->sin_port)); - } else if (family == AF_INET6) { - struct sockaddr_in6 *a = (struct sockaddr_in6 *)addr; - assert(a->sin6_family == family); - - strbuf *sb = strbuf_new(); - - const uint32_t *addrwords = (const uint32_t *)a->sin6_addr.s6_addr; - for (int i = 0; i < 4; i++) - put_fmt(sb, "%08X", addrwords[i]); - put_fmt(sb, ":%04X", ntohs(a->sin6_port)); - - return strbuf_to_str(sb); - } else { - return NULL; - } -} - -bool socket_peer_is_same_user(int fd) -{ - struct sockaddr_storage addr; - socklen_t addrlen; - int family; - bool toret = false; - char *local = NULL, *remote = NULL; - const char *path; - - addrlen = sizeof(addr); - if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0) - goto out; - family = addr.ss_family; - if ((path = procnet_path(family)) == NULL) - goto out; - local = format_sockaddr(&addr, family); - if (!local) - goto out; - - addrlen = sizeof(addr); - if (getpeername(fd, (struct sockaddr *)&addr, &addrlen) != 0) - goto out; - if (addr.ss_family != family) - goto out; - remote = format_sockaddr(&addr, family); - if (!remote) - goto out; - - ptrlen locpl = ptrlen_from_asciz(local); - ptrlen rempl = ptrlen_from_asciz(remote); - - /* - * Check that _both_ end of the socket are the uid we expect, as a - * sanity check on the /proc/net file being reasonable at all. - */ - uid_t our_uid = getuid(); - uid_t local_uid = -1, remote_uid = -1; - int got = lookup_uids_in_procnet_file( - path, locpl, rempl, &local_uid, &remote_uid); - if (got == (GOT_LOCAL_UID | GOT_REMOTE_UID) && - local_uid == our_uid && remote_uid == our_uid) - toret = true; - - out: - sfree(local); - sfree(remote); - return toret; -} diff --git a/unix/psocks.c b/unix/psocks.c deleted file mode 100644 index 6b30dc168..000000000 --- a/unix/psocks.c +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Main program for Unix psocks. - */ - -#include -#include - -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "psocks.h" - -typedef struct PsocksDataSinkPopen { - stdio_sink sink[2]; - PsocksDataSink pds; -} PsocksDataSinkPopen; - -static void popen_free(PsocksDataSink *pds) -{ - PsocksDataSinkPopen *pdsp = container_of(pds, PsocksDataSinkPopen, pds); - for (size_t i = 0; i < 2; i++) - pclose(pdsp->sink[i].fp); - sfree(pdsp); -} - -static PsocksDataSink *open_pipes( - const char *cmd, const char *const *direction_args, - const char *index_arg, char **err) -{ - FILE *fp[2]; - char *errmsg = NULL; - - for (size_t i = 0; i < 2; i++) { - /* No escaping needed: the provided command is already - * shell-quoted, and our extra arguments are simple */ - char *command = dupprintf("%s %s %s", cmd, - direction_args[i], index_arg); - - fp[i] = popen(command, "w"); - sfree(command); - - if (!fp[i]) { - if (!errmsg) - errmsg = dupprintf("%s", strerror(errno)); - } - } - - if (errmsg) { - for (size_t i = 0; i < 2; i++) - if (fp[i]) - pclose(fp[i]); - *err = errmsg; - return NULL; - } - - PsocksDataSinkPopen *pdsp = snew(PsocksDataSinkPopen); - - for (size_t i = 0; i < 2; i++) { - setvbuf(fp[i], NULL, _IONBF, 0); - stdio_sink_init(&pdsp->sink[i], fp[i]); - pdsp->pds.s[i] = BinarySink_UPCAST(&pdsp->sink[i]); - } - - pdsp->pds.free = popen_free; - - return &pdsp->pds; -} - -static int signalpipe[2] = { -1, -1 }; -static void sigchld(int signum) -{ - if (write(signalpipe[1], "x", 1) <= 0) - /* not much we can do about it */; -} - -static pid_t subcommand_pid = -1; - -static bool still_running = true; - -static void start_subcommand(strbuf *args) -{ - pid_t pid; - - /* - * Set up the pipe we'll use to tell us about SIGCHLD. - */ - if (pipe(signalpipe) < 0) { - perror("pipe"); - exit(1); - } - putty_signal(SIGCHLD, sigchld); - - /* - * Make an array of argument pointers that execvp will like. - */ - size_t nargs = 0; - for (size_t i = 0; i < args->len; i++) - if (args->s[i] == '\0') - nargs++; - - char **exec_args = snewn(nargs + 1, char *); - char *p = args->s; - for (size_t a = 0; a < nargs; a++) { - exec_args[a] = p; - size_t len = strlen(p); - assert(len < args->len - (p - args->s)); - p += 1 + len; - } - exec_args[nargs] = NULL; - - pid = fork(); - if (pid < 0) { - perror("fork"); - exit(1); - } else if (pid == 0) { - execvp(exec_args[0], exec_args); - perror("exec"); - _exit(127); - } else { - subcommand_pid = pid; - sfree(exec_args); - } -} - -static const PsocksPlatform platform = { - open_pipes, - start_subcommand, -}; - -static bool psocks_pw_setup(void *ctx, pollwrapper *pw) -{ - if (signalpipe[0] >= 0) - pollwrap_add_fd_rwx(pw, signalpipe[0], SELECT_R); - return true; -} - -static void psocks_pw_check(void *ctx, pollwrapper *pw) -{ - if (signalpipe[0] >= 0 && - pollwrap_check_fd_rwx(pw, signalpipe[0], SELECT_R)) { - while (true) { - int status; - pid_t pid = waitpid(-1, &status, WNOHANG); - if (pid <= 0) - break; - if (pid == subcommand_pid) - still_running = false; - } - } -} - -static bool psocks_continue(void *ctx, bool found_any_fd, - bool ran_any_callback) -{ - return still_running; -} - -int main(int argc, char **argv) -{ - psocks_state *ps = psocks_new(&platform); - psocks_cmdline(ps, argc, argv); - - sk_init(); - uxsel_init(); - psocks_start(ps); - - cli_main_loop(psocks_pw_setup, psocks_pw_check, psocks_continue, NULL); -} diff --git a/unix/psusan.c b/unix/psusan.c deleted file mode 100644 index f5f7ed2a6..000000000 --- a/unix/psusan.c +++ /dev/null @@ -1,421 +0,0 @@ -/* - * 'psusan': Pseudo Ssh for Untappable, Separately Authenticated Networks - * - * This is a standalone application that speaks on its standard I/O - * (or a listening Unix-domain socket) the server end of the bare - * ssh-connection protocol used by PuTTY's connection sharing. - * - * The idea of this tool is that you can use it to communicate across - * any 8-bit-clean data channel between two inconveniently separated - * domains, provided the channel is already (as the name suggests) - * adequately secured against eavesdropping and modification and - * already authenticated as the right user. - * - * If you're sitting at one end of such a channel and want to type - * commands into the other end, the most obvious thing to do is to run - * a terminal session directly over it. But if you run psusan at one - * end, and a PuTTY (or compatible) client at the other end, then you - * not only get a single terminal session: you get all the other SSH - * amenities, like the ability to spawn extra terminal sessions, - * forward ports or X11 connections, even forward an SSH agent. - * - * There are a surprising number of channels of that kind; see the man - * page for some examples. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "mpint.h" -#include "ssh.h" -#include "ssh/server.h" - -void modalfatalbox(const 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 nonfatal(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "ERROR: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); -} - -char *platform_default_s(const char *name) -{ - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} - -FontSpec *platform_default_fontspec(const char *name) -{ - return fontspec_new_default(); -} - -Filename *platform_default_filename(const char *name) -{ - return filename_from_str(""); -} - -char *x_get_default(const char *key) -{ - return NULL; /* this is a stub */ -} - -void old_keyfile_warning(void) { } - -void timer_change_notify(unsigned long next) -{ -} - -char *platform_get_x_display(void) { return NULL; } - -void make_unix_sftp_filehandle_key(void *vdata, size_t size) -{ - /* psusan runs without a random number generator, so we can't make - * this up by random_read. Fortunately, psusan is also - * non-adversarial, so it's safe to generate this trivially. */ - unsigned char *data = (unsigned char *)vdata; - for (size_t i = 0; i < size; i++) - data[i] = (unsigned)rand() / ((unsigned)RAND_MAX / 256); -} - -static bool verbose; - -struct server_instance { - unsigned id; - LogPolicy logpolicy; -}; - -static void log_to_stderr(unsigned id, const char *msg) -{ - if (!verbose) - return; - if (id != (unsigned)-1) - fprintf(stderr, "#%u: ", id); - fputs(msg, stderr); - fputc('\n', stderr); - fflush(stderr); -} - -static void server_eventlog(LogPolicy *lp, const char *event) -{ - struct server_instance *inst = container_of( - lp, struct server_instance, logpolicy); - if (verbose) - log_to_stderr(inst->id, event); -} - -static void server_logging_error(LogPolicy *lp, const char *event) -{ - struct server_instance *inst = container_of( - lp, struct server_instance, logpolicy); - log_to_stderr(inst->id, event); /* unconditional */ -} - -static int server_askappend( - LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) -{ - return 2; /* always overwrite (FIXME: could make this a cmdline option) */ -} - -static const LogPolicyVtable server_logpolicy_vt = { - .eventlog = server_eventlog, - .askappend = server_askappend, - .logging_error = server_logging_error, - .verbose = null_lp_verbose_no, -}; - -static void show_help(FILE *fp) -{ - fputs("usage: psusan [options]\n" - "options: --listen SOCKETPATH listen for connections on a Unix-domain socket\n" - " --listen-once (with --listen) stop after one connection\n" - " --verbose print log messages to standard error\n" - " --sessiondir DIR cwd for session subprocess (default $HOME)\n" - " --sshlog FILE write ssh-connection packet log to FILE\n" - " --sshrawlog FILE write packets and raw data log to FILE\n" - "also: psusan --help show this text\n" - " psusan --version show version information\n", fp); -} - -static void show_version_and_exit(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("%s: %s\n%s\n", appname, ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); -} - -const bool buildinfo_gtk_relevant = false; - -static bool listening = false, listen_once = false; -static bool finished = false; -void server_instance_terminated(LogPolicy *lp) -{ - struct server_instance *inst = container_of( - lp, struct server_instance, logpolicy); - - if (listening && !listen_once) { - log_to_stderr(inst->id, "connection terminated"); - } else { - finished = true; - } - - sfree(inst); -} - -bool psusan_continue(void *ctx, bool fd, bool cb) -{ - return !finished; -} - -static bool longoptarg(const char *arg, const char *expected, - const char **val, int *argcp, char ***argvp) -{ - int len = strlen(expected); - if (memcmp(arg, expected, len)) - return false; - if (arg[len] == '=') { - *val = arg + len + 1; - return true; - } else if (arg[len] == '\0') { - if (--*argcp > 0) { - *val = *++*argvp; - return true; - } else { - fprintf(stderr, "%s: option %s expects an argument\n", - appname, expected); - exit(1); - } - } - return false; -} - -static bool longoptnoarg(const char *arg, const char *expected) -{ - int len = strlen(expected); - if (memcmp(arg, expected, len)) - return false; - if (arg[len] == '=') { - fprintf(stderr, "%s: option %s expects no argument\n", - appname, expected); - exit(1); - } else if (arg[len] == '\0') { - return true; - } - return false; -} - -struct server_config { - Conf *conf; - const SshServerConfig *ssc; - - unsigned next_id; - - Socket *listening_socket; - Plug listening_plug; -}; - -static Plug *server_conn_plug( - struct server_config *cfg, struct server_instance **inst_out) -{ - struct server_instance *inst = snew(struct server_instance); - - memset(inst, 0, sizeof(*inst)); - - inst->id = cfg->next_id++; - inst->logpolicy.vt = &server_logpolicy_vt; - - if (inst_out) - *inst_out = inst; - - return ssh_server_plug( - cfg->conf, cfg->ssc, NULL, 0, NULL, NULL, - &inst->logpolicy, &unix_live_sftpserver_vt); -} - -static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - log_to_stderr(-1, error_msg); -} - -static void server_closing(Plug *plug, PlugCloseType type, - const char *error_msg) -{ - if (type != PLUGCLOSE_NORMAL) - log_to_stderr(-1, error_msg); -} - -static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) -{ - struct server_config *cfg = container_of( - p, struct server_config, listening_plug); - Socket *s; - const char *err; - - struct server_instance *inst; - - if (listen_once) { - if (!cfg->listening_socket) /* in case of rapid double-accept */ - return 1; - sk_close(cfg->listening_socket); - cfg->listening_socket = NULL; - } - - Plug *plug = server_conn_plug(cfg, &inst); - s = constructor(ctx, plug); - if ((err = sk_socket_error(s)) != NULL) - return 1; - - SocketPeerInfo *pi = sk_peer_info(s); - - char *msg = dupprintf("new connection from %s", pi->log_text); - log_to_stderr(inst->id, msg); - sfree(msg); - sk_free_peer_info(pi); - - sk_set_frozen(s, false); - ssh_server_start(plug, s); - return 0; -} - -static const PlugVtable server_plugvt = { - .log = server_log, - .closing = server_closing, - .accepting = server_accepting, -}; - -unsigned auth_methods(AuthPolicy *ap) -{ return 0; } -bool auth_none(AuthPolicy *ap, ptrlen username) -{ return false; } -int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password, - ptrlen *new_password_opt) -{ return 0; } -bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob) -{ return false; } -RSAKey *auth_publickey_ssh1( - AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus) -{ return NULL; } -AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username) -{ return NULL; } -int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses) -{ return -1; } -char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username) -{ return NULL; } -bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response) -{ return false; } -bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method) -{ return false; } - -int main(int argc, char **argv) -{ - const char *listen_socket = NULL; - - SshServerConfig ssc; - - Conf *conf = make_ssh_server_conf(); - - memset(&ssc, 0, sizeof(ssc)); - - ssc.application_name = "PSUSAN"; - ssc.session_starting_dir = getenv("HOME"); - ssc.bare_connection = true; - - while (--argc > 0) { - const char *arg = *++argv; - const char *val; - - if (longoptnoarg(arg, "--help")) { - show_help(stdout); - exit(0); - } else if (longoptnoarg(arg, "--version")) { - show_version_and_exit(); - } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) { - verbose = true; - } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) { - ssc.session_starting_dir = val; - } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) || - longoptarg(arg, "-sshlog", &val, &argc, &argv)) { - Filename *logfile = filename_from_str(val); - conf_set_filename(conf, CONF_logfilename, logfile); - filename_free(logfile); - conf_set_int(conf, CONF_logtype, LGTYP_PACKETS); - conf_set_int(conf, CONF_logxfovr, LGXF_OVR); - } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) || - longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) { - Filename *logfile = filename_from_str(val); - conf_set_filename(conf, CONF_logfilename, logfile); - filename_free(logfile); - conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW); - conf_set_int(conf, CONF_logxfovr, LGXF_OVR); - } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) { - listen_socket = val; - } else if (!strcmp(arg, "--listen-once")) { - listen_once = true; - } else { - fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg); - exit(1); - } - } - - sk_init(); - uxsel_init(); - - struct server_config scfg; - scfg.conf = conf; - scfg.ssc = &ssc; - scfg.next_id = 0; - - if (listen_socket) { - listening = true; - scfg.listening_plug.vt = &server_plugvt; - SockAddr *addr = unix_sock_addr(listen_socket); - scfg.listening_socket = new_unix_listener(addr, &scfg.listening_plug); - char *msg = dupprintf("listening on Unix socket %s", listen_socket); - log_to_stderr(-1, msg); - sfree(msg); - } else { - struct server_instance *inst; - Plug *plug = server_conn_plug(&scfg, &inst); - ssh_server_start(plug, make_fd_socket(0, 1, -1, NULL, 0, plug)); - log_to_stderr(inst->id, "running directly on stdio"); - } - - cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check, - psusan_continue, NULL); - - return 0; -} diff --git a/unix/pterm-config-xpm.c b/unix/pterm-config-xpm.c deleted file mode 100644 index 92835c158..000000000 --- a/unix/pterm-config-xpm.c +++ /dev/null @@ -1,150 +0,0 @@ -/* 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/unix/pterm-xpm.c b/unix/pterm-xpm.c deleted file mode 100644 index aea5e4e20..000000000 --- a/unix/pterm-xpm.c +++ /dev/null @@ -1,143 +0,0 @@ -/* 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/unix/pterm.bundle b/unix/pterm.bundle deleted file mode 100644 index 0d7012160..000000000 --- a/unix/pterm.bundle +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - ${env:PUTTY_GTK_PREFIX_FROM_MAKEFILE} - - - gtk+-3.0 - - ${project}/../osxlaunch - - - ${project}/pterm.plist - - - ${project}/../ptermapp - - - - ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/immodules/*.so - - - - ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/printbackends/*.so - - - - ${prefix}/share/themes/Adwaita - - - - ${project}/../icons/Pterm.icns - - - - Adwaita - - - diff --git a/unix/pterm.c b/unix/pterm.c deleted file mode 100644 index 87dd3e991..000000000 --- a/unix/pterm.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * pterm main program. - */ - -#include -#include - -#include "putty.h" - -const bool use_event_log = false; /* pterm doesn't need it */ -const bool new_session = false, saved_sessions = false; /* or these */ -const bool dup_check_launchable = false; /* no need to check host name - * in conf */ -const bool use_pty_argv = true; - -const unsigned cmdline_tooltype = TOOLTYPE_NONNETWORK; - -/* window.c will call this, and in pterm it's not needed */ -void noise_ultralight(NoiseSourceId id, unsigned long data) { } - -const struct BackendVtable *select_backend(Conf *conf) -{ - return &pty_backend; -} - -void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx) -{ - /* - * 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. So we do that and then just immediately call the - * post-dialog function with a positive result. - */ - conf_set_int(conf, CONF_protocol, -1); - after(afterctx, 1); -} - -void cleanup_exit(int code) -{ - exit(code); -} - -void setup(bool single) -{ - settings_set_default_protocol(-1); - - if (single) - pty_pre_init(); -} diff --git a/unix/pterm.plist b/unix/pterm.plist deleted file mode 100644 index 03e57b880..000000000 --- a/unix/pterm.plist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - CFBundleIconFile - Pterm.icns - CFBundleName - Pterm - CFBundleDisplayName - Pterm - CFBundleExecutable - Pterm - CFBundleVersion - Unidentified build - CFBundleShortVersionString - Unidentified build - CFBundleDevelopmentRegion - en - CFBundleIdentifier - org.tartarus.projects.putty.macpterm - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - CFBundleSignature - ???? - NSHumanReadableCopyright - © 1997-2015 Simon Tatham. All rights reserved. - - diff --git a/unix/pty.c b/unix/pty.c deleted file mode 100644 index c03328f70..000000000 --- a/unix/pty.c +++ /dev/null @@ -1,1614 +0,0 @@ -/* - * Pseudo-tty backend for pterm. - */ - -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if HAVE_UTMP_H -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "ssh/server.h" /* to check the prototypes of server-needed things */ -#include "tree234.h" - -#ifndef OMIT_UTMP -#include -#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 - -typedef struct Pty 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 */ - -typedef struct PtyFd { - int fd; - Pty *pty; -} PtyFd; - -struct Pty { - Conf *conf; - - int master_fd, slave_fd; - int pipefds[6]; - PtyFd fds[3]; - int master_i, master_o, master_e; - - Seat *seat; - size_t output_backlog; - char name[FILENAME_MAX]; - pid_t child_pid; - int term_width, term_height; - bool child_dead, finished; - int exit_code; - bufchain output_data; - bool pending_eof; - Backend backend; -}; - -#define PTY_MAX_BACKLOG 32768 - -/* - * We store all the (active) PtyFd structures in a tree sorted by fd, - * so that when we get an uxsel notification we know which backend - * instance is the owner of the pty that caused it, and then we can - * find out which fd is the relevant one too. - */ -static int ptyfd_compare(void *av, void *bv) -{ - PtyFd *a = (PtyFd *)av; - PtyFd *b = (PtyFd *)bv; - - if (a->fd < b->fd) - return -1; - else if (a->fd > b->fd) - return +1; - return 0; -} - -static int ptyfd_find(void *av, void *bv) -{ - int a = *(int *)av; - PtyFd *b = (PtyFd *)bv; - - if (a < b->fd) - return -1; - else if (a > b->fd) - return +1; - return 0; -} - -static tree234 *ptyfds = NULL; - -/* - * We also have a tree of Pty structures themselves, 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 bool 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; - -char *pty_osx_envrestore_prefix; - -static void pty_close(Pty *pty); -static void pty_try_write(Pty *pty); - -#ifndef OMIT_UTMP -static void setup_utmp(char *ttyname, char *location) -{ -#if HAVE_LASTLOG - struct lastlog lastlog_entry; - FILE *lastlog; -#endif - struct passwd *pw; - struct timeval tv; - - pw = getpwuid(getuid()); - if (!pw) - return; /* can't stamp utmp if we don't have a username */ - memset(&utmp_entry, 0, sizeof(utmp_entry)); - utmp_entry.ut_type = USER_PROCESS; - utmp_entry.ut_pid = getpid(); -#if __GNUC__ >= 8 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wstringop-truncation" -#endif // __GNUC__ >= 8 - 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)); -#if __GNUC__ >= 8 -# pragma GCC diagnostic pop -#endif // __GNUC__ >= 8 - /* - * 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(); - -#if HAVE_UPDWTMPX - /* Reportedly, AIX 5.1 has and pututxline(), but no - * updwtmpx(). */ - updwtmpx(WTMPX_FILE, &utmp_entry); -#endif - -#if 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 = true; - -} - -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; - -#if HAVE_UPDWTMPX - updwtmpx(WTMPX_FILE, &utmp_entry); -#endif - - 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 = false; /* 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 */; -} - -static void pty_setup_sigchld_handler(void) -{ - static bool setup = false; - if (!setup) { - putty_signal(SIGCHLD, sigchld_handler); - setup = true; - } -} - -#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 - ; - -#if HAVE_POSIX_OPENPT -#ifdef SET_NONBLOCK_VIA_OPENPT - /* - * OS X, as of 10.10 at least, doesn't permit me to set O_NONBLOCK - * on pty master fds via the usual fcntl mechanism. Fortunately, - * it does let me work around this by adding O_NONBLOCK to the - * posix_openpt flags parameter, which isn't a documented use of - * the API but seems to work. So we'll do that for now. - */ - pty->master_fd = posix_openpt(flags | O_NONBLOCK); -#else - pty->master_fd = posix_openpt(flags); -#endif - - 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 - -#ifndef SET_NONBLOCK_VIA_OPENPT - nonblock(pty->master_fd); -#endif -} - -static Pty *new_pty_struct(void) -{ - Pty *pty = snew(Pty); - memset(pty, 0, sizeof(Pty)); - pty->conf = NULL; - pty->pending_eof = false; - bufchain_init(&pty->output_data); - return 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) -{ -#ifndef NO_PTY_PRE_INIT - - Pty *pty; - -#ifndef OMIT_UTMP - pid_t pid; - int pipefd[2]; -#endif - - pty = single_pty = new_pty_struct(); - - /* set the child signal handler straight away; it needs to be set - * before we ever fork. */ - pty_setup_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. */ - { -#if HAVE_SETRESUID && HAVE_SETRESGID - 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 - } - -#endif /* NO_PTY_PRE_INIT */ - -} - -static void pty_try_wait(void); -static void pty_uxsel_setup(Pty *pty); - -static void pty_real_select_result(Pty *pty, int fd, int event, int status) -{ - char buf[4096]; - int ret; - bool 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. - */ - pty->child_dead = true; - del234(ptys_by_pid, pty); - pty->exit_code = status; - - /* - * If this is an ordinary pty session, this is also the - * moment to terminate the whole backend. - * - * We _could_ instead 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. - */ - if (pty->master_fd >= 0) - finished = true; - } - } else { - if (event == SELECT_R) { - bool is_stdout = (fd == pty->master_o); - - ret = read(fd, buf, sizeof(buf)); - - /* - * Treat EIO on a pty master as equivalent to EOF (because - * that's how the kernel seems to report the event where - * the last process connected to the other end of the pty - * went away). - */ - if (fd == pty->master_fd && ret < 0 && errno == EIO) - ret = 0; - - if (ret == 0) { - /* - * EOF on this input fd, so to begin with, we may as - * well close it, and remove all references to it in - * the pty's fd fields. - */ - uxsel_del(fd); - close(fd); - if (pty->master_fd == fd) - pty->master_fd = -1; - if (pty->master_o == fd) - pty->master_o = -1; - if (pty->master_e == fd) - pty->master_e = -1; - - if (is_stdout) { - /* - * We assume a clean exit if the pty (or stdout - * pipe) 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; - pty_try_wait(); /* one last effort to collect exit code */ - if (!pty->child_dead) - pty->exit_code = 0; - } - } else if (ret < 0) { - perror("read pty master"); - exit(1); - } else if (ret > 0) { - pty->output_backlog = seat_output( - pty->seat, !is_stdout, buf, ret); - pty_uxsel_setup(pty); - } - } else if (event == SELECT_W) { - /* - * Attempt to send data down the pty. - */ - pty_try_write(pty); - } - } - - if (finished && !pty->finished) { - int close_on_exit; - int i; - - for (i = 0; i < 3; i++) - if (pty->fds[i].fd >= 0) - uxsel_del(pty->fds[i].fd); - - pty_close(pty); - - 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; - if (WIFEXITED(pty->exit_code)) { - message = dupprintf( - "\r\n[pterm: process terminated with exit code %d]\r\n", - WEXITSTATUS(pty->exit_code)); - } else if (WIFSIGNALED(pty->exit_code)) { -#if !HAVE_STRSIGNAL - message = dupprintf( - "\r\n[pterm: process terminated on signal %d]\r\n", - WTERMSIG(pty->exit_code)); -#else - message = dupprintf( - "\r\n[pterm: process terminated on signal %d (%s)]\r\n", - WTERMSIG(pty->exit_code), - strsignal(WTERMSIG(pty->exit_code))); -#endif - } else { - /* _Shouldn't_ happen, but if it does, a vague message - * is better than no message at all */ - message = dupprintf("\r\n[pterm: process terminated]\r\n"); - } - seat_stdout_pl(pty->seat, ptrlen_from_asciz(message)); - sfree(message); - } - - seat_eof(pty->seat); - seat_notify_remote_exit(pty->seat); - } -} - -static void pty_try_wait(void) -{ - Pty *pty; - pid_t pid; - int status; - - do { - pid = waitpid(-1, &status, WNOHANG); - - pty = find234(ptys_by_pid, &pid, pty_find_by_pid); - - if (pty) - pty_real_select_result(pty, -1, -1, status); - } while (pid > 0); -} - -void pty_select_result(int fd, int event) -{ - if (fd == pty_signal_pipe[0]) { - char c[1]; - - if (read(pty_signal_pipe[0], c, 1) <= 0) - /* ignore error */; - /* ignore its value; it'll be `x' */ - - pty_try_wait(); - } else { - PtyFd *ptyfd = find234(ptyfds, &fd, ptyfd_find); - - if (ptyfd) - pty_real_select_result(ptyfd->pty, fd, event, 0); - } -} - -static void pty_uxsel_setup_fd(Pty *pty, int fd) -{ - int rwx = 0; - - if (fd < 0) - return; - - /* read from standard output and standard error pipes, assuming - * we're not too backlogged */ - if ((pty->master_o == fd || pty->master_e == fd) && - pty->output_backlog < PTY_MAX_BACKLOG) - rwx |= SELECT_R; - /* write to standard input pipe if we have any data */ - if (pty->master_i == fd && bufchain_size(&pty->output_data)) - rwx |= SELECT_W; - - uxsel_set(fd, rwx, pty_select_result); -} - -static void pty_uxsel_setup(Pty *pty) -{ - /* - * We potentially have three separate fds here, but on the other - * hand, some of them might be the same (if they're a pty master). - * So we can't just call uxsel_set(master_o, SELECT_R) and then - * uxsel_set(master_i, SELECT_W), without the latter potentially - * undoing the work of the former if master_o == master_i. - * - * Instead, here we call a single uxsel on each one of these fds - * (if it exists at all), and for each one, check it against all - * three to see which bits to set. - */ - pty_uxsel_setup_fd(pty, pty->master_o); - pty_uxsel_setup_fd(pty, pty->master_e); - pty_uxsel_setup_fd(pty, pty->master_i); - - /* - * 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], SELECT_R, pty_select_result); -} - -static void copy_ttymodes_into_termios( - struct termios *attrs, struct ssh_ttymodes modes) -{ -#define TTYMODE_CHAR(name, ssh_opcode, cc_index) { \ - if (modes.have_mode[ssh_opcode]) { \ - unsigned value = modes.mode_val[ssh_opcode]; \ - /* normalise wire value of 255 to local _POSIX_VDISABLE */ \ - attrs->c_cc[cc_index] = (value == 255 ? \ - _POSIX_VDISABLE : value); \ - } \ - } - -#define TTYMODE_FLAG(flagval, ssh_opcode, field, flagmask) { \ - if (modes.have_mode[ssh_opcode]) { \ - attrs->c_##field##flag &= ~flagmask; \ - if (modes.mode_val[ssh_opcode]) \ - attrs->c_##field##flag |= flagval; \ - } \ - } - -#define TTYMODES_LOCAL_ONLY /* omit any that this platform doesn't know */ -#include "ssh/ttymode-list.h" - -#undef TTYMODES_LOCAL_ONLY -#undef TTYMODE_CHAR -#undef TTYMODE_FLAG - - if (modes.have_mode[TTYMODE_ISPEED]) - cfsetispeed(attrs, modes.mode_val[TTYMODE_ISPEED]); - if (modes.have_mode[TTYMODE_OSPEED]) - cfsetospeed(attrs, modes.mode_val[TTYMODE_OSPEED]); -} - -/* - * The main setup function for the pty back end. This doesn't match - * the signature of backend_init(), partly because it has to be able - * to take extra arguments such as an argv array, and also because - * once we're changing the type signature _anyway_ we can discard the - * stuff that's not really applicable to this backend like host names - * and port numbers. - */ -Backend *pty_backend_create( - Seat *seat, LogContext *logctx, Conf *conf, char **argv, const char *cmd, - struct ssh_ttymodes ttymodes, bool pipes_instead, const char *dir, - const char *const *env_vars_to_unset) -{ - int slavefd; - pid_t pid, pgrp; -#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ - bool got_windowid; - long windowid; -#endif - Pty *pty; - int i; - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - if (single_pty) { - pty = single_pty; - assert(pty->conf == NULL); - } else { - pty = new_pty_struct(); - pty->master_fd = pty->slave_fd = -1; -#ifndef OMIT_UTMP - pty_stamped_utmp = false; -#endif - } - for (i = 0; i < 6; i++) - pty->pipefds[i] = -1; - for (i = 0; i < 3; i++) { - pty->fds[i].fd = -1; - pty->fds[i].pty = 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->seat = seat; - pty->backend.vt = &pty_backend; - - pty->conf = conf_copy(conf); - pty->term_width = conf_get_int(conf, CONF_width); - pty->term_height = conf_get_int(conf, CONF_height); - - if (!ptyfds) - ptyfds = newtree234(ptyfd_compare); - - if (pipes_instead) { - if (pty->master_fd >= 0) { - /* If somehow we've got a pty master already and don't - * need it, throw it away! */ - close(pty->master_fd); -#ifndef OMIT_UTMP - if (pty_utmp_helper_pipe >= 0) { - close(pty_utmp_helper_pipe); /* don't need this either */ - pty_utmp_helper_pipe = -1; - } -#endif - } - - - for (i = 0; i < 6; i += 2) { - if (pipe(pty->pipefds + i) < 0) { - backend_free(&pty->backend); - return NULL; - } - } - - pty->fds[0].fd = pty->master_i = pty->pipefds[1]; - pty->fds[1].fd = pty->master_o = pty->pipefds[2]; - pty->fds[2].fd = pty->master_e = pty->pipefds[4]; - - add234(ptyfds, &pty->fds[0]); - add234(ptyfds, &pty->fds[1]); - add234(ptyfds, &pty->fds[2]); - } else { - if (pty->master_fd < 0) - pty_open_master(pty); - -#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_bool(conf, CONF_stamp_utmp)) { - /* We're not stamping utmp, so just let the child - * process die that was waiting to unstamp it later. */ - close(pty_utmp_helper_pipe); - pty_utmp_helper_pipe = -1; - } else { - const char *location = seat_get_x_display(pty->seat); - 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 - - pty->master_i = pty->master_fd; - pty->master_o = pty->master_fd; - pty->master_e = -1; - - pty->fds[0].fd = pty->master_fd; - add234(ptyfds, &pty->fds[0]); - } - -#ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ - got_windowid = seat_get_windowid(pty->seat, &windowid); -#endif - - /* - * Set up the signal handler to catch SIGCHLD, if pty_pre_init - * didn't already do it. - */ - pty_setup_sigchld_handler(); - - /* - * Fork and execute the command. - */ - pid = fork(); - if (pid < 0) { - perror("fork"); - exit(1); - } - - if (pid == 0) { - struct termios attrs; - - /* - * We are the child. - */ - - if (pty_osx_envrestore_prefix) { - int plen = strlen(pty_osx_envrestore_prefix); - extern char **environ; - char **ep; - - restart_osx_env_restore: - for (ep = environ; *ep; ep++) { - char *e = *ep; - - if (!strncmp(e, pty_osx_envrestore_prefix, plen)) { - bool unset = (e[plen] == 'u'); - char *pname = dupprintf("%.*s", (int)strcspn(e, "="), e); - char *name = pname + plen + 1; - char *value = e + strcspn(e, "="); - if (*value) value++; - value = dupstr(value); - if (unset) - unsetenv(name); - else - setenv(name, value, 1); - unsetenv(pname); - sfree(pname); - sfree(value); - goto restart_osx_env_restore; - } - } - } - - pgrp = getpid(); - - if (pipes_instead) { - int i; - dup2(pty->pipefds[0], 0); - dup2(pty->pipefds[3], 1); - dup2(pty->pipefds[5], 2); - for (i = 0; i < 6; i++) - close(pty->pipefds[i]); - - setsid(); - } else { - 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 - tcsetpgrp(0, pgrp); - - /* - * Set up configuration-dependent termios settings on the new - * pty. Linux would have let us do this on the pty master - * before we forked, but that fails on OS X, so we do it here - * instead. - */ - if (tcgetattr(0, &attrs) == 0) { - /* - * Set the backspace character to be whichever of ^H and - * ^? is specified by bksp_is_delete. - */ - attrs.c_cc[VERASE] = conf_get_bool(conf, CONF_bksp_is_delete) - ? '\177' : '\010'; - - /* - * Set the IUTF8 bit iff the character set is UTF-8. - */ -#ifdef IUTF8 - if (seat_is_utf8(seat)) - attrs.c_iflag |= IUTF8; - else - attrs.c_iflag &= ~IUTF8; -#endif - - copy_ttymodes_into_termios(&attrs, ttymodes); - - tcsetattr(0, TCSANOW, &attrs); - } - } - - setpgid(pgrp, pgrp); - if (!pipes_instead) { - int ptyfd = open(pty->name, O_WRONLY, 0); - if (ptyfd >= 0) - close(ptyfd); - } - setpgid(pgrp, pgrp); - - if (env_vars_to_unset) - for (const char *const *p = env_vars_to_unset; *p; p++) - unsetenv(*p); - - if (!pipes_instead) { - 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 */ - if (got_windowid) { - 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 = seat_get_x_display(pty->seat); - if (x_display) { - char *x_display_env_var = dupprintf("DISPLAY=%s", x_display); - putenv(x_display_env_var); - /* As above, we don't free this. */ - } else { - unsetenv("DISPLAY"); - } - } -#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); - 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. - */ - } - } - - if (dir) { - if (chdir(dir) < 0) { - /* Ignore the error - nothing we can sensibly do about it, - * and our existing cwd is as good a fallback as any. */ - } - } - - /* - * 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. SIGPIPE was also - * (potentially) blocked by us during 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(SIGPIPE, false); - if (argv || cmd) { - /* - * If we were given a separated argument list, try to exec - * it. - */ - if (argv) { - execvp(argv[0], argv); - } - /* - * Otherwise, if we were given a single command string, - * try passing that to $SHELL -c. - * - * In the case of pterm, this system of fallbacks 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). We arrange this by setting argv to the full - * argument list, and also setting cmd to the single - * element of argv if it's a length-1 list. - * - * 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'. - * - * In other use cases, a caller can set only one of argv - * and cmd to get a fixed handling of the input. - */ - if (cmd) { - char *shell = getenv("SHELL"); - if (shell) - execl(shell, shell, "-c", cmd, (void *)NULL); - } - } else { - const char *shell = getenv("SHELL"); - if (!shell) - shell = "/bin/sh"; - char *shellname; - if (conf_get_bool(conf, CONF_login_shell)) { - const char *p = strrchr(shell, '/'); - p = p ? p+1 : shell; - shellname = dupprintf("-%s", p); - } else - shellname = (char *)shell; - execl(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); - if (pty->pipefds[0] >= 0) { - close(pty->pipefds[0]); - pty->pipefds[0] = -1; - } - if (pty->pipefds[3] >= 0) { - close(pty->pipefds[3]); - pty->pipefds[3] = -1; - } - if (pty->pipefds[5] >= 0) { - close(pty->pipefds[5]); - pty->pipefds[5] = -1; - } - add234(ptys_by_pid, pty); - } - - pty_uxsel_setup(pty); - - return &pty->backend; -} - -/* - * This is the pty backend's _official_ init method, for BackendVtable - * purposes. Its job is just to be an API converter, ignoring the - * irrelevant input parameters and making up auxiliary outputs. Also - * it gets the argv array from the global variable pty_argv, expecting - * that it will have been invoked by pterm. - */ -static char *pty_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - const char *cmd = NULL; - struct ssh_ttymodes modes; - - memset(&modes, 0, sizeof(modes)); - - if (pty_argv && pty_argv[0] && !pty_argv[1]) - cmd = pty_argv[0]; - - assert(vt == &pty_backend); - *backend_handle = pty_backend_create( - seat, logctx, conf, pty_argv, cmd, modes, false, NULL, NULL); - *realhost = dupstr(""); - return NULL; -} - -static void pty_reconfig(Backend *be, Conf *conf) -{ - Pty *pty = container_of(be, Pty, backend); - /* - * 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(Backend *be) -{ - Pty *pty = container_of(be, Pty, backend); - int i; - - pty_close(pty); - - /* Either of these may fail `not found'. That's fine with us. */ - del234(ptys_by_pid, pty); - for (i = 0; i < 3; i++) - if (pty->fds[i].fd >= 0) - del234(ptyfds, &pty->fds[i]); - - bufchain_clear(&pty->output_data); - - 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) -{ - ssize_t ret; - - assert(pty->master_i >= 0); - - while (bufchain_size(&pty->output_data) > 0) { - ptrlen data = bufchain_prefix(&pty->output_data); - ret = write(pty->master_i, data.ptr, 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); - } - - if (pty->pending_eof && bufchain_size(&pty->output_data) == 0) { - /* This should only happen if pty->master_i is a pipe that - * doesn't alias either output fd */ - assert(pty->master_i != pty->master_o); - assert(pty->master_i != pty->master_e); - uxsel_del(pty->master_i); - close(pty->master_i); - pty->master_i = -1; - pty->pending_eof = false; - } - - pty_uxsel_setup(pty); -} - -/* - * Called to send data down the pty. - */ -static void pty_send(Backend *be, const char *buf, size_t len) -{ - Pty *pty = container_of(be, Pty, backend); - - if (pty->master_i < 0 || pty->pending_eof) - return; /* ignore all writes if fd closed */ - - bufchain_add(&pty->output_data, buf, len); - pty_try_write(pty); -} - -static void pty_close(Pty *pty) -{ - int i; - - if (pty->master_o >= 0) - uxsel_del(pty->master_o); - if (pty->master_e >= 0) - uxsel_del(pty->master_e); - if (pty->master_i >= 0) - uxsel_del(pty->master_i); - - if (pty->master_fd >= 0) { - close(pty->master_fd); - pty->master_fd = -1; - } - for (i = 0; i < 6; i++) { - if (pty->pipefds[i] >= 0) - close(pty->pipefds[i]); - pty->pipefds[i] = -1; - } - pty->master_i = pty->master_o = pty->master_e = -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 size_t pty_sendbuffer(Backend *be) -{ - Pty *pty = container_of(be, Pty, backend); - return bufchain_size(&pty->output_data); -} - -/* - * Called to set the size of the window - */ -static void pty_size(Backend *be, int width, int height) -{ - Pty *pty = container_of(be, Pty, backend); - struct winsize size; - int xpixel = 0, ypixel = 0; - - pty->term_width = width; - pty->term_height = height; - - if (pty->master_fd < 0) - return; - - seat_get_window_pixel_size(pty->seat, &xpixel, &ypixel); - - size.ws_row = (unsigned short)pty->term_height; - size.ws_col = (unsigned short)pty->term_width; - size.ws_xpixel = (unsigned short)xpixel; - size.ws_ypixel = (unsigned short)ypixel; - ioctl(pty->master_fd, TIOCSWINSZ, (void *)&size); - return; -} - -/* - * Send special codes. - */ -static void pty_special(Backend *be, SessionSpecialCode code, int arg) -{ - Pty *pty = container_of(be, Pty, backend); - - if (code == SS_BRK) { - if (pty->master_fd >= 0) - tcsendbreak(pty->master_fd, 0); - return; - } - - if (code == SS_EOF) { - if (pty->master_i >= 0 && pty->master_i != pty->master_fd) { - pty->pending_eof = true; - pty_try_write(pty); - } - return; - } - - { - int sig = -1; - - #define SIGNAL_SUB(name) if (code == SS_SIG ## name) sig = SIG ## name; - #define SIGNAL_MAIN(name, text) SIGNAL_SUB(name) - #define SIGNALS_LOCAL_ONLY - #include "ssh/signal-list.h" - #undef SIGNAL_SUB - #undef SIGNAL_MAIN - #undef SIGNALS_LOCAL_ONLY - - if (sig != -1) { - if (!pty->child_dead) - kill(pty->child_pid, sig); - return; - } - } - - return; -} - -/* - * Return a list of the special codes that make sense in this - * protocol. - */ -static const SessionSpecial *pty_get_specials(Backend *be) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - /* - * 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 bool pty_connected(Backend *be) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - return true; -} - -static bool pty_sendok(Backend *be) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - return true; -} - -static void pty_unthrottle(Backend *be, size_t backlog) -{ - Pty *pty = container_of(be, Pty, backend); - pty->output_backlog = backlog; - pty_uxsel_setup(pty); -} - -static bool pty_ldisc(Backend *be, int option) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - return false; /* neither editing nor echoing */ -} - -static void pty_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - /* This is a stub. */ -} - -static int pty_exitcode(Backend *be) -{ - Pty *pty = container_of(be, Pty, backend); - if (!pty->finished) - return -1; /* not dead yet */ - else if (WIFSIGNALED(pty->exit_code)) - return 128 + WTERMSIG(pty->exit_code); - else - return WEXITSTATUS(pty->exit_code); -} - -int pty_backend_exit_signum(Backend *be) -{ - Pty *pty = container_of(be, Pty, backend); - - if (!pty->finished || !WIFSIGNALED(pty->exit_code)) - return -1; - - return WTERMSIG(pty->exit_code); -} - -ptrlen pty_backend_exit_signame(Backend *be, char **aux_msg) -{ - *aux_msg = NULL; - - int sig = pty_backend_exit_signum(be); - if (sig < 0) - return PTRLEN_LITERAL(""); - - #define SIGNAL_SUB(s) { \ - if (sig == SIG ## s) \ - return PTRLEN_LITERAL(#s); \ - } - #define SIGNAL_MAIN(s, desc) SIGNAL_SUB(s) - #define SIGNALS_LOCAL_ONLY - #include "ssh/signal-list.h" - #undef SIGNAL_MAIN - #undef SIGNAL_SUB - #undef SIGNALS_LOCAL_ONLY - - *aux_msg = dupprintf("untranslatable signal number %d: %s", - sig, strsignal(sig)); - return PTRLEN_LITERAL("HUP"); /* need some kind of default */ -} - -static int pty_cfg_info(Backend *be) -{ - /* Pty *pty = container_of(be, Pty, backend); */ - return 0; -} - -const BackendVtable pty_backend = { - .init = pty_init, - .free = pty_free, - .reconfig = pty_reconfig, - .send = pty_send, - .sendbuffer = pty_sendbuffer, - .size = pty_size, - .special = pty_special, - .get_specials = pty_get_specials, - .connected = pty_connected, - .exitcode = pty_exitcode, - .sendok = pty_sendok, - .ldisc_option_state = pty_ldisc, - .provide_ldisc = pty_provide_ldisc, - .unthrottle = pty_unthrottle, - .cfg_info = pty_cfg_info, - .id = "pty", - .displayname_tc = "pty", - .displayname_lc = "pty", - .protocol = -1, -}; diff --git a/unix/putty-config-xpm.c b/unix/putty-config-xpm.c deleted file mode 100644 index 34c5d4910..000000000 --- a/unix/putty-config-xpm.c +++ /dev/null @@ -1,150 +0,0 @@ -/* 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/unix/putty-xpm.c b/unix/putty-xpm.c deleted file mode 100644 index 56d16bee1..000000000 --- a/unix/putty-xpm.c +++ /dev/null @@ -1,147 +0,0 @@ -/* 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/unix/putty.bundle b/unix/putty.bundle deleted file mode 100644 index d635c4f25..000000000 --- a/unix/putty.bundle +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - ${env:PUTTY_GTK_PREFIX_FROM_MAKEFILE} - - - gtk+-3.0 - - ${project}/../osxlaunch - - - ${project}/putty.plist - - - ${project}/../puttyapp - - - - ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/immodules/*.so - - - - ${prefix}/lib/${gtkdir}/${pkg:${gtk}:gtk_binary_version}/printbackends/*.so - - - - ${prefix}/share/themes/Adwaita - - - - ${project}/../icons/PuTTY.icns - - - - Adwaita - - - diff --git a/unix/putty.c b/unix/putty.c deleted file mode 100644 index a96217e30..000000000 --- a/unix/putty.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Unix PuTTY main program. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "ssh.h" -#include "storage.h" - -#include "gtkcompat.h" - -/* - * Stubs to avoid pty.c needing to be linked in. - */ -const bool use_pty_argv = false; -char **pty_argv; /* never used */ -char *pty_osx_envrestore_prefix; - -/* - * Clean up and exit. - */ -void cleanup_exit(int code) -{ - /* - * Clean up. - */ - sk_cleanup(); - random_save_seed(); - exit(code); -} - -const struct BackendVtable *select_backend(Conf *conf) -{ - const struct BackendVtable *vt = - backend_vt_from_proto(conf_get_int(conf, CONF_protocol)); - assert(vt != NULL); - return vt; -} - -void initial_config_box(Conf *conf, post_dialog_fn_t after, void *afterctx) -{ - char *title = dupcat(appname, " Configuration"); - create_config_box(title, conf, false, 0, after, afterctx); - sfree(title); -} - -const bool use_event_log = true, new_session = true, saved_sessions = true; -const bool dup_check_launchable = true; - -/* - * 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 bool share_can_be_downstream = true; -const bool share_can_be_upstream = true; - -const unsigned cmdline_tooltype = - TOOLTYPE_HOST_ARG | - TOOLTYPE_PORT_ARG | - TOOLTYPE_NO_VERBOSE_OPTION; - -void setup(bool single) -{ - sk_init(); - settings_set_default_protocol(be_default_protocol); - /* Find the appropriate default port. */ - { - const struct BackendVtable *vt = - backend_vt_from_proto(be_default_protocol); - settings_set_default_port(0); /* illegal */ - if (vt) - settings_set_default_port(vt->default_port); - } -} diff --git a/unix/putty.plist b/unix/putty.plist deleted file mode 100644 index 9ec6b7a67..000000000 --- a/unix/putty.plist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - CFBundleIconFile - PuTTY.icns - CFBundleName - PuTTY - CFBundleDisplayName - PuTTY - CFBundleExecutable - PuTTY - CFBundleVersion - Unidentified build - CFBundleShortVersionString - Unidentified build - CFBundleDevelopmentRegion - en - CFBundleIdentifier - org.tartarus.projects.putty.macputty - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - APPL - CFBundleSignature - ???? - NSHumanReadableCopyright - © 1997-2015 Simon Tatham. All rights reserved. - - diff --git a/unix/serial.c b/unix/serial.c deleted file mode 100644 index 1e20dd203..000000000 --- a/unix/serial.c +++ /dev/null @@ -1,596 +0,0 @@ -/* - * 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 Serial; -struct Serial { - Seat *seat; - LogContext *logctx; - int fd; - bool finished; - size_t inbufsize; - bufchain output_data; - Backend backend; -}; - -/* - * 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 void serial_select_result(int fd, int event); -static void serial_uxsel_setup(Serial *serial); -static void serial_try_write(Serial *serial); - -static char *serial_configure(Serial *serial, Conf *conf) -{ - struct termios options; - int bflag, bval, speed, flow, parity; - const char *str; - - if (serial->fd < 0) - return dupstr("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); - logeventf(serial->logctx, "Configuring baud rate %d", bval); - - 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 dupstr("Invalid number of data bits " - "(need 5, 6, 7 or 8)"); - } - logeventf(serial->logctx, "Configuring %d data bits", - conf_get_int(conf, CONF_serdatabits)); - - if (conf_get_int(conf, CONF_serstopbits) >= 4) { - options.c_cflag |= CSTOPB; - } else { - options.c_cflag &= ~CSTOPB; - } - logeventf(serial->logctx, "Configuring %s", - (options.c_cflag & CSTOPB ? "2 stop bits" : "1 stop bit")); - - 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"; - logeventf(serial->logctx, "Configuring %s flow control", str); - - /* 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"; - } - logeventf(serial->logctx, "Configuring %s parity", str); - - 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 dupprintf("Configuring serial port: %s", strerror(errno)); - - 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 char *serial_init(const BackendVtable *vt, Seat *seat, - Backend **backend_handle, LogContext *logctx, - Conf *conf, const char *host, int port, - char **realhost, bool nodelay, bool keepalive) -{ - Serial *serial; - char *err; - char *line; - - /* No local authentication phase in this protocol */ - seat_set_trust_status(seat, false); - - serial = snew(Serial); - memset(serial, 0, sizeof(Serial)); - serial->backend.vt = vt; - *backend_handle = &serial->backend; - - serial->seat = seat; - serial->logctx = logctx; - serial->finished = false; - serial->inbufsize = 0; - bufchain_init(&serial->output_data); - - line = conf_get_str(conf, CONF_serline); - logeventf(serial->logctx, "Opening serial device %s", line); - - serial->fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); - if (serial->fd < 0) - return dupprintf("Opening serial port '%s': %s", - line, strerror(errno)); - - 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. - */ - seat_update_specials_menu(serial->seat); - - return NULL; -} - -static void serial_close(Serial *serial) -{ - if (serial->fd >= 0) { - uxsel_del(serial->fd); - close(serial->fd); - serial->fd = -1; - } -} - -static void serial_free(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - - serial_close(serial); - - bufchain_clear(&serial->output_data); - - sfree(serial); -} - -static void serial_reconfig(Backend *be, Conf *conf) -{ - Serial *serial = container_of(be, Serial, backend); - - char *err = serial_configure(serial, conf); - if (err) { - /* - * FIXME: apart from freeing the dynamically allocated - * message, what should we do if this returns an error? - */ - sfree(err); - } -} - -static void serial_select_result(int fd, int event) -{ - Serial *serial; - char buf[4096]; - int ret; - bool finished = false; - - serial = find234(serial_by_fd, &fd, serial_find_by_fd); - - if (!serial) - return; /* 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; /* spurious */ -#endif -#ifdef EWOULDBLOCK - if (errno == EWOULDBLOCK) - return; /* spurious */ -#endif - perror("read serial port"); - exit(1); - } else if (ret > 0) { - serial->inbufsize = seat_stdout(serial->seat, 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; - - seat_notify_remote_exit(serial->seat); - } -} - -static void serial_uxsel_setup(Serial *serial) -{ - int rwx = 0; - - if (serial->inbufsize <= SERIAL_MAX_BACKLOG) - rwx |= SELECT_R; - if (bufchain_size(&serial->output_data)) - rwx |= SELECT_W; /* might also want to write to it */ - uxsel_set(serial->fd, rwx, serial_select_result); -} - -static void serial_try_write(Serial *serial) -{ - ssize_t ret; - - assert(serial->fd >= 0); - - while (bufchain_size(&serial->output_data) > 0) { - ptrlen data = bufchain_prefix(&serial->output_data); - ret = write(serial->fd, data.ptr, 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); - } - - seat_sent(serial->seat, bufchain_size(&serial->output_data)); - serial_uxsel_setup(serial); -} - -/* - * Called to send data down the serial connection. - */ -static void serial_send(Backend *be, const char *buf, size_t len) -{ - Serial *serial = container_of(be, Serial, backend); - - if (serial->fd < 0) - return; - - bufchain_add(&serial->output_data, buf, len); - serial_try_write(serial); -} - -/* - * Called to query the current sendability status. - */ -static size_t serial_sendbuffer(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - return bufchain_size(&serial->output_data); -} - -/* - * Called to set the size of the window - */ -static void serial_size(Backend *be, int width, int height) -{ - /* Do nothing! */ - return; -} - -/* - * Send serial special codes. - */ -static void serial_special(Backend *be, SessionSpecialCode code, int arg) -{ - Serial *serial = container_of(be, Serial, backend); - - if (serial->fd >= 0 && code == SS_BRK) { - tcsendbreak(serial->fd, 0); - logevent(serial->logctx, "Sending serial break at user request"); - } - - return; -} - -/* - * Return a list of the special codes that make sense in this - * protocol. - */ -static const SessionSpecial *serial_get_specials(Backend *be) -{ - static const struct SessionSpecial specials[] = { - {"Break", SS_BRK}, - {NULL, SS_EXITMENU} - }; - return specials; -} - -static bool serial_connected(Backend *be) -{ - return true; /* always connected */ -} - -static bool serial_sendok(Backend *be) -{ - return true; -} - -static void serial_unthrottle(Backend *be, size_t backlog) -{ - Serial *serial = container_of(be, Serial, backend); - serial->inbufsize = backlog; - serial_uxsel_setup(serial); -} - -static bool serial_ldisc(Backend *be, int option) -{ - /* - * Local editing and local echo are off by default. - */ - return false; -} - -static void serial_provide_ldisc(Backend *be, Ldisc *ldisc) -{ - /* This is a stub. */ -} - -static int serial_exitcode(Backend *be) -{ - Serial *serial = container_of(be, Serial, backend); - 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(Backend *be) -{ - return 0; -} - -const BackendVtable serial_backend = { - .init = serial_init, - .free = serial_free, - .reconfig = serial_reconfig, - .send = serial_send, - .sendbuffer = serial_sendbuffer, - .size = serial_size, - .special = serial_special, - .get_specials = serial_get_specials, - .connected = serial_connected, - .exitcode = serial_exitcode, - .sendok = serial_sendok, - .ldisc_option_state = serial_ldisc, - .provide_ldisc = serial_provide_ldisc, - .unthrottle = serial_unthrottle, - .cfg_info = serial_cfg_info, - .id = "serial", - .displayname_tc = "Serial", - .displayname_lc = "serial", - .protocol = PROT_SERIAL, - .serial_parity_mask = ((1 << SER_PAR_NONE) | - (1 << SER_PAR_ODD) | - (1 << SER_PAR_EVEN)), - .serial_flow_mask = ((1 << SER_FLOW_NONE) | - (1 << SER_FLOW_XONXOFF) | - (1 << SER_FLOW_RTSCTS)), -}; diff --git a/unix/sftp.c b/unix/sftp.c deleted file mode 100644 index dd25fa207..000000000 --- a/unix/sftp.c +++ /dev/null @@ -1,581 +0,0 @@ -/* - * sftp.c: the Unix-specific parts of PSFTP and PSCP. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "psftp.h" - -#if HAVE_GLOB_H -#include -#endif - -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 bool platform_uses_x11_unix_by_default = true; - -/* - * Default settings that are specific to PSFTP. - */ -char *platform_default_s(const char *name) -{ - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} - -FontSpec *platform_default_fontspec(const char *name) -{ - return fontspec_new_default(); -} - -Filename *platform_default_filename(const char *name) -{ - if (!strcmp(name, "LogFileName")) - return filename_from_str("putty.log"); - else - return filename_from_str(""); -} - -SeatPromptResult filexfer_get_userpass_input(Seat *seat, prompts_t *p) -{ - /* The file transfer tools don't support Restart Session, so we - * can just have a single static cmdline_get_passwd_input_state - * that's never reset */ - static cmdline_get_passwd_input_state cmdline_state = - CMDLINE_GET_PASSWD_INPUT_STATE_INIT; - - SeatPromptResult spr; - spr = cmdline_get_passwd_input(p, &cmdline_state, false); - if (spr.kind == SPRK_INCOMPLETE) - spr = console_get_userpass_input(p); - return spr; -} - -/* - * 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; - size_t 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. - */ - sgrowarray(buffer, size, size); - } -} - -struct RFile { - int fd; -}; - -RFile *open_existing_file(const char *name, uint64_t *size, - unsigned long *mtime, unsigned long *atime, - long *perms) -{ - int fd; - RFile *f; - - fd = open(name, O_RDONLY); - if (fd < 0) - return NULL; - - f = snew(RFile); - f->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 = statbuf.st_size; - - if (mtime) - *mtime = statbuf.st_mtime; - - if (atime) - *atime = statbuf.st_atime; - - if (perms) - *perms = statbuf.st_mode; - } - - return f; -} - -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(const char *name, long perms) -{ - int fd; - WFile *f; - - fd = open(name, O_CREAT | O_TRUNC | O_WRONLY, - (mode_t)(perms ? perms : 0666)); - if (fd < 0) - return NULL; - - f = snew(WFile); - f->fd = fd; - f->name = dupstr(name); - - return f; -} - - -WFile *open_existing_wfile(const char *name, uint64_t *size) -{ - int fd; - WFile *f; - - fd = open(name, O_APPEND | O_WRONLY); - if (fd < 0) - return NULL; - - f = snew(WFile); - f->fd = fd; - f->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 = statbuf.st_size; - } - - return f; -} - -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_t offset, int whence) -{ - int lseek_whence; - - 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, offset, lseek_whence) >= 0 ? 0 : -1; -} - -uint64_t get_file_posn(WFile *f) -{ - return lseek(f->fd, (off_t) 0, SEEK_CUR); -} - -int file_type(const 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(const char *name, const char **errmsg) -{ - DIR *dp = opendir(name); - if (!dp) { - *errmsg = strerror(errno); - return NULL; - } - - DirHandle *dir = snew(DirHandle); - dir->dir = dp; - return dir; -} - -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(const char *name, bool 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 { -#if HAVE_GLOB_H - 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; -#else - /* On a system without glob.h, we just have to return a - * failure code */ - return WCTYPE_NONEXISTENT; -#endif - } -} - -/* - * Actually return matching file names for a local wildcard. - */ -#if HAVE_GLOB_H -struct WildcardMatcher { - glob_t globbed; - int i; -}; -WildcardMatcher *begin_wildcard_matching(const char *name) { - WildcardMatcher *dir = snew(WildcardMatcher); - - if (glob(name, 0, NULL, &dir->globbed) < 0) { - sfree(dir); - return NULL; - } - - dir->i = 0; - - return dir; -} -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); -} -#else -WildcardMatcher *begin_wildcard_matching(const char *name) -{ - return NULL; -} -char *wildcard_get_filename(WildcardMatcher *dir) -{ - unreachable("Can't construct a valid WildcardMatcher without "); -} -void finish_wildcard_matching(WildcardMatcher *dir) -{ - unreachable("Can't construct a valid WildcardMatcher without "); -} -#endif - -char *stripslashes(const char *str, bool local) -{ - char *p; - - /* - * On Unix, we do the same thing regardless of the 'local' - * parameter. - */ - p = strrchr(str, '/'); - if (p) str = p+1; - - return (char *)str; -} - -bool vet_filename(const char *name) -{ - if (strchr(name, '/')) - return false; - - if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]))) - return false; - - return true; -} - -bool create_directory(const char *name) -{ - return mkdir(name, 0777) == 0; -} - -char *dir_file_cat(const char *dir, const char *file) -{ - ptrlen dir_pl = ptrlen_from_asciz(dir); - return dupcat( - dir, ptrlen_endswith(dir_pl, PTRLEN_LITERAL("/"), NULL) ? "" : "/", - file); -} - -/* - * Do a select() between all currently active network fds and - * optionally stdin, using cli_main_loop. - */ - -struct ssh_sftp_mainloop_ctx { - bool include_stdin, no_fds_ok; - int toret; -}; -static bool ssh_sftp_pw_setup(void *vctx, pollwrapper *pw) -{ - struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx; - int fdstate, rwx; - - if (!ctx->no_fds_ok && !toplevel_callback_pending() && - first_fd(&fdstate, &rwx) < 0) { - ctx->toret = -1; - return false; /* terminate cli_main_loop */ - } - - if (ctx->include_stdin) - pollwrap_add_fd_rwx(pw, 0, SELECT_R); - - return true; -} -static void ssh_sftp_pw_check(void *vctx, pollwrapper *pw) -{ - struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx; - - if (ctx->include_stdin && pollwrap_check_fd_rwx(pw, 0, SELECT_R)) - ctx->toret = 1; -} -static bool ssh_sftp_mainloop_continue(void *vctx, bool found_any_fd, - bool ran_any_callback) -{ - struct ssh_sftp_mainloop_ctx *ctx = (struct ssh_sftp_mainloop_ctx *)vctx; - if (ctx->toret != 0 || found_any_fd || ran_any_callback) - return false; /* finish the loop */ - return true; -} -static int ssh_sftp_do_select(bool include_stdin, bool no_fds_ok) -{ - struct ssh_sftp_mainloop_ctx ctx[1]; - ctx->include_stdin = include_stdin; - ctx->no_fds_ok = no_fds_ok; - ctx->toret = 0; - - cli_main_loop(ssh_sftp_pw_setup, ssh_sftp_pw_check, - ssh_sftp_mainloop_continue, ctx); - - return ctx->toret; -} - -/* - * 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(const char *prompt, bool no_fds_ok) -{ - char *buf; - size_t buflen, bufsize; - int 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) { - sgrowarray(buf, bufsize, buflen); - 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) {} - -void platform_psftp_pre_conn_setup(LogPolicy *lp) {} - -const bool buildinfo_gtk_relevant = false; - -/* - * 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/unix/sftpserver.c b/unix/sftpserver.c deleted file mode 100644 index fe3c2501c..000000000 --- a/unix/sftpserver.c +++ /dev/null @@ -1,703 +0,0 @@ -/* - * Implement the SftpServer abstraction, in the 'live' form (i.e. - * really operating on the Unix filesystem). - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "ssh/server.h" -#include "ssh/sftp.h" -#include "tree234.h" - -typedef struct UnixSftpServer UnixSftpServer; - -struct UnixSftpServer { - unsigned *fdseqs; - bool *fdsopen; - size_t fdsize; - - tree234 *dirhandles; - int last_dirhandle_index; - - char handlekey[8]; - - SftpServer srv; -}; - -struct uss_dirhandle { - int index; - DIR *dp; -}; - -#define USS_DIRHANDLE_SEQ (0xFFFFFFFFU) - -static int uss_dirhandle_cmp(void *av, void *bv) -{ - struct uss_dirhandle *a = (struct uss_dirhandle *)av; - struct uss_dirhandle *b = (struct uss_dirhandle *)bv; - if (a->index < b->index) - return -1; - if (a->index > b->index) - return +1; - return 0; -} - -static SftpServer *uss_new(const SftpServerVtable *vt) -{ - UnixSftpServer *uss = snew(UnixSftpServer); - - memset(uss, 0, sizeof(UnixSftpServer)); - - uss->dirhandles = newtree234(uss_dirhandle_cmp); - uss->srv.vt = vt; - - make_unix_sftp_filehandle_key(uss->handlekey, sizeof(uss->handlekey)); - - return &uss->srv; -} - -static void uss_free(SftpServer *srv) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - struct uss_dirhandle *udh; - - for (size_t i = 0; i < uss->fdsize; i++) - if (uss->fdsopen[i]) - close(i); - sfree(uss->fdseqs); - - while ((udh = delpos234(uss->dirhandles, 0)) != NULL) { - closedir(udh->dp); - sfree(udh); - } - - sfree(uss); -} - -static void uss_return_handle_raw( - UnixSftpServer *uss, SftpReplyBuilder *reply, int index, unsigned seq) -{ - unsigned char handlebuf[8]; - PUT_32BIT_MSB_FIRST(handlebuf, index); - PUT_32BIT_MSB_FIRST(handlebuf + 4, seq); - des_encrypt_xdmauth(uss->handlekey, handlebuf, 8); - fxp_reply_handle(reply, make_ptrlen(handlebuf, 8)); -} - -static bool uss_decode_handle( - UnixSftpServer *uss, ptrlen handle, int *index, unsigned *seq) -{ - unsigned char handlebuf[8]; - - if (handle.len != 8) - return false; - memcpy(handlebuf, handle.ptr, 8); - des_decrypt_xdmauth(uss->handlekey, handlebuf, 8); - *index = toint(GET_32BIT_MSB_FIRST(handlebuf)); - *seq = GET_32BIT_MSB_FIRST(handlebuf + 4); - return true; -} - -static void uss_return_new_handle( - UnixSftpServer *uss, SftpReplyBuilder *reply, int fd) -{ - assert(fd >= 0); - if (fd >= uss->fdsize) { - size_t old_size = uss->fdsize; - sgrowarray(uss->fdseqs, uss->fdsize, fd); - uss->fdsopen = sresize(uss->fdsopen, uss->fdsize, bool); - while (old_size < uss->fdsize) { - uss->fdseqs[old_size] = 0; - uss->fdsopen[old_size] = false; - old_size++; - } - } - assert(!uss->fdsopen[fd]); - uss->fdsopen[fd] = true; - if (++uss->fdseqs[fd] == USS_DIRHANDLE_SEQ) - uss->fdseqs[fd] = 0; - uss_return_handle_raw(uss, reply, fd, uss->fdseqs[fd]); -} - -static int uss_try_lookup_fd(UnixSftpServer *uss, ptrlen handle) -{ - int fd; - unsigned seq; - if (!uss_decode_handle(uss, handle, &fd, &seq) || - fd < 0 || fd >= uss->fdsize || - !uss->fdsopen[fd] || uss->fdseqs[fd] != seq) - return -1; - - return fd; -} - -static int uss_lookup_fd(UnixSftpServer *uss, SftpReplyBuilder *reply, - ptrlen handle) -{ - int fd = uss_try_lookup_fd(uss, handle); - if (fd < 0) - fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle"); - return fd; -} - -static void uss_return_new_dirhandle( - UnixSftpServer *uss, SftpReplyBuilder *reply, DIR *dp) -{ - struct uss_dirhandle *udh = snew(struct uss_dirhandle); - udh->index = uss->last_dirhandle_index++; - udh->dp = dp; - struct uss_dirhandle *added = add234(uss->dirhandles, udh); - assert(added == udh); - uss_return_handle_raw(uss, reply, udh->index, USS_DIRHANDLE_SEQ); -} - -static struct uss_dirhandle *uss_try_lookup_dirhandle( - UnixSftpServer *uss, ptrlen handle) -{ - struct uss_dirhandle key, *udh; - unsigned seq; - - if (!uss_decode_handle(uss, handle, &key.index, &seq) || - seq != USS_DIRHANDLE_SEQ || - (udh = find234(uss->dirhandles, &key, NULL)) == NULL) - return NULL; - - return udh; -} - -static struct uss_dirhandle *uss_lookup_dirhandle( - UnixSftpServer *uss, SftpReplyBuilder *reply, ptrlen handle) -{ - struct uss_dirhandle *udh = uss_try_lookup_dirhandle(uss, handle); - if (!udh) - fxp_reply_error(reply, SSH_FX_FAILURE, "invalid file handle"); - return udh; -} - -static void uss_error(UnixSftpServer *uss, SftpReplyBuilder *reply) -{ - unsigned code = SSH_FX_FAILURE; - switch (errno) { - case ENOENT: - code = SSH_FX_NO_SUCH_FILE; - break; - case EPERM: - code = SSH_FX_PERMISSION_DENIED; - break; - } - fxp_reply_error(reply, code, strerror(errno)); -} - -static void uss_realpath(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *inpath = mkstr(path); - char *outpath = realpath(inpath, NULL); - free(inpath); - - if (!outpath) { - uss_error(uss, reply); - } else { - fxp_reply_simple_name(reply, ptrlen_from_asciz(outpath)); - free(outpath); - } -} - -static void uss_open(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, unsigned flags, struct fxp_attrs attrs) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - int openflags = 0; - if (!((SSH_FXF_READ | SSH_FXF_WRITE) &~ flags)) - openflags |= O_RDWR; - else if (flags & SSH_FXF_WRITE) - openflags |= O_WRONLY; - else if (flags & SSH_FXF_READ) - openflags |= O_RDONLY; - if (flags & SSH_FXF_APPEND) - openflags |= O_APPEND; - if (flags & SSH_FXF_CREAT) - openflags |= O_CREAT; - if (flags & SSH_FXF_TRUNC) - openflags |= O_TRUNC; - if (flags & SSH_FXF_EXCL) - openflags |= O_EXCL; - - char *pathstr = mkstr(path); - int fd = open(pathstr, openflags, GET_PERMISSIONS(attrs, 0777)); - free(pathstr); - - if (fd < 0) { - uss_error(uss, reply); - } else { - uss_return_new_handle(uss, reply, fd); - } -} - -static void uss_opendir(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *pathstr = mkstr(path); - DIR *dp = opendir(pathstr); - free(pathstr); - - if (!dp) { - uss_error(uss, reply); - } else { - uss_return_new_dirhandle(uss, reply, dp); - } -} - -static void uss_close(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - int fd; - struct uss_dirhandle *udh; - - if ((udh = uss_try_lookup_dirhandle(uss, handle)) != NULL) { - closedir(udh->dp); - del234(uss->dirhandles, udh); - sfree(udh); - fxp_reply_ok(reply); - } else if ((fd = uss_lookup_fd(uss, reply, handle)) >= 0) { - close(fd); - assert(0 <= fd && fd <= uss->fdsize); - uss->fdsopen[fd] = false; - fxp_reply_ok(reply); - } - /* if both failed, uss_lookup_fd will have filled in an error response */ -} - -static void uss_mkdir(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, struct fxp_attrs attrs) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *pathstr = mkstr(path); - int status = mkdir(pathstr, GET_PERMISSIONS(attrs, 0777)); - free(pathstr); - - if (status < 0) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static void uss_rmdir(SftpServer *srv, SftpReplyBuilder *reply, ptrlen path) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *pathstr = mkstr(path); - int status = rmdir(pathstr); - free(pathstr); - - if (status < 0) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static void uss_remove(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *pathstr = mkstr(path); - int status = unlink(pathstr); - free(pathstr); - - if (status < 0) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static void uss_rename(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen srcpath, ptrlen dstpath) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *srcstr = mkstr(srcpath), *dststr = mkstr(dstpath); - int status = rename(srcstr, dststr); - free(srcstr); - free(dststr); - - if (status < 0) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static struct fxp_attrs uss_translate_struct_stat(const struct stat *st) -{ - struct fxp_attrs attrs; - - attrs.flags = (SSH_FILEXFER_ATTR_SIZE | - SSH_FILEXFER_ATTR_PERMISSIONS | - SSH_FILEXFER_ATTR_UIDGID | - SSH_FILEXFER_ATTR_ACMODTIME); - - attrs.size = st->st_size; - attrs.permissions = st->st_mode; - attrs.uid = st->st_uid; - attrs.gid = st->st_gid; - attrs.atime = st->st_atime; - attrs.mtime = st->st_mtime; - - return attrs; -} - -static void uss_reply_struct_stat(SftpReplyBuilder *reply, - const struct stat *st) -{ - fxp_reply_attrs(reply, uss_translate_struct_stat(st)); -} - -static void uss_stat(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, bool follow_symlinks) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - struct stat st; - - char *pathstr = mkstr(path); - int status = (follow_symlinks ? stat : lstat) (pathstr, &st); - free(pathstr); - - if (status < 0) { - uss_error(uss, reply); - } else { - uss_reply_struct_stat(reply, &st); - } -} - -static void uss_fstat(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - struct stat st; - int fd; - - if ((fd = uss_lookup_fd(uss, reply, handle)) < 0) - return; - int status = fstat(fd, &st); - - if (status < 0) { - uss_error(uss, reply); - } else { - uss_reply_struct_stat(reply, &st); - } -} - -#if !HAVE_FUTIMES -static inline int futimes(int fd, const struct timeval tv[2]) -{ - /* If the OS doesn't support futimes(3) then we have to pretend it - * always returns failure */ - errno = EINVAL; - return -1; -} -#endif - -/* - * The guts of setstat and fsetstat, macroised so that they can call - * fchown(fd,...) or chown(path,...) depending on parameters. - */ -#define SETSTAT_GUTS(api_prefix, api_arg, attrs, success) do \ - { \ - if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) \ - if (api_prefix(truncate)(api_arg, attrs.size) < 0) \ - success = false; \ - if (attrs.flags & SSH_FILEXFER_ATTR_UIDGID) \ - if (api_prefix(chown)(api_arg, attrs.uid, attrs.gid) < 0) \ - success = false; \ - if (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) \ - if (api_prefix(chmod)(api_arg, attrs.permissions) < 0) \ - success = false; \ - if (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME) { \ - struct timeval tv[2]; \ - tv[0].tv_sec = attrs.atime; \ - tv[1].tv_sec = attrs.mtime; \ - tv[0].tv_usec = tv[1].tv_usec = 0; \ - if (api_prefix(utimes)(api_arg, tv) < 0) \ - success = false; \ - } \ - } while (0) - -#define PATH_PREFIX(func) func -#define FD_PREFIX(func) f ## func - -static void uss_setstat(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen path, struct fxp_attrs attrs) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - - char *pathstr = mkstr(path); - bool success = true; - SETSTAT_GUTS(PATH_PREFIX, pathstr, attrs, success); - free(pathstr); - - if (!success) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static void uss_fsetstat(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, struct fxp_attrs attrs) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - int fd; - - if ((fd = uss_lookup_fd(uss, reply, handle)) < 0) - return; - - bool success = true; - SETSTAT_GUTS(FD_PREFIX, fd, attrs, success); - - if (!success) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static void uss_read(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, unsigned length) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - int fd; - char *buf; - - if ((fd = uss_lookup_fd(uss, reply, handle)) < 0) - return; - - if ((buf = malloc(length)) == NULL) { - /* A rare case in which I bother to check malloc failure, - * because in this case we can localise the problem easily by - * turning it into a failure response from this one sftp - * request */ - fxp_reply_error(reply, SSH_FX_FAILURE, - "Out of memory for read buffer"); - return; - } - - char *p = buf; - - int status = lseek(fd, offset, SEEK_SET); - if (status >= 0 || errno == ESPIPE) { - bool seekable = (status >= 0); - while (length > 0) { - status = read(fd, p, length); - if (status <= 0) - break; - - unsigned bytes_read = status; - assert(bytes_read <= length); - length -= bytes_read; - p += bytes_read; - - if (!seekable) { - /* - * If the seek failed because the file is fundamentally - * not a seekable kind of thing, abandon this loop after - * one attempt, i.e. we just read whatever we could get - * and we don't mind returning a short buffer. - */ - } - } - } - - if (status < 0) { - uss_error(uss, reply); - } else if (p == buf) { - fxp_reply_error(reply, SSH_FX_EOF, "End of file"); - } else { - fxp_reply_data(reply, make_ptrlen(buf, p - buf)); - } - - free(buf); -} - -static void uss_write(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, uint64_t offset, ptrlen data) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - int fd; - - if ((fd = uss_lookup_fd(uss, reply, handle)) < 0) - return; - - const char *p = data.ptr; - unsigned length = data.len; - - int status = lseek(fd, offset, SEEK_SET); - if (status >= 0 || errno == ESPIPE) { - - while (length > 0) { - status = write(fd, p, length); - assert(status != 0); - if (status < 0) - break; - - unsigned bytes_written = status; - assert(bytes_written <= length); - length -= bytes_written; - p += bytes_written; - } - } - - if (status < 0) { - uss_error(uss, reply); - } else { - fxp_reply_ok(reply); - } -} - -static void uss_readdir(SftpServer *srv, SftpReplyBuilder *reply, - ptrlen handle, int max_entries, bool omit_longname) -{ - UnixSftpServer *uss = container_of(srv, UnixSftpServer, srv); - struct dirent *de; - struct uss_dirhandle *udh; - - if ((udh = uss_lookup_dirhandle(uss, reply, handle)) == NULL) - return; - - errno = 0; - de = readdir(udh->dp); - if (!de) { - if (errno == 0) { - fxp_reply_error(reply, SSH_FX_EOF, "End of directory"); - } else { - uss_error(uss, reply); - } - } else { - ptrlen longname = PTRLEN_LITERAL(""); - char *longnamebuf = NULL; - struct fxp_attrs attrs = no_attrs; - -#if HAVE_FSTATAT && HAVE_DIRFD - struct stat st; - if (!fstatat(dirfd(udh->dp), de->d_name, &st, AT_SYMLINK_NOFOLLOW)) { - char perms[11], *uidbuf = NULL, *gidbuf = NULL; - struct passwd *pwd; - struct group *grp; - const char *user, *group; - struct tm tm; - - attrs = uss_translate_struct_stat(&st); - - if (!omit_longname) { - - strcpy(perms, "----------"); - switch (st.st_mode & S_IFMT) { - case S_IFBLK: perms[0] = 'b'; break; - case S_IFCHR: perms[0] = 'c'; break; - case S_IFDIR: perms[0] = 'd'; break; - case S_IFIFO: perms[0] = 'p'; break; - case S_IFLNK: perms[0] = 'l'; break; - case S_IFSOCK: perms[0] = 's'; break; - } - if (st.st_mode & S_IRUSR) - perms[1] = 'r'; - if (st.st_mode & S_IWUSR) - perms[2] = 'w'; - if (st.st_mode & S_IXUSR) - perms[3] = (st.st_mode & S_ISUID ? 's' : 'x'); - else - perms[3] = (st.st_mode & S_ISUID ? 'S' : '-'); - if (st.st_mode & S_IRGRP) - perms[4] = 'r'; - if (st.st_mode & S_IWGRP) - perms[5] = 'w'; - if (st.st_mode & S_IXGRP) - perms[6] = (st.st_mode & S_ISGID ? 's' : 'x'); - else - perms[6] = (st.st_mode & S_ISGID ? 'S' : '-'); - if (st.st_mode & S_IROTH) - perms[7] = 'r'; - if (st.st_mode & S_IWOTH) - perms[8] = 'w'; - if (st.st_mode & S_IXOTH) - perms[9] = 'x'; - - if ((pwd = getpwuid(st.st_uid)) != NULL) - user = pwd->pw_name; - else - user = uidbuf = dupprintf("%u", (unsigned)st.st_uid); - if ((grp = getgrgid(st.st_gid)) != NULL) - group = grp->gr_name; - else - group = gidbuf = dupprintf("%u", (unsigned)st.st_gid); - - tm = *localtime(&st.st_mtime); - - longnamebuf = dupprintf( - "%s %3u %-8s %-8s %8"PRIuMAX" %.3s %2d %02d:%02d %s", - perms, (unsigned)st.st_nlink, user, group, - (uintmax_t)st.st_size, - (&"JanFebMarAprMayJunJulAugSepOctNovDec"[3*tm.tm_mon]), - tm.tm_mday, tm.tm_hour, tm.tm_min, de->d_name); - longname = ptrlen_from_asciz(longnamebuf); - - sfree(uidbuf); - sfree(gidbuf); - } - } -#endif - - /* FIXME: be able to return more than one, in which case we - * must also check max_entries */ - fxp_reply_name_count(reply, 1); - fxp_reply_full_name(reply, ptrlen_from_asciz(de->d_name), - longname, attrs); - - sfree(longnamebuf); - } -} - -const SftpServerVtable unix_live_sftpserver_vt = { - .new = uss_new, - .free = uss_free, - .realpath = uss_realpath, - .open = uss_open, - .opendir = uss_opendir, - .close = uss_close, - .mkdir = uss_mkdir, - .rmdir = uss_rmdir, - .remove = uss_remove, - .rename = uss_rename, - .stat = uss_stat, - .fstat = uss_fstat, - .setstat = uss_setstat, - .fsetstat = uss_fsetstat, - .read = uss_read, - .write = uss_write, - .readdir = uss_readdir, -}; diff --git a/unix/sharing.c b/unix/sharing.c deleted file mode 100644 index 8db2d71ed..000000000 --- a/unix/sharing.c +++ /dev/null @@ -1,370 +0,0 @@ -/* - * Unix implementation of SSH connection-sharing IPC setup. - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "tree234.h" -#include "putty.h" -#include "network.h" -#include "proxy/proxy.h" -#include "ssh.h" - -#define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare" -#define SALT_FILENAME "salt" -#define SALT_SIZE 64 -#ifndef PIPE_BUF -#define PIPE_BUF _POSIX_PIPE_BUF -#endif - -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_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. - */ - random_read(saltbuf, SALT_SIZE); - 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. - */ - { - unsigned char digest[32]; - char retbuf[65]; - - ssh_hash *h = ssh_hash_new(&ssh_sha256); - put_string(h, saltbuf, SALT_SIZE); - put_stringz(h, pi_name); - ssh_hash_final(h, 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, - bool can_upstream, bool 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"); - 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, false, true, false, false, - downplug, conf, NULL); - 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"); - remove(filename); - sfree(filename); - - filename = dupcat(dirname, "/lock"); - 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/unix/storage.c b/unix/storage.c deleted file mode 100644 index 042887f26..000000000 --- a/unix/storage.c +++ /dev/null @@ -1,974 +0,0 @@ -/* - * storage.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, INDEX_HOSTCADIR, INDEX_HOSTCA -}; - -static const char hex[16] = "0123456789ABCDEF"; - -static void make_session_filename(const char *in, strbuf *out) -{ - if (!in || !*in) - in = "Default Settings"; - - 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')) { - put_byte(out, '%'); - put_byte(out, hex[((unsigned char) *in) >> 4]); - put_byte(out, hex[((unsigned char) *in) & 15]); - } else - put_byte(out, *in); - in++; - } -} - -static void decode_session_filename(const char *in, strbuf *out) -{ - 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); - - put_byte(out, (i << 4) + j); - in += 3; - } else { - put_byte(out, *in++); - } - } -} - -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; - char *xdg_dir, *old_dir, *old_dir2, *old_dir3, *home, *pwd_home; - - env = getenv("PUTTYDIR"); - if (env) - return dupstr(env); - - home = getenv("HOME"); - pwd = getpwuid(getuid()); - if (pwd && pwd->pw_dir) { - pwd_home = pwd->pw_dir; - } else { - pwd_home = NULL; - } - - xdg_dir = NULL; - env = getenv("XDG_CONFIG_HOME"); - if (env && *env) { - xdg_dir = dupprintf("%s/putty", env); - } - if (!xdg_dir) { - if (home) { - tmp = home; - } else if (pwd_home) { - tmp = pwd_home; - } else { - tmp = ""; - } - xdg_dir = dupprintf("%s/.config/putty", tmp); - } - if (xdg_dir && access(xdg_dir, F_OK) == 0) { - return xdg_dir; - } - - old_dir = old_dir2 = old_dir3 = NULL; - if (home) { - old_dir = dupprintf("%s/.putty", home); - } - if (pwd_home) { - old_dir2 = dupprintf("%s/.putty", pwd_home); - } - old_dir3 = dupstr("/.putty"); - - if (old_dir && access(old_dir, F_OK) == 0) { - ret = old_dir; - goto out; - } - if (old_dir2 && access(old_dir2, F_OK) == 0) { - ret = old_dir2; - goto out; - } - if (access(old_dir3, F_OK) == 0) { - ret = old_dir3; - goto out; - } -#ifdef XDG_DEFAULT - if (xdg_dir) { - ret = xdg_dir; - goto out; - } -#endif - ret = old_dir ? old_dir : (old_dir2 ? old_dir2 : old_dir3); - - out: - if (ret != old_dir) - sfree(old_dir); - if (ret != old_dir2) - sfree(old_dir2); - if (ret != old_dir3) - sfree(old_dir3); - if (ret != xdg_dir) - sfree(xdg_dir); - return ret; - } - 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) { - strbuf *sb = strbuf_new(); - tmp = make_filename(INDEX_SESSIONDIR, NULL); - put_fmt(sb, "%s/", tmp); - sfree(tmp); - make_session_filename(subname, sb); - return strbuf_to_str(sb); - } - 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; - } - if (index == INDEX_HOSTCADIR) { - env = getenv("PUTTYSSHHOSTCAS"); - if (env) - return dupstr(env); - tmp = make_filename(INDEX_DIR, NULL); - ret = dupprintf("%s/sshhostcas", tmp); - sfree(tmp); - return ret; - } - if (index == INDEX_HOSTCA) { - strbuf *sb = strbuf_new(); - tmp = make_filename(INDEX_HOSTCADIR, NULL); - put_fmt(sb, "%s/", tmp); - sfree(tmp); - make_session_filename(subname, sb); - return strbuf_to_str(sb); - } - tmp = make_filename(INDEX_DIR, NULL); - ret = dupprintf("%s/ERROR", tmp); - sfree(tmp); - return ret; -} - -struct settings_w { - FILE *fp; -}; - -settings_w *open_settings_w(const char *sessionname, char **errmsg) -{ - char *filename, *err; - FILE *fp; - - *errmsg = NULL; - - /* - * Start by making sure the .putty directory and its sessions - * subdir actually exist. - */ - filename = make_filename(INDEX_DIR, NULL); - if ((err = make_dir_path(filename, 0700)) != NULL) { - *errmsg = dupprintf("Unable to save session: %s", err); - sfree(err); - sfree(filename); - return NULL; - } - sfree(filename); - - filename = make_filename(INDEX_SESSIONDIR, NULL); - if ((err = make_dir_path(filename, 0700)) != NULL) { - *errmsg = dupprintf("Unable to save session: %s", err); - sfree(err); - 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); - - settings_w *toret = snew(settings_w); - toret->fp = fp; - return toret; -} - -void write_setting_s(settings_w *handle, const char *key, const char *value) -{ - fprintf(handle->fp, "%s=%s\n", key, value); -} - -void write_setting_i(settings_w *handle, const char *key, int value) -{ - fprintf(handle->fp, "%s=%d\n", key, value); -} - -void close_settings_w(settings_w *handle) -{ - fclose(handle->fp); - sfree(handle); -} - -/* ---------------------------------------------------------------------- - * System for treating X resources as a fallback source of defaults, - * after data read from a saved-session disk file. - * - * The read_setting_* functions will call get_setting(key) as a - * fallback if the setting isn't in the file they loaded. That in turn - * will hand on to x_get_default, which the front end application - * provides, and which actually reads resources from the X server (if - * appropriate). In between, there's a tree234 of X-resource shaped - * settings living locally in this file: the front end can call - * provide_xrm_string() to insert a setting into this tree (typically - * in response to an -xrm command line option or similar), and those - * will override the actual X resources. - */ - -struct skeyval { - const char *key; - const char *value; -}; - -static tree234 *xrmtree = NULL; - -static 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(const char *string, const char *progname) -{ - const char *p, *q; - struct skeyval *xrms, *ret; - - p = q = strchr(string, ':'); - if (!q) { - fprintf(stderr, "%s: expected a colon in resource string" - " \"%s\"\n", progname, string); - return; - } - xrms = snew(struct skeyval); - - while (p > string && p[-1] != '.' && p[-1] != '*') - p--; - xrms->key = mkstr(make_ptrlen(p, q-p)); - - q++; - 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); - } -} - -static 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); -} - -/* ---------------------------------------------------------------------- - * Main code for reading settings from a disk file, calling the above - * get_setting() as a fallback if necessary. - */ - -struct settings_r { - tree234 *t; -}; - -settings_r *open_settings_r(const char *sessionname) -{ - char *filename; - FILE *fp; - char *line; - settings_r *toret; - - filename = make_filename(INDEX_SESSION, sessionname); - fp = fopen(filename, "r"); - sfree(filename); - if (!fp) - return NULL; /* can't open */ - - toret = snew(settings_r); - toret->t = 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(toret->t, kv); - - sfree(line); - } - - fclose(fp); - - return toret; -} - -char *read_setting_s(settings_r *handle, const char *key) -{ - const char *val; - struct skeyval tmp, *kv; - - tmp.key = key; - if (handle != NULL && - (kv = find234(handle->t, &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(settings_r *handle, const char *key, int defvalue) -{ - const char *val; - struct skeyval tmp, *kv; - - tmp.key = key; - if (handle != NULL && - (kv = find234(handle->t, &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(settings_r *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"); - 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); - FontSpec *fs = fontspec_new(tmp2); - sfree(tmp2); - sfree(tmp); - return fs; - } else { - sfree(tmp); - return NULL; - } -} -Filename *read_setting_filename(settings_r *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(settings_w *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"); - write_setting_s(handle, suffname, fs->name); - sfree(suffname); -} -void write_setting_filename(settings_w *handle, - const char *name, Filename *result) -{ - write_setting_s(handle, name, result->path); -} - -void close_settings_r(settings_r *handle) -{ - struct skeyval *kv; - - if (!handle) - return; - - while ( (kv = index234(handle->t, 0)) != NULL) { - del234(handle->t, kv); - sfree((char *)kv->key); - sfree((char *)kv->value); - sfree(kv); - } - - freetree234(handle->t); - sfree(handle); -} - -void del_settings(const char *sessionname) -{ - char *filename; - filename = make_filename(INDEX_SESSION, sessionname); - unlink(filename); - sfree(filename); -} - -struct settings_e { - DIR *dp; -}; - -settings_e *enum_settings_start(void) -{ - DIR *dp; - char *filename; - - filename = make_filename(INDEX_SESSIONDIR, NULL); - dp = opendir(filename); - sfree(filename); - - settings_e *toret = snew(settings_e); - toret->dp = dp; - return toret; -} - -static bool enum_dir_next(DIR *dp, int index, strbuf *out) -{ - struct dirent *de; - struct stat st; - strbuf *fullpath; - - if (!dp) - return false; - - fullpath = strbuf_new(); - - char *sessiondir = make_filename(index, NULL); - put_dataz(fullpath, sessiondir); - sfree(sessiondir); - put_byte(fullpath, '/'); - - size_t baselen = fullpath->len; - - while ( (de = readdir(dp)) != NULL ) { - strbuf_shrink_to(fullpath, baselen); - put_dataz(fullpath, de->d_name); - - if (stat(fullpath->s, &st) < 0 || !S_ISREG(st.st_mode)) - continue; /* try another one */ - - decode_session_filename(de->d_name, out); - strbuf_free(fullpath); - return true; - } - - strbuf_free(fullpath); - return false; -} - -bool enum_settings_next(settings_e *handle, strbuf *out) -{ - return enum_dir_next(handle->dp, INDEX_SESSIONDIR, out); -} - -void enum_settings_finish(settings_e *handle) -{ - if (handle->dp) - closedir(handle->dp); - sfree(handle); -} - -struct host_ca_enum { - DIR *dp; -}; - -host_ca_enum *enum_host_ca_start(void) -{ - host_ca_enum *handle = snew(host_ca_enum); - - char *filename = make_filename(INDEX_HOSTCADIR, NULL); - handle->dp = opendir(filename); - sfree(filename); - - return handle; -} - -bool enum_host_ca_next(host_ca_enum *handle, strbuf *out) -{ - return enum_dir_next(handle->dp, INDEX_HOSTCADIR, out); -} - -void enum_host_ca_finish(host_ca_enum *handle) -{ - if (handle->dp) - closedir(handle->dp); - sfree(handle); -} - -host_ca *host_ca_load(const char *name) -{ - char *filename = make_filename(INDEX_HOSTCA, name); - FILE *fp = fopen(filename, "r"); - sfree(filename); - if (!fp) - return NULL; - - host_ca *hca = host_ca_new(); - hca->name = dupstr(name); - - char *line; - CertExprBuilder *eb = NULL; - - while ( (line = fgetline(fp)) ) { - char *value = strchr(line, '='); - - if (!value) { - sfree(line); - continue; - } - *value++ = '\0'; - value[strcspn(value, "\r\n")] = '\0'; /* trim trailing NL */ - - if (!strcmp(line, "PublicKey")) { - hca->ca_public_key = base64_decode_sb(ptrlen_from_asciz(value)); - } else if (!strcmp(line, "MatchHosts")) { - if (!eb) - eb = cert_expr_builder_new(); - cert_expr_builder_add(eb, value); - } else if (!strcmp(line, "Validity")) { - hca->validity_expression = strbuf_to_str( - percent_decode_sb(ptrlen_from_asciz(value))); - } else if (!strcmp(line, "PermitRSASHA1")) { - hca->opts.permit_rsa_sha1 = atoi(value); - } else if (!strcmp(line, "PermitRSASHA256")) { - hca->opts.permit_rsa_sha256 = atoi(value); - } else if (!strcmp(line, "PermitRSASHA512")) { - hca->opts.permit_rsa_sha512 = atoi(value); - } - - sfree(line); - } - - fclose(fp); - - if (eb) { - if (!hca->validity_expression) { - hca->validity_expression = cert_expr_expression(eb); - } - cert_expr_builder_free(eb); - } - - return hca; -} - -char *host_ca_save(host_ca *hca) -{ - if (!*hca->name) - return dupstr("CA record must have a name"); - - char *filename = make_filename(INDEX_HOSTCA, hca->name); - FILE *fp = fopen(filename, "w"); - if (!fp) - return dupprintf("Unable to open file '%s'", filename); - - fprintf(fp, "PublicKey="); - base64_encode_fp(fp, ptrlen_from_strbuf(hca->ca_public_key), 0); - fprintf(fp, "\n"); - - fprintf(fp, "Validity="); - percent_encode_fp(fp, ptrlen_from_asciz(hca->validity_expression), NULL); - fprintf(fp, "\n"); - - fprintf(fp, "PermitRSASHA1=%d\n", (int)hca->opts.permit_rsa_sha1); - fprintf(fp, "PermitRSASHA256=%d\n", (int)hca->opts.permit_rsa_sha256); - fprintf(fp, "PermitRSASHA512=%d\n", (int)hca->opts.permit_rsa_sha512); - - bool bad = ferror(fp); - if (fclose(fp) < 0) - bad = true; - - char *err = NULL; - if (bad) - err = dupprintf("Unable to write file '%s'", filename); - - sfree(filename); - return err; -} - -char *host_ca_delete(const char *name) -{ - if (!*name) - return dupstr("CA record must have a name"); - char *filename = make_filename(INDEX_HOSTCA, name); - bool bad = remove(filename) < 0; - - char *err = NULL; - if (bad) - err = dupprintf("Unable to delete file '%s'", filename); - - sfree(filename); - return err; -} - -/* - * 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 check_stored_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; -} - -bool have_ssh_host_key(const char *hostname, int port, - const char *keytype) -{ - /* - * If we have a host key, check_stored_host_key will return 0 or 2. - * If we don't have one, it'll return 1. - */ - return check_stored_host_key(hostname, port, keytype, "") != 1; -} - -void store_host_key(Seat *seat, 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, *errmsg; - - dir = make_filename(INDEX_DIR, NULL); - if ((errmsg = make_dir_path(dir, 0700)) != NULL) { - seat_nonfatal(seat, "Unable to store host key: %s", errmsg); - sfree(errmsg); - sfree(dir); - sfree(tmpfilename); - return; - } - sfree(dir); - - wfp = fopen(tmpfilename, "w"); - } - if (!wfp) { - seat_nonfatal(seat, "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) { - seat_nonfatal(seat, "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, *errmsg; - - dir = make_filename(INDEX_DIR, NULL); - if ((errmsg = make_dir_path(dir, 0700)) != NULL) { - nonfatal("Unable to write random seed: %s", errmsg); - sfree(errmsg); - 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) -{ -} diff --git a/unix/stubs/no-uxsel.c b/unix/stubs/no-uxsel.c deleted file mode 100644 index 310a8ca3b..000000000 --- a/unix/stubs/no-uxsel.c +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Stub version of uxsel.c, for test programs. - */ - -#include "putty.h" - -void uxsel_init(void) -{ -} - -void uxsel_set(int fd, int rwx, uxsel_callback_fn callback) -{ -} - -void uxsel_del(int fd) -{ -} - -int next_fd(int *state, int *rwx) -{ - return -1; -} - -int first_fd(int *state, int *rwx) -{ - return -1; -} - -void select_result(int fd, int event) -{ -} diff --git a/unix/unicode.c b/unix/unicode.c deleted file mode 100644 index 7be64a53e..000000000 --- a/unix/unicode.c +++ /dev/null @@ -1,278 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include - -#include "putty.h" -#include "charset.h" -#include "terminal.h" -#include "misc.h" - -/* - * Unix Unicode-handling routines. - */ - -bool is_dbcs_leadbyte(int codepage, char byte) -{ - return false; /* 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) { - if (n >= wclen) - return n; - 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) { - if (n >= wclen) - return n; - 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, const char *defchr) -{ - if (codepage == DEFAULT_CODEPAGE) { - char output[MB_LEN_MAX]; - mbstate_t state; - int n = 0; - - memset(&state, 0, sizeof state); - - while (wclen > 0) { - size_t 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. - */ -bool init_ucs(struct unicode_data *ucsdata, char *linecharset, - bool utf8_override, int font_charset, int vtmode) -{ - int i; - bool ret = false; - - /* - * 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 = true; - - /* - * 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; -} - -void init_ucs_generic(Conf *conf, struct unicode_data *ucsdata) -{ - init_ucs(ucsdata, conf_get_str(conf, CONF_line_codepage), - conf_get_bool(conf, CONF_utf8_override), - CS_NONE, conf_get_int(conf, CONF_vtmode)); -} - -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(const char *cp_name) -{ - if (!cp_name || !*cp_name) - return CS_UTF8; - return charset_from_localenc(cp_name); -} diff --git a/unix/unifont.c b/unix/unifont.c deleted file mode 100644 index e9f8623a8..000000000 --- a/unix/unifont.c +++ /dev/null @@ -1,3806 +0,0 @@ -/* - * 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 -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "unifont.h" -#include "gtkcompat.h" -#include "gtkmisc.h" -#include "tree234.h" - -#ifndef NOT_X_WINDOWS -#include -#include -#include -#include -#include "x11misc.h" -#endif - -/* - * 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. - */ - -#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 an element of a larger structure containing data - * specific to the subtype. - */ - -#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 UnifontVtable *fontclass); - -struct UnifontVtable { - /* - * `Methods' of the `class'. - */ - unifont *(*create)(GtkWidget *widget, const char *name, bool wide, - bool bold, int shadowoffset, bool shadowalways); - unifont *(*create_fallback)(GtkWidget *widget, int height, bool wide, - bool bold, int shadowoffset, - bool shadowalways); - void (*destroy)(unifont *font); - bool (*has_glyph)(unifont *font, wchar_t glyph); - void (*draw_text)(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth); - void (*draw_combining)(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool 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, bool resolve_aliases); - char *(*scale_fontname)(GtkWidget *widget, const char *name, int size); - char *(*size_increment)(unifont *font, int increment); - - /* - * `Static data members' of the `class'. - */ - const char *prefix; -}; - -#ifndef NOT_X_WINDOWS - -/* ---------------------------------------------------------------------- - * X11 font implementation, directly using Xlib calls. Conditioned out - * if X11 fonts aren't available at all (e.g. building with GTK3 for a - * back end other than X). - */ - -static bool x11font_has_glyph(unifont *font, wchar_t glyph); -static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth); -static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth); -static unifont *x11font_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool 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, - bool resolve_aliases); -static char *x11font_scale_fontname(GtkWidget *widget, const char *name, - int size); -static char *x11font_size_increment(unifont *font, int increment); - -#ifdef DRAW_TEXT_CAIRO -struct cairo_cached_glyph { - cairo_surface_t *surface; - unsigned char *bitmap; -}; -#endif - -/* - * Structure storing a single physical XFontStruct, plus associated - * data. - */ -typedef struct x11font_individual { - /* The XFontStruct itself. */ - XFontStruct *xfs; - - /* - * The `allocated' flag indicates whether we've tried to fetch - * this subfont already (thus distinguishing xfs==NULL because we - * haven't tried yet from xfs==NULL because we tried and failed, - * so that we don't keep trying and failing subsequently). - */ - bool allocated; - -#ifdef DRAW_TEXT_CAIRO - /* - * A cache of glyph bitmaps downloaded from the X server when - * we're in Cairo rendering mode. If glyphcache itself is - * non-NULL, then entries in [0,nglyphs) are expected to be - * initialised to either NULL or a bitmap pointer. - */ - struct cairo_cached_glyph *glyphcache; - int nglyphs; - - /* - * X server paraphernalia for actually downloading the glyphs. - */ - Pixmap pixmap; - GC gc; - int pixwidth, pixheight, pixoriginx, pixoriginy; - - /* - * Paraphernalia for loading the resulting bitmaps into Cairo. - */ - int rowsize, allsize, indexflip; -#endif - -} x11font_individual; - -struct x11font { - /* - * Copy of the X display handle, so we don't have to keep - * extracting it from GDK. - */ - Display *disp; - /* - * Individual physical X fonts. We store a number of these, for - * automatically guessed bold and wide variants. - */ - x11font_individual fonts[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. - */ - bool 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. - */ - bool 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 shadowoffset; - bool wide, bold, shadowalways; - - unifont u; -}; - -static const UnifontVtable x11font_vtable = { - .create = x11font_create, - .create_fallback = NULL, /* no fallback fonts in X11 */ - .destroy = x11font_destroy, - .has_glyph = x11font_has_glyph, - .draw_text = x11font_draw_text, - .draw_combining = x11font_draw_combining, - .enum_fonts = x11font_enum_fonts, - .canonify_fontname = x11font_canonify_fontname, - .scale_fontname = x11font_scale_fontname, - .size_increment = x11font_size_increment, - .prefix = "server", -}; - -#define XLFD_STRING_PARTS_LIST(S,I) \ - S(foundry) \ - S(family_name) \ - S(weight_name) \ - S(slant) \ - S(setwidth_name) \ - S(add_style_name) \ - I(pixel_size) \ - I(point_size) \ - I(resolution_x) \ - I(resolution_y) \ - S(spacing) \ - I(average_width) \ - S(charset_registry) \ - S(charset_encoding) \ - /* end of list */ - -/* Special value for int fields that xlfd_recompose will render as "*" */ -#define XLFD_INT_WILDCARD INT_MIN - -struct xlfd_decomposed { -#define STR_FIELD(f) const char *f; -#define INT_FIELD(f) int f; - XLFD_STRING_PARTS_LIST(STR_FIELD, INT_FIELD) -#undef STR_FIELD -#undef INT_FIELD -}; - -static struct xlfd_decomposed *xlfd_decompose(const char *xlfd) -{ - char *p, *components[14]; - struct xlfd_decomposed *dec; - int i; - - if (!xlfd) - return NULL; - - dec = snew_plus(struct xlfd_decomposed, strlen(xlfd) + 1); - p = snew_plus_get_aux(dec); - strcpy(p, xlfd); - - for (i = 0; i < 14; i++) { - if (*p != '-') { - /* Malformed XLFD: not enough '-' */ - sfree(dec); - return NULL; - } - *p++ = '\0'; - components[i] = p; - p += strcspn(p, "-"); - } - if (*p) { - /* Malformed XLFD: too many '-' */ - sfree(dec); - return NULL; - } - - i = 0; -#define STORE_STR(f) dec->f = components[i++]; -#define STORE_INT(f) dec->f = atoi(components[i++]); - XLFD_STRING_PARTS_LIST(STORE_STR, STORE_INT) -#undef STORE_STR -#undef STORE_INT - - return dec; -} - -static char *xlfd_recompose(const struct xlfd_decomposed *dec) -{ -#define FMT_STR(f) "-%s" -#define ARG_STR(f) , dec->f -#define FMT_INT(f) "%s%.*d" -#define ARG_INT(f) \ - , dec->f == XLFD_INT_WILDCARD ? "-*" : "-" \ - , dec->f == XLFD_INT_WILDCARD ? 0 : 1 \ - , dec->f == XLFD_INT_WILDCARD ? 0 : dec->f - return dupprintf(XLFD_STRING_PARTS_LIST(FMT_STR, FMT_INT) - XLFD_STRING_PARTS_LIST(ARG_STR, ARG_INT)); -#undef FMT_STR -#undef ARG_STR -#undef FMT_INT -#undef ARG_INT -} - -static char *x11_guess_derived_font_name(Display *disp, XFontStruct *xfs, - bool bold, bool wide) -{ - Atom fontprop = XInternAtom(disp, "FONT", False); - unsigned long ret; - if (XGetFontProperty(xfs, fontprop, &ret)) { - char *name = XGetAtomName(disp, (Atom)ret); - struct xlfd_decomposed *xlfd = xlfd_decompose(name); - if (!xlfd) - return NULL; - - if (bold) - xlfd->weight_name = "bold"; - - if (wide) { - /* Width name obviously may have changed. */ - /* Additional style may now become e.g. `ja' or `ko'. */ - xlfd->setwidth_name = xlfd->add_style_name = "*"; - - /* Expect to double the average width. */ - xlfd->average_width *= 2; - } - - { - char *ret = xlfd_recompose(xlfd); - sfree(xlfd); - return ret; - } - } - return NULL; -} - -static int x11_font_width(XFontStruct *xfs, bool 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 const XCharStruct *x11_char_struct( - XFontStruct *xfs, unsigned char byte1, unsigned char byte2) -{ - int index; - - /* - * 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 NULL; - - 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 NULL; - 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 &xfs->max_bounds; - - return &xfs->per_char[index]; -} - -static bool x11_font_has_glyph( - XFontStruct *xfs, unsigned char byte1, unsigned char byte2) -{ - /* - * 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. - */ - const XCharStruct *xcs = x11_char_struct(xfs, byte1, byte2); - return xcs && (xcs->ascent + xcs->descent > 0 || xcs->width > 0); -} - -static unifont *x11font_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool shadowalways) -{ - struct x11font *xfont; - XFontStruct *xfs; - Display *disp; - Atom charset_registry, charset_encoding, spacing; - unsigned long registry_ret, encoding_ret, spacing_ret; - int pubcs, realcs; - bool sixteen_bit, variable; - int i; - - if ((disp = get_x11_display()) == NULL) - return NULL; - - 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); - 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->u.strikethrough_y = xfont->u.ascent - (xfont->u.ascent * 3 / 8); -#ifdef DRAW_TEXT_GDK - xfont->u.preferred_drawtype = DRAWTYPE_GDK; -#elif defined DRAW_TEXT_CAIRO - xfont->u.preferred_drawtype = DRAWTYPE_CAIRO; -#else -#error No drawtype available at all -#endif - xfont->disp = disp; - xfont->real_charset = realcs; - xfont->sixteen_bit = sixteen_bit; - xfont->variable = variable; - xfont->wide = wide; - xfont->bold = bold; - xfont->shadowoffset = shadowoffset; - xfont->shadowalways = shadowalways; - - for (i = 0; i < lenof(xfont->fonts); i++) { - xfont->fonts[i].xfs = NULL; - xfont->fonts[i].allocated = false; -#ifdef DRAW_TEXT_CAIRO - xfont->fonts[i].glyphcache = NULL; - xfont->fonts[i].nglyphs = 0; - xfont->fonts[i].pixmap = None; - xfont->fonts[i].gc = None; -#endif - } - xfont->fonts[0].xfs = xfs; - xfont->fonts[0].allocated = true; - - return &xfont->u; -} - -static void x11font_destroy(unifont *font) -{ - struct x11font *xfont = container_of(font, struct x11font, u); - Display *disp = xfont->disp; - int i; - - for (i = 0; i < lenof(xfont->fonts); i++) { - if (xfont->fonts[i].xfs) - XFreeFont(disp, xfont->fonts[i].xfs); -#ifdef DRAW_TEXT_CAIRO - if (xfont->fonts[i].gc != None) - XFreeGC(disp, xfont->fonts[i].gc); - if (xfont->fonts[i].pixmap != None) - XFreePixmap(disp, xfont->fonts[i].pixmap); - if (xfont->fonts[i].glyphcache) { - int j; - for (j = 0; j < xfont->fonts[i].nglyphs; j++) { - cairo_surface_destroy(xfont->fonts[i].glyphcache[j].surface); - sfree(xfont->fonts[i].glyphcache[j].bitmap); - } - sfree(xfont->fonts[i].glyphcache); - } -#endif - } - sfree(xfont); -} - -static void x11_alloc_subfont(struct x11font *xfont, int sfid) -{ - Display *disp = xfont->disp; - char *derived_name = x11_guess_derived_font_name( - disp, xfont->fonts[0].xfs, sfid & 1, !!(sfid & 2)); - xfont->fonts[sfid].xfs = XLoadQueryFont(disp, derived_name); - xfont->fonts[sfid].allocated = true; - sfree(derived_name); - /* Note that xfont->fonts[sfid].xfs may still be NULL, if XLQF failed. */ -} - -static bool x11font_has_glyph(unifont *font, wchar_t glyph) -{ - struct x11font *xfont = container_of(font, struct x11font, u); - - 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].xfs, - 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, ""); - if (sblen == 0 || !sbstring[0]) - return false; /* not even in the charset */ - - return x11_font_has_glyph(xfont->fonts[0].xfs, 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 */ -#elif GTK_CHECK_VERSION(3,0,0) -#define GDK_DRAWABLE_XID(d) GDK_WINDOW_XID(d) /* GTK3's name for this */ -#endif - -static int x11font_width_16(unifont_drawctx *ctx, x11font_individual *xfi, - const void *vstring, int start, int length) -{ - const XChar2b *string = (const XChar2b *)vstring; - return XTextWidth16(xfi->xfs, string+start, length); -} - -static int x11font_width_8(unifont_drawctx *ctx, x11font_individual *xfi, - const void *vstring, int start, int length) -{ - const char *string = (const char *)vstring; - return XTextWidth(xfi->xfs, string+start, length); -} - -#ifdef DRAW_TEXT_GDK -static void x11font_gdk_setup(unifont_drawctx *ctx, x11font_individual *xfi, - Display *disp) -{ - XSetFont(disp, GDK_GC_XGC(ctx->u.gdk.gc), xfi->xfs->fid); -} - -static void x11font_gdk_draw_16(unifont_drawctx *ctx, x11font_individual *xfi, - Display *disp, int x, int y, - const void *vstring, int start, int length) -{ - const XChar2b *string = (const XChar2b *)vstring; - XDrawString16(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target), - GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length); -} - -static void x11font_gdk_draw_8(unifont_drawctx *ctx, x11font_individual *xfi, - Display *disp, int x, int y, - const void *vstring, int start, int length) -{ - const char *string = (const char *)vstring; - XDrawString(disp, GDK_DRAWABLE_XID(ctx->u.gdk.target), - GDK_GC_XGC(ctx->u.gdk.gc), x, y, string+start, length); -} -#endif - -#ifdef DRAW_TEXT_CAIRO -static void x11font_cairo_setup( - unifont_drawctx *ctx, x11font_individual *xfi, Display *disp) -{ - if (xfi->pixmap == None) { - XGCValues gcvals; - GdkWindow *widgetwin = gtk_widget_get_window(ctx->u.cairo.widget); - int widgetscr = GDK_SCREEN_XNUMBER(gdk_window_get_screen(widgetwin)); - - xfi->pixwidth = - xfi->xfs->max_bounds.rbearing - xfi->xfs->min_bounds.lbearing; - xfi->pixheight = - xfi->xfs->max_bounds.ascent + xfi->xfs->max_bounds.descent; - xfi->pixoriginx = -xfi->xfs->min_bounds.lbearing; - xfi->pixoriginy = xfi->xfs->max_bounds.ascent; - - xfi->rowsize = cairo_format_stride_for_width(CAIRO_FORMAT_A1, - xfi->pixwidth); - xfi->allsize = xfi->rowsize * xfi->pixheight; - - { - /* - * Test host endianness and use it to set xfi->indexflip, - * which is XORed into our left-shift counts in order to - * implement the CAIRO_FORMAT_A1 specification, in which - * each bitmap byte is oriented LSB-first on little-endian - * platforms and MSB-first on big-endian ones. - * - * This is the same technique Cairo itself uses to test - * endianness, so hopefully it'll work in any situation - * where Cairo is usable at all. - */ - static const int endianness_test = 1; - xfi->indexflip = (*((char *) &endianness_test) == 1) ? 0 : 7; - } - - xfi->pixmap = XCreatePixmap( - disp, GDK_DRAWABLE_XID(gtk_widget_get_window(ctx->u.cairo.widget)), - xfi->pixwidth, xfi->pixheight, 1); - gcvals.foreground = WhitePixel(disp, widgetscr); - gcvals.background = BlackPixel(disp, widgetscr); - gcvals.font = xfi->xfs->fid; - xfi->gc = XCreateGC(disp, xfi->pixmap, - GCForeground | GCBackground | GCFont, &gcvals); - } -} - -static void x11font_cairo_cache_glyph( - Display *disp, x11font_individual *xfi, int glyphindex) -{ - XImage *image; - int x, y; - unsigned char *bitmap; - const XCharStruct *xcs = x11_char_struct(xfi->xfs, glyphindex >> 8, - glyphindex & 0xFF); - - bitmap = snewn(xfi->allsize, unsigned char); - memset(bitmap, 0, xfi->allsize); - - image = XGetImage(disp, xfi->pixmap, 0, 0, - xfi->pixwidth, xfi->pixheight, AllPlanes, XYPixmap); - for (y = xfi->pixoriginy - xcs->ascent; - y < xfi->pixoriginy + xcs->descent; y++) { - for (x = xfi->pixoriginx + xcs->lbearing; - x < xfi->pixoriginx + xcs->rbearing; x++) { - unsigned long pixel = XGetPixel(image, x, y); - if (pixel) { - int byteindex = y * xfi->rowsize + x/8; - int bitindex = (x & 7) ^ xfi->indexflip; - bitmap[byteindex] |= 1U << bitindex; - } - } - } - XDestroyImage(image); - - if (xfi->nglyphs <= glyphindex) { - /* Round up to the next multiple of 256 on the general - * principle that Unicode characters come in contiguous blocks - * often used together */ - int old_nglyphs = xfi->nglyphs; - xfi->nglyphs = (glyphindex + 0x100) & ~0xFF; - xfi->glyphcache = sresize(xfi->glyphcache, xfi->nglyphs, - struct cairo_cached_glyph); - - while (old_nglyphs < xfi->nglyphs) { - xfi->glyphcache[old_nglyphs].surface = NULL; - xfi->glyphcache[old_nglyphs].bitmap = NULL; - old_nglyphs++; - } - } - xfi->glyphcache[glyphindex].bitmap = bitmap; - xfi->glyphcache[glyphindex].surface = cairo_image_surface_create_for_data( - bitmap, CAIRO_FORMAT_A1, xfi->pixwidth, xfi->pixheight, xfi->rowsize); -} - -static void x11font_cairo_draw_glyph(unifont_drawctx *ctx, - x11font_individual *xfi, int x, int y, - int glyphindex) -{ - if (xfi->glyphcache[glyphindex].surface) { - cairo_mask_surface(ctx->u.cairo.cr, - xfi->glyphcache[glyphindex].surface, - x - xfi->pixoriginx, y - xfi->pixoriginy); - } -} - -static void x11font_cairo_draw_16( - unifont_drawctx *ctx, x11font_individual *xfi, Display *disp, - int x, int y, const void *vstring, int start, int length) -{ - const XChar2b *string = (const XChar2b *)vstring + start; - int i; - for (i = 0; i < length; i++) { - if (x11_font_has_glyph(xfi->xfs, string[i].byte1, string[i].byte2)) { - int glyphindex = (256 * (unsigned char)string[i].byte1 + - (unsigned char)string[i].byte2); - if (glyphindex >= xfi->nglyphs || - !xfi->glyphcache[glyphindex].surface) { - XDrawImageString16(disp, xfi->pixmap, xfi->gc, - xfi->pixoriginx, xfi->pixoriginy, - string+i, 1); - x11font_cairo_cache_glyph(disp, xfi, glyphindex); - } - x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex); - x += XTextWidth16(xfi->xfs, string+i, 1); - } - } -} - -static void x11font_cairo_draw_8( - unifont_drawctx *ctx, x11font_individual *xfi, Display *disp, - int x, int y, const void *vstring, int start, int length) -{ - const char *string = (const char *)vstring + start; - int i; - for (i = 0; i < length; i++) { - if (x11_font_has_glyph(xfi->xfs, 0, string[i])) { - int glyphindex = (unsigned char)string[i]; - if (glyphindex >= xfi->nglyphs || - !xfi->glyphcache[glyphindex].surface) { - XDrawImageString(disp, xfi->pixmap, xfi->gc, - xfi->pixoriginx, xfi->pixoriginy, - string+i, 1); - x11font_cairo_cache_glyph(disp, xfi, glyphindex); - } - x11font_cairo_draw_glyph(ctx, xfi, x, y, glyphindex); - x += XTextWidth(xfi->xfs, string+i, 1); - } - } -} -#endif /* DRAW_TEXT_CAIRO */ - -struct x11font_drawfuncs { - int (*width)(unifont_drawctx *ctx, x11font_individual *xfi, - const void *vstring, int start, int length); - void (*setup)(unifont_drawctx *ctx, x11font_individual *xfi, - Display *disp); - void (*draw)(unifont_drawctx *ctx, x11font_individual *xfi, Display *disp, - int x, int y, const void *vstring, int start, int length); -}; - -/* - * This array has two entries per compiled-in drawtype; of each pair, - * the first is for an 8-bit font and the second for 16-bit. - */ -static const struct x11font_drawfuncs x11font_drawfuncs[2*DRAWTYPE_NTYPES] = { -#ifdef DRAW_TEXT_GDK - /* gdk, 8-bit */ - { - x11font_width_8, - x11font_gdk_setup, - x11font_gdk_draw_8, - }, - /* gdk, 16-bit */ - { - x11font_width_16, - x11font_gdk_setup, - x11font_gdk_draw_16, - }, -#endif -#ifdef DRAW_TEXT_CAIRO - /* cairo, 8-bit */ - { - x11font_width_8, - x11font_cairo_setup, - x11font_cairo_draw_8, - }, - /* [3] cairo, 16-bit */ - { - x11font_width_16, - x11font_cairo_setup, - x11font_cairo_draw_16, - }, -#endif -}; - -static void x11font_really_draw_text( - const struct x11font_drawfuncs *dfns, unifont_drawctx *ctx, - x11font_individual *xfi, Display *disp, - int x, int y, const void *string, int nchars, - int shadowoffset, bool fontvariable, int cellwidth) -{ - int start = 0, step, nsteps; - bool 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; - } - - dfns->setup(ctx, xfi, disp); - - while (nsteps-- > 0) { - int X = x; - if (centre) - X += (cellwidth - dfns->width(ctx, xfi, string, start, step)) / 2; - - dfns->draw(ctx, xfi, disp, X, y, string, start, step); - if (shadowoffset) - dfns->draw(ctx, xfi, disp, X + shadowoffset, y, - string, start, step); - - x += cellwidth; - start += step; - } -} - -static void x11font_draw_text(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth) -{ - struct x11font *xfont = container_of(font, struct x11font, u); - int sfid; - int shadowoffset = 0; - int mult = (wide ? 2 : 1); - int index = 2 * (int)ctx->type; - - wide = wide && !xfont->wide; - bold = 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 = false; - } - sfid = 2 * wide + bold; - if (!xfont->fonts[sfid].allocated) - x11_alloc_subfont(xfont, sfid); - if (bold && !xfont->fonts[sfid].xfs) { - bold = false; - shadowoffset = xfont->shadowoffset; - sfid = 2 * wide + bold; - if (!xfont->fonts[sfid].allocated) - x11_alloc_subfont(xfont, sfid); - } - - if (!xfont->fonts[sfid].xfs) - return; /* we've tried our best, but no luck */ - - 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(x11font_drawfuncs + index + 1, ctx, - &xfont->fonts[sfid], xfont->disp, 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, "."); - x11font_really_draw_text(x11font_drawfuncs + index + 0, ctx, - &xfont->fonts[sfid], xfont->disp, x, y, - sbstring, sblen, shadowoffset, - xfont->variable, cellwidth * mult); - sfree(sbstring); - } -} - -static void x11font_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth) -{ - /* - * For server-side fonts, there's no sophisticated system for - * combining characters intelligently, so the best we can do is to - * overprint them on each other in the obvious way. - */ - int i; - for (i = 0; i < len; i++) - x11font_draw_text(ctx, font, x, y, string+i, 1, wide, bold, cellwidth); -} - -static void x11font_enum_fonts(GtkWidget *widget, - fontsel_add_entry callback, void *callback_ctx) -{ - Display *disp; - char **fontnames; - char *tmp = NULL; - int nnames, i, max, tmpsize; - - if ((disp = get_x11_display()) == NULL) - return; - - max = 32768; - while (1) { - fontnames = XListFonts(disp, "*", max, &nnames); - if (nnames >= max) { - XFreeFontNames(fontnames); - max *= 2; - } else - break; - } - - tmpsize = 0; - - for (i = 0; i < nnames; i++) { - struct xlfd_decomposed *xlfd = xlfd_decompose(fontnames[i]); - if (xlfd) { - char *p, *font, *style, *stylekey, *charset; - int weightkey, slantkey, setwidthkey; - int thistmpsize; - - /* - * Convert a dismembered XLFD into the format we'll be - * using in the font selector. - */ - - thistmpsize = 4 * strlen(fontnames[i]) + 256; - if (tmpsize < thistmpsize) { - tmpsize = thistmpsize; - tmp = sresize(tmp, tmpsize, char); - } - p = tmp; - - /* - * Font name is in the form "family (foundry)". (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)", xlfd->family_name, xlfd->foundry); - - /* - * Character set. - */ - charset = p; - p += 1 + sprintf(p, "%s-%s", xlfd->charset_registry, - xlfd->charset_encoding); - - /* - * Style is a mixture of quite a lot of the fields, - * with some strange formatting. - */ - style = p; - p += sprintf(p, "%s", xlfd->weight_name[0] ? xlfd->weight_name : - "regular"); - if (!g_ascii_strcasecmp(xlfd->slant, "i")) - p += sprintf(p, " italic"); - else if (!g_ascii_strcasecmp(xlfd->slant, "o")) - p += sprintf(p, " oblique"); - else if (!g_ascii_strcasecmp(xlfd->slant, "ri")) - p += sprintf(p, " reverse italic"); - else if (!g_ascii_strcasecmp(xlfd->slant, "ro")) - p += sprintf(p, " reverse oblique"); - else if (!g_ascii_strcasecmp(xlfd->slant, "ot")) - p += sprintf(p, " other-slant"); - if (xlfd->setwidth_name[0] && - g_ascii_strcasecmp(xlfd->setwidth_name, "normal")) - p += sprintf(p, " %s", xlfd->setwidth_name); - if (!g_ascii_strcasecmp(xlfd->spacing, "m")) - p += sprintf(p, " [M]"); - if (!g_ascii_strcasecmp(xlfd->spacing, "c")) - p += sprintf(p, " [C]"); - if (xlfd->add_style_name[0]) - p += sprintf(p, " %s", xlfd->add_style_name); - - /* - * 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(xlfd->weight_name, "medium") || - !g_ascii_strcasecmp(xlfd->weight_name, "regular") || - !g_ascii_strcasecmp(xlfd->weight_name, "normal") || - !g_ascii_strcasecmp(xlfd->weight_name, "book")) - weightkey = 0; - else if (!g_ascii_strncasecmp(xlfd->weight_name, "demi", 4) || - !g_ascii_strncasecmp(xlfd->weight_name, "semi", 4)) - weightkey = 1; - else - weightkey = 2; - if (!g_ascii_strcasecmp(xlfd->slant, "r")) - slantkey = 0; - else if (!g_ascii_strncasecmp(xlfd->slant, "r", 1)) - slantkey = 2; - else - slantkey = 1; - if (!g_ascii_strcasecmp(xlfd->setwidth_name, "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(xlfd->weight_name), xlfd->weight_name, - slantkey, (int)strlen(xlfd->slant), xlfd->slant, - setwidthkey, - (int)strlen(xlfd->setwidth_name), xlfd->setwidth_name, - (int)strlen(xlfd->spacing), xlfd->spacing, - (int)strlen(xlfd->add_style_name), xlfd->add_style_name); - - assert(p - tmp < thistmpsize); - - /* - * Flags: we need to know whether this is a monospaced - * font, which we do by examining the spacing field - * again. - */ - int flags = FONTFLAG_SERVERSIDE; - if (!strchr("CcMm", xlfd->spacing[0])) - flags |= FONTFLAG_NONMONOSPACED; - - /* - * Some fonts have a pixel size of zero, meaning they're - * treated as scalable. For these purposes, we only want - * fonts whose pixel size we actually know, so filter - * those out. - */ - if (xlfd->pixel_size) - callback(callback_ctx, fontnames[i], font, charset, - style, stylekey, xlfd->pixel_size, flags, - &x11font_vtable); - - sfree(xlfd); - } 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); - - sfree(xlfd); - } - } - XFreeFontNames(fontnames); - sfree(tmp); -} - -static char *x11font_canonify_fontname(GtkWidget *widget, const char *name, - int *size, int *flags, - bool 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; - Atom fontprop, fontprop2; - unsigned long ret; - - if ((disp = get_x11_display()) == NULL) - return NULL; - - 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 */ -} - -static char *x11font_size_increment(unifont *font, int increment) -{ - struct x11font *xfont = container_of(font, struct x11font, u); - Display *disp = xfont->disp; - Atom fontprop = XInternAtom(disp, "FONT", False); - char *returned_name = NULL; - unsigned long ret; - if (XGetFontProperty(xfont->fonts[0].xfs, fontprop, &ret)) { - struct xlfd_decomposed *xlfd; - struct xlfd_decomposed *xlfd_best; - char *wc; - char **fontnames; - int nnames, i, max; - - xlfd = xlfd_decompose(XGetAtomName(disp, (Atom)ret)); - if (!xlfd) - return NULL; - - /* - * Form a wildcard consisting of everything in the - * original XLFD except for the size-related fields. - */ - { - struct xlfd_decomposed xlfd_wc = *xlfd; /* structure copy */ - xlfd_wc.pixel_size = XLFD_INT_WILDCARD; - xlfd_wc.point_size = XLFD_INT_WILDCARD; - xlfd_wc.average_width = XLFD_INT_WILDCARD; - wc = xlfd_recompose(&xlfd_wc); - } - - /* - * Fetch all the font names matching that wildcard. - */ - max = 32768; - while (1) { - fontnames = XListFonts(disp, wc, max, &nnames); - if (nnames >= max) { - XFreeFontNames(fontnames); - max *= 2; - } else - break; - } - - sfree(wc); - - /* - * Iterate over those to find the one closest in size to the - * original font, in the correct direction. - */ - -#define FLIPPED_SIZE(xlfd) \ - (((xlfd)->pixel_size + (xlfd)->point_size) * \ - (increment < 0 ? -1 : +1)) - - xlfd_best = NULL; - for (i = 0; i < nnames; i++) { - struct xlfd_decomposed *xlfd2 = xlfd_decompose(fontnames[i]); - if (!xlfd2) - continue; - - if (xlfd2->pixel_size != 0 && - FLIPPED_SIZE(xlfd2) > FLIPPED_SIZE(xlfd) && - (!xlfd_best || FLIPPED_SIZE(xlfd2)u.vt->prefix, ":", bare_returned_name); - sfree(bare_returned_name); - } - - XFreeFontNames(fontnames); - sfree(xlfd); - sfree(xlfd_best); - } - return returned_name; -} - -#endif /* NOT_X_WINDOWS */ - -#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 bool pangofont_has_glyph(unifont *font, wchar_t glyph); -static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth); -static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth); -static unifont *pangofont_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool shadowalways); -static unifont *pangofont_create_fallback(GtkWidget *widget, int height, - bool wide, bool bold, - int shadowoffset, bool 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, - bool resolve_aliases); -static char *pangofont_scale_fontname(GtkWidget *widget, const char *name, - int size); -static char *pangofont_size_increment(unifont *font, int increment); - -struct pangofont { - /* - * Pango objects. - */ - PangoFontDescription *desc; - PangoFontset *fset; - /* - * The containing widget. - */ - GtkWidget *widget; - /* - * Data passed in to unifont_create(). - */ - int shadowoffset; - bool bold, 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; - - struct unifont u; -}; - -static const UnifontVtable pangofont_vtable = { - .create = pangofont_create, - .create_fallback = pangofont_create_fallback, - .destroy = pangofont_destroy, - .has_glyph = pangofont_has_glyph, - .draw_text = pangofont_draw_text, - .draw_combining = pangofont_draw_combining, - .enum_fonts = pangofont_enum_fonts, - .canonify_fontname = pangofont_canonify_fontname, - .scale_fontname = pangofont_scale_fontname, - .size_increment = pangofont_size_increment, - .prefix = "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 bool pangofont_check_desc_makes_sense(PangoContext *ctx, - PangoFontDescription *desc) -{ -#ifndef PANGO_PRE_1POINT6 - PangoFontMap *map; -#endif - PangoFontFamily **families; - int i, nfamilies; - bool 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, - bool wide, bool bold, - int shadowoffset, bool 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_CEIL(pango_font_metrics_get_ascent(metrics)); - pfont->u.descent = - PANGO_PIXELS_CEIL(pango_font_metrics_get_descent(metrics)); - pfont->u.height = pfont->u.ascent + pfont->u.descent; - pfont->u.strikethrough_y = - PANGO_PIXELS(pango_font_metrics_get_ascent(metrics) - - pango_font_metrics_get_strikethrough_position(metrics)); - pfont->u.want_fallback = false; -#ifdef DRAW_TEXT_CAIRO - pfont->u.preferred_drawtype = DRAWTYPE_CAIRO; -#elif defined DRAW_TEXT_GDK - pfont->u.preferred_drawtype = DRAWTYPE_GDK; -#else -#error No drawtype available at all -#endif - /* 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 &pfont->u; -} - -static unifont *pangofont_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool 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, - bool wide, bool bold, - int shadowoffset, bool 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 = container_of(font, struct pangofont, u); - pango_font_description_free(pfont->desc); - sfree(pfont->widthcache); - g_object_unref(pfont->fset); - sfree(pfont); -} - -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 - * asking Pango for text sizes 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_extents(layout, NULL, &rect); - pfont->widthcache[uchr] = rect.width; - } - - return pfont->widthcache[uchr]; -} - -static bool pangofont_has_glyph(unifont *font, wchar_t glyph) -{ - /* Pango implements font fallback, so assume it has everything */ - return true; -} - -#ifdef DRAW_TEXT_GDK -static void pango_gdk_draw_layout(unifont_drawctx *ctx, - gint x, gint y, PangoLayout *layout) -{ - gdk_draw_layout(ctx->u.gdk.target, ctx->u.gdk.gc, x, y, layout); -} -#endif - -#ifdef DRAW_TEXT_CAIRO -static void pango_cairo_draw_layout(unifont_drawctx *ctx, - gint x, gint y, PangoLayout *layout) -{ - cairo_move_to(ctx->u.cairo.cr, x, y); - pango_cairo_show_layout(ctx->u.cairo.cr, layout); -} -#endif - -static void pangofont_draw_internal(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth, bool combining) -{ - struct pangofont *pfont = container_of(font, struct pangofont, u); - PangoLayout *layout; - PangoRectangle rect; - char *utfstring, *utfptr; - int utflen; - bool shadowbold = false; - void (*draw_layout)(unifont_drawctx *ctx, - gint x, gint y, PangoLayout *layout) = NULL; - -#ifdef DRAW_TEXT_GDK - if (ctx->type == DRAWTYPE_GDK) { - draw_layout = pango_gdk_draw_layout; - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (ctx->type == DRAWTYPE_CAIRO) { - draw_layout = pango_cairo_draw_layout; - } -#endif - assert(draw_layout); - - 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, "."); - - utfptr = utfstring; - while (utflen > 0) { - int clen, n; - int desired = cellwidth * PANGO_SCALE; - - /* - * 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. - */ - - if (combining) { - /* - * For a character with combining stuff, we just dump the - * whole lot in one go, and expect it to take up just one - * character cell. - */ - clen = utflen; - n = 1; - } else { - /* - * 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) != desired) { - /* - * 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 (is_rtl(string[n-1]) || - pangofont_char_width(layout, pfont, - string[n-1], utfptr + oldclen, - clen - oldclen) != desired) { - clen = oldclen; - n--; - break; - } - } - } - } - - pango_layout_set_text(layout, utfptr, clen); - pango_layout_get_pixel_extents(layout, NULL, &rect); - - draw_layout(ctx, - x + (n*cellwidth - rect.width)/2, - y + (pfont->u.height - rect.height)/2, layout); - if (shadowbold) - draw_layout(ctx, - 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); -} - -static void pangofont_draw_text(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth) -{ - pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold, - cellwidth, false); -} - -static void pangofont_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth) -{ - wchar_t *tmpstring = NULL; - if (mk_wcwidth(string[0]) == 0) { - /* - * If we've been told to draw a sequence of _only_ combining - * characters, prefix a space so that they have something to - * combine with. - */ - tmpstring = snewn(len+1, wchar_t); - memcpy(tmpstring+1, string, len * sizeof(wchar_t)); - tmpstring[0] = L' '; - string = tmpstring; - len++; - } - pangofont_draw_internal(ctx, font, x, y, string, len, wide, bold, - cellwidth, true); - sfree(tmpstring); -} - -/* - * 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, *stylekey; - - pango_font_description_set_size(desc, sizes[k]); - - fullname = pango_font_description_to_string(desc); - - /* - * Construct the sorting key for font styles. - */ - { - strbuf *buf = strbuf_new(); - - int weight = pango_font_description_get_weight(desc); - /* Weight: normal, then lighter, then bolder */ - if (weight <= PANGO_WEIGHT_NORMAL) - weight = PANGO_WEIGHT_NORMAL - weight; - put_fmt(buf, "%4d", weight); - - put_fmt(buf, " %2d", - pango_font_description_get_style(desc)); - - int stretch = pango_font_description_get_stretch(desc); - /* Stretch: closer to normal sorts earlier */ - stretch = 2 * abs(PANGO_STRETCH_NORMAL - stretch) + - (stretch < PANGO_STRETCH_NORMAL); - put_fmt(buf, " %2d", stretch); - - put_fmt(buf, " %2d", - pango_font_description_get_variant(desc)); - - stylekey = strbuf_to_str(buf); - } - - /* - * 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); - - sfree(stylekey); - 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, - bool 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; -} - -static char *pangofont_size_increment(unifont *font, int increment) -{ - struct pangofont *pfont = container_of(font, struct pangofont, u); - PangoFontDescription *desc; - int size; - char *newname, *retname; - - desc = pango_font_description_copy_static(pfont->desc); - - size = pango_font_description_get_size(desc); - size += PANGO_SCALE * increment; - - if (size <= 0) { - retname = NULL; - } else { - pango_font_description_set_size(desc, size); - newname = pango_font_description_to_string(desc); - retname = dupcat(pfont->u.vt->prefix, ":", 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 UnifontVtable *unifont_types[] = { -#if GTK_CHECK_VERSION(2,0,0) - &pangofont_vtable, -#endif -#ifndef NOT_X_WINDOWS - &x11font_vtable, -#endif -}; - -/* - * 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, bool wide, - bool bold, int shadowoffset, bool 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(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth) -{ - font->vt->draw_text(ctx, font, x, y, string, len, wide, bold, cellwidth); -} - -void unifont_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth) -{ - font->vt->draw_combining(ctx, font, x, y, string, len, wide, bold, - cellwidth); -} - -char *unifont_size_increment(unifont *font, int increment) -{ - return font->vt->size_increment(font, increment); -} - -/* ---------------------------------------------------------------------- - * 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(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth); -static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth); -static void multifont_destroy(unifont *font); -static char *multifont_size_increment(unifont *font, int increment); - -struct multifont { - unifont *main; - unifont *fallback; - - struct unifont u; -}; - -static const UnifontVtable multifont_vtable = { - .create = NULL, /* creation is done specially */ - .create_fallback = NULL, - .destroy = multifont_destroy, - .has_glyph = NULL, - .draw_text = multifont_draw_text, - .draw_combining = multifont_draw_combining, - .enum_fonts = NULL, - .canonify_fontname = NULL, - .scale_fontname = NULL, - .size_increment = multifont_size_increment, - .prefix = "client", -}; - -unifont *multifont_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool 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.strikethrough_y = font->strikethrough_y; - mfont->u.public_charset = font->public_charset; - mfont->u.want_fallback = false; /* shouldn't be needed, but just in case */ - mfont->u.preferred_drawtype = font->preferred_drawtype; - mfont->main = font; - mfont->fallback = fallback; - - return &mfont->u; -} - -static void multifont_destroy(unifont *font) -{ - struct multifont *mfont = container_of(font, struct multifont, u); - unifont_destroy(mfont->main); - if (mfont->fallback) - unifont_destroy(mfont->fallback); - sfree(mfont); -} - -typedef void (*unifont_draw_func_t)(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth); - -static void multifont_draw_main(unifont_drawctx *ctx, unifont *font, int x, - int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth, - int cellinc, unifont_draw_func_t draw) -{ - struct multifont *mfont = container_of(font, struct multifont, u); - unifont *f; - bool ok; - int 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. - */ - f = ok ? mfont->main : mfont->fallback; - if (f) - draw(ctx, f, x, y, string, i, wide, bold, cellwidth); - string += i; - len -= i; - x += i * cellinc; - } -} - -static void multifont_draw_text(unifont_drawctx *ctx, unifont *font, int x, - int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth) -{ - multifont_draw_main(ctx, font, x, y, string, len, wide, bold, - cellwidth, cellwidth, unifont_draw_text); -} - -static void multifont_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, - int len, bool wide, bool bold, - int cellwidth) -{ - multifont_draw_main(ctx, font, x, y, string, len, wide, bold, - cellwidth, 0, unifont_draw_combining); -} - -static char *multifont_size_increment(unifont *font, int increment) -{ - struct multifont *mfont = container_of(font, struct multifont, u); - return unifont_size_increment(mfont->main, increment); -} - -#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 { - GtkListStore *family_model, *style_model, *size_model; - GtkWidget *family_list, *style_list, *size_entry, *size_list; - GtkWidget *filter_buttons[4]; - int n_filter_buttons; - GtkWidget *preview_area; -#ifndef NO_BACKING_PIXMAPS - GdkPixmap *preview_pixmap; -#endif - 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; - bool inhibit_response; /* inhibit callbacks when we change GUI controls */ - - unifontsel u; -} 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 UnifontVtable *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_draw_preview_text(unifontsel_internal *fs); - -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); - unifontsel_draw_preview_text(fs); -} - -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; - - fs->inhibit_response = true; - - 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); - - fs->inhibit_response = false; -} - -static void unifontsel_setup_stylelist(unifontsel_internal *fs, - int start, int end) -{ - GtkTreeIter iter; - int i, listindex, minpos = -1, maxpos = -1; - bool 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, 4, PANGO_WEIGHT_NORMAL, -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, 4, PANGO_WEIGHT_BOLD, -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) { - /* _shouldn't_ happen unless font list is completely funted */ - break; - } - 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 < fs->n_filter_buttons; i++) { - int flagbit = GPOINTER_TO_INT(g_object_get_data( - G_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_inner(unifont_drawctx *dctx, - 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; - -#ifdef DRAW_TEXT_GDK - if (dctx->type == DRAWTYPE_GDK) { - gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_bg); - gdk_draw_rectangle(dctx->u.gdk.target, dctx->u.gdk.gc, 1, 0, 0, - fs->preview_width, fs->preview_height); - gdk_gc_set_foreground(dctx->u.gdk.gc, &fs->preview_fg); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (dctx->type == DRAWTYPE_CAIRO) { - cairo_set_source_rgb(dctx->u.cairo.cr, - fs->preview_bg.red / 65535.0, - fs->preview_bg.green / 65535.0, - fs->preview_bg.blue / 65535.0); - cairo_paint(dctx->u.cairo.cr); - cairo_set_source_rgb(dctx->u.cairo.cr, - fs->preview_fg.red / 65535.0, - fs->preview_fg.green / 65535.0, - fs->preview_fg.blue / 65535.0); - } -#endif - - 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(dctx, font, - 0, font->ascent, - L"bankrupt jilted showmen quiz convex fogey", - 41, false, false, font->width); - info->fontclass->draw_text(dctx, 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(dctx, font, - 0, font->ascent + font->height * 2, - L"0123456789!?,.:;<>()[]{}\\/`'\"+*-=~#_@|%&^$", - 42, false, false, font->width); - - info->fontclass->destroy(font); - } - - sfree(sizename); -} - -static void unifontsel_draw_preview_text(unifontsel_internal *fs) -{ - unifont_drawctx dctx; - GdkWindow *target; - -#ifndef NO_BACKING_PIXMAPS - target = fs->preview_pixmap; -#else - target = gtk_widget_get_window(fs->preview_area); -#endif - if (!target) /* we may be called when we haven't created everything yet */ - return; - - dctx.type = DRAWTYPE_DEFAULT; -#ifdef DRAW_TEXT_GDK - if (dctx.type == DRAWTYPE_GDK) { - dctx.u.gdk.target = target; - dctx.u.gdk.gc = gdk_gc_new(target); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (dctx.type == DRAWTYPE_CAIRO) { -#if GTK_CHECK_VERSION(3,22,0) - cairo_region_t *region; -#endif - - dctx.u.cairo.widget = GTK_WIDGET(fs->preview_area); - -#if GTK_CHECK_VERSION(3,22,0) - dctx.u.cairo.gdkwin = gtk_widget_get_window(dctx.u.cairo.widget); - region = gdk_window_get_clip_region(dctx.u.cairo.gdkwin); - dctx.u.cairo.drawctx = gdk_window_begin_draw_frame( - dctx.u.cairo.gdkwin, region); - dctx.u.cairo.cr = gdk_drawing_context_get_cairo_context( - dctx.u.cairo.drawctx); - cairo_region_destroy(region); -#else - dctx.u.cairo.cr = gdk_cairo_create(target); -#endif - } -#endif - - unifontsel_draw_preview_text_inner(&dctx, fs); - -#ifdef DRAW_TEXT_GDK - if (dctx.type == DRAWTYPE_GDK) { - gdk_gc_unref(dctx.u.gdk.gc); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (dctx.type == DRAWTYPE_CAIRO) { -#if GTK_CHECK_VERSION(3,22,0) - gdk_window_end_draw_frame(dctx.u.cairo.gdkwin, dctx.u.cairo.drawctx); -#else - cairo_destroy(dctx.u.cairo.cr); -#endif - } -#endif - - gdk_window_invalidate_rect(gtk_widget_get_window(fs->preview_area), - NULL, false); -} - -static void unifontsel_select_font(unifontsel_internal *fs, - fontinfo *info, int size, int leftlist, - bool size_is_explicit) -{ - int index; - int minval, maxval; - gboolean success; - 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); - success = gtk_tree_model_get_iter(GTK_TREE_MODEL(fs->family_model), - &iter, treepath); - assert(success); - 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_editable_set_editable(GTK_EDITABLE(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; - bool newstate = gtk_toggle_button_get_active(tb); - int newflags; - int flagbit = GPOINTER_TO_INT(g_object_get_data(G_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 UnifontVtable *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); - if (!info) - return; /* _shouldn't_ happen unless font list is completely funted */ - info = update_for_intended_size(fs, info); - if (!info) - return; /* similarly shouldn't happen */ - 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); - if (!info) - return; /* _shouldn't_ happen unless font list is completely funted */ - info = update_for_intended_size(fs, info); - if (!info) - return; /* similarly shouldn't happen */ - 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); - if (!info) - return; /* _shouldn't_ happen unless font list is completely funted */ - 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); - } -} - -#if GTK_CHECK_VERSION(3,0,0) -static gint unifontsel_draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) -{ - unifontsel_internal *fs = (unifontsel_internal *)data; - unifont_drawctx dctx; - - dctx.type = DRAWTYPE_CAIRO; - dctx.u.cairo.widget = widget; - dctx.u.cairo.cr = cr; - unifontsel_draw_preview_text_inner(&dctx, fs); - - return true; -} -#else -static gint unifontsel_expose_area(GtkWidget *widget, GdkEventExpose *event, - gpointer data) -{ - unifontsel_internal *fs = (unifontsel_internal *)data; - -#ifndef NO_BACKING_PIXMAPS - if (fs->preview_pixmap) { - gdk_draw_pixmap(gtk_widget_get_window(widget), - (gtk_widget_get_style(widget)->fg_gc - [gtk_widget_get_state(widget)]), - fs->preview_pixmap, - event->area.x, event->area.y, - event->area.x, event->area.y, - event->area.width, event->area.height); - } -#else - unifontsel_draw_preview_text(fs); -#endif - - return true; -} -#endif - -static gint unifontsel_configure_area(GtkWidget *widget, - GdkEventConfigure *event, gpointer data) -{ -#ifndef NO_BACKING_PIXMAPS - 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(gtk_widget_get_window(widget), - nx, ny, -1); - fs->preview_width = nx; - fs->preview_height = ny; - - unifontsel_draw_preview_text(fs); - } -#endif - - gdk_window_invalidate_rect(gtk_widget_get_window(widget), 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; - - { - int width, height; - - /* - * Invent some magic size constants. - */ - get_label_text_dimensions("Quite Long Font Name (Foundry)", - &width, &height); - font_width = width; - lists_height = 14 * height; - preview_height = 5 * height; - - get_label_text_dimensions("Italic Extra Condensed", &width, &height); - style_width = width; - - get_label_text_dimensions("48000", &width, &height); - size_width = width; - } - - /* - * 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), STANDARD_CANCEL_LABEL, GTK_RESPONSE_CANCEL); - fs->u.ok_button = gtk_dialog_add_button( - GTK_DIALOG(fs->u.window), STANDARD_OK_LABEL, 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. - */ -#if GTK_CHECK_VERSION(3,0,0) - table = gtk_grid_new(); - gtk_grid_set_column_spacing(GTK_GRID(table), 8); -#else - table = gtk_table_new(8, 3, false); - gtk_table_set_col_spacings(GTK_TABLE(table), 8); -#endif - gtk_widget_show(table); - -#if GTK_CHECK_VERSION(3,0,0) - /* GtkAlignment has become deprecated and we use the "margin" - * property */ - w = table; - g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL); -#elif 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 - /* In GTK < 2.4, even that isn't available */ - w = table; -#endif - - gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area( - GTK_DIALOG(fs->u.window))), - w, true, true, 0); - - label = gtk_label_new_with_mnemonic("_Font:"); - gtk_widget_show(label); - align_label_left(GTK_LABEL(label)); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1); - g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); -#endif - - /* - * 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); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), scroll, 0, 1, 1, 2); - g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), scroll, 0, 1, 1, 3, GTK_FILL, - GTK_EXPAND | GTK_FILL, 0, 0); -#endif - fs->family_model = model; - fs->family_list = w; - - label = gtk_label_new_with_mnemonic("_Style:"); - gtk_widget_show(label); - align_label_left(GTK_LABEL(label)); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), label, 1, 0, 1, 1); - g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); -#endif - - /* - * 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. Also, since GTK3 at - * least doesn't seem to display insensitive elements differently - * by default, we add a further column to change their style. - */ - model = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, - G_TYPE_BOOLEAN, 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( - "Style", gtk_cell_renderer_text_new(), - "text", 0, "sensitive", 3, "weight", 4, (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); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), scroll, 1, 1, 1, 2); - g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), scroll, 1, 2, 1, 3, GTK_FILL, - GTK_EXPAND | GTK_FILL, 0, 0); -#endif - fs->style_model = model; - fs->style_list = w; - - label = gtk_label_new_with_mnemonic("Si_ze:"); - gtk_widget_show(label); - align_label_left(GTK_LABEL(label)); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), label, 2, 0, 1, 1); - g_object_set(G_OBJECT(label), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); -#endif - - /* - * 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); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), w, 2, 1, 1, 1); - g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), w, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); -#endif - 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); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), scroll, 2, 2, 1, 1); - g_object_set(G_OBJECT(scroll), "expand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), scroll, 2, 3, 2, 3, GTK_FILL, - GTK_EXPAND | GTK_FILL, 0, 0); -#endif - fs->size_model = model; - fs->size_list = w; - - /* - * Preview widget. - */ - fs->preview_area = gtk_drawing_area_new(); -#ifndef NO_BACKING_PIXMAPS - fs->preview_pixmap = NULL; -#endif - 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; -#if !GTK_CHECK_VERSION(3,0,0) - 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); -#endif -#if GTK_CHECK_VERSION(3,0,0) - g_signal_connect(G_OBJECT(fs->preview_area), "draw", - G_CALLBACK(unifontsel_draw_area), fs); -#else - g_signal_connect(G_OBJECT(fs->preview_area), "expose_event", - G_CALLBACK(unifontsel_expose_area), fs); -#endif - g_signal_connect(G_OBJECT(fs->preview_area), "configure_event", - G_CALLBACK(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(3,0,0) - /* GtkAlignment has become deprecated and we use the "margin" - * property */ - g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL); -#elif 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); -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), w, 0, 3, 3, 1); - g_object_set(G_OBJECT(w), "expand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), w, 0, 3, 3, 4, - GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 8); -#endif - - /* - * We only provide the checkboxes for client- and server-side - * fonts if we have the X11 back end available, because that's the - * only situation in which more than one class of font is - * available anyway. - */ - fs->n_filter_buttons = 0; -#ifndef NOT_X_WINDOWS - w = gtk_check_button_new_with_label("Show client-side fonts"); - g_object_set_data(G_OBJECT(w), "user-data", - GINT_TO_POINTER(FONTFLAG_CLIENTSIDE)); - g_signal_connect(G_OBJECT(w), "toggled", - G_CALLBACK(unifontsel_button_toggled), fs); - gtk_widget_show(w); - fs->filter_buttons[fs->n_filter_buttons++] = w; -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), w, 0, 4, 3, 1); - g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), w, 0, 3, 4, 5, GTK_FILL, 0, 0, 0); -#endif - w = gtk_check_button_new_with_label("Show server-side fonts"); - g_object_set_data(G_OBJECT(w), "user-data", - GINT_TO_POINTER(FONTFLAG_SERVERSIDE)); - g_signal_connect(G_OBJECT(w), "toggled", - G_CALLBACK(unifontsel_button_toggled), fs); - gtk_widget_show(w); - fs->filter_buttons[fs->n_filter_buttons++] = w; -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), w, 0, 5, 3, 1); - g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), w, 0, 3, 5, 6, GTK_FILL, 0, 0, 0); -#endif - w = gtk_check_button_new_with_label("Show server-side font aliases"); - g_object_set_data(G_OBJECT(w), "user-data", - GINT_TO_POINTER(FONTFLAG_SERVERALIAS)); - g_signal_connect(G_OBJECT(w), "toggled", - G_CALLBACK(unifontsel_button_toggled), fs); - gtk_widget_show(w); - fs->filter_buttons[fs->n_filter_buttons++] = w; -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), w, 0, 6, 3, 1); - g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), w, 0, 3, 6, 7, GTK_FILL, 0, 0, 0); -#endif -#endif /* NOT_X_WINDOWS */ - w = gtk_check_button_new_with_label("Show non-monospaced fonts"); - g_object_set_data(G_OBJECT(w), "user-data", - GINT_TO_POINTER(FONTFLAG_NONMONOSPACED)); - g_signal_connect(G_OBJECT(w), "toggled", - G_CALLBACK(unifontsel_button_toggled), fs); - gtk_widget_show(w); - fs->filter_buttons[fs->n_filter_buttons++] = w; -#if GTK_CHECK_VERSION(3,0,0) - gtk_grid_attach(GTK_GRID(table), w, 0, 7, 3, 1); - g_object_set(G_OBJECT(w), "hexpand", true, (const char *)NULL); -#else - gtk_table_attach(GTK_TABLE(table), w, 0, 3, 7, 8, GTK_FILL, 0, 0, 0); -#endif - - assert(fs->n_filter_buttons <= 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 &fs->u; -} - -void unifontsel_destroy(unifontsel *fontsel) -{ - unifontsel_internal *fs = container_of(fontsel, unifontsel_internal, u); - fontinfo *info; - -#ifndef NO_BACKING_PIXMAPS - if (fs->preview_pixmap) - gdk_pixmap_unref(fs->preview_pixmap); -#endif - - 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 = container_of(fontsel, unifontsel_internal, u); - 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 = DEFAULT_GTK_FONT; - - /* - * 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 = container_of(fontsel, unifontsel_internal, u); - 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); - sfree(name); - return ret; - } - } - - return dupcat(fs->selected->fontclass->prefix, ":", - fs->selected->realname); -} - -#endif /* GTK_CHECK_VERSION(2,0,0) */ diff --git a/unix/unifont.h b/unix/unifont.h deleted file mode 100644 index c0f011214..000000000 --- a/unix/unifont.h +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Header file for unifont.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 - -/* - * We support two entirely different drawing systems: the old - * GDK1/GDK2 one which works on server-side X drawables, and the - * new-style Cairo one. GTK1 only supports GDK drawing; GTK3 only - * supports Cairo; GTK2 supports both, but deprecates GTK, so we only - * enable it if we aren't trying on purpose to compile without the - * deprecated functions. - * - * Our different font classes may prefer different drawing systems: X - * server-side fonts are a lot faster to draw with GDK, but for - * everything else we prefer Cairo, on general grounds of modernness - * and also in particular because its matrix-based scaling system - * gives much nicer results for double-width and double-height text - * when a scalable font is in use. - */ -#if !GTK_CHECK_VERSION(3,0,0) && !defined GDK_DISABLE_DEPRECATED -#define DRAW_TEXT_GDK -#endif -#if GTK_CHECK_VERSION(2,8,0) -#define DRAW_TEXT_CAIRO -#endif - -#if GTK_CHECK_VERSION(3,0,0) || defined GDK_DISABLE_DEPRECATED -/* - * Where the facility is available, we prefer to render text on to a - * persistent server-side pixmap, and redraw windows by simply - * blitting rectangles of that pixmap into them as needed. This is - * better for performance since we avoid expensive font rendering - * calls where possible, and it's particularly good over a non-local X - * connection because the response to an expose event can now be a - * very simple rectangle-copy operation rather than a lot of fiddly - * drawing or bitmap transfer. - * - * However, GTK is deprecating the use of server-side pixmaps, so we - * have to disable this mode under some circumstances. - */ -#define NO_BACKING_PIXMAPS -#endif - -/* - * Exports from unifont.c. - */ -typedef struct UnifontVtable UnifontVtable; /* contents internal to - * unifont.c */ -typedef struct unifont { - const struct UnifontVtable *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, strikethrough_y; - - /* - * 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. - */ - bool want_fallback; - - /* - * Preferred drawing API to use when this class of font is active. - * (See the enum below, in unifont_drawctx.) - */ - int preferred_drawtype; -} unifont; - -/* A default drawtype, for the case where no font exists to make the - * decision with. */ -#ifdef DRAW_TEXT_CAIRO -#define DRAW_DEFAULT_CAIRO -#define DRAWTYPE_DEFAULT DRAWTYPE_CAIRO -#elif defined DRAW_TEXT_GDK -#define DRAW_DEFAULT_GDK -#define DRAWTYPE_DEFAULT DRAWTYPE_GDK -#else -#error No drawtype available at all -#endif - -/* - * Drawing context passed in to unifont_draw_text, which contains - * everything required to know where and how to draw the requested - * text. - */ -typedef struct unifont_drawctx { - enum { -#ifdef DRAW_TEXT_GDK - DRAWTYPE_GDK, -#endif -#ifdef DRAW_TEXT_CAIRO - DRAWTYPE_CAIRO, -#endif - DRAWTYPE_NTYPES - } type; - union { -#ifdef DRAW_TEXT_GDK - struct { - GdkDrawable *target; - GdkGC *gc; - } gdk; -#endif -#ifdef DRAW_TEXT_CAIRO - struct { - /* Need an actual widget, in order to backtrack to its X - * screen number when creating server-side pixmaps */ - GtkWidget *widget; - cairo_t *cr; - cairo_matrix_t origmatrix; -#if GTK_CHECK_VERSION(3,22,0) - GdkWindow *gdkwin; - GdkDrawingContext *drawctx; -#endif - } cairo; -#endif - } u; -} unifont_drawctx; - -unifont *unifont_create(GtkWidget *widget, const char *name, - bool wide, bool bold, - int shadowoffset, bool shadowalways); -void unifont_destroy(unifont *font); -void unifont_draw_text(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth); -/* Same as unifont_draw_text, but expects 'string' to contain one - * normal char plus combining chars, and overdraws them all in the - * same character cell. */ -void unifont_draw_combining(unifont_drawctx *ctx, unifont *font, - int x, int y, const wchar_t *string, int len, - bool wide, bool bold, int cellwidth); -/* Return a name that will select a bigger/smaller font than this one, - * or NULL if no such name is available. */ -char *unifont_size_increment(unifont *font, int increment); - -/* - * 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, - bool wide, bool bold, - int shadowoffset, bool 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/unix/uppity.c b/unix/uppity.c deleted file mode 100644 index 711c29521..000000000 --- a/unix/uppity.c +++ /dev/null @@ -1,971 +0,0 @@ -/* - * SSH server for Unix: main program. - * - * ====================================================================== - * - * This server is NOT SECURE! - * - * DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT! - * - * Its purpose is to speak the server end of everything PuTTY speaks - * on the client side, so that I can test that I haven't broken PuTTY - * when I reorganise its code, even things like RSA key exchange or - * chained auth methods which it's hard to find a server that speaks - * at all. - * - * It has no interaction with the OS's authentication system: the - * authentications it will accept are configurable by command-line - * option, and once you authenticate, it will run the connection - * protocol - including all subprocesses and shells - under the same - * Unix user id you started it under. - * - * It really is only suitable for testing the actual SSH protocol. - * Don't use it for anything more serious! - * - * ====================================================================== - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "putty.h" -#include "mpint.h" -#include "ssh.h" -#include "ssh/server.h" - -void modalfatalbox(const 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 nonfatal(const char *p, ...) -{ - va_list ap; - fprintf(stderr, "ERROR: "); - va_start(ap, p); - vfprintf(stderr, p, ap); - va_end(ap); - fputc('\n', stderr); -} - -char *platform_default_s(const char *name) -{ - return NULL; -} - -bool platform_default_b(const char *name, bool def) -{ - return def; -} - -int platform_default_i(const char *name, int def) -{ - return def; -} - -FontSpec *platform_default_fontspec(const char *name) -{ - return fontspec_new_default(); -} - -Filename *platform_default_filename(const char *name) -{ - return filename_from_str(""); -} - -char *x_get_default(const char *key) -{ - return NULL; /* this is a stub */ -} - -void old_keyfile_warning(void) { } - -void timer_change_notify(unsigned long next) -{ -} - -char *platform_get_x_display(void) { return NULL; } - -void make_unix_sftp_filehandle_key(void *data, size_t size) -{ - random_read(data, size); -} - -static bool verbose; - -struct server_config; - -struct AuthPolicyShared { - struct AuthPolicy_ssh1_pubkey *ssh1keys; - struct AuthPolicy_ssh2_pubkey *ssh2keys; -}; - -struct AuthPolicy { - struct AuthPolicyShared *shared; - int kbdint_state; -}; - -struct server_instance { - unsigned id; - AuthPolicy ap; - LogPolicy logpolicy; - struct server_config *cfg; -}; - -struct server_config { - unsigned config_id; - - Conf *conf; - const SshServerConfig *ssc; - - ssh_key **hostkeys; - int nhostkeys; - - RSAKey *hostkey1; - - unsigned auth_methods; - - struct AuthPolicyShared *ap_shared; - - Socket *listening_socket; - Plug listening_plug; -}; - -static unsigned next_id = 0; - -static void log_to_stderr(unsigned id, const char *msg) -{ - if (id != (unsigned)-1) - fprintf(stderr, "conn#%u: ", id); - fputs(msg, stderr); - fputc('\n', stderr); - fflush(stderr); -} - -static void server_eventlog(LogPolicy *lp, const char *event) -{ - struct server_instance *inst = container_of( - lp, struct server_instance, logpolicy); - if (verbose) - log_to_stderr(inst->id, event); -} - -static void server_logging_error(LogPolicy *lp, const char *event) -{ - struct server_instance *inst = container_of( - lp, struct server_instance, logpolicy); - log_to_stderr(inst->id, event); /* unconditional */ -} - -static int server_askappend( - LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) -{ - return 2; /* always overwrite (FIXME: could make this a cmdline option) */ -} - -static const LogPolicyVtable server_logpolicy_vt = { - .eventlog = server_eventlog, - .askappend = server_askappend, - .logging_error = server_logging_error, - .verbose = null_lp_verbose_no, -}; - -struct AuthPolicy_ssh1_pubkey { - RSAKey key; - struct AuthPolicy_ssh1_pubkey *next; -}; -struct AuthPolicy_ssh2_pubkey { - ptrlen public_blob; - struct AuthPolicy_ssh2_pubkey *next; -}; - -unsigned auth_methods(AuthPolicy *ap) -{ - struct server_instance *inst = container_of( - ap, struct server_instance, ap); - return inst->cfg->auth_methods; -} -bool auth_none(AuthPolicy *ap, ptrlen username) -{ - struct server_instance *inst = container_of( - ap, struct server_instance, ap); - if (inst->cfg->auth_methods & AUTHMETHOD_NONE) - return true; - return false; -} -int auth_password(AuthPolicy *ap, ptrlen username, ptrlen password, - ptrlen *new_password_opt) -{ - const char *PHONY_GOOD_PASSWORD = "weasel"; - const char *PHONY_BAD_PASSWORD = "ferret"; - - if (!new_password_opt) { - /* Accept login with our preconfigured good password */ - if (ptrlen_eq_string(password, PHONY_GOOD_PASSWORD)) - return 1; - /* Don't outright reject the bad password, but insist on a change */ - if (ptrlen_eq_string(password, PHONY_BAD_PASSWORD)) - return 2; - /* Reject anything else */ - return 0; - } else { - /* In a password-change request, expect the bad password as input */ - if (!ptrlen_eq_string(password, PHONY_BAD_PASSWORD)) - return 0; - /* Accept a request to change it to the good password */ - if (ptrlen_eq_string(*new_password_opt, PHONY_GOOD_PASSWORD)) - return 1; - /* Outright reject a request to change it to the same password - * as it already 'was' */ - if (ptrlen_eq_string(*new_password_opt, PHONY_BAD_PASSWORD)) - return 0; - /* Anything else, pretend the new pw wasn't good enough, and - * re-request a change */ - return 2; - } -} -bool auth_publickey(AuthPolicy *ap, ptrlen username, ptrlen public_blob) -{ - struct AuthPolicy_ssh2_pubkey *iter; - for (iter = ap->shared->ssh2keys; iter; iter = iter->next) { - if (ptrlen_eq_ptrlen(public_blob, iter->public_blob)) - return true; - } - return false; -} -RSAKey *auth_publickey_ssh1( - AuthPolicy *ap, ptrlen username, mp_int *rsa_modulus) -{ - struct AuthPolicy_ssh1_pubkey *iter; - for (iter = ap->shared->ssh1keys; iter; iter = iter->next) { - if (mp_cmp_eq(rsa_modulus, iter->key.modulus)) - return &iter->key; - } - return NULL; -} -AuthKbdInt *auth_kbdint_prompts(AuthPolicy *ap, ptrlen username) -{ - AuthKbdInt *aki; - - switch (ap->kbdint_state) { - case 0: - aki = snew(AuthKbdInt); - aki->title = dupstr("Initial double prompt"); - aki->instruction = - dupstr("First prompt should echo, second should not"); - aki->nprompts = 2; - aki->prompts = snewn(aki->nprompts, AuthKbdIntPrompt); - aki->prompts[0].prompt = dupstr("Echoey prompt: "); - aki->prompts[0].echo = true; - aki->prompts[1].prompt = dupstr("Silent prompt: "); - aki->prompts[1].echo = false; - return aki; - case 1: { - struct server_instance *inst = container_of( - ap, struct server_instance, ap); - aki = snew(AuthKbdInt); - if (inst->cfg->ssc->stunt_allow_trivial_ki_auth) { - aki->title = dupstr(""); - aki->instruction = dupstr(""); - } else { - aki->title = dupstr("Zero-prompt step"); - aki->instruction = dupstr("Shouldn't see any prompts this time"); - } - aki->nprompts = 0; - aki->prompts = NULL; - return aki; - } - default: - ap->kbdint_state = 0; - return NULL; - } -} -int auth_kbdint_responses(AuthPolicy *ap, const ptrlen *responses) -{ - switch (ap->kbdint_state) { - case 0: - if (ptrlen_eq_string(responses[0], "stoat") && - ptrlen_eq_string(responses[1], "weasel")) { - ap->kbdint_state++; - return 0; /* those are the expected responses */ - } else { - ap->kbdint_state = 0; - return -1; - } - break; - case 1: - return +1; /* succeed after the zero-prompt step */ - default: - ap->kbdint_state = 0; - return -1; - } -} -char *auth_ssh1int_challenge(AuthPolicy *ap, unsigned method, ptrlen username) -{ - /* FIXME: test returning a challenge string without \n, and ensure - * it gets printed as a prompt in its own right, without PuTTY - * making up a "Response: " prompt to follow it */ - return dupprintf("This is a dummy %s challenge!\n", - (method == AUTHMETHOD_TIS ? "TIS" : "CryptoCard")); -} -bool auth_ssh1int_response(AuthPolicy *ap, ptrlen response) -{ - return ptrlen_eq_string(response, "otter"); -} -bool auth_successful(AuthPolicy *ap, ptrlen username, unsigned method) -{ - return true; -} - -static void safety_warning(FILE *fp) -{ - fputs(" =================================================\n" - " THIS SSH SERVER IS NOT WRITTEN TO BE SECURE!\n" - " DO NOT DEPLOY IT IN A HOSTILE-FACING ENVIRONMENT!\n" - " =================================================\n", fp); -} - -static void show_help(FILE *fp) -{ - safety_warning(fp); - fputs("\n" - "usage: uppity [options] [--and ...]\n" - "options: --listen [PORT|PATH] listen to a port on localhost, or Unix socket\n" - " --listen-once (with --listen) stop after one " - "connection\n" - " --hostkey KEY SSH host key (need at least one)\n" - " --rsakexkey KEY key for SSH-2 RSA key exchange " - "(in SSH-1 format)\n" - " --userkey KEY public key" - " acceptable for user authentication\n" - " --sessiondir DIR cwd for session subprocess (default $HOME)\n" - " --bannertext TEXT send TEXT as SSH-2 auth banner\n" - " --bannerfile FILE send contents of FILE as SSH-2 auth " - "banner\n" - " --kexinit-kex STR override list of SSH-2 KEX methods\n" - " --kexinit-hostkey STR override list of SSH-2 host key " - "types\n" - " --kexinit-cscipher STR override list of SSH-2 " - "client->server ciphers\n" - " --kexinit-sccipher STR override list of SSH-2 " - "server->client ciphers\n" - " --kexinit-csmac STR override list of SSH-2 " - "client->server MACs\n" - " --kexinit-scmac STR override list of SSH-2 " - "server->client MACs\n" - " --kexinit-cscomp STR override list of SSH-2 " - "c->s compression types\n" - " --kexinit-sccomp STR override list of SSH-2 " - "s->c compression types\n" - " --ssh1-ciphers STR override list of SSH-1 ciphers\n" - " --ssh1-no-compression forbid compression in SSH-1\n" - " --deny-auth METHOD forbid a userauth method\n" - " --allow-auth METHOD allow a userauth method\n" - " (METHOD = none/password/publickey/kbdint/tis/" - "cryptocard)\n" - " --exitsignum send buggy numeric \"exit-signal\" " - "message\n" - " --verbose print event log messages to standard " - "error\n" - " --sshlog FILE write SSH packet log to FILE\n" - " --sshrawlog FILE write SSH packets + raw data log" - " to FILE\n" - " --and run a separate server on another port\n" - "also: uppity --help show this text\n" - " uppity --version show version information\n" - "\n", fp); - safety_warning(fp); -} - -static void show_version_and_exit(void) -{ - char *buildinfo_text = buildinfo("\n"); - printf("%s: %s\n%s\n", appname, ver, buildinfo_text); - sfree(buildinfo_text); - exit(0); -} - -const bool buildinfo_gtk_relevant = false; - -static bool listening = false, listen_once = false; -static bool finished = false; -void server_instance_terminated(LogPolicy *lp) -{ - struct server_instance *inst = container_of( - lp, struct server_instance, logpolicy); - - if (listening && !listen_once) { - log_to_stderr(inst->id, "connection terminated"); - } else { - finished = true; - } - - sfree(inst); -} - -static bool longoptarg(const char *arg, const char *expected, - const char **val, int *argcp, char ***argvp) -{ - int len = strlen(expected); - if (memcmp(arg, expected, len)) - return false; - if (arg[len] == '=') { - *val = arg + len + 1; - return true; - } else if (arg[len] == '\0') { - if (--*argcp > 0) { - *val = *++*argvp; - return true; - } else { - fprintf(stderr, "%s: option %s expects an argument\n", - appname, expected); - exit(1); - } - } - return false; -} - -static bool longoptnoarg(const char *arg, const char *expected) -{ - int len = strlen(expected); - if (memcmp(arg, expected, len)) - return false; - if (arg[len] == '=') { - fprintf(stderr, "%s: option %s expects no argument\n", - appname, expected); - exit(1); - } else if (arg[len] == '\0') { - return true; - } - return false; -} - -static Plug *server_conn_plug( - struct server_config *cfg, struct server_instance **inst_out) -{ - struct server_instance *inst = snew(struct server_instance); - - memset(inst, 0, sizeof(*inst)); - - inst->id = next_id++; - inst->ap.shared = cfg->ap_shared; - if (cfg->ssc->stunt_allow_trivial_ki_auth) - inst->ap.kbdint_state = 1; - inst->logpolicy.vt = &server_logpolicy_vt; - inst->cfg = cfg; - - if (inst_out) - *inst_out = inst; - - return ssh_server_plug( - cfg->conf, cfg->ssc, cfg->hostkeys, cfg->nhostkeys, cfg->hostkey1, - &inst->ap, &inst->logpolicy, &unix_live_sftpserver_vt); -} - -static void server_log(Plug *plug, PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code) -{ - log_to_stderr((unsigned)-1, error_msg); -} - -static void server_closing(Plug *plug, PlugCloseType type, - const char *error_msg) -{ - if (type != PLUGCLOSE_NORMAL) - log_to_stderr((unsigned)-1, error_msg); -} - -static int server_accepting(Plug *p, accept_fn_t constructor, accept_ctx_t ctx) -{ - struct server_config *cfg = container_of( - p, struct server_config, listening_plug); - Socket *s; - const char *err; - - struct server_instance *inst; - - if (listen_once) { - if (!cfg->listening_socket) /* in case of rapid double-accept */ - return 1; - sk_close(cfg->listening_socket); - cfg->listening_socket = NULL; - } - - unsigned old_next_id = next_id; - - Plug *plug = server_conn_plug(cfg, &inst); - s = constructor(ctx, plug); - if ((err = sk_socket_error(s)) != NULL) - return 1; - - SocketPeerInfo *pi = sk_peer_info(s); - - if (pi->addressfamily != ADDRTYPE_LOCAL && !sk_peer_trusted(s)) { - fprintf(stderr, "rejected connection to serv#%u " - "from %s (untrustworthy peer)\n", - cfg->config_id, pi->log_text); - sk_free_peer_info(pi); - sk_close(s); - next_id = old_next_id; - return 1; - } - - char *msg = dupprintf("new connection to serv#%u from %s", - cfg->config_id, pi->log_text); - log_to_stderr(inst->id, msg); - sfree(msg); - sk_free_peer_info(pi); - - sk_set_frozen(s, false); - ssh_server_start(plug, s); - return 0; -} - -static const PlugVtable server_plugvt = { - .log = server_log, - .closing = server_closing, - .accepting = server_accepting, -}; - -static unsigned auth_method_from_name(const char *name) -{ - if (!strcmp(name, "none")) - return AUTHMETHOD_NONE; - if (!strcmp(name, "tis")) - return AUTHMETHOD_TIS; - if (!strcmp(name, "cryptocard") || !strcmp(name, "ccard")) - return AUTHMETHOD_CRYPTOCARD; - if (!strcmp(name, "keyboard-interactive") || !strcmp(name, "k-i") || - !strcmp(name, "kbdint") || !strcmp(name, "ki")) - return AUTHMETHOD_KBDINT; - if (!strcmp(name, "publickey") || !strcmp(name, "pubkey") || - !strcmp(name, "pk")) - return AUTHMETHOD_PUBLICKEY; - if (!strcmp(name, "password") || !strcmp(name, "pw")) - return AUTHMETHOD_PASSWORD; - return 0; -} - -struct cmdline_instance { - int listen_port; - const char *listen_socket; - ssh_key **hostkeys; - size_t nhostkeys, hostkeysize; - RSAKey *hostkey1; - unsigned auth_methods; - struct AuthPolicyShared aps; - SshServerConfig ssc; - Conf *conf; -}; - -static void init_cmdline_instance(struct cmdline_instance *ci) -{ - ci->listen_port = -1; - ci->listen_socket = NULL; - - ci->hostkeys = NULL; - ci->nhostkeys = ci->hostkeysize = 0; - ci->hostkey1 = NULL; - - ci->conf = make_ssh_server_conf(); - - ci->aps.ssh1keys = NULL; - ci->aps.ssh2keys = NULL; - - memset(&ci->ssc, 0, sizeof(ci->ssc)); - - ci->ssc.application_name = "Uppity"; - ci->ssc.session_starting_dir = getenv("HOME"); - ci->ssc.ssh1_cipher_mask = SSH1_SUPPORTED_CIPHER_MASK; - ci->ssc.ssh1_allow_compression = true; - - ci->auth_methods = (AUTHMETHOD_PUBLICKEY | AUTHMETHOD_PASSWORD | - AUTHMETHOD_KBDINT | AUTHMETHOD_TIS | - AUTHMETHOD_CRYPTOCARD); -} - -static void cmdline_instance_start(struct cmdline_instance *ci) -{ - static unsigned next_server_config_id = 0; - - struct server_config *scfg = snew(struct server_config); - scfg->config_id = next_server_config_id++; - scfg->conf = ci->conf; - scfg->ssc = &ci->ssc; - scfg->hostkeys = ci->hostkeys; - scfg->nhostkeys = ci->nhostkeys; - scfg->hostkey1 = ci->hostkey1; - scfg->ap_shared = &ci->aps; - scfg->auth_methods = ci->auth_methods; - - if (ci->listen_port >= 0 || ci->listen_socket) { - listening = true; - scfg->listening_plug.vt = &server_plugvt; - char *msg; - if (ci->listen_port >= 0) { - scfg->listening_socket = sk_newlistener( - NULL, ci->listen_port, &scfg->listening_plug, true, - ADDRTYPE_UNSPEC); - msg = dupprintf("serv#%u: listening on port %d", - scfg->config_id, ci->listen_port); - } else { - SockAddr *addr = unix_sock_addr(ci->listen_socket); - scfg->listening_socket = new_unix_listener( - addr, &scfg->listening_plug); - msg = dupprintf("serv#%u: listening on Unix socket %s", - scfg->config_id, ci->listen_socket); - } - - log_to_stderr(-1, msg); - sfree(msg); - } else { - struct server_instance *inst; - Plug *plug = server_conn_plug(scfg, &inst); - ssh_server_start(plug, make_fd_socket(0, 1, -1, NULL, 0, plug)); - log_to_stderr(inst->id, "speaking SSH on stdio"); - } -} - -int main(int argc, char **argv) -{ - size_t ninstances = 0, instancessize = 8; - struct cmdline_instance *instances = snewn( - instancessize, struct cmdline_instance); - struct cmdline_instance *ci = &instances[ninstances++]; - init_cmdline_instance(ci); - - if (argc <= 1) { - /* - * We're going to terminate with an error message below, - * because there are no host keys. But we'll display the help - * as additional standard-error output, if nothing else so - * that people see the giant safety warning. - */ - show_help(stderr); - fputc('\n', stderr); - } - - while (--argc > 0) { - const char *arg = *++argv; - const char *val; - - if (!strcmp(arg, "--help")) { - show_help(stdout); - exit(0); - } else if (longoptnoarg(arg, "--version")) { - show_version_and_exit(); - } else if (longoptnoarg(arg, "--verbose") || !strcmp(arg, "-v")) { - verbose = true; - } else if (longoptnoarg(arg, "--and")) { - sgrowarray(instances, instancessize, ninstances); - ci = &instances[ninstances++]; - init_cmdline_instance(ci); - } else if (longoptarg(arg, "--listen", &val, &argc, &argv)) { - if (val[0] == '/') { - ci->listen_port = -1; - ci->listen_socket = val; - } else { - ci->listen_port = atoi(val); - ci->listen_socket = NULL; - } - } else if (!strcmp(arg, "--listen-once")) { - listen_once = true; - } else if (longoptarg(arg, "--hostkey", &val, &argc, &argv)) { - Filename *keyfile; - int keytype; - const char *error; - - keyfile = filename_from_str(val); - keytype = key_type(keyfile); - - if (keytype == SSH_KEYTYPE_SSH2) { - ssh2_userkey *uk; - ssh_key *key; - uk = ppk_load_f(keyfile, NULL, &error); - filename_free(keyfile); - if (!uk || !uk->key) { - fprintf(stderr, "%s: unable to load host key '%s': " - "%s\n", appname, val, error); - exit(1); - } - char *invalid = ssh_key_invalid(uk->key, 0); - if (invalid) { - fprintf(stderr, "%s: host key '%s' is unusable: " - "%s\n", appname, val, invalid); - exit(1); - } - key = uk->key; - sfree(uk->comment); - sfree(uk); - - for (int i = 0; i < ci->nhostkeys; i++) - if (ssh_key_alg(ci->hostkeys[i]) == ssh_key_alg(key)) { - fprintf(stderr, "%s: host key '%s' duplicates key " - "type %s\n", appname, val, - ssh_key_alg(key)->ssh_id); - exit(1); - } - - sgrowarray(ci->hostkeys, ci->hostkeysize, ci->nhostkeys); - ci->hostkeys[ci->nhostkeys++] = key; - } else if (keytype == SSH_KEYTYPE_SSH1) { - if (ci->hostkey1) { - fprintf(stderr, "%s: host key '%s' is a redundant " - "SSH-1 host key\n", appname, val); - exit(1); - } - ci->hostkey1 = snew(RSAKey); - if (!rsa1_load_f(keyfile, ci->hostkey1, NULL, &error)) { - fprintf(stderr, "%s: unable to load host key '%s': " - "%s\n", appname, val, error); - exit(1); - } - } else { - fprintf(stderr, "%s: '%s' is not loadable as a " - "private key (%s)", appname, val, - key_type_to_str(keytype)); - exit(1); - } - } else if (longoptarg(arg, "--rsakexkey", &val, &argc, &argv)) { - Filename *keyfile; - int keytype; - const char *error; - - keyfile = filename_from_str(val); - keytype = key_type(keyfile); - - if (keytype != SSH_KEYTYPE_SSH1) { - fprintf(stderr, "%s: '%s' is not loadable as an SSH-1 format " - "private key (%s)", appname, val, - key_type_to_str(keytype)); - exit(1); - } - - if (ci->ssc.rsa_kex_key) { - freersakey(ci->ssc.rsa_kex_key); - } else { - ci->ssc.rsa_kex_key = snew(RSAKey); - } - - if (!rsa1_load_f(keyfile, ci->ssc.rsa_kex_key, NULL, &error)) { - fprintf(stderr, "%s: unable to load RSA kex key '%s': " - "%s\n", appname, val, error); - exit(1); - } - - ci->ssc.rsa_kex_key->sshk.vt = &ssh_rsa; - } else if (longoptarg(arg, "--userkey", &val, &argc, &argv)) { - Filename *keyfile; - int keytype; - const char *error; - - keyfile = filename_from_str(val); - keytype = key_type(keyfile); - - if (keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 || - keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) { - strbuf *sb = strbuf_new(); - struct AuthPolicy_ssh2_pubkey *node; - void *blob; - - if (!ppk_loadpub_f(keyfile, NULL, BinarySink_UPCAST(sb), - NULL, &error)) { - fprintf(stderr, "%s: unable to load user key '%s': " - "%s\n", appname, val, error); - exit(1); - } - - node = snew_plus(struct AuthPolicy_ssh2_pubkey, sb->len); - blob = snew_plus_get_aux(node); - memcpy(blob, sb->u, sb->len); - node->public_blob = make_ptrlen(blob, sb->len); - - node->next = ci->aps.ssh2keys; - ci->aps.ssh2keys = node; - - strbuf_free(sb); - } else if (keytype == SSH_KEYTYPE_SSH1_PUBLIC) { - strbuf *sb = strbuf_new(); - BinarySource src[1]; - struct AuthPolicy_ssh1_pubkey *node; - - if (!rsa1_loadpub_f(keyfile, BinarySink_UPCAST(sb), - NULL, &error)) { - fprintf(stderr, "%s: unable to load user key '%s': " - "%s\n", appname, val, error); - exit(1); - } - - node = snew(struct AuthPolicy_ssh1_pubkey); - BinarySource_BARE_INIT(src, sb->u, sb->len); - get_rsa_ssh1_pub(src, &node->key, RSA_SSH1_EXPONENT_FIRST); - - node->next = ci->aps.ssh1keys; - ci->aps.ssh1keys = node; - - strbuf_free(sb); - } else { - fprintf(stderr, "%s: '%s' is not loadable as a public key " - "(%s)\n", appname, val, key_type_to_str(keytype)); - exit(1); - } - } else if (longoptarg(arg, "--bannerfile", &val, &argc, &argv)) { - FILE *fp = fopen(val, "r"); - if (!fp) { - fprintf(stderr, "%s: %s: open: %s\n", appname, - val, strerror(errno)); - exit(1); - } - strbuf *sb = strbuf_new(); - if (!read_file_into(BinarySink_UPCAST(sb), fp)) { - fprintf(stderr, "%s: %s: read: %s\n", appname, - val, strerror(errno)); - exit(1); - } - fclose(fp); - ci->ssc.banner = ptrlen_from_strbuf(sb); - } else if (longoptarg(arg, "--bannertext", &val, &argc, &argv)) { - ci->ssc.banner = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--sessiondir", &val, &argc, &argv)) { - ci->ssc.session_starting_dir = val; - } else if (longoptarg(arg, "--kexinit-kex", &val, &argc, &argv)) { - ci->ssc.kex_override[KEXLIST_KEX] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-hostkey", &val, &argc, &argv)) { - ci->ssc.kex_override[KEXLIST_HOSTKEY] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-cscipher", &val, &argc, &argv)) { - ci->ssc.kex_override[KEXLIST_CSCIPHER] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-csmac", &val, &argc, &argv)) { - ci->ssc.kex_override[KEXLIST_CSMAC] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-cscomp", &val, &argc, &argv)) { - ci->ssc.kex_override[KEXLIST_CSCOMP] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-sccipher", &val, &argc, &argv)) { - ci->ssc.kex_override[KEXLIST_SCCIPHER] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-scmac", &val, &argc, &argv)) { - ci->ssc.kex_override[KEXLIST_SCMAC] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--kexinit-sccomp", &val, &argc, &argv)) { - ci->ssc.kex_override[KEXLIST_SCCOMP] = ptrlen_from_asciz(val); - } else if (longoptarg(arg, "--allow-auth", &val, &argc, &argv)) { - unsigned method = auth_method_from_name(val); - if (!method) { - fprintf(stderr, "%s: unrecognised auth method '%s'\n", - appname, val); - exit(1); - } - ci->auth_methods |= method; - } else if (longoptarg(arg, "--deny-auth", &val, &argc, &argv)) { - unsigned method = auth_method_from_name(val); - if (!method) { - fprintf(stderr, "%s: unrecognised auth method '%s'\n", - appname, val); - exit(1); - } - ci->auth_methods &= ~method; - } else if (longoptarg(arg, "--ssh1-ciphers", &val, &argc, &argv)) { - ptrlen list = ptrlen_from_asciz(val); - ptrlen word; - unsigned long mask = 0; - while (word = ptrlen_get_word(&list, ","), word.len != 0) { - -#define SSH1_CIPHER_CASE(bitpos, name) \ - if (ptrlen_eq_string(word, name)) { \ - mask |= 1U << bitpos; \ - continue; \ - } - SSH1_SUPPORTED_CIPHER_LIST(SSH1_CIPHER_CASE); -#undef SSH1_CIPHER_CASE - - fprintf(stderr, "%s: unrecognised SSH-1 cipher '%.*s'\n", - appname, PTRLEN_PRINTF(word)); - exit(1); - } - ci->ssc.ssh1_cipher_mask = mask; - } else if (longoptnoarg(arg, "--ssh1-no-compression")) { - ci->ssc.ssh1_allow_compression = false; - } else if (longoptnoarg(arg, "--exitsignum")) { - ci->ssc.exit_signal_numeric = true; - } else if (longoptarg(arg, "--sshlog", &val, &argc, &argv) || - longoptarg(arg, "-sshlog", &val, &argc, &argv)) { - Filename *logfile = filename_from_str(val); - conf_set_filename(ci->conf, CONF_logfilename, logfile); - filename_free(logfile); - conf_set_int(ci->conf, CONF_logtype, LGTYP_PACKETS); - conf_set_int(ci->conf, CONF_logxfovr, LGXF_OVR); - } else if (longoptarg(arg, "--sshrawlog", &val, &argc, &argv) || - longoptarg(arg, "-sshrawlog", &val, &argc, &argv)) { - Filename *logfile = filename_from_str(val); - conf_set_filename(ci->conf, CONF_logfilename, logfile); - filename_free(logfile); - conf_set_int(ci->conf, CONF_logtype, LGTYP_SSHRAW); - conf_set_int(ci->conf, CONF_logxfovr, LGXF_OVR); - } else if (!strcmp(arg, "--pretend-to-accept-any-pubkey")) { - ci->ssc.stunt_pretend_to_accept_any_pubkey = true; - } else if (!strcmp(arg, "--open-unconditional-agent-socket")) { - ci->ssc.stunt_open_unconditional_agent_socket = true; - } else if (!strcmp(arg, "--allow-none-auth")) { - /* backwards-compatibility synonym for --allow-auth=none */ - ci->auth_methods |= AUTHMETHOD_NONE; - } else if (!strcmp(arg, "--allow-trivial-ki-auth")) { - ci->ssc.stunt_allow_trivial_ki_auth = true; - } else if (!strcmp(arg, "--return-success-to-pubkey-offer")) { - ci->ssc.stunt_return_success_to_pubkey_offer = true; - } else if (!strcmp(arg, "--close-after-banner")) { - ci->ssc.stunt_close_after_banner = true; - } else { - fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg); - exit(1); - } - } - - if (ninstances > 1 && listen_once) { - fprintf(stderr, "%s: cannot listen once only with multiple server " - "instances\n", appname); - exit(1); - } - for (size_t i = 0; i < ninstances; i++) { - ci = &instances[i]; - if (ci->nhostkeys == 0 && !ci->hostkey1) { - fprintf(stderr, "%s: specify at least one host key\n", appname); - exit(1); - } - if (ninstances > 1 && !(ci->listen_port >= 0 || ci->listen_socket)) { - fprintf(stderr, "%s: cannot talk to stdio with multiple server " - "instances\n", appname); - exit(1); - } - } - - random_ref(); - - /* - * Block SIGPIPE, so that we'll get EPIPE individually on - * particular network connections that go wrong. - */ - putty_signal(SIGPIPE, SIG_IGN); - - sk_init(); - uxsel_init(); - - for (size_t i = 0; i < ninstances; i++) - cmdline_instance_start(&instances[i]); - - cli_main_loop(cliloop_no_pw_setup, cliloop_no_pw_check, - cliloop_always_continue, NULL); - - return 0; -} diff --git a/unix/utils/align_label_left.c b/unix/utils/align_label_left.c deleted file mode 100644 index eaae01ab4..000000000 --- a/unix/utils/align_label_left.c +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Helper function to align the text in a GtkLabel to the left, which - * has to be done in several different ways depending on GTK version. - */ - -#include -#include "putty.h" -#include "gtkcompat.h" -#include "gtkmisc.h" - -void align_label_left(GtkLabel *label) -{ -#if GTK_CHECK_VERSION(3,16,0) - gtk_label_set_xalign(label, 0.0); -#elif GTK_CHECK_VERSION(3,14,0) - gtk_widget_set_halign(GTK_WIDGET(label), GTK_ALIGN_START); -#else - gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0); -#endif -} diff --git a/unix/utils/arm_arch_queries.c b/unix/utils/arm_arch_queries.c deleted file mode 100644 index c3dc286bb..000000000 --- a/unix/utils/arm_arch_queries.c +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Unix implementation of the OS query functions that detect Arm - * architecture extensions. - */ - -#include "putty.h" -#include "ssh.h" - -#include "utils/arm_arch_queries.h" - -#if defined __arm__ || defined __aarch64__ - -bool platform_aes_neon_available(void) -{ -#if defined HWCAP_AES - return getauxval(AT_HWCAP) & HWCAP_AES; -#elif defined HWCAP2_AES - return getauxval(AT_HWCAP2) & HWCAP2_AES; -#elif defined __APPLE__ - SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_AES"); - /* Older M1 macOS didn't provide this flag, but as far as I know - * implemented the crypto extension anyway, so treat 'feature - * missing' as 'implemented' */ - return res != SYSCTL_OFF; -#else - return false; -#endif -} - -bool platform_pmull_neon_available(void) -{ -#if defined HWCAP_PMULL - return getauxval(AT_HWCAP) & HWCAP_PMULL; -#elif defined HWCAP2_PMULL - return getauxval(AT_HWCAP2) & HWCAP2_PMULL; -#elif defined __APPLE__ - SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_PMULL"); - /* As above, treat 'missing' as enabled */ - return res != SYSCTL_OFF; -#else - return false; -#endif -} - -bool platform_sha256_neon_available(void) -{ -#if defined HWCAP_SHA2 - return getauxval(AT_HWCAP) & HWCAP_SHA2; -#elif defined HWCAP2_SHA2 - return getauxval(AT_HWCAP2) & HWCAP2_SHA2; -#elif defined __APPLE__ - SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_SHA256"); - /* As above, treat 'missing' as enabled */ - return res != SYSCTL_OFF; -#else - return false; -#endif -} - -bool platform_sha1_neon_available(void) -{ -#if defined HWCAP_SHA1 - return getauxval(AT_HWCAP) & HWCAP_SHA1; -#elif defined HWCAP2_SHA1 - return getauxval(AT_HWCAP2) & HWCAP2_SHA1; -#elif defined __APPLE__ - SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_SHA1"); - /* As above, treat 'missing' as enabled */ - return res != SYSCTL_OFF; -#else - return false; -#endif -} - -bool platform_sha512_neon_available(void) -{ -#if defined HWCAP_SHA512 - return getauxval(AT_HWCAP) & HWCAP_SHA512; -#elif defined HWCAP2_SHA512 - return getauxval(AT_HWCAP2) & HWCAP2_SHA512; -#elif defined __APPLE__ - /* There are two sysctl flags for this, apparently invented at - * different times. Try both, falling back to the older one. */ - SysctlResult res = test_sysctl_flag("hw.optional.arm.FEAT_SHA512"); - if (res != SYSCTL_MISSING) - return res == SYSCTL_ON; - - res = test_sysctl_flag("hw.optional.armv8_2_sha512"); - return res == SYSCTL_ON; -#else - return false; -#endif -} - -#else /* defined __arm__ || defined __aarch64__ */ - -/* - * Include _something_ in this file to prevent an annoying compiler - * warning, and to avoid having to condition out this file in - * CMakeLists. It's in a library, so this variable shouldn't end up in - * any actual program, because nothing will refer to it. - */ -const int arm_arch_queries_dummy_variable = 0; - -#endif /* defined __arm__ || defined __aarch64__ */ diff --git a/unix/utils/arm_arch_queries.h b/unix/utils/arm_arch_queries.h deleted file mode 100644 index fa46c6220..000000000 --- a/unix/utils/arm_arch_queries.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Header included only by arm_arch_queries.c. - * - * The only reason this is a header file instead of a source file is - * so that I can define 'static inline' functions which may or may not - * be used, without provoking a compiler warning when I turn out not - * to use them in the subsequent source file. - */ - -#ifndef PUTTY_ARM_ARCH_QUERIES_H -#define PUTTY_ARM_ARCH_QUERIES_H - -#if defined __APPLE__ -#if HAVE_SYS_SYSCTL_H -#include -#endif -#endif /* defined __APPLE__ */ - -#if defined __arm__ || defined __aarch64__ - -#if HAVE_SYS_TYPES_H -#include -#endif - -#if HAVE_SYS_AUXV_H -#include -#endif - -#if HAVE_ASM_HWCAP_H -#include -#endif - -#if HAVE_GETAUXVAL -/* No code needed: getauxval has just the API we want already */ -#elif HAVE_ELF_AUX_INFO -/* Implement the simple getauxval API in terms of FreeBSD elf_aux_info */ -static inline u_long getauxval(int which) -{ - u_long toret; - if (elf_aux_info(which, &toret, sizeof(toret)) != 0) - return 0; /* elf_aux_info didn't work */ - return toret; -} -#else -/* Implement a stub getauxval which returns no capabilities */ -static inline u_long getauxval(int which) { return 0; } -#endif - -#endif /* defined __arm__ || defined __aarch64__ */ - -#if defined __APPLE__ -typedef enum { SYSCTL_MISSING, SYSCTL_OFF, SYSCTL_ON } SysctlResult; - -static inline SysctlResult test_sysctl_flag(const char *flagname) -{ -#if HAVE_SYSCTLBYNAME - int value; - size_t size = sizeof(value); - if (sysctlbyname(flagname, &value, &size, NULL, 0) == 0 && - size == sizeof(value)) { - return value != 0 ? SYSCTL_ON : SYSCTL_OFF; - } -#else /* HAVE_SYSCTLBYNAME */ - return SYSCTL_MISSING; -#endif /* HAVE_SYSCTLBYNAME */ -} -#endif /* defined __APPLE__ */ - -#endif /* PUTTY_ARM_ARCH_QUERIES_H */ diff --git a/unix/utils/block_signal.c b/unix/utils/block_signal.c deleted file mode 100644 index 8c6245ff5..000000000 --- a/unix/utils/block_signal.c +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Handy function to block or unblock a signal, which does all the - * messing about with sigset_t for you. - */ - -#include -#include - -#include "defs.h" - -void block_signal(int sig, bool 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); - } -} diff --git a/unix/utils/buildinfo_gtk_version.c b/unix/utils/buildinfo_gtk_version.c deleted file mode 100644 index a2ec187e3..000000000 --- a/unix/utils/buildinfo_gtk_version.c +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Return the version of GTK we were compiled against, for buildinfo. - */ - -#include -#include "putty.h" -#include "gtkcompat.h" -#include "gtkmisc.h" - -char *buildinfo_gtk_version(void) -{ - return dupprintf("%d.%d.%d", - GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION); -} diff --git a/unix/utils/cloexec.c b/unix/utils/cloexec.c deleted file mode 100644 index 2fc22289a..000000000 --- a/unix/utils/cloexec.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Set and clear the FD_CLOEXEC fcntl option on a file descriptor. - * - * We don't realistically expect 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. - */ - -#include -#include -#include -#include - -#include - -#include "putty.h" - -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); - } -} diff --git a/unix/utils/dputs.c b/unix/utils/dputs.c deleted file mode 100644 index 97a275e00..000000000 --- a/unix/utils/dputs.c +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Implementation of dputs() for Unix. - * - * The debug messages are written to standard output, and also into a - * file called debug.log. - */ - -#include - -#include "putty.h" - -static FILE *debug_fp = NULL; - -void dputs(const 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); -} diff --git a/unix/utils/filename.c b/unix/utils/filename.c deleted file mode 100644 index e786b222c..000000000 --- a/unix/utils/filename.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Implementation of Filename for Unix, including f_open(). - */ - -#include -#include - -#include "putty.h" - -Filename *filename_from_str(const char *str) -{ - Filename *fn = snew(Filename); - fn->path = dupstr(str); - return fn; -} - -Filename *filename_copy(const Filename *fn) -{ - return filename_from_str(fn->path); -} - -const char *filename_to_str(const Filename *fn) -{ - return fn->path; -} - -bool filename_equal(const Filename *f1, const Filename *f2) -{ - return !strcmp(f1->path, f2->path); -} - -bool filename_is_null(const Filename *fn) -{ - return !fn->path[0]; -} - -void filename_free(Filename *fn) -{ - sfree(fn->path); - sfree(fn); -} - -void filename_serialise(BinarySink *bs, const Filename *f) -{ - put_asciz(bs, f->path); -} -Filename *filename_deserialise(BinarySource *src) -{ - return filename_from_str(get_asciz(src)); -} - -char filename_char_sanitise(char c) -{ - if (c == '/') - return '.'; - return c; -} - -FILE *f_open(const Filename *filename, char const *mode, bool 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); - } -} diff --git a/unix/utils/fontspec.c b/unix/utils/fontspec.c deleted file mode 100644 index b9bf81955..000000000 --- a/unix/utils/fontspec.c +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Implementation of FontSpec for Unix. This is more or less - * degenerate - on this platform a font specification is just a - * string. - */ - -#include "putty.h" - -FontSpec *fontspec_new(const char *name) -{ - FontSpec *f = snew(FontSpec); - f->name = dupstr(name); - return f; -} - -FontSpec *fontspec_new_default(void) -{ - return fontspec_new(""); -} - -FontSpec *fontspec_copy(const FontSpec *f) -{ - return fontspec_new(f->name); -} - -void fontspec_free(FontSpec *f) -{ - sfree(f->name); - sfree(f); -} - -void fontspec_serialise(BinarySink *bs, FontSpec *f) -{ - put_asciz(bs, f->name); -} - -FontSpec *fontspec_deserialise(BinarySource *src) -{ - return fontspec_new(get_asciz(src)); -} diff --git a/unix/utils/get_label_text_dimensions.c b/unix/utils/get_label_text_dimensions.c deleted file mode 100644 index fe1c13af9..000000000 --- a/unix/utils/get_label_text_dimensions.c +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Determine the dimensions of a piece of text in the standard - * font used in GTK interface elements like labels. We do this by - * instantiating an actual GtkLabel, and then querying its size. - */ - -#include -#include "putty.h" -#include "gtkcompat.h" -#include "gtkmisc.h" - -void get_label_text_dimensions(const char *text, int *width, int *height) -{ - GtkWidget *label = gtk_label_new(text); - - /* - * GTK2 and GTK3 require us to query the size completely - * differently. I'm sure there ought to be an easier approach than - * the way I'm doing this in GTK3, too! - */ -#if GTK_CHECK_VERSION(3,0,0) - PangoLayout *layout = gtk_label_get_layout(GTK_LABEL(label)); - PangoRectangle logrect; - pango_layout_get_extents(layout, NULL, &logrect); - if (width) - *width = logrect.width / PANGO_SCALE; - if (height) - *height = logrect.height / PANGO_SCALE; -#else - GtkRequisition req; - gtk_widget_size_request(label, &req); - if (width) - *width = req.width; - if (height) - *height = req.height; -#endif - - g_object_ref_sink(G_OBJECT(label)); -#if GTK_CHECK_VERSION(2,10,0) - g_object_unref(label); -#endif -} diff --git a/unix/utils/get_username.c b/unix/utils/get_username.c deleted file mode 100644 index 61e259c01..000000000 --- a/unix/utils/get_username.c +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Implementation of get_username() for Unix. - */ - -#include -#include - -#include "putty.h" - -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(); -#if HAVE_SETPWENT - setpwent(); -#endif - 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; - } -#if HAVE_ENDPWENT - endpwent(); -#endif - - return dupstr(ret); -} diff --git a/unix/utils/get_x11_display.c b/unix/utils/get_x11_display.c deleted file mode 100644 index d9462c271..000000000 --- a/unix/utils/get_x11_display.c +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Return the Xlib 'Display *' underlying our GTK environment, if any. - */ - -#include -#include "putty.h" -#include "gtkcompat.h" -#include "gtkmisc.h" - -#ifndef NOT_X_WINDOWS - -#include -#include - -Display *get_x11_display(void) -{ -#if GTK_CHECK_VERSION(3,0,0) - if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) - return NULL; -#endif - return GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); -} - -#else - -/* - * Include _something_ in this file to prevent an annoying compiler - * warning, and to avoid having to condition out this file in - * CMakeLists. It's in a library, so this variable shouldn't end up in - * any actual program, because nothing will refer to it. - */ -const int get_x11_display_dummy_variable = 0; - -#endif diff --git a/unix/utils/getticks.c b/unix/utils/getticks.c deleted file mode 100644 index 9d169816b..000000000 --- a/unix/utils/getticks.c +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Implement getticks() for Unix. - */ - -#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 HAVE_CLOCK_GETTIME && HAVE_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); - } -} diff --git a/unix/utils/keysym_to_unicode.c b/unix/utils/keysym_to_unicode.c deleted file mode 100644 index 37e419fe5..000000000 --- a/unix/utils/keysym_to_unicode.c +++ /dev/null @@ -1,1011 +0,0 @@ -/* - * Map 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/unix/utils/make_dir_and_check_ours.c b/unix/utils/make_dir_and_check_ours.c deleted file mode 100644 index cab4dc20f..000000000 --- a/unix/utils/make_dir_and_check_ours.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Create a directory accessible only to us, and then check afterwards - * that we really did end up with a directory with the right ownership - * and permissions. - * - * The idea is that this is a directory in which we're about to create - * something sensitive, like a listening Unix-domain socket for SSH - * connection sharing or an SSH agent. We want to be protected 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 (say) use our authenticated SSH sessions. - * - * NOTE: The strategy used in this function is not safe if the enemy - * has unrestricted write access to the containing directory. In that - * case, they could move our directory out of the way and make a new - * one, after this function returns and before we create our socket - * (or whatever) inside it. - * - * But this should be OK for temp directories (which modify the - * default world-write behaviour by also setting the 't' bit, - * preventing people from renaming or deleting things in there that - * they don't own). And of course it's also safe if the directory is - * writable only by our _own_ uid. - */ - -#include -#include - -#include -#include -#include - -#include "putty.h" - -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)); - - /* - * Stat the directory and check its ownership and permissions. - */ - 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; -} diff --git a/unix/utils/make_dir_path.c b/unix/utils/make_dir_path.c deleted file mode 100644 index 4d212fe47..000000000 --- a/unix/utils/make_dir_path.c +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Make a path of subdirectories, tolerating EEXIST at every step. - */ - -#include -#include - -#include -#include -#include - -#include "putty.h" - -char *make_dir_path(const char *path, mode_t mode) -{ - int pos = 0; - char *prefix; - - while (1) { - pos += strcspn(path + pos, "/"); - - if (pos > 0) { - prefix = dupprintf("%.*s", pos, path); - - if (mkdir(prefix, mode) < 0 && errno != EEXIST) { - char *ret = dupprintf("%s: mkdir: %s", - prefix, strerror(errno)); - sfree(prefix); - return ret; - } - - sfree(prefix); - } - - if (!path[pos]) - return NULL; - pos += strspn(path + pos, "/"); - } -} diff --git a/unix/utils/make_spr_sw_abort_errno.c b/unix/utils/make_spr_sw_abort_errno.c deleted file mode 100644 index 57f58c2fa..000000000 --- a/unix/utils/make_spr_sw_abort_errno.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Constructor function for a SeatPromptResult of the 'software abort' - * category, whose error message includes the translation of an OS - * error code. - */ - -#include "putty.h" - -static void spr_errno_errfn(SeatPromptResult spr, BinarySink *bs) -{ - put_fmt(bs, "%s: %s", spr.errdata_lit, strerror(spr.errdata_u)); -} - -SeatPromptResult make_spr_sw_abort_errno(const char *prefix, int errno_value) -{ - SeatPromptResult spr; - spr.kind = SPRK_SW_ABORT; - spr.errfn = spr_errno_errfn; - spr.errdata_lit = prefix; - spr.errdata_u = errno_value; - return spr; -} diff --git a/unix/utils/nonblock.c b/unix/utils/nonblock.c deleted file mode 100644 index cece206c0..000000000 --- a/unix/utils/nonblock.c +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Set and clear the O_NONBLOCK fcntl option on an open file. - * - * We don't realistically expect 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. - * - * Returns the previous state of O_NONBLOCK. - */ - -#include -#include -#include -#include - -#include - -#include "putty.h" - -bool 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; -} - -bool 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; -} diff --git a/unix/utils/open_for_write_would_lose_data.c b/unix/utils/open_for_write_would_lose_data.c deleted file mode 100644 index 46e695fd0..000000000 --- a/unix/utils/open_for_write_would_lose_data.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Unix implementation of open_for_write_would_lose_data(). - */ - -#include -#include - -#include "putty.h" - -bool open_for_write_would_lose_data(const Filename *fn) -{ - struct stat st; - - if (stat(fn->path, &st) < 0) { - /* - * If the file doesn't even exist, we obviously want to return - * false. If we failed to stat it for any other reason, - * ignoring the precise error code and returning false still - * doesn't seem too unreasonable, because then we'll try to - * open the file for writing and report _that_ error, which is - * likely to be more to the point. - */ - return false; - } - - /* - * OK, something exists at this pathname and we've found out - * something about it. But an open-for-write will only - * destructively truncate it if it's a regular file with nonzero - * size. If it's empty, or some other kind of special thing like a - * character device (e.g. /dev/tty) or a named pipe, then opening - * it for write is already non-destructive and it's pointless and - * annoying to warn about it just because the same file can be - * opened for reading. (Indeed, if it's a named pipe, opening it - * for reading actually _causes inconvenience_ in its own right, - * even before the question of whether it gives misleading - * information.) - */ - if (S_ISREG(st.st_mode) && st.st_size > 0) { - return true; - } - - return false; -} diff --git a/unix/utils/our_dialog.c b/unix/utils/our_dialog.c deleted file mode 100644 index 0ca8a7d24..000000000 --- a/unix/utils/our_dialog.c +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Functions to arrange controls in a basically dialog-like window. - * - * The best method for doing this has varied wildly with versions of - * GTK, hence the set of wrapper functions here. - * - * In GTK 1, a GtkDialog has an 'action_area' at the bottom, which is - * a GtkHBox which stretches to cover the full width of the dialog. So - * we can either add buttons or other widgets to that box directly, or - * alternatively we can fill the hbox with some layout class of our - * own such as a Columns widget. - * - * In GTK 2, the action area has become a GtkHButtonBox, and its - * layout behaviour seems to be different and not what we want. 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. - * - * In GTK 3, we typically don't even want to use GtkDialog at all, - * because GTK 3 has become a lot more restrictive about what you can - * sensibly use GtkDialog for - it deprecates direct access to the - * action area in favour of making you provide nothing but - * dialog-ending buttons in the form of (text, response code) pairs, - * so you can't put any other kind of control in there, or fiddle with - * alignment and positioning, or even have a button that _doesn't_ end - * the dialog (e.g. 'View Licence' in our About box). So instead of - * GtkDialog, we use a straight-up GtkWindow and have it contain a - * vbox as its (unique) child widget; and we implement the action area - * by adding a separator and another widget at the bottom of that - * vbox. - */ - -#include -#include "putty.h" -#include "gtkcompat.h" -#include "gtkmisc.h" - -GtkWidget *our_dialog_new(void) -{ -#if GTK_CHECK_VERSION(3,0,0) - /* - * See comment in our_dialog_set_action_area(): in GTK 3, we use - * GtkWindow in place of GtkDialog for most purposes. - */ - GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL); - GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8); - gtk_container_add(GTK_CONTAINER(w), vbox); - gtk_widget_show(vbox); - return w; -#else - return gtk_dialog_new(); -#endif -} - -void our_dialog_set_action_area(GtkWindow *dlg, GtkWidget *w) -{ -#if !GTK_CHECK_VERSION(2,0,0) - - gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), - w, true, true, 0); - -#elif !GTK_CHECK_VERSION(3,0,0) - - 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(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), - align, false, true, 0); - - w = gtk_hseparator_new(); - gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), - w, false, true, 0); - gtk_widget_show(w); - gtk_widget_hide(gtk_dialog_get_action_area(GTK_DIALOG(dlg))); - g_object_set(G_OBJECT(dlg), "has-separator", true, (const char *)NULL); - -#else /* GTK 3 */ - - /* GtkWindow is a GtkBin, hence contains exactly one child, which - * here we always expect to be a vbox */ - GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg))); - GtkWidget *sep; - - g_object_set(G_OBJECT(w), "margin", 8, (const char *)NULL); - gtk_box_pack_end(vbox, w, false, true, 0); - - sep = gtk_hseparator_new(); - gtk_box_pack_end(vbox, sep, false, true, 0); - gtk_widget_show(sep); - -#endif -} - -GtkBox *our_dialog_make_action_hbox(GtkWindow *dlg) -{ -#if GTK_CHECK_VERSION(3,0,0) - GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - our_dialog_set_action_area(dlg, hbox); - g_object_set(G_OBJECT(hbox), "margin", 0, (const char *)NULL); - g_object_set(G_OBJECT(hbox), "spacing", 8, (const char *)NULL); - gtk_widget_show(hbox); - return GTK_BOX(hbox); -#else /* not GTK 3 */ - return GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(dlg))); -#endif -} - -void our_dialog_add_to_content_area(GtkWindow *dlg, GtkWidget *w, - bool expand, bool fill, guint padding) -{ -#if GTK_CHECK_VERSION(3,0,0) - /* GtkWindow is a GtkBin, hence contains exactly one child, which - * here we always expect to be a vbox */ - GtkBox *vbox = GTK_BOX(gtk_bin_get_child(GTK_BIN(dlg))); - - gtk_box_pack_start(vbox, w, expand, fill, padding); -#else - gtk_box_pack_start( - GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), - w, expand, fill, padding); -#endif -} diff --git a/unix/utils/pgp_fingerprints.c b/unix/utils/pgp_fingerprints.c deleted file mode 100644 index 9fb6bf429..000000000 --- a/unix/utils/pgp_fingerprints.c +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Display the fingerprints of the PGP Master Keys to the user. - * - * (This is in its own file rather than in console.c, because it's - * appropriate even for Unix GUI apps.) - */ - -#include "putty.h" - -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 " PGP_MASTER_KEY_YEAR - " (" PGP_MASTER_KEY_DETAILS "):\n" - " " PGP_MASTER_KEY_FP "\n\n" - "Previous Master Key (" PGP_PREV_MASTER_KEY_YEAR - ", " PGP_PREV_MASTER_KEY_DETAILS "):\n" - " " PGP_PREV_MASTER_KEY_FP "\n", stdout); -} diff --git a/unix/utils/pollwrap.c b/unix/utils/pollwrap.c deleted file mode 100644 index 51121e361..000000000 --- a/unix/utils/pollwrap.c +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Wrapper system around poll() that lets me treat it more or less - * like select(), but avoiding the inherent limitation of select() - * that it can't handle the full range of fds that are capable of - * existing. - * - * The pollwrapper structure contains the 'struct pollfd' array passed - * to poll() itself, and also a tree234 that maps each fd to its - * location in the list, which makes it convenient to add or remove - * individual fds from the system or change what events you're - * watching for on them. So the API is _shaped_ basically like select, - * even if none of the details are identical: from outside this - * module, a pollwrapper can be used wherever you'd otherwise have had - * an fd_set. - * - * Also, this module translate between the simple select r/w/x - * classification and the richer poll flags. We have to stick to r/w/x - * in this code base, because it ports to other systems where that's - * all you get. - */ - -/* On some systems this is needed to get poll.h to define eg.. POLLRDNORM */ -#define _XOPEN_SOURCE - -#include - -#include "putty.h" -#include "tree234.h" - -struct pollwrapper { - struct pollfd *fds; - size_t nfd, fdsize; - tree234 *fdtopos; -}; - -typedef struct pollwrap_fdtopos pollwrap_fdtopos; -struct pollwrap_fdtopos { - int fd; - size_t pos; -}; - -static int pollwrap_fd_cmp(void *av, void *bv) -{ - pollwrap_fdtopos *a = (pollwrap_fdtopos *)av; - pollwrap_fdtopos *b = (pollwrap_fdtopos *)bv; - return a->fd < b->fd ? -1 : a->fd > b->fd ? +1 : 0; -} - -pollwrapper *pollwrap_new(void) -{ - pollwrapper *pw = snew(pollwrapper); - pw->fdsize = 16; - pw->nfd = 0; - pw->fds = snewn(pw->fdsize, struct pollfd); - pw->fdtopos = newtree234(pollwrap_fd_cmp); - return pw; -} - -void pollwrap_free(pollwrapper *pw) -{ - pollwrap_clear(pw); - freetree234(pw->fdtopos); - sfree(pw->fds); - sfree(pw); -} - -void pollwrap_clear(pollwrapper *pw) -{ - pw->nfd = 0; - for (pollwrap_fdtopos *f2p; - (f2p = delpos234(pw->fdtopos, 0)) != NULL ;) - sfree(f2p); -} - -void pollwrap_add_fd_events(pollwrapper *pw, int fd, int events) -{ - pollwrap_fdtopos *f2p, f2p_find; - - assert(fd >= 0); - - f2p_find.fd = fd; - f2p = find234(pw->fdtopos, &f2p_find, NULL); - if (!f2p) { - sgrowarray(pw->fds, pw->fdsize, pw->nfd); - size_t index = pw->nfd++; - pw->fds[index].fd = fd; - pw->fds[index].events = pw->fds[index].revents = 0; - - f2p = snew(pollwrap_fdtopos); - f2p->fd = fd; - f2p->pos = index; - pollwrap_fdtopos *added = add234(pw->fdtopos, f2p); - assert(added == f2p); - } - - pw->fds[f2p->pos].events |= events; -} - -/* Omit any of the POLL{RD,WR}{NORM,BAND} flag values that are still - * not defined by poll.h, just in case */ -#ifndef POLLRDNORM -#define POLLRDNORM 0 -#endif -#ifndef POLLRDBAND -#define POLLRDBAND 0 -#endif -#ifndef POLLWRNORM -#define POLLWRNORM 0 -#endif -#ifndef POLLWRBAND -#define POLLWRBAND 0 -#endif - -#define SELECT_R_IN (POLLIN | POLLRDNORM | POLLRDBAND) -#define SELECT_W_IN (POLLOUT | POLLWRNORM | POLLWRBAND) -#define SELECT_X_IN (POLLPRI) - -#define SELECT_R_OUT (SELECT_R_IN | POLLERR | POLLHUP) -#define SELECT_W_OUT (SELECT_W_IN | POLLERR) -#define SELECT_X_OUT (SELECT_X_IN) - -void pollwrap_add_fd_rwx(pollwrapper *pw, int fd, int rwx) -{ - int events = 0; - if (rwx & SELECT_R) - events |= SELECT_R_IN; - if (rwx & SELECT_W) - events |= SELECT_W_IN; - if (rwx & SELECT_X) - events |= SELECT_X_IN; - pollwrap_add_fd_events(pw, fd, events); -} - -int pollwrap_poll_instant(pollwrapper *pw) -{ - return poll(pw->fds, pw->nfd, 0); -} - -int pollwrap_poll_endless(pollwrapper *pw) -{ - return poll(pw->fds, pw->nfd, -1); -} - -int pollwrap_poll_timeout(pollwrapper *pw, int milliseconds) -{ - assert(milliseconds >= 0); - return poll(pw->fds, pw->nfd, milliseconds); -} - -static void pollwrap_get_fd_events_revents(pollwrapper *pw, int fd, - int *events_p, int *revents_p) -{ - pollwrap_fdtopos *f2p, f2p_find; - int events = 0, revents = 0; - - assert(fd >= 0); - - f2p_find.fd = fd; - f2p = find234(pw->fdtopos, &f2p_find, NULL); - if (f2p) { - events = pw->fds[f2p->pos].events; - revents = pw->fds[f2p->pos].revents; - } - - if (events_p) - *events_p = events; - if (revents_p) - *revents_p = revents; -} - -int pollwrap_get_fd_events(pollwrapper *pw, int fd) -{ - int revents; - pollwrap_get_fd_events_revents(pw, fd, NULL, &revents); - return revents; -} - -int pollwrap_get_fd_rwx(pollwrapper *pw, int fd) -{ - int events, revents; - pollwrap_get_fd_events_revents(pw, fd, &events, &revents); - int rwx = 0; - if ((events & POLLIN) && (revents & SELECT_R_OUT)) - rwx |= SELECT_R; - if ((events & POLLOUT) && (revents & SELECT_W_OUT)) - rwx |= SELECT_W; - if ((events & POLLPRI) && (revents & SELECT_X_OUT)) - rwx |= SELECT_X; - return rwx; -} diff --git a/unix/utils/signal.c b/unix/utils/signal.c deleted file mode 100644 index 81234f978..000000000 --- a/unix/utils/signal.c +++ /dev/null @@ -1,30 +0,0 @@ -/* - * PuTTY's wrapper on signal(2). - * - * 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). - */ - -#include - -#include "defs.h" - -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; -} diff --git a/unix/utils/string_width.c b/unix/utils/string_width.c deleted file mode 100644 index c944308fa..000000000 --- a/unix/utils/string_width.c +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Return the width of a string in the font used in GTK controls. Used - * as a means of picking a sensible size for dialog boxes and pieces - * of them, in a way that should adapt sensibly to changes in font and - * resolution. - */ - -#include -#include "putty.h" -#include "gtkcompat.h" -#include "gtkmisc.h" - -int string_width(const char *text) -{ - int ret; - get_label_text_dimensions(text, &ret, NULL); - return ret; -} diff --git a/unix/utils/x11_ignore_error.c b/unix/utils/x11_ignore_error.c deleted file mode 100644 index a4165ab5f..000000000 --- a/unix/utils/x11_ignore_error.c +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Error handling mechanism which permits us to ignore specific X11 - * errors from particular requests. We maintain a list of upcoming - * potential error events that we want to not treat as fatal errors. - */ - -#include -#include -#include -#include -#include - -#include "putty.h" - -#ifndef NOT_X_WINDOWS - -#include -#include -#include - -#include "x11misc.h" - -static int (*orig_x11_error_handler)(Display *thisdisp, XErrorEvent *err); - -struct x11_err_to_ignore { - Display *display; - unsigned char error_code; - unsigned long serial; -}; - -static struct x11_err_to_ignore *errs; -static size_t nerrs, errsize; - -static int x11_error_handler(Display *thisdisp, XErrorEvent *err) -{ - for (size_t i = 0; i < nerrs; i++) { - if (thisdisp == errs[i].display && - err->serial == errs[i].serial && - err->error_code == errs[i].error_code) { - /* Ok, this is an error we're happy to ignore */ - return 0; - } - } - - return (*orig_x11_error_handler)(thisdisp, err); -} - -void x11_ignore_error(Display *disp, unsigned char errcode) -{ - /* - * Install our error handler, if we haven't already. - */ - if (!orig_x11_error_handler) - orig_x11_error_handler = XSetErrorHandler(x11_error_handler); - - /* - * This is as good a moment as any to winnow the ignore list based - * on requests we know to have been processed. - */ - { - unsigned long last = LastKnownRequestProcessed(disp); - size_t i, j; - for (i = j = 0; i < nerrs; i++) { - if (errs[i].display == disp && errs[i].serial <= last) - continue; - errs[j++] = errs[i]; - } - nerrs = j; - } - - sgrowarray(errs, errsize, nerrs); - errs[nerrs].display = disp; - errs[nerrs].error_code = errcode; - errs[nerrs].serial = NextRequest(disp); - nerrs++; -} - -#else /* NOT_X_WINDOWS */ - -/* - * Include _something_ in this file to prevent an annoying compiler - * warning, and to avoid having to condition out this file in - * CMakeLists. It's in a library, so this variable shouldn't end up in - * any actual program, because nothing will refer to it. - */ -const int x11_ignore_error_dummy_variable = 0; - -#endif /* NOT_X_WINDOWS */ diff --git a/unix/uxsel.c b/unix/uxsel.c deleted file mode 100644 index 18d512ac9..000000000 --- a/unix/uxsel.c +++ /dev/null @@ -1,127 +0,0 @@ -/* - * uxsel.c - * - * This module is a sort of all-purpose interchange for file - * descriptors. At one end it talks to network.c and pty.c and - * anything else which might have one or more fds that need - * select() or poll()-type things doing to them during an extended - * program run; at the other end it talks to window.c or plink.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; - uxsel_id *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; - - assert(fd >= 0); - - 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) { - if (oldfd->id) - 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); -} - -void select_result(int fd, int event) -{ - struct fd *fdstruct = find234(fds, &fd, uxsel_fd_findcmp); - - noise_ultralight(NOISE_SOURCE_IOID, fd); - - /* - * 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) - fdstruct->callback(fd, event); -} diff --git a/unix/window.c b/unix/window.c deleted file mode 100644 index f8dbda740..000000000 --- a/unix/window.c +++ /dev/null @@ -1,5682 +0,0 @@ -/* - * window.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 -#if !GTK_CHECK_VERSION(3,0,0) -#include -#endif - -#if GTK_CHECK_VERSION(2,0,0) -#include -#endif - -#define MAY_REFER_TO_GTK_IN_HEADERS - -#include "putty.h" -#include "terminal.h" -#include "gtkcompat.h" -#include "unifont.h" -#include "gtkmisc.h" - -#ifndef NOT_X_WINDOWS -#include -#include -#include -#include -#endif - -#include "x11misc.h" - -GdkAtom compound_text_atom, utf8_string_atom; -static GdkAtom clipboard_atom -#if GTK_CHECK_VERSION(2,0,0) /* GTK1 will have to fill this in at startup */ - = GDK_SELECTION_CLIPBOARD -#endif - ; - -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 -/* - * Because calling gtk_clipboard_set_with_data triggers a call to the - * clipboard_clear function from the last time, we need to arrange a - * way to distinguish a real call to clipboard_clear for the _new_ - * instance of the clipboard data from the leftover call for the - * outgoing one. We do this by setting the user data field in our - * gtk_clipboard_set_with_data() call, instead of the obvious pointer - * to 'inst', to one of these. - */ -struct clipboard_data_instance { - char *pasteout_data_utf8; - int pasteout_data_utf8_len; - struct clipboard_state *state; - struct clipboard_data_instance *next, *prev; -}; -#endif - -struct clipboard_state { - GtkFrontend *inst; - int clipboard; - GdkAtom atom; -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - GtkClipboard *gtkclipboard; - struct clipboard_data_instance *current_cdi; -#else - char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8; - int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len; -#endif -}; - -typedef struct XpmHolder XpmHolder; /* only used for GTK 1 */ - -struct GtkFrontend { - GtkWidget *window, *area, *sbar; - gboolean sbar_visible; - gboolean drawing_area_got_size, drawing_area_realised; - gboolean drawing_area_setup_needed; - bool drawing_area_setup_called; - GtkBox *hbox; - GtkAdjustment *sbar_adjust; - GtkWidget *menu, *specialsmenu, *specialsitem1, *specialsitem2, - *restartitem; - GtkWidget *sessionsmenu; -#ifndef NOT_X_WINDOWS - Display *disp; -#endif -#ifndef NO_BACKING_PIXMAPS - /* - * Server-side pixmap which we use to cache the terminal window's - * contents. When we draw text in the terminal, we draw it to this - * pixmap first, and then blit from there to the actual window; - * this way, X expose events can be handled with an absolute - * minimum of network traffic, by just sending a command to - * re-blit an appropriate rectangle from this pixmap. - */ - GdkPixmap *pixmap; -#endif -#ifdef DRAW_TEXT_CAIRO - /* - * If we're drawing using Cairo, we cache the same image on the - * client side in a Cairo surface. - * - * In GTK2+Cairo, this happens _as well_ as having the server-side - * pixmap cache above; in GTK3+Cairo, server-side pixmaps are - * deprecated, so we _just_ have this client-side cache. In the - * latter case that means we have to transmit a big wodge of - * bitmap data over the X connection on every expose event; but - * GTK3 apparently deliberately provides no way to avoid that - * inefficiency, and at least this way we don't _also_ have to - * redo any font rendering just because the window was temporarily - * covered. - */ - cairo_surface_t *surface; -#endif - int backing_w, backing_h; -#if GTK_CHECK_VERSION(2,0,0) - GtkIMContext *imc; -#endif - unifont *fonts[4]; /* normal, bold, wide, widebold */ - int xpos, ypos, gravity; - bool gotpos; - GdkCursor *rawcursor, *textcursor, *blankcursor, *waitcursor, *currcursor; - GdkColor cols[OSC4_NCOLOURS]; /* indexed by xterm colour indices */ -#if !GTK_CHECK_VERSION(3,0,0) - GdkColormap *colmap; -#endif - bool direct_to_font; - struct clipboard_state clipstates[N_CLIPBOARDS]; -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - /* Remember all clipboard_data_instance structures currently - * associated with this GtkFrontend, in case they're still around - * when it gets destroyed */ - struct clipboard_data_instance cdi_headtail; -#endif - int clipboard_ctrlshiftins, clipboard_ctrlshiftcv; - int font_width, font_height; - int width, height, scale; - bool ignore_sbar; - bool mouseptr_visible; - BusyStatus busy_status; - int alt_keycode; - int alt_digits; - char *wintitle; - char *icontitle; - int master_fd, master_func_id; - Ldisc *ldisc; - Backend *backend; - Terminal *term; - cmdline_get_passwd_input_state cmdline_get_passwd_state; - LogContext *logctx; - bool exited; - struct unicode_data ucsdata; - Conf *conf; - eventlog_stuff *eventlogstuff; - guint32 input_event_time; /* Timestamp of the most recent input event. */ - GtkWidget *dialogs[DIALOG_SLOT_LIMIT]; -#if GTK_CHECK_VERSION(3,4,0) - gdouble cumulative_hscroll, cumulative_vscroll; -#endif - /* Cached things out of conf that we refer to a lot */ - int bold_style; - int window_border; - int cursor_type; - int drawtype; - int meta_mod_mask; -#ifdef OSX_META_KEY_CONFIG - int system_mod_mask; -#endif - bool send_raw_mouse; - bool pointer_indicates_raw_mouse; - unifont_drawctx uctx; -#if GTK_CHECK_VERSION(2,0,0) - GdkPixbuf *trust_sigil_pb; -#else - GdkPixmap *trust_sigil_pm; -#endif - int trust_sigil_w, trust_sigil_h; - - /* - * Not every GDK backend can be relied on 100% to reply to a - * resize request in a timely manner. (In X11 it's all - * asynchronous and goes via the window manager, and if your - * window manager is seriously unwell, you'd rather not have - * terminal windows start becoming unusable as a knock-on effect, - * since those are just the thing you might need to use for - * emergency WM maintenance!) - * - * So when we ask GTK to resize our terminal window, we also set a - * 5-second timer, after which we'll regretfully conclude that a - * resize (or ConfigureNotify telling us no resize took place) is - * probably not going to happen after all. - */ - bool win_resize_pending, term_resize_notification_required; - long win_resize_timeout; - #define WIN_RESIZE_TIMEOUT (TICKSPERSEC*5) - - Seat seat; - TermWin termwin; - LogPolicy logpolicy; -}; - -static void cache_conf_values(GtkFrontend *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); -#ifdef OSX_META_KEY_CONFIG - inst->meta_mod_mask = 0; - if (conf_get_bool(inst->conf, CONF_osx_option_meta)) - inst->meta_mod_mask |= GDK_MOD1_MASK; - if (conf_get_bool(inst->conf, CONF_osx_command_meta)) - inst->meta_mod_mask |= GDK_MOD2_MASK; - inst->system_mod_mask = GDK_MOD2_MASK & ~inst->meta_mod_mask; -#else - inst->meta_mod_mask = GDK_MOD1_MASK; -#endif -} - -static void start_backend(GtkFrontend *inst); -static void exit_callback(void *vinst); -static void destroy_inst_connection(GtkFrontend *inst); -static void delete_inst(GtkFrontend *inst); - -static void post_fatal_message_box_toplevel(void *vctx) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - gtk_widget_destroy(inst->window); -} - -static void post_fatal_message_box(void *vctx, int result) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL); - queue_toplevel_callback(post_fatal_message_box_toplevel, inst); -} - -static void common_connfatal_message_box( - GtkFrontend *inst, const char *msg, post_dialog_fn_t postfn) -{ - char *title = dupcat(appname, " Fatal Error"); - GtkWidget *dialog = create_message_box( - inst->window, title, msg, - string_width("REASONABLY LONG LINE OF TEXT FOR BASIC SANITY"), - false, &buttons_ok, postfn, inst); - register_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL, dialog); - sfree(title); -} - -void fatal_message_box(GtkFrontend *inst, const char *msg) -{ - common_connfatal_message_box(inst, msg, post_fatal_message_box); -} - -static void connection_fatal_callback(void *vctx) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - destroy_inst_connection(inst); -} - -static void post_nonfatal_message_box(void *vctx, int result) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - unregister_dialog(&inst->seat, DIALOG_SLOT_CONNECTION_FATAL); -} - -static void gtk_seat_connection_fatal(Seat *seat, const char *msg) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - if (conf_get_int(inst->conf, CONF_close_on_exit) == FORCE_ON) { - fatal_message_box(inst, msg); - } else { - common_connfatal_message_box(inst, msg, post_nonfatal_message_box); - } - - inst->exited = true; /* suppress normal exit handling */ - queue_toplevel_callback(connection_fatal_callback, inst); -} - -static void gtk_seat_nonfatal(Seat *seat, const char *msg) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - nonfatal_message_box(inst->window, msg); -} - -/* - * Default settings that are specific to pterm. - */ -FontSpec *platform_default_fontspec(const char *name) -{ - if (!strcmp(name, "Font")) - return fontspec_new(DEFAULT_GTK_FONT); - else - return fontspec_new_default(); -} - -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; -} - -bool platform_default_b(const char *name, bool def) -{ - if (!strcmp(name, "WinNameAlways")) { - /* X natively supports icon titles, so use 'em by default */ - return false; - } - return def; -} - -int platform_default_i(const char *name, int def) -{ - if (!strcmp(name, "CloseOnExit")) - return 2; /* maps to FORCE_ON after painful rearrangement :-( */ - return def; -} - -static char *gtk_seat_get_ttymode(Seat *seat, const char *mode) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return term_get_ttymode(inst->term, mode); -} - -static size_t gtk_seat_output(Seat *seat, SeatOutputType type, - const void *data, size_t len) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return term_data(inst->term, data, len); -} - -static void gtkwin_unthrottle(TermWin *win, size_t bufsize) -{ - GtkFrontend *inst = container_of(win, GtkFrontend, termwin); - if (inst->backend) - backend_unthrottle(inst->backend, bufsize); -} - -static bool gtk_seat_eof(Seat *seat) -{ - /* GtkFrontend *inst = container_of(seat, GtkFrontend, seat); */ - return true; /* do respond to incoming EOF with outgoing */ -} - -static SeatPromptResult gtk_seat_get_userpass_input(Seat *seat, prompts_t *p) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - SeatPromptResult spr; - spr = cmdline_get_passwd_input(p, &inst->cmdline_get_passwd_state, true); - if (spr.kind == SPRK_INCOMPLETE) - spr = term_get_userpass_input(inst->term, p); - return spr; -} - -static bool gtk_seat_is_utf8(Seat *seat) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return inst->ucsdata.line_codepage == CS_UTF8; -} - -static void get_window_pixel_size(GtkFrontend *inst, int *w, int *h) -{ - /* - * 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), w, h); -#else - gdk_window_get_size(gtk_widget_get_window(inst->window), w, h); -#endif -} - -static bool gtk_seat_get_window_pixel_size(Seat *seat, int *w, int *h) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - get_window_pixel_size(inst, w, h); - return true; -} - -StripCtrlChars *gtk_seat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return stripctrl_new_term(bs_out, false, 0, inst->term); -} - -static void gtk_seat_notify_remote_exit(Seat *seat); -static void gtk_seat_update_specials_menu(Seat *seat); -static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status); -static const char *gtk_seat_get_x_display(Seat *seat); -#ifndef NOT_X_WINDOWS -static bool gtk_seat_get_windowid(Seat *seat, long *id); -#endif -static void gtk_seat_set_trust_status(Seat *seat, bool trusted); -static bool gtk_seat_can_set_trust_status(Seat *seat); -static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y); - -static const SeatVtable gtk_seat_vt = { - .output = gtk_seat_output, - .eof = gtk_seat_eof, - .sent = nullseat_sent, - .banner = nullseat_banner_to_stderr, - .get_userpass_input = gtk_seat_get_userpass_input, - .notify_session_started = nullseat_notify_session_started, - .notify_remote_exit = gtk_seat_notify_remote_exit, - .notify_remote_disconnect = nullseat_notify_remote_disconnect, - .connection_fatal = gtk_seat_connection_fatal, - .nonfatal = gtk_seat_nonfatal, - .update_specials_menu = gtk_seat_update_specials_menu, - .get_ttymode = gtk_seat_get_ttymode, - .set_busy_status = gtk_seat_set_busy_status, - .confirm_ssh_host_key = gtk_seat_confirm_ssh_host_key, - .confirm_weak_crypto_primitive = gtk_seat_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = gtk_seat_confirm_weak_cached_hostkey, - .prompt_descriptions = gtk_seat_prompt_descriptions, - .is_utf8 = gtk_seat_is_utf8, - .echoedit_update = nullseat_echoedit_update, - .get_x_display = gtk_seat_get_x_display, -#ifdef NOT_X_WINDOWS - .get_windowid = nullseat_get_windowid, -#else - .get_windowid = gtk_seat_get_windowid, -#endif - .get_window_pixel_size = gtk_seat_get_window_pixel_size, - .stripctrl_new = gtk_seat_stripctrl_new, - .set_trust_status = gtk_seat_set_trust_status, - .can_set_trust_status = gtk_seat_can_set_trust_status, - .has_mixed_input_stream = nullseat_has_mixed_input_stream_yes, - .verbose = nullseat_verbose_yes, - .interactive = nullseat_interactive_yes, - .get_cursor_position = gtk_seat_get_cursor_position, -}; - -static void gtk_eventlog(LogPolicy *lp, const char *string) -{ - GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); - logevent_dlg(inst->eventlogstuff, string); -} - -static int gtk_askappend(LogPolicy *lp, Filename *filename, - void (*callback)(void *ctx, int result), void *ctx) -{ - GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); - return gtkdlg_askappend(&inst->seat, filename, callback, ctx); -} - -static void gtk_logging_error(LogPolicy *lp, const char *event) -{ - GtkFrontend *inst = container_of(lp, GtkFrontend, logpolicy); - - /* Send 'can't open log file' errors to the terminal window. - * (Marked as stderr, although terminal.c won't care.) */ - seat_stderr_pl(&inst->seat, ptrlen_from_asciz(event)); - seat_stderr_pl(&inst->seat, PTRLEN_LITERAL("\r\n")); -} - -static const LogPolicyVtable gtk_logpolicy_vt = { - .eventlog = gtk_eventlog, - .askappend = gtk_askappend, - .logging_error = gtk_logging_error, - .verbose = null_lp_verbose_yes, -}; - -/* - * 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) -{ - if (button == MBT_LEFT) - return MBT_SELECT; - if (button == MBT_MIDDLE) - return MBT_PASTE; - if (button == MBT_RIGHT) - return MBT_EXTEND; - if (button == MBT_NOTHING) - return MBT_NOTHING; - return 0; /* shouldn't happen */ -} - -/* - * Return the top-level GtkWindow associated with a particular - * front end instance. - */ -GtkWidget *gtk_seat_get_window(Seat *seat) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - return inst->window; -} - -/* - * Set and clear a pointer to a dialog box created as a result of the - * network code wanting to ask an asynchronous user question (e.g. - * 'what about this dodgy host key, then?'). - */ -void register_dialog(Seat *seat, enum DialogSlot slot, GtkWidget *dialog) -{ - GtkFrontend *inst; - assert(seat->vt == >k_seat_vt); - inst = container_of(seat, GtkFrontend, seat); - assert(slot < DIALOG_SLOT_LIMIT); - assert(!inst->dialogs[slot]); - inst->dialogs[slot] = dialog; -} -void unregister_dialog(Seat *seat, enum DialogSlot slot) -{ - GtkFrontend *inst; - assert(seat->vt == >k_seat_vt); - inst = container_of(seat, GtkFrontend, seat); - assert(slot < DIALOG_SLOT_LIMIT); - assert(inst->dialogs[slot]); - inst->dialogs[slot] = NULL; -} - -/* - * Minimise or restore the window in response to a server-side - * request. - */ -static void gtkwin_set_minimised(TermWin *tw, bool minimised) -{ - /* - * GTK 1.2 doesn't know how to do this. - */ -#if GTK_CHECK_VERSION(2,0,0) - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - if (minimised) - 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. - */ -static void gtkwin_move(TermWin *tw, int x, int y) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - /* - * 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) - /* in case we reset this at startup due to a geometry string */ - gtk_window_set_gravity(GTK_WINDOW(inst->window), GDK_GRAVITY_NORTH_EAST); - gtk_window_move(GTK_WINDOW(inst->window), x, y); -#else - gdk_window_move(gtk_widget_get_window(inst->window), x, y); -#endif -} - -/* - * Move the window to the top or bottom of the z-order in response - * to a server-side request. - */ -static void gtkwin_set_zorder(TermWin *tw, bool top) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - if (top) - gdk_window_raise(gtk_widget_get_window(inst->window)); - else - gdk_window_lower(gtk_widget_get_window(inst->window)); -} - -/* - * Refresh the window in response to a server-side request. - */ -static void gtkwin_refresh(TermWin *tw) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - term_invalidate(inst->term); -} - -/* - * Maximise or restore the window in response to a server-side - * request. - */ -static void gtkwin_set_maximised(TermWin *tw, bool maximised) -{ - /* - * GTK 1.2 doesn't know how to do this. - */ -#if GTK_CHECK_VERSION(2,0,0) - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - if (maximised) - gtk_window_maximize(GTK_WINDOW(inst->window)); - else - gtk_window_unmaximize(GTK_WINDOW(inst->window)); -#endif -} - -/* - * Find out whether a dialog box already exists for this window in a - * particular DialogSlot. If it does, uniconify it (if we can) and - * raise it, so that the user realises they've already been asked this - * question. - */ -static bool find_and_raise_dialog(GtkFrontend *inst, enum DialogSlot slot) -{ - GtkWidget *dialog = inst->dialogs[slot]; - if (!dialog) - return false; - -#if GTK_CHECK_VERSION(2,0,0) - gtk_window_deiconify(GTK_WINDOW(dialog)); -#endif - gdk_window_raise(gtk_widget_get_window(dialog)); - return true; -} - -static void warn_on_close_callback(void *vctx, int result) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - unregister_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE); - if (result) - gtk_widget_destroy(inst->window); -} - -/* - * Handle the 'delete window' event (e.g. user clicking the WM close - * button). The return value false means the window should close, and - * true means it shouldn't. - * - * (That's counterintuitive, but really, in GTK terms, true means 'I - * have done everything necessary to handle this event, so the default - * handler need not do anything', i.e. 'suppress default handler', - * i.e. 'do not close the window'.) - */ -gint delete_window(GtkWidget *widget, GdkEvent *event, GtkFrontend *inst) -{ - if (!inst->exited && conf_get_bool(inst->conf, CONF_warn_on_close)) { - /* - * We're not going to exit right now. We must put up a - * warn-on-close dialog, unless one already exists, in which - * case we'll just re-emphasise that one. - */ - if (!find_and_raise_dialog(inst, DIALOG_SLOT_WARN_ON_CLOSE)) { - char *title = dupcat(appname, " Exit Confirmation"); - char *msg, *additional = NULL; - if (inst->backend && inst->backend->vt->close_warn_text) { - additional = inst->backend->vt->close_warn_text(inst->backend); - } - msg = dupprintf("Are you sure you want to close this session?%s%s", - additional ? "\n" : "", - additional ? additional : ""); - GtkWidget *dialog = create_message_box( - inst->window, title, msg, - string_width("Most of the width of the above text"), - false, &buttons_yn, warn_on_close_callback, inst); - register_dialog(&inst->seat, DIALOG_SLOT_WARN_ON_CLOSE, dialog); - sfree(title); - sfree(msg); - sfree(additional); - } - return true; - } - return false; -} - -#if GTK_CHECK_VERSION(2,0,0) -static gboolean window_state_event( - GtkWidget *widget, GdkEventWindowState *event, gpointer user_data) -{ - GtkFrontend *inst = (GtkFrontend *)user_data; - term_notify_minimised( - inst->term, event->new_window_state & GDK_WINDOW_STATE_ICONIFIED); - return false; -} -#endif - -static void update_mouseptr(GtkFrontend *inst) -{ - switch (inst->busy_status) { - case BUSY_NOT: - if (!inst->mouseptr_visible) { - gdk_window_set_cursor(gtk_widget_get_window(inst->area), - inst->blankcursor); - } else if (inst->pointer_indicates_raw_mouse) { - gdk_window_set_cursor(gtk_widget_get_window(inst->area), - inst->rawcursor); - } else { - gdk_window_set_cursor(gtk_widget_get_window(inst->area), - inst->textcursor); - } - break; - case BUSY_WAITING: /* XXX can we do better? */ - case BUSY_CPU: - /* We always display these cursors. */ - gdk_window_set_cursor(gtk_widget_get_window(inst->area), - inst->waitcursor); - break; - default: - unreachable("Bad busy_status"); - } -} - -static void show_mouseptr(GtkFrontend *inst, bool show) -{ - if (!conf_get_bool(inst->conf, CONF_hide_mouseptr)) - show = true; - inst->mouseptr_visible = show; - update_mouseptr(inst); -} - -static void draw_backing_rect(GtkFrontend *inst); - -static void drawing_area_setup(GtkFrontend *inst, int width, int height) -{ - int w, h, new_scale; - - /* - * See if the terminal size has changed. - */ - w = (width - 2*inst->window_border) / inst->font_width; - h = (height - 2*inst->window_border) / inst->font_height; - if (w != inst->width || h != inst->height) { - /* - * Update conf. - */ - inst->width = w; - inst->height = h; - conf_set_int(inst->conf, CONF_width, inst->width); - conf_set_int(inst->conf, CONF_height, inst->height); - /* - * We must refresh the window's backing image. - */ - inst->drawing_area_setup_needed = true; - } - -#if GTK_CHECK_VERSION(3,10,0) - new_scale = gtk_widget_get_scale_factor(inst->area); - if (new_scale != inst->scale) - inst->drawing_area_setup_needed = true; -#else - new_scale = 1; -#endif - - int new_backing_w = width * new_scale; - int new_backing_h = height * new_scale; - - if (inst->backing_w != new_backing_w || inst->backing_h != new_backing_h) - inst->drawing_area_setup_needed = true; - - /* - * GTK will sometimes send us configure events when nothing about - * the window size has actually changed. In some situations this - * can happen quite often, so it's a worthwhile optimisation to - * detect that situation and avoid the expensive reinitialisation - * of the backing surface / image, and so on. - * - * However, we must still communicate to the terminal that we - * received a resize event, because sometimes a trivial resize - * event (to the same size we already were) is a signal from the - * window system that a _nontrivial_ resize we recently asked for - * has failed to happen. - */ - - inst->drawing_area_setup_called = true; - if (inst->term) - term_size(inst->term, h, w, conf_get_int(inst->conf, CONF_savelines)); - if (inst->win_resize_pending) { - if (inst->term_resize_notification_required) - term_resize_request_completed(inst->term); - inst->win_resize_pending = false; - } - - if (!inst->drawing_area_setup_needed) - return; - - inst->drawing_area_setup_needed = false; - inst->scale = new_scale; - inst->backing_w = new_backing_w; - inst->backing_h = new_backing_h; - -#ifndef NO_BACKING_PIXMAPS - if (inst->pixmap) { - gdk_pixmap_unref(inst->pixmap); - inst->pixmap = NULL; - } - - inst->pixmap = gdk_pixmap_new(gtk_widget_get_window(inst->area), - inst->backing_w, inst->backing_h, -1); -#endif - -#ifdef DRAW_TEXT_CAIRO - if (inst->surface) { - cairo_surface_destroy(inst->surface); - inst->surface = NULL; - } - - inst->surface = cairo_image_surface_create( - CAIRO_FORMAT_ARGB32, inst->backing_w, inst->backing_h); -#endif - - draw_backing_rect(inst); - - if (inst->term) - term_invalidate(inst->term); - -#if GTK_CHECK_VERSION(2,0,0) - gtk_im_context_set_client_window( - inst->imc, gtk_widget_get_window(inst->area)); -#endif -} - -static void drawing_area_setup_simple(GtkFrontend *inst) -{ - /* - * Wrapper on drawing_area_setup which fetches the width and - * height of the drawing area. We go directly to the inner version - * in the case where a new size allocation comes in (just in case - * GTK hasn't installed it in the normal place yet). - */ -#if GTK_CHECK_VERSION(2,0,0) - GdkRectangle alloc; - gtk_widget_get_allocation(inst->area, &alloc); -#else - GtkAllocation alloc = inst->area->allocation; -#endif - drawing_area_setup(inst, alloc.width, alloc.height); -} - -static void drawing_area_setup_cb(void *vctx) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - - if (!inst->drawing_area_setup_called) - drawing_area_setup_simple(inst); -} - -static void area_realised(GtkWidget *widget, GtkFrontend *inst) -{ - inst->drawing_area_realised = true; - if (inst->drawing_area_realised && inst->drawing_area_got_size && - inst->drawing_area_setup_needed) - drawing_area_setup_simple(inst); -} - -static void area_size_allocate( - GtkWidget *widget, GdkRectangle *alloc, GtkFrontend *inst) -{ - inst->drawing_area_got_size = true; - if (inst->drawing_area_realised && inst->drawing_area_got_size) - drawing_area_setup(inst, alloc->width, alloc->height); -} - -#if GTK_CHECK_VERSION(3,10,0) -static void area_check_scale(GtkFrontend *inst) -{ - if (!inst->drawing_area_setup_needed && - inst->scale != gtk_widget_get_scale_factor(inst->area)) { - drawing_area_setup_simple(inst); - if (inst->term) { - term_invalidate(inst->term); - term_update(inst->term); - } - } -} -#endif - -static gboolean window_configured( - GtkWidget *widget, GdkEventConfigure *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - if (inst->term) { - term_notify_window_pos(inst->term, event->x, event->y); - term_notify_window_size_pixels( - inst->term, event->width, event->height); - if (inst->drawing_area_realised && inst->drawing_area_got_size) { - inst->drawing_area_setup_called = false; - queue_toplevel_callback(drawing_area_setup_cb, inst); - } - } - return false; -} - -#if GTK_CHECK_VERSION(3,10,0) -static gboolean area_configured( - GtkWidget *widget, GdkEventConfigure *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - area_check_scale(inst); - return false; -} -#endif - -#ifdef DRAW_TEXT_CAIRO -static void cairo_setup_draw_ctx(GtkFrontend *inst) -{ - cairo_get_matrix(inst->uctx.u.cairo.cr, - &inst->uctx.u.cairo.origmatrix); - cairo_set_line_width(inst->uctx.u.cairo.cr, 1.0); - cairo_set_line_cap(inst->uctx.u.cairo.cr, CAIRO_LINE_CAP_SQUARE); - cairo_set_line_join(inst->uctx.u.cairo.cr, CAIRO_LINE_JOIN_MITER); - /* This antialiasing setting appears to be ignored for Pango - * font rendering but honoured for stroking and filling paths; - * I don't quite understand the logic of that, but I won't - * complain since it's exactly what I happen to want */ - cairo_set_antialias(inst->uctx.u.cairo.cr, CAIRO_ANTIALIAS_NONE); -} -#endif - -#if GTK_CHECK_VERSION(3,0,0) -static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - -#if GTK_CHECK_VERSION(3,10,0) - /* - * This may be the first we hear of the window scale having - * changed, in which case we must hastily reconstruct our backing - * surface before we copy the wrong one into the newly resized - * real window. - */ - area_check_scale(inst); -#endif - - /* - * GTK3 window redraw: we always expect Cairo to be enabled, so - * that inst->surface exists, and pixmaps to be disabled, so that - * inst->pixmap does not exist. Hence, we just blit from - * inst->surface to the window. - */ - if (inst->surface) { - GdkRectangle dirtyrect; - cairo_surface_t *target_surface; - double orig_sx, orig_sy; - cairo_matrix_t m; - - /* - * Furtle around in the Cairo setup to force the device scale - * back to 1, so that when we blit a collection of pixels from - * our backing surface into the window, they really are - * _pixels_ and not some confusing antialiased slightly-offset - * 2x2 rectangle of pixeloids. - * - * I have no idea whether GTK expects me not to mess with the - * device scale in the cairo_surface_t backing its window, so - * I carefully put it back when I've finished. - * - * In some GTK setups, the Cairo context we're given may not - * have a zero translation offset in its matrix, in which case - * we have to adjust that to compensate for the change of - * scale, or else the old translation offset (designed for the - * old scale) will be multiplied by the new scale instead and - * put everything in the wrong place. - */ - target_surface = cairo_get_target(cr); - cairo_get_matrix(cr, &m); - cairo_surface_get_device_scale(target_surface, &orig_sx, &orig_sy); - cairo_surface_set_device_scale(target_surface, 1.0, 1.0); - cairo_translate(cr, m.x0 * (orig_sx - 1.0), m.y0 * (orig_sy - 1.0)); - - gdk_cairo_get_clip_rectangle(cr, &dirtyrect); - - cairo_set_source_surface(cr, inst->surface, 0, 0); - cairo_rectangle(cr, dirtyrect.x, dirtyrect.y, - dirtyrect.width, dirtyrect.height); - cairo_fill(cr); - - cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy); - } - - return true; -} -#else -gint expose_area(GtkWidget *widget, GdkEventExpose *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - -#ifndef NO_BACKING_PIXMAPS - /* - * Draw to the exposed part of the window from the server-side - * backing pixmap. - */ - if (inst->pixmap) { - gdk_draw_pixmap(gtk_widget_get_window(widget), - (gtk_widget_get_style(widget)->fg_gc - [gtk_widget_get_state(widget)]), - inst->pixmap, - event->area.x, event->area.y, - event->area.x, event->area.y, - event->area.width, event->area.height); - } -#else - /* - * Failing that, draw from the client-side Cairo surface. (We - * should never be compiled in a context where we have _neither_ - * inst->surface nor inst->pixmap.) - */ - if (inst->surface) { - cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget)); - cairo_set_source_surface(cr, inst->surface, 0, 0); - cairo_rectangle(cr, event->area.x, event->area.y, - event->area.width, event->area.height); - cairo_fill(cr); - cairo_destroy(cr); - } -#endif - - return true; -} -#endif - -#define KEY_PRESSED(k) \ - (inst->keystate[(k) / 32] & (1 << ((k) % 32))) - -#ifdef KEY_EVENT_DIAGNOSTICS -char *dup_keyval_name(guint keyval) -{ - const char *name = gdk_keyval_name(keyval); - if (name) - return dupstr(name); - else - return dupprintf("UNKNOWN[%u]", (unsigned)keyval); -} -#endif - -static void change_font_size(GtkFrontend *inst, int increment); -static void key_pressed(GtkFrontend *inst); - -/* Subroutine used in key_event */ -static int return_key(GtkFrontend *inst, char *output, bool *special) -{ - int end; - - /* Ugly label so we can come here as a fallback from - * numeric keypad Enter handling */ - if (inst->term->cr_lf_return) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Return in cr_lf_return mode, translating as 0d 0a\n"); -#endif - output[1] = '\015'; - output[2] = '\012'; - end = 3; - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Return special case, translating as 0d + special\n"); -#endif - output[1] = '\015'; - end = 2; - *special = true; - } - - return end; -} - -gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - char output[256]; - wchar_t ucsoutput[2]; - int ucsval, start, end, output_charset; - bool special, use_ucsoutput; - bool force_format_numeric_keypad = false; - bool generated_something = false; - char num_keypad_key = '\0'; - const char *event_string = event->string ? event->string : ""; - - noise_ultralight(NOISE_SOURCE_KEY, event->keyval); - -#ifdef OSX_META_KEY_CONFIG - if (event->state & inst->system_mod_mask) - return false; /* let GTK process OS X Command key */ -#endif - - /* 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; - -#ifdef KEY_EVENT_DIAGNOSTICS - { - char *type_string, *state_string, *keyval_string, *string_string; - - type_string = (event->type == GDK_KEY_PRESS ? dupstr("PRESS") : - event->type == GDK_KEY_RELEASE ? dupstr("RELEASE") : - dupprintf("UNKNOWN[%d]", (int)event->type)); - - { - static const struct { - int mod_bit; - const char *name; - } mod_bits[] = { - {GDK_SHIFT_MASK, "SHIFT"}, - {GDK_LOCK_MASK, "LOCK"}, - {GDK_CONTROL_MASK, "CONTROL"}, - {GDK_MOD1_MASK, "MOD1"}, - {GDK_MOD2_MASK, "MOD2"}, - {GDK_MOD3_MASK, "MOD3"}, - {GDK_MOD4_MASK, "MOD4"}, - {GDK_MOD5_MASK, "MOD5"}, - {GDK_SUPER_MASK, "SUPER"}, - {GDK_HYPER_MASK, "HYPER"}, - {GDK_META_MASK, "META"}, - }; - int i; - int val = event->state; - - state_string = dupstr(""); - - for (i = 0; i < lenof(mod_bits); i++) { - if (val & mod_bits[i].mod_bit) { - char *old = state_string; - state_string = dupcat(state_string, - state_string[0] ? "|" : "", - mod_bits[i].name); - sfree(old); - - val &= ~mod_bits[i].mod_bit; - } - } - - if (val || !state_string[0]) { - char *old = state_string; - state_string = dupprintf("%s%s%d", state_string, - state_string[0] ? "|" : "", val); - sfree(old); - } - } - - keyval_string = dup_keyval_name(event->keyval); - - string_string = dupstr(""); - { - int i; - for (i = 0; event_string[i]; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)event_string[i] & 0xFF); - sfree(old); - } - } - - debug("key_event: type=%s keyval=%s state=%s " - "hardware_keycode=%d is_modifier=%s string=[%s]\n", - type_string, keyval_string, state_string, - (int)event->hardware_keycode, - event->is_modifier ? "true" : "false", - string_string); - - sfree(type_string); - sfree(state_string); - sfree(keyval_string); - sfree(string_string); - } -#endif /* KEY_EVENT_DIAGNOSTICS */ - - /* - * 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_KEY_Meta_L || - event->keyval == GDK_KEY_Meta_R || - event->keyval == GDK_KEY_Alt_L || - event->keyval == GDK_KEY_Alt_R) && - inst->alt_keycode >= 0 && inst->alt_digits > 1) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - modifier release terminates Alt+numberpad input, " - "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) -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key release, passing to IM\n"); -#endif - if (gtk_im_context_filter_keypress(inst->imc, event)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key release accepted by IM\n"); -#endif - return true; - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key release not accepted by IM\n"); -#endif - } -#endif - } - - if (event->type == GDK_KEY_PRESS) { - /* - * 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_KEY_Meta_L || - event->keyval == GDK_KEY_Meta_R || - event->keyval == GDK_KEY_Alt_L || - event->keyval == GDK_KEY_Alt_R)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - modifier press potentially begins Alt+numberpad " - "input\n"); -#endif - inst->alt_keycode = -1; - inst->alt_digits = 0; - goto done; /* this generates nothing else */ - } - - /* - * If we're seeing a numberpad key press with Meta down, - * consider adding it to alt_keycode if that's sensible. - * Anything _else_ with Meta down cancels any possibility - * of an ALT keycode: we set alt_keycode to -2. - */ - if ((event->state & inst->meta_mod_mask) && inst->alt_keycode != -2) { - int digit = -1; - switch (event->keyval) { - case GDK_KEY_KP_0: case GDK_KEY_KP_Insert: digit = 0; break; - case GDK_KEY_KP_1: case GDK_KEY_KP_End: digit = 1; break; - case GDK_KEY_KP_2: case GDK_KEY_KP_Down: digit = 2; break; - case GDK_KEY_KP_3: case GDK_KEY_KP_Page_Down: digit = 3; break; - case GDK_KEY_KP_4: case GDK_KEY_KP_Left: digit = 4; break; - case GDK_KEY_KP_5: case GDK_KEY_KP_Begin: digit = 5; break; - case GDK_KEY_KP_6: case GDK_KEY_KP_Right: digit = 6; break; - case GDK_KEY_KP_7: case GDK_KEY_KP_Home: digit = 7; break; - case GDK_KEY_KP_8: case GDK_KEY_KP_Up: digit = 8; break; - case GDK_KEY_KP_9: case GDK_KEY_KP_Page_Up: digit = 9; break; - } - if (digit < 0) - inst->alt_keycode = -2; /* it's invalid */ - else { -#if defined(DEBUG) && defined(KEY_EVENT_DIAGNOSTICS) - int old_keycode = 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_EVENT_DIAGNOSTICS - debug(" - Alt+numberpad digit %d added to keycode %d" - " gives %d\n", digit, old_keycode, inst->alt_keycode); -#endif - /* Having used this digit, we now do nothing more with it. */ - goto done; - } - } - - if (event->keyval == GDK_KEY_greater && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl->: increase font size\n"); -#endif - if (!inst->win_resize_pending) - change_font_size(inst, +1); - return true; - } - if (event->keyval == GDK_KEY_less && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-<: increase font size\n"); -#endif - if (!inst->win_resize_pending) - change_font_size(inst, -1); - return true; - } - - /* - * Shift-PgUp and Shift-PgDn don't even generate keystrokes - * at all. - */ - if (event->keyval == GDK_KEY_Page_Up && - ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == - (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-PgUp scroll\n"); -#endif - term_scroll(inst->term, 1, 0); - return true; - } - if (event->keyval == GDK_KEY_Page_Up && - (event->state & GDK_SHIFT_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-PgUp scroll\n"); -#endif - term_scroll(inst->term, 0, -inst->height/2); - return true; - } - if (event->keyval == GDK_KEY_Page_Up && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-PgUp scroll\n"); -#endif - term_scroll(inst->term, 0, -1); - return true; - } - if (event->keyval == GDK_KEY_Page_Down && - ((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == - (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-shift-PgDn scroll\n"); -#endif - term_scroll(inst->term, -1, 0); - return true; - } - if (event->keyval == GDK_KEY_Page_Down && - (event->state & GDK_SHIFT_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-PgDn scroll\n"); -#endif - term_scroll(inst->term, 0, +inst->height/2); - return true; - } - if (event->keyval == GDK_KEY_Page_Down && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-PgDn scroll\n"); -#endif - term_scroll(inst->term, 0, +1); - return true; - } - - /* - * Neither do Shift-Ins or Ctrl-Ins (if enabled). - */ - if (event->keyval == GDK_KEY_Insert && - (event->state & GDK_SHIFT_MASK)) { - int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins); - - switch (cfgval) { - case CLIPUI_IMPLICIT: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Insert: paste from PRIMARY\n"); -#endif - term_request_paste(inst->term, CLIP_PRIMARY); - return true; - case CLIPUI_EXPLICIT: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Insert: paste from CLIPBOARD\n"); -#endif - term_request_paste(inst->term, CLIP_CLIPBOARD); - return true; - case CLIPUI_CUSTOM: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Insert: paste from custom clipboard\n"); -#endif - term_request_paste(inst->term, inst->clipboard_ctrlshiftins); - return true; - default: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Insert: no paste action\n"); -#endif - break; - } - } - if (event->keyval == GDK_KEY_Insert && - (event->state & GDK_CONTROL_MASK)) { - static const int clips_clipboard[] = { CLIP_CLIPBOARD }; - int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftins); - - switch (cfgval) { - case CLIPUI_IMPLICIT: - /* do nothing; re-copy to PRIMARY is not needed */ -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Insert: non-copy to PRIMARY\n"); -#endif - return true; - case CLIPUI_EXPLICIT: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Insert: copy to CLIPBOARD\n"); -#endif - term_request_copy(inst->term, - clips_clipboard, lenof(clips_clipboard)); - return true; - case CLIPUI_CUSTOM: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Insert: copy to custom clipboard\n"); -#endif - term_request_copy(inst->term, - &inst->clipboard_ctrlshiftins, 1); - return true; - default: -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Insert: no copy action\n"); -#endif - break; - } - } - - /* - * Another pair of copy-paste keys. - */ - if ((event->state & GDK_SHIFT_MASK) && - (event->state & GDK_CONTROL_MASK) && - (event->keyval == GDK_KEY_C || event->keyval == GDK_KEY_c || - event->keyval == GDK_KEY_V || event->keyval == GDK_KEY_v)) { - int cfgval = conf_get_int(inst->conf, CONF_ctrlshiftcv); - bool paste = (event->keyval == GDK_KEY_V || - event->keyval == GDK_KEY_v); - - switch (cfgval) { - case CLIPUI_IMPLICIT: - if (paste) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-V: paste from PRIMARY\n"); -#endif - term_request_paste(inst->term, CLIP_PRIMARY); - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-C: non-copy to PRIMARY\n"); -#endif - } - return true; - case CLIPUI_EXPLICIT: - if (paste) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-V: paste from CLIPBOARD\n"); -#endif - term_request_paste(inst->term, CLIP_CLIPBOARD); - } else { - static const int clips[] = { CLIP_CLIPBOARD }; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-C: copy to CLIPBOARD\n"); -#endif - term_request_copy(inst->term, clips, lenof(clips)); - } - return true; - case CLIPUI_CUSTOM: - if (paste) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-V: paste from custom clipboard\n"); -#endif - term_request_paste(inst->term, - inst->clipboard_ctrlshiftcv); - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-C: copy to custom clipboard\n"); -#endif - term_request_copy(inst->term, - &inst->clipboard_ctrlshiftcv, 1); - } - return true; - } - } - - special = false; - use_ucsoutput = false; - - /* 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 /* !GTK_CHECK_VERSION(2,0,0) */ - /* - * 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. - */ - bool numeric = false; - bool nethack_mode = conf_get_bool(inst->conf, CONF_nethack_keypad); - bool app_keypad_mode = (inst->term->app_keypad_keys && - !conf_get_bool(inst->conf, CONF_no_applic_k)); - - switch (event->keyval) { - case GDK_KEY_Num_Lock: num_keypad_key = 'G'; break; - case GDK_KEY_KP_Divide: num_keypad_key = '/'; break; - case GDK_KEY_KP_Multiply: num_keypad_key = '*'; break; - case GDK_KEY_KP_Subtract: num_keypad_key = '-'; break; - case GDK_KEY_KP_Add: num_keypad_key = '+'; break; - case GDK_KEY_KP_Enter: num_keypad_key = '\r'; break; - case GDK_KEY_KP_0: num_keypad_key = '0'; numeric = true; break; - case GDK_KEY_KP_Insert: num_keypad_key = '0'; break; - case GDK_KEY_KP_1: num_keypad_key = '1'; numeric = true; break; - case GDK_KEY_KP_End: num_keypad_key = '1'; break; - case GDK_KEY_KP_2: num_keypad_key = '2'; numeric = true; break; - case GDK_KEY_KP_Down: num_keypad_key = '2'; break; - case GDK_KEY_KP_3: num_keypad_key = '3'; numeric = true; break; - case GDK_KEY_KP_Page_Down: num_keypad_key = '3'; break; - case GDK_KEY_KP_4: num_keypad_key = '4'; numeric = true; break; - case GDK_KEY_KP_Left: num_keypad_key = '4'; break; - case GDK_KEY_KP_5: num_keypad_key = '5'; numeric = true; break; - case GDK_KEY_KP_Begin: num_keypad_key = '5'; break; - case GDK_KEY_KP_6: num_keypad_key = '6'; numeric = true; break; - case GDK_KEY_KP_Right: num_keypad_key = '6'; break; - case GDK_KEY_KP_7: num_keypad_key = '7'; numeric = true; break; - case GDK_KEY_KP_Home: num_keypad_key = '7'; break; - case GDK_KEY_KP_8: num_keypad_key = '8'; numeric = true; break; - case GDK_KEY_KP_Up: num_keypad_key = '8'; break; - case GDK_KEY_KP_9: num_keypad_key = '9'; numeric = true; break; - case GDK_KEY_KP_Page_Up: num_keypad_key = '9'; break; - case GDK_KEY_KP_Decimal: num_keypad_key = '.'; numeric = true; break; - case GDK_KEY_KP_Delete: num_keypad_key = '.'; break; - } - if ((app_keypad_mode && num_keypad_key && - (numeric || inst->term->funky_type != FUNKY_XTERM)) || - (nethack_mode && num_keypad_key >= '1' && num_keypad_key <= '9')) { - /* In these modes, we override the keypad handling: - * regardless of Num Lock, the keys are handled by - * format_numeric_keypad_key below. */ - force_format_numeric_keypad = true; - } else { - bool try_filter = true; - -#ifdef META_MANUAL_MASK - if (event->state & META_MANUAL_MASK & inst->meta_mod_mask) { - /* - * If this key event had a Meta modifier bit set which - * is also in META_MANUAL_MASK, that means passing - * such an event to the GtkIMContext will be unhelpful - * (it will eat the keystroke and turn it into - * something not what we wanted). - */ -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Meta modifier requiring manual intervention, " - "suppressing IM filtering\n"); -#endif - try_filter = false; - } -#endif - - if (try_filter) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - general key press, passing to IM\n"); -#endif - if (gtk_im_context_filter_keypress(inst->imc, event)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key press accepted by IM\n"); -#endif - return true; - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - key press not accepted by IM\n"); -#endif - } - } - } - - /* - * 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); - -#ifdef KEY_EVENT_DIAGNOSTICS - { - char *string_string = dupstr(""); - int i; - - for (i = 0; i < wlen; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%04x", string_string, - string_string[0] ? " " : "", - (unsigned)widedata[i]); - sfree(old); - } - debug(" - string translated into Unicode = [%s]\n", - string_string); - sfree(string_string); - } -#endif - - wp = widedata; - ulen = charset_from_unicode(&wp, &wlen, output+1, lenof(output)-2, - CS_UTF8, NULL, NULL, 0); - -#ifdef KEY_EVENT_DIAGNOSTICS - { - char *string_string = dupstr(""); - int i; - - for (i = 0; i < ulen; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)output[i+1] & 0xFF); - sfree(old); - } - debug(" - string translated into UTF-8 = [%s]\n", - string_string); - sfree(string_string); - } -#endif - - output[1+ulen] = '\0'; - } -#endif /* !GTK_CHECK_VERSION(2,0,0) */ - - if (!output[1] && - (ucsval = keysym_to_unicode(event->keyval)) >= 0) { - ucsoutput[0] = '\033'; - ucsoutput[1] = ucsval; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - keysym_to_unicode gave %04x\n", - (unsigned)ucsoutput[1]); -#endif - use_ucsoutput = true; - end = 2; - } else { - output[lenof(output)-1] = '\0'; - end = strlen(output); - } - if (event->state & inst->meta_mod_mask) { - start = 0; - if (end == 1) end = 0; - -#ifdef META_MANUAL_MASK - if (event->state & META_MANUAL_MASK) { - /* - * Key events which have a META_MANUAL_MASK meta bit - * set may have a keyval reflecting that, e.g. on OS X - * the Option key acts as an AltGr-like modifier and - * causes different Unicode characters to be output. - * - * To work around this, we clear the dangerous - * modifier bit and retranslate from the hardware - * keycode as if the key had been pressed without that - * modifier. Then we prefix Esc to *that*. - */ - guint new_keyval; - GdkModifierType consumed; - if (gdk_keymap_translate_keyboard_state( - gdk_keymap_get_for_display(gdk_display_get_default()), - event->hardware_keycode, - event->state & ~META_MANUAL_MASK, - 0, &new_keyval, NULL, NULL, &consumed)) { - ucsoutput[0] = '\033'; - ucsoutput[1] = gdk_keyval_to_unicode(new_keyval); -#ifdef KEY_EVENT_DIAGNOSTICS - { - char *keyval_name = dup_keyval_name(new_keyval); - debug(" - retranslation for manual Meta: " - "new keyval = %s, Unicode = %04x\n", - keyval_name, (unsigned)ucsoutput[1]); - sfree(keyval_name); - } -#endif - use_ucsoutput = true; - end = 2; - } - } -#endif - } 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)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-` special case, translating as 1c\n"); -#endif - output[1] = '\x1C'; - use_ucsoutput = false; - end = 2; - } - - /* Some GTK backends (e.g. Quartz) do not change event->string - * in response to the Control modifier. So we do it ourselves - * here, if it's not already happened. - * - * The translations below are in line with X11 policy as far - * as I know. */ - if ((event->state & GDK_CONTROL_MASK) && end == 2) { - int orig = use_ucsoutput ? ucsoutput[1] : output[1]; - int new = orig; - - if (new >= '3' && new <= '7') { - /* ^3,...,^7 map to 0x1B,...,0x1F */ - new += '\x1B' - '3'; - } else if (new == '2' || new == ' ') { - /* ^2 and ^Space are both ^@, i.e. \0 */ - new = '\0'; - } else if (new == '8') { - /* ^8 is DEL */ - new = '\x7F'; - } else if (new == '/') { - /* ^/ is the same as ^_ */ - new = '\x1F'; - } else if (new >= 0x40 && new < 0x7F) { - /* Everything anywhere near the alphabetics just gets - * masked. */ - new &= 0x1F; - } - /* Anything else, e.g. '0', is unchanged. */ - - if (orig == new) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - manual Ctrl key handling did nothing\n"); -#endif - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - manual Ctrl key handling: %02x -> %02x\n", - (unsigned)orig, (unsigned)new); -#endif - output[1] = new; - use_ucsoutput = false; - } - } - - /* Control-Break sends a Break special to the backend */ - if (event->keyval == GDK_KEY_Break && - (event->state & GDK_CONTROL_MASK)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Break special case, sending SS_BRK\n"); -#endif - if (inst->backend) - backend_special(inst->backend, SS_BRK, 0); - return true; - } - - /* We handle Return ourselves, because it needs to be flagged as - * special to ldisc. */ - if (event->keyval == GDK_KEY_Return) { - end = return_key(inst, output, &special); - use_ucsoutput = false; - } - - /* 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) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-{space,2,@} special case, translating as 00\n"); -#endif - 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)) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Ctrl-Shift-space special case, translating as 00a0\n"); -#endif - 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_KEY_BackSpace && - !(event->state & GDK_SHIFT_MASK)) { - output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ? - '\x7F' : '\x08'; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Backspace, translating as %02x\n", - (unsigned)output[1]); -#endif - use_ucsoutput = false; - end = 2; - special = true; - } - /* For Shift Backspace, do opposite of what is configured. */ - if (event->keyval == GDK_KEY_BackSpace && - (event->state & GDK_SHIFT_MASK)) { - output[1] = conf_get_bool(inst->conf, CONF_bksp_is_delete) ? - '\x08' : '\x7F'; -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Backspace, translating as %02x\n", - (unsigned)output[1]); -#endif - use_ucsoutput = false; - end = 2; - special = true; - } - - /* Shift-Tab is ESC [ Z */ - if (event->keyval == GDK_KEY_ISO_Left_Tab || - (event->keyval == GDK_KEY_Tab && - (event->state & GDK_SHIFT_MASK))) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Shift-Tab, translating as ESC [ Z\n"); -#endif - 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_KEY_Tab && end <= 1) { -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - Tab, translating as 09\n"); -#endif - output[1] = '\t'; - end = 2; - } - - if (num_keypad_key && force_format_numeric_keypad) { - end = 1 + format_numeric_keypad_key( - output+1, inst->term, num_keypad_key, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK); -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - numeric keypad key"); -#endif - use_ucsoutput = false; - goto done; - } - - switch (event->keyval) { - int fkey_number; - bool consumed_meta_key; - - case GDK_KEY_F1: fkey_number = 1; goto numbered_function_key; - case GDK_KEY_F2: fkey_number = 2; goto numbered_function_key; - case GDK_KEY_F3: fkey_number = 3; goto numbered_function_key; - case GDK_KEY_F4: fkey_number = 4; goto numbered_function_key; - case GDK_KEY_F5: fkey_number = 5; goto numbered_function_key; - case GDK_KEY_F6: fkey_number = 6; goto numbered_function_key; - case GDK_KEY_F7: fkey_number = 7; goto numbered_function_key; - case GDK_KEY_F8: fkey_number = 8; goto numbered_function_key; - case GDK_KEY_F9: fkey_number = 9; goto numbered_function_key; - case GDK_KEY_F10: fkey_number = 10; goto numbered_function_key; - case GDK_KEY_F11: fkey_number = 11; goto numbered_function_key; - case GDK_KEY_F12: fkey_number = 12; goto numbered_function_key; - case GDK_KEY_F13: fkey_number = 13; goto numbered_function_key; - case GDK_KEY_F14: fkey_number = 14; goto numbered_function_key; - case GDK_KEY_F15: fkey_number = 15; goto numbered_function_key; - case GDK_KEY_F16: fkey_number = 16; goto numbered_function_key; - case GDK_KEY_F17: fkey_number = 17; goto numbered_function_key; - case GDK_KEY_F18: fkey_number = 18; goto numbered_function_key; - case GDK_KEY_F19: fkey_number = 19; goto numbered_function_key; - case GDK_KEY_F20: fkey_number = 20; goto numbered_function_key; - numbered_function_key: - consumed_meta_key = false; - end = 1 + format_function_key(output+1, inst->term, fkey_number, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK, - event->state & inst->meta_mod_mask, - &consumed_meta_key); - if (consumed_meta_key) - start = 1; /* supersedes the usual prefixing of Esc */ -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - function key F%d", fkey_number); -#endif - use_ucsoutput = false; - goto done; - - SmallKeypadKey sk_key; - case GDK_KEY_Home: case GDK_KEY_KP_Home: - sk_key = SKK_HOME; goto small_keypad_key; - case GDK_KEY_Insert: case GDK_KEY_KP_Insert: - sk_key = SKK_INSERT; goto small_keypad_key; - case GDK_KEY_Delete: case GDK_KEY_KP_Delete: - sk_key = SKK_DELETE; goto small_keypad_key; - case GDK_KEY_End: case GDK_KEY_KP_End: - sk_key = SKK_END; goto small_keypad_key; - case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up: - sk_key = SKK_PGUP; goto small_keypad_key; - case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down: - sk_key = SKK_PGDN; goto small_keypad_key; - small_keypad_key: - /* These keys don't generate terminal input with Ctrl */ - if (event->state & GDK_CONTROL_MASK) - break; - - end = 1 + format_small_keypad_key( - output+1, inst->term, sk_key, event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK, - event->state & inst->meta_mod_mask, &consumed_meta_key); - if (consumed_meta_key) - start = 1; /* supersedes the usual prefixing of Esc */ -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - small keypad key"); -#endif - use_ucsoutput = false; - goto done; - - int xkey; - case GDK_KEY_Up: case GDK_KEY_KP_Up: - xkey = 'A'; goto arrow_key; - case GDK_KEY_Down: case GDK_KEY_KP_Down: - xkey = 'B'; goto arrow_key; - case GDK_KEY_Right: case GDK_KEY_KP_Right: - xkey = 'C'; goto arrow_key; - case GDK_KEY_Left: case GDK_KEY_KP_Left: - xkey = 'D'; goto arrow_key; - case GDK_KEY_Begin: case GDK_KEY_KP_Begin: - xkey = 'G'; goto arrow_key; - arrow_key: - consumed_meta_key = false; - end = 1 + format_arrow_key( - output+1, inst->term, xkey, event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK, - event->state & inst->meta_mod_mask, &consumed_meta_key); - if (consumed_meta_key) - start = 1; /* supersedes the usual prefixing of Esc */ -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - arrow key"); -#endif - use_ucsoutput = false; - goto done; - } - - if (num_keypad_key) { - end = 1 + format_numeric_keypad_key( - output+1, inst->term, num_keypad_key, - event->state & GDK_SHIFT_MASK, - event->state & GDK_CONTROL_MASK); -#ifdef KEY_EVENT_DIAGNOSTICS - debug(" - numeric keypad key"); -#endif - - if (end == 1 && num_keypad_key == '\r') { - /* Keypad Enter, lacking any other translation, - * becomes the same special Return code as normal - * Return. */ - end = return_key(inst, output, &special); - use_ucsoutput = false; - } - - use_ucsoutput = false; - goto done; - } - - goto done; - } - - done: - - if (end-start > 0) { - if (special) { -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = start; i < end; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)output[i] & 0xFF); - sfree(old); - } - debug(" - final output, special, generic encoding = [%s]\n", - string_string); - sfree(string_string); -#endif - /* - * For special control characters, the character set - * should never matter. - */ - output[end] = '\0'; /* NUL-terminate */ - generated_something = true; - term_keyinput(inst->term, -1, output+start, -2); - } else if (!inst->direct_to_font) { - if (!use_ucsoutput) { -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = start; i < end; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)output[i] & 0xFF); - sfree(old); - } - debug(" - final output in %s = [%s]\n", - charset_to_localenc(output_charset), string_string); - sfree(string_string); -#endif - generated_something = true; - term_keyinput(inst->term, output_charset, - output+start, end-start); - } else { -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = start; i < end; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%04x", string_string, - string_string[0] ? " " : "", - (unsigned)ucsoutput[i]); - sfree(old); - } - debug(" - final output in Unicode = [%s]\n", - string_string); - sfree(string_string); -#endif - - /* - * We generated our own Unicode key data from the - * keysym, so use that instead. - */ - generated_something = true; - term_keyinputw(inst->term, ucsoutput+start, end-start); - } - } else { - /* - * In direct-to-font mode, we just send the string - * exactly as we received it. - */ -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = start; i < end; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)output[i] & 0xFF); - sfree(old); - } - debug(" - final output in direct-to-font encoding = [%s]\n", - string_string); - sfree(string_string); -#endif - generated_something = true; - term_keyinput(inst->term, -1, output+start, end-start); - } - - show_mouseptr(inst, false); - } - - if (generated_something) - key_pressed(inst); - return true; -} - -#if GTK_CHECK_VERSION(2,0,0) -void input_method_commit_event(GtkIMContext *imc, gchar *str, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - -#ifdef KEY_EVENT_DIAGNOSTICS - char *string_string = dupstr(""); - int i; - - for (i = 0; str[i]; i++) { - char *old = string_string; - string_string = dupprintf("%s%s%02x", string_string, - string_string[0] ? " " : "", - (unsigned)str[i] & 0xFF); - sfree(old); - } - debug(" - IM commit event in UTF-8 = [%s]\n", string_string); - sfree(string_string); -#endif - - term_keyinput(inst->term, CS_UTF8, str, strlen(str)); - show_mouseptr(inst, false); - key_pressed(inst); -} -#endif - -#define SCROLL_INCREMENT_LINES 5 - -#if GTK_CHECK_VERSION(3,4,0) -gboolean scroll_internal(GtkFrontend *inst, gdouble xdelta, gdouble ydelta, - guint state, gdouble ex, gdouble ey) -{ - int x, y; - bool shift, ctrl, alt, raw_mouse_mode; - - show_mouseptr(inst, true); - - shift = state & GDK_SHIFT_MASK; - ctrl = state & GDK_CONTROL_MASK; - alt = state & inst->meta_mod_mask; - - x = (ex - inst->window_border) / inst->font_width; - y = (ey - inst->window_border) / inst->font_height; - - raw_mouse_mode = (inst->send_raw_mouse && - !(shift && conf_get_bool(inst->conf, - CONF_mouse_override))); - - inst->cumulative_vscroll += ydelta * SCROLL_INCREMENT_LINES; - - if (!raw_mouse_mode) { - int scroll_lines = (int)inst->cumulative_vscroll; /* rounds toward 0 */ - if (scroll_lines) { - term_scroll(inst->term, 0, scroll_lines); - inst->cumulative_vscroll -= scroll_lines; - } - return true; - } else { - int scroll_events = (int)(inst->cumulative_vscroll / - SCROLL_INCREMENT_LINES); - if (scroll_events) { - int button; - - inst->cumulative_vscroll -= scroll_events * SCROLL_INCREMENT_LINES; - - if (scroll_events > 0) { - button = MBT_WHEEL_DOWN; - } else { - button = MBT_WHEEL_UP; - scroll_events = -scroll_events; - } - - while (scroll_events-- > 0) { - term_mouse(inst->term, button, translate_button(button), - MA_CLICK, x, y, shift, ctrl, alt); - } - } - - /* - * Now do the same for horizontal scrolling. But because we - * _only_ use that for passing through to mouse reporting, we - * don't even collect the scroll deltas while not in - * raw_mouse_mode. (Otherwise there would likely be a huge - * unexpected lurch when raw_mouse_mode was enabled!) - */ - inst->cumulative_hscroll += xdelta * SCROLL_INCREMENT_LINES; - scroll_events = (int)(inst->cumulative_hscroll / - SCROLL_INCREMENT_LINES); - if (scroll_events) { - int button; - - inst->cumulative_hscroll -= scroll_events * SCROLL_INCREMENT_LINES; - - if (scroll_events > 0) { - button = MBT_WHEEL_RIGHT; - } else { - button = MBT_WHEEL_LEFT; - scroll_events = -scroll_events; - } - - while (scroll_events-- > 0) { - term_mouse(inst->term, button, translate_button(button), - MA_CLICK, x, y, shift, ctrl, alt); - } - } - - return true; - } -} -#endif - -static gboolean button_internal(GtkFrontend *inst, GdkEventButton *event) -{ - bool shift, ctrl, alt, raw_mouse_mode; - int x, y, button, act; - - /* Remember the timestamp. */ - inst->input_event_time = event->time; - - noise_ultralight(NOISE_SOURCE_MOUSEBUTTON, event->button); - - show_mouseptr(inst, true); - - shift = event->state & GDK_SHIFT_MASK; - ctrl = event->state & GDK_CONTROL_MASK; - alt = event->state & inst->meta_mod_mask; - - raw_mouse_mode = (inst->send_raw_mouse && - !(shift && conf_get_bool(inst->conf, - CONF_mouse_override))); - - if (!raw_mouse_mode) { - if (event->button == 4 && event->type == GDK_BUTTON_PRESS) { - term_scroll(inst->term, 0, -SCROLL_INCREMENT_LINES); - return true; - } - if (event->button == 5 && event->type == GDK_BUTTON_PRESS) { - term_scroll(inst->term, 0, +SCROLL_INCREMENT_LINES); - return true; - } - } - - if (event->button == 3 && ctrl) { - /* Just in case this happened in mid-select */ - term_cancel_selection_drag(inst->term); -#if GTK_CHECK_VERSION(3,22,0) - gtk_menu_popup_at_pointer(GTK_MENU(inst->menu), (GdkEvent *)event); -#else - gtk_menu_popup(GTK_MENU(inst->menu), NULL, NULL, NULL, NULL, - event->button, event->time); -#endif - return true; - } - - if (event->button == 1) - button = MBT_LEFT; - else if (event->button == 2) - button = MBT_MIDDLE; - else if (event->button == 3) - button = MBT_RIGHT; - else if (event->button == 4) - button = MBT_WHEEL_UP; - else if (event->button == 5) - button = MBT_WHEEL_DOWN; - else - return false; /* don't even know what button! */ - - switch (event->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 = (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), act, - x, y, shift, ctrl, alt); - - return true; -} - -gboolean button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - return button_internal(inst, event); -} - -#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) -{ - GtkFrontend *inst = (GtkFrontend *)data; - GdkScrollDirection dir; - -#if GTK_CHECK_VERSION(3,4,0) - gdouble dx, dy; - if (gdk_event_get_scroll_deltas((GdkEvent *)event, &dx, &dy)) { - return scroll_internal(inst, dx, dy, event->state, event->x, event->y); - } else if (!gdk_event_get_scroll_direction((GdkEvent *)event, &dir)) { - return false; - } -#else - dir = event->direction; -#endif - - guint button; - GdkEventButton *event_button; - gboolean ret; - - if (dir == GDK_SCROLL_UP) - button = 4; - else if (dir == GDK_SCROLL_DOWN) - button = 5; - else - return false; - - event_button = (GdkEventButton *)gdk_event_new(GDK_BUTTON_PRESS); - event_button->window = g_object_ref(event->window); - event_button->send_event = event->send_event; - event_button->time = event->time; - event_button->x = event->x; - event_button->y = event->y; - event_button->axes = NULL; - event_button->state = event->state; - event_button->button = button; - event_button->device = g_object_ref(event->device); - event_button->x_root = event->x_root; - event_button->y_root = event->y_root; - ret = button_internal(inst, event_button); - gdk_event_free((GdkEvent *)event_button); - return ret; -} -#endif - -gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - bool shift, ctrl, alt; - Mouse_Action action = MA_DRAG; - Mouse_Button button = MBT_NOTHING; - int x, y; - - /* Remember the timestamp. */ - inst->input_event_time = event->time; - - noise_ultralight(NOISE_SOURCE_MOUSEPOS, - ((uint32_t)event->x << 16) | (uint32_t)event->y); - - show_mouseptr(inst, true); - - shift = event->state & GDK_SHIFT_MASK; - ctrl = event->state & GDK_CONTROL_MASK; - alt = event->state & inst->meta_mod_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 - action = MA_MOVE; - - 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), action, - x, y, shift, ctrl, alt); - - return true; -} - -static void key_pressed(GtkFrontend *inst) -{ - /* - * If our child process has exited but not closed, terminate on - * any keypress. - * - * This is a UI feature specific to GTK PuTTY, because GTK PuTTY - * will (at least sometimes) be running under X, and under X the - * window manager is sometimes absent (very occasionally on - * purpose, more usually temporarily because it's crashed). So - * it's useful to have a way to close an application window - * without depending on protocols like WM_DELETE_WINDOW that are - * typically generated by the WM (e.g. in response to a close - * button in the window frame). - */ - if (inst->exited) - gtk_widget_destroy(inst->window); -} - -static void exit_callback(void *vctx) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - int exitcode, close_on_exit; - - if (!inst->exited && - (exitcode = backend_exitcode(inst->backend)) >= 0) { - destroy_inst_connection(inst); - - 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_widget_destroy(inst->window); - } - } -} - -static void gtk_seat_notify_remote_exit(Seat *seat) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - queue_toplevel_callback(exit_callback, inst); -} - -static void destroy_inst_connection(GtkFrontend *inst) -{ - inst->exited = true; - if (inst->ldisc) { - ldisc_free(inst->ldisc); - inst->ldisc = NULL; - } - if (inst->backend) { - backend_free(inst->backend); - inst->backend = NULL; - } - if (inst->term) - term_provide_backend(inst->term, NULL); - if (inst->menu) { - seat_update_specials_menu(&inst->seat); - gtk_widget_set_sensitive(inst->restartitem, true); - } -} - -static void delete_inst(GtkFrontend *inst) -{ - int dialog_slot; - for (dialog_slot = 0; dialog_slot < DIALOG_SLOT_LIMIT; dialog_slot++) { - if (inst->dialogs[dialog_slot]) { - gtk_widget_destroy(inst->dialogs[dialog_slot]); - inst->dialogs[dialog_slot] = NULL; - } - } - if (inst->window) { - gtk_widget_destroy(inst->window); - inst->window = NULL; - } - if (inst->menu) { - gtk_widget_destroy(inst->menu); - inst->menu = NULL; - } - destroy_inst_connection(inst); - if (inst->term) { - term_free(inst->term); - inst->term = NULL; - } - if (inst->conf) { - conf_free(inst->conf); - inst->conf = NULL; - } - if (inst->logctx) { - log_free(inst->logctx); - inst->logctx = NULL; - } -#if GTK_CHECK_VERSION(2,0,0) - if (inst->trust_sigil_pb) { - g_object_unref(G_OBJECT(inst->trust_sigil_pb)); - inst->trust_sigil_pb = NULL; - } -#else - if (inst->trust_sigil_pm) { - gdk_pixmap_unref(inst->trust_sigil_pm); - inst->trust_sigil_pm = NULL; - } -#endif - -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - /* - * Clear up any in-flight clipboard_data_instances. We can't - * actually _free_ them, but we detach them from the inst that's - * about to be destroyed. - */ - while (inst->cdi_headtail.next != &inst->cdi_headtail) { - struct clipboard_data_instance *cdi = inst->cdi_headtail.next; - cdi->state = NULL; - cdi->next->prev = cdi->prev; - cdi->prev->next = cdi->next; - cdi->next = cdi->prev = cdi; - } -#endif - - /* - * Delete any top-level callbacks associated with inst, which - * would otherwise become stale-pointer dereferences waiting to - * happen. We do this last, because some of the above cleanups - * (notably shutting down the backend) might themelves queue such - * callbacks, so we need to make sure they don't do that _after_ - * we're supposed to have cleaned everything up. - */ - delete_callbacks_for_context(inst); - - eventlogstuff_free(inst->eventlogstuff); - - sfree(inst); -} - -void destroy(GtkWidget *widget, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - inst->window = NULL; - delete_inst(inst); - session_window_closed(); -} - -gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - term_set_focus(inst->term, event->in); - term_update(inst->term); - show_mouseptr(inst, true); - return false; -} - -static void gtk_seat_set_busy_status(Seat *seat, BusyStatus status) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - inst->busy_status = status; - update_mouseptr(inst); -} - -static void gtkwin_set_raw_mouse_mode(TermWin *tw, bool activate) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - inst->send_raw_mouse = activate; -} - -static void gtkwin_set_raw_mouse_mode_pointer(TermWin *tw, bool activate) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - inst->pointer_indicates_raw_mouse = activate; - update_mouseptr(inst); -} - -#if GTK_CHECK_VERSION(2,0,0) -static void compute_whole_window_size(GtkFrontend *inst, - int wchars, int hchars, - int *wpix, int *hpix); -#endif - -static void gtkwin_deny_term_resize(void *vctx) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - drawing_area_setup_simple(inst); -} - -static void gtkwin_timer(void *vctx, unsigned long now) -{ - GtkFrontend *inst = (GtkFrontend *)vctx; - - if (inst->win_resize_pending && now == inst->win_resize_timeout) { - if (inst->term_resize_notification_required) - term_resize_request_completed(inst->term); - inst->win_resize_pending = false; - } -} - -static void request_resize_internal(GtkFrontend *inst, bool from_terminal, - int w, int h) -{ -#if GTK_CHECK_VERSION(2,0,0) - /* - * Initial check: don't even try to resize a window if it's in one - * of the states that make that impossible. This includes being - * maximised; being full-screen (if we ever implement that); or - * being in various tiled states. - * - * On X11, the effect of trying to resize the window when it can't - * be resized should be that the window manager sends us a - * synthetic ConfigureNotify event restating our existing size - * (ICCCM section 4.1.5 "Configuring the Window"). That's awkward - * to deal with, but not impossible; so for X11 alone, we might - * not bother with this check. - * - * (In any case, X11 has other reasons why a window resize might - * be rejected, which this enumeration can't be aware of in any - * case. For example, if your window manager is a tiling one, then - * all your windows are _de facto_ tiled, but not _de jure_ in a - * way that GDK will know about. So we have to handle those - * synthetic ConfigureNotifies in any case.) - * - * On Wayland, as of GTK 3.24.20, the effects are much worse: it - * looks to me as if GTK stops ever sending us "draw" events, or - * even a size_allocate, so the display locks up completely until - * you toggle the maximised state of the window by some other - * means. So it's worth checking! - */ - GdkWindow *gdkwin = gtk_widget_get_window(inst->window); - if (gdkwin) { - GdkWindowState state = gdk_window_get_state(gdkwin); - if (state & (GDK_WINDOW_STATE_MAXIMIZED | - GDK_WINDOW_STATE_FULLSCREEN | -#if GTK_CHECK_VERSION(3,10,0) - GDK_WINDOW_STATE_TILED | -#endif -#if GTK_CHECK_VERSION(3,22,23) - GDK_WINDOW_STATE_TOP_TILED | - GDK_WINDOW_STATE_RIGHT_TILED | - GDK_WINDOW_STATE_BOTTOM_TILED | - GDK_WINDOW_STATE_LEFT_TILED | -#endif - 0)) { - queue_toplevel_callback(gtkwin_deny_term_resize, inst); - if (from_terminal) - term_resize_request_completed(inst->term); - return; - } - } -#endif - -#if !GTK_CHECK_VERSION(3,0,0) - - 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_pixel_size(inst, &large_x, &large_y); - large_x += 32; - large_y += 32; - - gtk_widget_set_size_request(inst->area, large_x, large_y); - 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. - */ - gtk_widget_set_size_request(inst->area, area_x, area_y); -#if GTK_CHECK_VERSION(2,0,0) - gtk_window_resize(GTK_WINDOW(inst->window), - area_x + offset_x, area_y + offset_y); -#else - 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(gtk_widget_get_window(inst->window), - area_x + offset_x, area_y + offset_y); -#endif - -#else /* GTK_CHECK_VERSION(3,0,0) */ - - int wp, hp; - compute_whole_window_size(inst, w, h, &wp, &hp); - gtk_window_resize(GTK_WINDOW(inst->window), wp, hp); - -#endif - - inst->win_resize_pending = true; - inst->term_resize_notification_required = from_terminal; - inst->win_resize_timeout = schedule_timer( - WIN_RESIZE_TIMEOUT, gtkwin_timer, inst); -} - -static void gtkwin_request_resize(TermWin *tw, int w, int h) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - request_resize_internal(inst, true, w, h); -} - -#if GTK_CHECK_VERSION(3,0,0) -char *colour_to_css(const GdkColor *col) -{ - GdkRGBA rgba; - rgba.red = col->red / 65535.0; - rgba.green = col->green / 65535.0; - rgba.blue = col->blue / 65535.0; - rgba.alpha = 1.0; - return gdk_rgba_to_string(&rgba); -} -#endif - -void set_gtk_widget_background(GtkWidget *widget, const GdkColor *col) -{ -#if GTK_CHECK_VERSION(3,0,0) - GtkCssProvider *provider = gtk_css_provider_new(); - char *col_css = colour_to_css(col); - char *data = dupprintf( - "#drawing-area, #top-level { background-color: %s; }\n", col_css); - gtk_css_provider_load_from_data(provider, data, -1, NULL); - GtkStyleContext *context = gtk_widget_get_style_context(widget); - gtk_style_context_add_provider(context, GTK_STYLE_PROVIDER(provider), - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - free(data); - free(col_css); -#else - if (gtk_widget_get_window(widget)) { - /* For GTK1, which doesn't have a 'const' on - * gdk_window_set_background's second parameter type. */ - GdkColor col_mutable = *col; - gdk_window_set_background(gtk_widget_get_window(widget), &col_mutable); - } -#endif -} - -void set_window_background(GtkFrontend *inst) -{ - if (inst->area) - set_gtk_widget_background(GTK_WIDGET(inst->area), &inst->cols[258]); - if (inst->window) - set_gtk_widget_background(GTK_WIDGET(inst->window), &inst->cols[258]); -} - -static void gtkwin_palette_set(TermWin *tw, unsigned start, unsigned ncolours, - const rgb *colours) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - - assert(start <= OSC4_NCOLOURS); - assert(ncolours <= OSC4_NCOLOURS - start); - -#if !GTK_CHECK_VERSION(3,0,0) - if (!inst->colmap) { - inst->colmap = gdk_colormap_get_system(); - } else { - gdk_colormap_free_colors(inst->colmap, inst->cols, OSC4_NCOLOURS); - } -#endif - - for (unsigned i = 0; i < ncolours; i++) { - const rgb *in = &colours[i]; - GdkColor *out = &inst->cols[start + i]; - - out->red = in->r * 0x0101; - out->green = in->g * 0x0101; - out->blue = in->b * 0x0101; - } - -#if !GTK_CHECK_VERSION(3,0,0) - { - gboolean success[OSC4_NCOLOURS]; - gdk_colormap_alloc_colors(inst->colmap, inst->cols + start, - ncolours, false, true, success); - for (unsigned i = 0; i < ncolours; i++) { - if (!success[i]) - g_error("%s: couldn't allocate colour %d (#%02x%02x%02x)\n", - appname, start + 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)); - } - } -#endif - - if (start <= OSC4_COLOUR_bg && OSC4_COLOUR_bg < start + ncolours) { - /* Default Background has changed, so ensure that space between text - * area and window border is refreshed. */ - set_window_background(inst); - if (inst->area && gtk_widget_get_window(inst->area)) { - draw_backing_rect(inst); - gtk_widget_queue_draw(inst->area); - } - } -} - -static void gtkwin_palette_get_overrides(TermWin *tw, Terminal *term) -{ - /* GTK has no analogue of Windows's 'standard system colours', so GTK PuTTY - * has no config option to override the normally configured colours from - * it */ -} - -static struct clipboard_state *clipboard_from_atom( - GtkFrontend *inst, GdkAtom atom) -{ - int i; - - for (i = 0; i < N_CLIPBOARDS; i++) { - struct clipboard_state *state = &inst->clipstates[i]; - if (state->inst == inst && state->atom == atom) - return state; - } - - return NULL; -} - -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - -/* ---------------------------------------------------------------------- - * Clipboard handling, using the high-level GtkClipboard interface in - * as hands-off a way as possible. We write and read the clipboard as - * UTF-8 text, and let GTK deal with converting to any other text - * formats it feels like. - */ - -void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom) -{ - struct clipboard_state *state = &inst->clipstates[clipboard]; - - state->inst = inst; - state->clipboard = clipboard; - state->atom = atom; - - if (state->atom != GDK_NONE) { - state->gtkclipboard = gtk_clipboard_get_for_display( - gdk_display_get_default(), state->atom); - g_object_set_data(G_OBJECT(state->gtkclipboard), "user-data", state); - } else { - state->gtkclipboard = NULL; - } -} - -int init_clipboard(GtkFrontend *inst) -{ - set_clipboard_atom(inst, CLIP_PRIMARY, GDK_SELECTION_PRIMARY); - set_clipboard_atom(inst, CLIP_CLIPBOARD, clipboard_atom); - return true; -} - -static void clipboard_provide_data(GtkClipboard *clipboard, - GtkSelectionData *selection_data, - guint info, gpointer data) -{ - struct clipboard_data_instance *cdi = - (struct clipboard_data_instance *)data; - - if (cdi->state && cdi->state->current_cdi == cdi) { - gtk_selection_data_set_text(selection_data, cdi->pasteout_data_utf8, - cdi->pasteout_data_utf8_len); - } -} - -static void clipboard_clear(GtkClipboard *clipboard, gpointer data) -{ - struct clipboard_data_instance *cdi = - (struct clipboard_data_instance *)data; - - if (cdi->state && cdi->state->current_cdi == cdi) { - if (cdi->state->inst && cdi->state->inst->term) { - term_lost_clipboard_ownership(cdi->state->inst->term, - cdi->state->clipboard); - } - cdi->state->current_cdi = NULL; - } - sfree(cdi->pasteout_data_utf8); - cdi->next->prev = cdi->prev; - cdi->prev->next = cdi->next; - sfree(cdi); -} - -static void gtkwin_clip_write( - TermWin *tw, int clipboard, wchar_t *data, int *attr, - truecolour *truecolour, int len, bool must_deselect) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - struct clipboard_state *state = &inst->clipstates[clipboard]; - struct clipboard_data_instance *cdi; - - if (inst->direct_to_font) { - /* In this clipboard mode, we just can't paste if we're in - * direct-to-font mode. Fortunately, that shouldn't be - * important, because we'll only use this clipboard handling - * code on systems where that kind of font doesn't exist - * anyway. */ - return; - } - - if (!state->gtkclipboard) - return; - - cdi = snew(struct clipboard_data_instance); - cdi->state = state; - state->current_cdi = cdi; - cdi->pasteout_data_utf8 = snewn(len*6, char); - cdi->prev = inst->cdi_headtail.prev; - cdi->next = &inst->cdi_headtail; - cdi->next->prev = cdi; - cdi->prev->next = cdi; - { - const wchar_t *tmp = data; - int tmplen = len; - cdi->pasteout_data_utf8_len = - charset_from_unicode(&tmp, &tmplen, cdi->pasteout_data_utf8, - len*6, CS_UTF8, NULL, NULL, 0); - } - - /* - * It would be nice to just call gtk_clipboard_set_text() in place - * of all of the faffing below. Unfortunately, that won't give me - * access to the clipboard-clear event, which we use to visually - * deselect text in the terminal. - */ - { - GtkTargetList *targetlist; - GtkTargetEntry *targettable; - gint n_targets; - - targetlist = gtk_target_list_new(NULL, 0); - gtk_target_list_add_text_targets(targetlist, 0); - targettable = gtk_target_table_new_from_list(targetlist, &n_targets); - gtk_clipboard_set_with_data(state->gtkclipboard, targettable, - n_targets, clipboard_provide_data, - clipboard_clear, cdi); - gtk_target_table_free(targettable, n_targets); - gtk_target_list_unref(targetlist); - } -} - -static void clipboard_text_received(GtkClipboard *clipboard, - const gchar *text, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - wchar_t *paste; - int paste_len; - int length; - - if (!text) - return; - - length = strlen(text); - - paste = snewn(length, wchar_t); - paste_len = mb_to_wc(CS_UTF8, 0, text, length, paste, length); - - term_do_paste(inst->term, paste, paste_len); - - sfree(paste); -} - -static void gtkwin_clip_request_paste(TermWin *tw, int clipboard) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - struct clipboard_state *state = &inst->clipstates[clipboard]; - - if (!state->gtkclipboard) - return; - - gtk_clipboard_request_text(state->gtkclipboard, - clipboard_text_received, inst); -} - -#else /* JUST_USE_GTK_CLIPBOARD_UTF8 */ - -/* ---------------------------------------------------------------------- - * Clipboard handling for X, using the low-level gtk_selection_* - * interface, handling conversions to fiddly things like compound text - * ourselves, and storing in X cut buffers too. - * - * This version of the clipboard code has to be kept around for GTK1, - * which doesn't have the higher-level GtkClipboard interface at all. - * And since it works on GTK2 and GTK3 too and has had a good few - * years of shakedown and bug fixing, we might as well keep using it - * where it's applicable. - * - * It's _possible_ that we might be able to replicate all the - * important wrinkles of this code in GtkClipboard. (In particular, - * cut buffers or local analogue look as if they might be accessible - * via gtk_clipboard_set_can_store(), and delivering text in - * non-Unicode formats only in the direct-to-font case ought to be - * possible if we can figure out the right set of things to put in the - * GtkTargetList.) But that work can wait until there's a need for it! - */ - -#ifndef NOT_X_WINDOWS - -/* Store the data in a cut-buffer. */ -static void store_cutbuffer(GtkFrontend *inst, char *ptr, int len) -{ - if (inst->disp) { - /* ICCCM says we must rotate the buffers before storing to buffer 0. */ - XRotateBuffers(inst->disp, 1); - XStoreBytes(inst->disp, ptr, len); - } -} - -/* Retrieve data from a cut-buffer. - * Returned data needs to be freed with XFree(). - */ -static char *retrieve_cutbuffer(GtkFrontend *inst, int *nbytes) -{ - char *ptr; - if (!inst->disp) { - *nbytes = 0; - return NULL; - } - ptr = XFetchBytes(inst->disp, nbytes); - if (*nbytes <= 0 && ptr != 0) { - XFree(ptr); - ptr = 0; - } - return ptr; -} - -#endif /* NOT_X_WINDOWS */ - -static void gtkwin_clip_write( - TermWin *tw, int clipboard, wchar_t *data, int *attr, - truecolour *truecolour, int len, bool must_deselect) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - struct clipboard_state *state = &inst->clipstates[clipboard]; - - if (state->pasteout_data) - sfree(state->pasteout_data); - if (state->pasteout_data_ctext) - sfree(state->pasteout_data_ctext); - if (state->pasteout_data_utf8) - sfree(state->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; -#ifndef NOT_X_WINDOWS - XTextProperty tp; - char *list[1]; -#endif - - state->pasteout_data_utf8 = snewn(len*6, char); - state->pasteout_data_utf8_len = len*6; - state->pasteout_data_utf8_len = - charset_from_unicode(&tmp, &tmplen, state->pasteout_data_utf8, - state->pasteout_data_utf8_len, - CS_UTF8, NULL, NULL, 0); - if (state->pasteout_data_utf8_len == 0) { - sfree(state->pasteout_data_utf8); - state->pasteout_data_utf8 = NULL; - } else { - state->pasteout_data_utf8 = - sresize(state->pasteout_data_utf8, - state->pasteout_data_utf8_len + 1, char); - state->pasteout_data_utf8[state->pasteout_data_utf8_len] = '\0'; - } - - /* - * Now let Xlib convert our UTF-8 data into compound text. - */ -#ifndef NOT_X_WINDOWS - list[0] = state->pasteout_data_utf8; - if (inst->disp && Xutf8TextListToTextProperty( - inst->disp, list, 1, XCompoundTextStyle, &tp) == 0) { - state->pasteout_data_ctext = snewn(tp.nitems+1, char); - memcpy(state->pasteout_data_ctext, tp.value, tp.nitems); - state->pasteout_data_ctext_len = tp.nitems; - XFree(tp.value); - } else -#endif - { - state->pasteout_data_ctext = NULL; - state->pasteout_data_ctext_len = 0; - } - } else { - state->pasteout_data_utf8 = NULL; - state->pasteout_data_utf8_len = 0; - state->pasteout_data_ctext = NULL; - state->pasteout_data_ctext_len = 0; - } - - state->pasteout_data = snewn(len*6, char); - state->pasteout_data_len = len*6; - state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, - data, len, state->pasteout_data, - state->pasteout_data_len, NULL); - if (state->pasteout_data_len == 0) { - sfree(state->pasteout_data); - state->pasteout_data = NULL; - } else { - state->pasteout_data = - sresize(state->pasteout_data, state->pasteout_data_len, char); - } - -#ifndef NOT_X_WINDOWS - /* The legacy X cut buffers go with PRIMARY, not any other clipboard */ - if (state->atom == GDK_SELECTION_PRIMARY) - store_cutbuffer(inst, state->pasteout_data, state->pasteout_data_len); -#endif - - if (gtk_selection_owner_set(inst->area, state->atom, - inst->input_event_time)) { -#if GTK_CHECK_VERSION(2,0,0) - gtk_selection_clear_targets(inst->area, state->atom); -#endif - gtk_selection_add_target(inst->area, state->atom, - GDK_SELECTION_TYPE_STRING, 1); - if (state->pasteout_data_ctext) - gtk_selection_add_target(inst->area, state->atom, - compound_text_atom, 1); - if (state->pasteout_data_utf8) - gtk_selection_add_target(inst->area, state->atom, - utf8_string_atom, 1); - } - - if (must_deselect) - term_lost_clipboard_ownership(inst->term, clipboard); -} - -static void selection_get(GtkWidget *widget, GtkSelectionData *seldata, - guint info, guint time_stamp, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - GdkAtom target = gtk_selection_data_get_target(seldata); - struct clipboard_state *state = clipboard_from_atom( - inst, gtk_selection_data_get_selection(seldata)); - - if (!state) - return; - - if (target == utf8_string_atom) - gtk_selection_data_set(seldata, target, 8, - (unsigned char *)state->pasteout_data_utf8, - state->pasteout_data_utf8_len); - else if (target == compound_text_atom) - gtk_selection_data_set(seldata, target, 8, - (unsigned char *)state->pasteout_data_ctext, - state->pasteout_data_ctext_len); - else - gtk_selection_data_set(seldata, target, 8, - (unsigned char *)state->pasteout_data, - state->pasteout_data_len); -} - -static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, - gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - struct clipboard_state *state = clipboard_from_atom( - inst, seldata->selection); - - if (!state) - return true; - - term_lost_clipboard_ownership(inst->term, state->clipboard); - if (state->pasteout_data) - sfree(state->pasteout_data); - if (state->pasteout_data_ctext) - sfree(state->pasteout_data_ctext); - if (state->pasteout_data_utf8) - sfree(state->pasteout_data_utf8); - state->pasteout_data = NULL; - state->pasteout_data_len = 0; - state->pasteout_data_ctext = NULL; - state->pasteout_data_ctext_len = 0; - state->pasteout_data_utf8 = NULL; - state->pasteout_data_utf8_len = 0; - return true; -} - -static void gtkwin_clip_request_paste(TermWin *tw, int clipboard) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - struct clipboard_state *state = &inst->clipstates[clipboard]; - - /* - * 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, state->atom, 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, state->atom, - GDK_SELECTION_TYPE_STRING, - inst->input_event_time); - } -} - -static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, - guint time, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - char *text; - int length; -#ifndef NOT_X_WINDOWS - char **list; - bool free_list_required = false; - bool free_required = false; -#endif - int charset; - GdkAtom seldata_target = gtk_selection_data_get_target(seldata); - GdkAtom seldata_type = gtk_selection_data_get_data_type(seldata); - const guchar *seldata_data = gtk_selection_data_get_data(seldata); - gint seldata_length = gtk_selection_data_get_length(seldata); - wchar_t *paste; - int paste_len; - struct clipboard_state *state = clipboard_from_atom( - inst, gtk_selection_data_get_selection(seldata)); - - if (!state) - return; - - 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, state->atom, - 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, state->atom, - 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) { -#ifndef NOT_X_WINDOWS - text = retrieve_cutbuffer(inst, &length); - if (length == 0) - return; - /* Xterm is rumoured to expect Latin-1, though I haven't checked the - * source, so use that as a de-facto standard. */ - charset = CS_ISO8859_1; - free_required = true; -#else - return; -#endif - } else { - /* - * Convert COMPOUND_TEXT into UTF-8. - */ - if (seldata_type == compound_text_atom) { -#ifndef NOT_X_WINDOWS - XTextProperty tp; - int ret, count; - - tp.value = (unsigned char *)seldata_data; - tp.encoding = (Atom) seldata_type; - tp.format = gtk_selection_data_get_format(seldata); - tp.nitems = seldata_length; - ret = inst->disp == NULL ? -1 : - Xutf8TextPropertyToTextList(inst->disp, &tp, &list, &count); - if (ret == 0 && count == 1) { - text = list[0]; - length = strlen(list[0]); - charset = CS_UTF8; - free_list_required = true; - } else -#endif - { - /* - * Compound text failed; fall back to STRING. - */ - gtk_selection_convert(inst->area, state->atom, - GDK_SELECTION_TYPE_STRING, - inst->input_event_time); - return; - } - } else { - text = (char *)seldata_data; - length = seldata_length; - charset = (seldata_type == utf8_string_atom ? - CS_UTF8 : inst->ucsdata.line_codepage); - } - } - - paste = snewn(length, wchar_t); - paste_len = mb_to_wc(charset, 0, text, length, paste, length); - - term_do_paste(inst->term, paste, paste_len); - - sfree(paste); - -#ifndef NOT_X_WINDOWS - if (free_list_required) - XFreeStringList(list); - if (free_required) - XFree(text); -#endif -} - -static void init_one_clipboard(GtkFrontend *inst, int clipboard) -{ - struct clipboard_state *state = &inst->clipstates[clipboard]; - - state->inst = inst; - state->clipboard = clipboard; -} - -void set_clipboard_atom(GtkFrontend *inst, int clipboard, GdkAtom atom) -{ - struct clipboard_state *state = &inst->clipstates[clipboard]; - - state->inst = inst; - state->clipboard = clipboard; - - state->atom = atom; -} - -void init_clipboard(GtkFrontend *inst) -{ -#ifndef NOT_X_WINDOWS - /* - * Ensure that all the cut buffers exist - according to the ICCCM, - * we must do this before we start using cut buffers. - */ - if (inst->disp) { - unsigned char empty[] = ""; - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER0, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER1, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER2, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER3, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER4, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER5, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER6, - XA_STRING, 8, PropModeAppend, empty, 0); - x11_ignore_error(inst->disp, BadMatch); - XChangeProperty(inst->disp, GDK_ROOT_WINDOW(), XA_CUT_BUFFER7, - XA_STRING, 8, PropModeAppend, empty, 0); - } -#endif - - inst->clipstates[CLIP_PRIMARY].atom = GDK_SELECTION_PRIMARY; - inst->clipstates[CLIP_CLIPBOARD].atom = clipboard_atom; - init_one_clipboard(inst, CLIP_PRIMARY); - init_one_clipboard(inst, CLIP_CLIPBOARD); - - g_signal_connect(G_OBJECT(inst->area), "selection_received", - G_CALLBACK(selection_received), inst); - g_signal_connect(G_OBJECT(inst->area), "selection_get", - G_CALLBACK(selection_get), inst); - g_signal_connect(G_OBJECT(inst->area), "selection_clear_event", - G_CALLBACK(selection_clear), inst); -} - -/* - * End of selection/clipboard handling. - * ---------------------------------------------------------------------- - */ - -#endif /* JUST_USE_GTK_CLIPBOARD_UTF8 */ - -static void set_window_titles(GtkFrontend *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_bool(inst->conf, CONF_win_name_always)) - gdk_window_set_icon_name(gtk_widget_get_window(inst->window), - inst->icontitle); -} - -static void gtkwin_set_title(TermWin *tw, const char *title, int codepage) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - sfree(inst->wintitle); - if (codepage != CP_UTF8) { - wchar_t *title_w = dup_mb_to_wc(codepage, 0, title); - inst->wintitle = encode_wide_string_as_utf8(title_w); - sfree(title_w); - } else { - inst->wintitle = dupstr(title); - } - set_window_titles(inst); -} - -static void gtkwin_set_icon_title(TermWin *tw, const char *title, int codepage) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - sfree(inst->icontitle); - if (codepage != CP_UTF8) { - wchar_t *title_w = dup_mb_to_wc(codepage, 0, title); - inst->icontitle = encode_wide_string_as_utf8(title_w); - sfree(title_w); - } else { - inst->icontitle = dupstr(title); - } - set_window_titles(inst); -} - -static void gtkwin_set_scrollbar(TermWin *tw, int total, int start, int page) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - if (!conf_get_bool(inst->conf, CONF_scrollbar)) - return; - inst->ignore_sbar = true; - gtk_adjustment_set_lower(inst->sbar_adjust, 0); - gtk_adjustment_set_upper(inst->sbar_adjust, total); - gtk_adjustment_set_value(inst->sbar_adjust, start); - gtk_adjustment_set_page_size(inst->sbar_adjust, page); - gtk_adjustment_set_step_increment(inst->sbar_adjust, 1); - gtk_adjustment_set_page_increment(inst->sbar_adjust, page/2); -#if !GTK_CHECK_VERSION(3,18,0) - gtk_adjustment_changed(inst->sbar_adjust); -#endif - inst->ignore_sbar = false; -} - -void scrollbar_moved(GtkAdjustment *adj, GtkFrontend *inst) -{ - if (!conf_get_bool(inst->conf, CONF_scrollbar)) - return; - if (!inst->ignore_sbar) - term_scroll(inst->term, 1, (int)gtk_adjustment_get_value(adj)); -} - -static void show_scrollbar(GtkFrontend *inst, gboolean visible) -{ - inst->sbar_visible = visible; - if (visible) - gtk_widget_show(inst->sbar); - else - gtk_widget_hide(inst->sbar); -} - -static void gtkwin_set_cursor_pos(TermWin *tw, 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). - */ -static void gtkwin_bell(TermWin *tw, int mode) -{ - /* GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); */ - if (mode == BELL_DEFAULT) - gdk_display_beep(gdk_display_get_default()); -} - -static int gtkwin_char_width(TermWin *tw, int uc) -{ - /* - * In this front end, double-width characters are handled using a - * separate font, so this can safely just return 1 always. - */ - return 1; -} - -static bool gtkwin_setup_draw_ctx(TermWin *tw) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - - if (!gtk_widget_get_window(inst->area)) - return false; - - inst->uctx.type = inst->drawtype; -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - /* If we're doing GDK-based drawing, then we also expect - * inst->pixmap to exist. */ - inst->uctx.u.gdk.target = inst->pixmap; - inst->uctx.u.gdk.gc = gdk_gc_new(gtk_widget_get_window(inst->area)); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area); - /* If we're doing Cairo drawing, we expect inst->surface to - * exist, and we draw to that first, regardless of whether we - * subsequently copy the results to inst->pixmap. */ - inst->uctx.u.cairo.cr = cairo_create(inst->surface); - cairo_scale(inst->uctx.u.cairo.cr, inst->scale, inst->scale); - cairo_setup_draw_ctx(inst); - } -#endif - return true; -} - -static void gtkwin_free_draw_ctx(TermWin *tw) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - gdk_gc_unref(inst->uctx.u.gdk.gc); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_destroy(inst->uctx.u.cairo.cr); - } -#endif -} - - -static void draw_update(GtkFrontend *inst, int x, int y, int w, int h) -{ -#if defined DRAW_TEXT_CAIRO && !defined NO_BACKING_PIXMAPS - if (inst->uctx.type == DRAWTYPE_CAIRO) { - /* - * If inst->surface and inst->pixmap both exist, then we've - * just drawn new content to the former which we must copy to - * the latter. - */ - cairo_t *cr = gdk_cairo_create(inst->pixmap); - cairo_set_source_surface(cr, inst->surface, 0, 0); - cairo_rectangle(cr, x, y, w, h); - cairo_fill(cr); - cairo_destroy(cr); - } -#endif - - /* - * Now we just queue a window redraw, which will cause - * inst->surface or inst->pixmap (whichever is appropriate for our - * compile mode) to be copied to the real window when we receive - * the resulting "expose" or "draw" event. - * - * Amazingly, this one API call is actually valid in all versions - * of GTK :-) - */ - gtk_widget_queue_draw_area(inst->area, x, y, w, h); -} - -#ifdef DRAW_TEXT_CAIRO -static void cairo_set_source_rgb_dim(cairo_t *cr, double r, double g, double b, - bool dim) -{ - if (dim) - cairo_set_source_rgb(cr, r * 2 / 3, g * 2 / 3, b * 2 / 3); - else - cairo_set_source_rgb(cr, r, g, b); -} -#endif - -static void draw_set_colour(GtkFrontend *inst, int col, bool dim) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - if (dim) { -#if GTK_CHECK_VERSION(2,0,0) - GdkColor color; - color.red = inst->cols[col].red * 2 / 3; - color.green = inst->cols[col].green * 2 / 3; - color.blue = inst->cols[col].blue * 2 / 3; - gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color); -#else - /* Poor GTK1 fallback */ - gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]); -#endif - } else { - gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[col]); - } - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr, - inst->cols[col].red / 65535.0, - inst->cols[col].green / 65535.0, - inst->cols[col].blue / 65535.0, dim); - } -#endif -} - -static void draw_set_colour_rgb(GtkFrontend *inst, optionalrgb orgb, bool dim) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { -#if GTK_CHECK_VERSION(2,0,0) - GdkColor color; - color.red = orgb.r * 256; - color.green = orgb.g * 256; - color.blue = orgb.b * 256; - if (dim) { - color.red = color.red * 2 / 3; - color.green = color.green * 2 / 3; - color.blue = color.blue * 2 / 3; - } - gdk_gc_set_rgb_fg_color(inst->uctx.u.gdk.gc, &color); -#else - /* Poor GTK1 fallback */ - gdk_gc_set_foreground(inst->uctx.u.gdk.gc, &inst->cols[256]); -#endif - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_set_source_rgb_dim(inst->uctx.u.cairo.cr, orgb.r / 255.0, - orgb.g / 255.0, orgb.b / 255.0, dim); - } -#endif -} - -static void draw_rectangle(GtkFrontend *inst, bool filled, - int x, int y, int w, int h) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - gdk_draw_rectangle(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, - filled, x, y, w, h); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_new_path(inst->uctx.u.cairo.cr); - if (filled) { - cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h); - cairo_fill(inst->uctx.u.cairo.cr); - } else { - cairo_rectangle(inst->uctx.u.cairo.cr, - x + 0.5, y + 0.5, w, h); - cairo_close_path(inst->uctx.u.cairo.cr); - cairo_stroke(inst->uctx.u.cairo.cr); - } - } -#endif -} - -static void draw_clip(GtkFrontend *inst, int x, int y, int w, int h) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - GdkRectangle r; - - r.x = x; - r.y = y; - r.width = w; - r.height = h; - - gdk_gc_set_clip_rectangle(inst->uctx.u.gdk.gc, &r); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_reset_clip(inst->uctx.u.cairo.cr); - cairo_new_path(inst->uctx.u.cairo.cr); - cairo_rectangle(inst->uctx.u.cairo.cr, x, y, w, h); - cairo_clip(inst->uctx.u.cairo.cr); - } -#endif -} - -static void draw_point(GtkFrontend *inst, int x, int y) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - gdk_draw_point(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, x, y); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_new_path(inst->uctx.u.cairo.cr); - cairo_rectangle(inst->uctx.u.cairo.cr, x, y, 1, 1); - cairo_fill(inst->uctx.u.cairo.cr); - } -#endif -} - -static void draw_line(GtkFrontend *inst, int x0, int y0, int x1, int y1) -{ -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { - gdk_draw_line(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, - x0, y0, x1, y1); - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_new_path(inst->uctx.u.cairo.cr); - cairo_move_to(inst->uctx.u.cairo.cr, x0 + 0.5, y0 + 0.5); - cairo_line_to(inst->uctx.u.cairo.cr, x1 + 0.5, y1 + 0.5); - cairo_stroke(inst->uctx.u.cairo.cr); - } -#endif -} - -static void draw_stretch_before(GtkFrontend *inst, int x, int y, - int w, bool wdouble, - int h, bool hdouble, bool hbothalf) -{ -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_matrix_t matrix; - - matrix.xy = 0; - matrix.yx = 0; - - if (wdouble) { - matrix.xx = 2; - matrix.x0 = -x; - } else { - matrix.xx = 1; - matrix.x0 = 0; - } - - if (hdouble) { - matrix.yy = 2; - if (hbothalf) { - matrix.y0 = -(y+h); - } else { - matrix.y0 = -y; - } - } else { - matrix.yy = 1; - matrix.y0 = 0; - } - cairo_transform(inst->uctx.u.cairo.cr, &matrix); - } -#endif -} - -static void draw_stretch_after(GtkFrontend *inst, int x, int y, - int w, bool wdouble, - int h, bool hdouble, bool hbothalf) -{ -#ifdef DRAW_TEXT_GDK -#ifndef NO_BACKING_PIXMAPS - if (inst->uctx.type == DRAWTYPE_GDK) { - /* - * 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; - if (wdouble) { - for (i = 0; i < w; i++) { - gdk_draw_pixmap(inst->uctx.u.gdk.target, - inst->uctx.u.gdk.gc, - inst->uctx.u.gdk.target, - x + 2*i, y, - x + 2*i+1, y, - w - i, h); - } - w *= 2; - } - - if (hdouble) { - int dt, db; - /* Now stretch vertically, in the same way. */ - if (hbothalf) - dt = 0, db = 1; - else - dt = 1, db = 0; - for (i = 0; i < h; i += 2) { - gdk_draw_pixmap(inst->uctx.u.gdk.target, - inst->uctx.u.gdk.gc, - inst->uctx.u.gdk.target, - x, y + dt*i + db, - x, y + dt*(i+1), - w, h-i-1); - } - } - } -#else -#error No way to implement stretching in GDK without a reliable backing pixmap -#endif -#endif /* DRAW_TEXT_GDK */ -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - cairo_set_matrix(inst->uctx.u.cairo.cr, - &inst->uctx.u.cairo.origmatrix); - } -#endif -} - -static void draw_backing_rect(GtkFrontend *inst) -{ - if (!win_setup_draw_ctx(&inst->termwin)) - return; - - draw_set_colour(inst, 258, false); - draw_rectangle(inst, true, 0, 0, inst->backing_w, inst->backing_h); - draw_update(inst, 0, 0, inst->backing_w, inst->backing_h); - win_free_draw_ctx(&inst->termwin); -} - -/* - * 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'. - */ -static void do_text_internal( - GtkFrontend *inst, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour truecolour) -{ - int ncombining; - int nfg, nbg, t, fontid, rlen, widefactor; - bool bold; - bool monochrome = - gdk_visual_get_depth(gtk_widget_get_visual(inst->area)) == 1; - - if (attr & TATTR_COMBINING) { - ncombining = len; - len = 1; - } else - ncombining = 1; - - if (monochrome) - truecolour.fg = truecolour.bg = optionalrgb_none; - - 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))) { - struct optionalrgb trgb; - - t = nfg; - nfg = nbg; - nbg = t; - - trgb = truecolour.fg; - truecolour.fg = truecolour.bg; - truecolour.bg = trgb; - } - if ((inst->bold_style & BOLD_STYLE_COLOUR) && (attr & ATTR_BOLD)) { - if (nfg < 16) nfg |= 8; - else if (nfg >= 256) nfg |= 1; - } - if ((inst->bold_style & BOLD_STYLE_COLOUR) && (attr & ATTR_BLINK)) { - if (nbg < 16) nbg |= 8; - else if (nbg >= 256) nbg |= 1; - } - if ((attr & TATTR_ACTCURS) && !monochrome) { - truecolour.fg = truecolour.bg = optionalrgb_none; - nfg = 260; - nbg = 261; - attr &= ~ATTR_DIM; /* don't dim the cursor */ - } - - fontid = 0; - - if (attr & ATTR_WIDE) { - widefactor = 2; - fontid |= 2; - } else { - widefactor = 1; - } - - if ((attr & ATTR_BOLD) && (inst->bold_style & BOLD_STYLE_FONT)) { - bold = true; - fontid |= 1; - } else { - bold = false; - } - - 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 */ - if (len == 0) - return; /* rounded down half a double-width char to zero */ - } - rlen = len * 2; - } else - rlen = len; - - draw_clip(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, - inst->font_height); - - if ((lattr & LATTR_MODE) != LATTR_NORM) { - draw_stretch_before(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, true, - inst->font_height, - ((lattr & LATTR_MODE) != LATTR_WIDE), - ((lattr & LATTR_MODE) == LATTR_BOT)); - } - - if (truecolour.bg.enabled) - draw_set_colour_rgb(inst, truecolour.bg, attr & ATTR_DIM); - else - draw_set_colour(inst, nbg, attr & ATTR_DIM); - draw_rectangle(inst, true, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, inst->font_height); - - if (truecolour.fg.enabled) - draw_set_colour_rgb(inst, truecolour.fg, attr & ATTR_DIM); - else - draw_set_colour(inst, nfg, attr & ATTR_DIM); - if (ncombining > 1) { - assert(len == 1); - unifont_draw_combining(&inst->uctx, inst->fonts[fontid], - x*inst->font_width+inst->window_border, - (y*inst->font_height+inst->window_border+ - inst->fonts[0]->ascent), - text, ncombining, widefactor > 1, - bold, inst->font_width); - } else { - unifont_draw_text(&inst->uctx, inst->fonts[fontid], - x*inst->font_width+inst->window_border, - (y*inst->font_height+inst->window_border+ - inst->fonts[0]->ascent), - text, 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; - draw_line(inst, 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 (attr & ATTR_STRIKE) { - int sheight = inst->fonts[fontid]->strikethrough_y; - draw_line(inst, x*inst->font_width+inst->window_border, - y*inst->font_height + sheight + inst->window_border, - (x+len)*widefactor*inst->font_width-1+inst->window_border, - y*inst->font_height + sheight + inst->window_border); - } - - if ((lattr & LATTR_MODE) != LATTR_NORM) { - draw_stretch_after(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - rlen*widefactor*inst->font_width, true, - inst->font_height, - ((lattr & LATTR_MODE) != LATTR_WIDE), - ((lattr & LATTR_MODE) == LATTR_BOT)); - } -} - -static void gtkwin_draw_text( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour truecolour) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - int widefactor; - - do_text_internal(inst, x, y, text, len, attr, lattr, truecolour); - - 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; - } - - draw_update(inst, - x*inst->font_width+inst->window_border, - y*inst->font_height+inst->window_border, - len*widefactor*inst->font_width, inst->font_height); -} - -static void gtkwin_draw_cursor( - TermWin *tw, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr, truecolour truecolour) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - bool active, passive; - int widefactor; - - if (attr & TATTR_PASCURS) { - attr &= ~TATTR_PASCURS; - passive = true; - } else - passive = false; - if ((attr & TATTR_ACTCURS) && inst->cursor_type != CURSOR_BLOCK) { - attr &= ~TATTR_ACTCURS; - active = true; - } else - active = false; - do_text_internal(inst, x, y, text, len, attr, lattr, truecolour); - - 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 == CURSOR_BLOCK) { - /* - * 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) { - draw_set_colour(inst, 261, false); - draw_rectangle(inst, false, - 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 == CURSOR_UNDERLINE) { - 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 /* inst->cursor_type == CURSOR_VERTICAL_LINE */ { - 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; - } - - draw_set_colour(inst, 261, false); - if (passive) { - for (i = 0; i < length; i++) { - if (i % 2 == 0) { - draw_point(inst, startx, starty); - } - startx += dx; - starty += dy; - } - } else if (active) { - draw_line(inst, startx, starty, - startx + (length-1) * dx, starty + (length-1) * dy); - } /* else no cursor (e.g., blinked off) */ - } - - draw_update(inst, - 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 -} - -#if !GTK_CHECK_VERSION(2,0,0) -/* - * For GTK 1, manual code to scale an in-memory XPM, producing a new - * one as output. It will be ugly, but good enough to use as a trust - * sigil. - */ -struct XpmHolder { - char **strings; - size_t nstrings; -}; - -static void xpmholder_free(XpmHolder *xh) -{ - for (size_t i = 0; i < xh->nstrings; i++) - sfree(xh->strings[i]); - sfree(xh->strings); - sfree(xh); -} - -static XpmHolder *xpm_scale(const char *const *xpm, int wo, int ho) -{ - /* Get image dimensions, # colours, and chars-per-pixel */ - int wi = 0, hi = 0, nc = 0, cpp = 0; - int retd = sscanf(xpm[0], "%d %d %d %d", &wi, &hi, &nc, &cpp); - assert(retd == 4); - - /* Make output XpmHolder */ - XpmHolder *xh = snew(XpmHolder); - xh->nstrings = 1 + nc + ho; - xh->strings = snewn(xh->nstrings, char *); - - /* Set up header */ - xh->strings[0] = dupprintf("%d %d %d %d", wo, ho, nc, cpp); - for (int i = 0; i < nc; i++) - xh->strings[1 + i] = dupstr(xpm[1 + i]); - - /* Scale image */ - for (int yo = 0; yo < ho; yo++) { - int yi = yo * hi / ho; - char *ro = snewn(cpp * wo + 1, char); - ro[cpp * wo] = '\0'; - xh->strings[1 + nc + yo] = ro; - const char *ri = xpm[1 + nc + yi]; - - for (int xo = 0; xo < wo; xo++) { - int xi = xo * wi / wo; - memcpy(ro + cpp * xo, ri + cpp * xi, cpp); - } - } - - return xh; -} -#endif /* !GTK_CHECK_VERSION(2,0,0) */ - -static void gtkwin_draw_trust_sigil(TermWin *tw, int cx, int cy) -{ - GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); - - int x = cx * inst->font_width + inst->window_border; - int y = cy * inst->font_height + inst->window_border; - int w = 2*inst->font_width, h = inst->font_height; - - if (inst->trust_sigil_w != w || inst->trust_sigil_h != h || -#if GTK_CHECK_VERSION(2,0,0) - !inst->trust_sigil_pb -#else - !inst->trust_sigil_pm -#endif - ) { - -#if GTK_CHECK_VERSION(2,0,0) - if (inst->trust_sigil_pb) - g_object_unref(G_OBJECT(inst->trust_sigil_pb)); -#else - if (inst->trust_sigil_pm) - gdk_pixmap_unref(inst->trust_sigil_pm); -#endif - - int best_icon_index = 0; - unsigned score = UINT_MAX; - for (int i = 0; i < n_main_icon; i++) { - int iw, ih; - if (sscanf(main_icon[i][0], "%d %d", &iw, &ih) == 2) { - int this_excess = (iw + ih) - (w + h); - unsigned this_score = (abs(this_excess) | - (this_excess > 0 ? 0 : 0x80000000U)); - if (this_score < score) { - best_icon_index = i; - score = this_score; - } - } - } - -#if GTK_CHECK_VERSION(2,0,0) - GdkPixbuf *icon_unscaled = gdk_pixbuf_new_from_xpm_data( - (const gchar **)main_icon[best_icon_index]); - inst->trust_sigil_pb = gdk_pixbuf_scale_simple( - icon_unscaled, w, h, GDK_INTERP_BILINEAR); - g_object_unref(G_OBJECT(icon_unscaled)); -#else - XpmHolder *xh = xpm_scale(main_icon[best_icon_index], w, h); - inst->trust_sigil_pm = gdk_pixmap_create_from_xpm_d( - gtk_widget_get_window(inst->window), NULL, - &inst->cols[258], xh->strings); - xpmholder_free(xh); -#endif - - inst->trust_sigil_w = w; - inst->trust_sigil_h = h; - } - -#ifdef DRAW_TEXT_GDK - if (inst->uctx.type == DRAWTYPE_GDK) { -#if GTK_CHECK_VERSION(2,0,0) - gdk_draw_pixbuf(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, - inst->trust_sigil_pb, 0, 0, x, y, w, h, - GDK_RGB_DITHER_NORMAL, 0, 0); -#else - gdk_draw_pixmap(inst->uctx.u.gdk.target, inst->uctx.u.gdk.gc, - inst->trust_sigil_pm, 0, 0, x, y, w, h); -#endif - } -#endif -#ifdef DRAW_TEXT_CAIRO - if (inst->uctx.type == DRAWTYPE_CAIRO) { - inst->uctx.u.cairo.widget = GTK_WIDGET(inst->area); - cairo_save(inst->uctx.u.cairo.cr); - cairo_translate(inst->uctx.u.cairo.cr, x, y); - gdk_cairo_set_source_pixbuf(inst->uctx.u.cairo.cr, - inst->trust_sigil_pb, 0, 0); - cairo_rectangle(inst->uctx.u.cairo.cr, 0, 0, w, h); - cairo_fill(inst->uctx.u.cairo.cr); - cairo_restore(inst->uctx.u.cairo.cr); - } -#endif - - draw_update(inst, x, y, w, h); -} - -GdkCursor *make_mouse_ptr(GtkFrontend *inst, int cursor_val) -{ - if (cursor_val == -1) { -#if GTK_CHECK_VERSION(2,16,0) - cursor_val = GDK_BLANK_CURSOR; -#else - /* - * Work around absence of GDK_BLANK_CURSOR by inventing a - * blank pixmap. - */ - GdkCursor *ret; - GdkColor bg = { 0, 0, 0, 0 }; - GdkPixmap *pm = gdk_pixmap_new(NULL, 1, 1, 1); - GdkGC *gc = gdk_gc_new(pm); - gdk_gc_set_foreground(gc, &bg); - gdk_draw_rectangle(pm, gc, 1, 0, 0, 1, 1); - gdk_gc_unref(gc); - ret = gdk_cursor_new_from_pixmap(pm, pm, &bg, &bg, 1, 1); - gdk_pixmap_unref(pm); - return ret; -#endif - } - - return gdk_cursor_new(cursor_val); -} - -void modalfatalbox(const 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); -} - -static const char *gtk_seat_get_x_display(Seat *seat) -{ - return gdk_get_display(); -} - -#ifndef NOT_X_WINDOWS -static bool gtk_seat_get_windowid(Seat *seat, long *id) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - GdkWindow *window = gtk_widget_get_window(inst->area); - if (!GDK_IS_X11_WINDOW(window)) - return false; - *id = GDK_WINDOW_XID(window); - return true; -} -#endif - -char *setup_fonts_ucs(GtkFrontend *inst) -{ - bool shadowbold = conf_get_bool(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]; - } - - if (inst->font_width != inst->fonts[0]->width || - inst->font_height != inst->fonts[0]->height) { - - inst->font_width = inst->fonts[0]->width; - inst->font_height = inst->fonts[0]->height; - - /* - * The font size has changed, so force the next call to - * drawing_area_setup to regenerate the backing surface. - */ - inst->drawing_area_setup_needed = true; - } - - inst->direct_to_font = init_ucs(&inst->ucsdata, - conf_get_str(inst->conf, CONF_line_codepage), - conf_get_bool(inst->conf, CONF_utf8_override), - inst->fonts[0]->public_charset, - conf_get_int(inst->conf, CONF_vtmode)); - - inst->drawtype = inst->fonts[0]->preferred_drawtype; - - return NULL; -} - -#if GTK_CHECK_VERSION(3,0,0) -struct find_app_menu_bar_ctx { - GtkWidget *area, *menubar; -}; -static void find_app_menu_bar(GtkWidget *widget, gpointer data) -{ - struct find_app_menu_bar_ctx *ctx = (struct find_app_menu_bar_ctx *)data; - if (widget != ctx->area && GTK_IS_MENU_BAR(widget)) - ctx->menubar = widget; -} -#endif - -static void compute_geom_hints(GtkFrontend *inst, GdkGeometry *geom) -{ - /* - * Unused fields in geom. - */ - geom->max_width = geom->max_height = -1; - geom->min_aspect = geom->max_aspect = 0; - - /* - * Set up the geometry fields we care about, with reference to - * just the drawing area. We'll correct for other widgets in a - * moment. - */ - geom->min_width = inst->font_width + 2*inst->window_border; - geom->min_height = inst->font_height + 2*inst->window_border; - 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; - - /* - * If we've got a scrollbar visible, then we must include its - * width as part of the base and min width, and also ensure that - * our window's minimum height is at least the height required by - * the scrollbar. - * - * In the latter case, we must also take care to arrange that - * (geom->min_height - geom->base_height) is an integer multiple of - * geom->height_inc, because if it's not, then some window managers - * (we know of xfwm4) get confused, with the effect that they - * resize our window to a height based on min_height instead of - * base_height, which we then round down and the window ends up - * too short. - */ - if (inst->sbar_visible) { - GtkRequisition req; - int min_sb_height; - -#if GTK_CHECK_VERSION(3,0,0) - gtk_widget_get_preferred_size(inst->sbar, &req, NULL); -#else - gtk_widget_size_request(inst->sbar, &req); -#endif - - /* Compute rounded-up scrollbar height. */ - min_sb_height = req.height; - min_sb_height += geom->height_inc - 1; - min_sb_height -= ((min_sb_height - geom->base_height%geom->height_inc) - % geom->height_inc); - - geom->min_width += req.width; - geom->base_width += req.width; - if (geom->min_height < min_sb_height) - geom->min_height = min_sb_height; - } - -#if GTK_CHECK_VERSION(3,0,0) - /* - * And if we're running a main-gtk-application.c based program and - * GtkApplicationWindow has given us a menu bar inside the window, - * then we must take that into account as well. - * - * In its unbounded wisdom, GtkApplicationWindow doesn't actually - * give us a direct function call to _find_ the menu bar widget. - * Fortunately, we can find it by enumerating the children of the - * top-level window and looking for one we didn't put there - * ourselves. - */ - { - struct find_app_menu_bar_ctx ctx[1]; - ctx->area = inst->area; - ctx->menubar = NULL; - gtk_container_foreach(GTK_CONTAINER(inst->window), - find_app_menu_bar, ctx); - - if (ctx->menubar) { - GtkRequisition req; - int min_menu_width; - gtk_widget_get_preferred_size(ctx->menubar, NULL, &req); - - /* - * This time, the height adjustment is easy (the menu bar - * sits above everything), but we have to take care with - * the _width_ to ensure we keep min_width and base_width - * congruent modulo width_inc. - */ - geom->min_height += req.height; - geom->base_height += req.height; - - min_menu_width = req.width; - min_menu_width += geom->width_inc - 1; - min_menu_width -= - ((min_menu_width - geom->base_width%geom->width_inc) - % geom->width_inc); - if (geom->min_width < min_menu_width) - geom->min_width = min_menu_width; - } - } -#endif -} - -void set_geom_hints(GtkFrontend *inst) -{ - /* - * 2021-12-20: I've found that on Ubuntu 20.04 Wayland (using GTK - * 3.24.20), setting geometry hints causes the window size to come - * out wrong. As far as I can tell, that's because the GDK Wayland - * backend internally considers windows to be a lot larger than - * their obvious display size (*even* considering visible window - * furniture like title bars), with an extra margin on every side - * to account for surrounding effects like shadows. And the - * geometry hints like base size and resize increment are applied - * to that larger size rather than the more obvious 'client area' - * size. So when we ask for a window of exactly the size we want, - * it gets modified by GDK based on the geometry hints, but - * applying this extra margin, which causes the size to be a - * little bit too small. - * - * I don't know how you can sensibly find out the size of that - * margin. If I did, I could account for it in the geometry hints. - * But I also see that gtk_window_set_geometry_hints is removed in - * GTK 4, which suggests that probably doing a lot of hard work to - * fix this is not the way forward. - * - * So instead, I simply avoid setting geometry hints at all on any - * GDK backend other than X11, and hopefully that's a workaround. - */ -#if GTK_CHECK_VERSION(3,0,0) && !defined NOT_X_WINDOWS - if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) - return; -#endif - - const struct BackendVtable *vt; - GdkGeometry geom; - gint flags = GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE | GDK_HINT_RESIZE_INC; - compute_geom_hints(inst, &geom); -#if GTK_CHECK_VERSION(2,0,0) - if (inst->gotpos) - flags |= GDK_HINT_USER_POS; -#endif - vt = backend_vt_from_proto(conf_get_int(inst->conf, CONF_protocol)); - if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) { - /* Window resizing forbidden. Set both minimum and maximum - * dimensions to be the initial size. */ - geom.min_width = geom.base_width + geom.width_inc * inst->width; - geom.min_height = geom.base_height + geom.height_inc * inst->height; - geom.max_width = geom.min_width; - geom.max_height = geom.min_height; - flags |= GDK_HINT_MAX_SIZE; - } - gtk_window_set_geometry_hints(GTK_WINDOW(inst->window), - NULL, &geom, flags); -} - -#if GTK_CHECK_VERSION(2,0,0) -static void compute_whole_window_size(GtkFrontend *inst, - int wchars, int hchars, - int *wpix, int *hpix) -{ - GdkGeometry geom; - compute_geom_hints(inst, &geom); - if (wpix) *wpix = geom.base_width + wchars * geom.width_inc; - if (hpix) *hpix = geom.base_height + hchars * geom.height_inc; -} -#endif - -void clear_scrollback_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - term_clrsb(inst->term); -} - -void reset_terminal_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - term_pwron(inst->term, true); - if (inst->ldisc) - ldisc_echoedit_update(inst->ldisc); -} - -void copy_clipboard_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - static const int clips[] = { MENU_CLIPBOARD }; - term_request_copy(inst->term, clips, lenof(clips)); -} - -void paste_clipboard_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - term_request_paste(inst->term, MENU_CLIPBOARD); -} - -void copy_all_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - static const int clips[] = { COPYALL_CLIPBOARDS }; - term_copyall(inst->term, clips, lenof(clips)); -} - -void special_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - SessionSpecial *sc = g_object_get_data(G_OBJECT(item), "user-data"); - - if (inst->backend) - backend_special(inst->backend, sc->code, sc->arg); -} - -void about_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - about_box(inst->window); -} - -void event_log_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - showeventlog(inst->eventlogstuff, inst->window); -} - -void setup_clipboards(GtkFrontend *inst, Terminal *term, Conf *conf) -{ - assert(term->mouse_select_clipboards[0] == CLIP_LOCAL); - - term->n_mouse_select_clipboards = 1; - term->mouse_select_clipboards[ - term->n_mouse_select_clipboards++] = MOUSE_SELECT_CLIPBOARD; - - if (conf_get_bool(conf, CONF_mouseautocopy)) { - term->mouse_select_clipboards[ - term->n_mouse_select_clipboards++] = CLIP_CLIPBOARD; - } - - set_clipboard_atom(inst, CLIP_CUSTOM_1, GDK_NONE); - set_clipboard_atom(inst, CLIP_CUSTOM_2, GDK_NONE); - set_clipboard_atom(inst, CLIP_CUSTOM_3, GDK_NONE); - - switch (conf_get_int(conf, CONF_mousepaste)) { - case CLIPUI_IMPLICIT: - term->mouse_paste_clipboard = MOUSE_PASTE_CLIPBOARD; - break; - case CLIPUI_EXPLICIT: - term->mouse_paste_clipboard = CLIP_CLIPBOARD; - break; - case CLIPUI_CUSTOM: - term->mouse_paste_clipboard = CLIP_CUSTOM_1; - set_clipboard_atom(inst, CLIP_CUSTOM_1, - gdk_atom_intern( - conf_get_str(conf, CONF_mousepaste_custom), - false)); - break; - default: - term->mouse_paste_clipboard = CLIP_NULL; - break; - } - - if (conf_get_int(conf, CONF_ctrlshiftins) == CLIPUI_CUSTOM) { - GdkAtom atom = gdk_atom_intern( - conf_get_str(conf, CONF_ctrlshiftins_custom), false); - struct clipboard_state *state = clipboard_from_atom(inst, atom); - if (state) { - inst->clipboard_ctrlshiftins = state->clipboard; - } else { - inst->clipboard_ctrlshiftins = CLIP_CUSTOM_2; - set_clipboard_atom(inst, CLIP_CUSTOM_2, atom); - } - } - - if (conf_get_int(conf, CONF_ctrlshiftcv) == CLIPUI_CUSTOM) { - GdkAtom atom = gdk_atom_intern( - conf_get_str(conf, CONF_ctrlshiftcv_custom), false); - struct clipboard_state *state = clipboard_from_atom(inst, atom); - if (state) { - inst->clipboard_ctrlshiftins = state->clipboard; - } else { - inst->clipboard_ctrlshiftcv = CLIP_CUSTOM_3; - set_clipboard_atom(inst, CLIP_CUSTOM_3, atom); - } - } -} - -struct after_change_settings_dialog_ctx { - GtkFrontend *inst; - Conf *newconf; -}; - -static void after_change_settings_dialog(void *vctx, int retval); - -void change_settings_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - struct after_change_settings_dialog_ctx *ctx; - GtkWidget *dialog; - char *title; - - if (find_and_raise_dialog(inst, DIALOG_SLOT_RECONFIGURE)) - return; - - title = dupcat(appname, " Reconfiguration"); - - ctx = snew(struct after_change_settings_dialog_ctx); - ctx->inst = inst; - ctx->newconf = conf_copy(inst->conf); - - term_pre_reconfig(inst->term, ctx->newconf); - - dialog = create_config_box( - title, ctx->newconf, true, - inst->backend ? backend_cfg_info(inst->backend) : 0, - after_change_settings_dialog, ctx); - register_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE, dialog); - - sfree(title); -} - -static void after_change_settings_dialog(void *vctx, int retval) -{ - struct after_change_settings_dialog_ctx ctx = - *(struct after_change_settings_dialog_ctx *)vctx; - GtkFrontend *inst = ctx.inst; - Conf *oldconf = inst->conf, *newconf = ctx.newconf; - bool need_size; - - sfree(vctx); /* we've copied this already */ - - unregister_dialog(&inst->seat, DIALOG_SLOT_RECONFIGURE); - - if (retval > 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_echoedit_update(inst->ldisc); - } - /* Pass new config data to the terminal */ - term_reconfig(inst->term, inst->conf); - setup_clipboards(inst, inst->term, inst->conf); - /* Pass new config data to the back end */ - if (inst->backend) - backend_reconfig(inst->backend, inst->conf); - - cache_conf_values(inst); - - need_size = false; - - /* - * 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_bool(oldconf, CONF_scrollbar) != - conf_get_bool(newconf, CONF_scrollbar)) { - show_scrollbar(inst, conf_get_bool(newconf, CONF_scrollbar)); - need_size = true; - } - if (conf_get_bool(oldconf, CONF_scrollbar_on_left) != - conf_get_bool(newconf, CONF_scrollbar_on_left)) { - gtk_box_reorder_child(inst->hbox, inst->sbar, - conf_get_bool(newconf, CONF_scrollbar_on_left) - ? 0 : 1); - } - - /* - * Redo the whole tangled fonts and Unicode mess if - * necessary. - */ - 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_bool(oldconf, CONF_utf8_override) != - conf_get_bool(newconf, CONF_utf8_override) || - conf_get_int(oldconf, CONF_vtmode) != - conf_get_int(newconf, CONF_vtmode) || - conf_get_bool(oldconf, CONF_shadowbold) != - conf_get_bool(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); - create_message_box( - inst->window, "Font setup error", msgboxtext, - string_width("Could not change fonts in terminal window:"), - false, &buttons_ok, trivial_post_dialog_fn, 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_internal(inst, false, - 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); - } -} - -static void change_font_size(GtkFrontend *inst, int increment) -{ - static const int conf_keys[lenof(inst->fonts)] = { - CONF_font, CONF_boldfont, CONF_widefont, CONF_wideboldfont, - }; - FontSpec *oldfonts[lenof(inst->fonts)]; - FontSpec *newfonts[lenof(inst->fonts)]; - char *errmsg = NULL; - int i; - - for (i = 0; i < lenof(newfonts); i++) - oldfonts[i] = newfonts[i] = NULL; - - for (i = 0; i < lenof(inst->fonts); i++) { - if (inst->fonts[i]) { - char *newname = unifont_size_increment(inst->fonts[i], increment); - if (!newname) - goto cleanup; - newfonts[i] = fontspec_new(newname); - sfree(newname); - } - } - - for (i = 0; i < lenof(newfonts); i++) { - if (newfonts[i]) { - oldfonts[i] = fontspec_copy( - conf_get_fontspec(inst->conf, conf_keys[i])); - conf_set_fontspec(inst->conf, conf_keys[i], newfonts[i]); - } - } - - errmsg = setup_fonts_ucs(inst); - if (errmsg) - goto cleanup; - - /* Success, so suppress putting everything back */ - for (i = 0; i < lenof(newfonts); i++) { - if (oldfonts[i]) { - fontspec_free(oldfonts[i]); - oldfonts[i] = NULL; - } - } - - set_geom_hints(inst); - request_resize_internal(inst, false, conf_get_int(inst->conf, CONF_width), - conf_get_int(inst->conf, CONF_height)); - term_invalidate(inst->term); - gtk_widget_queue_draw(inst->area); - - cleanup: - for (i = 0; i < lenof(oldfonts); i++) { - if (oldfonts[i]) { - conf_set_fontspec(inst->conf, conf_keys[i], oldfonts[i]); - fontspec_free(oldfonts[i]); - } - if (newfonts[i]) - fontspec_free(newfonts[i]); - } - sfree(errmsg); -} - -void dup_session_menuitem(GtkMenuItem *item, gpointer gdata) -{ - GtkFrontend *inst = (GtkFrontend *)gdata; - - launch_duplicate_session(inst->conf); -} - -void new_session_menuitem(GtkMenuItem *item, gpointer data) -{ - launch_new_session(); -} - -void restart_session_menuitem(GtkMenuItem *item, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)data; - - if (!inst->backend) { - logevent(inst->logctx, "----- Session restarted -----"); - term_pwron(inst->term, false); - start_backend(inst); - inst->exited = false; - } -} - -void saved_session_menuitem(GtkMenuItem *item, gpointer data) -{ - char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data"); - - launch_saved_session(str); -} - -void saved_session_freedata(GtkMenuItem *item, gpointer data) -{ - char *str = (char *)g_object_get_data(G_OBJECT(item), "user-data"); - - sfree(str); -} - -void app_menu_action(GtkFrontend *frontend, enum MenuAction action) -{ - GtkFrontend *inst = (GtkFrontend *)frontend; - switch (action) { - case MA_COPY: - copy_clipboard_menuitem(NULL, inst); - break; - case MA_PASTE: - paste_clipboard_menuitem(NULL, inst); - break; - case MA_COPY_ALL: - copy_all_menuitem(NULL, inst); - break; - case MA_DUPLICATE_SESSION: - dup_session_menuitem(NULL, inst); - break; - case MA_RESTART_SESSION: - restart_session_menuitem(NULL, inst); - break; - case MA_CHANGE_SETTINGS: - change_settings_menuitem(NULL, inst); - break; - case MA_CLEAR_SCROLLBACK: - clear_scrollback_menuitem(NULL, inst); - break; - case MA_RESET_TERMINAL: - reset_terminal_menuitem(NULL, inst); - break; - case MA_EVENT_LOG: - event_log_menuitem(NULL, inst); - break; - } -} - -static void update_savedsess_menu(GtkMenuItem *menuitem, gpointer data) -{ - GtkFrontend *inst = (GtkFrontend *)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); - g_object_set_data(G_OBJECT(menuitem), "user-data", - dupstr(sesslist.sessions[i])); - g_signal_connect(G_OBJECT(menuitem), "activate", - G_CALLBACK(saved_session_menuitem), - inst); - g_signal_connect(G_OBJECT(menuitem), "destroy", - G_CALLBACK(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) -{ -#if GTK_CHECK_VERSION(2,0,0) - GList *iconlist; - int n; -#else - GdkPixmap *iconpm; - GdkBitmap *iconmask; -#endif - - if (!n_icon) - return; - - gtk_widget_realize(window); -#if GTK_CHECK_VERSION(2,0,0) - gtk_window_set_icon(GTK_WINDOW(window), - gdk_pixbuf_new_from_xpm_data((const gchar **)icon[0])); -#else - iconpm = gdk_pixmap_create_from_xpm_d(gtk_widget_get_window(window), - &iconmask, NULL, (gchar **)icon[0]); - gdk_window_set_icon(gtk_widget_get_window(window), NULL, iconpm, iconmask); -#endif - -#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])); - } - gtk_window_set_icon_list(GTK_WINDOW(window), iconlist); -#endif -} - -static void free_special_cmd(gpointer data) { sfree(data); } - -static void gtk_seat_update_specials_menu(Seat *seat) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - const SessionSpecial *specials; - - if (inst->backend) - specials = backend_get_specials(inst->backend); - 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 SS_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 SS_EXITMENU: - nesting--; - if (nesting) { - menu = saved_menu; /* XXX lame stacking */ - saved_menu = NULL; - } - break; - case SS_SEP: - menuitem = gtk_menu_item_new(); - break; - default: { - menuitem = gtk_menu_item_new_with_label(specials[i].name); - SessionSpecial *sc = snew(SessionSpecial); - *sc = specials[i]; /* structure copy */ - g_object_set_data_full(G_OBJECT(menuitem), "user-data", - sc, free_special_cmd); - g_signal_connect(G_OBJECT(menuitem), "activate", - G_CALLBACK(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(GtkFrontend *inst) -{ - const struct BackendVtable *vt; - char *error, *realhost; - - inst->cmdline_get_passwd_state = cmdline_get_passwd_input_state_new; - - vt = select_backend(inst->conf); - - seat_set_trust_status(&inst->seat, true); - error = backend_init(vt, &inst->seat, &inst->backend, - inst->logctx, inst->conf, - conf_get_str(inst->conf, CONF_host), - conf_get_int(inst->conf, CONF_port), - &realhost, - conf_get_bool(inst->conf, CONF_tcp_nodelay), - conf_get_bool(inst->conf, CONF_tcp_keepalives)); - - if (error) { - if (cmdline_tooltype & TOOLTYPE_NONNETWORK) { - /* Special case for pterm. */ - seat_connection_fatal(&inst->seat, - "Unable to open terminal:\n%s", - error); - } else { - seat_connection_fatal(&inst->seat, - "Unable to open connection to %s:\n%s", - conf_dest(inst->conf), error); - } - sfree(error); - inst->exited = true; - return; - } - - term_setup_window_titles(inst->term, realhost); - sfree(realhost); - - term_provide_backend(inst->term, inst->backend); - - inst->ldisc = ldisc_create(inst->conf, inst->term, inst->backend, - &inst->seat); - - gtk_widget_set_sensitive(inst->restartitem, false); -} - -#if GTK_CHECK_VERSION(2,0,0) -static void get_monitor_geometry(GtkWidget *widget, GdkRectangle *geometry) -{ -#if GTK_CHECK_VERSION(3,4,0) - GdkDisplay *display = gtk_widget_get_display(widget); - GdkWindow *gdkwindow = gtk_widget_get_window(widget); -# if GTK_CHECK_VERSION(3,22,0) - GdkMonitor *monitor; - if (gdkwindow) - monitor = gdk_display_get_monitor_at_window(display, gdkwindow); - else - monitor = gdk_display_get_monitor(display, 0); - gdk_monitor_get_geometry(monitor, geometry); -# else - GdkScreen *screen = gdk_display_get_default_screen(display); - gint monitor_num = gdk_screen_get_monitor_at_window(screen, gdkwindow); - gdk_screen_get_monitor_geometry(screen, monitor_num, geometry); -# endif -#else - geometry->x = geometry->y = 0; - geometry->width = gdk_screen_width(); - geometry->height = gdk_screen_height(); -#endif -} -#endif - -static const TermWinVtable gtk_termwin_vt = { - .setup_draw_ctx = gtkwin_setup_draw_ctx, - .draw_text = gtkwin_draw_text, - .draw_cursor = gtkwin_draw_cursor, - .draw_trust_sigil = gtkwin_draw_trust_sigil, - .char_width = gtkwin_char_width, - .free_draw_ctx = gtkwin_free_draw_ctx, - .set_cursor_pos = gtkwin_set_cursor_pos, - .set_raw_mouse_mode = gtkwin_set_raw_mouse_mode, - .set_raw_mouse_mode_pointer = gtkwin_set_raw_mouse_mode_pointer, - .set_scrollbar = gtkwin_set_scrollbar, - .bell = gtkwin_bell, - .clip_write = gtkwin_clip_write, - .clip_request_paste = gtkwin_clip_request_paste, - .refresh = gtkwin_refresh, - .request_resize = gtkwin_request_resize, - .set_title = gtkwin_set_title, - .set_icon_title = gtkwin_set_icon_title, - .set_minimised = gtkwin_set_minimised, - .set_maximised = gtkwin_set_maximised, - .move = gtkwin_move, - .set_zorder = gtkwin_set_zorder, - .palette_set = gtkwin_palette_set, - .palette_get_overrides = gtkwin_palette_get_overrides, - .unthrottle = gtkwin_unthrottle, -}; - -void new_session_window(Conf *conf, const char *geometry_string) -{ - GtkFrontend *inst; - - prepare_session(conf); - - /* - * Create an instance structure and initialise to zeroes - */ - inst = snew(GtkFrontend); - memset(inst, 0, sizeof(*inst)); -#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 - inst->cdi_headtail.next = inst->cdi_headtail.prev = &inst->cdi_headtail; -#endif - inst->alt_keycode = -1; /* this one needs _not_ to be zero */ - inst->busy_status = BUSY_NOT; - inst->conf = conf; - inst->wintitle = inst->icontitle = NULL; - inst->drawtype = DRAWTYPE_DEFAULT; -#if GTK_CHECK_VERSION(3,4,0) - inst->cumulative_vscroll = 0.0; - inst->cumulative_hscroll = 0.0; -#endif - inst->drawing_area_setup_needed = true; - - inst->termwin.vt = >k_termwin_vt; - inst->seat.vt = >k_seat_vt; - inst->logpolicy.vt = >k_logpolicy_vt; - -#ifndef NOT_X_WINDOWS - inst->disp = get_x11_display(); - if (geometry_string) { - int flags, x, y; - unsigned int w, h; - flags = XParseGeometry(geometry_string, &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)); - } - } -#endif - - 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); - if (!clipboard_atom) - clipboard_atom = gdk_atom_intern("CLIPBOARD", false); - - inst->area = gtk_drawing_area_new(); - gtk_widget_set_name(GTK_WIDGET(inst->area), "drawing-area"); - - { - char *errmsg = setup_fonts_ucs(inst); - if (errmsg) { - window_setup_error(errmsg); - sfree(errmsg); - gtk_widget_destroy(inst->area); - sfree(inst); - return; - } - } - -#if GTK_CHECK_VERSION(2,0,0) - inst->imc = gtk_im_multicontext_new(); -#endif - - inst->window = make_gtk_toplevel_window(inst); - gtk_widget_set_name(GTK_WIDGET(inst->window), "top-level"); - { - const char *winclass = conf_get_str(inst->conf, CONF_winclass); - if (*winclass) { -#if GTK_CHECK_VERSION(3,22,0) -#ifndef NOT_X_WINDOWS - GdkWindow *gdkwin; - gtk_widget_realize(GTK_WIDGET(inst->window)); - gdkwin = gtk_widget_get_window(GTK_WIDGET(inst->window)); - if (inst->disp && gdk_window_ensure_native(gdkwin)) { - XClassHint *xch = XAllocClassHint(); - xch->res_name = (char *)winclass; - xch->res_class = (char *)winclass; - XSetClassHint(inst->disp, GDK_WINDOW_XID(gdkwin), xch); - XFree(xch); - } -#endif - /* - * If we do have NOT_X_WINDOWS set, then we don't have any - * function in GTK 3.22 equivalent to the above. But then, - * surely in that situation the deprecated - * gtk_window_set_wmclass wouldn't have done anything - * meaningful in previous GTKs either. - */ -#else - gtk_window_set_wmclass(GTK_WINDOW(inst->window), - winclass, winclass); -#endif - } - } - -#if GTK_CHECK_VERSION(2,0,0) - { - const BackendVtable *vt = select_backend(inst->conf); - if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) - gtk_window_set_resizable(GTK_WINDOW(inst->window), false); - } -#endif - - inst->width = conf_get_int(inst->conf, CONF_width); - inst->height = conf_get_int(inst->conf, CONF_height); - cache_conf_values(inst); - - init_clipboard(inst); - - 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_bool(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_bool(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)); - - gtk_widget_show(inst->area); - show_scrollbar(inst, conf_get_bool(inst->conf, CONF_scrollbar)); - gtk_widget_show(GTK_WIDGET(inst->hbox)); - - /* - * We must call gtk_widget_realize before setting up the geometry - * hints, so that GtkApplicationWindow will have actually created - * its menu bar (if it's going to) and hence compute_geom_hints - * can find it to take its size into account. - */ - gtk_widget_realize(inst->window); - set_geom_hints(inst); - -#if GTK_CHECK_VERSION(3,0,0) - { - int wp, hp; - compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp); - gtk_window_set_default_size(GTK_WINDOW(inst->window), wp, hp); - } -#else - { - int w = inst->font_width * inst->width + 2*inst->window_border; - int h = inst->font_height * inst->height + 2*inst->window_border; -#if GTK_CHECK_VERSION(2,0,0) - gtk_widget_set_size_request(inst->area, w, h); -#else - gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area), w, h); -#endif - } -#endif - -#if GTK_CHECK_VERSION(2,0,0) - if (inst->gotpos) { - static const GdkGravity gravities[] = { - GDK_GRAVITY_NORTH_WEST, - GDK_GRAVITY_NORTH_EAST, - GDK_GRAVITY_SOUTH_WEST, - GDK_GRAVITY_SOUTH_EAST, - }; - int x = inst->xpos, y = inst->ypos; - int wp, hp; - GdkRectangle monitor_geometry; - compute_whole_window_size(inst, inst->width, inst->height, &wp, &hp); - get_monitor_geometry(GTK_WIDGET(inst->window), &monitor_geometry); - if (inst->gravity & 1) x += (monitor_geometry.width - wp); - if (inst->gravity & 2) y += (monitor_geometry.height - hp); - gtk_window_set_gravity(GTK_WINDOW(inst->window), - gravities[inst->gravity & 3]); - gtk_window_move(GTK_WINDOW(inst->window), x, y); - } -#else - 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); - } -#endif - - g_signal_connect(G_OBJECT(inst->window), "destroy", - G_CALLBACK(destroy), inst); - g_signal_connect(G_OBJECT(inst->window), "delete_event", - G_CALLBACK(delete_window), inst); - g_signal_connect(G_OBJECT(inst->window), "key_press_event", - G_CALLBACK(key_event), inst); - g_signal_connect(G_OBJECT(inst->window), "key_release_event", - G_CALLBACK(key_event), inst); - g_signal_connect(G_OBJECT(inst->window), "focus_in_event", - G_CALLBACK(focus_event), inst); - g_signal_connect(G_OBJECT(inst->window), "focus_out_event", - G_CALLBACK(focus_event), inst); - g_signal_connect(G_OBJECT(inst->area), "realize", - G_CALLBACK(area_realised), inst); - g_signal_connect(G_OBJECT(inst->area), "size_allocate", - G_CALLBACK(area_size_allocate), inst); - g_signal_connect(G_OBJECT(inst->window), "configure_event", - G_CALLBACK(window_configured), inst); -#if GTK_CHECK_VERSION(3,10,0) - g_signal_connect(G_OBJECT(inst->area), "configure_event", - G_CALLBACK(area_configured), inst); -#endif -#if GTK_CHECK_VERSION(3,0,0) - g_signal_connect(G_OBJECT(inst->area), "draw", - G_CALLBACK(draw_area), inst); -#else - g_signal_connect(G_OBJECT(inst->area), "expose_event", - G_CALLBACK(expose_area), inst); -#endif - g_signal_connect(G_OBJECT(inst->area), "button_press_event", - G_CALLBACK(button_event), inst); - g_signal_connect(G_OBJECT(inst->area), "button_release_event", - G_CALLBACK(button_event), inst); -#if GTK_CHECK_VERSION(2,0,0) - g_signal_connect(G_OBJECT(inst->area), "scroll_event", - G_CALLBACK(scroll_event), inst); -#endif - g_signal_connect(G_OBJECT(inst->area), "motion_notify_event", - G_CALLBACK(motion_event), 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_bool(inst->conf, CONF_scrollbar)) - g_signal_connect(G_OBJECT(inst->sbar_adjust), "value_changed", - G_CALLBACK(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 -#if GTK_CHECK_VERSION(3,4,0) - | GDK_SMOOTH_SCROLL_MASK -#endif - ); - - 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; - - 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); \ - g_signal_connect(G_OBJECT(menuitem), "activate", \ - G_CALLBACK(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); - MKSEP(); - MKMENUITEM("Copy to " CLIPNAME_EXPLICIT_OBJECT, - copy_clipboard_menuitem); - MKMENUITEM("Paste from " CLIPNAME_EXPLICIT_OBJECT, - paste_clipboard_menuitem); - MKMENUITEM("Copy All", copy_all_menuitem); - MKSEP(); - s = dupcat("About ", appname); - 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); - inst->currcursor = inst->textcursor; - show_mouseptr(inst, true); - - inst->eventlogstuff = eventlogstuff_new(); - - inst->term = term_init(inst->conf, &inst->ucsdata, &inst->termwin); - setup_clipboards(inst, inst->term, inst->conf); - inst->logctx = log_init(&inst->logpolicy, inst->conf); - term_provide_logctx(inst->term, inst->logctx); - - term_size(inst->term, inst->height, inst->width, - conf_get_int(inst->conf, CONF_savelines)); - -#if GTK_CHECK_VERSION(2,0,0) - /* Delay this signal connection until after inst->term exists */ - g_signal_connect(G_OBJECT(inst->window), "window_state_event", - G_CALLBACK(window_state_event), inst); -#endif - - inst->exited = false; - - start_backend(inst); - - if (inst->ldisc) /* early backend failure might make this NULL already */ - ldisc_echoedit_update(inst->ldisc); /* cause ldisc to notice changes */ -} - -static void gtk_seat_set_trust_status(Seat *seat, bool trusted) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - term_set_trust_status(inst->term, trusted); -} - -static bool gtk_seat_can_set_trust_status(Seat *seat) -{ - return true; -} - -static bool gtk_seat_get_cursor_position(Seat *seat, int *x, int *y) -{ - GtkFrontend *inst = container_of(seat, GtkFrontend, seat); - if (inst->term) { - term_get_cursor_position(inst->term, x, y); - return true; - } - return false; -} diff --git a/unix/x11.c b/unix/x11.c deleted file mode 100644 index 8ec2da073..000000000 --- a/unix/x11.c +++ /dev/null @@ -1,224 +0,0 @@ -/* - * x11.c: fetch local auth data for X forwarding. - */ - -#include -#include -#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; - bool needs_free; - - /* - * Find the .Xauthority file. - */ - needs_free = false; - xauthfile = getenv("XAUTHORITY"); - if (!xauthfile) { - xauthfile = getenv("HOME"); - if (xauthfile) { - xauthfile = dupcat(xauthfile, "/.Xauthority"); - needs_free = true; - } - } - - if (xauthfile) { - Filename *xauthfn = filename_from_str(xauthfile); - if (needs_free) - sfree(xauthfile); - - x11_get_auth_from_authfile(disp, xauthfn); - filename_free(xauthfn); - } -} - -const bool platform_uses_x11_unix_by_default = true; - -int platform_make_x11_server(Plug *plug, const char *progname, int mindisp, - const char *screen_number_suffix, - ptrlen authproto, ptrlen authdata, - Socket **sockets, Conf *conf) -{ - char *tmpdir; - char *authfilename = NULL; - strbuf *authfiledata = NULL; - char *unix_path = NULL; - - SockAddr *a_tcp = NULL, *a_unix = NULL; - - int authfd; - FILE *authfp; - - int displayno; - - authfiledata = strbuf_new_nm(); - - int nsockets = 0; - - /* - * Look for a free TCP port to run our server on. - */ - for (displayno = mindisp;; displayno++) { - const char *err; - int tcp_port = displayno + 6000; - int addrtype = ADDRTYPE_IPV4; - - sockets[nsockets] = new_listener( - NULL, tcp_port, plug, false, conf, addrtype); - - err = sk_socket_error(sockets[nsockets]); - if (!err) { - char *hostname = get_hostname(); - if (hostname) { - char *canonicalname = NULL; - a_tcp = sk_namelookup(hostname, &canonicalname, addrtype); - sfree(canonicalname); - } - sfree(hostname); - nsockets++; - break; /* success! */ - } else { - sk_close(sockets[nsockets]); - } - - /* - * If we weren't able to bind to this port because it's in use - * by another program, go round this loop and try again. But - * for any other reason, give up completely and return failure - * to our caller. - * - * sk_socket_error currently has no machine-readable component - * (it would need a cross-platform abstraction of the socket - * error types we care about, plus translation from each OS - * error enumeration into that). So we use the disgusting - * approach of a string compare between the error string and - * the one EADDRINUSE would have given :-( - */ - if (strcmp(err, strerror(EADDRINUSE))) - goto out; - } - - if (a_tcp) { - x11_format_auth_for_authfile( - BinarySink_UPCAST(authfiledata), - a_tcp, displayno, authproto, authdata); - } - - /* - * Try to establish the Unix-domain analogue. That may or may not - * work - file permissions in /tmp may prevent it, for example - - * but it's worth a try, and we don't consider it a fatal error if - * it doesn't work. - */ - unix_path = dupprintf("/tmp/.X11-unix/X%d", displayno); - a_unix = unix_sock_addr(unix_path); - - sockets[nsockets] = new_unix_listener(a_unix, plug); - if (!sk_socket_error(sockets[nsockets])) { - x11_format_auth_for_authfile( - BinarySink_UPCAST(authfiledata), - a_unix, displayno, authproto, authdata); - nsockets++; - } else { - sk_close(sockets[nsockets]); - sfree(unix_path); - unix_path = NULL; - } - - /* - * Decide where the authority data will be written. - */ - - tmpdir = getenv("TMPDIR"); - if (!tmpdir || !*tmpdir) - tmpdir = "/tmp"; - - authfilename = dupcat(tmpdir, "/", progname, "-Xauthority-XXXXXX"); - - { - int oldumask = umask(077); - authfd = mkstemp(authfilename); - umask(oldumask); - } - if (authfd < 0) { - while (nsockets-- > 0) - sk_close(sockets[nsockets]); - goto out; - } - - /* - * Spawn a subprocess which will try to reliably delete our - * auth file when we terminate, in case we die unexpectedly. - */ - { - int cleanup_pipe[2]; - pid_t pid; - - /* Don't worry if pipe or fork fails; it's not _that_ critical. */ - if (!pipe(cleanup_pipe)) { - if ((pid = fork()) == 0) { - int buf[1024]; - /* - * Our parent process holds the writing end of - * this pipe, and writes nothing to it. Hence, - * we expect read() to return EOF as soon as - * that process terminates. - */ - - close(0); - close(1); - close(2); - - setpgid(0, 0); - close(cleanup_pipe[1]); - close(authfd); - while (read(cleanup_pipe[0], buf, sizeof(buf)) > 0); - unlink(authfilename); - if (unix_path) - unlink(unix_path); - _exit(0); - } else if (pid < 0) { - close(cleanup_pipe[0]); - close(cleanup_pipe[1]); - } else { - close(cleanup_pipe[0]); - cloexec(cleanup_pipe[1]); - } - } - } - - authfp = fdopen(authfd, "wb"); - fwrite(authfiledata->u, 1, authfiledata->len, authfp); - fclose(authfp); - - { - char *display = dupprintf(":%d%s", displayno, screen_number_suffix); - conf_set_str_str(conf, CONF_environmt, "DISPLAY", display); - sfree(display); - } - conf_set_str_str(conf, CONF_environmt, "XAUTHORITY", authfilename); - - /* - * FIXME: return at least the DISPLAY and XAUTHORITY env settings, - * and perhaps also the display number - */ - - out: - if (a_tcp) - sk_addr_free(a_tcp); - /* a_unix doesn't need freeing, because new_unix_listener took it over */ - sfree(authfilename); - strbuf_free(authfiledata); - sfree(unix_path); - return nsockets; -} diff --git a/unix/x11misc.h b/unix/x11misc.h deleted file mode 100644 index 5f5a2d265..000000000 --- a/unix/x11misc.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * x11misc.h: header file for functions that need to refer to Xlib - * data types. Has to be separate from unix.h so that we can include - * it only after including the X headers, which in turn has to be done - * after putty.h has told us whether NOT_X_WINDOWS is defined. - */ - -#ifndef NOT_X_WINDOWS - -/* Defined in unix/utils */ -void x11_ignore_error(Display *disp, unsigned char errcode); -Display *get_x11_display(void); - -#endif diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt deleted file mode 100644 index 5851a62fc..000000000 --- a/utils/CMakeLists.txt +++ /dev/null @@ -1,85 +0,0 @@ -add_sources_from_current_dir(utils - antispoof.c - backend_socket_log.c - base64_decode_atom.c - base64_decode.c - base64_encode_atom.c - base64_encode.c - base64_valid.c - bufchain.c - buildinfo.c - burnstr.c - burnwcs.c - cert-expr.c - chomp.c - cmdline_get_passwd_input_state_new.c - conf.c - conf_data.c - conf_dest.c - conf_launchable.c - ctrlparse.c - ctrlset_normalise.c - debug.c - decode_utf8.c - decode_utf8_to_wchar.c - decode_utf8_to_wide_string.c - default_description.c - dupcat.c - dupprintf.c - dupstr.c - dupwcs.c - dup_mb_to_wc.c - dup_wc_to_mb.c - encode_utf8.c - encode_wide_string_as_utf8.c - fgetline.c - host_ca_new_free.c - host_strchr.c - host_strchr_internal.c - host_strcspn.c - host_strduptrim.c - host_strrchr.c - key_components.c - log_proxy_stderr.c - make_spr_sw_abort_static.c - marshal.c - memory.c - memxor.c - nullstrcmp.c - out_of_memory.c - parse_blocksize.c - percent_decode.c - percent_encode.c - prompts.c - ptrlen.c - read_file_into.c - seat_connection_fatal.c - seat_dialog_text.c - seat_nonfatal.c - sessprep.c - sk_free_peer_info.c - smemclr.c - smemeq.c - spr_get_error_message.c - ssh_key_clone.c - ssh2_pick_fingerprint.c - sshutils.c - strbuf.c - string_length_for_printf.c - stripctrl.c - tempseat.c - tree234.c - unicode-known.c - unicode-norm.c - validate_manual_hostkey.c - version.c - wcwidth.c - wildcard.c - wordwrap.c - write_c_string_literal.c - x11authfile.c - x11authnames.c - x11_dehexify.c - x11_identify_auth_proto.c - x11_make_greeting.c - x11_parse_ip.c) diff --git a/utils/antispoof.c b/utils/antispoof.c deleted file mode 100644 index 6435944fe..000000000 --- a/utils/antispoof.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "putty.h" -#include "misc.h" - -void seat_antispoof_msg(InteractionReadySeat iseat, const char *msg) -{ - strbuf *sb = strbuf_new(); - seat_set_trust_status(iseat.seat, true); - if (seat_can_set_trust_status(iseat.seat)) { - /* - * If the seat can directly indicate that this message is - * generated by the client, then we can just use the message - * unmodified as an unspoofable header. - */ - put_dataz(sb, msg); - } else if (*msg) { - /* - * Otherwise, add enough padding around it that the server - * wouldn't be able to mimic it within our line-length - * constraint. - */ - put_fmt(sb, "-- %s ", msg); - while (sb->len < 78) - put_byte(sb, '-'); - } - put_datapl(sb, PTRLEN_LITERAL("\r\n")); - seat_banner_pl(iseat, ptrlen_from_strbuf(sb)); - strbuf_free(sb); -} diff --git a/utils/backend_socket_log.c b/utils/backend_socket_log.c deleted file mode 100644 index 783cca319..000000000 --- a/utils/backend_socket_log.c +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include - -#include "putty.h" -#include "network.h" - -void backend_socket_log(Seat *seat, LogContext *logctx, - PlugLogType type, SockAddr *addr, int port, - const char *error_msg, int error_code, Conf *conf, - bool session_started) -{ - char addrbuf[256], *msg; - - switch (type) { - case PLUGLOG_CONNECT_TRYING: - sk_getaddr(addr, addrbuf, lenof(addrbuf)); - if (sk_addr_needs_port(addr)) { - msg = dupprintf("Connecting to %s port %d", addrbuf, port); - } else { - msg = dupprintf("Connecting to %s", addrbuf); - } - break; - case PLUGLOG_CONNECT_FAILED: - sk_getaddr(addr, addrbuf, lenof(addrbuf)); - msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); - break; - case PLUGLOG_CONNECT_SUCCESS: - if (addr) - sk_getaddr(addr, addrbuf, lenof(addrbuf)); - else /* fallback if address unavailable */ - sprintf(addrbuf, "remote host"); - msg = dupprintf("Connected to %s", addrbuf); - break; - case PLUGLOG_PROXY_MSG: { - /* Proxy-related log messages have their own identifying - * prefix already, put on by our caller. */ - int len, log_to_term; - - /* Suffix \r\n temporarily, so we can log to the terminal. */ - msg = dupprintf("%s\r\n", error_msg); - len = strlen(msg); - assert(len >= 2); - - log_to_term = conf_get_int(conf, CONF_proxy_log_to_term); - if (log_to_term == AUTO) - log_to_term = session_started ? FORCE_OFF : FORCE_ON; - if (log_to_term == FORCE_ON) - seat_stderr(seat, msg, len); - - msg[len-2] = '\0'; /* remove the \r\n again */ - break; - } - default: - msg = NULL; /* shouldn't happen, but placate optimiser */ - break; - } - - if (msg) { - logevent(logctx, msg); - sfree(msg); - } -} diff --git a/utils/base64_decode.c b/utils/base64_decode.c deleted file mode 100644 index 4f00f1968..000000000 --- a/utils/base64_decode.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "misc.h" - -void base64_decode_bs(BinarySink *bs, ptrlen input) -{ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, input); - - while (get_avail(src)) { - char b64atom[4]; - unsigned char binatom[3]; - - for (size_t i = 0; i < 4 ;) { - char c = get_byte(src); - if (get_err(src)) - c = '='; - if (c == '\n' || c == '\r') - continue; - b64atom[i++] = c; - } - - put_data(bs, binatom, base64_decode_atom(b64atom, binatom)); - } -} - -void base64_decode_fp(FILE *fp, ptrlen input) -{ - stdio_sink ss; - stdio_sink_init(&ss, fp); - base64_decode_bs(BinarySink_UPCAST(&ss), input); -} - -strbuf *base64_decode_sb(ptrlen input) -{ - strbuf *sb = strbuf_new_nm(); - base64_decode_bs(BinarySink_UPCAST(sb), input); - return sb; -} diff --git a/utils/base64_decode_atom.c b/utils/base64_decode_atom.c deleted file mode 100644 index 2a98150ea..000000000 --- a/utils/base64_decode_atom.c +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Core routine to decode a single atomic base64 chunk. - */ - -#include "defs.h" -#include "misc.h" - -int base64_decode_atom(const 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; -} diff --git a/utils/base64_encode.c b/utils/base64_encode.c deleted file mode 100644 index 3db85571d..000000000 --- a/utils/base64_encode.c +++ /dev/null @@ -1,40 +0,0 @@ -#include "misc.h" - -void base64_encode_bs(BinarySink *bs, ptrlen input, int cpl) -{ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, input); - int linelen = 0; - - while (get_avail(src)) { - size_t n = get_avail(src) < 3 ? get_avail(src) : 3; - ptrlen binatom = get_data(src, n); - - char b64atom[4]; - base64_encode_atom(binatom.ptr, binatom.len, b64atom); - for (size_t i = 0; i < 4; i++) { - if (cpl > 0 && linelen >= cpl) { - linelen = 0; - put_byte(bs, '\n'); - } - put_byte(bs, b64atom[i]); - linelen++; - } - } - if (cpl > 0) - put_byte(bs, '\n'); -} - -void base64_encode_fp(FILE *fp, ptrlen input, int cpl) -{ - stdio_sink ss; - stdio_sink_init(&ss, fp); - base64_encode_bs(BinarySink_UPCAST(&ss), input, cpl); -} - -strbuf *base64_encode_sb(ptrlen input, int cpl) -{ - strbuf *sb = strbuf_new_nm(); - base64_encode_bs(BinarySink_UPCAST(sb), input, cpl); - return sb; -} diff --git a/utils/base64_encode_atom.c b/utils/base64_encode_atom.c deleted file mode 100644 index c33476f00..000000000 --- a/utils/base64_encode_atom.c +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Core routine to encode a single atomic base64 chunk. - */ - -#include "defs.h" -#include "misc.h" - -void base64_encode_atom(const 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] = '='; -} diff --git a/utils/base64_valid.c b/utils/base64_valid.c deleted file mode 100644 index 8eb1f3a07..000000000 --- a/utils/base64_valid.c +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Determine whether a string looks like valid base64-encoded data. - */ - -#include "misc.h" - -static inline bool valid_char_main(char c) -{ - return ((c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - (c >= '0' && c <= '9') || - c == '+' || c == '/'); -} - -bool base64_valid(ptrlen data) -{ - size_t blocklen = 0, nequals = 0; - - for (size_t i = 0; i < data.len; i++) { - char c = ((const char *)data.ptr)[i]; - - if (c == '\n' || c == '\r') - continue; - - if (valid_char_main(c)) { - if (nequals) /* can't go back to data after = */ - return false; - blocklen++; - if (blocklen == 4) - blocklen = 0; - continue; - } - - if (c == '=') { - if (blocklen == 0 && nequals) /* started a fresh block */ - return false; - - nequals++; - blocklen++; - if (blocklen == 4) { - if (nequals > 2) - return false; /* nonsensical final block */ - blocklen = 0; - } - continue; - } - - return false; /* bad character */ - } - - if (blocklen == 0 || blocklen == 2 || blocklen == 3) - return true; /* permit eliding the trailing = */ - return false; -} diff --git a/utils/bufchain.c b/utils/bufchain.c deleted file mode 100644 index 9b02c65ff..000000000 --- a/utils/bufchain.c +++ /dev/null @@ -1,193 +0,0 @@ -/* - * 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 - */ - -#include "defs.h" -#include "misc.h" - -#define BUFFER_MIN_GRANULE 512 - -struct bufchain_granule { - struct bufchain_granule *next; - char *bufpos, *bufend, *bufmax; -}; - -static void uninitialised_queue_idempotent_callback(IdempotentCallback *ic) -{ - unreachable("bufchain callback used while uninitialised"); -} - -void bufchain_init(bufchain *ch) -{ - ch->head = ch->tail = NULL; - ch->buffersize = 0; - ch->ic = NULL; - ch->queue_idempotent_callback = uninitialised_queue_idempotent_callback; -} - -void bufchain_clear(bufchain *ch) -{ - struct bufchain_granule *b; - while (ch->head) { - b = ch->head; - ch->head = ch->head->next; - smemclr(b, sizeof(*b)); - sfree(b); - } - ch->tail = NULL; - ch->buffersize = 0; -} - -size_t bufchain_size(bufchain *ch) -{ - return ch->buffersize; -} - -void bufchain_set_callback_inner( - bufchain *ch, IdempotentCallback *ic, - void (*queue_idempotent_callback)(IdempotentCallback *ic)) -{ - ch->queue_idempotent_callback = queue_idempotent_callback; - ch->ic = ic; -} - -void bufchain_add(bufchain *ch, const void *data, size_t 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) { - size_t 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) { - size_t 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; - } - } - - if (ch->ic) - ch->queue_idempotent_callback(ch->ic); -} - -void bufchain_consume(bufchain *ch, size_t 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; - smemclr(tmp, sizeof(*tmp)); - sfree(tmp); - } else - ch->head->bufpos += remlen; - ch->buffersize -= remlen; - len -= remlen; - } -} - -ptrlen bufchain_prefix(bufchain *ch) -{ - return make_ptrlen(ch->head->bufpos, ch->head->bufend - ch->head->bufpos); -} - -void bufchain_fetch(bufchain *ch, void *data, size_t 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; - } -} - -void bufchain_fetch_consume(bufchain *ch, void *data, size_t len) -{ - bufchain_fetch(ch, data, len); - bufchain_consume(ch, len); -} - -bool bufchain_try_fetch(bufchain *ch, void *data, size_t len) -{ - if (ch->buffersize >= len) { - bufchain_fetch(ch, data, len); - return true; - } else { - return false; - } -} - -bool bufchain_try_consume(bufchain *ch, size_t len) -{ - if (ch->buffersize >= len) { - bufchain_consume(ch, len); - return true; - } else { - return false; - } -} - -bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len) -{ - if (ch->buffersize >= len) { - bufchain_fetch_consume(ch, data, len); - return true; - } else { - return false; - } -} - -size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len) -{ - if (len > ch->buffersize) - len = ch->buffersize; - if (len) - bufchain_fetch_consume(ch, data, len); - return len; -} diff --git a/utils/buildinfo.c b/utils/buildinfo.c deleted file mode 100644 index 5a837e012..000000000 --- a/utils/buildinfo.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Return a string describing everything we know about how this - * particular binary was built: from what source, for what target - * platform, using what tools, with what settings, etc. - */ - -#include "putty.h" - -char *buildinfo(const char *newline) -{ - strbuf *buf = strbuf_new(); - - put_fmt(buf, "Build platform: %d-bit %s", - (int)(CHAR_BIT * sizeof(void *)), BUILDINFO_PLATFORM); - -#ifdef __clang_version__ -#define FOUND_COMPILER - put_fmt(buf, "%sCompiler: clang %s", newline, __clang_version__); -#elif defined __GNUC__ && defined __VERSION__ -#define FOUND_COMPILER - put_fmt(buf, "%sCompiler: gcc %s", newline, __VERSION__); -#endif - -#if defined _MSC_VER -#ifndef FOUND_COMPILER -#define FOUND_COMPILER - put_fmt(buf, "%sCompiler: ", newline); -#else - put_fmt(buf, ", emulating "); -#endif - put_fmt(buf, "Visual Studio"); - -#if 0 - /* - * List of _MSC_VER values and their translations taken from - * https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros - * - * The pointless #if 0 branch containing this comment is there so - * that every real clause can start with #elif and there's no - * anomalous first clause. That way the patch looks nicer when you - * add extra ones. - * - * Mostly you can tell the version just from _MSC_VER, but in some - * cases, two different compiler versions have the same _MSC_VER - * value, and have to be distinguished by _MSC_FULL_VER. - */ -#elif _MSC_VER == 1933 - put_fmt(buf, " 2022 (17.3)"); -#elif _MSC_VER == 1932 - put_fmt(buf, " 2022 (17.2)"); -#elif _MSC_VER == 1931 - put_fmt(buf, " 2022 (17.1)"); -#elif _MSC_VER == 1930 - put_fmt(buf, " 2022 (17.0)"); -#elif _MSC_VER == 1929 && _MSC_FULL_VER >= 192930100 - put_fmt(buf, " 2019 (16.11)"); -#elif _MSC_VER == 1929 - put_fmt(buf, " 2019 (16.10)"); -#elif _MSC_VER == 1928 && _MSC_FULL_VER >= 192829500 - put_fmt(buf, " 2019 (16.9)"); -#elif _MSC_VER == 1928 - put_fmt(buf, " 2019 (16.8)"); -#elif _MSC_VER == 1927 - put_fmt(buf, " 2019 (16.7)"); -#elif _MSC_VER == 1926 - put_fmt(buf, " 2019 (16.6)"); -#elif _MSC_VER == 1925 - put_fmt(buf, " 2019 (16.5)"); -#elif _MSC_VER == 1924 - put_fmt(buf, " 2019 (16.4)"); -#elif _MSC_VER == 1923 - put_fmt(buf, " 2019 (16.3)"); -#elif _MSC_VER == 1922 - put_fmt(buf, " 2019 (16.2)"); -#elif _MSC_VER == 1921 - put_fmt(buf, " 2019 (16.1)"); -#elif _MSC_VER == 1920 - put_fmt(buf, " 2019 (16.0)"); -#elif _MSC_VER == 1916 - put_fmt(buf, " 2017 version 15.9"); -#elif _MSC_VER == 1915 - put_fmt(buf, " 2017 version 15.8"); -#elif _MSC_VER == 1914 - put_fmt(buf, " 2017 version 15.7"); -#elif _MSC_VER == 1913 - put_fmt(buf, " 2017 version 15.6"); -#elif _MSC_VER == 1912 - put_fmt(buf, " 2017 version 15.5"); -#elif _MSC_VER == 1911 - put_fmt(buf, " 2017 version 15.3"); -#elif _MSC_VER == 1910 - put_fmt(buf, " 2017 RTW (15.0)"); -#elif _MSC_VER == 1900 - put_fmt(buf, " 2015 (14.0)"); -#elif _MSC_VER == 1800 - put_fmt(buf, " 2013 (12.0)"); -#elif _MSC_VER == 1700 - put_fmt(buf, " 2012 (11.0)"); -#elif _MSC_VER == 1600 - put_fmt(buf, " 2010 (10.0)"); -#elif _MSC_VER == 1500 - put_fmt(buf, " 2008 (9.0)"); -#elif _MSC_VER == 1400 - put_fmt(buf, " 2005 (8.0)"); -#elif _MSC_VER == 1310 - put_fmt(buf, " .NET 2003 (7.1)"); -#elif _MSC_VER == 1300 - put_fmt(buf, " .NET 2002 (7.0)"); -#elif _MSC_VER == 1200 - put_fmt(buf, " 6.0"); -#else - put_fmt(buf, ", unrecognised version"); -#endif - put_fmt(buf, ", _MSC_VER=%d", (int)_MSC_VER); -#ifdef _MSC_FULL_VER - put_fmt(buf, ", _MSC_FULL_VER=%d", (int)_MSC_FULL_VER); -#endif -#endif - -#ifdef BUILDINFO_GTK - { - char *gtk_buildinfo = buildinfo_gtk_version(); - if (gtk_buildinfo) { - put_fmt(buf, "%sCompiled against GTK version %s", - newline, gtk_buildinfo); - sfree(gtk_buildinfo); - } - } -#endif -#if defined _WINDOWS - { - int echm = has_embedded_chm(); - if (echm >= 0) - put_fmt(buf, "%sEmbedded HTML Help file: %s", newline, - echm ? "yes" : "no"); - } -#endif - -#if defined _WINDOWS && defined MINEFIELD - put_fmt(buf, "%sBuild option: MINEFIELD", newline); -#endif -#ifdef NO_IPV6 - put_fmt(buf, "%sBuild option: NO_IPV6", newline); -#endif -#ifdef NO_GSSAPI - put_fmt(buf, "%sBuild option: NO_GSSAPI", newline); -#endif -#ifdef STATIC_GSSAPI - put_fmt(buf, "%sBuild option: STATIC_GSSAPI", newline); -#endif -#ifdef UNPROTECT - put_fmt(buf, "%sBuild option: UNPROTECT", newline); -#endif -#ifdef FUZZING - put_fmt(buf, "%sBuild option: FUZZING", newline); -#endif -#ifdef DEBUG - put_fmt(buf, "%sBuild option: DEBUG", newline); -#endif - - put_fmt(buf, "%sSource commit: %s", newline, commitid); - - return strbuf_to_str(buf); -} diff --git a/utils/burnstr.c b/utils/burnstr.c deleted file mode 100644 index 33214d897..000000000 --- a/utils/burnstr.c +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 'Burn' a dynamically allocated string, in the sense of destroying - * it beyond recovery: overwrite it with zeroes and then free it. - */ - -#include "defs.h" -#include "misc.h" - -void burnstr(char *string) -{ - if (string) { - smemclr(string, strlen(string)); - sfree(string); - } -} diff --git a/utils/burnwcs.c b/utils/burnwcs.c deleted file mode 100644 index 15d325f16..000000000 --- a/utils/burnwcs.c +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 'Burn' a dynamically allocated wide string, in the sense of - * destroying it beyond recovery: overwrite it with zeroes and then - * free it. - */ - -#include - -#include "defs.h" -#include "misc.h" - -void burnwcs(wchar_t *string) -{ - if (string) { - smemclr(string, sizeof(*string) * wcslen(string)); - sfree(string); - } -} diff --git a/utils/cert-expr.c b/utils/cert-expr.c deleted file mode 100644 index b22b380c6..000000000 --- a/utils/cert-expr.c +++ /dev/null @@ -1,968 +0,0 @@ -/* - * Parser for the boolean expression language used to configure what - * host names an OpenSSH certificate will be trusted to sign for. - */ - -/* - -Language specification -====================== - -Outer lexical layer: the input expression is broken up into tokens, -with any whitespace between them discarded and ignored. The following -tokens are special: - - ( ) && || ! - -and the remaining token type is an 'atom', which is any non-empty -sequence of characters from the following set: - - ABCDEFGHIJKLMNOPQRSTUVWXYZ - abcdefghijklmnopqrstuvwxyz - 0123456789 - .-_*?[]/: - -Inner lexical layer: once the boundaries of an 'atom' token have been -determined by the outer lex layer, each atom is further classified -into one of the following subtypes: - - - If it contains no ':' or '/', it's taken to be a wildcard matching - hostnames, e.g. "*.example.com". - - - If it begins with 'port:' followed by digits, it's taken to be a - single port number specification, e.g. "port:22". - - - If it begins with 'port:' followed by two digit sequences separated - by '-', it's taken to be a port number range, e.g. "port:0-1023". - - - Any other atom is reserved for future expansion. (See Rationale.) - -Syntax layer: all of those types of atom are interpreted as predicates -applied to the (hostname, port) data configured for the SSH connection -for which the certificate is being validated. - -Wildcards are handled using the syntax in wildcard.c. The dot- -separated structure of hostnames is thus not special; the '*' in -"*.example.com" will match any number of subdomains under example.com. - -More complex boolean expressions can be made by combining those -predicates using the boolean operators and parentheses, in the obvious -way: && and || are infix operators representing logical AND and OR, ! -is a prefix operator representing logical NOT, and parentheses -indicate grouping. - -Each of && and || can associate freely with itself (that is, you can -write "a && b && c" without having to parenthesise one or the other -subexpression). But they are forbidden to associate with _each other_. -That is, if you write "a && b || c" or "a || b && c", it's a syntax -error, and you must add parentheses to indicate which operator was -intended to have the higher priority. - -Rationale -========= - -Atoms: restrictions -------------------- - -The characters permitted in the 'atom' token don't include \, even -though it's a special character defined by wildcard.c. That's because -in this restricted context wildcards will never need it: no hostname -contains a literal \, and neither does any hostname contain a literal -instance of any of the wildcard characters that wildcard.c allows you -to use \ to escape. - -Atoms: future extension ------------------------ - -The specification of the 'atom' token is intended to leave space for -more than one kind of future extension. - -Most obviously, additional special predicates similar to "port:", with -different disambiguating prefixes. I don't know what things of that -kind we might need, but space is left for them just in case. - -Also, the unused '/' in the permitted-characters spec is intended to -leave open the possibility of allowing certificate acceptance to be -based on IP address, because the usual CIDR syntax for specifying IP -ranges (e.g. "192.168.1.0/24" or "2345:6789:abcd:ef01::/128") would be -lexed as a single atom under these rules. - -For the moment, certificate acceptance rules based on IP address are -not supported, because it's not clear what the semantics ought to be. -There are two problems with using IP addresses for this purpose: - - 1. Sometimes they come from the DNS, which means you can't trust - them. The whole idea of SSH is to end-to-end authenticate the host - key against only the input given _by the user_ to the client. Any - additional data provided by the network, such as the result of a - DNS lookup, is suspect. - - On the other hand, sometimes the IP address *is* part of the user - input, because the user can provide an IP address rather than a - hostname as the intended connection destination. So there are two - kinds of IP address, and they should very likely be treated - differently. - - 2. Sometimes the server's IP address is not even *known* by the - client, if you're connecting via a proxy and leaving DNS lookups - to the proxy. - -So, what should a boolean expression do if it's asked to accept or -reject based on an IP address, and the IP address is unknown or -untrustworthy? I'm not sure, and therefore, in the initial version of -this expression system, I haven't implemented them at all. - -But the syntax is still available for a future extension to use, if we -come up with good answers to these questions. - -(One possibility would be to evaluate the whole expression in Kleene -three-valued logic, so that every subexpression has the possible -answers TRUE, FALSE and UNKNOWN. If a definite IP address is not -available, IP address predicates evaluate to UNKNOWN. Then, once the -expression as a whole is evaluated, fail closed, by interpreting -UNKNOWN as 'reject'. The effect would be that a positive _or_ negative -constraint on the IP address would cause rejection if the IP address -is not reliably known, because once the predicate itself has returned -UNKNOWN, negating it still gives UNKNOWN. The only way you could still -accept a certificate in that situation would be if the overall -structure of the expression meant that the test of the IP address -couldn't affect the result anyway, e.g. if it was ANDed with another -subexpression that definitely evaluated to FALSE, or ORed with one -that evaluated to TRUE. This system seems conceptually elegant to me, -but the argument against it is that it's complicated and -counterintuitive, which is not a property you want in something a user -is writing for security purposes!) - -Operator precedence -------------------- - -Why did I choose to make && and || refuse to associate with each -other, instead of applying the usual C precedence rule that && beats -||? Because I think the C precedence rule is essentially arbitrary, in -the sense that when people are writing boolean expressions in practice -based on predicates from the rest of their program, it's about equally -common to want to nest an && within an || and vice versa. So the -default precedence rule only gives the user what they actually wanted -about 50% of the time, and leads to absent-minded errors about as -often as it conveniently allows you to omit a pair of parens. - -With my mathematician hat on, it's not so arbitrary. I agree that if -you're *going* to give || and && a relative priority then it makes -more sense to make && the higher-priority one, because if you're -thinking algebraically, && is more multiplicative and || is more -additive. But the pure-maths contexts in which that's convenient have -nothing to do with general boolean expressions in if statements. - -This boolean syntax is still close enough to that of C and its -derivatives to allow easy enough expression interchange (not counting -the fact that atoms would need rewriting). Any boolean expression -structure accepted by this syntax is also legal C and means the same -thing; any expression structure accepted by C is either legal and -equivalent in this syntax, or will fail with an error. In no case is -anything accepted but mapped to a different meaning. - - */ - -#include "putty.h" - -typedef enum Token { - TOK_LPAR, TOK_RPAR, - TOK_AND, TOK_OR, TOK_NOT, - TOK_ATOM, - TOK_END, TOK_ERROR -} Token; - -static inline bool is_space(char c) -{ - return (c == ' ' || c == '\n' || c == '\r' || c == '\t' || - c == '\f' || c == '\v'); -} - -static inline bool is_operator_char(char c) -{ - return (c == '(' || c == ')' || c == '&' || c == '|' || c == '!'); -} - -static inline bool is_atom_char(char c) -{ - return (('A' <= c && c <= 'Z') || - ('a' <= c && c <= 'z') || - ('0' <= c && c <= '9') || - c == '.' || c == '-' || c == '_' || c == '*' || c == '?' || - c == '[' || c == ']' || c == '/' || c == ':'); -} - -static Token lex(ptrlen *text, ptrlen *token, char **err) -{ - const char *p = text->ptr, *e = p + text->len; - Token type = TOK_ERROR; - - /* Skip whitespace */ - while (p < e && is_space(*p)) - p++; - - const char *start = p; - - if (!(p < e)) { - type = TOK_END; - goto out; - } - - if (is_operator_char(*p)) { - /* Match boolean-expression tokens */ - static const struct operator { - ptrlen text; - Token type; - } operators[] = { - {PTRLEN_DECL_LITERAL("("), TOK_LPAR}, - {PTRLEN_DECL_LITERAL(")"), TOK_RPAR}, - {PTRLEN_DECL_LITERAL("&&"), TOK_AND}, - {PTRLEN_DECL_LITERAL("||"), TOK_OR}, - {PTRLEN_DECL_LITERAL("!"), TOK_NOT}, - }; - - for (size_t i = 0; i < lenof(operators); i++) { - const struct operator *op = &operators[i]; - if (e - p >= op->text.len && - ptrlen_eq_ptrlen(op->text, make_ptrlen(p, op->text.len))) { - p += op->text.len; - type = op->type; - goto out; - } - } - - /* - * Report an error if one of the operator characters is used - * in a way that doesn't match something in that table (e.g. a - * single &). - */ - p++; - type = TOK_ERROR; - *err = dupstr("unrecognised boolean operator"); - goto out; - } else if (is_atom_char(*p)) { - /* - * Match an 'atom' token, which is any non-empty sequence of - * characters from the combined set that allows hostname - * wildcards, IP address ranges and special predicates like - * port numbers. - */ - do { - p++; - } while (p < e && is_atom_char(*p)); - - type = TOK_ATOM; - goto out; - } else { - /* - * Otherwise, report an error. - */ - p++; - type = TOK_ERROR; - *err = dupstr("unexpected character in expression"); - goto out; - } - - out: - *token = make_ptrlen(start, p - start); - text->ptr = p; - text->len = e - p; - return type; -} - -typedef enum Operator { - OP_AND, OP_OR, OP_NOT, - OP_HOSTNAME_WC, OP_PORT_RANGE -} Operator; - -typedef struct ExprNode ExprNode; -struct ExprNode { - Operator op; - ptrlen text; - union { - struct { - /* OP_AND, OP_OR */ - ExprNode **subexprs; - size_t nsubexprs; - }; - struct { - /* OP_NOT */ - ExprNode *subexpr; - }; - struct { - /* OP_HOSTNAME_WC */ - char *wc; - }; - struct { - /* OP_PORT_RANGE */ - unsigned lo, hi; /* both inclusive */ - }; - }; -}; - -static ExprNode *exprnode_new(Operator op, ptrlen text) -{ - ExprNode *en = snew(ExprNode); - memset(en, 0, sizeof(*en)); - en->op = op; - en->text = text; - return en; -} - -static void exprnode_free(ExprNode *en) -{ - switch (en->op) { - case OP_AND: - case OP_OR: - for (size_t i = 0; i < en->nsubexprs; i++) - exprnode_free(en->subexprs[i]); - sfree(en->subexprs); - break; - case OP_NOT: - exprnode_free(en->subexpr); - break; - case OP_HOSTNAME_WC: - sfree(en->wc); - break; - case OP_PORT_RANGE: - break; - default: - unreachable("unhandled node type in exprnode_free"); - } - - sfree(en); -} - -static unsigned ptrlen_to_port_number(ptrlen input) -{ - unsigned val = 0; - for (const char *p = input.ptr, *end = p + input.len; p < end; p++) { - assert('0' <= *p && *p <= '9'); /* expect parser to have checked */ - val = 10 * val + (*p - '0'); - if (val >= 65536) - val = 65536; /* normalise 'too large' to avoid integer overflow */ - } - return val; -} - -typedef struct ParserState ParserState; -struct ParserState { - ptrlen currtext; - Token tok; - ptrlen toktext; - char *err; - ptrlen errloc; -}; - -static void error(ParserState *ps, char *errtext, ptrlen errloc) -{ - if (!ps->err) { - ps->err = errtext; - ps->errloc = errloc; - } else { - sfree(errtext); - } -} - -static void advance(ParserState *ps) -{ - char *err = NULL; - ps->tok = lex(&ps->currtext, &ps->toktext, &err); - if (ps->tok == TOK_ERROR) - error(ps, err, ps->toktext); -} - -static ExprNode *parse_atom(ParserState *ps); -static ExprNode *parse_expr(ParserState *ps); - -static bool atom_is_hostname_wc(ptrlen toktext) -{ - return !ptrlen_contains(toktext, ":/"); -} - -static ExprNode *parse_atom(ParserState *ps) -{ - if (ps->tok == TOK_LPAR) { - ptrlen openpar = ps->toktext; - advance(ps); /* eat the ( */ - - ExprNode *subexpr = parse_expr(ps); - if (!subexpr) - return NULL; - - if (ps->tok != TOK_RPAR) { - error(ps, dupstr("expected ')' after parenthesised subexpression"), - subexpr->text); - exprnode_free(subexpr); - return NULL; - } - - ptrlen closepar = ps->toktext; - advance(ps); /* eat the ) */ - - /* We can reuse the existing AST node, but we need to extend - * its bounds within the input expression to include the - * parentheses */ - subexpr->text = make_ptrlen_startend( - openpar.ptr, ptrlen_end(closepar)); - return subexpr; - } - - if (ps->tok == TOK_NOT) { - ptrlen notloc = ps->toktext; - advance(ps); /* eat the ! */ - - ExprNode *subexpr = parse_atom(ps); - if (!subexpr) - return NULL; - - ExprNode *en = exprnode_new( - OP_NOT, make_ptrlen_startend( - notloc.ptr, ptrlen_end(subexpr->text))); - en->subexpr = subexpr; - return en; - } - - if (ps->tok == TOK_ATOM) { - if (atom_is_hostname_wc(ps->toktext)) { - /* Hostname wildcard. */ - ExprNode *en = exprnode_new(OP_HOSTNAME_WC, ps->toktext); - en->wc = mkstr(ps->toktext); - advance(ps); - return en; - } - - ptrlen tail; - if (ptrlen_startswith(ps->toktext, PTRLEN_LITERAL("port:"), &tail)) { - /* Port number (single or range). */ - unsigned lo, hi; - char *minus; - static const char DIGITS[] = "0123456789\0"; - bool parse_ok = false; - - if (tail.len > 0 && ptrlen_contains_only(tail, DIGITS)) { - lo = ptrlen_to_port_number(tail); - if (lo >= 65536) { - error(ps, dupstr("port number too large"), tail); - return NULL; - } - hi = lo; - parse_ok = true; - } else if ((minus = memchr(tail.ptr, '-', tail.len)) != NULL) { - ptrlen pl_lo = make_ptrlen_startend(tail.ptr, minus); - ptrlen pl_hi = make_ptrlen_startend(minus+1, ptrlen_end(tail)); - if (pl_lo.len > 0 && ptrlen_contains_only(pl_lo, DIGITS) && - pl_hi.len > 0 && ptrlen_contains_only(pl_hi, DIGITS)) { - - lo = ptrlen_to_port_number(pl_lo); - if (lo >= 65536) { - error(ps, dupstr("port number too large"), pl_lo); - return NULL; - } - - hi = ptrlen_to_port_number(pl_hi); - if (hi >= 65536) { - error(ps, dupstr("port number too large"), pl_hi); - return NULL; - } - - if (hi < lo) { - error(ps, dupstr("port number range is backwards"), - make_ptrlen_startend(pl_lo.ptr, - ptrlen_end(pl_hi))); - return NULL; - } - - parse_ok = true; - } - } - - if (!parse_ok) { - error(ps, dupstr("unable to parse port number specification"), - ps->toktext); - return NULL; - } - - - ExprNode *en = exprnode_new(OP_PORT_RANGE, ps->toktext); - en->lo = lo; - en->hi = hi; - advance(ps); - return en; - } - } - - error(ps, dupstr("expected a predicate or a parenthesised subexpression"), - ps->toktext); - return NULL; -} - -static ExprNode *parse_expr(ParserState *ps) -{ - ExprNode *subexpr = parse_atom(ps); - if (!subexpr) - return NULL; - - if (ps->tok != TOK_AND && ps->tok != TOK_OR) - return subexpr; - - Token operator = ps->tok; - ExprNode *en = exprnode_new(ps->tok == TOK_AND ? OP_AND : OP_OR, - subexpr->text); - size_t subexprs_size = 0; - - sgrowarray(en->subexprs, subexprs_size, en->nsubexprs); - en->subexprs[en->nsubexprs++] = subexpr; - - while (true) { - advance(ps); /* eat the operator */ - - subexpr = parse_atom(ps); - if (!subexpr) { - exprnode_free(en); - return NULL; - } - sgrowarray(en->subexprs, subexprs_size, en->nsubexprs); - en->subexprs[en->nsubexprs++] = subexpr; - en->text = make_ptrlen_startend( - en->text.ptr, ptrlen_end(subexpr->text)); - - if (ps->tok != TOK_AND && ps->tok != TOK_OR) - return en; - - if (ps->tok != operator) { - error(ps, dupstr("expected parentheses to disambiguate && and || " - "on either side of expression"), subexpr->text); - exprnode_free(en); - return NULL; - } - } -} - -static ExprNode *parse(ptrlen expr, char **error_msg, ptrlen *error_loc) -{ - ParserState ps[1]; - ps->currtext = expr; - ps->err = NULL; - advance(ps); - - ExprNode *en = parse_expr(ps); - if (en && ps->tok != TOK_END) { - error(ps, dupstr("unexpected text at end of expression"), - make_ptrlen_startend(ps->toktext.ptr, ptrlen_end(expr))); - exprnode_free(en); - en = NULL; - } - - if (!en) { - if (error_msg) - *error_msg = ps->err; - else - sfree(ps->err); - if (error_loc) - *error_loc = ps->errloc; - return NULL; - } - - return en; -} - -static bool eval(ExprNode *en, const char *hostname, unsigned port) -{ - switch (en->op) { - case OP_AND: - for (size_t i = 0; i < en->nsubexprs; i++) - if (!eval(en->subexprs[i], hostname, port)) - return false; - return true; - - case OP_OR: - for (size_t i = 0; i < en->nsubexprs; i++) - if (eval(en->subexprs[i], hostname, port)) - return true; - return false; - - case OP_NOT: - return !eval(en->subexpr, hostname, port); - - case OP_HOSTNAME_WC: - return wc_match(en->wc, hostname); - - case OP_PORT_RANGE: - return en->lo <= port && port <= en->hi; - - default: - unreachable("unhandled node type in eval"); - } -} - -bool cert_expr_match_str(const char *expression, - const char *hostname, unsigned port) -{ - ExprNode *en = parse(ptrlen_from_asciz(expression), NULL, NULL); - if (!en) - return false; - - bool matched = eval(en, hostname, port); - exprnode_free(en); - return matched; -} - -bool cert_expr_valid(const char *expression, - char **error_msg, ptrlen *error_loc) -{ - ExprNode *en = parse(ptrlen_from_asciz(expression), error_msg, error_loc); - if (en) { - exprnode_free(en); - return true; - } else { - return false; - } -} - -struct CertExprBuilder { - char **wcs; - size_t nwcs, wcsize; -}; - -CertExprBuilder *cert_expr_builder_new(void) -{ - CertExprBuilder *eb = snew(CertExprBuilder); - eb->wcs = NULL; - eb->nwcs = eb->wcsize = 0; - return eb; -} - -void cert_expr_builder_free(CertExprBuilder *eb) -{ - for (size_t i = 0; i < eb->nwcs; i++) - sfree(eb->wcs[i]); - sfree(eb->wcs); - sfree(eb); -} - -void cert_expr_builder_add(CertExprBuilder *eb, const char *wildcard) -{ - /* Check this wildcard is lexically valid as an atom */ - ptrlen orig = ptrlen_from_asciz(wildcard), pl = orig; - ptrlen toktext; - char *err; - Token tok = lex(&pl, &toktext, &err); - if (!(tok == TOK_ATOM && - toktext.ptr == orig.ptr && - toktext.len == orig.len && - atom_is_hostname_wc(toktext))) { - if (tok == TOK_ERROR) - sfree(err); - return; - } - - sgrowarray(eb->wcs, eb->wcsize, eb->nwcs); - eb->wcs[eb->nwcs++] = mkstr(orig); -} - -char *cert_expr_expression(CertExprBuilder *eb) -{ - strbuf *sb = strbuf_new(); - for (size_t i = 0; i < eb->nwcs; i++) { - if (i) - put_dataz(sb, " || "); - put_dataz(sb, eb->wcs[i]); - } - return strbuf_to_str(sb); -} - -#ifdef TEST - -void out_of_memory(void) { fprintf(stderr, "out of memory\n"); abort(); } - -static void exprnode_dump(BinarySink *bs, ExprNode *en, const char *origtext) -{ - put_fmt(bs, "(%zu:%zu ", - (size_t)((const char *)en->text.ptr - origtext), - (size_t)((const char *)ptrlen_end(en->text) - origtext)); - switch (en->op) { - case OP_AND: - case OP_OR: - put_dataz(bs, en->op == OP_AND ? "and" : "or"); - for (size_t i = 0; i < en->nsubexprs; i++) { - put_byte(bs, ' '); - exprnode_dump(bs, en->subexprs[i], origtext); - } - break; - case OP_NOT: - put_dataz(bs, "not "); - exprnode_dump(bs, en->subexpr, origtext); - break; - case OP_HOSTNAME_WC: - put_dataz(bs, "host-wc '"); - put_dataz(bs, en->wc); - put_byte(bs, '\''); - break; - case OP_PORT_RANGE: - put_fmt(bs, "port-range %u %u", en->lo, en->hi); - break; - default: - unreachable("unhandled node type in exprnode_dump"); - } - put_byte(bs, ')'); -} - -static const struct ParseTest { - const char *file; - int line; - const char *expr, *output; -} parsetests[] = { -#define T(expr_, output_) { \ - .file=__FILE__, .line=__LINE__, .expr=expr_, .output=output_} - - T("*.example.com", "(0:13 host-wc '*.example.com')"), - T("port:0", "(0:6 port-range 0 0)"), - T("port:22", "(0:7 port-range 22 22)"), - T("port:22-22", "(0:10 port-range 22 22)"), - T("port:65535", "(0:10 port-range 65535 65535)"), - T("port:0-1023", "(0:11 port-range 0 1023)"), - - T("&", "ERR:0:1:unrecognised boolean operator"), - T("|", "ERR:0:1:unrecognised boolean operator"), - T(";", "ERR:0:1:unexpected character in expression"), - T("port:", "ERR:0:5:unable to parse port number specification"), - T("port:abc", "ERR:0:8:unable to parse port number specification"), - T("port:65536", "ERR:5:10:port number too large"), - T("port:65536-65537", "ERR:5:10:port number too large"), - T("port:0-65536", "ERR:7:12:port number too large"), - T("port:23-22", "ERR:5:10:port number range is backwards"), - - T("a", "(0:1 host-wc 'a')"), - T("(a)", "(0:3 host-wc 'a')"), - T("((a))", "(0:5 host-wc 'a')"), - T(" (\n(\ra\t)\f)\v", "(1:10 host-wc 'a')"), - T("a&&b", "(0:4 and (0:1 host-wc 'a') (3:4 host-wc 'b'))"), - T("a||b", "(0:4 or (0:1 host-wc 'a') (3:4 host-wc 'b'))"), - T("a&&b&&c", "(0:7 and (0:1 host-wc 'a') (3:4 host-wc 'b') (6:7 host-wc 'c'))"), - T("a||b||c", "(0:7 or (0:1 host-wc 'a') (3:4 host-wc 'b') (6:7 host-wc 'c'))"), - T("a&&(b||c)", "(0:9 and (0:1 host-wc 'a') (3:9 or (4:5 host-wc 'b') (7:8 host-wc 'c')))"), - T("a||(b&&c)", "(0:9 or (0:1 host-wc 'a') (3:9 and (4:5 host-wc 'b') (7:8 host-wc 'c')))"), - T("(a&&b)||c", "(0:9 or (0:6 and (1:2 host-wc 'a') (4:5 host-wc 'b')) (8:9 host-wc 'c'))"), - T("(a||b)&&c", "(0:9 and (0:6 or (1:2 host-wc 'a') (4:5 host-wc 'b')) (8:9 host-wc 'c'))"), - T("!a&&b", "(0:5 and (0:2 not (1:2 host-wc 'a')) (4:5 host-wc 'b'))"), - T("a&&!b&&c", "(0:8 and (0:1 host-wc 'a') (3:5 not (4:5 host-wc 'b')) (7:8 host-wc 'c'))"), - T("!a||b", "(0:5 or (0:2 not (1:2 host-wc 'a')) (4:5 host-wc 'b'))"), - T("a||!b||c", "(0:8 or (0:1 host-wc 'a') (3:5 not (4:5 host-wc 'b')) (7:8 host-wc 'c'))"), - - T("", "ERR:0:0:expected a predicate or a parenthesised subexpression"), - T("a &&", "ERR:4:4:expected a predicate or a parenthesised subexpression"), - T("a ||", "ERR:4:4:expected a predicate or a parenthesised subexpression"), - T("a b c d", "ERR:2:7:unexpected text at end of expression"), - T("(", "ERR:1:1:expected a predicate or a parenthesised subexpression"), - T("(a", "ERR:1:2:expected ')' after parenthesised subexpression"), - T("(a b", "ERR:1:2:expected ')' after parenthesised subexpression"), - T("a&&b&&c||d||e", "ERR:6:7:expected parentheses to disambiguate && and || on either side of expression"), - T("a||b||c&&d&&e", "ERR:6:7:expected parentheses to disambiguate && and || on either side of expression"), - T("!", "ERR:1:1:expected a predicate or a parenthesised subexpression"), - - T("!a", "(0:2 not (1:2 host-wc 'a'))"), - -#undef T -}; - -static const struct EvalTest { - const char *file; - int line; - const char *expr; - const char *host; - unsigned port; - bool output; -} evaltests[] = { -#define T(expr_, host_, port_, output_) { \ - .file=__FILE__, .line=__LINE__, \ - .expr=expr_, .host=host_, .port=port_, .output=output_} - - T("*.example.com", "hostname.example.com", 22, true), - T("*.example.com", "hostname.example.org", 22, false), - T("*.example.com", "hostname.dept.example.com", 22, true), - T("*.example.com && port:22", "hostname.example.com", 21, false), - T("*.example.com && port:22", "hostname.example.com", 22, true), - T("*.example.com && port:22", "hostname.example.com", 23, false), - T("*.example.com && port:22-24", "hostname.example.com", 21, false), - T("*.example.com && port:22-24", "hostname.example.com", 22, true), - T("*.example.com && port:22-24", "hostname.example.com", 23, true), - T("*.example.com && port:22-24", "hostname.example.com", 24, true), - T("*.example.com && port:22-24", "hostname.example.com", 25, false), - - T("*a* && *b* && *c*", "", 22, false), - T("*a* && *b* && *c*", "a", 22, false), - T("*a* && *b* && *c*", "b", 22, false), - T("*a* && *b* && *c*", "c", 22, false), - T("*a* && *b* && *c*", "ab", 22, false), - T("*a* && *b* && *c*", "ac", 22, false), - T("*a* && *b* && *c*", "bc", 22, false), - T("*a* && *b* && *c*", "abc", 22, true), - - T("*a* || *b* || *c*", "", 22, false), - T("*a* || *b* || *c*", "a", 22, true), - T("*a* || *b* || *c*", "b", 22, true), - T("*a* || *b* || *c*", "c", 22, true), - T("*a* || *b* || *c*", "ab", 22, true), - T("*a* || *b* || *c*", "ac", 22, true), - T("*a* || *b* || *c*", "bc", 22, true), - T("*a* || *b* || *c*", "abc", 22, true), - - T("*a* && !*b* && *c*", "", 22, false), - T("*a* && !*b* && *c*", "a", 22, false), - T("*a* && !*b* && *c*", "b", 22, false), - T("*a* && !*b* && *c*", "c", 22, false), - T("*a* && !*b* && *c*", "ab", 22, false), - T("*a* && !*b* && *c*", "ac", 22, true), - T("*a* && !*b* && *c*", "bc", 22, false), - T("*a* && !*b* && *c*", "abc", 22, false), - - T("*a* || !*b* || *c*", "", 22, true), - T("*a* || !*b* || *c*", "a", 22, true), - T("*a* || !*b* || *c*", "b", 22, false), - T("*a* || !*b* || *c*", "c", 22, true), - T("*a* || !*b* || *c*", "ab", 22, true), - T("*a* || !*b* || *c*", "ac", 22, true), - T("*a* || !*b* || *c*", "bc", 22, true), - T("*a* || !*b* || *c*", "abc", 22, true), - -#undef T -}; - -int main(int argc, char **argv) -{ - if (argc > 1) { - /* - * Parse an expression from the command line. - */ - - ptrlen expr = ptrlen_from_asciz(argv[1]); - char *error_msg; - ptrlen error_loc; - ExprNode *en = parse(expr, &error_msg, &error_loc); - if (!en) { - fprintf(stderr, "ERR:%zu:%zu:%s\n", - (size_t)((const char *)error_loc.ptr - argv[1]), - (size_t)((const char *)ptrlen_end(error_loc) - argv[1]), - error_msg); - fprintf(stderr, "%.*s\n", PTRLEN_PRINTF(expr)); - for (const char *p = expr.ptr, *e = error_loc.ptr; p 2) { - /* - * Test-evaluate against a host/port pair given on the - * command line. - */ - const char *host = argv[2]; - unsigned port = (argc > 3 ? strtoul(argv[3], NULL, 0) : 22); - bool result = eval(en, host, port); - printf("%s\n", result ? "accept" : "reject"); - } else { - /* - * Just dump the result of parsing the expression. - */ - stdio_sink ss[1]; - stdio_sink_init(ss, stdout); - exprnode_dump(BinarySink_UPCAST(ss), en, expr.ptr); - put_byte(ss, '\n'); - } - - exprnode_free(en); - - return 0; - } else { - /* - * Run our automated tests. - */ - size_t pass = 0, fail = 0; - - for (size_t i = 0; i < lenof(parsetests); i++) { - const struct ParseTest *test = &parsetests[i]; - - ptrlen expr = ptrlen_from_asciz(test->expr); - char *error_msg; - ptrlen error_loc; - ExprNode *en = parse(expr, &error_msg, &error_loc); - - strbuf *output = strbuf_new(); - if (!en) { - put_fmt(output, "ERR:%zu:%zu:%s", - (size_t)((const char *)error_loc.ptr - test->expr), - (size_t)((const char *)ptrlen_end(error_loc) - - test->expr), - error_msg); - sfree(error_msg); - } else { - exprnode_dump(BinarySink_UPCAST(output), en, expr.ptr); - exprnode_free(en); - } - - if (ptrlen_eq_ptrlen(ptrlen_from_strbuf(output), - ptrlen_from_asciz(test->output))) { - pass++; - } else { - fprintf(stderr, "FAIL: parsetests[%zu] @ %s:%d:\n" - " expression: %s\n" - " expected: %s\n" - " actual: %s\n", - i, test->file, test->line, test->expr, - test->output, output->s); - fail++; - } - - strbuf_free(output); - } - - for (size_t i = 0; i < lenof(evaltests); i++) { - const struct EvalTest *test = &evaltests[i]; - - ptrlen expr = ptrlen_from_asciz(test->expr); - char *error_msg; - ptrlen error_loc; - ExprNode *en = parse(expr, &error_msg, &error_loc); - - if (!en) { - fprintf(stderr, "FAIL: evaltests[%zu] @ %s:%d:\n" - " expression: %s\n" - " parse error: %zu:%zu:%s\n", - i, test->file, test->line, test->expr, - (size_t)((const char *)error_loc.ptr - test->expr), - (size_t)((const char *)ptrlen_end(error_loc) - - test->expr), - error_msg); - sfree(error_msg); - } else { - bool output = eval(en, test->host, test->port); - if (output == test->output) { - pass++; - } else { - fprintf(stderr, "FAIL: evaltests[%zu] @ %s:%d:\n" - " expression: %s\n" - " host: %s\n" - " port: %u\n" - " expected: %s\n" - " actual: %s\n", - i, test->file, test->line, test->expr, - test->host, test->port, - test->output ? "accept" : "reject", - output ? "accept" : "reject"); - fail++; - } - exprnode_free(en); - } - } - - fprintf(stderr, "pass %zu fail %zu total %zu\n", - pass, fail, pass+fail); - return fail != 0; - } -} - -#endif // TEST diff --git a/utils/chomp.c b/utils/chomp.c deleted file mode 100644 index 866fc6529..000000000 --- a/utils/chomp.c +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Perl-style 'chomp', for a line we just read with fgetline. - * - * Unlike Perl chomp, however, we're deliberately forgiving of strange - * line-ending conventions. - * - * Also we forgive NULL on input, so you can just write 'line = - * chomp(fgetline(fp));' and not bother checking for NULL until - * afterwards. - */ - -#include - -#include "defs.h" -#include "misc.h" - -char *chomp(char *str) -{ - if (str) { - int len = strlen(str); - while (len > 0 && (str[len-1] == '\r' || str[len-1] == '\n')) - len--; - str[len] = '\0'; - } - return str; -} diff --git a/utils/cmdline_get_passwd_input_state_new.c b/utils/cmdline_get_passwd_input_state_new.c deleted file mode 100644 index cd39bfa11..000000000 --- a/utils/cmdline_get_passwd_input_state_new.c +++ /dev/null @@ -1,9 +0,0 @@ -/* - * A preinitialised cmdline_get_passwd_input_state which makes it easy - * to assign by structure copy. - */ - -#include "putty.h" - -const cmdline_get_passwd_input_state cmdline_get_passwd_input_state_new = - CMDLINE_GET_PASSWD_INPUT_STATE_INIT; diff --git a/utils/conf.c b/utils/conf.c deleted file mode 100644 index 7915ddded..000000000 --- a/utils/conf.c +++ /dev/null @@ -1,577 +0,0 @@ -/* - * 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" - -/* - * 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; -}; - -/* Variant form of struct key which doesn't contain dynamic data, used - * for lookups. */ -struct constkey { - int primary; - union { - int i; - const char *s; - } secondary; -}; - -struct value { - union { - bool boolval; - 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 (conf_key_info[a->primary].subkey_type) { - case CONF_TYPE_INT: - if (a->secondary.i < b->secondary.i) - return -1; - else if (a->secondary.i > b->secondary.i) - return +1; - return 0; - case CONF_TYPE_STR: - return strcmp(a->secondary.s, b->secondary.s); - default: - return 0; - } -} - -static int conf_cmp_constkey(void *av, void *bv) -{ - struct key *a = (struct key *)av; - struct constkey *b = (struct constkey *)bv; - - if (a->primary < b->primary) - return -1; - else if (a->primary > b->primary) - return +1; - switch (conf_key_info[a->primary].subkey_type) { - case CONF_TYPE_INT: - if (a->secondary.i < b->secondary.i) - return -1; - else if (a->secondary.i > b->secondary.i) - return +1; - return 0; - case CONF_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 (conf_key_info[key->primary].subkey_type == CONF_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 (conf_key_info[to->primary].subkey_type) { - case CONF_TYPE_INT: - to->secondary.i = from->secondary.i; - break; - case CONF_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 == CONF_TYPE_STR) - sfree(val->u.stringval); - else if (type == CONF_TYPE_FILENAME) - filename_free(val->u.fileval); - else if (type == CONF_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 CONF_TYPE_BOOL: - to->u.boolval = from->u.boolval; - break; - case CONF_TYPE_INT: - to->u.intval = from->u.intval; - break; - case CONF_TYPE_STR: - to->u.stringval = dupstr(from->u.stringval); - break; - case CONF_TYPE_FILENAME: - to->u.fileval = filename_copy(from->u.fileval); - break; - case CONF_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, conf_key_info[entry->key.primary].value_type); - sfree(entry); -} - -Conf *conf_new(void) -{ - Conf *conf = snew(struct conf_tag); - - conf->tree = newtree234(conf_cmp); - - return conf; -} - -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, - conf_key_info[entry->key.primary].value_type); - add234(newconf->tree, entry2); - } -} - -Conf *conf_copy(Conf *oldconf) -{ - Conf *newconf = conf_new(); - - conf_copy_into(newconf, oldconf); - - return newconf; -} - -bool conf_get_bool(Conf *conf, int primary) -{ - struct key key; - struct conf_entry *entry; - - assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); - assert(conf_key_info[primary].value_type == CONF_TYPE_BOOL); - key.primary = primary; - entry = find234(conf->tree, &key, NULL); - assert(entry); - return entry->value.u.boolval; -} - -int conf_get_int(Conf *conf, int primary) -{ - struct key key; - struct conf_entry *entry; - - assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); - assert(conf_key_info[primary].value_type == CONF_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(conf_key_info[primary].subkey_type == CONF_TYPE_INT); - assert(conf_key_info[primary].value_type == CONF_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(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); - assert(conf_key_info[primary].value_type == CONF_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(conf_key_info[primary].subkey_type == CONF_TYPE_STR); - assert(conf_key_info[primary].value_type == CONF_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 constkey key; - struct conf_entry *entry; - - assert(conf_key_info[primary].subkey_type == CONF_TYPE_STR); - assert(conf_key_info[primary].value_type == CONF_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, conf_cmp_constkey, 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 constkey key; - struct conf_entry *entry; - int index; - - assert(conf_key_info[primary].subkey_type == CONF_TYPE_STR); - assert(conf_key_info[primary].value_type == CONF_TYPE_STR); - key.primary = primary; - key.secondary.s = ""; - entry = findrelpos234(conf->tree, &key, conf_cmp_constkey, - 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(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); - assert(conf_key_info[primary].value_type == CONF_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(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); - assert(conf_key_info[primary].value_type == CONF_TYPE_FONT); - key.primary = primary; - entry = find234(conf->tree, &key, NULL); - assert(entry); - return entry->value.u.fontval; -} - -void conf_set_bool(Conf *conf, int primary, bool value) -{ - struct conf_entry *entry = snew(struct conf_entry); - - assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); - assert(conf_key_info[primary].value_type == CONF_TYPE_BOOL); - entry->key.primary = primary; - entry->value.u.boolval = value; - conf_insert(conf, entry); -} - -void conf_set_int(Conf *conf, int primary, int value) -{ - struct conf_entry *entry = snew(struct conf_entry); - - assert(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); - assert(conf_key_info[primary].value_type == CONF_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(conf_key_info[primary].subkey_type == CONF_TYPE_INT); - assert(conf_key_info[primary].value_type == CONF_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(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); - assert(conf_key_info[primary].value_type == CONF_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(conf_key_info[primary].subkey_type == CONF_TYPE_STR); - assert(conf_key_info[primary].value_type == CONF_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(conf_key_info[primary].subkey_type == CONF_TYPE_STR); - assert(conf_key_info[primary].value_type == CONF_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(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); - assert(conf_key_info[primary].value_type == CONF_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(conf_key_info[primary].subkey_type == CONF_TYPE_NONE); - assert(conf_key_info[primary].value_type == CONF_TYPE_FONT); - entry->key.primary = primary; - entry->value.u.fontval = fontspec_copy(value); - conf_insert(conf, entry); -} - -void conf_serialise(BinarySink *bs, Conf *conf) -{ - int i; - struct conf_entry *entry; - - for (i = 0; (entry = index234(conf->tree, i)) != NULL; i++) { - put_uint32(bs, entry->key.primary); - - switch (conf_key_info[entry->key.primary].subkey_type) { - case CONF_TYPE_INT: - put_uint32(bs, entry->key.secondary.i); - break; - case CONF_TYPE_STR: - put_asciz(bs, entry->key.secondary.s); - break; - } - switch (conf_key_info[entry->key.primary].value_type) { - case CONF_TYPE_BOOL: - put_bool(bs, entry->value.u.boolval); - break; - case CONF_TYPE_INT: - put_uint32(bs, entry->value.u.intval); - break; - case CONF_TYPE_STR: - put_asciz(bs, entry->value.u.stringval); - break; - case CONF_TYPE_FILENAME: - filename_serialise(bs, entry->value.u.fileval); - break; - case CONF_TYPE_FONT: - fontspec_serialise(bs, entry->value.u.fontval); - break; - } - } - - put_uint32(bs, 0xFFFFFFFFU); -} - -bool conf_deserialise(Conf *conf, BinarySource *src) -{ - struct conf_entry *entry; - unsigned primary; - - while (1) { - primary = get_uint32(src); - - if (get_err(src)) - return false; - if (primary == 0xFFFFFFFFU) - return true; - if (primary >= N_CONFIG_OPTIONS) - return false; - - entry = snew(struct conf_entry); - entry->key.primary = primary; - - switch (conf_key_info[entry->key.primary].subkey_type) { - case CONF_TYPE_INT: - entry->key.secondary.i = toint(get_uint32(src)); - break; - case CONF_TYPE_STR: - entry->key.secondary.s = dupstr(get_asciz(src)); - break; - } - - switch (conf_key_info[entry->key.primary].value_type) { - case CONF_TYPE_BOOL: - entry->value.u.boolval = get_bool(src); - break; - case CONF_TYPE_INT: - entry->value.u.intval = toint(get_uint32(src)); - break; - case CONF_TYPE_STR: - entry->value.u.stringval = dupstr(get_asciz(src)); - break; - case CONF_TYPE_FILENAME: - entry->value.u.fileval = filename_deserialise(src); - break; - case CONF_TYPE_FONT: - entry->value.u.fontval = fontspec_deserialise(src); - break; - } - - if (get_err(src)) { - free_entry(entry); - return false; - } - - conf_insert(conf, entry); - } -} diff --git a/utils/conf_data.c b/utils/conf_data.c deleted file mode 100644 index 74bf60cd3..000000000 --- a/utils/conf_data.c +++ /dev/null @@ -1,53 +0,0 @@ -#include "putty.h" - -#define CONF_ENUM(name, ...) \ - static const ConfSaveEnumValue conf_enum_values_##name[] = { \ - __VA_ARGS__ \ - }; const ConfSaveEnumType conf_enum_##name = { \ - .values = conf_enum_values_##name, \ - .nvalues = lenof(conf_enum_values_##name), \ - }; - -#define VALUE(eval, sval) { eval, sval, false } -#define VALUE_OBSOLETE(eval, sval) { eval, sval, true } - -#include "conf-enums.h" - -bool conf_enum_map_to_storage(const ConfSaveEnumType *etype, - int confval, int *storageval_out) -{ - for (size_t i = 0; i < etype->nvalues; i++) - if (!etype->values[i].obsolete && - etype->values[i].confval == confval) { - *storageval_out = etype->values[i].storageval; - return true; - } - return false; -} - -bool conf_enum_map_from_storage(const ConfSaveEnumType *etype, - int storageval, int *confval_out) -{ - for (size_t i = 0; i < etype->nvalues; i++) - if (etype->values[i].storageval == storageval) { - *confval_out = etype->values[i].confval; - return true; - } - return false; -} - -#define CONF_OPTION(id, ...) { __VA_ARGS__ }, -#define VALUE_TYPE(x) .value_type = CONF_TYPE_ ## x -#define SUBKEY_TYPE(x) .subkey_type = CONF_TYPE_ ## x -#define DEFAULT_INT(x) .default_value.ival = x -#define DEFAULT_STR(x) .default_value.sval = x -#define DEFAULT_BOOL(x) .default_value.bval = x -#define SAVE_KEYWORD(x) .save_keyword = x -#define STORAGE_ENUM(x) .storage_enum = &conf_enum_ ## x -#define SAVE_CUSTOM .save_custom = true -#define LOAD_CUSTOM .load_custom = true -#define NOT_SAVED .not_saved = true - -const ConfKeyInfo conf_key_info[] = { - #include "conf.h" -}; diff --git a/utils/conf_dest.c b/utils/conf_dest.c deleted file mode 100644 index 13e4ce655..000000000 --- a/utils/conf_dest.c +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Decide whether the 'host name' or 'serial line' field of a Conf is - * important, based on which protocol it has selected. - */ - -#include "putty.h" - -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); -} - diff --git a/utils/conf_launchable.c b/utils/conf_launchable.c deleted file mode 100644 index 904ade61e..000000000 --- a/utils/conf_launchable.c +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Determine whether or not a Conf represents a session which can - * sensibly be launched right now. - */ - -#include "putty.h" - -bool 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; -} diff --git a/utils/ctrlparse.c b/utils/ctrlparse.c deleted file mode 100644 index 86f879021..000000000 --- a/utils/ctrlparse.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 - */ - -#include - -#include "defs.h" -#include "misc.h" - -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; -} diff --git a/utils/ctrlset_normalise.c b/utils/ctrlset_normalise.c deleted file mode 100644 index 3d922ebb4..000000000 --- a/utils/ctrlset_normalise.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Helper function from the dialog.h mechanism. - */ - -#include "putty.h" -#include "misc.h" -#include "dialog.h" - -void ctrlset_normalise_aligns(struct controlset *s) -{ - /* - * The algorithm in here is quadratic time. Never on very much data, but - * even so, let's avoid bothering to use it where possible. In most - * controlsets, there's no use of align_next_to in any case, so we have - * nothing to do. - */ - for (size_t j = 0; j < s->ncontrols; j++) - if (s->ctrls[j]->align_next_to) - goto must_do_something; - /* If we fell out of this loop, there's nothing to do here */ - return; - must_do_something:; - - size_t *idx = snewn(s->ncontrols, size_t); - - /* - * Follow align_next_to links to identify, for each control, the least - * index within this controlset of things it's linked to. That way, - * controls with the same idx[j] will be in the same alignment class. - */ - for (size_t j = 0; j < s->ncontrols; j++) { - dlgcontrol *c = s->ctrls[j]; - idx[j] = j; - if (c->align_next_to) { - for (size_t k = 0; k < j; k++) { - if (s->ctrls[k] == c->align_next_to) { - idx[j] = idx[k]; - break; - } - } - } - } - - /* - * Having done that, re-link each control to the most recent one in its - * class, so that the links form a backward linked list. - */ - for (size_t j = 0; j < s->ncontrols; j++) { - dlgcontrol *c = s->ctrls[j]; - c->align_next_to = NULL; - for (size_t k = j; k-- > 0 ;) { - if (idx[k] == idx[j]) { - c->align_next_to = s->ctrls[k]; - break; - } - } - } - - sfree(idx); -} diff --git a/utils/debug.c b/utils/debug.c deleted file mode 100644 index 79e437a2e..000000000 --- a/utils/debug.c +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Debugging routines used by the debug() macros, at least if you - * compiled with -DDEBUG (aka the PUTTY_DEBUG cmake option) so that - * those macros don't optimise down to nothing. - */ - -#include "defs.h" -#include "misc.h" -#include "utils/utils.h" - -void debug_printf(const 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(const void *buf, int len, bool L) -{ - int i; - const unsigned char *p = buf; - char foo[17]; - if (L) { - int delta; - debug_printf("\t%d (0x%x) bytes:\n", len, len); - delta = 15 & (uintptr_t)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%2.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); - } -} diff --git a/utils/decode_utf8.c b/utils/decode_utf8.c deleted file mode 100644 index 38b2001ae..000000000 --- a/utils/decode_utf8.c +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Decode a single UTF-8 character. - */ - -#include "putty.h" -#include "misc.h" - -unsigned decode_utf8(BinarySource *src, DecodeUTF8Failure *err) -{ - /* Permit user to pass NULL as the err pointer */ - DecodeUTF8Failure dummy; - if (!err) err = &dummy; - - /* If the source has no byte available, this will return 0, which - * we'll return immediately and is a reasonable error return anyway */ - unsigned char c = get_byte(src); - - /* One-byte cases. */ - if (c < 0x80) { - *err = DUTF8_SUCCESS; - return c; - } else if (c < 0xC0) { - *err = DUTF8_SPURIOUS_CONTINUATION; - return 0xFFFD; - } - - unsigned long wc, min; - size_t ncont; - if (c < 0xE0) { - wc = c & 0x1F; ncont = 1; min = 0x80; - } else if (c < 0xF0) { - wc = c & 0x0F; ncont = 2; min = 0x800; - } else if (c < 0xF8) { - wc = c & 0x07; ncont = 3; min = 0x10000; - } else if (c < 0xFC) { - wc = c & 0x03; ncont = 4; min = 0x200000; - } else if (c < 0xFE) { - wc = c & 0x01; ncont = 5; min = 0x4000000; - } else { - *err = DUTF8_ILLEGAL_BYTE; /* FE or FF */ - return 0xFFFD; - } - - while (ncont-- > 0) { - if (!get_avail(src)) { - *err = DUTF8_E_OUT_OF_DATA; - return 0xFFFD; - } - unsigned char cont = get_byte(src); - if (!(0x80 <= cont && cont < 0xC0)) { - BinarySource_REWIND_TO(src, src->pos - 1); - *err = DUTF8_TRUNCATED_SEQUENCE; - return 0xFFFD; - } - - wc = (wc << 6) | (cont & 0x3F); - } - - if (wc < min) { - *err = DUTF8_OVERLONG_ENCODING; - return 0xFFFD; - } - if (0xD800 <= wc && wc < 0xE000) { - *err = DUTF8_ENCODED_SURROGATE; - return 0xFFFD; - } - if (wc > 0x10FFFF) { - *err = DUTF8_CODE_POINT_TOO_BIG; - return 0xFFFD; /* outside Unicode range */ - } - *err = DUTF8_SUCCESS; - return wc; -} - -const char *const decode_utf8_error_strings[DUTF8_N_FAILURE_CODES] = { - #define MSG_ENTRY(sym, string) string, - DECODE_UTF8_FAILURE_LIST(MSG_ENTRY) - #undef MSG_ENTRY -}; - -#ifdef TEST - -#include - -void out_of_memory(void) -{ - fprintf(stderr, "out of memory!\n"); - exit(2); -} - -static const char *const decode_utf8_error_syms[DUTF8_N_FAILURE_CODES] = { - #define SYM_ENTRY(sym, string) #sym, - DECODE_UTF8_FAILURE_LIST(SYM_ENTRY) - #undef SYM_ENTRY -}; - -bool dotest(const char *file, int line, const char *input, size_t ninput, - const unsigned long *chars, size_t nchars) -{ - BinarySource src[1]; - BinarySource_BARE_INIT(src, input, ninput); - size_t noutput = 0; - - printf("%s:%d: test start\n", file, line); - - while (get_avail(src)) { - size_t before = src->pos; - DecodeUTF8Failure err; - unsigned long wc = decode_utf8(src, &err); - - printf("%s:%d in+%"SIZEu" out+%"SIZEu":", file, line, before, noutput); - while (before < src->pos) - printf(" %02x", (unsigned)(unsigned char)(input[before++])); - printf(" -> U-%08lx %s\n", wc, decode_utf8_error_syms[err]); - - if (noutput >= nchars) { - printf("%s:%d: FAIL: expected no further output\n", file, line); - return false; - } - - if (chars[noutput] != wc) { - printf("%s:%d: FAIL: expected U-%08lx\n", - file, line, chars[noutput]); - return false; - } - - noutput++; - - DecodeUTF8Failure expected_err; - if (wc == 0xFFFD) { - /* In the 'chars' array, any occurrence of 0xFFFD is followed - * by the expected error code */ - assert(noutput < nchars && "bad test data"); - expected_err = chars[noutput++]; - } else { - /* Expect success status to go with any non-FFFD character */ - expected_err = DUTF8_SUCCESS; - } - if (err != expected_err) { - printf("%s:%d: FAIL: expected %s\n", file, line, - decode_utf8_error_syms[expected_err]); - return false; - } - } - - if (noutput < nchars) { - printf("%s:%d: FAIL: expected further output\n", file, line); - return false; - } - - printf("%s:%d: pass\n", file, line); - return true; -} - -#define DOTEST(input, ...) do { \ - static const unsigned long chars[] = { __VA_ARGS__ }; \ - ntest++; \ - if (dotest(__FILE__, __LINE__, input, sizeof(input)-1, \ - chars, lenof(chars))) \ - npass++; \ - } while (0) - -int main(void) -{ - int ntest = 0, npass = 0; - - DOTEST("\xCE\xBA\xE1\xBD\xB9\xCF\x83\xCE\xBC\xCE\xB5", - 0x03BA, 0x1F79, 0x03C3, 0x03BC, 0x03B5); - - /* First sequence of each length */ - DOTEST("\x00", 0x0000); - DOTEST("\xC2\x80", 0x0080); - DOTEST("\xE0\xA0\x80", 0x0800); - DOTEST("\xF0\x90\x80\x80", 0x00010000); - DOTEST("\xF8\x88\x80\x80\x80", - 0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x00200000 */ - DOTEST("\xFC\x84\x80\x80\x80\x80", - 0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x04000000 */ - - /* Last sequence of each length */ - DOTEST("\x7F", 0x007F); - DOTEST("\xDF\xBF", 0x07FF); - DOTEST("\xEF\xBF\xBF", 0xFFFF); - DOTEST("\xF7\xBF\xBF\xBF", - 0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x001FFFFF */ - DOTEST("\xFB\xBF\xBF\xBF\xBF", - 0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x03FFFFFF */ - DOTEST("\xFD\xBF\xBF\xBF\xBF\xBF", - 0xFFFD, DUTF8_CODE_POINT_TOO_BIG); /* would be 0x7FFFFFFF */ - - /* Endpoints of the surrogate range */ - DOTEST("\xED\x9F\xBF", 0xD7FF); - DOTEST("\xED\xA0\x80", 0xFFFD, DUTF8_ENCODED_SURROGATE); /* 0xD800 */ - DOTEST("\xED\xBF\xBF", 0xFFFD, DUTF8_ENCODED_SURROGATE); /* 0xDFFF */ - DOTEST("\xEE\x80\x80", 0xE000); - - /* REPLACEMENT CHARACTER itself */ - DOTEST("\xEF\xBF\xBD", 0xFFFD, DUTF8_SUCCESS); /* FFFD but no error! */ - - /* Endpoints of the legal Unicode range */ - DOTEST("\xF4\x8F\xBF\xBF", 0x0010FFFF); - DOTEST("\xF4\x90\x80\x80", 0xFFFD, - DUTF8_CODE_POINT_TOO_BIG); /* would be 0x00110000 */ - - /* Spurious continuation bytes, each shown as a separate failure */ - DOTEST("\x80 \x81\x82 \xBD\xBE\xBF", - 0xFFFD, DUTF8_SPURIOUS_CONTINUATION, - 0x0020, - 0xFFFD, DUTF8_SPURIOUS_CONTINUATION, - 0xFFFD, DUTF8_SPURIOUS_CONTINUATION, - 0x0020, - 0xFFFD, DUTF8_SPURIOUS_CONTINUATION, - 0xFFFD, DUTF8_SPURIOUS_CONTINUATION, - 0xFFFD, DUTF8_SPURIOUS_CONTINUATION); - - /* Truncated sequences, each shown as just one failure. The last - * one gets a different error code because the sequence is - * interrupted by the end of the string instead of another - * character, so that if the string were a prefix of a longer - * chunk of data then that would not _necessarily_ indicate an - * error */ - DOTEST("\xC2\xE0\xA0\xF0\x90\x80\xF8\x88\x80\x80\xFC\x84\x80\x80\x80", - 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, - 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, - 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, - 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, - 0xFFFD, DUTF8_E_OUT_OF_DATA); - DOTEST("\xC2 \xE0\xA0 \xF0\x90\x80 \xF8\x88\x80\x80 \xFC\x84\x80\x80\x80", - 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, - 0x0020, - 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, - 0x0020, - 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, - 0x0020, - 0xFFFD, DUTF8_TRUNCATED_SEQUENCE, - 0x0020, - 0xFFFD, DUTF8_E_OUT_OF_DATA); - - /* Illegal bytes */ - DOTEST("\xFE\xFF", 0xFFFD, DUTF8_ILLEGAL_BYTE, 0xFFFD, DUTF8_ILLEGAL_BYTE); - - /* Overlong sequences */ - DOTEST("\xC1\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING); - DOTEST("\xE0\x9F\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING); - DOTEST("\xF0\x8F\xBF\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING); - DOTEST("\xF8\x87\xBF\xBF\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING); - DOTEST("\xFC\x83\xBF\xBF\xBF\xBF", 0xFFFD, DUTF8_OVERLONG_ENCODING); - - DOTEST("\xC0\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING); - DOTEST("\xE0\x80\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING); - DOTEST("\xF0\x80\x80\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING); - DOTEST("\xF8\x80\x80\x80\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING); - DOTEST("\xFC\x80\x80\x80\x80\x80", 0xFFFD, DUTF8_OVERLONG_ENCODING); - - printf("%d tests %d passed", ntest, npass); - if (npass < ntest) { - printf(" %d FAILED\n", ntest-npass); - return 1; - } else { - printf("\n"); - return 0; - } -} -#endif diff --git a/utils/decode_utf8_to_wchar.c b/utils/decode_utf8_to_wchar.c deleted file mode 100644 index b21cbb5f5..000000000 --- a/utils/decode_utf8_to_wchar.c +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Decode a single UTF-8 character to the platform's local wchar_t. - */ - -#include "putty.h" -#include "misc.h" - -size_t decode_utf8_to_wchar(BinarySource *src, wchar_t *out, - DecodeUTF8Failure *err) -{ - size_t outlen = 0; - unsigned wc = decode_utf8(src, err); - if (sizeof(wchar_t) > 2 || wc < 0x10000) { - out[outlen++] = wc; - } else { - unsigned wcoff = wc - 0x10000; - out[outlen++] = 0xD800 | (0x3FF & (wcoff >> 10)); - out[outlen++] = 0xDC00 | (0x3FF & wcoff); - } - return outlen; -} diff --git a/utils/decode_utf8_to_wide_string.c b/utils/decode_utf8_to_wide_string.c deleted file mode 100644 index 1ab9a8de9..000000000 --- a/utils/decode_utf8_to_wide_string.c +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Decode a string of UTF-8 to a wchar_t string. - */ - -#include "misc.h" - -wchar_t *decode_utf8_to_wide_string(const char *s) -{ - wchar_t *ws = NULL; - size_t wlen = 0, wsize = 0; - - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, ptrlen_from_asciz(s)); - - while (get_avail(src) > 0) { - /* - * decode_utf8_to_wchar might emit up to 2 wchar_t if wchar_t - * is 16 bits (because of UTF-16 surrogates), but will emit at - * most one if wchar_t is 32-bit - */ - sgrowarrayn(ws, wsize, wlen, 1 + (sizeof(wchar_t) < 4)); - - /* We ignore 'err': if it is set, then the character decode - * function will have emitted U+FFFD REPLACEMENT CHARACTER, - * which is what we'd have done in response anyway. */ - DecodeUTF8Failure err; - wlen += decode_utf8_to_wchar(src, ws + wlen, &err); - } - - /* Reallocate to the final size and append the trailing NUL */ - ws = sresize(ws, wlen + 1, wchar_t); - ws[wlen] = L'\0'; - - return ws; -} diff --git a/utils/default_description.c b/utils/default_description.c deleted file mode 100644 index e0695ee6f..000000000 --- a/utils/default_description.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Construct a description string for a backend to use as - * backend_description(), or a plug as plug_description(). - * - * For some backends this will be overridden: e.g. SSH prefers to - * think in terms of _logical_ host names (i.e. the one associated - * with the host key) rather than the physical details of where you're - * connecting to. But this default is good for simpler backends. - */ - -#include "putty.h" - -char *default_description(const BackendVtable *backvt, - const char *host, int port) -{ - const char *be_name = backvt->displayname_lc; - - if (backvt->default_port && port == backvt->default_port) - return dupprintf("%s connection to %s", be_name, host); - else - return dupprintf("%s connection to %s port %d", be_name, host, port); -} diff --git a/utils/dup_mb_to_wc.c b/utils/dup_mb_to_wc.c deleted file mode 100644 index f6c489753..000000000 --- a/utils/dup_mb_to_wc.c +++ /dev/null @@ -1,34 +0,0 @@ -/* - * dup_mb_to_wc: memory-allocating wrapper on mb_to_wc. - * - * Also dup_mb_to_wc_c: same but you already know the length of the - * string, and you get told the length of the returned wide string. - * (But it's still NUL-terminated, for convenience.) - */ - -#include "putty.h" -#include "misc.h" - -wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, - size_t inlen, size_t *outlen_p) -{ - assert(inlen <= INT_MAX); - size_t mult; - for (mult = 1 ;; mult++) { - wchar_t *ret = snewn(mult*inlen + 2, wchar_t); - size_t outlen = mb_to_wc(codepage, flags, string, inlen, ret, - mult*inlen + 1); - if (outlen < mult*inlen+1) { - if (outlen_p) - *outlen_p = outlen; - 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), NULL); -} diff --git a/utils/dup_wc_to_mb.c b/utils/dup_wc_to_mb.c deleted file mode 100644 index 4a55803c6..000000000 --- a/utils/dup_wc_to_mb.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * dup_wc_to_mb: memory-allocating wrapper on wc_to_mb. - * - * Also dup_wc_to_mb_c: same but you already know the length of the - * wide string, and you get told the length of the returned string. - * (But it's still NUL-terminated, for convenience.). - */ - -#include - -#include "putty.h" -#include "misc.h" - -char *dup_wc_to_mb_c(int codepage, int flags, const wchar_t *string, - size_t inlen, const char *defchr, size_t *outlen_p) -{ - assert(inlen <= INT_MAX); - - size_t outsize = inlen+1; - char *out = snewn(outsize, char); - - while (true) { - size_t outlen = wc_to_mb(codepage, flags, string, inlen, out, outsize, - defchr); - /* We can only be sure we've consumed the whole input if the - * output is not within a multibyte-character-length of the - * end of the buffer! */ - if (outlen < outsize && outsize - outlen > MB_LEN_MAX) { - if (outlen_p) - *outlen_p = outlen; - out[outlen] = '\0'; - return out; - } - - sgrowarray(out, outsize, outsize); - } -} - -char *dup_wc_to_mb(int codepage, int flags, const wchar_t *string, - const char *defchr) -{ - return dup_wc_to_mb_c(codepage, flags, string, wcslen(string), - defchr, NULL); -} diff --git a/utils/dupcat.c b/utils/dupcat.c deleted file mode 100644 index ddd6599e2..000000000 --- a/utils/dupcat.c +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Implementation function behind dupcat() in misc.h. - * - * This function is called with an arbitrary number of 'const char *' - * parameters, of which the last one is a null pointer. The wrapper - * macro puts on the null pointer itself, so normally callers don't - * have to. - */ - -#include -#include - -#include "defs.h" -#include "misc.h" - -char *dupcat_fn(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; -} - diff --git a/utils/dupprintf.c b/utils/dupprintf.c deleted file mode 100644 index aa9f330b9..000000000 --- a/utils/dupprintf.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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. - */ - -#include - -#include "defs.h" -#include "misc.h" -#include "utils/utils.h" - -/* Work around lack of va_copy in old MSC */ -#if defined _MSC_VER && !defined va_copy -#define va_copy(a, b) TYPECHECK( \ - (va_list *)0 == &(a) && (va_list *)0 == &(b), \ - memcpy(&a, &b, sizeof(va_list))) -#endif - -/* Also lack of vsnprintf before VS2015 */ -#if defined _WINDOWS && \ - !defined __MINGW32__ && \ - !defined __WINE__ && \ - _MSC_VER < 1900 -#define vsnprintf _vsnprintf -#endif - -char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr, - const char *fmt, va_list ap) -{ - size_t size = *sizeptr; - sgrowarrayn_nm(buf, size, oldlen, 512); - - while (1) { - va_list aq; - va_copy(aq, ap); - int len = vsnprintf(buf + oldlen, size - oldlen, fmt, aq); - va_end(aq); - - if (len >= 0 && len < size) { - /* This is the C99-specified criterion for snprintf to have - * been completely successful. */ - *sizeptr = size; - return buf; - } else if (len > 0) { - /* This is the C99 error condition: the returned length is - * the required buffer size not counting the NUL. */ - sgrowarrayn_nm(buf, size, oldlen + 1, len); - } 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. */ - sgrowarray_nm(buf, size, size); - } - } -} - -char *dupvprintf(const char *fmt, va_list ap) -{ - size_t size = 0; - return dupvprintf_inner(NULL, 0, &size, fmt, ap); -} -char *dupprintf(const char *fmt, ...) -{ - char *ret; - va_list ap; - va_start(ap, fmt); - ret = dupvprintf(fmt, ap); - va_end(ap); - return ret; -} diff --git a/utils/dupstr.c b/utils/dupstr.c deleted file mode 100644 index fd79583f0..000000000 --- a/utils/dupstr.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Allocate a duplicate of an ordinary C NUL-terminated string. - */ - -#include - -#include "defs.h" -#include "misc.h" - -char *dupstr(const char *s) -{ - char *p = NULL; - if (s) { - int len = strlen(s); - p = snewn(len + 1, char); - strcpy(p, s); - } - return p; -} diff --git a/utils/dupwcs.c b/utils/dupwcs.c deleted file mode 100644 index 11fba3303..000000000 --- a/utils/dupwcs.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Allocate a duplicate of a NUL-terminated wchar_t string. - */ - -#include - -#include "defs.h" -#include "misc.h" - -wchar_t *dupwcs(const wchar_t *s) -{ - wchar_t *p = NULL; - if (s) { - int len = wcslen(s); - p = snewn(len + 1, wchar_t); - wcscpy(p, s); - } - return p; -} diff --git a/utils/encode_utf8.c b/utils/encode_utf8.c deleted file mode 100644 index d24f09515..000000000 --- a/utils/encode_utf8.c +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Encode a single code point as UTF-8. - */ - -#include "defs.h" -#include "misc.h" - -void BinarySink_put_utf8_char(BinarySink *output, unsigned ch) -{ - if (ch < 0x80) { - put_byte(output, ch); - } else if (ch < 0x800) { - put_byte(output, 0xC0 | (ch >> 6)); - put_byte(output, 0x80 | (ch & 0x3F)); - } else if (ch < 0x10000) { - put_byte(output, 0xE0 | (ch >> 12)); - put_byte(output, 0x80 | ((ch >> 6) & 0x3F)); - put_byte(output, 0x80 | (ch & 0x3F)); - } else { - assert(ch <= 0x10FFFF); - put_byte(output, 0xF0 | (ch >> 18)); - put_byte(output, 0x80 | ((ch >> 12) & 0x3F)); - put_byte(output, 0x80 | ((ch >> 6) & 0x3F)); - put_byte(output, 0x80 | (ch & 0x3F)); - } -} diff --git a/utils/encode_wide_string_as_utf8.c b/utils/encode_wide_string_as_utf8.c deleted file mode 100644 index f57828886..000000000 --- a/utils/encode_wide_string_as_utf8.c +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Encode a string of wchar_t as UTF-8. - */ - -#include "putty.h" -#include "misc.h" - -char *encode_wide_string_as_utf8(const wchar_t *ws) -{ - strbuf *sb = strbuf_new(); - while (*ws) { - unsigned long ch = *ws++; - if (sizeof(wchar_t) == 2 && IS_HIGH_SURROGATE(ch) && - IS_LOW_SURROGATE(*ws)) { - ch = FROM_SURROGATES(ch, *ws); - ws++; - } else if (IS_SURROGATE(ch)) { - ch = 0xfffd; /* illegal UTF-16 -> REPLACEMENT CHARACTER */ - } - put_utf8_char(sb, ch); - } - return strbuf_to_str(sb); -} diff --git a/utils/fgetline.c b/utils/fgetline.c deleted file mode 100644 index 2bb580f15..000000000 --- a/utils/fgetline.c +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Read an entire line of text from a file. Return a buffer - * malloced to be as big as necessary (caller must free). - */ - -#include "defs.h" -#include "misc.h" - -char *fgetline(FILE *fp) -{ - char *ret = snewn(512, char); - size_t 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 */ - sgrowarrayn_nm(ret, size, len, 512); - } - if (len == 0) { /* first fgets returned NULL */ - sfree(ret); - return NULL; - } - ret[len] = '\0'; - return ret; -} diff --git a/utils/host_ca_new_free.c b/utils/host_ca_new_free.c deleted file mode 100644 index 4c91c3200..000000000 --- a/utils/host_ca_new_free.c +++ /dev/null @@ -1,22 +0,0 @@ -#include "defs.h" -#include "misc.h" -#include "storage.h" - -host_ca *host_ca_new(void) -{ - host_ca *hca = snew(host_ca); - memset(hca, 0, sizeof(*hca)); - hca->opts.permit_rsa_sha1 = false; - hca->opts.permit_rsa_sha256 = true; - hca->opts.permit_rsa_sha512 = true; - return hca; -} - -void host_ca_free(host_ca *hca) -{ - sfree(hca->name); - sfree(hca->validity_expression); - if (hca->ca_public_key) - strbuf_free(hca->ca_public_key); - sfree(hca); -} diff --git a/utils/host_strchr.c b/utils/host_strchr.c deleted file mode 100644 index 363a915e6..000000000 --- a/utils/host_strchr.c +++ /dev/null @@ -1,18 +0,0 @@ -/* - * strchr-like wrapper around host_strchr_internal. - */ - -#include -#include - -#include "defs.h" -#include "misc.h" -#include "utils/utils.h" - -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); -} diff --git a/utils/host_strchr_internal.c b/utils/host_strchr_internal.c deleted file mode 100644 index f995b53de..000000000 --- a/utils/host_strchr_internal.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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. - * - * This internal function provides an API that's a bit like strchr (in - * that it returns a pointer to the character it found, or NULL), and - * a bit like strcspn (in that you can give it a set of characters to - * look for, not just one). Also it has an option to return the first - * or last character it finds. Other functions in the utils directory - * provide wrappers on it with APIs more like familiar - * functions. - */ - -#include -#include - -#include "defs.h" -#include "misc.h" -#include "utils/utils.h" - -const char *host_strchr_internal(const char *s, const char *set, bool 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++; - } -} - -#ifdef TEST - -int main(void) -{ - int passes = 0, fails = 0; - -#define TEST1(func, string, arg2, suffix, result) do \ - { \ - const char *str = string; \ - unsigned ret = func(str, arg2) suffix; \ - if (ret == result) { \ - passes++; \ - } else { \ - printf("fail: %s(%s,%s)%s = %u, expected %u\n", \ - #func, #string, #arg2, #suffix, ret, \ - (unsigned)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; -} - -#endif /* TEST */ diff --git a/utils/host_strcspn.c b/utils/host_strcspn.c deleted file mode 100644 index 958f47f84..000000000 --- a/utils/host_strcspn.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * strcspn-like wrapper around host_strchr_internal. - */ - -#include -#include - -#include "defs.h" -#include "misc.h" -#include "utils/utils.h" - -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); -} diff --git a/utils/host_strduptrim.c b/utils/host_strduptrim.c deleted file mode 100644 index 94492e641..000000000 --- a/utils/host_strduptrim.c +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Trim square brackets off the outside of an IPv6 address literal. - * Leave all other strings unchanged. Returns a fresh dynamically - * allocated string. - */ - -#include -#include - -#include "defs.h" -#include "misc.h" - -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 == '%') { - /* - * This delimiter character introduces an RFC 4007 scope - * id suffix (e.g. suffixing the address literal with - * %eth1 or %2 or some such). There's no syntax - * specification for the scope id, so just accept anything - * except the closing ]. - */ - p += strcspn(p, "]"); - } - if (*p == ']' && !p[1] && colons > 1) { - /* - * This looks like an IPv6 address literal (hex digits and - * at least two colons, plus optional scope id, 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); -} diff --git a/utils/host_strrchr.c b/utils/host_strrchr.c deleted file mode 100644 index a1dd4c940..000000000 --- a/utils/host_strrchr.c +++ /dev/null @@ -1,18 +0,0 @@ -/* - * strchr-like wrapper around host_strchr_internal. - */ - -#include -#include - -#include "defs.h" -#include "misc.h" -#include "utils/utils.h" - -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); -} diff --git a/utils/key_components.c b/utils/key_components.c deleted file mode 100644 index b072ff51f..000000000 --- a/utils/key_components.c +++ /dev/null @@ -1,93 +0,0 @@ -#include "ssh.h" -#include "mpint.h" - -key_components *key_components_new(void) -{ - key_components *kc = snew(key_components); - kc->ncomponents = 0; - kc->componentsize = 0; - kc->components = NULL; - return kc; -} - -static void key_components_add_str(key_components *kc, const char *name, - KeyComponentType type, ptrlen data) -{ - sgrowarray(kc->components, kc->componentsize, kc->ncomponents); - size_t n = kc->ncomponents++; - kc->components[n].name = dupstr(name); - kc->components[n].type = type; - kc->components[n].str = strbuf_dup_nm(data); -} - -void key_components_add_text(key_components *kc, - const char *name, const char *value) -{ - key_components_add_str(kc, name, KCT_TEXT, ptrlen_from_asciz(value)); -} - -void key_components_add_text_pl(key_components *kc, - const char *name, ptrlen value) -{ - key_components_add_str(kc, name, KCT_TEXT, value); -} - -void key_components_add_binary(key_components *kc, - const char *name, ptrlen value) -{ - key_components_add_str(kc, name, KCT_BINARY, value); -} - -void key_components_add_mp(key_components *kc, - const char *name, mp_int *value) -{ - sgrowarray(kc->components, kc->componentsize, kc->ncomponents); - size_t n = kc->ncomponents++; - kc->components[n].name = dupstr(name); - kc->components[n].type = KCT_MPINT; - kc->components[n].mp = mp_copy(value); -} - -void key_components_add_uint(key_components *kc, - const char *name, uintmax_t value) -{ - mp_int *mpvalue = mp_from_integer(value); - key_components_add_mp(kc, name, mpvalue); - mp_free(mpvalue); -} - -void key_components_add_copy(key_components *kc, - const char *name, const key_component *value) -{ - switch (value->type) { - case KCT_TEXT: - case KCT_BINARY: - key_components_add_str(kc, name, value->type, - ptrlen_from_strbuf(value->str)); - break; - case KCT_MPINT: - key_components_add_mp(kc, name, value->mp); - break; - } -} - -void key_components_free(key_components *kc) -{ - for (size_t i = 0; i < kc->ncomponents; i++) { - key_component *comp = &kc->components[i]; - sfree(comp->name); - switch (comp->type) { - case KCT_MPINT: - mp_free(comp->mp); - break; - case KCT_TEXT: - case KCT_BINARY: - strbuf_free(comp->str); - break; - default: - unreachable("bad key component type"); - } - } - sfree(kc->components); - sfree(kc); -} diff --git a/utils/log_proxy_stderr.c b/utils/log_proxy_stderr.c deleted file mode 100644 index 2a84173af..000000000 --- a/utils/log_proxy_stderr.c +++ /dev/null @@ -1,103 +0,0 @@ -#include -#include - -#include "putty.h" -#include "network.h" - -void psb_init(ProxyStderrBuf *psb) -{ - psb->size = 0; - psb->prefix = "proxy"; -} - -void psb_set_prefix(ProxyStderrBuf *psb, const char *prefix) -{ - psb->prefix = prefix; -} - -void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb, - const void *vdata, size_t len) -{ - const char *data = (const char *)vdata; - - /* - * This helper function allows us to collect the data written to a - * local proxy command's standard error in whatever size chunks we - * happen to get from its pipe, and whenever we have a complete - * line, we pass it to plug_log. - * - * (We also do this when the buffer in psb fills up, to avoid just - * allocating more and more memory forever, and also to keep Event - * Log lines reasonably bounded in size.) - * - * Prerequisites: a plug to log to, and a ProxyStderrBuf stored - * somewhere to collect any not-yet-output partial line. - */ - - while (len > 0) { - /* - * Copy as much data into psb->buf as will fit. - */ - assert(psb->size < lenof(psb->buf)); - size_t to_consume = lenof(psb->buf) - psb->size; - if (to_consume > len) - to_consume = len; - memcpy(psb->buf + psb->size, data, to_consume); - data += to_consume; - len -= to_consume; - psb->size += to_consume; - - /* - * Output any full lines in psb->buf. - */ - size_t pos = 0; - while (pos < psb->size) { - char *nlpos = memchr(psb->buf + pos, '\n', psb->size - pos); - if (!nlpos) - break; - - /* - * Found a newline in the buffer, so we can output a line. - */ - size_t endpos = nlpos - psb->buf; - while (endpos > pos && (psb->buf[endpos-1] == '\n' || - psb->buf[endpos-1] == '\r')) - endpos--; - char *msg = dupprintf( - "%s: %.*s", psb->prefix, (int)(endpos - pos), psb->buf + pos); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); - sfree(msg); - - pos = nlpos - psb->buf + 1; - assert(pos <= psb->size); - } - - /* - * If the buffer is completely full and we didn't output - * anything, then output the whole thing, flagging it as a - * truncated line. - */ - if (pos == 0 && psb->size == lenof(psb->buf)) { - char *msg = dupprintf( - "%s (partial line): %.*s", psb->prefix, (int)psb->size, - psb->buf); - plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); - sfree(msg); - - pos = psb->size = 0; - } - - /* - * Now move any remaining data up to the front of the buffer. - */ - size_t newsize = psb->size - pos; - if (newsize) - memmove(psb->buf, psb->buf + pos, newsize); - psb->size = newsize; - - /* - * And loop round again if there's more data to be read from - * our input. - */ - } -} diff --git a/utils/logeventf.c b/utils/logeventf.c deleted file mode 100644 index ba1626120..000000000 --- a/utils/logeventf.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Helpful wrapper functions around the raw logevent(). - * - * This source file lives in 'utils' because it's conceptually a - * convenience utility rather than core functionality. But it can't - * live in the utils _library_, because then it might refer to - * logevent() in an earlier library after Unix ld had already finished - * searching that library, and cause a link failure. So it must live - * alongside logging.c. - */ - -#include "putty.h" - -void logevent_and_free(LogContext *ctx, char *event) -{ - logevent(ctx, event); - sfree(event); -} - -void logeventvf(LogContext *ctx, const char *fmt, va_list ap) -{ - logevent_and_free(ctx, dupvprintf(fmt, ap)); -} - -void logeventf(LogContext *ctx, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - logeventvf(ctx, fmt, ap); - va_end(ap); -} diff --git a/utils/ltime.c b/utils/ltime.c deleted file mode 100644 index 75b89535d..000000000 --- a/utils/ltime.c +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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/utils/make_spr_sw_abort_static.c b/utils/make_spr_sw_abort_static.c deleted file mode 100644 index f9eac59fc..000000000 --- a/utils/make_spr_sw_abort_static.c +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Constructor function for a SeatPromptResult of the 'software abort' - * category, whose error message is in the simplest possible form of a - * static string constant. - */ - -#include "putty.h" - -static void spr_static_errfn(SeatPromptResult spr, BinarySink *bs) -{ - put_dataz(bs, spr.errdata_lit); -} - -SeatPromptResult make_spr_sw_abort_static(const char *str) -{ - SeatPromptResult spr; - spr.kind = SPRK_SW_ABORT; - spr.errfn = spr_static_errfn; - spr.errdata_lit = str; - return spr; -} diff --git a/utils/marshal.c b/utils/marshal.c deleted file mode 100644 index ec6a58068..000000000 --- a/utils/marshal.c +++ /dev/null @@ -1,358 +0,0 @@ -#include -#include -#include -#include - -#include "marshal.h" -#include "misc.h" - -void BinarySink_put_data(BinarySink *bs, const void *data, size_t len) -{ - bs->write(bs, data, len); -} - -void BinarySink_put_datapl(BinarySink *bs, ptrlen pl) -{ - BinarySink_put_data(bs, pl.ptr, pl.len); -} - -void BinarySink_put_padding(BinarySink *bs, size_t len, unsigned char padbyte) -{ - char buf[16]; - memset(buf, padbyte, sizeof(buf)); - while (len > 0) { - size_t thislen = len < sizeof(buf) ? len : sizeof(buf); - bs->write(bs, buf, thislen); - len -= thislen; - } -} - -void BinarySink_put_byte(BinarySink *bs, unsigned char val) -{ - bs->write(bs, &val, 1); -} - -void BinarySink_put_bool(BinarySink *bs, bool val) -{ - unsigned char cval = val ? 1 : 0; - bs->write(bs, &cval, 1); -} - -void BinarySink_put_uint16(BinarySink *bs, unsigned long val) -{ - unsigned char data[2]; - PUT_16BIT_MSB_FIRST(data, val); - bs->write(bs, data, sizeof(data)); -} - -void BinarySink_put_uint32(BinarySink *bs, unsigned long val) -{ - unsigned char data[4]; - PUT_32BIT_MSB_FIRST(data, val); - bs->write(bs, data, sizeof(data)); -} - -void BinarySink_put_uint64(BinarySink *bs, uint64_t val) -{ - unsigned char data[8]; - PUT_64BIT_MSB_FIRST(data, val); - bs->write(bs, data, sizeof(data)); -} - -void BinarySink_put_string(BinarySink *bs, const void *data, size_t len) -{ - /* Check that the string length fits in a uint32, without doing a - * potentially implementation-defined shift of more than 31 bits */ - assert((len >> 31) < 2); - - BinarySink_put_uint32(bs, len); - bs->write(bs, data, len); -} - -void BinarySink_put_stringpl(BinarySink *bs, ptrlen pl) -{ - BinarySink_put_string(bs, pl.ptr, pl.len); -} - -void BinarySink_put_stringz(BinarySink *bs, const char *str) -{ - BinarySink_put_string(bs, str, strlen(str)); -} - -void BinarySink_put_stringsb(BinarySink *bs, strbuf *buf) -{ - BinarySink_put_string(bs, buf->s, buf->len); - strbuf_free(buf); -} - -void BinarySink_put_asciz(BinarySink *bs, const char *str) -{ - bs->write(bs, str, strlen(str) + 1); -} - -bool BinarySink_put_pstring(BinarySink *bs, const char *str) -{ - size_t len = strlen(str); - if (len > 255) - return false; /* can't write a Pascal-style string this long */ - BinarySink_put_byte(bs, len); - bs->write(bs, str, len); - return true; -} - -void BinarySink_put_fmtv(BinarySink *bs, const char *fmt, va_list ap) -{ - if (bs->writefmtv) { - bs->writefmtv(bs, fmt, ap); - } else { - char *str = dupvprintf(fmt, ap); - bs->write(bs, str, strlen(str)); - burnstr(str); - } -} - -void BinarySink_put_fmt(BinarySink *bs, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - BinarySink_put_fmtv(bs, fmt, ap); - va_end(ap); -} - -/* ---------------------------------------------------------------------- */ - -static bool BinarySource_data_avail(BinarySource *src, size_t wanted) -{ - if (src->err) - return false; - - if (wanted <= src->len - src->pos) - return true; - - src->err = BSE_OUT_OF_DATA; - return false; -} - -#define avail(wanted) BinarySource_data_avail(src, wanted) -#define advance(dist) (src->pos += dist) -#define here ((const void *)((const unsigned char *)src->data + src->pos)) -#define consume(dist) \ - ((const void *)((const unsigned char *)src->data + \ - ((src->pos += dist) - dist))) - -ptrlen BinarySource_get_data(BinarySource *src, size_t wanted) -{ - if (!avail(wanted)) - return make_ptrlen("", 0); - - return make_ptrlen(consume(wanted), wanted); -} - -unsigned char BinarySource_get_byte(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(1)) - return 0; - - ucp = consume(1); - return *ucp; -} - -bool BinarySource_get_bool(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(1)) - return false; - - ucp = consume(1); - return *ucp != 0; -} - -unsigned BinarySource_get_uint16(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(2)) - return 0; - - ucp = consume(2); - return GET_16BIT_MSB_FIRST(ucp); -} - -unsigned long BinarySource_get_uint32(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(4)) - return 0; - - ucp = consume(4); - return GET_32BIT_MSB_FIRST(ucp); -} - -uint64_t BinarySource_get_uint64(BinarySource *src) -{ - const unsigned char *ucp; - - if (!avail(8)) - return 0; - - ucp = consume(8); - return GET_64BIT_MSB_FIRST(ucp); -} - -ptrlen BinarySource_get_string(BinarySource *src) -{ - const unsigned char *ucp; - size_t len; - - if (!avail(4)) - return make_ptrlen("", 0); - - ucp = consume(4); - len = GET_32BIT_MSB_FIRST(ucp); - - if (!avail(len)) - return make_ptrlen("", 0); - - return make_ptrlen(consume(len), len); -} - -const char *BinarySource_get_asciz(BinarySource *src) -{ - const char *start, *end; - - if (src->err) - return ""; - - start = here; - end = memchr(start, '\0', src->len - src->pos); - if (!end) { - src->err = BSE_OUT_OF_DATA; - return ""; - } - - advance(end + 1 - start); - return start; -} - -static ptrlen BinarySource_get_chars_internal( - BinarySource *src, const char *set, bool include) -{ - const char *start = here; - while (avail(1)) { - bool present = NULL != strchr(set, *(const char *)consume(0)); - if (present != include) - break; - (void) consume(1); - } - const char *end = here; - return make_ptrlen(start, end - start); -} - -ptrlen BinarySource_get_chars(BinarySource *src, const char *include_set) -{ - return BinarySource_get_chars_internal(src, include_set, true); -} - -ptrlen BinarySource_get_nonchars(BinarySource *src, const char *exclude_set) -{ - return BinarySource_get_chars_internal(src, exclude_set, false); -} - -ptrlen BinarySource_get_chomped_line(BinarySource *src) -{ - const char *start, *end; - - if (src->err) - return make_ptrlen(here, 0); - - start = here; - end = memchr(start, '\n', src->len - src->pos); - if (end) - advance(end + 1 - start); - else - advance(src->len - src->pos); - end = here; - - if (end > start && end[-1] == '\n') - end--; - if (end > start && end[-1] == '\r') - end--; - - return make_ptrlen(start, end - start); -} - -ptrlen BinarySource_get_pstring(BinarySource *src) -{ - const unsigned char *ucp; - size_t len; - - if (!avail(1)) - return make_ptrlen("", 0); - - ucp = consume(1); - len = *ucp; - - if (!avail(len)) - return make_ptrlen("", 0); - - return make_ptrlen(consume(len), len); -} - -void BinarySource_REWIND_TO__(BinarySource *src, size_t pos) -{ - if (pos <= src->len) { - src->pos = pos; - src->err = BSE_NO_ERROR; /* clear any existing error */ - } else { - src->pos = src->len; - src->err = BSE_OUT_OF_DATA; /* new error if we rewind out of range */ - } -} - -static void stdio_sink_write(BinarySink *bs, const void *data, size_t len) -{ - stdio_sink *sink = BinarySink_DOWNCAST(bs, stdio_sink); - fwrite(data, 1, len, sink->fp); -} - -void stdio_sink_init(stdio_sink *sink, FILE *fp) -{ - sink->fp = fp; - BinarySink_INIT(sink, stdio_sink_write); -} - -static void bufchain_sink_write(BinarySink *bs, const void *data, size_t len) -{ - bufchain_sink *sink = BinarySink_DOWNCAST(bs, bufchain_sink); - bufchain_add(sink->ch, data, len); -} - -void bufchain_sink_init(bufchain_sink *sink, bufchain *ch) -{ - sink->ch = ch; - BinarySink_INIT(sink, bufchain_sink_write); -} - -static void buffer_sink_write(BinarySink *bs, const void *data, size_t len) -{ - buffer_sink *sink = BinarySink_DOWNCAST(bs, buffer_sink); - if (len > sink->space) { - len = sink->space; - sink->overflowed = true; - } - memcpy(sink->out, data, len); - sink->space -= len; - sink->out += len; -} - -void buffer_sink_init(buffer_sink *sink, void *buffer, size_t len) -{ - sink->out = buffer; - sink->space = len; - sink->overflowed = false; - BinarySink_INIT(sink, buffer_sink_write); -} diff --git a/utils/memory.c b/utils/memory.c deleted file mode 100644 index 97ae94012..000000000 --- a/utils/memory.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * PuTTY's memory allocation wrappers. - */ - -#include -#include -#include - -#include "defs.h" -#include "puttymem.h" -#include "misc.h" - -void *safemalloc(size_t factor1, size_t factor2, size_t addend) -{ - if (factor1 > SIZE_MAX / factor2) - goto fail; - size_t product = factor1 * factor2; - - if (addend > SIZE_MAX) - goto fail; - if (product > SIZE_MAX - addend) - goto fail; - size_t size = product + addend; - - if (size == 0) - size = 1; - - void *p; -#ifdef MINEFIELD - p = minefield_c_malloc(size); -#else - p = malloc(size); -#endif - - if (!p) - goto fail; - - return p; - - fail: - out_of_memory(); -} - -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) - out_of_memory(); - - return p; -} - -void safefree(void *ptr) -{ - if (ptr) { -#ifdef MINEFIELD - minefield_c_free(ptr); -#else - free(ptr); -#endif - } -} - -void *safegrowarray(void *ptr, size_t *allocated, size_t eltsize, - size_t oldlen, size_t extralen, bool secret) -{ - /* The largest value we can safely multiply by eltsize */ - assert(eltsize > 0); - size_t maxsize = (~(size_t)0) / eltsize; - - size_t oldsize = *allocated; - - /* Range-check the input values */ - assert(oldsize <= maxsize); - assert(oldlen <= maxsize); - assert(extralen <= maxsize - oldlen); - - /* If the size is already enough, don't bother doing anything! */ - if (oldsize > oldlen + extralen) - return ptr; - - /* Find out how much we need to grow the array by. */ - size_t increment = (oldlen + extralen) - oldsize; - - /* Invent a new size. We want to grow the array by at least - * 'increment' elements; by at least a fixed number of bytes (to - * get things started when sizes are small); and by some constant - * factor of its old size (to avoid repeated calls to this - * function taking quadratic time overall). */ - if (increment < 256 / eltsize) - increment = 256 / eltsize; - if (increment < oldsize / 16) - increment = oldsize / 16; - - /* But we also can't grow beyond maxsize. */ - size_t maxincr = maxsize - oldsize; - if (increment > maxincr) - increment = maxincr; - - size_t newsize = oldsize + increment; - void *toret; - if (secret) { - toret = safemalloc(newsize, eltsize, 0); - if (oldsize) { - memcpy(toret, ptr, oldsize * eltsize); - smemclr(ptr, oldsize * eltsize); - sfree(ptr); - } - } else { - toret = saferealloc(ptr, newsize, eltsize); - } - *allocated = newsize; - return toret; -} diff --git a/utils/memxor.c b/utils/memxor.c deleted file mode 100644 index 4fd41f415..000000000 --- a/utils/memxor.c +++ /dev/null @@ -1,34 +0,0 @@ -/* - * XOR two pieces of memory, copying the result into a third, which - * may precisely alias one of the input pair (but no guarantees if it - * partially overlaps). - */ - -#include "defs.h" -#include "misc.h" - -void memxor(uint8_t *out, const uint8_t *in1, const uint8_t *in2, size_t size) -{ - switch (size & 15) { - case 0: - while (size >= 16) { - size -= 16; - *out++ = *in1++ ^ *in2++; - case 15: *out++ = *in1++ ^ *in2++; - case 14: *out++ = *in1++ ^ *in2++; - case 13: *out++ = *in1++ ^ *in2++; - case 12: *out++ = *in1++ ^ *in2++; - case 11: *out++ = *in1++ ^ *in2++; - case 10: *out++ = *in1++ ^ *in2++; - case 9: *out++ = *in1++ ^ *in2++; - case 8: *out++ = *in1++ ^ *in2++; - case 7: *out++ = *in1++ ^ *in2++; - case 6: *out++ = *in1++ ^ *in2++; - case 5: *out++ = *in1++ ^ *in2++; - case 4: *out++ = *in1++ ^ *in2++; - case 3: *out++ = *in1++ ^ *in2++; - case 2: *out++ = *in1++ ^ *in2++; - case 1: *out++ = *in1++ ^ *in2++; - } - } -} diff --git a/utils/nullstrcmp.c b/utils/nullstrcmp.c deleted file mode 100644 index a9e50f40c..000000000 --- a/utils/nullstrcmp.c +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Compare two strings, just like strcmp, except that we tolerate null - * pointers as a legal input, and define them to compare before any - * non-null string (even the empty string). - */ - -#include - -#include "defs.h" -#include "misc.h" - -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); -} diff --git a/utils/out_of_memory.c b/utils/out_of_memory.c deleted file mode 100644 index 1c9cb259e..000000000 --- a/utils/out_of_memory.c +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Standard implementation of the out_of_memory function called by our - * malloc wrappers. - */ - -#include "putty.h" - -void out_of_memory(void) -{ - modalfatalbox("Out of memory"); -} diff --git a/utils/parse_blocksize.c b/utils/parse_blocksize.c deleted file mode 100644 index 3d5592768..000000000 --- a/utils/parse_blocksize.c +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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. - */ - -#include -#include -#include - -#include "defs.h" -#include "misc.h" - -unsigned long parse_blocksize(const char *bs) -{ - char *suf; - unsigned long 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; -} diff --git a/utils/percent_decode.c b/utils/percent_decode.c deleted file mode 100644 index dff2c233d..000000000 --- a/utils/percent_decode.c +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Decode %-encoding in URL style. - */ - -#include - -#include "misc.h" - -void percent_decode_bs(BinarySink *bs, ptrlen data) -{ - for (const char *p = data.ptr, *e = ptrlen_end(data); p < e; p++) { - char c = *p; - if (c == '%' && e-p >= 3 && - isxdigit((unsigned char)p[1]) && - isxdigit((unsigned char)p[2])) { - char hex[3]; - hex[0] = p[1]; - hex[1] = p[2]; - hex[2] = '\0'; - put_byte(bs, strtoul(hex, NULL, 16)); - p += 2; - } else { - put_byte(bs, c); - } - } - -} - -void percent_decode_fp(FILE *fp, ptrlen data) -{ - stdio_sink ss; - stdio_sink_init(&ss, fp); - percent_decode_bs(BinarySink_UPCAST(&ss), data); -} - -strbuf *percent_decode_sb(ptrlen data) -{ - strbuf *sb = strbuf_new(); - percent_decode_bs(BinarySink_UPCAST(sb), data); - return sb; -} diff --git a/utils/percent_encode.c b/utils/percent_encode.c deleted file mode 100644 index ea04b0ac6..000000000 --- a/utils/percent_encode.c +++ /dev/null @@ -1,34 +0,0 @@ -/* - * %-encoding in URL style. - * - * Defaults to escaping % itself (necessary for decoding to even - * work), and any C0 escape character. Further bad characters can be - * provided in 'badchars'. - */ - -#include "misc.h" - -void percent_encode_bs(BinarySink *bs, ptrlen data, const char *badchars) -{ - for (const char *p = data.ptr, *e = ptrlen_end(data); p < e; p++) { - char c = *p; - if (c == '%' || c < ' ' || (badchars && strchr(badchars, c))) - put_fmt(bs, "%%%02X", (unsigned char)c); - else - put_byte(bs, c); - } -} - -void percent_encode_fp(FILE *fp, ptrlen data, const char *badchars) -{ - stdio_sink ss; - stdio_sink_init(&ss, fp); - percent_encode_bs(BinarySink_UPCAST(&ss), data, badchars); -} - -strbuf *percent_encode_sb(ptrlen data, const char *badchars) -{ - strbuf *sb = strbuf_new(); - percent_encode_bs(BinarySink_UPCAST(sb), data, badchars); - return sb; -} diff --git a/utils/prompts.c b/utils/prompts.c deleted file mode 100644 index 2b70133af..000000000 --- a/utils/prompts.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Functions for making, destroying, and manipulating prompts_t - * structures. - */ - -#include "putty.h" - -prompts_t *new_prompts(void) -{ - prompts_t *p = snew(prompts_t); - p->prompts = NULL; - p->n_prompts = p->prompts_size = 0; - p->data = NULL; - p->spr = SPR_INCOMPLETE; - p->to_server = true; /* to be on the safe side */ - p->name = p->instruction = NULL; - p->name_reqd = p->instr_reqd = false; - p->callback = NULL; - p->callback_ctx = NULL; - p->ldisc_ptr_to_us = NULL; - p->utf8 = false; - return p; -} - -void add_prompt(prompts_t *p, char *promptstr, bool echo) -{ - prompt_t *pr = snew(prompt_t); - pr->prompt = promptstr; - pr->echo = echo; - pr->result = strbuf_new_nm(); - sgrowarray(p->prompts, p->prompts_size, p->n_prompts); - p->prompts[p->n_prompts++] = pr; -} - -void prompt_set_result(prompt_t *pr, const char *newstr) -{ - strbuf_clear(pr->result); - put_dataz(pr->result, newstr); -} - -const char *prompt_get_result_ref(prompt_t *pr) -{ - return pr->result->s; -} - -char *prompt_get_result(prompt_t *pr) -{ - return dupstr(pr->result->s); -} - -void free_prompts(prompts_t *p) -{ - size_t i; - - /* If an Ldisc currently knows about us, tell it to forget us, so - * it won't dereference a stale pointer later. */ - if (p->ldisc_ptr_to_us) - *p->ldisc_ptr_to_us = NULL; - - for (i=0; i < p->n_prompts; i++) { - prompt_t *pr = p->prompts[i]; - strbuf_free(pr->result); - sfree(pr->prompt); - sfree(pr); - } - sfree(p->prompts); - sfree(p->name); - sfree(p->instruction); - sfree(p); -} diff --git a/utils/ptrlen.c b/utils/ptrlen.c deleted file mode 100644 index 37972e493..000000000 --- a/utils/ptrlen.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Functions to deal with ptrlens. - */ - -#include "defs.h" -#include "misc.h" - -bool ptrlen_eq_string(ptrlen pl, const char *str) -{ - size_t len = strlen(str); - return (pl.len == len && !memcmp(pl.ptr, str, len)); -} - -bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2) -{ - return (pl1.len == pl2.len && !memcmp(pl1.ptr, pl2.ptr, pl1.len)); -} - -int ptrlen_strcmp(ptrlen pl1, ptrlen pl2) -{ - size_t minlen = pl1.len < pl2.len ? pl1.len : pl2.len; - if (minlen) { /* tolerate plX.ptr==NULL as long as plX.len==0 */ - int cmp = memcmp(pl1.ptr, pl2.ptr, minlen); - if (cmp) - return cmp; - } - return pl1.len < pl2.len ? -1 : pl1.len > pl2.len ? +1 : 0; -} - -bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail) -{ - if (whole.len >= prefix.len && - !memcmp(whole.ptr, prefix.ptr, prefix.len)) { - if (tail) { - tail->ptr = (const char *)whole.ptr + prefix.len; - tail->len = whole.len - prefix.len; - } - return true; - } - return false; -} - -bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail) -{ - if (whole.len >= suffix.len && - !memcmp((char *)whole.ptr + (whole.len - suffix.len), - suffix.ptr, suffix.len)) { - if (tail) { - tail->ptr = whole.ptr; - tail->len = whole.len - suffix.len; - } - return true; - } - return false; -} - -bool ptrlen_contains(ptrlen input, const char *characters) -{ - for (const char *p = input.ptr, *end = p + input.len; p < end; p++) - if (strchr(characters, *p)) - return true; - return false; -} - -bool ptrlen_contains_only(ptrlen input, const char *characters) -{ - for (const char *p = input.ptr, *end = p + input.len; p < end; p++) - if (!strchr(characters, *p)) - return false; - return true; -} - -ptrlen ptrlen_get_word(ptrlen *input, const char *separators) -{ - const char *p = input->ptr, *end = p + input->len; - ptrlen toret; - - while (p < end && strchr(separators, *p)) - p++; - toret.ptr = p; - while (p < end && !strchr(separators, *p)) - p++; - toret.len = p - (const char *)toret.ptr; - - size_t to_consume = p - (const char *)input->ptr; - assert(to_consume <= input->len); - input->ptr = (const char *)input->ptr + to_consume; - input->len -= to_consume; - - return toret; -} - -char *mkstr(ptrlen pl) -{ - char *p = snewn(pl.len + 1, char); - memcpy(p, pl.ptr, pl.len); - p[pl.len] = '\0'; - return p; -} - -bool strstartswith(const char *s, const char *t) -{ - return !strncmp(s, t, strlen(t)); -} - -bool strendswith(const char *s, const char *t) -{ - size_t slen = strlen(s), tlen = strlen(t); - return slen >= tlen && !strcmp(s + (slen - tlen), t); -} diff --git a/utils/read_file_into.c b/utils/read_file_into.c deleted file mode 100644 index 595694845..000000000 --- a/utils/read_file_into.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Read an entire file into a BinarySink. - */ - -#include - -#include "defs.h" -#include "misc.h" - -bool read_file_into(BinarySink *bs, FILE *fp) -{ - char buf[4096]; - while (1) { - size_t retd = fread(buf, 1, sizeof(buf), fp); - if (retd == 0) - return !ferror(fp); - put_data(bs, buf, retd); - } -} diff --git a/utils/seat_connection_fatal.c b/utils/seat_connection_fatal.c deleted file mode 100644 index 9b0186b12..000000000 --- a/utils/seat_connection_fatal.c +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Wrapper function for the connection_fatal() method of a Seat, - * providing printf-style formatting. - */ - -#include "putty.h" - -void seat_connection_fatal(Seat *seat, const char *fmt, ...) -{ - va_list ap; - char *msg; - - va_start(ap, fmt); - msg = dupvprintf(fmt, ap); - va_end(ap); - - seat->vt->connection_fatal(seat, msg); - sfree(msg); /* if we return */ -} - diff --git a/utils/seat_dialog_text.c b/utils/seat_dialog_text.c deleted file mode 100644 index 03890b930..000000000 --- a/utils/seat_dialog_text.c +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Helper routines for dealing with SeatDialogText structures. - */ - -#include - -#include "putty.h" - -SeatDialogText *seat_dialog_text_new(void) -{ - SeatDialogText *sdt = snew(SeatDialogText); - sdt->nitems = sdt->itemsize = 0; - sdt->items = NULL; - return sdt; -} - -void seat_dialog_text_free(SeatDialogText *sdt) -{ - for (size_t i = 0; i < sdt->nitems; i++) - sfree(sdt->items[i].text); - sfree(sdt->items); - sfree(sdt); -} - -static void seat_dialog_text_append_v( - SeatDialogText *sdt, SeatDialogTextType type, const char *fmt, va_list ap) -{ - sgrowarray(sdt->items, sdt->itemsize, sdt->nitems); - SeatDialogTextItem *item = &sdt->items[sdt->nitems++]; - item->type = type; - item->text = dupvprintf(fmt, ap); -} - -void seat_dialog_text_append(SeatDialogText *sdt, SeatDialogTextType type, - const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - seat_dialog_text_append_v(sdt, type, fmt, ap); - va_end(ap); -} diff --git a/utils/seat_nonfatal.c b/utils/seat_nonfatal.c deleted file mode 100644 index d21e4c17d..000000000 --- a/utils/seat_nonfatal.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Wrapper function for the nonfatal() method of a Seat, - * providing printf-style formatting. - */ - -#include "putty.h" - -void seat_nonfatal(Seat *seat, const char *fmt, ...) -{ - va_list ap; - char *msg; - - va_start(ap, fmt); - msg = dupvprintf(fmt, ap); - va_end(ap); - - seat->vt->nonfatal(seat, msg); - sfree(msg); -} diff --git a/utils/sessprep.c b/utils/sessprep.c deleted file mode 100644 index 95e7b6580..000000000 --- a/utils/sessprep.c +++ /dev/null @@ -1,84 +0,0 @@ -/* - * sessprep.c: centralise some preprocessing done on Conf objects - * before launching them. - */ - -#include "putty.h" - -void prepare_session(Conf *conf) -{ - char *hostbuf = dupstr(conf_get_str(conf, CONF_host)); - char *host = hostbuf; - char *p, *q; - - /* - * Trim leading whitespace from the hostname. - */ - host += strspn(host, " \t"); - - /* - * See if host is of the form user@host, and separate out the - * username if so. - */ - if (host[0] != '\0') { - /* - * Use strrchr, in case the _username_ in turn is of the form - * user@host, which has been known. - */ - 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, and discard - * the text after it. - * - * The exact reason why we _ignore_ this text, rather than - * treating it as a port number, is unfortunately lost in the - * mists of history: the commit which originally introduced this - * change on 2001-05-06 was clear on _what_ it was doing but - * didn't bother to explain _why_. But I [SGT, 2017-12-03] suspect - * it has to do with priority order: what should a saved session - * do if its CONF_host contains 'server.example.com:123' and its - * CONF_port contains 456? If CONF_port contained the _default_ - * port number then it might be a good guess that the colon suffix - * on the host name was intended to override that, but you don't - * really want to get into making heuristic judgments on that - * basis. - * - * (Then again, you could just as easily make the same argument - * about whether a 'user@' prefix on the host name should override - * CONF_username, which this code _does_ do. I don't have a good - * answer, sadly. Both these pieces of behaviour have been around - * for years and it would probably cause subtle breakage in all - * sorts of long-forgotten scripting to go changing things around - * now.) - * - * In order to protect unbracketed IPv6 address literals against - * this treatment, we do not make this change at all if there's - * _more_ than one (un-IPv6-bracketed) colon. - */ - p = host_strchr(host, ':'); - if (p && p == host_strrchr(host, ':')) { - *p = '\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); -} diff --git a/utils/sk_free_peer_info.c b/utils/sk_free_peer_info.c deleted file mode 100644 index a66e09d70..000000000 --- a/utils/sk_free_peer_info.c +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Free a SocketPeerInfo, and everything that dangles off it. - */ - -#include "putty.h" - -void sk_free_peer_info(SocketPeerInfo *pi) -{ - if (pi) { - sfree((char *)pi->addr_text); - sfree((char *)pi->log_text); - sfree(pi); - } -} diff --git a/utils/smemclr.c b/utils/smemclr.c deleted file mode 100644 index 5503bdc44..000000000 --- a/utils/smemclr.c +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 - */ - -#include "defs.h" -#include "misc.h" - -/* - * Trivial function that is given a pointer to some memory and ignores - * it. - */ -static void no_op(void *ptr, size_t size) {} - -/* - * Function pointer that is given a pointer to some memory, and from - * the compiler's point of view, _might_ read it, or otherwise depend - * on its contents. - * - * In fact, this function pointer always points to no_op() above. But - * because the pointer itself is volatile-qualified, the compiler - * isn't allowed to optimise based on the assumption that that will - * always be the case. So it has to call through the function pointer - * anyway, on the basis that it _might_ have magically changed at run - * time into a pointer to some completely arbitrary function. And - * therefore it must also avoid optimising away any observable effect - * beforehand that a completely arbitrary function might depend on - - * such as the zeroing of our memory region. - */ -static void (*const volatile maybe_read)(void *ptr, size_t size) = no_op; - -void smemclr(void *b, size_t n) -{ - if (b && n > 0) { - /* - * Zero out the memory. - */ - memset(b, 0, n); - - /* - * Call the above function pointer, which (for all the - * compiler knows) might check that we've really zeroed the - * memory. - */ - maybe_read(b, n); - } -} diff --git a/utils/smemeq.c b/utils/smemeq.c deleted file mode 100644 index ce3761ecb..000000000 --- a/utils/smemeq.c +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Compare two fixed-size regions of memory, in a crypto-safe way, - * i.e. without timing or cache side channels indicating anything - * about what the answer was or where the first difference (if any) - * might have been. - */ - -#include "defs.h" -#include "misc.h" - -unsigned 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; -} diff --git a/utils/spr_get_error_message.c b/utils/spr_get_error_message.c deleted file mode 100644 index b9f81a47c..000000000 --- a/utils/spr_get_error_message.c +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Construct the error message from a SeatPromptResult, and return it - * in a dynamically allocated string. - */ - -#include "putty.h" - -char *spr_get_error_message(SeatPromptResult spr) -{ - strbuf *sb = strbuf_new(); - spr.errfn(spr, BinarySink_UPCAST(sb)); - return strbuf_to_str(sb); -} diff --git a/utils/ssh2_pick_fingerprint.c b/utils/ssh2_pick_fingerprint.c deleted file mode 100644 index f81b2f1dc..000000000 --- a/utils/ssh2_pick_fingerprint.c +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Choose an SSH-2 fingerprint type, out of an array of possible ones. - */ - -#include "defs.h" -#include "misc.h" -#include "ssh.h" - -FingerprintType ssh2_pick_fingerprint( - char **fingerprints, FingerprintType preferred_type) -{ - /* - * Keys are either SSH-2, in which case we have all fingerprint - * types, or SSH-1, in which case we have only MD5. So we return - * the default type if we can, or MD5 if that's all we have; no - * need for a fully general preference-list system. - */ - FingerprintType fptype = fingerprints[preferred_type] ? - preferred_type : SSH_FPTYPE_MD5; - assert(fingerprints[fptype]); - return fptype; -} - -FingerprintType ssh2_pick_default_fingerprint(char **fingerprints) -{ - return ssh2_pick_fingerprint(fingerprints, SSH_FPTYPE_DEFAULT); -} diff --git a/utils/ssh_key_clone.c b/utils/ssh_key_clone.c deleted file mode 100644 index 5824bdbf2..000000000 --- a/utils/ssh_key_clone.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Make a copy of an existing ssh_key object, e.g. to survive after - * the original is freed. - */ - -#include "misc.h" -#include "ssh.h" - -ssh_key *ssh_key_clone(ssh_key *key) -{ - /* - * To avoid having to add a special method in the vtable API, we - * clone by round-tripping through public and private blobs. - */ - strbuf *pub = strbuf_new_nm(); - ssh_key_public_blob(key, BinarySink_UPCAST(pub)); - - ssh_key *copy; - - if (ssh_key_has_private(key)) { - strbuf *priv = strbuf_new_nm(); - ssh_key_private_blob(key, BinarySink_UPCAST(priv)); - copy = ssh_key_new_priv(ssh_key_alg(key), ptrlen_from_strbuf(pub), - ptrlen_from_strbuf(priv)); - strbuf_free(priv); - } else { - copy = ssh_key_new_pub(ssh_key_alg(key), ptrlen_from_strbuf(pub)); - } - - strbuf_free(pub); - return copy; -} diff --git a/utils/sshutils.c b/utils/sshutils.c deleted file mode 100644 index 49e822196..000000000 --- a/utils/sshutils.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Supporting routines used in common by all the various components of - * the SSH system. - */ - -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "ssh/channel.h" - -/* ---------------------------------------------------------------------- - * Centralised standard methods for other channel implementations to - * borrow. - */ - -void chan_remotely_opened_confirmation(Channel *chan) -{ - unreachable("this channel type should never receive OPEN_CONFIRMATION"); -} - -void chan_remotely_opened_failure(Channel *chan, const char *errtext) -{ - unreachable("this channel type should never receive OPEN_FAILURE"); -} - -bool chan_default_want_close( - Channel *chan, bool sent_local_eof, bool rcvd_remote_eof) -{ - /* - * Default close policy: we start initiating the CHANNEL_CLOSE - * procedure as soon as both sides of the channel have seen EOF. - */ - return sent_local_eof && rcvd_remote_eof; -} - -bool chan_no_exit_status(Channel *chan, int status) -{ - return false; -} - -bool chan_no_exit_signal( - Channel *chan, ptrlen signame, bool core_dumped, ptrlen msg) -{ - return false; -} - -bool chan_no_exit_signal_numeric( - Channel *chan, int signum, bool core_dumped, ptrlen msg) -{ - return false; -} - -bool chan_no_run_shell(Channel *chan) -{ - return false; -} - -bool chan_no_run_command(Channel *chan, ptrlen command) -{ - return false; -} - -bool chan_no_run_subsystem(Channel *chan, ptrlen subsys) -{ - return false; -} - -bool chan_no_enable_x11_forwarding( - Channel *chan, bool oneshot, ptrlen authproto, ptrlen authdata, - unsigned screen_number) -{ - return false; -} - -bool chan_no_enable_agent_forwarding(Channel *chan) -{ - return false; -} - -bool chan_no_allocate_pty( - Channel *chan, ptrlen termtype, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight, struct ssh_ttymodes modes) -{ - return false; -} - -bool chan_no_set_env(Channel *chan, ptrlen var, ptrlen value) -{ - return false; -} - -bool chan_no_send_break(Channel *chan, unsigned length) -{ - return false; -} - -bool chan_no_send_signal(Channel *chan, ptrlen signame) -{ - return false; -} - -bool chan_no_change_window_size( - Channel *chan, unsigned width, unsigned height, - unsigned pixwidth, unsigned pixheight) -{ - return false; -} - -void chan_no_request_response(Channel *chan, bool success) -{ - unreachable("this channel type should never send a want-reply request"); -} - -/* ---------------------------------------------------------------------- - * Other miscellaneous utility functions. - */ - -void free_rportfwd(struct ssh_rportfwd *rpf) -{ - if (rpf) { - sfree(rpf->log_description); - sfree(rpf->shost); - sfree(rpf->dhost); - sfree(rpf); - } -} diff --git a/utils/strbuf.c b/utils/strbuf.c deleted file mode 100644 index c636de472..000000000 --- a/utils/strbuf.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Functions to work with strbufs. - */ - -#include "defs.h" -#include "misc.h" -#include "utils/utils.h" - -struct strbuf_impl { - size_t size; - struct strbuf visible; - bool nm; /* true if we insist on non-moving buffer resizes */ -}; - -#define STRBUF_SET_UPTR(buf) \ - ((buf)->visible.u = (unsigned char *)(buf)->visible.s) -#define STRBUF_SET_PTR(buf, ptr) \ - ((buf)->visible.s = (ptr), STRBUF_SET_UPTR(buf)) - -void *strbuf_append(strbuf *buf_o, size_t len) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - char *toret; - sgrowarray_general( - buf->visible.s, buf->size, buf->visible.len + 1, len, buf->nm); - STRBUF_SET_UPTR(buf); - toret = buf->visible.s + buf->visible.len; - buf->visible.len += len; - buf->visible.s[buf->visible.len] = '\0'; - return toret; -} - -void strbuf_shrink_to(strbuf *buf, size_t new_len) -{ - assert(new_len <= buf->len); - buf->len = new_len; - buf->s[buf->len] = '\0'; -} - -void strbuf_shrink_by(strbuf *buf, size_t amount_to_remove) -{ - assert(amount_to_remove <= buf->len); - buf->len -= amount_to_remove; - buf->s[buf->len] = '\0'; -} - -bool strbuf_chomp(strbuf *buf, char char_to_remove) -{ - if (buf->len > 0 && buf->s[buf->len-1] == char_to_remove) { - strbuf_shrink_by(buf, 1); - return true; - } - return false; -} - -static void strbuf_BinarySink_write( - BinarySink *bs, const void *data, size_t len) -{ - strbuf *buf_o = BinarySink_DOWNCAST(bs, strbuf); - memcpy(strbuf_append(buf_o, len), data, len); -} - -static void strbuf_BinarySink_writefmtv( - BinarySink *bs, const char *fmt, va_list ap) -{ - strbuf *buf_o = BinarySink_DOWNCAST(bs, strbuf); - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - STRBUF_SET_PTR(buf, dupvprintf_inner(buf->visible.s, buf->visible.len, - &buf->size, fmt, ap)); - buf->visible.len += strlen(buf->visible.s + buf->visible.len); -} - -static strbuf *strbuf_new_general(bool nm) -{ - struct strbuf_impl *buf = snew(struct strbuf_impl); - BinarySink_INIT(&buf->visible, strbuf_BinarySink_write); - buf->visible.binarysink_->writefmtv = strbuf_BinarySink_writefmtv; - buf->visible.len = 0; - buf->size = 512; - buf->nm = nm; - STRBUF_SET_PTR(buf, snewn(buf->size, char)); - *buf->visible.s = '\0'; - return &buf->visible; -} -strbuf *strbuf_new(void) { return strbuf_new_general(false); } -strbuf *strbuf_new_nm(void) { return strbuf_new_general(true); } -void strbuf_free(strbuf *buf_o) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - if (buf->visible.s) { - smemclr(buf->visible.s, buf->size); - sfree(buf->visible.s); - } - sfree(buf); -} -char *strbuf_to_str(strbuf *buf_o) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - char *ret = buf->visible.s; - sfree(buf); - return ret; -} -strbuf *strbuf_new_for_agent_query(void) -{ - strbuf *buf = strbuf_new(); - strbuf_append(buf, 4); - return buf; -} -void strbuf_finalise_agent_query(strbuf *buf_o) -{ - struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible); - assert(buf->visible.len >= 5); - PUT_32BIT_MSB_FIRST(buf->visible.u, buf->visible.len - 4); -} - -strbuf *strbuf_dup(ptrlen string) -{ - strbuf *buf = strbuf_new(); - put_datapl(buf, string); - return buf; -} - -strbuf *strbuf_dup_nm(ptrlen string) -{ - strbuf *buf = strbuf_new_nm(); - put_datapl(buf, string); - return buf; -} diff --git a/utils/string_length_for_printf.c b/utils/string_length_for_printf.c deleted file mode 100644 index 8b4553161..000000000 --- a/utils/string_length_for_printf.c +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Convert a size_t value to int, by saturating it at INT_MAX. Useful - * if you want to use the printf idiom "%.*s", where the '*' precision - * specifier expects an int in the variadic argument list, but what - * you have is not an int but a size_t. This method of converting to - * int will at least do something _safe_ with overlong values, even if - * (due to the limitation of printf itself) the whole string still - * won't be printed. - */ - -#include - -#include "defs.h" -#include "misc.h" - -int string_length_for_printf(size_t s) -{ - if (s > INT_MAX) - return INT_MAX; - return s; -} diff --git a/utils/stripctrl.c b/utils/stripctrl.c deleted file mode 100644 index 16a8dce24..000000000 --- a/utils/stripctrl.c +++ /dev/null @@ -1,470 +0,0 @@ -/* - * stripctrl.c: a facility for stripping control characters out of a - * data stream (defined as any multibyte character in the system - * locale which is neither printable nor \n), using the standard C - * library multibyte character facilities. - */ - -#include -#include -#include -#include -#include - -#include "putty.h" -#include "terminal.h" -#include "misc.h" -#include "marshal.h" - -#define SCC_BUFSIZE 64 -#define LINE_LIMIT 77 - -typedef struct StripCtrlCharsImpl StripCtrlCharsImpl; -struct StripCtrlCharsImpl { - mbstate_t mbs_in, mbs_out; - - bool permit_cr; - wchar_t substitution; - - char buf[SCC_BUFSIZE]; - size_t buflen; - - Terminal *term; - bool last_term_utf; - struct term_utf8_decode utf8; - unsigned long (*translate)(Terminal *, term_utf8_decode *, unsigned char); - - bool line_limit; - bool line_start; - size_t line_chars_remaining; - - BinarySink *bs_out; - - StripCtrlChars public; -}; - -static void stripctrl_locale_BinarySink_write( - BinarySink *bs, const void *vp, size_t len); -static void stripctrl_term_BinarySink_write( - BinarySink *bs, const void *vp, size_t len); - -static StripCtrlCharsImpl *stripctrl_new_common( - BinarySink *bs_out, bool permit_cr, wchar_t substitution) -{ - StripCtrlCharsImpl *scc = snew(StripCtrlCharsImpl); - memset(scc, 0, sizeof(StripCtrlCharsImpl)); /* zeroes mbstates */ - scc->bs_out = bs_out; - scc->permit_cr = permit_cr; - scc->substitution = substitution; - return scc; -} - -StripCtrlChars *stripctrl_new( - BinarySink *bs_out, bool permit_cr, wchar_t substitution) -{ - StripCtrlCharsImpl *scc = stripctrl_new_common( - bs_out, permit_cr, substitution); - BinarySink_INIT(&scc->public, stripctrl_locale_BinarySink_write); - return &scc->public; -} - -StripCtrlChars *stripctrl_new_term_fn( - BinarySink *bs_out, bool permit_cr, wchar_t substitution, - Terminal *term, unsigned long (*translate)( - Terminal *, term_utf8_decode *, unsigned char)) -{ - StripCtrlCharsImpl *scc = stripctrl_new_common( - bs_out, permit_cr, substitution); - scc->term = term; - scc->translate = translate; - BinarySink_INIT(&scc->public, stripctrl_term_BinarySink_write); - return &scc->public; -} - -void stripctrl_retarget(StripCtrlChars *sccpub, BinarySink *new_bs_out) -{ - StripCtrlCharsImpl *scc = - container_of(sccpub, StripCtrlCharsImpl, public); - scc->bs_out = new_bs_out; - stripctrl_reset(sccpub); -} - -void stripctrl_reset(StripCtrlChars *sccpub) -{ - StripCtrlCharsImpl *scc = - container_of(sccpub, StripCtrlCharsImpl, public); - - /* - * Clear all the fields that might have been in the middle of a - * multibyte character or non-default shift state, so that we can - * start converting a fresh piece of data to send to a channel - * that hasn't seen the previous output. - */ - memset(&scc->utf8, 0, sizeof(scc->utf8)); - memset(&scc->mbs_in, 0, sizeof(scc->mbs_in)); - memset(&scc->mbs_out, 0, sizeof(scc->mbs_out)); - - /* - * Also, reset the line-limiting system to its starting state. - */ - scc->line_start = true; -} - -void stripctrl_free(StripCtrlChars *sccpub) -{ - StripCtrlCharsImpl *scc = - container_of(sccpub, StripCtrlCharsImpl, public); - smemclr(scc, sizeof(StripCtrlCharsImpl)); - sfree(scc); -} - -void stripctrl_enable_line_limiting(StripCtrlChars *sccpub) -{ - StripCtrlCharsImpl *scc = - container_of(sccpub, StripCtrlCharsImpl, public); - scc->line_limit = true; - scc->line_start = true; -} - -static inline bool stripctrl_ctrlchar_ok(StripCtrlCharsImpl *scc, wchar_t wc) -{ - return wc == L'\n' || (wc == L'\r' && scc->permit_cr); -} - -static inline void stripctrl_check_line_limit( - StripCtrlCharsImpl *scc, wchar_t wc, size_t width) -{ - if (!scc->line_limit) - return; /* nothing to do */ - - if (scc->line_start) { - put_datapl(scc->bs_out, PTRLEN_LITERAL("| ")); - scc->line_start = false; - scc->line_chars_remaining = LINE_LIMIT; - } - - if (wc == '\n') { - scc->line_start = true; - return; - } - - if (scc->line_chars_remaining < width) { - put_datapl(scc->bs_out, PTRLEN_LITERAL("\r\n> ")); - scc->line_chars_remaining = LINE_LIMIT; - } - - assert(width <= scc->line_chars_remaining); - scc->line_chars_remaining -= width; -} - -static inline void stripctrl_locale_put_wc(StripCtrlCharsImpl *scc, wchar_t wc) -{ - int width = mk_wcwidth(wc); - if ((iswprint(wc) && width >= 0) || stripctrl_ctrlchar_ok(scc, wc)) { - /* Printable character, or one we're going to let through anyway. */ - if (width < 0) - width = 0; /* sanitise for stripctrl_check_line_limit */ - } else if (scc->substitution) { - wc = scc->substitution; - width = mk_wcwidth(wc); - assert(width >= 0); - } else { - /* No defined substitution, so don't write any output wchar_t. */ - return; - } - - stripctrl_check_line_limit(scc, wc, width); - - char outbuf[MB_LEN_MAX]; - size_t produced = wcrtomb(outbuf, wc, &scc->mbs_out); - if (produced > 0) - put_data(scc->bs_out, outbuf, produced); -} - -static inline void stripctrl_term_put_wc( - StripCtrlCharsImpl *scc, unsigned long wc) -{ - ptrlen prefix = PTRLEN_LITERAL(""); - int width = term_char_width(scc->term, wc); - - if (!(wc & ~0x9F) || width < 0) { - /* This is something the terminal interprets as a control - * character. */ - if (!stripctrl_ctrlchar_ok(scc, wc)) { - if (!scc->substitution) { - return; - } else { - wc = scc->substitution; - width = term_char_width(scc->term, wc); - assert(width >= 0); - } - } else { - if (width < 0) - width = 0; /* sanitise for stripctrl_check_line_limit */ - } - - if (wc == '\012') { - /* Precede \n with \r, because our terminal will not - * generally be in the ONLCR mode where it assumes that - * internally, and any \r on input has been stripped - * out. */ - prefix = PTRLEN_LITERAL("\r"); - } - } - - stripctrl_check_line_limit(scc, wc, width); - - if (prefix.len) - put_datapl(scc->bs_out, prefix); - - /* - * The Terminal implementation encodes 7-bit ASCII characters in - * UTF-8 mode, and all printing characters in non-UTF-8 (i.e. - * single-byte character set) mode, as values in the surrogate - * range (a conveniently unused piece of space in this context) - * whose low byte is the original 1-byte representation of the - * character. - */ - if ((wc - 0xD800) < (0xE000 - 0xD800)) - wc &= 0xFF; - - if (in_utf(scc->term)) { - put_utf8_char(scc->bs_out, wc); - } else { - put_byte(scc->bs_out, wc); - } -} - -static inline size_t stripctrl_locale_try_consume( - StripCtrlCharsImpl *scc, const char *p, size_t len) -{ - wchar_t wc; - mbstate_t mbs_orig = scc->mbs_in; - size_t consumed = mbrtowc(&wc, p, len, &scc->mbs_in); - - if (consumed == (size_t)-2) { - /* - * The buffer is too short to see the end of the multibyte - * character that it appears to be starting with. We return 0 - * for 'no data consumed', restore the conversion state from - * before consuming the partial character, and our caller will - * come back when it has more data available. - */ - scc->mbs_in = mbs_orig; - return 0; - } - - if (consumed == (size_t)-1) { - /* - * The buffer contains an illegal multibyte sequence. There's - * no really good way to recover from this, so we'll just - * reset our input state, consume a single byte without - * emitting anything, and hope we can resynchronise to - * _something_ sooner or later. - */ - memset(&scc->mbs_in, 0, sizeof(scc->mbs_in)); - return 1; - } - - if (consumed == 0) { - /* - * A zero wide character is encoded by the data, but mbrtowc - * hasn't told us how many input bytes it takes. There isn't - * really anything good we can do here, so we just advance by - * one byte in the hope that that was the NUL. - * - * (If it wasn't - that is, if we're in a multibyte encoding - * in which the terminator of a normal C string is encoded in - * some way other than a single zero byte - then probably lots - * of other things will have gone wrong before we get here!) - */ - stripctrl_locale_put_wc(scc, L'\0'); - return 1; - } - - /* - * Otherwise, this is the easy case: consumed > 0, and we've eaten - * a valid multibyte character. - */ - stripctrl_locale_put_wc(scc, wc); - return consumed; -} - -static void stripctrl_locale_BinarySink_write( - BinarySink *bs, const void *vp, size_t len) -{ - StripCtrlChars *sccpub = BinarySink_DOWNCAST(bs, StripCtrlChars); - StripCtrlCharsImpl *scc = - container_of(sccpub, StripCtrlCharsImpl, public); - const char *p = (const char *)vp; - - char *previous_locale = dupstr(setlocale(LC_CTYPE, NULL)); - setlocale(LC_CTYPE, ""); - - /* - * Deal with any partial multibyte character buffered from last - * time. - */ - while (scc->buflen > 0) { - size_t to_copy = SCC_BUFSIZE - scc->buflen; - if (to_copy > len) - to_copy = len; - - memcpy(scc->buf + scc->buflen, p, to_copy); - size_t consumed = stripctrl_locale_try_consume( - scc, scc->buf, scc->buflen + to_copy); - - if (consumed >= scc->buflen) { - /* - * We've consumed a multibyte character that includes all - * the data buffered from last time. So we can clear our - * buffer and move on to processing the main input string - * in situ, having first discarded whatever initial - * segment of it completed our previous character. - */ - size_t consumed_from_main_string = consumed - scc->buflen; - assert(consumed_from_main_string <= len); - p += consumed_from_main_string; - len -= consumed_from_main_string; - scc->buflen = 0; - break; - } - - if (consumed == 0) { - /* - * If we didn't manage to consume anything, i.e. the whole - * buffer contains an incomplete sequence, it had better - * be because our entire input string _this_ time plus - * whatever leftover data we had from _last_ time still - * comes to less than SCC_BUFSIZE. In other words, we've - * already copied all the new data on to the end of our - * buffer, and it still hasn't helped. So increment buflen - * to reflect the new data, and return. - */ - assert(to_copy == len); - scc->buflen += to_copy; - goto out; - } - - /* - * Otherwise, we've somehow consumed _less_ data than we had - * buffered, and yet we weren't able to consume that data in - * the last call to this function. That sounds impossible, but - * I can think of one situation in which it could happen: if - * we had an incomplete MB sequence last time, and now more - * data has arrived, it turns out to be an _illegal_ one, so - * we consume one byte in the hope of resynchronising. - * - * Anyway, in this case we move the buffer up and go back - * round this initial loop. - */ - scc->buflen -= consumed; - memmove(scc->buf, scc->buf + consumed, scc->buflen); - } - - /* - * Now charge along the main string. - */ - while (len > 0) { - size_t consumed = stripctrl_locale_try_consume(scc, p, len); - if (consumed == 0) - break; - assert(consumed <= len); - p += consumed; - len -= consumed; - } - - /* - * Any data remaining should be copied into our buffer, to keep - * for next time. - */ - assert(len <= SCC_BUFSIZE); - memcpy(scc->buf, p, len); - scc->buflen = len; - - out: - setlocale(LC_CTYPE, previous_locale); - sfree(previous_locale); -} - -static void stripctrl_term_BinarySink_write( - BinarySink *bs, const void *vp, size_t len) -{ - StripCtrlChars *sccpub = BinarySink_DOWNCAST(bs, StripCtrlChars); - StripCtrlCharsImpl *scc = - container_of(sccpub, StripCtrlCharsImpl, public); - - bool utf = in_utf(scc->term); - if (utf != scc->last_term_utf) { - scc->last_term_utf = utf; - scc->utf8.state = 0; - } - - for (const unsigned char *p = (const unsigned char *)vp; - len > 0; len--, p++) { - unsigned long t = scc->translate(scc->term, &scc->utf8, *p); - if (t == UCSTRUNCATED) { - stripctrl_term_put_wc(scc, 0xFFFD); - /* go round again */ - t = scc->translate(scc->term, &scc->utf8, *p); - } - if (t == UCSINCOMPLETE) - continue; - if (t == UCSINVALID) - t = 0xFFFD; - - stripctrl_term_put_wc(scc, t); - } -} - -char *stripctrl_string_ptrlen(StripCtrlChars *sccpub, ptrlen str) -{ - strbuf *out = strbuf_new(); - stripctrl_retarget(sccpub, BinarySink_UPCAST(out)); - put_datapl(sccpub, str); - stripctrl_retarget(sccpub, NULL); - return strbuf_to_str(out); -} - -#ifdef STRIPCTRL_TEST - -/* -gcc -std=c99 -DSTRIPCTRL_TEST -o scctest stripctrl.c marshal.c utils.c memory.c wcwidth.c -I . -I unix -I charset -*/ - -void out_of_memory(void) { fprintf(stderr, "out of memory\n"); abort(); } - -void stripctrl_write(BinarySink *bs, const void *vdata, size_t len) -{ - const uint8_t *p = vdata; - printf("["); - for (size_t i = 0; i < len; i++) - printf("%*s%02x", i?1:0, "", (unsigned)p[i]); - printf("]"); -} - -void stripctrl_test(StripCtrlChars *scc, ptrlen pl) -{ - stripctrl_write(NULL, pl.ptr, pl.len); - printf(" -> "); - put_datapl(scc, pl); - printf("\n"); -} - -int main(void) -{ - struct foo { BinarySink_IMPLEMENTATION; } foo; - BinarySink_INIT(&foo, stripctrl_write); - StripCtrlChars *scc = stripctrl_new(BinarySink_UPCAST(&foo), false, '?'); - stripctrl_test(scc, PTRLEN_LITERAL("a\033[1mb")); - stripctrl_test(scc, PTRLEN_LITERAL("a\xC2\x9B[1mb")); - stripctrl_test(scc, PTRLEN_LITERAL("a\xC2\xC2[1mb")); - stripctrl_test(scc, PTRLEN_LITERAL("\xC3")); - stripctrl_test(scc, PTRLEN_LITERAL("\xA9")); - stripctrl_test(scc, PTRLEN_LITERAL("\xE2\x80\x8F")); - stripctrl_test(scc, PTRLEN_LITERAL("a\0b")); - stripctrl_free(scc); - return 0; -} - -#endif /* STRIPCTRL_TEST */ diff --git a/utils/tempseat.c b/utils/tempseat.c deleted file mode 100644 index 4e4e2b13a..000000000 --- a/utils/tempseat.c +++ /dev/null @@ -1,442 +0,0 @@ -/* - * Implementation of the Seat trait that buffers output and other - * events until it can give them back to a real Seat. - * - * This is used by the SSH proxying code, which temporarily takes over - * the real user-facing Seat so that it can issue host key warnings, - * password prompts etc for the proxy SSH connection. While it's got - * the real Seat, it gives the primary connection's backend one of - * these temporary Seats in the interim, so that if the backend wants - * to send some kind of initial output, or start by reconfiguring the - * trust status, or what have you, then it can do that without having - * to keep careful track of the fact that its Seat is out on loan. - */ - -#include "putty.h" - -struct output_chunk { - struct output_chunk *next; - SeatOutputType type; - size_t size; -}; - -typedef struct TempSeat TempSeat; -struct TempSeat { - Seat *realseat; - - /* - * Single bufchain to hold all the buffered output, regardless of - * its type. - */ - bufchain output; - - /* - * List of pieces of that bufchain that are intended for one or - * another output destination - */ - struct output_chunk *outchunk_head, *outchunk_tail; - - bool seen_session_started; - bool seen_remote_exit; - bool seen_remote_disconnect; - bool seen_update_specials_menu; - bool seen_echoedit_update, echoing, editing; - bool seen_trust_status, trusted; - - Seat seat; -}; - -/* ---------------------------------------------------------------------- - * Methods we can usefully buffer, and pass their results on to the - * real Seat in tempseat_flush(). - */ - -static size_t tempseat_output(Seat *seat, SeatOutputType type, - const void *data, size_t len) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - - bufchain_add(&ts->output, data, len); - - if (!(ts->outchunk_tail && ts->outchunk_tail->type == type)) { - struct output_chunk *new_chunk = snew(struct output_chunk); - - new_chunk->type = type; - new_chunk->size = 0; - - new_chunk->next = NULL; - if (ts->outchunk_tail) - ts->outchunk_tail->next = new_chunk; - else - ts->outchunk_head = new_chunk; - ts->outchunk_tail = new_chunk; - } - ts->outchunk_tail->size += len; - - return bufchain_size(&ts->output); -} - -static void tempseat_notify_session_started(Seat *seat) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - ts->seen_session_started = true; -} - -static void tempseat_notify_remote_exit(Seat *seat) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - ts->seen_remote_exit = true; -} - -static void tempseat_notify_remote_disconnect(Seat *seat) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - ts->seen_remote_disconnect = true; -} - -static void tempseat_update_specials_menu(Seat *seat) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - ts->seen_update_specials_menu = true; -} - -static void tempseat_echoedit_update(Seat *seat, bool echoing, bool editing) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - ts->seen_echoedit_update = true; - ts->echoing = echoing; - ts->editing = editing; -} - -static void tempseat_set_trust_status(Seat *seat, bool trusted) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - ts->seen_trust_status = true; - ts->trusted = trusted; -} - -/* ---------------------------------------------------------------------- - * Methods we can safely pass straight on to the real Seat, usually - * (but not in every case) because they're read-only queries. - */ - -static char *tempseat_get_ttymode(Seat *seat, const char *mode) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - return seat_get_ttymode(ts->realseat, mode); -} - -static void tempseat_set_busy_status(Seat *seat, BusyStatus status) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - /* - * set_busy_status is generally called when something is about to - * do some single-threaded, event-loop blocking computation. This - * _shouldn't_ happen in a backend while it's waiting for a - * network connection to be made, but if for some reason it were - * to, there's no reason we can't just pass this straight to the - * real seat, because we expect that it will mark itself busy, - * compute, and mark itself unbusy, all between yields to the - * event loop that might give whatever else is using the real Seat - * an opportunity to do anything. - */ - seat_set_busy_status(ts->realseat, status); -} - -static bool tempseat_is_utf8(Seat *seat) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - return seat_is_utf8(ts->realseat); -} - -static const char *tempseat_get_x_display(Seat *seat) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - return seat_get_x_display(ts->realseat); -} - -static bool tempseat_get_windowid(Seat *seat, long *id_out) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - return seat_get_windowid(ts->realseat, id_out); -} - -static bool tempseat_get_window_pixel_size(Seat *seat, int *width, int *height) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - return seat_get_window_pixel_size(ts->realseat, width, height); -} - -static StripCtrlChars *tempseat_stripctrl_new( - Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - return seat_stripctrl_new(ts->realseat, bs_out, sic); -} - -static bool tempseat_verbose(Seat *seat) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - return seat_verbose(ts->realseat); -} - -static bool tempseat_interactive(Seat *seat) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - return seat_interactive(ts->realseat); -} - -static bool tempseat_get_cursor_position(Seat *seat, int *x, int *y) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - return seat_get_cursor_position(ts->realseat, x, y); -} - -static bool tempseat_can_set_trust_status(Seat *seat) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - return seat_can_set_trust_status(ts->realseat); -} - -static bool tempseat_has_mixed_input_stream(Seat *seat) -{ - TempSeat *ts = container_of(seat, TempSeat, seat); - return seat_has_mixed_input_stream(ts->realseat); -} - -static const SeatDialogPromptDescriptions *tempseat_prompt_descriptions( - Seat *seat) -{ - /* It might be OK to put this in the 'unreachable' category, but I - * think it's equally good to put it here, which allows for - * someone _preparing_ a prompt right now that they intend to - * present once the TempSeat has given way to the real one. */ - TempSeat *ts = container_of(seat, TempSeat, seat); - return seat_prompt_descriptions(ts->realseat); -} - -/* ---------------------------------------------------------------------- - * Methods that should never be called on a TempSeat, so we can put an - * unreachable() in them. - * - * A backend in possession of a TempSeat ought to be sitting and - * patiently waiting for a network connection attempt to either - * succeed or fail. And it should be aware of the possibility that the - * proxy setup code to which it has lent the real Seat might need to - * present interactive prompts - that's the whole point of lending out - * the Seat in the first place - so it absolutely shouldn't get any - * ideas about issuing some kind of prompt of its own while it waits - * for the network connection. - */ - -static SeatPromptResult tempseat_get_userpass_input(Seat *seat, prompts_t *p) -{ - /* - * Interactive prompts of this nature are a thing that a backend - * MUST NOT do while not in possession of the real Seat, because - * the whole point of temporarily lending the real Seat to - * something else is that so it can have a clear field to do - * interactive stuff of its own while making a network connection. - */ - unreachable("get_userpass_input should never be called on TempSeat"); -} - -static size_t tempseat_banner(Seat *seat, const void *data, size_t len) -{ - unreachable("banner should never be called on TempSeat"); -} - -static SeatPromptResult tempseat_confirm_ssh_host_key( - Seat *seat, const char *host, int port, const char *keytype, - char *keystr, SeatDialogText *text, HelpCtx helpctx, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - unreachable("confirm_ssh_host_key should never be called on TempSeat"); -} - -static SeatPromptResult tempseat_confirm_weak_crypto_primitive( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - unreachable("confirm_weak_crypto_primitive " - "should never be called on TempSeat"); -} - -static SeatPromptResult tempseat_confirm_weak_cached_hostkey( - Seat *seat, SeatDialogText *text, - void (*callback)(void *ctx, SeatPromptResult result), void *ctx) -{ - unreachable("confirm_weak_cached_hostkey " - "should never be called on TempSeat"); -} - -static void tempseat_connection_fatal(Seat *seat, const char *message) -{ - /* - * Fatal errors are another thing a backend should not have any - * reason to encounter while waiting to hear back about its - * network connection setup. - * - * Also, if a backend _did_ call this, it would be hellish to - * unpick all the error handling. Just passing on the fatal error - * to the real Seat wouldn't be good enough: what about freeing - * all the various things that are confusingly holding pointers to - * each other? Better to leave this as an assertion-failure level - * issue, so that if it does ever happen by accident, we'll know - * it's a bug. - */ - unreachable("connection_fatal should never be called on TempSeat"); -} - -static void tempseat_nonfatal(Seat *seat, const char *message) -{ - /* - * Non-fatal errors specific to a Seat should also not occur, - * because those will be for things like I/O errors writing the - * host key collection, and a backend's not _doing_ that when we - * haven't connected it to the host yet. - */ - unreachable("nonfatal should never be called on TempSeat"); -} - -static bool tempseat_eof(Seat *seat) -{ - /* - * EOF is _very nearly_ something that we could buffer, and pass - * on to the real Seat at flush time. The only difficulty is that - * sometimes the front end wants to respond to an incoming EOF by - * instructing the back end to send an outgoing one, which it does - * by returning a bool from its eof method. - * - * So we'd have to arrange that tempseat_flush caught that return - * value and passed it on to the calling backend. And then every - * backend would have to deal with tempseat_flush maybe returning - * it an 'actually, please start closing down now' indication, - * which could only happen _in theory_, if it had for some reason - * called seat_eof on the TempSeat. - * - * But in fact, we don't expect back ends to call seat_eof on the - * TempSeat in the first place, so all of that effort would be a - * total waste. Hence, we'll put EOF in the category of things we - * expect backends never to do while the real Seat is out on loan. - */ - unreachable("eof should never be called on TempSeat"); -} - -/* ---------------------------------------------------------------------- - * Done with the TempSeat methods. Here's the vtable definition and - * the main setup/teardown code. - */ - -static const struct SeatVtable tempseat_vt = { - .output = tempseat_output, - .eof = tempseat_eof, - .sent = nullseat_sent, - .banner = tempseat_banner, - .get_userpass_input = tempseat_get_userpass_input, - .notify_session_started = tempseat_notify_session_started, - .notify_remote_exit = tempseat_notify_remote_exit, - .notify_remote_disconnect = tempseat_notify_remote_disconnect, - .connection_fatal = tempseat_connection_fatal, - .nonfatal = tempseat_nonfatal, - .update_specials_menu = tempseat_update_specials_menu, - .get_ttymode = tempseat_get_ttymode, - .set_busy_status = tempseat_set_busy_status, - .confirm_ssh_host_key = tempseat_confirm_ssh_host_key, - .confirm_weak_crypto_primitive = tempseat_confirm_weak_crypto_primitive, - .confirm_weak_cached_hostkey = tempseat_confirm_weak_cached_hostkey, - .prompt_descriptions = tempseat_prompt_descriptions, - .is_utf8 = tempseat_is_utf8, - .echoedit_update = tempseat_echoedit_update, - .get_x_display = tempseat_get_x_display, - .get_windowid = tempseat_get_windowid, - .get_window_pixel_size = tempseat_get_window_pixel_size, - .stripctrl_new = tempseat_stripctrl_new, - .set_trust_status = tempseat_set_trust_status, - .can_set_trust_status = tempseat_can_set_trust_status, - .has_mixed_input_stream = tempseat_has_mixed_input_stream, - .verbose = tempseat_verbose, - .interactive = tempseat_interactive, - .get_cursor_position = tempseat_get_cursor_position, -}; - -Seat *tempseat_new(Seat *realseat) -{ - TempSeat *ts = snew(TempSeat); - memset(ts, 0, sizeof(*ts)); - ts->seat.vt = &tempseat_vt; - - ts->realseat = realseat; - bufchain_init(&ts->output); - ts->outchunk_head = ts->outchunk_tail = NULL; - - return &ts->seat; -} - -bool is_tempseat(Seat *seat) -{ - return seat->vt == &tempseat_vt; -} - -Seat *tempseat_get_real(Seat *seat) -{ - assert(seat->vt == &tempseat_vt); - TempSeat *ts = container_of(seat, TempSeat, seat); - return ts->realseat; -} - -void tempseat_free(Seat *seat) -{ - assert(seat->vt == &tempseat_vt); - TempSeat *ts = container_of(seat, TempSeat, seat); - bufchain_clear(&ts->output); - while (ts->outchunk_head) { - struct output_chunk *chunk = ts->outchunk_head; - ts->outchunk_head = chunk->next; - sfree(chunk); - } - sfree(ts); -} - -void tempseat_flush(Seat *seat) -{ - assert(seat->vt == &tempseat_vt); - TempSeat *ts = container_of(seat, TempSeat, seat); - - /* Empty the output bufchains into the real seat, taking care to - * preserve both separation and interleaving */ - while (bufchain_size(&ts->output)) { - ptrlen pl = bufchain_prefix(&ts->output); - - assert(ts->outchunk_head); - struct output_chunk *chunk = ts->outchunk_head; - - if (pl.len > chunk->size) - pl.len = chunk->size; - - seat_output(ts->realseat, chunk->type, pl.ptr, pl.len); - bufchain_consume(&ts->output, pl.len); - chunk->size -= pl.len; - if (chunk->size == 0) { - ts->outchunk_head = chunk->next; - sfree(chunk); - } - } - - /* That should have exactly emptied the output chunk list too */ - assert(!ts->outchunk_head); - - /* Pass on any other kinds of event we've buffered */ - if (ts->seen_session_started) - seat_notify_session_started(ts->realseat); - if (ts->seen_remote_exit) - seat_notify_remote_exit(ts->realseat); - if (ts->seen_remote_disconnect) - seat_notify_remote_disconnect(ts->realseat); - if (ts->seen_update_specials_menu) - seat_update_specials_menu(ts->realseat); - if (ts->seen_echoedit_update) - seat_echoedit_update(ts->realseat, ts->echoing, ts->editing); - if (ts->seen_trust_status) - seat_set_trust_status(ts->realseat, ts->trusted); -} diff --git a/utils/tree234.c b/utils/tree234.c deleted file mode 100644 index 463f218c4..000000000 --- a/utils/tree234.c +++ /dev/null @@ -1,1638 +0,0 @@ -/* - * 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 "defs.h" -#include "tree234.h" -#include "puttymem.h" - -#ifdef TEST -static int verbose = 0; -#define LOG(x) do \ - { \ - if (verbose > 2) \ - printf x; \ - } while (0) -#else -#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 *t = snew(tree234); - LOG(("created tree %p\n", t)); - t->root = NULL; - t->cmp = cmp; - return t; -} - -/* - * 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; -} - -/* - * Internal function to return the number of elements in a node. - */ -static int elements234(node234 *n) -{ - int i; - for (i = 0; i < 3; i++) - if (!n->elems[i]) - break; - return i; -} - -/* - * 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) -{ - search234_state ss; - int reldir = (relation == REL234_LT || relation == REL234_LE ? -1 : - relation == REL234_GT || relation == REL234_GE ? +1 : 0); - bool equal_permitted = (relation != REL234_LT && relation != REL234_GT); - void *toret; - - /* Only LT / GT relations are permitted with a null query element. */ - assert(!(equal_permitted && !e)); - - if (cmp == NULL) - cmp = t->cmp; - - search234_start(&ss, t); - while (ss.element) { - int cmpret; - - if (e) { - cmpret = cmp(e, ss.element); - } else { - cmpret = -reldir; /* invent a fixed compare result */ - } - - if (cmpret == 0) { - /* - * We've found an element that compares exactly equal to - * the query element. - */ - if (equal_permitted) { - /* If our search relation permits equality, we've - * finished already. */ - if (index) - *index = ss.index; - return ss.element; - } else { - /* Otherwise, pretend this element was slightly too - * big/small, according to the direction of search. */ - cmpret = reldir; - } - } - - search234_step(&ss, cmpret); - } - - /* - * No element compares equal to the one we were after, but - * ss.index indicates the index that element would have if it were - * inserted. - * - * So if our search relation is EQ, we must simply return failure. - */ - if (relation == REL234_EQ) - return NULL; - - /* - * Otherwise, we must do an index lookup for the previous index - * (if we're going left - LE or LT) or this index (if we're going - * right - GE or GT). - */ - if (relation == REL234_LT || relation == REL234_LE) { - ss.index--; - } - - /* - * 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. - */ - toret = index234(t, ss.index); - if (toret && index) - *index = ss.index; - return toret; -} -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); -} - -void search234_start(search234_state *state, tree234 *t) -{ - state->_node = t->root; - state->_base = 0; /* index of first element in this node's subtree */ - state->_last = -1; /* indicate that this node is not previously visited */ - search234_step(state, 0); -} -void search234_step(search234_state *state, int direction) -{ - node234 *node = state->_node; - int i; - - if (!node) { - state->element = NULL; - state->index = 0; - return; - } - - if (state->_last != -1) { - /* - * We're already pointing at some element of a node, so we - * should restrict to the elements left or right of it, - * depending on the requested search direction. - */ - assert(direction); - assert(node); - - if (direction > 0) - state->_lo = state->_last + 1; - else - state->_hi = state->_last - 1; - - if (state->_lo > state->_hi) { - /* - * We've run out of elements in this node, i.e. we've - * narrowed to nothing but a child pointer. Descend to - * that child, and update _base to the leftmost index of - * its subtree. - */ - for (i = 0; i < state->_lo; i++) - state->_base += 1 + node->counts[i]; - state->_node = node = node->kids[state->_lo]; - state->_last = -1; - } - } - - if (state->_last == -1) { - /* - * We've just entered a new node - either because of the above - * code, or because we were called from search234_start - and - * anything in that node is a viable answer. - */ - state->_lo = 0; - state->_hi = node ? elements234(node)-1 : 0; - } - - /* - * Now we've got something we can return. - */ - if (!node) { - state->element = NULL; - state->index = state->_base; - } else { - state->_last = (state->_lo + state->_hi) / 2; - state->element = node->elems[state->_last]; - state->index = state->_base + state->_last; - for (i = 0; i <= state->_last; i++) - state->index += node->counts[i]; - } -} - -/* - * 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")); - 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 -#include - -int n_errors = 0; - -/* - * Error reporting function. - */ -PRINTF_LIKE(1, 2) void error(char *fmt, ...) -{ - va_list ap; - printf("ERROR: "); - va_start(ap, fmt); - vfprintf(stdout, fmt, ap); - va_end(ap); - printf("\n"); - n_errors++; -} - -/* 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); - } - - /* - * 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, (char *)lower, i + 1, (char *)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[1]; - 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); - } - if (verbose) - 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, (char *)array[i], (char *)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) - -void 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 (verbose) - printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j], - realret, index); - } - } - - 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, (char *)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, - (char *)array[arraylen - 1]); - } else if (!arraylen && (realret != NULL)) { - error("find(NULL,LT) gave %s(%d) should be NULL", realret, index); - } -} - -void searchtest_recurse(search234_state ss, int lo, int hi, - char **expected, char *directionbuf, - char *directionptr) -{ - *directionptr = '\0'; - - if (!ss.element) { - if (lo != hi) { - error("search234(%s) gave NULL for non-empty interval [%d,%d)", - directionbuf, lo, hi); - } else if (ss.index != lo) { - error("search234(%s) gave index %d should be %d", - directionbuf, ss.index, lo); - } else { - if (verbose) - printf("%*ssearch234(%s) gave NULL,%d\n", - (int)(directionptr-directionbuf) * 2, "", directionbuf, - ss.index); - } - } else if (lo == hi) { - error("search234(%s) gave %s for empty interval [%d,%d)", - directionbuf, (char *)ss.element, lo, hi); - } else if (ss.element != expected[ss.index]) { - error("search234(%s) gave element %s should be %s", - directionbuf, (char *)ss.element, expected[ss.index]); - } else if (ss.index < lo || ss.index >= hi) { - error("search234(%s) gave index %d should be in [%d,%d)", - directionbuf, ss.index, lo, hi); - return; - } else { - search234_state next; - - if (verbose) - printf("%*ssearch234(%s) gave %s,%d\n", - (int)(directionptr-directionbuf) * 2, "", directionbuf, - (char *)ss.element, ss.index); - - next = ss; - search234_step(&next, -1); - *directionptr = '-'; - searchtest_recurse(next, lo, ss.index, - expected, directionbuf, directionptr+1); - - next = ss; - search234_step(&next, +1); - *directionptr = '+'; - searchtest_recurse(next, ss.index+1, hi, - expected, directionbuf, directionptr+1); - } -} - -void searchtest(void) -{ - char *expected[NSTR], *p; - char directionbuf[NSTR * 10]; - int n; - search234_state ss; - - if (verbose) - printf("beginning searchtest:"); - for (n = 0; (p = index234(tree, n)) != NULL; n++) { - expected[n] = p; - if (verbose) - printf(" %d=%s", n, p); - } - if (verbose) - printf(" count=%d\n", n); - - search234_start(&ss, tree); - searchtest_recurse(ss, 0, n, expected, directionbuf, directionbuf); -} - -void out_of_memory(void) -{ - fprintf(stderr, "out of memory!\n"); - exit(2); -} - -int main(int argc, char **argv) -{ - int in[NSTR]; - int i, j, k; - unsigned seed = 0; - - for (i = 1; i < argc; i++) { - char *arg = argv[i]; - if (!strcmp(arg, "-v")) { - verbose++; - } else { - fprintf(stderr, "unrecognised option '%s'\n", arg); - return 1; - } - } - - for (i = 0; i < NSTR; i++) - in[i] = 0; - array = NULL; - arraylen = arraysize = 0; - tree = newtree234(mycmp); - cmp = mycmp; - - verify(); - searchtest(); - for (i = 0; i < 10000; i++) { - j = randomnumber(&seed); - j %= NSTR; - if (verbose) - printf("trial: %d\n", i); - if (in[j]) { - if (verbose) - printf("deleting %s (%d)\n", strings[j], j); - deltest(strings[j]); - in[j] = 0; - } else { - if (verbose) - printf("adding %s (%d)\n", strings[j], j); - addtest(strings[j]); - in[j] = 1; - } - findtest(); - searchtest(); - } - - 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++) { - if (verbose) - printf("trial: %d\n", i); - j = randomnumber(&seed); - j %= NSTR; - k = randomnumber(&seed); - k %= count234(tree) + 1; - if (verbose) - printf("adding string %s at index %d\n", strings[j], k); - addpostest(strings[j], k); - } - while (count234(tree) > 0) { - if (verbose) - printf("cleanup: tree size %d\n", count234(tree)); - j = randomnumber(&seed); - j %= count234(tree); - if (verbose) - printf("deleting string %s from index %d\n", - (const char *)array[j], j); - delpostest(j); - } - - printf("%d errors found\n", n_errors); - return (n_errors != 0); -} - -#endif /* TEST */ diff --git a/utils/unicode-known.c b/utils/unicode-known.c deleted file mode 100644 index 01b9b8a4c..000000000 --- a/utils/unicode-known.c +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Check a UTF-8 string to ensure every character in it is part of the - * version of Unicode that we understand. - * - * (If it isn't, then we don't know what combining properties it has, - * so we can't safely NFC it and rely on the result not changing when - * we later update our Unicode version.) - */ - -#include "misc.h" -#include "unicode/version.h" - -static bool known(unsigned c) -{ - struct range { - unsigned start, end; - }; - static const struct range ranges[] = { - #include "unicode/known_chars.h" - }; - - const struct range *start = ranges, *end = start + lenof(ranges); - - while (end > start) { - const struct range *curr = start + (end-start) / 2; - if (c < curr->start) - end = curr; - else if (c > curr->end) - start = curr + 1; - else - return true; - } - - return false; -}; - -char *utf8_unknown_char(ptrlen input) -{ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, input); - - for (size_t nchars = 0; get_avail(src); nchars++) { - DecodeUTF8Failure err; - unsigned c = decode_utf8(src, &err); - if (err != DUTF8_SUCCESS) - return dupprintf( - "cannot normalise this string: UTF-8 decoding error " - "at character position %"SIZEu", byte position %"SIZEu": %s", - nchars, src->pos, decode_utf8_error_strings[err]); - if (!known(c)) - return dupprintf( - "cannot stably normalise this string: code point %04X " - "(at character position %"SIZEu", byte position %"SIZEu") " - "is not in Unicode %s", c, nchars, src->pos, - UNICODE_VERSION_SHORT); - } - - return NULL; -} diff --git a/utils/unicode-norm.c b/utils/unicode-norm.c deleted file mode 100644 index af620fd3c..000000000 --- a/utils/unicode-norm.c +++ /dev/null @@ -1,453 +0,0 @@ -#include -#include -#include "misc.h" - -typedef uint32_t uchar; -typedef int cclass_t; - -/* A local uchar-oriented analogue of strbuf */ -typedef struct ucharbuf { - uchar *buf; - size_t len, size; -} ucharbuf; - -static ucharbuf *ucharbuf_new(void) -{ - ucharbuf *ub = snew(ucharbuf); - ub->buf = NULL; - ub->len = ub->size = 0; - return ub; -} - -static void ucharbuf_append(ucharbuf *ub, uchar c) -{ - /* Use the _nm variant because this is used for passphrases */ - sgrowarray_nm(ub->buf, ub->size, ub->len); - ub->buf[ub->len++] = c; -} - -static void ucharbuf_free(ucharbuf *ub) -{ - if (ub->buf) { - memset(ub->buf, 0, ub->size * sizeof(*ub->buf)); - sfree(ub->buf); - } - sfree(ub); -} - -/* - * Constants relating to the arithmetic decomposition mapping of - * Hangul to jamo, from section 3.12 of Unicode 15.0.0. The following - * constant names match those in the spec. - */ -enum { - SBase = 0xAC00, /* base index for precomposed Hangul */ - LBase = 0x1100, /* base index for L (leading consonant) jamo */ - VBase = 0x1161, /* base index for V (vowel) jamo */ - TBase = 0x11A7, /* base index for T (trailing consonant) jamo */ - LCount = 19, /* number of L jamo */ - VCount = 21, /* number of V jamo */ - TCount = 28, /* number of T jamo, including not having one at all */ - NCount = VCount * TCount, /* number of Hangul for each L jamo */ - SCount = LCount * NCount, /* number of Hangul in total */ -}; - -static cclass_t combining_class(uchar c) -{ - struct range { - uchar start, end; - cclass_t cclass; - }; - static const struct range ranges[] = { - #include "unicode/combining_classes.h" - }; - - const struct range *start = ranges, *end = start + lenof(ranges); - - while (end > start) { - const struct range *curr = start + (end-start) / 2; - if (c < curr->start) - end = curr; - else if (c > curr->end) - start = curr + 1; - else - return curr->cclass; - } - - return 0; -}; - -static unsigned decompose_char(uchar c, uchar *out) -{ - struct decomp { - uchar composed, dec0, dec1; - }; - static const struct decomp decomps[] = { - #include "unicode/canonical_decomp.h" - }; - - if (c - SBase < SCount) { - /* Arithmetically decompose a Hangul character into jamo */ - uchar SIndex = c - SBase; - uchar LIndex = SIndex / NCount; - uchar VIndex = SIndex % NCount / TCount; - uchar TIndex = SIndex % TCount; - - unsigned n = 0; - out[n++] = LBase + LIndex; - out[n++] = VBase + VIndex; - if (TIndex) - out[n++] = TBase + TIndex; - return n; - } - - const struct decomp *start = decomps, *end = start + lenof(decomps); - - while (end > start) { - const struct decomp *curr = start + (end-start) / 2; - if (c < curr->composed) - end = curr; - else if (c > curr->composed) - start = curr + 1; - else { - out[0] = curr->dec0; - if (curr->dec1) { - out[1] = curr->dec1; - return 2; - } else { - return 1; - } - } - } - - return 0; -}; - -static uchar compose_chars(uchar S, uchar C) -{ - struct comp { - uchar dec0, dec1, composed; - }; - static const struct comp comps[] = { - #include "unicode/canonical_comp.h" - }; - - if (S - LBase < LCount && C - VBase < VCount) { - /* Arithmetically compose an L and V jamo into a Hangul LV - * character */ - return SBase + (S - LBase) * NCount + (C - VBase) * TCount; - } - - if (S - SBase < SCount && (S - SBase) % TCount == 0 && - C - TBase < TCount) { - /* Arithmetically compose an LV Hangul character and a T jamo - * into a Hangul LVT character */ - return S + C - TBase; - } - - const struct comp *start = comps, *end = start + lenof(comps); - - while (end > start) { - const struct comp *curr = start + (end-start) / 2; - if (S < curr->dec0) - end = curr; - else if (S > curr->dec0) - start = curr + 1; - else if (C < curr->dec1) - end = curr; - else if (C > curr->dec1) - start = curr + 1; - else - return curr->composed; - } - - return 0; -}; - -/* - * Recursively decompose a sequence of Unicode characters. The output - * is written to 'out', as a sequence of native-byte-order uchar. - */ -static void recursively_decompose(const uchar *str, size_t len, ucharbuf *out) -{ - uchar decomposed[3]; - - while (len-- > 0) { - uchar c = *str++; - unsigned n = decompose_char(c, decomposed); - if (n == 0) { - /* This character is indecomposable */ - ucharbuf_append(out, c); - } else { - /* This character has been decomposed into up to 3 - * characters, so we must now recursively decompose those */ - recursively_decompose(decomposed, n, out); - } - } -} - -/* - * Reorder combining marks according to the Canonical Ordering - * Algorithm (definition D109 in Unicode 15.0.0 section 3.11). - * - * The algorithm is phrased mechanistically, but the essence is: among - * any contiguous sequence of combining marks (that is, characters - * with cclass > 0), sort them by their cclass - but _stably_, i.e. - * breaking ties in cclass by preserving the original order of the - * characters in question. - */ -static void canonical_ordering(uchar *str, size_t len) -{ - for (size_t i = 1; i < len; i++) { - cclass_t cclass = combining_class(str[i]); - if (cclass == 0) - continue; - - size_t j = i; - while (j > 0 && combining_class(str[j-1]) > cclass) { - uchar tmp = str[j-1]; - str[j-1] = str[j]; - str[j] = tmp; - - j--; - } - } -} - -/* - * Canonically recompose characters according to the Canonical - * Composition Algorithm (definition D117 in Unicode 15.0.0 section - * 3.11). - */ -static size_t canonical_composition(uchar *str, size_t len) -{ - const uchar *in = str; - uchar *out = str; - uchar *last_starter = NULL; - cclass_t highest_cclass_between = -1; - - while (len > 0) { - len--; - uchar c = *in++; - cclass_t cclass = combining_class(c); - - if (last_starter && highest_cclass_between < cclass) { - uchar composed = compose_chars(*last_starter, c); - if (composed) { - *last_starter = composed; - continue; - } - } - - if (cclass == 0) { - last_starter = out; - highest_cclass_between = -1; - } else if (cclass > highest_cclass_between) { - highest_cclass_between = cclass; - } - - *out++ = c; - } - - return out - str; -} - -/* - * Render a string into NFD. - */ -static ucharbuf *nfd(ucharbuf *input) -{ - ucharbuf *output = ucharbuf_new(); - - /* - * Definition D118 in Unicode 15.0.0 section 3.11, referring to - * D68 in section 3.7: recursively decompose characters, then - * reorder combining marks. - */ - recursively_decompose(input->buf, input->len, output); - canonical_ordering(output->buf, output->len); - - return output; -} - -/* - * Render a string into NFC. - */ -static ucharbuf *nfc(ucharbuf *input) -{ - /* - * Definition D120 in Unicode 15.0.0 section 3.11: render the - * string into NFD, then apply the canonical composition algorithm. - */ - ucharbuf *output = nfd(input); - output->len = canonical_composition(output->buf, output->len); - - return output; -} - -/* - * Convert a UTF-8 string into NFC, returning it as UTF-8 again. - */ -strbuf *utf8_to_nfc(ptrlen input) -{ - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, input); - - ucharbuf *inbuf = ucharbuf_new(); - while (get_avail(src)) - ucharbuf_append(inbuf, decode_utf8(src, NULL)); - - ucharbuf *outbuf = nfc(inbuf); - - strbuf *output = strbuf_new_nm(); - for (size_t i = 0; i < outbuf->len; i++) - put_utf8_char(output, outbuf->buf[i]); - - ucharbuf_free(inbuf); - ucharbuf_free(outbuf); - - return output; -} - -#ifdef TEST -void out_of_memory(void) -{ - fprintf(stderr, "out of memory!\n"); - exit(2); -} - -static int pass, fail; - -static void subtest(const char *filename, int lineno, const char *subdesc, - char nftype, ucharbuf *input, ucharbuf *expected) -{ - /* - * Convert input into either NFC or NFD, and check it's equal to - * expected - */ - ucharbuf *nf; - switch (nftype) { - case 'C': - nf = nfc(input); - break; - case 'D': - nf = nfd(input); - break; - default: - unreachable("bad nftype"); - } - - if (nf->len == expected->len && !memcmp(nf->buf, expected->buf, nf->len)) { - pass++; - } else { - printf("%s:%d: failed %s: NF%c([", filename, lineno, subdesc, nftype); - for (size_t pos = 0; pos < input->len; pos += sizeof(uchar)) - printf("%s%04X", pos ? " " : "", (unsigned)input->buf[pos]); - printf("]) -> ["); - for (size_t pos = 0; pos < nf->len; pos += sizeof(uchar)) - printf("%s%04X", pos ? " " : "", (unsigned)nf->buf[pos]); - printf("] != ["); - for (size_t pos = 0; pos < expected->len; pos += sizeof(uchar)) - printf("%s%04X", pos ? " " : "", (unsigned)expected->buf[pos]); - printf("]\n"); - fail++; - } - - ucharbuf_free(nf); -} - -static void run_tests(const char *filename, FILE *fp) -{ - for (int lineno = 1;; lineno++) { - char *line = chomp(fgetline(fp)); - if (!line) - break; - - /* Strip section dividers which begin with @ */ - if (*line == '@') { - sfree(line); - continue; - } - - /* Strip comments, if any */ - ptrlen pl = ptrlen_from_asciz(line); - { - const char *p = memchr(pl.ptr, '#', pl.len); - if (p) - pl.len = p - (const char *)pl.ptr; - } - - /* Strip trailing space */ - while (pl.len > 0 && - (((char *)pl.ptr)[pl.len-1] == ' ' || - ((char *)pl.ptr)[pl.len-1] == '\t')) - pl.len--; - - /* Skip empty lines */ - if (!pl.len) { - sfree(line); - continue; - } - - /* Break up at semicolons, expecting five fields, each of - * which we decode into hex code points */ - ucharbuf *fields[5]; - for (size_t i = 0; i < lenof(fields); i++) { - ptrlen field = ptrlen_get_word(&pl, ";"); - fields[i] = ucharbuf_new(); - - ptrlen chr; - while ((chr = ptrlen_get_word(&field, " ")).len) { - char *chrstr = mkstr(chr); - uchar c = strtoul(chrstr, NULL, 16); - sfree(chrstr); - ucharbuf_append(fields[i], c); - } - } - - subtest(filename, lineno, "NFC(c1) = c2", 'C', fields[0], fields[1]); - subtest(filename, lineno, "NFC(c2) = c2", 'C', fields[1], fields[1]); - subtest(filename, lineno, "NFC(c3) = c2", 'C', fields[2], fields[1]); - subtest(filename, lineno, "NFC(c4) = c4", 'C', fields[3], fields[3]); - subtest(filename, lineno, "NFC(c5) = c4", 'C', fields[4], fields[3]); - subtest(filename, lineno, "NFD(c1) = c3", 'D', fields[0], fields[2]); - subtest(filename, lineno, "NFD(c2) = c3", 'D', fields[1], fields[2]); - subtest(filename, lineno, "NFD(c3) = c3", 'D', fields[2], fields[2]); - subtest(filename, lineno, "NFD(c4) = c5", 'D', fields[3], fields[4]); - subtest(filename, lineno, "NFD(c5) = c5", 'D', fields[4], fields[4]); - - for (size_t i = 0; i < lenof(fields); i++) - ucharbuf_free(fields[i]); - - sfree(line); - } -} - -int main(int argc, char **argv) -{ - if (argc != 2) { - fprintf(stderr, "test_unicode_norm: give an input file " - "of tests or '-'\n"); - return 1; - } - - const char *filename = argv[1]; - - if (!strcmp(filename, "-")) { - run_tests("", stdin); - } else { - FILE *fp = fopen(filename, "r"); - if (!fp) { - fprintf(stderr, "test_unicode_norm: unable to open '%s'\n", - filename); - return 1; - } - run_tests(filename, fp); - fclose(fp); - } - - printf("pass %d fail %d total %d\n", pass, fail, pass + fail); - - return fail != 0; -} -#endif diff --git a/utils/utils.h b/utils/utils.h deleted file mode 100644 index ea4cda0cc..000000000 --- a/utils/utils.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Internal header to the utils subdirectory, for definitions shared - * between the library implementations but not intended to be exposed - * further than that. - */ - -void dputs(const char *); - -char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr, - const char *fmt, va_list ap); - -const char *host_strchr_internal(const char *s, const char *set, bool first); diff --git a/utils/validate_manual_hostkey.c b/utils/validate_manual_hostkey.c deleted file mode 100644 index dd7c1aee4..000000000 --- a/utils/validate_manual_hostkey.c +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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. - */ - -#include -#include - -#include "putty.h" -#include "misc.h" - -#define BASE64_CHARS_NOEQ \ - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/" -#define BASE64_CHARS_ALL BASE64_CHARS_NOEQ "=" - -bool 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 (strstartswith(q, "SHA256:")) { - /* Test for a valid SHA256 key fingerprint. */ - r = q + 7; - if (strspn(r, BASE64_CHARS_NOEQ) == 43) { - memmove(key, q, 50); /* 7-char prefix + 43-char base64 */ - key[50] = '\0'; - return true; - } - } - - r = q; - if (strstartswith(r, "MD5:")) - r += 4; - if (strlen(r) == 16*3 - 1 && - r[strspn(r, "0123456789abcdefABCDEF:")] == 0) { - /* - * Test for a valid MD5 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 (r[3*i] == ':' || r[3*i+1] == ':') - goto not_fingerprint; /* sorry */ - for (i = 0; i < 15; i++) - if (r[3*i+2] != ':') - goto not_fingerprint; /* sorry */ - for (i = 0; i < 16*3 - 1; i++) - key[i] = tolower((unsigned char)r[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, BASE64_CHARS_ALL)] == 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 */ - - size_t base64_len = strspn(q, BASE64_CHARS_ALL); - memmove(key, q, base64_len); - key[base64_len] = '\0'; - return true; - } - not_ssh2_blob:; - } - - return false; -} diff --git a/utils/version.c b/utils/version.c deleted file mode 100644 index edc4da99a..000000000 --- a/utils/version.c +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 "putty.h" -#include "ssh.h" - -#include "version.h" - -const char ver[] = TEXTVER; -const 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/utils/wcwidth.c b/utils/wcwidth.c deleted file mode 100644 index 018e4c5b2..000000000 --- a/utils/wcwidth.c +++ /dev/null @@ -1,209 +0,0 @@ -/* - * 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 bool 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 false; - 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 true; - } - - return false; -} - - -/* 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 */ - static const struct interval combining[] = { - #include "unicode/nonspacing_chars.h" - }; - - /* A sorted list of intervals of double-width characters */ - static const struct interval wide[] = { - #include "unicode/wide_chars.h" - }; - - /* 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 */ - - /* binary search in table of double-width characters */ - if (bisearch(ucs, wide, - sizeof(wide) / sizeof(struct interval) - 1)) - return 2; - - /* normal width character */ - return 1; -} - - -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) -{ - /* A sorted list of intervals of ambiguous width characters */ - static const struct interval ambiguous[] = { - #include "unicode/ambiguous_wide_chars.h" - }; - - /* 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/utils/wildcard.c b/utils/wildcard.c deleted file mode 100644 index 255cc53f3..000000000 --- a/utils/wildcard.c +++ /dev/null @@ -1,486 +0,0 @@ -/* - * 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 *target_end) -{ - 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 < target_end) { - /* - * 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 == '[') { - bool invert = false; - bool matched = false; - /* - * Open bracket introduces a character class. - */ - f++; - if (*f == '^') { - invert = true; - 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 = true; - } 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. - */ -static int wc_match_inner( - const char *wildcard, const char *target, size_t target_len) -{ - const char *target_end = target + target_len; - 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, target_end); - 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, target_end); - - if (ret < 0) - return ret; /* syntax error */ - - if (ret > 0 && !*wildcard && target != target_end) { - /* - * 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 = target_end - (target - save_t); - wildcard = save_w; - return wc_match_fragment(&wildcard, &target, target_end); - } - - 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 == target_end; -} - -int wc_match(const char *wildcard, const char *target) -{ - return wc_match_inner(wildcard, target, strlen(target)); -} - -int wc_match_pl(const char *wildcard, ptrlen target) -{ - return wc_match_inner(wildcard, target.ptr, target.len); -} - -/* - * 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 true on success, or false 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! - */ -bool 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 false; /* it's a wildcard! */ - } else { - if (output) - *output++ = *wildcard; - wildcard++; - } - } - if (output) - *output = '\0'; - return true; /* it's clean */ -} - -#ifdef TEST - -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, t + strlen(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 /* TEST */ diff --git a/utils/wordwrap.c b/utils/wordwrap.c deleted file mode 100644 index 330b6a03a..000000000 --- a/utils/wordwrap.c +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Function to wrap text to a fixed number of columns. - * - * Currently, assumes the text is in a single-byte character set, - * because it's only used to display host key prompt messages. - * Extending to Unicode and using wcwidth() could be an extension. - */ - -#include "misc.h" - -void wordwrap(BinarySink *bs, ptrlen input, size_t maxwid) -{ - size_t col = 0; - while (true) { - ptrlen word = ptrlen_get_word(&input, " "); - if (!word.len) - break; - - /* At the start of a line, any word is legal, even if it's - * overlong, because we have to display it _somehow_ and - * wrapping to the next line won't make it any better. */ - if (col > 0) { - size_t newcol = col + 1 + word.len; - if (newcol <= maxwid) { - put_byte(bs, ' '); - col++; - } else { - put_byte(bs, '\n'); - col = 0; - } - } - - put_datapl(bs, word); - col += word.len; - } -} diff --git a/utils/write_c_string_literal.c b/utils/write_c_string_literal.c deleted file mode 100644 index e8b2f53ba..000000000 --- a/utils/write_c_string_literal.c +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Write data to a file or BinarySink in the form of a C string - * literal, with any non-printable-ASCII character escaped - * appropriately. - */ - -#include "defs.h" -#include "misc.h" - -void BinarySink_put_c_string_literal(BinarySink *bs, ptrlen str) -{ - for (const char *p = str.ptr; p < (const char *)str.ptr + str.len; p++) { - char c = *p; - - if (c == '\n') - put_datalit(bs, "\\n"); - else if (c == '\r') - put_datalit(bs, "\\r"); - else if (c == '\t') - put_datalit(bs, "\\t"); - else if (c == '\b') - put_datalit(bs, "\\b"); - else if (c == '\\') - put_datalit(bs, "\\\\"); - else if (c == '"') - put_datalit(bs, "\\\""); - else if (c >= 32 && c <= 126) - put_byte(bs, c); - else - put_fmt(bs, "\\%03o", (unsigned)c & 0xFFU); - } -} - -void write_c_string_literal(FILE *fp, ptrlen str) -{ - stdio_sink s; - stdio_sink_init(&s, fp); - put_c_string_literal(&s, str); -} diff --git a/utils/x11_dehexify.c b/utils/x11_dehexify.c deleted file mode 100644 index 080f8c915..000000000 --- a/utils/x11_dehexify.c +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Utility function to convert a textual representation of an X11 - * auth hex cookie into binary auth data. - */ - -#include "putty.h" -#include "ssh.h" - -void *x11_dehexify(ptrlen hexpl, int *outlen) -{ - int len, i; - unsigned char *ret; - - len = hexpl.len / 2; - ret = snewn(len, unsigned char); - - for (i = 0; i < len; i++) { - char bytestr[3]; - unsigned val = 0; - bytestr[0] = ((const char *)hexpl.ptr)[2*i]; - bytestr[1] = ((const char *)hexpl.ptr)[2*i+1]; - bytestr[2] = '\0'; - sscanf(bytestr, "%x", &val); - ret[i] = val; - } - - *outlen = len; - return ret; -} diff --git a/utils/x11_identify_auth_proto.c b/utils/x11_identify_auth_proto.c deleted file mode 100644 index 46e26dc0f..000000000 --- a/utils/x11_identify_auth_proto.c +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Utility function to convert a textual representation of an X11 - * auth protocol name into our integer protocol ids. - */ - -#include "putty.h" -#include "ssh.h" - -int x11_identify_auth_proto(ptrlen protoname) -{ - int protocol; - - for (protocol = 1; protocol < lenof(x11_authnames); protocol++) - if (ptrlen_eq_string(protoname, x11_authnames[protocol])) - return protocol; - return -1; -} diff --git a/utils/x11_make_greeting.c b/utils/x11_make_greeting.c deleted file mode 100644 index 4efd8e83b..000000000 --- a/utils/x11_make_greeting.c +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Construct an X11 greeting packet, including making up the right - * authorisation data. - */ - -#include "putty.h" -#include "ssh.h" - -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((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_X11(endian, greeting+2, protomajor); - PUT_16BIT_X11(endian, greeting+4, protominor); - PUT_16BIT_X11(endian, greeting+6, authnamelen); - PUT_16BIT_X11(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/utils/x11_parse_ip.c b/utils/x11_parse_ip.c deleted file mode 100644 index 8f6a7bfb8..000000000 --- a/utils/x11_parse_ip.c +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Try to make sense of a string as an IPv4 address, for - * XDM-AUTHORIZATION-1 purposes. - */ - -#include - -#include "putty.h" -#include "ssh.h" - -bool x11_parse_ip(const char *addr_string, unsigned long *ip) -{ - 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; - } -} diff --git a/utils/x11authfile.c b/utils/x11authfile.c deleted file mode 100644 index ffc91e9c1..000000000 --- a/utils/x11authfile.c +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Functions to handle .Xauthority files. - */ - -#include -#include -#include - -#include "putty.h" -#include "ssh.h" - -static ptrlen BinarySource_get_string_xauth(BinarySource *src) -{ - size_t len = get_uint16(src); - return get_data(src, len); -} -#define get_string_xauth(src) \ - BinarySource_get_string_xauth(BinarySource_UPCAST(src)) - -static void BinarySink_put_stringpl_xauth(BinarySink *bs, ptrlen pl) -{ - assert((pl.len >> 16) == 0); - put_uint16(bs, pl.len); - put_datapl(bs, pl); -} -#define put_stringpl_xauth(bs, ptrlen) \ - BinarySink_put_stringpl_xauth(BinarySink_UPCAST(bs),ptrlen) - -void x11_get_auth_from_authfile(struct X11Display *disp, - Filename *authfilename) -{ - FILE *authfp; - char *buf; - int size; - BinarySource src[1]; - int family, protocol; - ptrlen addr, protoname, data; - char *displaynum_string; - int displaynum; - bool ideal_match = false; - char *ourhostname; - - /* A maximally sized (wildly implausible) .Xauthority record - * consists of a 16-bit integer to start with, then four strings, - * each of which has a 16-bit length field followed by that many - * bytes of data (i.e. up to 0xFFFF bytes). */ - const size_t MAX_RECORD_SIZE = 2 + 4 * (2+0xFFFF); - - /* We'll want a buffer of twice that size (see below). */ - const size_t BUF_SIZE = 2 * MAX_RECORD_SIZE; - - /* - * 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. - */ - bool localhost = !disp->unixdomain && sk_address_is_local(disp->addr); - - authfp = f_open(authfilename, "rb", false); - if (!authfp) - return; - - ourhostname = get_hostname(); - - /* - * Allocate enough space to hold two maximally sized records, so - * that a full record can start anywhere in the first half. That - * way we avoid the accidentally-quadratic algorithm that would - * arise if we moved everything to the front of the buffer after - * consuming each record; instead, we only move everything to the - * front after our current position gets past the half-way mark. - * Before then, there's no need to move anyway; so this guarantees - * linear time, in that every byte written into this buffer moves - * at most once (because every move is from the second half of the - * buffer to the first half). - */ - buf = snewn(BUF_SIZE, char); - size = fread(buf, 1, BUF_SIZE, authfp); - BinarySource_BARE_INIT(src, buf, size); - - while (!ideal_match) { - bool match = false; - - if (src->pos >= MAX_RECORD_SIZE) { - size -= src->pos; - memcpy(buf, buf + src->pos, size); - size += fread(buf + size, 1, BUF_SIZE - size, authfp); - BinarySource_BARE_INIT(src, buf, size); - } - - family = get_uint16(src); - addr = get_string_xauth(src); - displaynum_string = mkstr(get_string_xauth(src)); - displaynum = displaynum_string[0] ? atoi(displaynum_string) : -1; - sfree(displaynum_string); - protoname = get_string_xauth(src); - data = get_string_xauth(src); - if (get_err(src)) - break; - - /* - * 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. - * - * - 'addr' 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). - * - * - 'displaynum' is the display number. An empty display - * number is a wildcard for any display number. - * - * - 'protoname' is the authorisation protocol, encoded as - * its canonical string name (i.e. "MIT-MAGIC-COOKIE-1", - * "XDM-AUTHORIZATION-1" or something we don't recognise). - * - * - 'data' is the actual authorisation data, stored in - * binary form. - */ - - if (disp->displaynum < 0 || - (displaynum >= 0 && disp->displaynum != displaynum)) - continue; /* not the one */ - - for (protocol = 1; protocol < lenof(x11_authnames); protocol++) - if (ptrlen_eq_string(protoname, 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 (addr.len == 4 && !memcmp(addr.ptr, 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 (addr.len == 16 && !memcmp(addr.ptr, buf, 16)) { - match = true; - ideal_match = !localhost; - } - } - break; - case 256: /* Unix-domain / localhost */ - if ((disp->unixdomain || localhost) - && ourhostname && ptrlen_eq_string(addr, ourhostname)) { - /* A matching Unix-domain socket is always the best - * match. */ - match = true; - 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(data.len, unsigned char); - memcpy(disp->localauthdata, data.ptr, data.len); - disp->localauthdatalen = data.len; - } - } - - fclose(authfp); - smemclr(buf, 2 * MAX_RECORD_SIZE); - sfree(buf); - sfree(ourhostname); -} - -void x11_format_auth_for_authfile( - BinarySink *bs, SockAddr *addr, int display_no, - ptrlen authproto, ptrlen authdata) -{ - if (sk_address_is_special_local(addr)) { - char *ourhostname = get_hostname(); - put_uint16(bs, 256); /* indicates Unix-domain socket */ - put_stringpl_xauth(bs, ptrlen_from_asciz(ourhostname)); - sfree(ourhostname); - } else if (sk_addrtype(addr) == ADDRTYPE_IPV4) { - char ipv4buf[4]; - sk_addrcopy(addr, ipv4buf); - put_uint16(bs, 0); /* indicates IPv4 */ - put_stringpl_xauth(bs, make_ptrlen(ipv4buf, 4)); - } else if (sk_addrtype(addr) == ADDRTYPE_IPV6) { - char ipv6buf[16]; - sk_addrcopy(addr, ipv6buf); - put_uint16(bs, 6); /* indicates IPv6 */ - put_stringpl_xauth(bs, make_ptrlen(ipv6buf, 16)); - } else { - unreachable("Bad address type in x11_format_auth_for_authfile"); - } - - { - char *numberbuf = dupprintf("%d", display_no); - put_stringpl_xauth(bs, ptrlen_from_asciz(numberbuf)); - sfree(numberbuf); - } - - put_stringpl_xauth(bs, authproto); - put_stringpl_xauth(bs, authdata); -} diff --git a/utils/x11authnames.c b/utils/x11authnames.c deleted file mode 100644 index ef3c26732..000000000 --- a/utils/x11authnames.c +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Definition of the array of X11 authorisation method names. - */ - -#include "putty.h" - -const char *const x11_authnames[X11_NAUTHS] = { - "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1" -}; diff --git a/version.h b/version.h deleted file mode 100644 index 492825fdf..000000000 --- a/version.h +++ /dev/null @@ -1,13 +0,0 @@ -/* - * This header file provides the various versioning-related #defines - * for a particular PuTTY build. - * - * When my automated build system does a full build, Buildscr - * completely overwrites this file with information derived from the - * circumstances and type of that build. The information _here_ is - * default stuff used for local development runs of 'make'. - */ - -#define TEXTVER "PuTTYNG" -#define SSHVER "PuTTYNG" -#define BINARY_VERSION 0,78,0,0 diff --git a/x11disp.c b/x11disp.c deleted file mode 100644 index 1dbc9c077..000000000 --- a/x11disp.c +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Functions to manage an X11Display structure, by creating one from - * an ordinary display name string, and freeing one. - */ - -#include -#include -#include -#include - -#include "putty.h" -#include "ssh.h" -#include "ssh/channel.h" -#include "tree234.h" - -struct X11Display *x11_setup_display(const char *display, Conf *conf, - char **error_msg) -{ - struct X11Display *disp = snew(struct X11Display); - char *localcopy; - - *error_msg = NULL; - - 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) { - *error_msg = dupprintf("display name '%s' has no ':number'" - " suffix", localcopy); - - sfree(disp); - sfree(localcopy); - return NULL; - } - - *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, - NULL, NULL); - - if ((err = sk_addr_error(disp->addr)) != NULL) { - *error_msg = dupprintf("unable to resolve host name '%s' in " - "display name", disp->hostname); - - sk_addr_free(disp->addr); - sfree(disp->hostname); - sfree(disp->unixsocketpath); - sfree(disp); - return NULL; - } - } - - /* - * 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 */ - Socket *s = sk_new(sk_addr_dup(ux), 0, false, false, - false, false, nullplug); - 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); -}