From 48888b970dcfdbe3414adfa3bc215ef04fc791fb Mon Sep 17 00:00:00 2001 From: cyd01 Date: Sun, 30 Jan 2022 16:56:43 +0100 Subject: [PATCH] Preparing version 0.77 --- 0.76_My_PuTTY/terminal.c | 23 + 0.76_My_PuTTY/windows/window.c | 22 + 0.77_My_PuTTY/agentf.c | 249 + 0.77_My_PuTTY/aqsync.c | 21 + 0.77_My_PuTTY/be_all.c | 34 + 0.77_My_PuTTY/be_all_s.c | 39 + 0.77_My_PuTTY/be_misc.c | 154 + 0.77_My_PuTTY/be_none.c | 15 + 0.77_My_PuTTY/be_nos_s.c | 25 + 0.77_My_PuTTY/be_nossh.c | 24 + 0.77_My_PuTTY/be_ssh.c | 18 + 0.77_My_PuTTY/callback.c | 133 + 0.77_My_PuTTY/cgtest.c | 788 ++ 0.77_My_PuTTY/charset/README | 15 + 0.77_My_PuTTY/charset/charset.h | 157 + 0.77_My_PuTTY/charset/enum.c | 19 + 0.77_My_PuTTY/charset/fromucs.c | 92 + 0.77_My_PuTTY/charset/internal.h | 89 + 0.77_My_PuTTY/charset/localenc.c | 126 + 0.77_My_PuTTY/charset/macenc.c | 168 + 0.77_My_PuTTY/charset/mimeenc.c | 219 + 0.77_My_PuTTY/charset/sbcs.c | 53 + 0.77_My_PuTTY/charset/sbcs.dat | 1139 +++ 0.77_My_PuTTY/charset/sbcsdat.c | 4094 +++++++++++ 0.77_My_PuTTY/charset/sbcsgen.pl | 110 + 0.77_My_PuTTY/charset/slookup.c | 29 + 0.77_My_PuTTY/charset/toucs.c | 90 + 0.77_My_PuTTY/charset/utf8.c | 877 +++ 0.77_My_PuTTY/charset/xenc.c | 94 + 0.77_My_PuTTY/clicons.c | 14 + 0.77_My_PuTTY/cmdgen.c | 1362 ++++ 0.77_My_PuTTY/cmdline.c | 1076 +++ 0.77_My_PuTTY/conf.c | 614 ++ 0.77_My_PuTTY/config.c | 4579 ++++++++++++ 0.77_My_PuTTY/console.c | 113 + 0.77_My_PuTTY/console.h | 17 + 0.77_My_PuTTY/cproxy.c | 179 + 0.77_My_PuTTY/defs.h | 221 + 0.77_My_PuTTY/dialog.c | 485 ++ 0.77_My_PuTTY/dialog.h | 700 ++ 0.77_My_PuTTY/ecc.c | 1167 +++ 0.77_My_PuTTY/ecc.h | 243 + 0.77_My_PuTTY/empty.h | 1 + 0.77_My_PuTTY/errsock.c | 74 + 0.77_My_PuTTY/fuzzterm.c | 215 + 0.77_My_PuTTY/import.c | 2325 ++++++ 0.77_My_PuTTY/ldisc.c | 375 + 0.77_My_PuTTY/ldisc.h | 27 + 0.77_My_PuTTY/licence.h | 19 + 0.77_My_PuTTY/logging.c | 664 ++ 0.77_My_PuTTY/mainchan.c | 538 ++ 0.77_My_PuTTY/marshal.c | 318 + 0.77_My_PuTTY/marshal.h | 340 + 0.77_My_PuTTY/memory.c | 134 + 0.77_My_PuTTY/millerrabin.c | 214 + 0.77_My_PuTTY/minibidi.c | 2025 +++++ 0.77_My_PuTTY/misc.c | 422 ++ 0.77_My_PuTTY/misc.h | 442 ++ 0.77_My_PuTTY/miscucs.c | 28 + 0.77_My_PuTTY/mpint.c | 2646 +++++++ 0.77_My_PuTTY/mpint.h | 445 ++ 0.77_My_PuTTY/mpint_i.h | 324 + 0.77_My_PuTTY/mpunsafe.c | 59 + 0.77_My_PuTTY/mpunsafe.h | 46 + 0.77_My_PuTTY/network.h | 314 + 0.77_My_PuTTY/nocmdline.c | 37 + 0.77_My_PuTTY/nocproxy.c | 35 + 0.77_My_PuTTY/nogss.c | 11 + 0.77_My_PuTTY/noprint.c | 38 + 0.77_My_PuTTY/noproxy.c | 32 + 0.77_My_PuTTY/norand.c | 22 + 0.77_My_PuTTY/noshare.c | 25 + 0.77_My_PuTTY/noterm.c | 11 + 0.77_My_PuTTY/notiming.c | 21 + 0.77_My_PuTTY/nullplug.c | 42 + 0.77_My_PuTTY/pageant.c | 2573 +++++++ 0.77_My_PuTTY/pageant.h | 264 + 0.77_My_PuTTY/pgssapi.c | 105 + 0.77_My_PuTTY/pgssapi.h | 333 + 0.77_My_PuTTY/pinger.c | 72 + 0.77_My_PuTTY/pockle.c | 450 ++ 0.77_My_PuTTY/portfwd.c | 1212 +++ 0.77_My_PuTTY/pproxy.c | 17 + 0.77_My_PuTTY/primecandidate.c | 445 ++ 0.77_My_PuTTY/proxy.c | 1513 ++++ 0.77_My_PuTTY/proxy.h | 111 + 0.77_My_PuTTY/pscp.c | 2387 ++++++ 0.77_My_PuTTY/psftp.c | 2929 ++++++++ 0.77_My_PuTTY/psftp.h | 227 + 0.77_My_PuTTY/psftpcommon.c | 102 + 0.77_My_PuTTY/psocks.c | 569 ++ 0.77_My_PuTTY/psocks.h | 28 + 0.77_My_PuTTY/putty.h | 2730 +++++++ 0.77_My_PuTTY/puttymem.h | 128 + 0.77_My_PuTTY/puttyps.h | 18 + 0.77_My_PuTTY/raw.c | 330 + 0.77_My_PuTTY/resource.h | 15 + 0.77_My_PuTTY/rlogin.c | 428 ++ 0.77_My_PuTTY/scpserver.c | 1399 ++++ 0.77_My_PuTTY/sesschan.c | 787 ++ 0.77_My_PuTTY/sessprep.c | 84 + 0.77_My_PuTTY/settings.c | 1788 +++++ 0.77_My_PuTTY/sftp.c | 1205 +++ 0.77_My_PuTTY/sftp.h | 544 ++ 0.77_My_PuTTY/sftpcommon.c | 139 + 0.77_My_PuTTY/sftpserver.c | 279 + 0.77_My_PuTTY/smallprimes.c | 44 + 0.77_My_PuTTY/ssh.c | 1251 ++++ 0.77_My_PuTTY/ssh.h | 1709 +++++ 0.77_My_PuTTY/ssh1bpp.c | 387 + 0.77_My_PuTTY/ssh1censor.c | 76 + 0.77_My_PuTTY/ssh1connection-client.c | 547 ++ 0.77_My_PuTTY/ssh1connection-server.c | 365 + 0.77_My_PuTTY/ssh1connection.c | 822 +++ 0.77_My_PuTTY/ssh1connection.h | 123 + 0.77_My_PuTTY/ssh1login-server.c | 447 ++ 0.77_My_PuTTY/ssh1login.c | 1257 ++++ 0.77_My_PuTTY/ssh2bpp-bare.c | 204 + 0.77_My_PuTTY/ssh2bpp.c | 982 +++ 0.77_My_PuTTY/ssh2censor.c | 107 + 0.77_My_PuTTY/ssh2connection-client.c | 505 ++ 0.77_My_PuTTY/ssh2connection-server.c | 306 + 0.77_My_PuTTY/ssh2connection.c | 1751 +++++ 0.77_My_PuTTY/ssh2connection.h | 235 + 0.77_My_PuTTY/ssh2kex-client.c | 932 +++ 0.77_My_PuTTY/ssh2kex-server.c | 330 + 0.77_My_PuTTY/ssh2transhk.c | 126 + 0.77_My_PuTTY/ssh2transport.c | 2181 ++++++ 0.77_My_PuTTY/ssh2transport.h | 245 + 0.77_My_PuTTY/ssh2userauth-server.c | 377 + 0.77_My_PuTTY/ssh2userauth.c | 2128 ++++++ 0.77_My_PuTTY/sshaes.c | 1913 +++++ 0.77_My_PuTTY/ssharcf.c | 141 + 0.77_My_PuTTY/sshargon2.c | 565 ++ 0.77_My_PuTTY/sshauxcrypt.c | 166 + 0.77_My_PuTTY/sshbcrypt.c | 119 + 0.77_My_PuTTY/sshblake2.c | 223 + 0.77_My_PuTTY/sshblowf.c | 699 ++ 0.77_My_PuTTY/sshblowf.h | 14 + 0.77_My_PuTTY/sshbpp.h | 179 + 0.77_My_PuTTY/sshccp.c | 1062 +++ 0.77_My_PuTTY/sshchan.h | 316 + 0.77_My_PuTTY/sshcommon.c | 946 +++ 0.77_My_PuTTY/sshcr.h | 87 + 0.77_My_PuTTY/sshcrc.c | 108 + 0.77_My_PuTTY/sshcrcda.c | 171 + 0.77_My_PuTTY/sshdes.c | 1048 +++ 0.77_My_PuTTY/sshdh.c | 272 + 0.77_My_PuTTY/sshdss.c | 503 ++ 0.77_My_PuTTY/sshdssg.c | 103 + 0.77_My_PuTTY/sshecc.c | 1706 +++++ 0.77_My_PuTTY/sshecdsag.c | 37 + 0.77_My_PuTTY/sshgss.h | 217 + 0.77_My_PuTTY/sshgssc.c | 288 + 0.77_My_PuTTY/sshgssc.h | 24 + 0.77_My_PuTTY/sshhmac.c | 257 + 0.77_My_PuTTY/sshkeygen.h | 292 + 0.77_My_PuTTY/sshmac.c | 43 + 0.77_My_PuTTY/sshmd5.c | 245 + 0.77_My_PuTTY/sshnogss.c | 19 + 0.77_My_PuTTY/sshppl.h | 174 + 0.77_My_PuTTY/sshprime.c | 762 ++ 0.77_My_PuTTY/sshprng.c | 287 + 0.77_My_PuTTY/sshpubk.c | 1982 +++++ 0.77_My_PuTTY/sshrand.c | 162 + 0.77_My_PuTTY/sshrsa.c | 1109 +++ 0.77_My_PuTTY/sshrsag.c | 292 + 0.77_My_PuTTY/sshserver.c | 591 ++ 0.77_My_PuTTY/sshserver.h | 143 + 0.77_My_PuTTY/sshsh256.c | 940 +++ 0.77_My_PuTTY/sshsh512.c | 836 +++ 0.77_My_PuTTY/sshsha.c | 934 +++ 0.77_My_PuTTY/sshsha3.c | 329 + 0.77_My_PuTTY/sshshare.c | 2182 ++++++ 0.77_My_PuTTY/sshsignals.h | 53 + 0.77_My_PuTTY/sshttymodes.h | 179 + 0.77_My_PuTTY/sshutils.c | 128 + 0.77_My_PuTTY/sshverstring.c | 635 ++ 0.77_My_PuTTY/sshzlib.c | 1253 ++++ 0.77_My_PuTTY/storage.h | 116 + 0.77_My_PuTTY/stripctrl.c | 471 ++ 0.77_My_PuTTY/supdup.c | 923 +++ 0.77_My_PuTTY/telnet.c | 1070 +++ 0.77_My_PuTTY/terminal.c | 8953 +++++++++++++++++++++++ 0.77_My_PuTTY/terminal.h | 572 ++ 0.77_My_PuTTY/testback.c | 214 + 0.77_My_PuTTY/testcrypt.c | 1587 ++++ 0.77_My_PuTTY/testcrypt.h | 326 + 0.77_My_PuTTY/testsc.c | 1645 +++++ 0.77_My_PuTTY/testzlib.c | 114 + 0.77_My_PuTTY/time.c | 16 + 0.77_My_PuTTY/timing.c | 222 + 0.77_My_PuTTY/tree234.c | 1613 ++++ 0.77_My_PuTTY/tree234.h | 195 + 0.77_My_PuTTY/unix/unix.h | 460 ++ 0.77_My_PuTTY/utils.c | 1122 +++ 0.77_My_PuTTY/version.c | 43 + 0.77_My_PuTTY/version.h | 5 + 0.77_My_PuTTY/wcwidth.c | 558 ++ 0.77_My_PuTTY/wildcard.c | 486 ++ 0.77_My_PuTTY/windows/MAKEFILE.MINGW | 2388 ++++++ 0.77_My_PuTTY/windows/kitty.mft | 45 + 0.77_My_PuTTY/windows/pageant-rc.h | 38 + 0.77_My_PuTTY/windows/pageant.ico | Bin 0 -> 4078 bytes 0.77_My_PuTTY/windows/pageant.mft | 31 + 0.77_My_PuTTY/windows/pageant.rc | 119 + 0.77_My_PuTTY/windows/pageants.ico | Bin 0 -> 510 bytes 0.77_My_PuTTY/windows/plink.rc | 8 + 0.77_My_PuTTY/windows/pscp.ico | Bin 0 -> 4078 bytes 0.77_My_PuTTY/windows/pscp.rc | 8 + 0.77_My_PuTTY/windows/psftp.rc | 8 + 0.77_My_PuTTY/windows/putty.ico | Bin 0 -> 4078 bytes 0.77_My_PuTTY/windows/putty.mft | 35 + 0.77_My_PuTTY/windows/putty.rc | 17 + 0.77_My_PuTTY/windows/puttycfg.ico | Bin 0 -> 4078 bytes 0.77_My_PuTTY/windows/puttygen-rc.h | 17 + 0.77_My_PuTTY/windows/puttygen.ico | Bin 0 -> 4078 bytes 0.77_My_PuTTY/windows/puttygen.mft | 31 + 0.77_My_PuTTY/windows/puttygen.rc | 114 + 0.77_My_PuTTY/windows/puttyins.ico | Bin 0 -> 11478 bytes 0.77_My_PuTTY/windows/puttytel.rc | 11 + 0.77_My_PuTTY/windows/rcstuff.h | 50 + 0.77_My_PuTTY/windows/sizetip.c | 192 + 0.77_My_PuTTY/windows/version.rc2 | 82 + 0.77_My_PuTTY/windows/version_major.txt | 1 + 0.77_My_PuTTY/windows/version_minor.txt | 1 + 0.77_My_PuTTY/windows/win_res.h | 143 + 0.77_My_PuTTY/windows/win_res.rc2 | 315 + 0.77_My_PuTTY/windows/wincapi.c | 94 + 0.77_My_PuTTY/windows/wincapi.h | 40 + 0.77_My_PuTTY/windows/wincfg.c | 631 ++ 0.77_My_PuTTY/windows/wincliloop.c | 136 + 0.77_My_PuTTY/windows/wincons.c | 472 ++ 0.77_My_PuTTY/windows/winctrls.c | 2784 +++++++ 0.77_My_PuTTY/windows/windefs.c | 44 + 0.77_My_PuTTY/windows/windlg.c | 1532 ++++ 0.77_My_PuTTY/windows/window.c | 8949 ++++++++++++++++++++++ 0.77_My_PuTTY/windows/wingss.c | 702 ++ 0.77_My_PuTTY/windows/winhandl.c | 731 ++ 0.77_My_PuTTY/windows/winhelp.c | 253 + 0.77_My_PuTTY/windows/winhelp.h | 208 + 0.77_My_PuTTY/windows/winhelp.rc2 | 8 + 0.77_My_PuTTY/windows/winhsock.c | 343 + 0.77_My_PuTTY/windows/winjump.c | 752 ++ 0.77_My_PuTTY/windows/winmisc.c | 467 ++ 0.77_My_PuTTY/windows/winmiscs.c | 285 + 0.77_My_PuTTY/windows/winnet.c | 2106 ++++++ 0.77_My_PuTTY/windows/winnohlp.c | 15 + 0.77_My_PuTTY/windows/winnoise.c | 142 + 0.77_My_PuTTY/windows/winnojmp.c | 8 + 0.77_My_PuTTY/windows/winnpc.c | 98 + 0.77_My_PuTTY/windows/winnps.c | 240 + 0.77_My_PuTTY/windows/winpgen.c | 2054 ++++++ 0.77_My_PuTTY/windows/winpgnt.c | 1893 +++++ 0.77_My_PuTTY/windows/winpgntc.c | 348 + 0.77_My_PuTTY/windows/winplink.c | 650 ++ 0.77_My_PuTTY/windows/winprint.c | 224 + 0.77_My_PuTTY/windows/winproxy.c | 107 + 0.77_My_PuTTY/windows/winseat.h | 17 + 0.77_My_PuTTY/windows/winsecur.c | 334 + 0.77_My_PuTTY/windows/winsecur.h | 53 + 0.77_My_PuTTY/windows/winselcli.c | 78 + 0.77_My_PuTTY/windows/winselgui.c | 38 + 0.77_My_PuTTY/windows/winser.c | 465 ++ 0.77_My_PuTTY/windows/winsftp.c | 697 ++ 0.77_My_PuTTY/windows/winshare.c | 151 + 0.77_My_PuTTY/windows/winsocks.c | 24 + 0.77_My_PuTTY/windows/winstore.c | 1451 ++++ 0.77_My_PuTTY/windows/winstuff.h | 740 ++ 0.77_My_PuTTY/windows/wintime.c | 26 + 0.77_My_PuTTY/windows/winucs.c | 1213 +++ 0.77_My_PuTTY/windows/winutils.c | 788 ++ 0.77_My_PuTTY/windows/winx11.c | 19 + 0.77_My_PuTTY/x11fwd.c | 1201 +++ 274 files changed, 155313 insertions(+) create mode 100644 0.77_My_PuTTY/agentf.c create mode 100644 0.77_My_PuTTY/aqsync.c create mode 100644 0.77_My_PuTTY/be_all.c create mode 100644 0.77_My_PuTTY/be_all_s.c create mode 100644 0.77_My_PuTTY/be_misc.c create mode 100644 0.77_My_PuTTY/be_none.c create mode 100644 0.77_My_PuTTY/be_nos_s.c create mode 100644 0.77_My_PuTTY/be_nossh.c create mode 100644 0.77_My_PuTTY/be_ssh.c create mode 100644 0.77_My_PuTTY/callback.c create mode 100644 0.77_My_PuTTY/cgtest.c create mode 100644 0.77_My_PuTTY/charset/README create mode 100644 0.77_My_PuTTY/charset/charset.h create mode 100644 0.77_My_PuTTY/charset/enum.c create mode 100644 0.77_My_PuTTY/charset/fromucs.c create mode 100644 0.77_My_PuTTY/charset/internal.h create mode 100644 0.77_My_PuTTY/charset/localenc.c create mode 100644 0.77_My_PuTTY/charset/macenc.c create mode 100644 0.77_My_PuTTY/charset/mimeenc.c create mode 100644 0.77_My_PuTTY/charset/sbcs.c create mode 100644 0.77_My_PuTTY/charset/sbcs.dat create mode 100644 0.77_My_PuTTY/charset/sbcsdat.c create mode 100644 0.77_My_PuTTY/charset/sbcsgen.pl create mode 100644 0.77_My_PuTTY/charset/slookup.c create mode 100644 0.77_My_PuTTY/charset/toucs.c create mode 100644 0.77_My_PuTTY/charset/utf8.c create mode 100644 0.77_My_PuTTY/charset/xenc.c create mode 100644 0.77_My_PuTTY/clicons.c create mode 100644 0.77_My_PuTTY/cmdgen.c create mode 100644 0.77_My_PuTTY/cmdline.c create mode 100644 0.77_My_PuTTY/conf.c create mode 100644 0.77_My_PuTTY/config.c create mode 100644 0.77_My_PuTTY/console.c create mode 100644 0.77_My_PuTTY/console.h create mode 100644 0.77_My_PuTTY/cproxy.c create mode 100644 0.77_My_PuTTY/defs.h create mode 100644 0.77_My_PuTTY/dialog.c create mode 100644 0.77_My_PuTTY/dialog.h create mode 100644 0.77_My_PuTTY/ecc.c create mode 100644 0.77_My_PuTTY/ecc.h create mode 100644 0.77_My_PuTTY/empty.h create mode 100644 0.77_My_PuTTY/errsock.c create mode 100644 0.77_My_PuTTY/fuzzterm.c create mode 100644 0.77_My_PuTTY/import.c create mode 100644 0.77_My_PuTTY/ldisc.c create mode 100644 0.77_My_PuTTY/ldisc.h create mode 100644 0.77_My_PuTTY/licence.h create mode 100644 0.77_My_PuTTY/logging.c create mode 100644 0.77_My_PuTTY/mainchan.c create mode 100644 0.77_My_PuTTY/marshal.c create mode 100644 0.77_My_PuTTY/marshal.h create mode 100644 0.77_My_PuTTY/memory.c create mode 100644 0.77_My_PuTTY/millerrabin.c create mode 100644 0.77_My_PuTTY/minibidi.c create mode 100644 0.77_My_PuTTY/misc.c create mode 100644 0.77_My_PuTTY/misc.h create mode 100644 0.77_My_PuTTY/miscucs.c create mode 100644 0.77_My_PuTTY/mpint.c create mode 100644 0.77_My_PuTTY/mpint.h create mode 100644 0.77_My_PuTTY/mpint_i.h create mode 100644 0.77_My_PuTTY/mpunsafe.c create mode 100644 0.77_My_PuTTY/mpunsafe.h create mode 100644 0.77_My_PuTTY/network.h create mode 100644 0.77_My_PuTTY/nocmdline.c create mode 100644 0.77_My_PuTTY/nocproxy.c create mode 100644 0.77_My_PuTTY/nogss.c create mode 100644 0.77_My_PuTTY/noprint.c create mode 100644 0.77_My_PuTTY/noproxy.c create mode 100644 0.77_My_PuTTY/norand.c create mode 100644 0.77_My_PuTTY/noshare.c create mode 100644 0.77_My_PuTTY/noterm.c create mode 100644 0.77_My_PuTTY/notiming.c create mode 100644 0.77_My_PuTTY/nullplug.c create mode 100644 0.77_My_PuTTY/pageant.c create mode 100644 0.77_My_PuTTY/pageant.h create mode 100644 0.77_My_PuTTY/pgssapi.c create mode 100644 0.77_My_PuTTY/pgssapi.h create mode 100644 0.77_My_PuTTY/pinger.c create mode 100644 0.77_My_PuTTY/pockle.c create mode 100644 0.77_My_PuTTY/portfwd.c create mode 100644 0.77_My_PuTTY/pproxy.c create mode 100644 0.77_My_PuTTY/primecandidate.c create mode 100644 0.77_My_PuTTY/proxy.c create mode 100644 0.77_My_PuTTY/proxy.h create mode 100644 0.77_My_PuTTY/pscp.c create mode 100644 0.77_My_PuTTY/psftp.c create mode 100644 0.77_My_PuTTY/psftp.h create mode 100644 0.77_My_PuTTY/psftpcommon.c create mode 100644 0.77_My_PuTTY/psocks.c create mode 100644 0.77_My_PuTTY/psocks.h create mode 100644 0.77_My_PuTTY/putty.h create mode 100644 0.77_My_PuTTY/puttymem.h create mode 100644 0.77_My_PuTTY/puttyps.h create mode 100644 0.77_My_PuTTY/raw.c create mode 100644 0.77_My_PuTTY/resource.h create mode 100644 0.77_My_PuTTY/rlogin.c create mode 100644 0.77_My_PuTTY/scpserver.c create mode 100644 0.77_My_PuTTY/sesschan.c create mode 100644 0.77_My_PuTTY/sessprep.c create mode 100644 0.77_My_PuTTY/settings.c create mode 100644 0.77_My_PuTTY/sftp.c create mode 100644 0.77_My_PuTTY/sftp.h create mode 100644 0.77_My_PuTTY/sftpcommon.c create mode 100644 0.77_My_PuTTY/sftpserver.c create mode 100644 0.77_My_PuTTY/smallprimes.c create mode 100644 0.77_My_PuTTY/ssh.c create mode 100644 0.77_My_PuTTY/ssh.h create mode 100644 0.77_My_PuTTY/ssh1bpp.c create mode 100644 0.77_My_PuTTY/ssh1censor.c create mode 100644 0.77_My_PuTTY/ssh1connection-client.c create mode 100644 0.77_My_PuTTY/ssh1connection-server.c create mode 100644 0.77_My_PuTTY/ssh1connection.c create mode 100644 0.77_My_PuTTY/ssh1connection.h create mode 100644 0.77_My_PuTTY/ssh1login-server.c create mode 100644 0.77_My_PuTTY/ssh1login.c create mode 100644 0.77_My_PuTTY/ssh2bpp-bare.c create mode 100644 0.77_My_PuTTY/ssh2bpp.c create mode 100644 0.77_My_PuTTY/ssh2censor.c create mode 100644 0.77_My_PuTTY/ssh2connection-client.c create mode 100644 0.77_My_PuTTY/ssh2connection-server.c create mode 100644 0.77_My_PuTTY/ssh2connection.c create mode 100644 0.77_My_PuTTY/ssh2connection.h create mode 100644 0.77_My_PuTTY/ssh2kex-client.c create mode 100644 0.77_My_PuTTY/ssh2kex-server.c create mode 100644 0.77_My_PuTTY/ssh2transhk.c create mode 100644 0.77_My_PuTTY/ssh2transport.c create mode 100644 0.77_My_PuTTY/ssh2transport.h create mode 100644 0.77_My_PuTTY/ssh2userauth-server.c create mode 100644 0.77_My_PuTTY/ssh2userauth.c create mode 100644 0.77_My_PuTTY/sshaes.c create mode 100644 0.77_My_PuTTY/ssharcf.c create mode 100644 0.77_My_PuTTY/sshargon2.c create mode 100644 0.77_My_PuTTY/sshauxcrypt.c create mode 100644 0.77_My_PuTTY/sshbcrypt.c create mode 100644 0.77_My_PuTTY/sshblake2.c create mode 100644 0.77_My_PuTTY/sshblowf.c create mode 100644 0.77_My_PuTTY/sshblowf.h create mode 100644 0.77_My_PuTTY/sshbpp.h create mode 100644 0.77_My_PuTTY/sshccp.c create mode 100644 0.77_My_PuTTY/sshchan.h create mode 100644 0.77_My_PuTTY/sshcommon.c create mode 100644 0.77_My_PuTTY/sshcr.h create mode 100644 0.77_My_PuTTY/sshcrc.c create mode 100644 0.77_My_PuTTY/sshcrcda.c create mode 100644 0.77_My_PuTTY/sshdes.c create mode 100644 0.77_My_PuTTY/sshdh.c create mode 100644 0.77_My_PuTTY/sshdss.c create mode 100644 0.77_My_PuTTY/sshdssg.c create mode 100644 0.77_My_PuTTY/sshecc.c create mode 100644 0.77_My_PuTTY/sshecdsag.c create mode 100644 0.77_My_PuTTY/sshgss.h create mode 100644 0.77_My_PuTTY/sshgssc.c create mode 100644 0.77_My_PuTTY/sshgssc.h create mode 100644 0.77_My_PuTTY/sshhmac.c create mode 100644 0.77_My_PuTTY/sshkeygen.h create mode 100644 0.77_My_PuTTY/sshmac.c create mode 100644 0.77_My_PuTTY/sshmd5.c create mode 100644 0.77_My_PuTTY/sshnogss.c create mode 100644 0.77_My_PuTTY/sshppl.h create mode 100644 0.77_My_PuTTY/sshprime.c create mode 100644 0.77_My_PuTTY/sshprng.c create mode 100644 0.77_My_PuTTY/sshpubk.c create mode 100644 0.77_My_PuTTY/sshrand.c create mode 100644 0.77_My_PuTTY/sshrsa.c create mode 100644 0.77_My_PuTTY/sshrsag.c create mode 100644 0.77_My_PuTTY/sshserver.c create mode 100644 0.77_My_PuTTY/sshserver.h create mode 100644 0.77_My_PuTTY/sshsh256.c create mode 100644 0.77_My_PuTTY/sshsh512.c create mode 100644 0.77_My_PuTTY/sshsha.c create mode 100644 0.77_My_PuTTY/sshsha3.c create mode 100644 0.77_My_PuTTY/sshshare.c create mode 100644 0.77_My_PuTTY/sshsignals.h create mode 100644 0.77_My_PuTTY/sshttymodes.h create mode 100644 0.77_My_PuTTY/sshutils.c create mode 100644 0.77_My_PuTTY/sshverstring.c create mode 100644 0.77_My_PuTTY/sshzlib.c create mode 100644 0.77_My_PuTTY/storage.h create mode 100644 0.77_My_PuTTY/stripctrl.c create mode 100644 0.77_My_PuTTY/supdup.c create mode 100644 0.77_My_PuTTY/telnet.c create mode 100644 0.77_My_PuTTY/terminal.c create mode 100644 0.77_My_PuTTY/terminal.h create mode 100644 0.77_My_PuTTY/testback.c create mode 100644 0.77_My_PuTTY/testcrypt.c create mode 100644 0.77_My_PuTTY/testcrypt.h create mode 100644 0.77_My_PuTTY/testsc.c create mode 100644 0.77_My_PuTTY/testzlib.c create mode 100644 0.77_My_PuTTY/time.c create mode 100644 0.77_My_PuTTY/timing.c create mode 100644 0.77_My_PuTTY/tree234.c create mode 100644 0.77_My_PuTTY/tree234.h create mode 100644 0.77_My_PuTTY/unix/unix.h create mode 100644 0.77_My_PuTTY/utils.c create mode 100644 0.77_My_PuTTY/version.c create mode 100644 0.77_My_PuTTY/version.h create mode 100644 0.77_My_PuTTY/wcwidth.c create mode 100644 0.77_My_PuTTY/wildcard.c create mode 100644 0.77_My_PuTTY/windows/MAKEFILE.MINGW create mode 100644 0.77_My_PuTTY/windows/kitty.mft create mode 100644 0.77_My_PuTTY/windows/pageant-rc.h create mode 100644 0.77_My_PuTTY/windows/pageant.ico create mode 100644 0.77_My_PuTTY/windows/pageant.mft create mode 100644 0.77_My_PuTTY/windows/pageant.rc create mode 100644 0.77_My_PuTTY/windows/pageants.ico create mode 100644 0.77_My_PuTTY/windows/plink.rc create mode 100644 0.77_My_PuTTY/windows/pscp.ico create mode 100644 0.77_My_PuTTY/windows/pscp.rc create mode 100644 0.77_My_PuTTY/windows/psftp.rc create mode 100644 0.77_My_PuTTY/windows/putty.ico create mode 100644 0.77_My_PuTTY/windows/putty.mft create mode 100644 0.77_My_PuTTY/windows/putty.rc create mode 100644 0.77_My_PuTTY/windows/puttycfg.ico create mode 100644 0.77_My_PuTTY/windows/puttygen-rc.h create mode 100644 0.77_My_PuTTY/windows/puttygen.ico create mode 100644 0.77_My_PuTTY/windows/puttygen.mft create mode 100644 0.77_My_PuTTY/windows/puttygen.rc create mode 100644 0.77_My_PuTTY/windows/puttyins.ico create mode 100644 0.77_My_PuTTY/windows/puttytel.rc create mode 100644 0.77_My_PuTTY/windows/rcstuff.h create mode 100644 0.77_My_PuTTY/windows/sizetip.c create mode 100644 0.77_My_PuTTY/windows/version.rc2 create mode 100644 0.77_My_PuTTY/windows/version_major.txt create mode 100644 0.77_My_PuTTY/windows/version_minor.txt create mode 100644 0.77_My_PuTTY/windows/win_res.h create mode 100644 0.77_My_PuTTY/windows/win_res.rc2 create mode 100644 0.77_My_PuTTY/windows/wincapi.c create mode 100644 0.77_My_PuTTY/windows/wincapi.h create mode 100644 0.77_My_PuTTY/windows/wincfg.c create mode 100644 0.77_My_PuTTY/windows/wincliloop.c create mode 100644 0.77_My_PuTTY/windows/wincons.c create mode 100644 0.77_My_PuTTY/windows/winctrls.c create mode 100644 0.77_My_PuTTY/windows/windefs.c create mode 100644 0.77_My_PuTTY/windows/windlg.c create mode 100644 0.77_My_PuTTY/windows/window.c create mode 100644 0.77_My_PuTTY/windows/wingss.c create mode 100644 0.77_My_PuTTY/windows/winhandl.c create mode 100644 0.77_My_PuTTY/windows/winhelp.c create mode 100644 0.77_My_PuTTY/windows/winhelp.h create mode 100644 0.77_My_PuTTY/windows/winhelp.rc2 create mode 100644 0.77_My_PuTTY/windows/winhsock.c create mode 100644 0.77_My_PuTTY/windows/winjump.c create mode 100644 0.77_My_PuTTY/windows/winmisc.c create mode 100644 0.77_My_PuTTY/windows/winmiscs.c create mode 100644 0.77_My_PuTTY/windows/winnet.c create mode 100644 0.77_My_PuTTY/windows/winnohlp.c create mode 100644 0.77_My_PuTTY/windows/winnoise.c create mode 100644 0.77_My_PuTTY/windows/winnojmp.c create mode 100644 0.77_My_PuTTY/windows/winnpc.c create mode 100644 0.77_My_PuTTY/windows/winnps.c create mode 100644 0.77_My_PuTTY/windows/winpgen.c create mode 100644 0.77_My_PuTTY/windows/winpgnt.c create mode 100644 0.77_My_PuTTY/windows/winpgntc.c create mode 100644 0.77_My_PuTTY/windows/winplink.c create mode 100644 0.77_My_PuTTY/windows/winprint.c create mode 100644 0.77_My_PuTTY/windows/winproxy.c create mode 100644 0.77_My_PuTTY/windows/winseat.h create mode 100644 0.77_My_PuTTY/windows/winsecur.c create mode 100644 0.77_My_PuTTY/windows/winsecur.h create mode 100644 0.77_My_PuTTY/windows/winselcli.c create mode 100644 0.77_My_PuTTY/windows/winselgui.c create mode 100644 0.77_My_PuTTY/windows/winser.c create mode 100644 0.77_My_PuTTY/windows/winsftp.c create mode 100644 0.77_My_PuTTY/windows/winshare.c create mode 100644 0.77_My_PuTTY/windows/winsocks.c create mode 100644 0.77_My_PuTTY/windows/winstore.c create mode 100644 0.77_My_PuTTY/windows/winstuff.h create mode 100644 0.77_My_PuTTY/windows/wintime.c create mode 100644 0.77_My_PuTTY/windows/winucs.c create mode 100644 0.77_My_PuTTY/windows/winutils.c create mode 100644 0.77_My_PuTTY/windows/winx11.c create mode 100644 0.77_My_PuTTY/x11fwd.c diff --git a/0.76_My_PuTTY/terminal.c b/0.76_My_PuTTY/terminal.c index 19294fc..471492e 100644 --- a/0.76_My_PuTTY/terminal.c +++ b/0.76_My_PuTTY/terminal.c @@ -3726,11 +3726,34 @@ static void do_osc(Terminal *term) // ok, let us try to answer something // base64-encode + // result in null-terminated char* out base64_encodestate _state; base64_init_encodestate(&_state); + char* out = malloc(reply_size*2); int count = base64_encode_block((char*)reply, reply_size, out, &_state); +#ifdef MOD_PERSO count += base64_encode_blockend(out + count, &_state); +#else + // finishing '=' characters + char* next_char = out + count; + switch (_state.step) + { + case step_B: + *next_char++ = base64_encode_value(_state.result); + *next_char++ = '='; + *next_char++ = '='; + break; + case step_C: + *next_char++ = base64_encode_value(_state.result); + *next_char++ = '='; + break; + case step_A: + break; + } + count = next_char - out; + out[count] = 0; +#endif // send escape seq diff --git a/0.76_My_PuTTY/windows/window.c b/0.76_My_PuTTY/windows/window.c index c3667e6..96bea08 100644 --- a/0.76_My_PuTTY/windows/window.c +++ b/0.76_My_PuTTY/windows/window.c @@ -5696,11 +5696,33 @@ if( (GetKeyState(VK_MENU)&0x8000) && (wParam==VK_SPACE) ) { memcpy(kev + 14, &type, sizeof(type)); // base64-encode kev + // result in null-terminated char* out base64_encodestate _state; base64_init_encodestate(&_state); char* out = malloc(15*2); int count = base64_encode_block(kev, 15, out, &_state); +#ifdef MOD_PERSO count += base64_encode_blockend(out + count, &_state); +#else + // finishing '=' characters + char* next_char = out + count; + switch (_state.step) + { + case step_B: + *next_char++ = base64_encode_value(_state.result); + *next_char++ = '='; + *next_char++ = '='; + break; + case step_C: + *next_char++ = base64_encode_value(_state.result); + *next_char++ = '='; + break; + case step_A: + break; + } + count = next_char - out; + out[count] = 0; +#endif // send escape seq diff --git a/0.77_My_PuTTY/agentf.c b/0.77_My_PuTTY/agentf.c new file mode 100644 index 0000000..ce69bac --- /dev/null +++ b/0.77_My_PuTTY/agentf.c @@ -0,0 +1,249 @@ +/* + * SSH agent forwarding. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "pageant.h" +#include "sshchan.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/0.77_My_PuTTY/aqsync.c b/0.77_My_PuTTY/aqsync.c new file mode 100644 index 0000000..dee048e --- /dev/null +++ b/0.77_My_PuTTY/aqsync.c @@ -0,0 +1,21 @@ +/* + * 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/0.77_My_PuTTY/be_all.c b/0.77_My_PuTTY/be_all.c new file mode 100644 index 0000000..4f65dfc --- /dev/null +++ b/0.77_My_PuTTY/be_all.c @@ -0,0 +1,34 @@ +/* + * Linking module for PuTTY proper: list the available backends + * including ssh. + */ + +#include +#include "putty.h" + +/* + * This appname is not strictly in the right place, since Plink + * also uses this module. However, Plink doesn't currently use any + * of the dialog-box sorts of things that make use of appname, so + * it shouldn't do any harm here. I'm trying to avoid having to + * have tiny little source modules containing nothing but + * declarations of appname, for as long as I can... + */ +const char *const appname = "PuTTY"; + +const int be_default_protocol = PROT_SSH; + +const struct BackendVtable *const backends[] = { + &ssh_backend, + &telnet_backend, + &rlogin_backend, + &supdup_backend, + &raw_backend, + &sshconn_backend, +#ifdef MOD_ADB + &adb_backend, +#endif + NULL +}; + +const size_t n_ui_backends = 1; diff --git a/0.77_My_PuTTY/be_all_s.c b/0.77_My_PuTTY/be_all_s.c new file mode 100644 index 0000000..bc16ed2 --- /dev/null +++ b/0.77_My_PuTTY/be_all_s.c @@ -0,0 +1,39 @@ +/* + * 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... + */ +#if (defined MOD_PERSO) && (!defined FLJ) +char *appname = "KiTTY"; +#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, +#ifdef MOD_ADB + &adb_backend, +#endif + &sshconn_backend, + NULL +}; + +const size_t n_ui_backends = 2; diff --git a/0.77_My_PuTTY/be_misc.c b/0.77_My_PuTTY/be_misc.c new file mode 100644 index 0000000..76f738e --- /dev/null +++ b/0.77_My_PuTTY/be_misc.c @@ -0,0 +1,154 @@ +/* + * be_misc.c: helper functions shared between main network backends. + */ + +#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: + sk_getaddr(addr, addrbuf, lenof(addrbuf)); + 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); + } +} + +void psb_init(ProxyStderrBuf *psb) +{ + psb->size = 0; +} + +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( + "proxy: %.*s", (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( + "proxy (partial line): %.*s", (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/0.77_My_PuTTY/be_none.c b/0.77_My_PuTTY/be_none.c new file mode 100644 index 0000000..f7b1f46 --- /dev/null +++ b/0.77_My_PuTTY/be_none.c @@ -0,0 +1,15 @@ +/* + * Linking module for programs that do not support selection of backend + * (such as pterm). + */ + +#include +#include "putty.h" + +const int be_default_protocol = -1; + +const struct BackendVtable *const backends[] = { + NULL +}; + +const size_t n_ui_backends = 0; diff --git a/0.77_My_PuTTY/be_nos_s.c b/0.77_My_PuTTY/be_nos_s.c new file mode 100644 index 0000000..0acae09 --- /dev/null +++ b/0.77_My_PuTTY/be_nos_s.c @@ -0,0 +1,25 @@ +/* + * Linking module for PuTTYtel: list the available backends not + * including ssh. + */ + +#include +#include "putty.h" + +const int be_default_protocol = PROT_TELNET; + +const char *const appname = "PuTTYtel"; + +const struct BackendVtable *const backends[] = { + &telnet_backend, + &serial_backend, + &rlogin_backend, + &supdup_backend, + &raw_backend, +#ifdef MOD_ADB + &adb_backend, +#endif + NULL +}; + +const size_t n_ui_backends = 2; diff --git a/0.77_My_PuTTY/be_nossh.c b/0.77_My_PuTTY/be_nossh.c new file mode 100644 index 0000000..c6ea9d8 --- /dev/null +++ b/0.77_My_PuTTY/be_nossh.c @@ -0,0 +1,24 @@ +/* + * Linking module for PuTTYtel: list the available backends not + * including ssh. + */ + +#include +#include "putty.h" + +const int be_default_protocol = PROT_TELNET; + +const char *const appname = "PuTTYtel"; + +const struct BackendVtable *const backends[] = { + &telnet_backend, + &rlogin_backend, + &supdup_backend, + &raw_backend, +#ifdef MOD_ADB + &adb_backend, +#endif + NULL +}; + +const size_t n_ui_backends = 1; diff --git a/0.77_My_PuTTY/be_ssh.c b/0.77_My_PuTTY/be_ssh.c new file mode 100644 index 0000000..90ca1d2 --- /dev/null +++ b/0.77_My_PuTTY/be_ssh.c @@ -0,0 +1,18 @@ +/* + * Linking module for programs that are restricted to only using + * SSH-type protocols (pscp and psftp). These still have a choice of + * two actual backends, because they can also speak PROT_SSHCONN. + */ + +#include +#include "putty.h" + +const int be_default_protocol = PROT_SSH; + +const struct BackendVtable *const backends[] = { + &ssh_backend, + &sshconn_backend, + NULL +}; + +const size_t n_ui_backends = 0; /* not used in programs with a config UI */ diff --git a/0.77_My_PuTTY/callback.c b/0.77_My_PuTTY/callback.c new file mode 100644 index 0000000..8c3696b --- /dev/null +++ b/0.77_My_PuTTY/callback.c @@ -0,0 +1,133 @@ +/* + * 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/0.77_My_PuTTY/cgtest.c b/0.77_My_PuTTY/cgtest.c new file mode 100644 index 0000000..99ddd9c --- /dev/null +++ b/0.77_My_PuTTY/cgtest.c @@ -0,0 +1,788 @@ +/* + * 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]; +int console_get_userpass_input_diagnostic(prompts_t *p) +{ + size_t i; + int ret = 1; + 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 = 0; + 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/0.77_My_PuTTY/charset/README b/0.77_My_PuTTY/charset/README new file mode 100644 index 0000000..2d08b36 --- /dev/null +++ b/0.77_My_PuTTY/charset/README @@ -0,0 +1,15 @@ +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/0.77_My_PuTTY/charset/charset.h b/0.77_My_PuTTY/charset/charset.h new file mode 100644 index 0000000..bc5ae3a --- /dev/null +++ b/0.77_My_PuTTY/charset/charset.h @@ -0,0 +1,157 @@ +/* + * charset.h - header file for general character set conversion + * routines. + */ + +#ifndef charset_charset_h +#define charset_charset_h + +#include + +/* + * Enumeration that lists all the multibyte or single-byte + * character sets known to this library. + */ +typedef enum { + CS_NONE, /* used for reporting errors, etc */ + CS_ISO8859_1, + CS_ISO8859_1_X11, /* X font encoding with VT100 glyphs */ + CS_ISO8859_2, + CS_ISO8859_3, + CS_ISO8859_4, + CS_ISO8859_5, + CS_ISO8859_6, + CS_ISO8859_7, + CS_ISO8859_8, + CS_ISO8859_9, + CS_ISO8859_10, + CS_ISO8859_11, + CS_ISO8859_13, + CS_ISO8859_14, + CS_ISO8859_15, + CS_ISO8859_16, + CS_CP437, + CS_CP850, + CS_CP852, + CS_CP866, + CS_CP1250, + CS_CP1251, + CS_CP1252, + CS_CP1253, + CS_CP1254, + CS_CP1255, + CS_CP1256, + CS_CP1257, + CS_CP1258, + CS_KOI8_R, + CS_KOI8_U, + CS_MAC_ROMAN, + CS_MAC_TURKISH, + CS_MAC_CROATIAN, + CS_MAC_ICELAND, + CS_MAC_ROMANIAN, + CS_MAC_GREEK, + CS_MAC_CYRILLIC, + CS_MAC_THAI, + CS_MAC_CENTEURO, + CS_MAC_SYMBOL, + CS_MAC_DINGBATS, + CS_MAC_ROMAN_OLD, + CS_MAC_CROATIAN_OLD, + CS_MAC_ICELAND_OLD, + CS_MAC_ROMANIAN_OLD, + CS_MAC_GREEK_OLD, + CS_MAC_CYRILLIC_OLD, + CS_MAC_UKRAINE, + CS_MAC_VT100, + CS_MAC_VT100_OLD, + CS_VISCII, + CS_HP_ROMAN8, + CS_DEC_MCS, + CS_UTF8 +} charset_t; + +typedef struct { + unsigned long s0; +} charset_state; + +/* + * Routine to convert a MB/SB character set to Unicode. + * + * This routine accepts some number of bytes, updates a state + * variable, and outputs some number of Unicode characters. There + * are no guarantees. You can't even guarantee that at most one + * Unicode character will be output per byte you feed in; for + * example, suppose you're reading UTF-8, you've seen E1 80, and + * then you suddenly see FE. Now you need to output _two_ error + * characters - one for the incomplete sequence E1 80, and one for + * the completely invalid UTF-8 byte FE. + * + * Returns the number of wide characters output; will never output + * more than the size of the buffer (as specified on input). + * Advances the `input' pointer and decrements `inlen', to indicate + * how far along the input string it got. + * + * The sequence of `errlen' wide characters pointed to by `errstr' + * will be used to indicate a conversion error. If `errstr' is + * NULL, `errlen' will be ignored, and the library will choose + * something sensible to do on its own. For Unicode, this will be + * U+FFFD (REPLACEMENT CHARACTER). + */ + +int charset_to_unicode(const char **input, int *inlen, + wchar_t *output, int outlen, + int charset, charset_state *state, + const wchar_t *errstr, int errlen); + +/* + * Routine to convert Unicode to an MB/SB character set. + * + * This routine accepts some number of Unicode characters, updates + * a state variable, and outputs some number of bytes. + * + * Returns the number of bytes characters output; will never output + * more than the size of the buffer (as specified on input), and + * will never output a partial MB character. Advances the `input' + * pointer and decrements `inlen', to indicate how far along the + * input string it got. + * + * The sequence of `errlen' characters pointed to by `errstr' will + * be used to indicate a conversion error. If `errstr' is NULL, + * `errlen' will be ignored, and the library will choose something + * sensible to do on its own (which will vary depending on the + * output charset). + */ + +int charset_from_unicode(const wchar_t **input, int *inlen, + char *output, int outlen, + int charset, charset_state *state, + const char *errstr, int errlen); + +/* + * Convert X11 encoding names to and from our charset identifiers. + */ +const char *charset_to_xenc(int charset); +int charset_from_xenc(const char *name); + +/* + * Convert MIME encoding names to and from our charset identifiers. + */ +const char *charset_to_mimeenc(int charset); +int charset_from_mimeenc(const char *name); + +/* + * Convert our own encoding names to and from our charset + * identifiers. + */ +const char *charset_to_localenc(int charset); +int charset_from_localenc(const char *name); +int charset_localenc_nth(int n); + +/* + * Convert Mac OS script/region/font to our charset identifiers. + */ +int charset_from_macenc(int script, int region, int sysvers, + const char *fontname); + +#endif /* charset_charset_h */ diff --git a/0.77_My_PuTTY/charset/enum.c b/0.77_My_PuTTY/charset/enum.c new file mode 100644 index 0000000..f659e84 --- /dev/null +++ b/0.77_My_PuTTY/charset/enum.c @@ -0,0 +1,19 @@ +/* + * enum.c - enumerate all charsets defined by the library. + * + * This file maintains a list of every other source file which + * contains ENUM_CHARSET definitions. It #includes each one with + * ENUM_CHARSETS defined, which causes those source files to do + * nothing at all except call the ENUM_CHARSET macro on each + * charset they define. + * + * This file in turn is included from various other places, with + * the ENUM_CHARSET macro defined to various different things. This + * allows us to have multiple implementations of the master charset + * lookup table (a static one and a dynamic one). + */ + +#define ENUM_CHARSETS +#include "sbcsdat.c" +#include "utf8.c" +#undef ENUM_CHARSETS diff --git a/0.77_My_PuTTY/charset/fromucs.c b/0.77_My_PuTTY/charset/fromucs.c new file mode 100644 index 0000000..6ccdb86 --- /dev/null +++ b/0.77_My_PuTTY/charset/fromucs.c @@ -0,0 +1,92 @@ +/* + * fromucs.c - convert Unicode to other character sets. + */ + +#include "charset.h" +#include "internal.h" + +struct charset_emit_param { + char *output; + int outlen; + const char *errstr; + int errlen; + int stopped; +}; + +static void charset_emit(void *ctx, long int output) +{ + struct charset_emit_param *param = (struct charset_emit_param *)ctx; + char outval; + char const *p; + int outlen; + + if (output == ERROR) { + p = param->errstr; + outlen = param->errlen; + } else { + outval = output; + p = &outval; + outlen = 1; + } + + if (param->outlen >= outlen) { + while (outlen > 0) { + *param->output++ = *p++; + param->outlen--; + outlen--; + } + } else { + param->stopped = 1; + } +} + +int charset_from_unicode(const wchar_t **input, int *inlen, + char *output, int outlen, + int charset, charset_state *state, + const char *errstr, int errlen) +{ + charset_spec const *spec = charset_find_spec(charset); + charset_state localstate; + struct charset_emit_param param; + + param.output = output; + param.outlen = outlen; + param.stopped = 0; + + /* + * charset_emit will expect a valid errstr. + */ + if (!errstr) { + /* *shrug* this is good enough, and consistent across all SBCS... */ + param.errstr = "."; + param.errlen = 1; + } + param.errstr = errstr; + param.errlen = errlen; + + if (!state) { + localstate.s0 = 0; + } else { + localstate = *state; /* structure copy */ + } + state = &localstate; + + while (*inlen > 0) { + int lenbefore = param.output - output; + spec->write(spec, **input, &localstate, charset_emit, ¶m); + if (param.stopped) { + /* + * The emit function has _tried_ to output some + * characters, but ran up against the end of the + * buffer. Leave immediately, and return what happened + * _before_ attempting to process this character. + */ + return lenbefore; + } + if (state) + *state = localstate; /* structure copy */ + (*input)++; + (*inlen)--; + } + return param.output - output; +} diff --git a/0.77_My_PuTTY/charset/internal.h b/0.77_My_PuTTY/charset/internal.h new file mode 100644 index 0000000..38df2ea --- /dev/null +++ b/0.77_My_PuTTY/charset/internal.h @@ -0,0 +1,89 @@ +/* + * internal.h - internal header stuff for the charset library. + */ + +#ifndef charset_internal_h +#define charset_internal_h + +/* This invariably comes in handy */ +#define lenof(x) ( sizeof((x)) / sizeof(*(x)) ) + +/* This is an invalid Unicode value used to indicate an error. */ +#define ERROR 0xFFFFL /* Unicode value representing error */ + +typedef struct charset_spec charset_spec; +typedef struct sbcs_data sbcs_data; + +struct charset_spec { + int charset; /* numeric identifier */ + + /* + * A function to read the character set and output Unicode + * characters. The `emit' function expects to get Unicode chars + * passed to it; it should be sent ERROR for any encoding error + * on the input. + */ + void (*read)(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx); + /* + * A function to read Unicode characters and output in this + * character set. The `emit' function expects to get byte + * values passed to it; it should be sent ERROR for any + * non-representable characters on the input. + */ + void (*write)(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx); + void const *data; +}; + +/* + * This is the format of `data' used by the SBCS read and write + * functions; so it's the format used in all SBCS definitions. + */ +struct sbcs_data { + /* + * This is a simple mapping table converting each SBCS position + * to a Unicode code point. Some positions may contain ERROR, + * indicating that that byte value is not defined in the SBCS + * in question and its occurrence in input is an error. + */ + unsigned long sbcs2ucs[256]; + + /* + * This lookup table is used to convert Unicode back to the + * SBCS. It consists of the valid byte values in the SBCS, + * sorted in order of their Unicode translation. So given a + * Unicode value U, you can do a binary search on this table + * using the above table as a lookup: when testing the Xth + * position in this table, you branch according to whether + * sbcs2ucs[ucs2sbcs[X]] is less than, greater than, or equal + * to U. + * + * Note that since there may be fewer than 256 valid byte + * values in a particular SBCS, we must supply the length of + * this table as well as the contents. + */ + unsigned char ucs2sbcs[256]; + int nvalid; +}; + +/* + * Prototypes for internal library functions. + */ +charset_spec const *charset_find_spec(int charset); +void read_sbcs(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx); +void write_sbcs(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx); + +/* + * Placate compiler warning about unused parameters, of which we + * expect to have some in this library. + */ +#define UNUSEDARG(x) ( (x) = (x) ) + +#endif /* charset_internal_h */ diff --git a/0.77_My_PuTTY/charset/localenc.c b/0.77_My_PuTTY/charset/localenc.c new file mode 100644 index 0000000..a24126d --- /dev/null +++ b/0.77_My_PuTTY/charset/localenc.c @@ -0,0 +1,126 @@ +/* + * local.c - translate our internal character set codes to and from + * our own set of plausibly legible character-set names. Also + * provides a canonical name for each encoding (useful for software + * announcing what character set it will be using), and a set of + * enumeration functions which return a list of supported + * encodings one by one. + * + * charset_from_localenc will attempt all other text translations + * as well as this table, to maximise the number of different ways + * you can select a supported charset. + */ + +#include +#include "charset.h" +#include "internal.h" + +static const struct { + const char *name; + int charset; + int return_in_enum; /* enumeration misses some charsets */ +} localencs[] = { + { "", CS_NONE, 0 }, + { "UTF-8", CS_UTF8, 1 }, + { "ISO-8859-1", CS_ISO8859_1, 1 }, + { "ISO-8859-1 with X11 line drawing", CS_ISO8859_1_X11, 0 }, + { "ISO-8859-2", CS_ISO8859_2, 1 }, + { "ISO-8859-3", CS_ISO8859_3, 1 }, + { "ISO-8859-4", CS_ISO8859_4, 1 }, + { "ISO-8859-5", CS_ISO8859_5, 1 }, + { "ISO-8859-6", CS_ISO8859_6, 1 }, + { "ISO-8859-7", CS_ISO8859_7, 1 }, + { "ISO-8859-8", CS_ISO8859_8, 1 }, + { "ISO-8859-9", CS_ISO8859_9, 1 }, + { "ISO-8859-10", CS_ISO8859_10, 1 }, + { "ISO-8859-11", CS_ISO8859_11, 1 }, + { "ISO-8859-13", CS_ISO8859_13, 1 }, + { "ISO-8859-14", CS_ISO8859_14, 1 }, + { "ISO-8859-15", CS_ISO8859_15, 1 }, + { "ISO-8859-16", CS_ISO8859_16, 1 }, + { "CP437", CS_CP437, 1 }, + { "CP850", CS_CP850, 1 }, + { "CP852", CS_CP852, 1 }, + { "CP866", CS_CP866, 1 }, + { "CP1250", CS_CP1250, 1 }, + { "CP1251", CS_CP1251, 1 }, + { "CP1252", CS_CP1252, 1 }, + { "CP1253", CS_CP1253, 1 }, + { "CP1254", CS_CP1254, 1 }, + { "CP1255", CS_CP1255, 1 }, + { "CP1256", CS_CP1256, 1 }, + { "CP1257", CS_CP1257, 1 }, + { "CP1258", CS_CP1258, 1 }, + { "KOI8-R", CS_KOI8_R, 1 }, + { "KOI8-U", CS_KOI8_U, 1 }, + { "Mac Roman", CS_MAC_ROMAN, 1 }, + { "Mac Turkish", CS_MAC_TURKISH, 1 }, + { "Mac Croatian", CS_MAC_CROATIAN, 1 }, + { "Mac Iceland", CS_MAC_ICELAND, 1 }, + { "Mac Romanian", CS_MAC_ROMANIAN, 1 }, + { "Mac Greek", CS_MAC_GREEK, 1 }, + { "Mac Cyrillic", CS_MAC_CYRILLIC, 1 }, + { "Mac Thai", CS_MAC_THAI, 1 }, + { "Mac Centeuro", CS_MAC_CENTEURO, 1 }, + { "Mac Symbol", CS_MAC_SYMBOL, 1 }, + { "Mac Dingbats", CS_MAC_DINGBATS, 1 }, + { "Mac Roman (old)", CS_MAC_ROMAN_OLD, 0 }, + { "Mac Croatian (old)", CS_MAC_CROATIAN_OLD, 0 }, + { "Mac Iceland (old)", CS_MAC_ICELAND_OLD, 0 }, + { "Mac Romanian (old)", CS_MAC_ROMANIAN_OLD, 0 }, + { "Mac Greek (old)", CS_MAC_GREEK_OLD, 0 }, + { "Mac Cyrillic (old)", CS_MAC_CYRILLIC_OLD, 0 }, + { "Mac Ukraine", CS_MAC_UKRAINE, 1 }, + { "Mac VT100", CS_MAC_VT100, 1 }, + { "Mac VT100 (old)", CS_MAC_VT100_OLD, 0 }, + { "VISCII", CS_VISCII, 1 }, + { "HP ROMAN8", CS_HP_ROMAN8, 1 }, + { "DEC MCS", CS_DEC_MCS, 1 }, +}; + +const char *charset_to_localenc(int charset) +{ + int i; + + for (i = 0; i < (int)lenof(localencs); i++) + if (charset == localencs[i].charset) + return localencs[i].name; + + return NULL; /* not found */ +} + +int charset_from_localenc(const char *name) +{ + int i; + + if ( (i = charset_from_mimeenc(name)) != CS_NONE) + return i; + if ( (i = charset_from_xenc(name)) != CS_NONE) + return i; + + for (i = 0; i < (int)lenof(localencs); i++) { + const char *p, *q; + p = name; + q = localencs[i].name; + while (*p || *q) { + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + break; + p++; q++; + } + if (!*p && !*q) + return localencs[i].charset; + } + + return CS_NONE; /* not found */ +} + +int charset_localenc_nth(int n) +{ + int i; + + for (i = 0; i < (int)lenof(localencs); i++) + if (localencs[i].return_in_enum && !n--) + return localencs[i].charset; + + return CS_NONE; /* end of list */ +} diff --git a/0.77_My_PuTTY/charset/macenc.c b/0.77_My_PuTTY/charset/macenc.c new file mode 100644 index 0000000..bf70e9c --- /dev/null +++ b/0.77_My_PuTTY/charset/macenc.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2003 Ben Harris + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/* + * macenc.c -- Convert a Mac OS script/region/font combination to our + * internal charset code. + */ + +#include + +#include "charset.h" +#include "internal.h" + +/* + * These are defined by Mac OS's , but we'd like to be + * independent of that. + */ + +#define smRoman 0 +#define smJapanese 1 +#define smTradChinese 2 +#define smKorean 3 +#define smArabic 4 +#define smHebrew 5 +#define smCyrillic 7 +#define smDevenagari 9 +#define smGurmukhi 10 +#define smGujurati 11 +#define smThai 21 +#define smSimpChinese 25 +#define smTibetan 26 +#define smEthiopic 28 +#define smCentralEuroRoman 29 + +#define verGreece 20 +#define verIceland 21 +#define verTurkey 24 +#define verYugoCroatian 25 +#define verRomania 39 +#define verFaroeIsl 47 +#define verIran 48 +#define verRussia 49 +#define verSlovenian 66 +#define verCroatia 68 +#define verBulgaria 72 +#define verScottishGaelic 75 +#define verManxGaelic 76 +#define verBreton 77 +#define verNunavut 78 +#define verWelsh 79 +#define verIrishGaelicScript 81 + +static const struct { + int script; + int region; + int sysvermin; + char const *fontname; + int charset; +} macencs[] = { + { smRoman, -1, 0x850, "VT100", CS_MAC_VT100 }, + { smRoman, -1, 0, "VT100", CS_MAC_VT100_OLD }, + /* + * From here on, this table is largely derived from + * , + * with _OLD version added based on the comments in individual + * mapping files. + */ + { smRoman, -1, 0, "Symbol", CS_MAC_SYMBOL }, + { smRoman, -1, 0, "Zapf Dingbats", CS_MAC_DINGBATS }, + { smRoman, verTurkey, 0, NULL, CS_MAC_TURKISH }, + { smRoman, verYugoCroatian, 0x850, NULL, CS_MAC_CROATIAN }, + { smRoman, verYugoCroatian, 0, NULL, CS_MAC_CROATIAN_OLD }, + { smRoman, verSlovenian, 0x850, NULL, CS_MAC_CROATIAN }, + { smRoman, verSlovenian, 0, NULL, CS_MAC_CROATIAN_OLD }, + { smRoman, verCroatia, 0x850, NULL, CS_MAC_CROATIAN }, + { smRoman, verCroatia, 0, NULL, CS_MAC_CROATIAN_OLD }, + { smRoman, verIceland, 0x850, NULL, CS_MAC_ICELAND }, + { smRoman, verIceland, 0, NULL, CS_MAC_ICELAND_OLD }, + { smRoman, verFaroeIsl, 0x850, NULL, CS_MAC_ICELAND }, + { smRoman, verFaroeIsl, 0, NULL, CS_MAC_ICELAND_OLD }, + { smRoman, verRomania, 0x850, NULL, CS_MAC_ROMANIAN }, + { smRoman, verRomania, 0, NULL, CS_MAC_ROMANIAN_OLD }, +#if 0 /* No mapping table on ftp.unicode.org */ + { smRoman, verIreland, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verIreland, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verScottishGaelic, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verScottishGaelic, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verManxGaelic, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verManxGaelic, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verBreton, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verBreton, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verWelsh, 0x850, NULL, CS_MAC_CELTIC }, + { smRoman, verWelsh, 0, NULL, CS_MAC_CELTIC_OLD }, + { smRoman, verIrishGaelicScript, 0x850, NULL, CS_MAC_GAELIC }, + { smRoman, verIrishGaelicScript, 0, NULL, CS_MAC_GAELIC_OLD }, +#endif + { smRoman, verGreece, 0x922, NULL, CS_MAC_GREEK }, + { smRoman, verGreece, 0, NULL, CS_MAC_GREEK_OLD }, + { smRoman, -1, 0x850, NULL, CS_MAC_ROMAN }, + { smRoman, -1, 0, NULL, CS_MAC_ROMAN_OLD }, +#if 0 /* Multi-byte encodings, not yet supported */ + { smJapanese, -1, 0, NULL, CS_MAC_JAPANESE }, + { smTradChinese, -1, 0, NULL, CS_MAC_CHINTRAD }, + { smKorean, -1, 0, NULL, CS_MAC_KOREAN }, +#endif +#if 0 /* Bidirectional encodings, not yet supported */ + { smArabic, verIran, 0, NULL, CS_MAC_FARSI }, + { smArabic, -1, 0, NULL, CS_MAC_ARABIC }, + { smHebrew, -1, 0, NULL, CS_MAC_HEBREW }, +#endif + { smCyrillic, -1, 0x900, NULL, CS_MAC_CYRILLIC }, + { smCyrillic, verRussia, 0, NULL, CS_MAC_CYRILLIC_OLD }, + { smCyrillic, verBulgaria, 0, NULL, CS_MAC_CYRILLIC_OLD }, + { smCyrillic, -1, 0, NULL, CS_MAC_UKRAINE }, +#if 0 /* Complex Indic scripts, not yet supported */ + { smDevanagari, -1, 0, NULL, CS_MAC_DEVENAGA }, + { smGurmukhi, -1, 0, NULL, CS_MAC_GURMUKHI }, + { smGujurati, -1, 0, NULL, CS_MAC_GUJURATI }, +#endif + { smThai, -1, 0, NULL, CS_MAC_THAI }, +#if 0 /* Multi-byte encoding, not yet supported */ + { smSimpChinese, -1, 0, NULL, CS_MAC_CHINSIMP }, +#endif +#if 0 /* No mapping table on ftp.unicode.org */ + { smTibetan, -1, 0, NULL, CS_MAC_TIBETAN }, + { smEthiopic, -1, 0, NULL, CS_MAC_ETHIOPIC }, + { smEthiopic, verNanavut, 0, NULL, CS_MAC_INUIT }, +#endif + { smCentralEuroRoman, -1, 0, NULL, CS_MAC_CENTEURO }, +}; + +int charset_from_macenc(int script, int region, int sysvers, + char const *fontname) +{ + int i; + + for (i = 0; i < (int)lenof(macencs); i++) + if ((macencs[i].script == script) && + (macencs[i].region < 0 || macencs[i].region == region) && + (macencs[i].sysvermin <= sysvers) && + (macencs[i].fontname == NULL || + (fontname != NULL && strcmp(macencs[i].fontname, fontname) == 0))) + return macencs[i].charset; + + return CS_NONE; +} diff --git a/0.77_My_PuTTY/charset/mimeenc.c b/0.77_My_PuTTY/charset/mimeenc.c new file mode 100644 index 0000000..fb9243c --- /dev/null +++ b/0.77_My_PuTTY/charset/mimeenc.c @@ -0,0 +1,219 @@ +/* + * mimeenc.c - translate our internal character set codes to and + * from MIME standard character-set names. + * + */ + +#include +#include "charset.h" +#include "internal.h" + +static const struct { + const char *name; + int charset; +} mimeencs[] = { + /* + * These names are taken from + * + * http://www.iana.org/assignments/character-sets + * + * Where multiple encoding names map to the same encoding id + * (such as the variety of aliases for ISO-8859-1), the first + * is considered canonical and will be returned when + * translating the id to a string. + */ + { "ISO-8859-1", CS_ISO8859_1 }, + { "iso-ir-100", CS_ISO8859_1 }, + { "ISO_8859-1", CS_ISO8859_1 }, + { "ISO_8859-1:1987", CS_ISO8859_1 }, + { "latin1", CS_ISO8859_1 }, + { "l1", CS_ISO8859_1 }, + { "IBM819", CS_ISO8859_1 }, + { "CP819", CS_ISO8859_1 }, + { "csISOLatin1", CS_ISO8859_1 }, + + { "ISO-8859-2", CS_ISO8859_2 }, + { "ISO_8859-2:1987", CS_ISO8859_2 }, + { "iso-ir-101", CS_ISO8859_2 }, + { "ISO_8859-2", CS_ISO8859_2 }, + { "latin2", CS_ISO8859_2 }, + { "l2", CS_ISO8859_2 }, + { "csISOLatin2", CS_ISO8859_2 }, + + { "ISO-8859-3", CS_ISO8859_3 }, + { "ISO_8859-3:1988", CS_ISO8859_3 }, + { "iso-ir-109", CS_ISO8859_3 }, + { "ISO_8859-3", CS_ISO8859_3 }, + { "latin3", CS_ISO8859_3 }, + { "l3", CS_ISO8859_3 }, + { "csISOLatin3", CS_ISO8859_3 }, + + { "ISO-8859-4", CS_ISO8859_4 }, + { "ISO_8859-4:1988", CS_ISO8859_4 }, + { "iso-ir-110", CS_ISO8859_4 }, + { "ISO_8859-4", CS_ISO8859_4 }, + { "latin4", CS_ISO8859_4 }, + { "l4", CS_ISO8859_4 }, + { "csISOLatin4", CS_ISO8859_4 }, + + { "ISO-8859-5", CS_ISO8859_5 }, + { "ISO_8859-5:1988", CS_ISO8859_5 }, + { "iso-ir-144", CS_ISO8859_5 }, + { "ISO_8859-5", CS_ISO8859_5 }, + { "cyrillic", CS_ISO8859_5 }, + { "csISOLatinCyrillic", CS_ISO8859_5 }, + + { "ISO-8859-6", CS_ISO8859_6 }, + { "ISO_8859-6:1987", CS_ISO8859_6 }, + { "iso-ir-127", CS_ISO8859_6 }, + { "ISO_8859-6", CS_ISO8859_6 }, + { "ECMA-114", CS_ISO8859_6 }, + { "ASMO-708", CS_ISO8859_6 }, + { "arabic", CS_ISO8859_6 }, + { "csISOLatinArabic", CS_ISO8859_6 }, + + { "ISO-8859-7", CS_ISO8859_7 }, + { "ISO_8859-7:1987", CS_ISO8859_7 }, + { "iso-ir-126", CS_ISO8859_7 }, + { "ISO_8859-7", CS_ISO8859_7 }, + { "ELOT_928", CS_ISO8859_7 }, + { "ECMA-118", CS_ISO8859_7 }, + { "greek", CS_ISO8859_7 }, + { "greek8", CS_ISO8859_7 }, + { "csISOLatinGreek", CS_ISO8859_7 }, + + { "ISO-8859-8", CS_ISO8859_8 }, + { "ISO_8859-8:1988", CS_ISO8859_8 }, + { "iso-ir-138", CS_ISO8859_8 }, + { "ISO_8859-8", CS_ISO8859_8 }, + { "hebrew", CS_ISO8859_8 }, + { "csISOLatinHebrew", CS_ISO8859_8 }, + + { "ISO-8859-9", CS_ISO8859_9 }, + { "ISO_8859-9:1989", CS_ISO8859_9 }, + { "iso-ir-148", CS_ISO8859_9 }, + { "ISO_8859-9", CS_ISO8859_9 }, + { "latin5", CS_ISO8859_9 }, + { "l5", CS_ISO8859_9 }, + { "csISOLatin5", CS_ISO8859_9 }, + + { "ISO-8859-10", CS_ISO8859_10 }, + { "iso-ir-157", CS_ISO8859_10 }, + { "l6", CS_ISO8859_10 }, + { "ISO_8859-10:1992", CS_ISO8859_10 }, + { "csISOLatin6", CS_ISO8859_10 }, + { "latin6", CS_ISO8859_10 }, + + { "ISO-8859-13", CS_ISO8859_13 }, + + { "ISO-8859-14", CS_ISO8859_14 }, + { "iso-ir-199", CS_ISO8859_14 }, + { "ISO_8859-14:1998", CS_ISO8859_14 }, + { "ISO_8859-14", CS_ISO8859_14 }, + { "latin8", CS_ISO8859_14 }, + { "iso-celtic", CS_ISO8859_14 }, + { "l8", CS_ISO8859_14 }, + + { "ISO-8859-15", CS_ISO8859_15 }, + { "ISO_8859-15", CS_ISO8859_15 }, + { "Latin-9", CS_ISO8859_15 }, + + { "ISO-8859-16", CS_ISO8859_16 }, + { "iso-ir-226", CS_ISO8859_16 }, + { "ISO_8859-16", CS_ISO8859_16 }, + { "ISO_8859-16:2001", CS_ISO8859_16 }, + { "latin10", CS_ISO8859_16 }, + { "l10", CS_ISO8859_16 }, + + { "IBM437", CS_CP437 }, + { "cp437", CS_CP437 }, + { "437", CS_CP437 }, + { "csPC8CodePage437", CS_CP437 }, + + { "IBM850", CS_CP850 }, + { "cp850", CS_CP850 }, + { "850", CS_CP850 }, + { "csPC850Multilingual", CS_CP850 }, + + { "IBM852", CS_CP852 }, + { "cp852", CS_CP852 }, + { "852", CS_CP852 }, + { "csIBM852", CS_CP852 }, + + { "IBM866", CS_CP866 }, + { "cp866", CS_CP866 }, + { "866", CS_CP866 }, + { "csIBM866", CS_CP866 }, + + { "windows-1250", CS_CP1250 }, + + { "windows-1251", CS_CP1251 }, + + { "windows-1252", CS_CP1252 }, + + { "windows-1253", CS_CP1253 }, + + { "windows-1254", CS_CP1254 }, + + { "windows-1255", CS_CP1255 }, + + { "windows-1256", CS_CP1256 }, + + { "windows-1257", CS_CP1257 }, + + { "windows-1258", CS_CP1258 }, + + { "KOI8-R", CS_KOI8_R }, + { "csKOI8R", CS_KOI8_R }, + + { "KOI8-U", CS_KOI8_U }, + + { "macintosh", CS_MAC_ROMAN_OLD }, + { "mac", CS_MAC_ROMAN_OLD }, + { "csMacintosh", CS_MAC_ROMAN_OLD }, + + { "VISCII", CS_VISCII }, + { "csVISCII", CS_VISCII }, + + { "hp-roman8", CS_HP_ROMAN8 }, + { "roman8", CS_HP_ROMAN8 }, + { "r8", CS_HP_ROMAN8 }, + { "csHPRoman8", CS_HP_ROMAN8 }, + + { "DEC-MCS", CS_DEC_MCS }, + { "dec", CS_DEC_MCS }, + { "csDECMCS", CS_DEC_MCS }, + + { "UTF-8", CS_UTF8 }, +}; + +const char *charset_to_mimeenc(int charset) +{ + int i; + + for (i = 0; i < (int)lenof(mimeencs); i++) + if (charset == mimeencs[i].charset) + return mimeencs[i].name; + + return NULL; /* not found */ +} + +int charset_from_mimeenc(const char *name) +{ + int i; + + for (i = 0; i < (int)lenof(mimeencs); i++) { + const char *p, *q; + p = name; + q = mimeencs[i].name; + while (*p || *q) { + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + break; + p++; q++; + } + if (!*p && !*q) + return mimeencs[i].charset; + } + + return CS_NONE; /* not found */ +} diff --git a/0.77_My_PuTTY/charset/sbcs.c b/0.77_My_PuTTY/charset/sbcs.c new file mode 100644 index 0000000..8e2a227 --- /dev/null +++ b/0.77_My_PuTTY/charset/sbcs.c @@ -0,0 +1,53 @@ +/* + * sbcs.c - routines to handle single-byte character sets. + */ + +#include "charset.h" +#include "internal.h" + +/* + * The charset_spec for any single-byte character set should + * provide read_sbcs() as its read function, and its `data' field + * should be a wchar_t string constant containing the 256 entries + * of the translation table. + */ + +void read_sbcs(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx) +{ + const struct sbcs_data *sd = charset->data; + + UNUSEDARG(state); + + emit(emitctx, sd->sbcs2ucs[input_chr]); +} + +void write_sbcs(charset_spec const *charset, long int input_chr, + charset_state *state, + void (*emit)(void *ctx, long int output), void *emitctx) +{ + const struct sbcs_data *sd = charset->data; + int i, j, k, c; + + UNUSEDARG(state); + + /* + * Binary-search in the ucs2sbcs table. + */ + i = -1; + j = sd->nvalid; + while (i+1 < j) { + k = (i+j)/2; + c = sd->ucs2sbcs[k]; + if (input_chr < sd->sbcs2ucs[c]) + j = k; + else if (input_chr > sd->sbcs2ucs[c]) + i = k; + else { + emit(emitctx, c); + return; + } + } + emit(emitctx, ERROR); +} diff --git a/0.77_My_PuTTY/charset/sbcs.dat b/0.77_My_PuTTY/charset/sbcs.dat new file mode 100644 index 0000000..4bf300f --- /dev/null +++ b/0.77_My_PuTTY/charset/sbcs.dat @@ -0,0 +1,1139 @@ + 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/0.77_My_PuTTY/charset/sbcsdat.c b/0.77_My_PuTTY/charset/sbcsdat.c new file mode 100644 index 0000000..7a31bf1 --- /dev/null +++ b/0.77_My_PuTTY/charset/sbcsdat.c @@ -0,0 +1,4094 @@ +/* + * sbcsdat.c - data definitions for single-byte character sets. + * + * Generated by sbcsgen.pl from sbcs.dat. + * You should edit those files rather than editing this one. + */ + +#ifndef ENUM_CHARSETS + +#include "charset.h" +#include "internal.h" + +static const sbcs_data data_CS_ISO8859_1 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }, + 256 +}; +const charset_spec charset_CS_ISO8859_1 = { + CS_ISO8859_1, read_sbcs, write_sbcs, &data_CS_ISO8859_1 +}; + +static const sbcs_data data_CS_ISO8859_2 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x02d8, 0x0141, 0x00a4, 0x013d, 0x015a, 0x00a7, + 0x00a8, 0x0160, 0x015e, 0x0164, 0x0179, 0x00ad, 0x017d, 0x017b, + 0x00b0, 0x0105, 0x02db, 0x0142, 0x00b4, 0x013e, 0x015b, 0x02c7, + 0x00b8, 0x0161, 0x015f, 0x0165, 0x017a, 0x02dd, 0x017e, 0x017c, + 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, + 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, + 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, + 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, + 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, + 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa4, 0xa7, 0xa8, 0xad, 0xb0, 0xb4, 0xb8, + 0xc1, 0xc2, 0xc4, 0xc7, 0xc9, 0xcb, 0xcd, 0xce, + 0xd3, 0xd4, 0xd6, 0xd7, 0xda, 0xdc, 0xdd, 0xdf, + 0xe1, 0xe2, 0xe4, 0xe7, 0xe9, 0xeb, 0xed, 0xee, + 0xf3, 0xf4, 0xf6, 0xf7, 0xfa, 0xfc, 0xfd, 0xc3, + 0xe3, 0xa1, 0xb1, 0xc6, 0xe6, 0xc8, 0xe8, 0xcf, + 0xef, 0xd0, 0xf0, 0xca, 0xea, 0xcc, 0xec, 0xc5, + 0xe5, 0xa5, 0xb5, 0xa3, 0xb3, 0xd1, 0xf1, 0xd2, + 0xf2, 0xd5, 0xf5, 0xc0, 0xe0, 0xd8, 0xf8, 0xa6, + 0xb6, 0xaa, 0xba, 0xa9, 0xb9, 0xde, 0xfe, 0xab, + 0xbb, 0xd9, 0xf9, 0xdb, 0xfb, 0xac, 0xbc, 0xaf, + 0xbf, 0xae, 0xbe, 0xb7, 0xa2, 0xff, 0xb2, 0xbd + }, + 256 +}; +const charset_spec charset_CS_ISO8859_2 = { + CS_ISO8859_2, read_sbcs, write_sbcs, &data_CS_ISO8859_2 +}; + +static const sbcs_data data_CS_ISO8859_3 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0126, 0x02d8, 0x00a3, 0x00a4, ERROR , 0x0124, 0x00a7, + 0x00a8, 0x0130, 0x015e, 0x011e, 0x0134, 0x00ad, ERROR , 0x017b, + 0x00b0, 0x0127, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x0125, 0x00b7, + 0x00b8, 0x0131, 0x015f, 0x011f, 0x0135, 0x00bd, ERROR , 0x017c, + 0x00c0, 0x00c1, 0x00c2, ERROR , 0x00c4, 0x010a, 0x0108, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + ERROR , 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x0120, 0x00d6, 0x00d7, + 0x011c, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x016c, 0x015c, 0x00df, + 0x00e0, 0x00e1, 0x00e2, ERROR , 0x00e4, 0x010b, 0x0109, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + ERROR , 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x0121, 0x00f6, 0x00f7, + 0x011d, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x016d, 0x015d, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xb0, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb7, 0xb8, 0xbd, 0xc0, 0xc1, + 0xc2, 0xc4, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, + 0xcd, 0xce, 0xcf, 0xd1, 0xd2, 0xd3, 0xd4, 0xd6, + 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdf, 0xe0, 0xe1, + 0xe2, 0xe4, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, 0xf4, 0xf6, + 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xc6, 0xe6, 0xc5, + 0xe5, 0xd8, 0xf8, 0xab, 0xbb, 0xd5, 0xf5, 0xa6, + 0xb6, 0xa1, 0xb1, 0xa9, 0xb9, 0xac, 0xbc, 0xde, + 0xfe, 0xaa, 0xba, 0xdd, 0xfd, 0xaf, 0xbf, 0xa2, + 0xff + }, + 249 +}; +const charset_spec charset_CS_ISO8859_3 = { + CS_ISO8859_3, read_sbcs, write_sbcs, &data_CS_ISO8859_3 +}; + +static const sbcs_data data_CS_ISO8859_4 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x0138, 0x0156, 0x00a4, 0x0128, 0x013b, 0x00a7, + 0x00a8, 0x0160, 0x0112, 0x0122, 0x0166, 0x00ad, 0x017d, 0x00af, + 0x00b0, 0x0105, 0x02db, 0x0157, 0x00b4, 0x0129, 0x013c, 0x02c7, + 0x00b8, 0x0161, 0x0113, 0x0123, 0x0167, 0x014a, 0x017e, 0x014b, + 0x0100, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x012e, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x0116, 0x00cd, 0x00ce, 0x012a, + 0x0110, 0x0145, 0x014c, 0x0136, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x0172, 0x00da, 0x00db, 0x00dc, 0x0168, 0x016a, 0x00df, + 0x0101, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x012f, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x0117, 0x00ed, 0x00ee, 0x012b, + 0x0111, 0x0146, 0x014d, 0x0137, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x0173, 0x00fa, 0x00fb, 0x00fc, 0x0169, 0x016b, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa4, 0xa7, 0xa8, 0xad, 0xaf, 0xb0, 0xb4, + 0xb8, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc9, + 0xcb, 0xcd, 0xce, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xda, 0xdb, 0xdc, 0xdf, 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe9, 0xeb, 0xed, 0xee, 0xf4, 0xf5, + 0xf6, 0xf7, 0xf8, 0xfa, 0xfb, 0xfc, 0xc0, 0xe0, + 0xa1, 0xb1, 0xc8, 0xe8, 0xd0, 0xf0, 0xaa, 0xba, + 0xcc, 0xec, 0xca, 0xea, 0xab, 0xbb, 0xa5, 0xb5, + 0xcf, 0xef, 0xc7, 0xe7, 0xd3, 0xf3, 0xa2, 0xa6, + 0xb6, 0xd1, 0xf1, 0xbd, 0xbf, 0xd2, 0xf2, 0xa3, + 0xb3, 0xa9, 0xb9, 0xac, 0xbc, 0xdd, 0xfd, 0xde, + 0xfe, 0xd9, 0xf9, 0xae, 0xbe, 0xb7, 0xff, 0xb2 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_4 = { + CS_ISO8859_4, read_sbcs, write_sbcs, &data_CS_ISO8859_4 +}; + +static const sbcs_data data_CS_ISO8859_5 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, + 0x0408, 0x0409, 0x040a, 0x040b, 0x040c, 0x00ad, 0x040e, 0x040f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, + 0x2116, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457, + 0x0458, 0x0459, 0x045a, 0x045b, 0x045c, 0x00a7, 0x045e, 0x045f + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xfd, 0xad, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xae, + 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, + 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, + 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, + 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, + 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, + 0xef, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfe, 0xff, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_5 = { + CS_ISO8859_5, read_sbcs, write_sbcs, &data_CS_ISO8859_5 +}; + +static const sbcs_data data_CS_ISO8859_6 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, ERROR , ERROR , ERROR , 0x00a4, ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , 0x060c, 0x00ad, ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , 0x061b, ERROR , ERROR , ERROR , 0x061f, + ERROR , 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, + 0x0628, 0x0629, 0x062a, 0x062b, 0x062c, 0x062d, 0x062e, 0x062f, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, + 0x0638, 0x0639, 0x063a, ERROR , ERROR , ERROR , ERROR , ERROR , + 0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, + 0x0648, 0x0649, 0x064a, 0x064b, 0x064c, 0x064d, 0x064e, 0x064f, + 0x0650, 0x0651, 0x0652, ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa4, 0xad, 0xac, 0xbb, 0xbf, 0xc1, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2 + }, + 211 +}; +const charset_spec charset_CS_ISO8859_6 = { + CS_ISO8859_6, read_sbcs, write_sbcs, &data_CS_ISO8859_6 +}; + +static const sbcs_data data_CS_ISO8859_7 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x2018, 0x2019, 0x00a3, ERROR , ERROR , 0x00a6, 0x00a7, + 0x00a8, 0x00a9, ERROR , 0x00ab, 0x00ac, 0x00ad, ERROR , 0x2015, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x0384, 0x0385, 0x0386, 0x00b7, + 0x0388, 0x0389, 0x038a, 0x00bb, 0x038c, 0x00bd, 0x038e, 0x038f, + 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, + 0x03a0, 0x03a1, ERROR , 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, + 0x03a8, 0x03a9, 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03ae, 0x03af, + 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, + 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, + 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, + 0x03c8, 0x03c9, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03ce, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa3, 0xa6, 0xa7, 0xa8, 0xa9, 0xab, 0xac, + 0xad, 0xb0, 0xb1, 0xb2, 0xb3, 0xb7, 0xbb, 0xbd, + 0xb4, 0xb5, 0xb6, 0xb8, 0xb9, 0xba, 0xbc, 0xbe, + 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd1, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xaf, + 0xa1, 0xa2 + }, + 250 +}; +const charset_spec charset_CS_ISO8859_7 = { + CS_ISO8859_7, read_sbcs, write_sbcs, &data_CS_ISO8859_7 +}; + +static const sbcs_data data_CS_ISO8859_8 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, ERROR , 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00d7, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00f7, 0x00bb, 0x00bc, 0x00bd, 0x00be, ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , 0x2017, + 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, 0x05d7, + 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd, 0x05de, 0x05df, + 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5, 0x05e6, 0x05e7, + 0x05e8, 0x05e9, 0x05ea, ERROR , ERROR , 0x200e, 0x200f, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xbb, 0xbc, 0xbd, 0xbe, 0xaa, 0xba, 0xe0, 0xe1, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfd, 0xfe, 0xdf + }, + 220 +}; +const charset_spec charset_CS_ISO8859_8 = { + CS_ISO8859_8, read_sbcs, write_sbcs, &data_CS_ISO8859_8 +}; + +static const sbcs_data data_CS_ISO8859_9 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x011e, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0130, 0x015e, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x011f, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x0131, 0x015f, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xd9, 0xda, 0xdb, 0xdc, 0xdf, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xff, 0xd0, 0xf0, 0xdd, 0xfd, 0xde, 0xfe + }, + 256 +}; +const charset_spec charset_CS_ISO8859_9 = { + CS_ISO8859_9, read_sbcs, write_sbcs, &data_CS_ISO8859_9 +}; + +static const sbcs_data data_CS_ISO8859_10 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x0112, 0x0122, 0x012a, 0x0128, 0x0136, 0x00a7, + 0x013b, 0x0110, 0x0160, 0x0166, 0x017d, 0x00ad, 0x016a, 0x014a, + 0x00b0, 0x0105, 0x0113, 0x0123, 0x012b, 0x0129, 0x0137, 0x00b7, + 0x013c, 0x0111, 0x0161, 0x0167, 0x017e, 0x2015, 0x016b, 0x014b, + 0x0100, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x012e, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x0116, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x0145, 0x014c, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x0168, + 0x00d8, 0x0172, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x0101, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x012f, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x0117, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x0146, 0x014d, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x0169, + 0x00f8, 0x0173, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x0138 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa7, 0xad, 0xb0, 0xb7, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc9, 0xcb, 0xcd, 0xce, 0xcf, + 0xd0, 0xd3, 0xd4, 0xd5, 0xd6, 0xd8, 0xda, 0xdb, + 0xdc, 0xdd, 0xde, 0xdf, 0xe1, 0xe2, 0xe3, 0xe4, + 0xe5, 0xe6, 0xe9, 0xeb, 0xed, 0xee, 0xef, 0xf0, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf8, 0xfa, 0xfb, 0xfc, + 0xfd, 0xfe, 0xc0, 0xe0, 0xa1, 0xb1, 0xc8, 0xe8, + 0xa9, 0xb9, 0xa2, 0xb2, 0xcc, 0xec, 0xca, 0xea, + 0xa3, 0xb3, 0xa5, 0xb5, 0xa4, 0xb4, 0xc7, 0xe7, + 0xa6, 0xb6, 0xff, 0xa8, 0xb8, 0xd1, 0xf1, 0xaf, + 0xbf, 0xd2, 0xf2, 0xaa, 0xba, 0xab, 0xbb, 0xd7, + 0xf7, 0xae, 0xbe, 0xd9, 0xf9, 0xac, 0xbc, 0xbd + }, + 256 +}; +const charset_spec charset_CS_ISO8859_10 = { + CS_ISO8859_10, read_sbcs, write_sbcs, &data_CS_ISO8859_10 +}; + +static const sbcs_data data_CS_ISO8859_11 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0e01, 0x0e02, 0x0e03, 0x0e04, 0x0e05, 0x0e06, 0x0e07, + 0x0e08, 0x0e09, 0x0e0a, 0x0e0b, 0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f, + 0x0e10, 0x0e11, 0x0e12, 0x0e13, 0x0e14, 0x0e15, 0x0e16, 0x0e17, + 0x0e18, 0x0e19, 0x0e1a, 0x0e1b, 0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f, + 0x0e20, 0x0e21, 0x0e22, 0x0e23, 0x0e24, 0x0e25, 0x0e26, 0x0e27, + 0x0e28, 0x0e29, 0x0e2a, 0x0e2b, 0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f, + 0x0e30, 0x0e31, 0x0e32, 0x0e33, 0x0e34, 0x0e35, 0x0e36, 0x0e37, + 0x0e38, 0x0e39, 0x0e3a, ERROR , ERROR , ERROR , ERROR , 0x0e3f, + 0x0e40, 0x0e41, 0x0e42, 0x0e43, 0x0e44, 0x0e45, 0x0e46, 0x0e47, + 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x0e4d, 0x0e4e, 0x0e4f, + 0x0e50, 0x0e51, 0x0e52, 0x0e53, 0x0e54, 0x0e55, 0x0e56, 0x0e57, + 0x0e58, 0x0e59, 0x0e5a, 0x0e5b, ERROR , ERROR , ERROR , ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, + 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb + }, + 248 +}; +const charset_spec charset_CS_ISO8859_11 = { + CS_ISO8859_11, read_sbcs, write_sbcs, &data_CS_ISO8859_11 +}; + +static const sbcs_data data_CS_ISO8859_13 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x201d, 0x00a2, 0x00a3, 0x00a4, 0x201e, 0x00a6, 0x00a7, + 0x00d8, 0x00a9, 0x0156, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00c6, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x201c, 0x00b5, 0x00b6, 0x00b7, + 0x00f8, 0x00b9, 0x0157, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00e6, + 0x0104, 0x012e, 0x0100, 0x0106, 0x00c4, 0x00c5, 0x0118, 0x0112, + 0x010c, 0x00c9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012a, 0x013b, + 0x0160, 0x0143, 0x0145, 0x00d3, 0x014c, 0x00d5, 0x00d6, 0x00d7, + 0x0172, 0x0141, 0x015a, 0x016a, 0x00dc, 0x017b, 0x017d, 0x00df, + 0x0105, 0x012f, 0x0101, 0x0107, 0x00e4, 0x00e5, 0x0119, 0x0113, + 0x010d, 0x00e9, 0x017a, 0x0117, 0x0123, 0x0137, 0x012b, 0x013c, + 0x0161, 0x0144, 0x0146, 0x00f3, 0x014d, 0x00f5, 0x00f6, 0x00f7, + 0x0173, 0x0142, 0x015b, 0x016b, 0x00fc, 0x017c, 0x017e, 0x2019 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa2, 0xa3, 0xa4, 0xa6, 0xa7, 0xa9, 0xab, + 0xac, 0xad, 0xae, 0xb0, 0xb1, 0xb2, 0xb3, 0xb5, + 0xb6, 0xb7, 0xb9, 0xbb, 0xbc, 0xbd, 0xbe, 0xc4, + 0xc5, 0xaf, 0xc9, 0xd3, 0xd5, 0xd6, 0xd7, 0xa8, + 0xdc, 0xdf, 0xe4, 0xe5, 0xbf, 0xe9, 0xf3, 0xf5, + 0xf6, 0xf7, 0xb8, 0xfc, 0xc2, 0xe2, 0xc0, 0xe0, + 0xc3, 0xe3, 0xc8, 0xe8, 0xc7, 0xe7, 0xcb, 0xeb, + 0xc6, 0xe6, 0xcc, 0xec, 0xce, 0xee, 0xc1, 0xe1, + 0xcd, 0xed, 0xcf, 0xef, 0xd9, 0xf9, 0xd1, 0xf1, + 0xd2, 0xf2, 0xd4, 0xf4, 0xaa, 0xba, 0xda, 0xfa, + 0xd0, 0xf0, 0xdb, 0xfb, 0xd8, 0xf8, 0xca, 0xea, + 0xdd, 0xfd, 0xde, 0xfe, 0xff, 0xb4, 0xa1, 0xa5 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_13 = { + CS_ISO8859_13, read_sbcs, write_sbcs, &data_CS_ISO8859_13 +}; + +static const sbcs_data data_CS_ISO8859_14 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x1e02, 0x1e03, 0x00a3, 0x010a, 0x010b, 0x1e0a, 0x00a7, + 0x1e80, 0x00a9, 0x1e82, 0x1e0b, 0x1ef2, 0x00ad, 0x00ae, 0x0178, + 0x1e1e, 0x1e1f, 0x0120, 0x0121, 0x1e40, 0x1e41, 0x00b6, 0x1e56, + 0x1e81, 0x1e57, 0x1e83, 0x1e60, 0x1ef3, 0x1e84, 0x1e85, 0x1e61, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x0174, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x1e6a, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x0176, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x0175, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x1e6b, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x0177, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa3, 0xa7, 0xa9, 0xad, 0xae, 0xb6, 0xc0, + 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd1, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd8, 0xd9, 0xda, + 0xdb, 0xdc, 0xdd, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, + 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, + 0xff, 0xa4, 0xa5, 0xb2, 0xb3, 0xd0, 0xf0, 0xde, + 0xfe, 0xaf, 0xa1, 0xa2, 0xa6, 0xab, 0xb0, 0xb1, + 0xb4, 0xb5, 0xb7, 0xb9, 0xbb, 0xbf, 0xd7, 0xf7, + 0xa8, 0xb8, 0xaa, 0xba, 0xbd, 0xbe, 0xac, 0xbc + }, + 256 +}; +const charset_spec charset_CS_ISO8859_14 = { + CS_ISO8859_14, read_sbcs, write_sbcs, &data_CS_ISO8859_14 +}; + +static const sbcs_data data_CS_ISO8859_15 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x20ac, 0x00a5, 0x0160, 0x00a7, + 0x0161, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x017d, 0x00b5, 0x00b6, 0x00b7, + 0x017e, 0x00b9, 0x00ba, 0x00bb, 0x0152, 0x0153, 0x0178, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa5, 0xa7, 0xa9, 0xaa, + 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, + 0xb3, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0xbc, 0xbd, 0xa6, 0xa8, 0xbe, 0xb4, 0xb8, 0xa4 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_15 = { + CS_ISO8859_15, read_sbcs, write_sbcs, &data_CS_ISO8859_15 +}; + +static const sbcs_data data_CS_ISO8859_16 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x0104, 0x0105, 0x0141, 0x20ac, 0x201e, 0x0160, 0x00a7, + 0x0161, 0x00a9, 0x0218, 0x00ab, 0x0179, 0x00ad, 0x017a, 0x017b, + 0x00b0, 0x00b1, 0x010c, 0x0142, 0x017d, 0x201d, 0x00b6, 0x00b7, + 0x017e, 0x010d, 0x0219, 0x00bb, 0x0152, 0x0153, 0x0178, 0x017c, + 0x00c0, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0106, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x0110, 0x0143, 0x00d2, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x015a, + 0x0170, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0118, 0x021a, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x0107, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x0111, 0x0144, 0x00f2, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x015b, + 0x0171, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x0119, 0x021b, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa7, 0xa9, 0xab, 0xad, 0xb0, 0xb1, 0xb6, + 0xb7, 0xbb, 0xc0, 0xc1, 0xc2, 0xc4, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd2, 0xd3, 0xd4, 0xd6, 0xd9, 0xda, 0xdb, 0xdc, + 0xdf, 0xe0, 0xe1, 0xe2, 0xe4, 0xe6, 0xe7, 0xe8, + 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf2, + 0xf3, 0xf4, 0xf6, 0xf9, 0xfa, 0xfb, 0xfc, 0xff, + 0xc3, 0xe3, 0xa1, 0xa2, 0xc5, 0xe5, 0xb2, 0xb9, + 0xd0, 0xf0, 0xdd, 0xfd, 0xa3, 0xb3, 0xd1, 0xf1, + 0xd5, 0xf5, 0xbc, 0xbd, 0xd7, 0xf7, 0xa6, 0xa8, + 0xd8, 0xf8, 0xbe, 0xac, 0xae, 0xaf, 0xbf, 0xb4, + 0xb8, 0xaa, 0xba, 0xde, 0xfe, 0xb5, 0xa5, 0xa4 + }, + 256 +}; +const charset_spec charset_CS_ISO8859_16 = { + CS_ISO8859_16, read_sbcs, write_sbcs, &data_CS_ISO8859_16 +}; + +static const sbcs_data data_CS_ISO8859_1_X11 = { + { + 0x0020, 0x2666, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, + 0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, + 0x23ba, 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, + 0x252c, 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff + }, + { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x1c, 0x1d, 0x1a, 0x1b, 0x10, 0x11, 0x13, 0x14, + 0x03, 0x06, 0x0a, 0x04, 0x05, 0x09, 0x12, 0x19, + 0x0d, 0x0c, 0x0e, 0x0b, 0x15, 0x16, 0x18, 0x17, + 0x0f, 0x02, 0x01 + }, + 251 +}; +const charset_spec charset_CS_ISO8859_1_X11 = { + CS_ISO8859_1_X11, read_sbcs, write_sbcs, &data_CS_ISO8859_1_X11 +}; + +static const sbcs_data data_CS_CP437 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, + 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, + 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, + 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, + 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, + 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, + 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, + 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, + 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, + 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, + 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, + 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xff, 0xad, 0x9b, 0x9c, 0x9d, 0xa6, 0xae, 0xaa, + 0xf8, 0xf1, 0xfd, 0xe6, 0xfa, 0xa7, 0xaf, 0xac, + 0xab, 0xa8, 0x8e, 0x8f, 0x92, 0x80, 0x90, 0xa5, + 0x99, 0x9a, 0xe1, 0x85, 0xa0, 0x83, 0x84, 0x86, + 0x91, 0x87, 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, + 0x8c, 0x8b, 0xa4, 0x95, 0xa2, 0x93, 0x94, 0xf6, + 0x97, 0xa3, 0x96, 0x81, 0x98, 0x9f, 0xe2, 0xe9, + 0xe4, 0xe8, 0xea, 0xe0, 0xeb, 0xee, 0xe3, 0xe5, + 0xe7, 0xed, 0xfc, 0x9e, 0xf9, 0xfb, 0xec, 0xef, + 0xf7, 0xf0, 0xf3, 0xf2, 0xa9, 0xf4, 0xf5, 0xc4, + 0xb3, 0xda, 0xbf, 0xc0, 0xd9, 0xc3, 0xb4, 0xc2, + 0xc1, 0xc5, 0xcd, 0xba, 0xd5, 0xd6, 0xc9, 0xb8, + 0xb7, 0xbb, 0xd4, 0xd3, 0xc8, 0xbe, 0xbd, 0xbc, + 0xc6, 0xc7, 0xcc, 0xb5, 0xb6, 0xb9, 0xd1, 0xd2, + 0xcb, 0xcf, 0xd0, 0xca, 0xd8, 0xd7, 0xce, 0xdf, + 0xdc, 0xdb, 0xdd, 0xde, 0xb0, 0xb1, 0xb2, 0xfe + }, + 256 +}; +const charset_spec charset_CS_CP437 = { + CS_CP437, read_sbcs, write_sbcs, &data_CS_CP437 +}; + +static const sbcs_data data_CS_CP850 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, + 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, + 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, + 0x00ff, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x00d7, 0x0192, + 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, + 0x00bf, 0x00ae, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, 0x00c2, 0x00c0, + 0x00a9, 0x2563, 0x2551, 0x2557, 0x255d, 0x00a2, 0x00a5, 0x2510, + 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x00e3, 0x00c3, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4, + 0x00f0, 0x00d0, 0x00ca, 0x00cb, 0x00c8, 0x0131, 0x00cd, 0x00ce, + 0x00cf, 0x2518, 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580, + 0x00d3, 0x00df, 0x00d4, 0x00d2, 0x00f5, 0x00d5, 0x00b5, 0x00fe, + 0x00de, 0x00da, 0x00db, 0x00d9, 0x00fd, 0x00dd, 0x00af, 0x00b4, + 0x00ad, 0x00b1, 0x2017, 0x00be, 0x00b6, 0x00a7, 0x00f7, 0x00b8, + 0x00b0, 0x00a8, 0x00b7, 0x00b9, 0x00b3, 0x00b2, 0x25a0, 0x00a0 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xff, 0xad, 0xbd, 0x9c, 0xcf, 0xbe, 0xdd, 0xf5, + 0xf9, 0xb8, 0xa6, 0xae, 0xaa, 0xf0, 0xa9, 0xee, + 0xf8, 0xf1, 0xfd, 0xfc, 0xef, 0xe6, 0xf4, 0xfa, + 0xf7, 0xfb, 0xa7, 0xaf, 0xac, 0xab, 0xf3, 0xa8, + 0xb7, 0xb5, 0xb6, 0xc7, 0x8e, 0x8f, 0x92, 0x80, + 0xd4, 0x90, 0xd2, 0xd3, 0xde, 0xd6, 0xd7, 0xd8, + 0xd1, 0xa5, 0xe3, 0xe0, 0xe2, 0xe5, 0x99, 0x9e, + 0x9d, 0xeb, 0xe9, 0xea, 0x9a, 0xed, 0xe8, 0xe1, + 0x85, 0xa0, 0x83, 0xc6, 0x84, 0x86, 0x91, 0x87, + 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b, + 0xd0, 0xa4, 0x95, 0xa2, 0x93, 0xe4, 0x94, 0xf6, + 0x9b, 0x97, 0xa3, 0x96, 0x81, 0xec, 0xe7, 0x98, + 0xd5, 0x9f, 0xf2, 0xc4, 0xb3, 0xda, 0xbf, 0xc0, + 0xd9, 0xc3, 0xb4, 0xc2, 0xc1, 0xc5, 0xcd, 0xba, + 0xc9, 0xbb, 0xc8, 0xbc, 0xcc, 0xb9, 0xcb, 0xca, + 0xce, 0xdf, 0xdc, 0xdb, 0xb0, 0xb1, 0xb2, 0xfe + }, + 256 +}; +const charset_spec charset_CS_CP850 = { + CS_CP850, read_sbcs, write_sbcs, &data_CS_CP850 +}; + +static const sbcs_data data_CS_CP866 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, + 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, + 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, + 0x0401, 0x0451, 0x0404, 0x0454, 0x0407, 0x0457, 0x040e, 0x045e, + 0x00b0, 0x2219, 0x00b7, 0x221a, 0x2116, 0x00a4, 0x25a0, 0x00a0 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xff, 0xfd, 0xf8, 0xfa, 0xf0, 0xf2, 0xf4, 0xf6, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf1, 0xf3, 0xf5, 0xf7, 0xfc, 0xf9, 0xfb, 0xc4, + 0xb3, 0xda, 0xbf, 0xc0, 0xd9, 0xc3, 0xb4, 0xc2, + 0xc1, 0xc5, 0xcd, 0xba, 0xd5, 0xd6, 0xc9, 0xb8, + 0xb7, 0xbb, 0xd4, 0xd3, 0xc8, 0xbe, 0xbd, 0xbc, + 0xc6, 0xc7, 0xcc, 0xb5, 0xb6, 0xb9, 0xd1, 0xd2, + 0xcb, 0xcf, 0xd0, 0xca, 0xd8, 0xd7, 0xce, 0xdf, + 0xdc, 0xdb, 0xdd, 0xde, 0xb0, 0xb1, 0xb2, 0xfe + }, + 256 +}; +const charset_spec charset_CS_CP866 = { + CS_CP866, read_sbcs, write_sbcs, &data_CS_CP866 +}; + +static const sbcs_data data_CS_CP852 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x016f, 0x0107, 0x00e7, + 0x0142, 0x00eb, 0x0150, 0x0151, 0x00ee, 0x0179, 0x00c4, 0x0106, + 0x00c9, 0x0139, 0x013a, 0x00f4, 0x00f6, 0x013d, 0x013e, 0x015a, + 0x015b, 0x00d6, 0x00dc, 0x0164, 0x0165, 0x0141, 0x00d7, 0x010d, + 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x0104, 0x0105, 0x017d, 0x017e, + 0x0118, 0x0119, 0x00ac, 0x017a, 0x010c, 0x015f, 0x00ab, 0x00bb, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, 0x00c2, 0x011a, + 0x015e, 0x2563, 0x2551, 0x2557, 0x255d, 0x017b, 0x017c, 0x2510, + 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x0102, 0x0103, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4, + 0x0111, 0x0110, 0x010e, 0x00cb, 0x010f, 0x0147, 0x00cd, 0x00ce, + 0x011b, 0x2518, 0x250c, 0x2588, 0x2584, 0x0162, 0x016e, 0x2580, + 0x00d3, 0x00df, 0x00d4, 0x0143, 0x0144, 0x0148, 0x0160, 0x0161, + 0x0154, 0x00da, 0x0155, 0x0170, 0x00fd, 0x00dd, 0x0163, 0x00b4, + 0x00ad, 0x02dd, 0x02db, 0x02c7, 0x02d8, 0x00a7, 0x00f7, 0x00b8, + 0x00b0, 0x00a8, 0x02d9, 0x0171, 0x0158, 0x0159, 0x25a0, 0x00a0 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xff, 0xcf, 0xf5, 0xf9, 0xae, 0xaa, 0xf0, 0xf8, + 0xef, 0xf7, 0xaf, 0xb5, 0xb6, 0x8e, 0x80, 0x90, + 0xd3, 0xd6, 0xd7, 0xe0, 0xe2, 0x99, 0x9e, 0xe9, + 0x9a, 0xed, 0xe1, 0xa0, 0x83, 0x84, 0x87, 0x82, + 0x89, 0xa1, 0x8c, 0xa2, 0x93, 0x94, 0xf6, 0xa3, + 0x81, 0xec, 0xc6, 0xc7, 0xa4, 0xa5, 0x8f, 0x86, + 0xac, 0x9f, 0xd2, 0xd4, 0xd1, 0xd0, 0xa8, 0xa9, + 0xb7, 0xd8, 0x91, 0x92, 0x95, 0x96, 0x9d, 0x88, + 0xe3, 0xe4, 0xd5, 0xe5, 0x8a, 0x8b, 0xe8, 0xea, + 0xfc, 0xfd, 0x97, 0x98, 0xb8, 0xad, 0xe6, 0xe7, + 0xdd, 0xee, 0x9b, 0x9c, 0xde, 0x85, 0xeb, 0xfb, + 0x8d, 0xab, 0xbd, 0xbe, 0xa6, 0xa7, 0xf3, 0xf4, + 0xfa, 0xf2, 0xf1, 0xc4, 0xb3, 0xda, 0xbf, 0xc0, + 0xd9, 0xc3, 0xb4, 0xc2, 0xc1, 0xc5, 0xcd, 0xba, + 0xc9, 0xbb, 0xc8, 0xbc, 0xcc, 0xb9, 0xcb, 0xca, + 0xce, 0xdf, 0xdc, 0xdb, 0xb0, 0xb1, 0xb2, 0xfe + }, + 256 +}; +const charset_spec charset_CS_CP852 = { + CS_CP852, read_sbcs, write_sbcs, &data_CS_CP852 +}; + +static const sbcs_data data_CS_CP1250 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, ERROR , 0x201e, 0x2026, 0x2020, 0x2021, + ERROR , 0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179, + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + ERROR , 0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a, + 0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b, + 0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c, + 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, + 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, + 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, + 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, + 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, + 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa4, 0xa6, 0xa7, 0xa8, 0xa9, 0xab, 0xac, + 0xad, 0xae, 0xb0, 0xb1, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xbb, 0xc1, 0xc2, 0xc4, 0xc7, 0xc9, 0xcb, + 0xcd, 0xce, 0xd3, 0xd4, 0xd6, 0xd7, 0xda, 0xdc, + 0xdd, 0xdf, 0xe1, 0xe2, 0xe4, 0xe7, 0xe9, 0xeb, + 0xed, 0xee, 0xf3, 0xf4, 0xf6, 0xf7, 0xfa, 0xfc, + 0xfd, 0xc3, 0xe3, 0xa5, 0xb9, 0xc6, 0xe6, 0xc8, + 0xe8, 0xcf, 0xef, 0xd0, 0xf0, 0xca, 0xea, 0xcc, + 0xec, 0xc5, 0xe5, 0xbc, 0xbe, 0xa3, 0xb3, 0xd1, + 0xf1, 0xd2, 0xf2, 0xd5, 0xf5, 0xc0, 0xe0, 0xd8, + 0xf8, 0x8c, 0x9c, 0xaa, 0xba, 0x8a, 0x9a, 0xde, + 0xfe, 0x8d, 0x9d, 0xd9, 0xf9, 0xdb, 0xfb, 0x8f, + 0x9f, 0xaf, 0xbf, 0x8e, 0x9e, 0xa1, 0xa2, 0xff, + 0xb2, 0xbd, 0x96, 0x97, 0x91, 0x92, 0x82, 0x93, + 0x94, 0x84, 0x86, 0x87, 0x95, 0x85, 0x89, 0x8b, + 0x9b, 0x80, 0x99 + }, + 251 +}; +const charset_spec charset_CS_CP1250 = { + CS_CP1250, read_sbcs, write_sbcs, &data_CS_CP1250 +}; + +static const sbcs_data data_CS_CP1251 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0402, 0x0403, 0x201a, 0x0453, 0x201e, 0x2026, 0x2020, 0x2021, + 0x20ac, 0x2030, 0x0409, 0x2039, 0x040a, 0x040c, 0x040b, 0x040f, + 0x0452, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + ERROR , 0x2122, 0x0459, 0x203a, 0x045a, 0x045c, 0x045b, 0x045f, + 0x00a0, 0x040e, 0x045e, 0x0408, 0x00a4, 0x0490, 0x00a6, 0x00a7, + 0x0401, 0x00a9, 0x0404, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0407, + 0x00b0, 0x00b1, 0x0406, 0x0456, 0x0491, 0x00b5, 0x00b6, 0x00b7, + 0x0451, 0x2116, 0x0454, 0x00bb, 0x0458, 0x0405, 0x0455, 0x0457, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa4, 0xa6, 0xa7, 0xa9, 0xab, 0xac, 0xad, + 0xae, 0xb0, 0xb1, 0xb5, 0xb6, 0xb7, 0xbb, 0xa8, + 0x80, 0x81, 0xaa, 0xbd, 0xb2, 0xaf, 0xa3, 0x8a, + 0x8c, 0x8e, 0x8d, 0xa1, 0x8f, 0xc0, 0xc1, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xb8, 0x90, 0x83, + 0xba, 0xbe, 0xb3, 0xbf, 0xbc, 0x9a, 0x9c, 0x9e, + 0x9d, 0xa2, 0x9f, 0xa5, 0xb4, 0x96, 0x97, 0x91, + 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, 0x87, 0x95, + 0x85, 0x89, 0x8b, 0x9b, 0x88, 0xb9, 0x99 + }, + 255 +}; +const charset_spec charset_CS_CP1251 = { + CS_CP1251, read_sbcs, write_sbcs, &data_CS_CP1251 +}; + +static const sbcs_data data_CS_CP1252 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, ERROR , 0x017d, ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, ERROR , 0x017e, 0x0178, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x8c, 0x9c, 0x8a, 0x9a, 0x9f, 0x8e, 0x9e, 0x83, + 0x88, 0x98, 0x96, 0x97, 0x91, 0x92, 0x82, 0x93, + 0x94, 0x84, 0x86, 0x87, 0x95, 0x85, 0x89, 0x8b, + 0x9b, 0x80, 0x99 + }, + 251 +}; +const charset_spec charset_CS_CP1252 = { + CS_CP1252, read_sbcs, write_sbcs, &data_CS_CP1252 +}; + +static const sbcs_data data_CS_CP1253 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + ERROR , 0x2030, ERROR , 0x2039, ERROR , ERROR , ERROR , ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + ERROR , 0x2122, ERROR , 0x203a, ERROR , ERROR , ERROR , ERROR , + 0x00a0, 0x0385, 0x0386, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, ERROR , 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x2015, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x0384, 0x00b5, 0x00b6, 0x00b7, + 0x0388, 0x0389, 0x038a, 0x00bb, 0x038c, 0x00bd, 0x038e, 0x038f, + 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, + 0x03a0, 0x03a1, ERROR , 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, + 0x03a8, 0x03a9, 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03ae, 0x03af, + 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, + 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, + 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, + 0x03c8, 0x03c9, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03ce, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, + 0xab, 0xac, 0xad, 0xae, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb5, 0xb6, 0xb7, 0xbb, 0xbd, 0x83, 0xb4, 0xa1, + 0xa2, 0xb8, 0xb9, 0xba, 0xbc, 0xbe, 0xbf, 0xc0, + 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, + 0xd1, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0x96, 0x97, 0xaf, + 0x91, 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, 0x87, + 0x95, 0x85, 0x89, 0x8b, 0x9b, 0x80, 0x99 + }, + 239 +}; +const charset_spec charset_CS_CP1253 = { + CS_CP1253, read_sbcs, write_sbcs, &data_CS_CP1253 +}; + +static const sbcs_data data_CS_CP1254 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, ERROR , ERROR , ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, ERROR , ERROR , 0x0178, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x011e, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0130, 0x015e, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + 0x011f, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x0131, 0x015f, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + 0xd9, 0xda, 0xdb, 0xdc, 0xdf, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xff, 0xd0, 0xf0, 0xdd, 0xfd, 0x8c, 0x9c, + 0xde, 0xfe, 0x8a, 0x9a, 0x9f, 0x83, 0x88, 0x98, + 0x96, 0x97, 0x91, 0x92, 0x82, 0x93, 0x94, 0x84, + 0x86, 0x87, 0x95, 0x85, 0x89, 0x8b, 0x9b, 0x80, + 0x99 + }, + 249 +}; +const charset_spec charset_CS_CP1254 = { + CS_CP1254, read_sbcs, write_sbcs, &data_CS_CP1254 +}; + +static const sbcs_data data_CS_CP1255 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, ERROR , 0x2039, ERROR , ERROR , ERROR , ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, ERROR , 0x203a, ERROR , ERROR , ERROR , ERROR , + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x20aa, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00d7, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00f7, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x05b0, 0x05b1, 0x05b2, 0x05b3, 0x05b4, 0x05b5, 0x05b6, 0x05b7, + 0x05b8, 0x05b9, ERROR , 0x05bb, 0x05bc, 0x05bd, 0x05be, 0x05bf, + 0x05c0, 0x05c1, 0x05c2, 0x05c3, 0x05f0, 0x05f1, 0x05f2, 0x05f3, + 0x05f4, ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, 0x05d7, + 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd, 0x05de, 0x05df, + 0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5, 0x05e6, 0x05e7, + 0x05e8, 0x05e9, 0x05ea, ERROR , ERROR , 0x200e, 0x200f, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xaa, 0xba, 0x83, + 0x88, 0x98, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xfd, 0xfe, 0x96, + 0x97, 0x91, 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, + 0x87, 0x95, 0x85, 0x89, 0x8b, 0x9b, 0xa4, 0x80, + 0x99 + }, + 233 +}; +const charset_spec charset_CS_CP1255 = { + CS_CP1255, read_sbcs, write_sbcs, &data_CS_CP1255 +}; + +static const sbcs_data data_CS_CP1256 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, 0x067e, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, 0x0679, 0x2039, 0x0152, 0x0686, 0x0698, 0x0688, + 0x06af, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x06a9, 0x2122, 0x0691, 0x203a, 0x0153, 0x200c, 0x200d, 0x06ba, + 0x00a0, 0x060c, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x06be, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x061b, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x061f, + 0x06c1, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, + 0x0628, 0x0629, 0x062a, 0x062b, 0x062c, 0x062d, 0x062e, 0x062f, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x00d7, + 0x0637, 0x0638, 0x0639, 0x063a, 0x0640, 0x0641, 0x0642, 0x0643, + 0x00e0, 0x0644, 0x00e2, 0x0645, 0x0646, 0x0647, 0x0648, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x0649, 0x064a, 0x00ee, 0x00ef, + 0x064b, 0x064c, 0x064d, 0x064e, 0x00f4, 0x064f, 0x0650, 0x00f7, + 0x0651, 0x00f9, 0x0652, 0x00fb, 0x00fc, 0x200e, 0x200f, 0x06d2 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, + 0xa9, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, + 0xbb, 0xbc, 0xbd, 0xbe, 0xd7, 0xe0, 0xe2, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xee, 0xef, 0xf4, 0xf7, + 0xf9, 0xfb, 0xfc, 0x8c, 0x9c, 0x83, 0x88, 0xa1, + 0xba, 0xbf, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe1, 0xe3, 0xe4, 0xe5, 0xe6, 0xec, 0xed, 0xf0, + 0xf1, 0xf2, 0xf3, 0xf5, 0xf6, 0xf8, 0xfa, 0x8a, + 0x81, 0x8d, 0x8f, 0x9a, 0x8e, 0x98, 0x90, 0x9f, + 0xaa, 0xc0, 0xff, 0x9d, 0x9e, 0xfd, 0xfe, 0x96, + 0x97, 0x91, 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, + 0x87, 0x95, 0x85, 0x89, 0x8b, 0x9b, 0x80, 0x99 + }, + 256 +}; +const charset_spec charset_CS_CP1256 = { + CS_CP1256, read_sbcs, write_sbcs, &data_CS_CP1256 +}; + +static const sbcs_data data_CS_CP1257 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, ERROR , 0x201e, 0x2026, 0x2020, 0x2021, + ERROR , 0x2030, ERROR , 0x2039, ERROR , 0x00a8, 0x02c7, 0x00b8, + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + ERROR , 0x2122, ERROR , 0x203a, ERROR , 0x00af, 0x02db, ERROR , + 0x00a0, ERROR , 0x00a2, 0x00a3, 0x00a4, ERROR , 0x00a6, 0x00a7, + 0x00d8, 0x00a9, 0x0156, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00c6, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00f8, 0x00b9, 0x0157, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00e6, + 0x0104, 0x012e, 0x0100, 0x0106, 0x00c4, 0x00c5, 0x0118, 0x0112, + 0x010c, 0x00c9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012a, 0x013b, + 0x0160, 0x0143, 0x0145, 0x00d3, 0x014c, 0x00d5, 0x00d6, 0x00d7, + 0x0172, 0x0141, 0x015a, 0x016a, 0x00dc, 0x017b, 0x017d, 0x00df, + 0x0105, 0x012f, 0x0101, 0x0107, 0x00e4, 0x00e5, 0x0119, 0x0113, + 0x010d, 0x00e9, 0x017a, 0x0117, 0x0123, 0x0137, 0x012b, 0x013c, + 0x0161, 0x0144, 0x0146, 0x00f3, 0x014d, 0x00f5, 0x00f6, 0x00f7, + 0x0173, 0x0142, 0x015b, 0x016b, 0x00fc, 0x017c, 0x017e, 0x02d9 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa2, 0xa3, 0xa4, 0xa6, 0xa7, 0x8d, 0xa9, + 0xab, 0xac, 0xad, 0xae, 0x9d, 0xb0, 0xb1, 0xb2, + 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0x8f, 0xb9, 0xbb, + 0xbc, 0xbd, 0xbe, 0xc4, 0xc5, 0xaf, 0xc9, 0xd3, + 0xd5, 0xd6, 0xd7, 0xa8, 0xdc, 0xdf, 0xe4, 0xe5, + 0xbf, 0xe9, 0xf3, 0xf5, 0xf6, 0xf7, 0xb8, 0xfc, + 0xc2, 0xe2, 0xc0, 0xe0, 0xc3, 0xe3, 0xc8, 0xe8, + 0xc7, 0xe7, 0xcb, 0xeb, 0xc6, 0xe6, 0xcc, 0xec, + 0xce, 0xee, 0xc1, 0xe1, 0xcd, 0xed, 0xcf, 0xef, + 0xd9, 0xf9, 0xd1, 0xf1, 0xd2, 0xf2, 0xd4, 0xf4, + 0xaa, 0xba, 0xda, 0xfa, 0xd0, 0xf0, 0xdb, 0xfb, + 0xd8, 0xf8, 0xca, 0xea, 0xdd, 0xfd, 0xde, 0xfe, + 0x8e, 0xff, 0x9e, 0x96, 0x97, 0x91, 0x92, 0x82, + 0x93, 0x94, 0x84, 0x86, 0x87, 0x95, 0x85, 0x89, + 0x8b, 0x9b, 0x80, 0x99 + }, + 244 +}; +const charset_spec charset_CS_CP1257 = { + CS_CP1257, read_sbcs, write_sbcs, &data_CS_CP1257 +}; + +static const sbcs_data data_CS_CP1258 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x20ac, ERROR , 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, + 0x02c6, 0x2030, ERROR , 0x2039, 0x0152, ERROR , ERROR , ERROR , + ERROR , 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + 0x02dc, 0x2122, ERROR , 0x203a, 0x0153, ERROR , ERROR , 0x0178, + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x0300, 0x00cd, 0x00ce, 0x00cf, + 0x0110, 0x00d1, 0x0309, 0x00d3, 0x00d4, 0x01a0, 0x00d6, 0x00d7, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x01af, 0x0303, 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x0301, 0x00ed, 0x00ee, 0x00ef, + 0x0111, 0x00f1, 0x0323, 0x00f3, 0x00f4, 0x01a1, 0x00f6, 0x00f7, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x01b0, 0x20ab, 0x00ff + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf, 0xd1, 0xd3, + 0xd4, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, + 0xdf, 0xe0, 0xe1, 0xe2, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xed, 0xee, 0xef, 0xf1, + 0xf3, 0xf4, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xff, 0xc3, 0xe3, 0xd0, 0xf0, 0x8c, 0x9c, + 0x9f, 0x83, 0xd5, 0xf5, 0xdd, 0xfd, 0x88, 0x98, + 0xcc, 0xec, 0xde, 0xd2, 0xf2, 0x96, 0x97, 0x91, + 0x92, 0x82, 0x93, 0x94, 0x84, 0x86, 0x87, 0x95, + 0x85, 0x89, 0x8b, 0x9b, 0xfe, 0x80, 0x99 + }, + 247 +}; +const charset_spec charset_CS_CP1258 = { + CS_CP1258, read_sbcs, write_sbcs, &data_CS_CP1258 +}; + +static const sbcs_data data_CS_KOI8_R = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x2500, 0x2502, 0x250c, 0x2510, 0x2514, 0x2518, 0x251c, 0x2524, + 0x252c, 0x2534, 0x253c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590, + 0x2591, 0x2592, 0x2593, 0x2320, 0x25a0, 0x2219, 0x221a, 0x2248, + 0x2264, 0x2265, 0x00a0, 0x2321, 0x00b0, 0x00b2, 0x00b7, 0x00f7, + 0x2550, 0x2551, 0x2552, 0x0451, 0x2553, 0x2554, 0x2555, 0x2556, + 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x255c, 0x255d, 0x255e, + 0x255f, 0x2560, 0x2561, 0x0401, 0x2562, 0x2563, 0x2564, 0x2565, + 0x2566, 0x2567, 0x2568, 0x2569, 0x256a, 0x256b, 0x256c, 0x00a9, + 0x044e, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, + 0x0445, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, + 0x043f, 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, + 0x044c, 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, + 0x042e, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, + 0x0425, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, + 0x041f, 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, + 0x042c, 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x9a, 0xbf, 0x9c, 0x9d, 0x9e, 0x9f, 0xb3, 0xe1, + 0xe2, 0xf7, 0xe7, 0xe4, 0xe5, 0xf6, 0xfa, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf2, + 0xf3, 0xf4, 0xf5, 0xe6, 0xe8, 0xe3, 0xfe, 0xfb, + 0xfd, 0xff, 0xf9, 0xf8, 0xfc, 0xe0, 0xf1, 0xc1, + 0xc2, 0xd7, 0xc7, 0xc4, 0xc5, 0xd6, 0xda, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd2, + 0xd3, 0xd4, 0xd5, 0xc6, 0xc8, 0xc3, 0xde, 0xdb, + 0xdd, 0xdf, 0xd9, 0xd8, 0xdc, 0xc0, 0xd1, 0xa3, + 0x95, 0x96, 0x97, 0x98, 0x99, 0x93, 0x9b, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0xa0, 0xa1, 0xa2, 0xa4, 0xa5, 0xa6, + 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, + 0xaf, 0xb0, 0xb1, 0xb2, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0x8b, + 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x94 + }, + 256 +}; +const charset_spec charset_CS_KOI8_R = { + CS_KOI8_R, read_sbcs, write_sbcs, &data_CS_KOI8_R +}; + +static const sbcs_data data_CS_KOI8_U = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x2500, 0x2502, 0x250c, 0x2510, 0x2514, 0x2518, 0x251c, 0x2524, + 0x252c, 0x2534, 0x253c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590, + 0x2591, 0x2592, 0x2593, 0x2320, 0x25a0, 0x2219, 0x221a, 0x2248, + 0x2264, 0x2265, 0x00a0, 0x2321, 0x00b0, 0x00b2, 0x00b7, 0x00f7, + 0x2550, 0x2551, 0x2552, 0x0451, 0x0454, 0x2554, 0x0456, 0x0457, + 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x0491, 0x255d, 0x255e, + 0x255f, 0x2560, 0x2561, 0x0401, 0x0404, 0x2563, 0x0406, 0x0407, + 0x2566, 0x2567, 0x2568, 0x2569, 0x256a, 0x0490, 0x256c, 0x00a9, + 0x044e, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, + 0x0445, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, + 0x043f, 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, + 0x044c, 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, + 0x042e, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, + 0x0425, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, + 0x041f, 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, + 0x042c, 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x9a, 0xbf, 0x9c, 0x9d, 0x9e, 0x9f, 0xb3, 0xb4, + 0xb6, 0xb7, 0xe1, 0xe2, 0xf7, 0xe7, 0xe4, 0xe5, + 0xf6, 0xfa, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, + 0xef, 0xf0, 0xf2, 0xf3, 0xf4, 0xf5, 0xe6, 0xe8, + 0xe3, 0xfe, 0xfb, 0xfd, 0xff, 0xf9, 0xf8, 0xfc, + 0xe0, 0xf1, 0xc1, 0xc2, 0xd7, 0xc7, 0xc4, 0xc5, + 0xd6, 0xda, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, + 0xcf, 0xd0, 0xd2, 0xd3, 0xd4, 0xd5, 0xc6, 0xc8, + 0xc3, 0xde, 0xdb, 0xdd, 0xdf, 0xd9, 0xd8, 0xdc, + 0xc0, 0xd1, 0xa3, 0xa4, 0xa6, 0xa7, 0xbd, 0xad, + 0x95, 0x96, 0x97, 0x98, 0x99, 0x93, 0x9b, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0xa0, 0xa1, 0xa2, 0xa5, 0xa8, 0xa9, + 0xaa, 0xab, 0xac, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, + 0xb5, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbe, 0x8b, + 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x94 + }, + 256 +}; +const charset_spec charset_CS_KOI8_U = { + CS_KOI8_U, read_sbcs, write_sbcs, &data_CS_KOI8_U +}; + +static const sbcs_data data_CS_MAC_ROMAN = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x20ac, 0x2039, 0x203a, 0xfb01, 0xfb02, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xb4, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, 0xe9, + 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0x84, + 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, 0xf2, + 0xf3, 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, + 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, + 0x92, 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, + 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xd8, + 0xf5, 0xce, 0xcf, 0xd9, 0xc4, 0xf6, 0xff, 0xf9, + 0xfa, 0xfb, 0xfe, 0xf7, 0xfd, 0xbd, 0xb9, 0xd0, + 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, 0xd3, 0xe3, 0xa0, + 0xe0, 0xa5, 0xc9, 0xe4, 0xdc, 0xdd, 0xda, 0xdb, + 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, 0xb0, 0xba, + 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0, 0xde, 0xdf + }, + 256 +}; +const charset_spec charset_CS_MAC_ROMAN = { + CS_MAC_ROMAN, read_sbcs, write_sbcs, &data_CS_MAC_ROMAN +}; + +static const sbcs_data data_CS_MAC_TURKISH = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x011e, 0x011f, 0x0130, 0x0131, 0x015e, 0x015f, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, ERROR , 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xb4, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, 0xe9, + 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0x84, + 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, 0xf2, + 0xf3, 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, + 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, + 0x92, 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, + 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xd8, + 0xda, 0xdb, 0xdc, 0xdd, 0xce, 0xcf, 0xde, 0xdf, + 0xd9, 0xc4, 0xf6, 0xff, 0xf9, 0xfa, 0xfb, 0xfe, + 0xf7, 0xfd, 0xbd, 0xb9, 0xd0, 0xd1, 0xd4, 0xd5, + 0xe2, 0xd2, 0xd3, 0xe3, 0xa0, 0xe0, 0xa5, 0xc9, + 0xe4, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, 0xb0, + 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 255 +}; +const charset_spec charset_CS_MAC_TURKISH = { + CS_MAC_TURKISH, read_sbcs, write_sbcs, &data_CS_MAC_TURKISH +}; + +static const sbcs_data data_CS_MAC_CROATIAN = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x0160, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x017d, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x2206, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x0161, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x017e, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x0106, 0x00ab, + 0x010c, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x0110, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0xf8ff, 0x00a9, 0x2044, 0x20ac, 0x2039, 0x203a, 0x00c6, 0x00bb, + 0x2013, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x0107, 0x00c1, + 0x010d, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0x0111, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x03c0, 0x00cb, 0x02da, 0x00b8, 0x00ca, 0x00e6, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xa4, 0xac, 0xd9, 0xbb, + 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, 0xb5, + 0xa6, 0xe1, 0xfc, 0xbc, 0xdf, 0xc0, 0xcb, 0xe7, + 0xe5, 0xcc, 0x80, 0x81, 0xde, 0x82, 0xe9, 0x83, + 0xfd, 0xfa, 0xed, 0xea, 0xeb, 0xec, 0x84, 0xf1, + 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, 0xf2, 0xf3, + 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, + 0xfe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, + 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, + 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xc6, 0xe6, + 0xc8, 0xe8, 0xd0, 0xf0, 0xf5, 0xce, 0xcf, 0xa9, + 0xb9, 0xae, 0xbe, 0xc4, 0xf6, 0xff, 0xfb, 0xf7, + 0xbd, 0xf9, 0xe0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, + 0xd3, 0xe3, 0xa0, 0xa5, 0xc9, 0xe4, 0xdc, 0xdd, + 0xda, 0xdb, 0xaa, 0xb6, 0xb4, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xd8 + }, + 256 +}; +const charset_spec charset_CS_MAC_CROATIAN = { + CS_MAC_CROATIAN, read_sbcs, write_sbcs, &data_CS_MAC_CROATIAN +}; + +static const sbcs_data data_CS_MAC_ICELAND = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x00dd, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x20ac, 0x00d0, 0x00f0, 0x00de, 0x00fe, + 0x00fd, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xb4, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, 0xe9, + 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0xdc, + 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, + 0xf2, 0xf3, 0x86, 0xa0, 0xde, 0xa7, 0x88, 0x87, + 0x89, 0x8b, 0x8a, 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, + 0x90, 0x91, 0x93, 0x92, 0x94, 0x95, 0xdd, 0x96, + 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6, 0xbf, 0x9d, + 0x9c, 0x9e, 0x9f, 0xe0, 0xdf, 0xd8, 0xf5, 0xce, + 0xcf, 0xd9, 0xc4, 0xf6, 0xff, 0xf9, 0xfa, 0xfb, + 0xfe, 0xf7, 0xfd, 0xbd, 0xb9, 0xd0, 0xd1, 0xd4, + 0xd5, 0xe2, 0xd2, 0xd3, 0xe3, 0xa5, 0xc9, 0xe4, + 0xda, 0xdb, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_MAC_ICELAND = { + CS_MAC_ICELAND, read_sbcs, write_sbcs, &data_CS_MAC_ICELAND +}; + +static const sbcs_data data_CS_MAC_ROMANIAN = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x0102, 0x0218, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x0103, 0x0219, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x20ac, 0x2039, 0x203a, 0x021a, 0x021b, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xb4, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0x82, 0xe9, 0x83, + 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0x84, 0xf1, + 0xee, 0xef, 0xcd, 0x85, 0xf4, 0xf2, 0xf3, 0x86, + 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, 0x8d, + 0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, 0x95, + 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6, 0x9d, + 0x9c, 0x9e, 0x9f, 0xd8, 0xae, 0xbe, 0xf5, 0xce, + 0xcf, 0xd9, 0xc4, 0xaf, 0xbf, 0xde, 0xdf, 0xf6, + 0xff, 0xf9, 0xfa, 0xfb, 0xfe, 0xf7, 0xfd, 0xbd, + 0xb9, 0xd0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, 0xd3, + 0xe3, 0xa0, 0xe0, 0xa5, 0xc9, 0xe4, 0xdc, 0xdd, + 0xda, 0xdb, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_MAC_ROMANIAN = { + CS_MAC_ROMANIAN, read_sbcs, write_sbcs, &data_CS_MAC_ROMANIAN +}; + +static const sbcs_data data_CS_MAC_GREEK = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00b9, 0x00b2, 0x00c9, 0x00b3, 0x00d6, 0x00dc, 0x0385, + 0x00e0, 0x00e2, 0x00e4, 0x0384, 0x00a8, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00a3, 0x2122, 0x00ee, 0x00ef, 0x2022, 0x00bd, + 0x2030, 0x00f4, 0x00f6, 0x00a6, 0x20ac, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x0393, 0x0394, 0x0398, 0x039b, 0x039e, 0x03a0, 0x00df, + 0x00ae, 0x00a9, 0x03a3, 0x03aa, 0x00a7, 0x2260, 0x00b0, 0x00b7, + 0x0391, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x0392, 0x0395, 0x0396, + 0x0397, 0x0399, 0x039a, 0x039c, 0x03a6, 0x03ab, 0x03a8, 0x03a9, + 0x03ac, 0x039d, 0x00ac, 0x039f, 0x03a1, 0x2248, 0x03a4, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x03a5, 0x03a7, 0x0386, 0x0388, 0x0153, + 0x2013, 0x2015, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x0389, + 0x038a, 0x038c, 0x038e, 0x03ad, 0x03ae, 0x03af, 0x03cc, 0x038f, + 0x03cd, 0x03b1, 0x03b2, 0x03c8, 0x03b4, 0x03b5, 0x03c6, 0x03b3, + 0x03b7, 0x03b9, 0x03be, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03bf, + 0x03c0, 0x03ce, 0x03c1, 0x03c3, 0x03c4, 0x03b8, 0x03c9, 0x03c2, + 0x03c7, 0x03c5, 0x03b6, 0x03ca, 0x03cb, 0x0390, 0x03b0, 0x00ad + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0x92, 0xb4, 0x9b, 0xac, 0x8c, 0xa9, 0xc7, + 0xc2, 0xff, 0xa8, 0xae, 0xb1, 0x82, 0x84, 0xaf, + 0x81, 0xc8, 0x97, 0x80, 0x83, 0x85, 0x86, 0xa7, + 0x88, 0x89, 0x8a, 0x8d, 0x8f, 0x8e, 0x90, 0x91, + 0x94, 0x95, 0x99, 0x9a, 0xd6, 0x9d, 0x9e, 0x9f, + 0xcf, 0x8b, 0x87, 0xcd, 0xce, 0xd7, 0xd8, 0xd9, + 0xda, 0xdf, 0xfd, 0xb0, 0xb5, 0xa1, 0xa2, 0xb6, + 0xb7, 0xb8, 0xa3, 0xb9, 0xba, 0xa4, 0xbb, 0xc1, + 0xa5, 0xc3, 0xa6, 0xc4, 0xaa, 0xc6, 0xcb, 0xbc, + 0xcc, 0xbe, 0xbf, 0xab, 0xbd, 0xc0, 0xdb, 0xdc, + 0xdd, 0xfe, 0xe1, 0xe2, 0xe7, 0xe4, 0xe5, 0xfa, + 0xe8, 0xf5, 0xe9, 0xeb, 0xec, 0xed, 0xee, 0xea, + 0xef, 0xf0, 0xf2, 0xf7, 0xf3, 0xf4, 0xf9, 0xe6, + 0xf8, 0xe3, 0xf6, 0xfb, 0xfc, 0xde, 0xe0, 0xf1, + 0xd0, 0xd1, 0xd4, 0xd5, 0xd2, 0xd3, 0xa0, 0x96, + 0xc9, 0x98, 0x9c, 0x93, 0xc5, 0xad, 0xb2, 0xb3 + }, + 256 +}; +const charset_spec charset_CS_MAC_GREEK = { + CS_MAC_GREEK, read_sbcs, write_sbcs, &data_CS_MAC_GREEK +}; + +static const sbcs_data data_CS_MAC_CYRILLIC = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x2020, 0x00b0, 0x0490, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x0406, + 0x00ae, 0x00a9, 0x2122, 0x0402, 0x0452, 0x2260, 0x0403, 0x0453, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x0456, 0x00b5, 0x0491, 0x0408, + 0x0404, 0x0454, 0x0407, 0x0457, 0x0409, 0x0459, 0x040a, 0x045a, + 0x0458, 0x0405, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x040b, 0x045b, 0x040c, 0x045c, 0x0455, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x201e, + 0x040e, 0x045e, 0x040f, 0x045f, 0x2116, 0x0401, 0x0451, 0x044f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x20ac + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xa3, 0xa4, 0xa9, 0xc7, 0xc2, 0xa8, 0xa1, + 0xb1, 0xb5, 0xa6, 0xc8, 0xd6, 0xc4, 0xdd, 0xab, + 0xae, 0xb8, 0xc1, 0xa7, 0xba, 0xb7, 0xbc, 0xbe, + 0xcb, 0xcd, 0xd8, 0xda, 0x80, 0x81, 0x82, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, + 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, + 0x9c, 0x9d, 0x9e, 0x9f, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, + 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe, 0xdf, 0xde, 0xac, 0xaf, 0xb9, + 0xcf, 0xb4, 0xbb, 0xc0, 0xbd, 0xbf, 0xcc, 0xce, + 0xd9, 0xdb, 0xa2, 0xb6, 0xd0, 0xd1, 0xd4, 0xd5, + 0xd2, 0xd3, 0xd7, 0xa0, 0xa5, 0xc9, 0xff, 0xdc, + 0xaa, 0xc6, 0xc3, 0xb0, 0xc5, 0xad, 0xb2, 0xb3 + }, + 256 +}; +const charset_spec charset_CS_MAC_CYRILLIC = { + CS_MAC_CYRILLIC, read_sbcs, write_sbcs, &data_CS_MAC_CYRILLIC +}; + +static const sbcs_data data_CS_MAC_THAI = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00ab, 0x00bb, 0x2026, 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, + 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x201c, 0x201d, 0x0e4d, + ERROR , 0x2022, 0x0e31, 0x0e47, 0x0e34, 0x0e35, 0x0e36, 0x0e37, + 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x2018, 0x2019, ERROR , + 0x00a0, 0x0e01, 0x0e02, 0x0e03, 0x0e04, 0x0e05, 0x0e06, 0x0e07, + 0x0e08, 0x0e09, 0x0e0a, 0x0e0b, 0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f, + 0x0e10, 0x0e11, 0x0e12, 0x0e13, 0x0e14, 0x0e15, 0x0e16, 0x0e17, + 0x0e18, 0x0e19, 0x0e1a, 0x0e1b, 0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f, + 0x0e20, 0x0e21, 0x0e22, 0x0e23, 0x0e24, 0x0e25, 0x0e26, 0x0e27, + 0x0e28, 0x0e29, 0x0e2a, 0x0e2b, 0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f, + 0x0e30, 0x0e31, 0x0e32, 0x0e33, 0x0e34, 0x0e35, 0x0e36, 0x0e37, + 0x0e38, 0x0e39, 0x0e3a, 0x2060, 0x200b, 0x2013, 0x2014, 0x0e3f, + 0x0e40, 0x0e41, 0x0e42, 0x0e43, 0x0e44, 0x0e45, 0x0e46, 0x0e47, + 0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c, 0x0e4d, 0x2122, 0x0e4f, + 0x0e50, 0x0e51, 0x0e52, 0x0e53, 0x0e54, 0x0e55, 0x0e56, 0x0e57, + 0x0e58, 0x0e59, 0x00ae, 0x00a9, ERROR , ERROR , ERROR , ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xa0, 0xfb, 0x80, 0xfa, 0x81, 0xa1, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, + 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, + 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xef, 0xf0, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xdc, 0xdd, 0xde, 0x9d, 0x9e, 0x8d, 0x8e, + 0x91, 0x82, 0xdb, 0xee + }, + 228 +}; +const charset_spec charset_CS_MAC_THAI = { + CS_MAC_THAI, read_sbcs, write_sbcs, &data_CS_MAC_THAI +}; + +static const sbcs_data data_CS_MAC_CENTEURO = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x0100, 0x0101, 0x00c9, 0x0104, 0x00d6, 0x00dc, 0x00e1, + 0x0105, 0x010c, 0x00e4, 0x010d, 0x0106, 0x0107, 0x00e9, 0x0179, + 0x017a, 0x010e, 0x00ed, 0x010f, 0x0112, 0x0113, 0x0116, 0x00f3, + 0x0117, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x011a, 0x011b, 0x00fc, + 0x2020, 0x00b0, 0x0118, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x0119, 0x00a8, 0x2260, 0x0123, 0x012e, + 0x012f, 0x012a, 0x2264, 0x2265, 0x012b, 0x0136, 0x2202, 0x2211, + 0x0142, 0x013b, 0x013c, 0x013d, 0x013e, 0x0139, 0x013a, 0x0145, + 0x0146, 0x0143, 0x00ac, 0x221a, 0x0144, 0x0147, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x0148, 0x0150, 0x00d5, 0x0151, 0x014c, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x014d, 0x0154, 0x0155, 0x0158, 0x2039, 0x203a, 0x0159, 0x0156, + 0x0157, 0x0160, 0x201a, 0x201e, 0x0161, 0x015a, 0x015b, 0x00c1, + 0x0164, 0x0165, 0x00cd, 0x017d, 0x017e, 0x016a, 0x00d3, 0x00d4, + 0x016b, 0x016e, 0x00da, 0x016f, 0x0170, 0x0171, 0x0172, 0x0173, + 0x00dd, 0x00fd, 0x0137, 0x017b, 0x0141, 0x017c, 0x0122, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xa3, 0xa4, 0xac, 0xa9, 0xc7, 0xc2, 0xa8, + 0xa1, 0xa6, 0xc8, 0xe7, 0x80, 0x83, 0xea, 0xee, + 0xef, 0xcd, 0x85, 0xf2, 0x86, 0xf8, 0xa7, 0x87, + 0x8a, 0x8e, 0x92, 0x97, 0x99, 0x9b, 0x9a, 0xd6, + 0x9c, 0x9f, 0xf9, 0x81, 0x82, 0x84, 0x88, 0x8c, + 0x8d, 0x89, 0x8b, 0x91, 0x93, 0x94, 0x95, 0x96, + 0x98, 0xa2, 0xab, 0x9d, 0x9e, 0xfe, 0xae, 0xb1, + 0xb4, 0xaf, 0xb0, 0xb5, 0xfa, 0xbd, 0xbe, 0xb9, + 0xba, 0xbb, 0xbc, 0xfc, 0xb8, 0xc1, 0xc4, 0xbf, + 0xc0, 0xc5, 0xcb, 0xcf, 0xd8, 0xcc, 0xce, 0xd9, + 0xda, 0xdf, 0xe0, 0xdb, 0xde, 0xe5, 0xe6, 0xe1, + 0xe4, 0xe8, 0xe9, 0xed, 0xf0, 0xf1, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0x8f, 0x90, 0xfb, 0xfd, 0xeb, + 0xec, 0xff, 0xd0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, + 0xd3, 0xe3, 0xa0, 0xa5, 0xc9, 0xdc, 0xdd, 0xaa, + 0xb6, 0xc6, 0xb7, 0xc3, 0xad, 0xb2, 0xb3, 0xd7 + }, + 256 +}; +const charset_spec charset_CS_MAC_CENTEURO = { + CS_MAC_CENTEURO, read_sbcs, write_sbcs, &data_CS_MAC_CENTEURO +}; + +static const sbcs_data data_CS_MAC_SYMBOL = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x2200, 0x0023, 0x2203, 0x0025, 0x0026, 0x220d, + 0x0028, 0x0029, 0x2217, 0x002b, 0x002c, 0x2212, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x2245, 0x0391, 0x0392, 0x03a7, 0x0394, 0x0395, 0x03a6, 0x0393, + 0x0397, 0x0399, 0x03d1, 0x039a, 0x039b, 0x039c, 0x039d, 0x039f, + 0x03a0, 0x0398, 0x03a1, 0x03a3, 0x03a4, 0x03a5, 0x03c2, 0x03a9, + 0x039e, 0x03a8, 0x0396, 0x005b, 0x2234, 0x005d, 0x22a5, 0x005f, + 0xf8e5, 0x03b1, 0x03b2, 0x03c7, 0x03b4, 0x03b5, 0x03c6, 0x03b3, + 0x03b7, 0x03b9, 0x03d5, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03bf, + 0x03c0, 0x03b8, 0x03c1, 0x03c3, 0x03c4, 0x03c5, 0x03d6, 0x03c9, + 0x03be, 0x03c8, 0x03b6, 0x007b, 0x007c, 0x007d, 0x223c, 0x007f, + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + 0x20ac, 0x03d2, 0x2032, 0x2264, 0x2044, 0x221e, 0x0192, 0x2663, + 0x2666, 0x2665, 0x2660, 0x2194, 0x2190, 0x2191, 0x2192, 0x2193, + 0x00b0, 0x00b1, 0x2033, 0x2265, 0x00d7, 0x221d, 0x2202, 0x2022, + 0x00f7, 0x2260, 0x2261, 0x2248, 0x2026, 0xf8e6, 0x23af, 0x21b5, + 0x2135, 0x2111, 0x211c, 0x2118, 0x2297, 0x2295, 0x2205, 0x2229, + 0x222a, 0x2283, 0x2287, 0x2284, 0x2282, 0x2286, 0x2208, 0x2209, + 0x2220, 0x2207, 0x00ae, 0x00a9, 0x2122, 0x220f, 0x221a, 0x22c5, + 0x00ac, 0x2227, 0x2228, 0x21d4, 0x21d0, 0x21d1, 0x21d2, 0x21d3, + 0x22c4, 0x3008, 0x00ae, 0x00a9, 0x2122, 0x2211, 0x239b, 0x239c, + 0x239d, 0x23a1, 0x23a2, 0x23a3, 0x23a7, 0x23a8, 0x23a9, 0x23aa, + 0xf8ff, 0x3009, 0x222b, 0x2320, 0x23ae, 0x2321, 0x239e, 0x239f, + 0x23a0, 0x23a4, 0x23a5, 0x23a6, 0x23ab, 0x23ac, 0x23ad, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x23, 0x25, 0x26, 0x28, 0x29, 0x2b, + 0x2c, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, + 0x3d, 0x3e, 0x3f, 0x5b, 0x5d, 0x5f, 0x7b, 0x7c, + 0x7d, 0x7f, 0xd3, 0xd8, 0xd2, 0xb0, 0xb1, 0xb4, + 0xb8, 0xa6, 0x41, 0x42, 0x47, 0x44, 0x45, 0x5a, + 0x48, 0x51, 0x49, 0x4b, 0x4c, 0x4d, 0x4e, 0x58, + 0x4f, 0x50, 0x52, 0x53, 0x54, 0x55, 0x46, 0x43, + 0x59, 0x57, 0x61, 0x62, 0x67, 0x64, 0x65, 0x7a, + 0x68, 0x71, 0x69, 0x6b, 0x6c, 0x6d, 0x6e, 0x78, + 0x6f, 0x70, 0x72, 0x56, 0x73, 0x74, 0x75, 0x66, + 0x63, 0x79, 0x77, 0x4a, 0xa1, 0x6a, 0x76, 0xb7, + 0xbc, 0xa2, 0xb2, 0xa4, 0xa0, 0xc1, 0xc3, 0xc2, + 0xd4, 0xc0, 0xac, 0xad, 0xae, 0xaf, 0xab, 0xbf, + 0xdc, 0xdd, 0xde, 0xdf, 0xdb, 0x22, 0xb6, 0x24, + 0xc6, 0xd1, 0xce, 0xcf, 0x27, 0xd5, 0xe5, 0x2d, + 0x2a, 0xd6, 0xb5, 0xa5, 0xd0, 0xd9, 0xda, 0xc7, + 0xc8, 0xf2, 0x5c, 0x7e, 0x40, 0xbb, 0xb9, 0xba, + 0xa3, 0xb3, 0xcc, 0xc9, 0xcb, 0xcd, 0xca, 0xc5, + 0xc4, 0x5e, 0xe0, 0xd7, 0xf3, 0xf5, 0xe6, 0xe7, + 0xe8, 0xf6, 0xf7, 0xf8, 0xe9, 0xea, 0xeb, 0xf9, + 0xfa, 0xfb, 0xec, 0xed, 0xee, 0xef, 0xfc, 0xfd, + 0xfe, 0xf4, 0xbe, 0xaa, 0xa7, 0xa9, 0xa8, 0xe1, + 0xf1, 0x60, 0xbd, 0xf0 + }, + 220 +}; +const charset_spec charset_CS_MAC_SYMBOL = { + CS_MAC_SYMBOL, read_sbcs, write_sbcs, &data_CS_MAC_SYMBOL +}; + +static const sbcs_data data_CS_MAC_DINGBATS = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x2701, 0x2702, 0x2703, 0x2704, 0x260e, 0x2706, 0x2707, + 0x2708, 0x2709, 0x261b, 0x261e, 0x270c, 0x270d, 0x270e, 0x270f, + 0x2710, 0x2711, 0x2712, 0x2713, 0x2714, 0x2715, 0x2716, 0x2717, + 0x2718, 0x2719, 0x271a, 0x271b, 0x271c, 0x271d, 0x271e, 0x271f, + 0x2720, 0x2721, 0x2722, 0x2723, 0x2724, 0x2725, 0x2726, 0x2727, + 0x2605, 0x2729, 0x272a, 0x272b, 0x272c, 0x272d, 0x272e, 0x272f, + 0x2730, 0x2731, 0x2732, 0x2733, 0x2734, 0x2735, 0x2736, 0x2737, + 0x2738, 0x2739, 0x273a, 0x273b, 0x273c, 0x273d, 0x273e, 0x273f, + 0x2740, 0x2741, 0x2742, 0x2743, 0x2744, 0x2745, 0x2746, 0x2747, + 0x2748, 0x2749, 0x274a, 0x274b, 0x25cf, 0x274d, 0x25a0, 0x274f, + 0x2750, 0x2751, 0x2752, 0x25b2, 0x25bc, 0x25c6, 0x2756, 0x25d7, + 0x2758, 0x2759, 0x275a, 0x275b, 0x275c, 0x275d, 0x275e, 0x007f, + 0x2768, 0x2769, 0x276a, 0x276b, 0x276c, 0x276d, 0x276e, 0x276f, + 0x2770, 0x2771, 0x2772, 0x2773, 0x2774, 0x2775, ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , ERROR , + ERROR , 0x2761, 0x2762, 0x2763, 0x2764, 0x2765, 0x2766, 0x2767, + 0x2663, 0x2666, 0x2665, 0x2660, 0x2460, 0x2461, 0x2462, 0x2463, + 0x2464, 0x2465, 0x2466, 0x2467, 0x2468, 0x2469, 0x2776, 0x2777, + 0x2778, 0x2779, 0x277a, 0x277b, 0x277c, 0x277d, 0x277e, 0x277f, + 0x2780, 0x2781, 0x2782, 0x2783, 0x2784, 0x2785, 0x2786, 0x2787, + 0x2788, 0x2789, 0x278a, 0x278b, 0x278c, 0x278d, 0x278e, 0x278f, + 0x2790, 0x2791, 0x2792, 0x2793, 0x2794, 0x2192, 0x2194, 0x2195, + 0x2798, 0x2799, 0x279a, 0x279b, 0x279c, 0x279d, 0x279e, 0x279f, + 0x27a0, 0x27a1, 0x27a2, 0x27a3, 0x27a4, 0x27a5, 0x27a6, 0x27a7, + 0x27a8, 0x27a9, 0x27aa, 0x27ab, 0x27ac, 0x27ad, 0x27ae, 0x27af, + ERROR , 0x27b1, 0x27b2, 0x27b3, 0x27b4, 0x27b5, 0x27b6, 0x27b7, + 0x27b8, 0x27b9, 0x27ba, 0x27bb, 0x27bc, 0x27bd, 0x27be, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x7f, 0xd5, 0xd6, 0xd7, 0xac, 0xad, 0xae, + 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0x6e, + 0x73, 0x74, 0x75, 0x6c, 0x77, 0x48, 0x25, 0x2a, + 0x2b, 0xab, 0xa8, 0xaa, 0xa9, 0x21, 0x22, 0x23, + 0x24, 0x26, 0x27, 0x28, 0x29, 0x2c, 0x2d, 0x2e, + 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, + 0x47, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6d, 0x6f, 0x70, 0x71, + 0x72, 0x76, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, + 0x7e, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd8, 0xd9, 0xda, + 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe + }, + 235 +}; +const charset_spec charset_CS_MAC_DINGBATS = { + CS_MAC_DINGBATS, read_sbcs, write_sbcs, &data_CS_MAC_DINGBATS +}; + +static const sbcs_data data_CS_MAC_ROMAN_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x00a4, 0x2039, 0x203a, 0xfb01, 0xfb02, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xa4, 0xac, + 0xa9, 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, + 0xab, 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, + 0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, + 0xe9, 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, + 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, + 0xf2, 0xf3, 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, + 0x8a, 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, + 0x93, 0x92, 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, + 0x9b, 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, + 0xd8, 0xf5, 0xce, 0xcf, 0xd9, 0xc4, 0xf6, 0xff, + 0xf9, 0xfa, 0xfb, 0xfe, 0xf7, 0xfd, 0xbd, 0xb9, + 0xd0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, 0xd3, 0xe3, + 0xa0, 0xe0, 0xa5, 0xc9, 0xe4, 0xdc, 0xdd, 0xda, + 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, 0xb0, 0xba, + 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0, 0xde, 0xdf + }, + 256 +}; +const charset_spec charset_CS_MAC_ROMAN_OLD = { + CS_MAC_ROMAN_OLD, read_sbcs, write_sbcs, &data_CS_MAC_ROMAN_OLD +}; + +static const sbcs_data data_CS_MAC_CROATIAN_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x0160, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x017d, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x2206, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x0161, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x017e, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x0106, 0x00ab, + 0x010c, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x0110, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0xf8ff, 0x00a9, 0x2044, 0x00a4, 0x2039, 0x203a, 0x00c6, 0x00bb, + 0x2013, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x0107, 0x00c1, + 0x010d, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0x0111, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x03c0, 0x00cb, 0x02da, 0x00b8, 0x00ca, 0x00e6, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xa4, 0xac, 0xd9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, 0xab, + 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xdf, 0xc0, 0xcb, + 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xde, 0x82, 0xe9, + 0x83, 0xfd, 0xfa, 0xed, 0xea, 0xeb, 0xec, 0x84, + 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, 0xf4, 0xf2, + 0xf3, 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, + 0x8c, 0xfe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, + 0x92, 0x94, 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, + 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, 0xc6, + 0xe6, 0xc8, 0xe8, 0xd0, 0xf0, 0xf5, 0xce, 0xcf, + 0xa9, 0xb9, 0xae, 0xbe, 0xc4, 0xf6, 0xff, 0xfb, + 0xf7, 0xbd, 0xf9, 0xe0, 0xd1, 0xd4, 0xd5, 0xe2, + 0xd2, 0xd3, 0xe3, 0xa0, 0xa5, 0xc9, 0xe4, 0xdc, + 0xdd, 0xda, 0xaa, 0xb6, 0xb4, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xd8 + }, + 256 +}; +const charset_spec charset_CS_MAC_CROATIAN_OLD = { + CS_MAC_CROATIAN_OLD, read_sbcs, write_sbcs, &data_CS_MAC_CROATIAN_OLD +}; + +static const sbcs_data data_CS_MAC_ICELAND_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x00dd, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x00a4, 0x00d0, 0x00f0, 0x00de, 0x00fe, + 0x00fd, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xa4, 0xac, + 0xa9, 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, + 0xab, 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, + 0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0xae, 0x82, + 0xe9, 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, + 0xdc, 0x84, 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xaf, + 0xf4, 0xf2, 0xf3, 0x86, 0xa0, 0xde, 0xa7, 0x88, + 0x87, 0x89, 0x8b, 0x8a, 0x8c, 0xbe, 0x8d, 0x8f, + 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, 0x95, 0xdd, + 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6, 0xbf, + 0x9d, 0x9c, 0x9e, 0x9f, 0xe0, 0xdf, 0xd8, 0xf5, + 0xce, 0xcf, 0xd9, 0xc4, 0xf6, 0xff, 0xf9, 0xfa, + 0xfb, 0xfe, 0xf7, 0xfd, 0xbd, 0xb9, 0xd0, 0xd1, + 0xd4, 0xd5, 0xe2, 0xd2, 0xd3, 0xe3, 0xa5, 0xc9, + 0xe4, 0xda, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_MAC_ICELAND_OLD = { + CS_MAC_ICELAND_OLD, read_sbcs, write_sbcs, &data_CS_MAC_ICELAND_OLD +}; + +static const sbcs_data data_CS_MAC_ROMANIAN_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x0102, 0x0218, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x2202, 0x2211, + 0x220f, 0x03c0, 0x222b, 0x00aa, 0x00ba, 0x03a9, 0x0103, 0x0219, + 0x00bf, 0x00a1, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x25ca, + 0x00ff, 0x0178, 0x2044, 0x00a4, 0x2039, 0x203a, 0x021a, 0x021b, + 0x2021, 0x00b7, 0x201a, 0x201e, 0x2030, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + 0xf8ff, 0x00d2, 0x00da, 0x00db, 0x00d9, 0x0131, 0x02c6, 0x02dc, + 0x00af, 0x02d8, 0x02d9, 0x02da, 0x00b8, 0x02dd, 0x02db, 0x02c7 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xc1, 0xa2, 0xa3, 0xdb, 0xb4, 0xa4, 0xac, + 0xa9, 0xbb, 0xc7, 0xc2, 0xa8, 0xf8, 0xa1, 0xb1, + 0xab, 0xb5, 0xa6, 0xe1, 0xfc, 0xbc, 0xc8, 0xc0, + 0xcb, 0xe7, 0xe5, 0xcc, 0x80, 0x81, 0x82, 0xe9, + 0x83, 0xe6, 0xe8, 0xed, 0xea, 0xeb, 0xec, 0x84, + 0xf1, 0xee, 0xef, 0xcd, 0x85, 0xf4, 0xf2, 0xf3, + 0x86, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, 0x8c, + 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, 0x92, 0x94, + 0x95, 0x96, 0x98, 0x97, 0x99, 0x9b, 0x9a, 0xd6, + 0x9d, 0x9c, 0x9e, 0x9f, 0xd8, 0xae, 0xbe, 0xf5, + 0xce, 0xcf, 0xd9, 0xc4, 0xaf, 0xbf, 0xde, 0xdf, + 0xf6, 0xff, 0xf9, 0xfa, 0xfb, 0xfe, 0xf7, 0xfd, + 0xbd, 0xb9, 0xd0, 0xd1, 0xd4, 0xd5, 0xe2, 0xd2, + 0xd3, 0xe3, 0xa0, 0xe0, 0xa5, 0xc9, 0xe4, 0xdc, + 0xdd, 0xda, 0xaa, 0xb6, 0xc6, 0xb8, 0xb7, 0xc3, + 0xb0, 0xba, 0xc5, 0xad, 0xb2, 0xb3, 0xd7, 0xf0 + }, + 256 +}; +const charset_spec charset_CS_MAC_ROMANIAN_OLD = { + CS_MAC_ROMANIAN_OLD, read_sbcs, write_sbcs, &data_CS_MAC_ROMANIAN_OLD +}; + +static const sbcs_data data_CS_MAC_GREEK_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x00c4, 0x00b9, 0x00b2, 0x00c9, 0x00b3, 0x00d6, 0x00dc, 0x0385, + 0x00e0, 0x00e2, 0x00e4, 0x0384, 0x00a8, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00a3, 0x2122, 0x00ee, 0x00ef, 0x2022, 0x00bd, + 0x2030, 0x00f4, 0x00f6, 0x00a6, 0x00ad, 0x00f9, 0x00fb, 0x00fc, + 0x2020, 0x0393, 0x0394, 0x0398, 0x039b, 0x039e, 0x03a0, 0x00df, + 0x00ae, 0x00a9, 0x03a3, 0x03aa, 0x00a7, 0x2260, 0x00b0, 0x00b7, + 0x0391, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x0392, 0x0395, 0x0396, + 0x0397, 0x0399, 0x039a, 0x039c, 0x03a6, 0x03ab, 0x03a8, 0x03a9, + 0x03ac, 0x039d, 0x00ac, 0x039f, 0x03a1, 0x2248, 0x03a4, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x03a5, 0x03a7, 0x0386, 0x0388, 0x0153, + 0x2013, 0x2015, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x0389, + 0x038a, 0x038c, 0x038e, 0x03ad, 0x03ae, 0x03af, 0x03cc, 0x038f, + 0x03cd, 0x03b1, 0x03b2, 0x03c8, 0x03b4, 0x03b5, 0x03c6, 0x03b3, + 0x03b7, 0x03b9, 0x03be, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03bf, + 0x03c0, 0x03ce, 0x03c1, 0x03c3, 0x03c4, 0x03b8, 0x03c9, 0x03c2, + 0x03c7, 0x03c5, 0x03b6, 0x03ca, 0x03cb, 0x0390, 0x03b0, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0x92, 0xb4, 0x9b, 0xac, 0x8c, 0xa9, 0xc7, + 0xc2, 0x9c, 0xa8, 0xae, 0xb1, 0x82, 0x84, 0xaf, + 0x81, 0xc8, 0x97, 0x80, 0x83, 0x85, 0x86, 0xa7, + 0x88, 0x89, 0x8a, 0x8d, 0x8f, 0x8e, 0x90, 0x91, + 0x94, 0x95, 0x99, 0x9a, 0xd6, 0x9d, 0x9e, 0x9f, + 0xcf, 0x8b, 0x87, 0xcd, 0xce, 0xd7, 0xd8, 0xd9, + 0xda, 0xdf, 0xfd, 0xb0, 0xb5, 0xa1, 0xa2, 0xb6, + 0xb7, 0xb8, 0xa3, 0xb9, 0xba, 0xa4, 0xbb, 0xc1, + 0xa5, 0xc3, 0xa6, 0xc4, 0xaa, 0xc6, 0xcb, 0xbc, + 0xcc, 0xbe, 0xbf, 0xab, 0xbd, 0xc0, 0xdb, 0xdc, + 0xdd, 0xfe, 0xe1, 0xe2, 0xe7, 0xe4, 0xe5, 0xfa, + 0xe8, 0xf5, 0xe9, 0xeb, 0xec, 0xed, 0xee, 0xea, + 0xef, 0xf0, 0xf2, 0xf7, 0xf3, 0xf4, 0xf9, 0xe6, + 0xf8, 0xe3, 0xf6, 0xfb, 0xfc, 0xde, 0xe0, 0xf1, + 0xd0, 0xd1, 0xd4, 0xd5, 0xd2, 0xd3, 0xa0, 0x96, + 0xc9, 0x98, 0x93, 0xc5, 0xad, 0xb2, 0xb3 + }, + 255 +}; +const charset_spec charset_CS_MAC_GREEK_OLD = { + CS_MAC_GREEK_OLD, read_sbcs, write_sbcs, &data_CS_MAC_GREEK_OLD +}; + +static const sbcs_data data_CS_MAC_CYRILLIC_OLD = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x2020, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x0406, + 0x00ae, 0x00a9, 0x2122, 0x0402, 0x0452, 0x2260, 0x0403, 0x0453, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x0456, 0x00b5, 0x2022, 0x0408, + 0x0404, 0x0454, 0x0407, 0x0457, 0x0409, 0x0459, 0x040a, 0x045a, + 0x0458, 0x0405, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x040b, 0x045b, 0x040c, 0x045c, 0x0455, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x201e, + 0x040e, 0x045e, 0x040f, 0x045f, 0x2116, 0x0401, 0x0451, 0x044f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x00a4 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xa2, 0xa3, 0xff, 0xa4, 0xa9, 0xc7, 0xc2, + 0xa8, 0xa1, 0xb1, 0xb5, 0xa6, 0xc8, 0xd6, 0xc4, + 0xdd, 0xab, 0xae, 0xb8, 0xc1, 0xa7, 0xba, 0xb7, + 0xbc, 0xbe, 0xcb, 0xcd, 0xd8, 0xda, 0x80, 0x81, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xe0, 0xe1, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, + 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xdf, 0xde, 0xac, + 0xaf, 0xb9, 0xcf, 0xb4, 0xbb, 0xc0, 0xbd, 0xbf, + 0xcc, 0xce, 0xd9, 0xdb, 0xd0, 0xd1, 0xd4, 0xd5, + 0xd2, 0xd3, 0xd7, 0xa0, 0xa5, 0xc9, 0xdc, 0xaa, + 0xc6, 0xc3, 0xb0, 0xc5, 0xad, 0xb2, 0xb3 + }, + 255 +}; +const charset_spec charset_CS_MAC_CYRILLIC_OLD = { + CS_MAC_CYRILLIC_OLD, read_sbcs, write_sbcs, &data_CS_MAC_CYRILLIC_OLD +}; + +static const sbcs_data data_CS_MAC_UKRAINE = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, + 0x2020, 0x00b0, 0x0490, 0x00a3, 0x00a7, 0x2022, 0x00b6, 0x0406, + 0x00ae, 0x00a9, 0x2122, 0x0402, 0x0452, 0x2260, 0x0403, 0x0453, + 0x221e, 0x00b1, 0x2264, 0x2265, 0x0456, 0x00b5, 0x0491, 0x0408, + 0x0404, 0x0454, 0x0407, 0x0457, 0x0409, 0x0459, 0x040a, 0x045a, + 0x0458, 0x0405, 0x00ac, 0x221a, 0x0192, 0x2248, 0x2206, 0x00ab, + 0x00bb, 0x2026, 0x00a0, 0x040b, 0x045b, 0x040c, 0x045c, 0x0455, + 0x2013, 0x2014, 0x201c, 0x201d, 0x2018, 0x2019, 0x00f7, 0x201e, + 0x040e, 0x045e, 0x040f, 0x045f, 0x2116, 0x0401, 0x0451, 0x044f, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x00a4 + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0xca, 0xa3, 0xff, 0xa4, 0xa9, 0xc7, 0xc2, 0xa8, + 0xa1, 0xb1, 0xb5, 0xa6, 0xc8, 0xd6, 0xc4, 0xdd, + 0xab, 0xae, 0xb8, 0xc1, 0xa7, 0xba, 0xb7, 0xbc, + 0xbe, 0xcb, 0xcd, 0xd8, 0xda, 0x80, 0x81, 0x82, + 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, + 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xe0, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, + 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0xfb, 0xfc, 0xfd, 0xfe, 0xdf, 0xde, 0xac, 0xaf, + 0xb9, 0xcf, 0xb4, 0xbb, 0xc0, 0xbd, 0xbf, 0xcc, + 0xce, 0xd9, 0xdb, 0xa2, 0xb6, 0xd0, 0xd1, 0xd4, + 0xd5, 0xd2, 0xd3, 0xd7, 0xa0, 0xa5, 0xc9, 0xdc, + 0xaa, 0xc6, 0xc3, 0xb0, 0xc5, 0xad, 0xb2, 0xb3 + }, + 256 +}; +const charset_spec charset_CS_MAC_UKRAINE = { + CS_MAC_UKRAINE, read_sbcs, write_sbcs, &data_CS_MAC_UKRAINE +}; + +static const sbcs_data data_CS_MAC_VT100 = { + { + 0x2400, 0x2401, 0x2402, 0x2403, 0x2404, 0x2405, 0x2406, 0x2407, + 0x2408, 0x2409, 0x240a, 0x240b, 0x240c, 0x240d, 0x240e, 0x240f, + 0x2410, 0x2411, 0x2412, 0x2413, 0x2414, 0x2415, 0x2416, 0x2417, + 0x2418, 0x2419, 0x241a, 0x241b, 0x241c, 0x241d, 0x241e, 0x241f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2421, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x00dd, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x00b8, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x00d7, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x00b9, 0x00b2, + 0x00b3, 0x03c0, 0x00a6, 0x00aa, 0x00ba, 0x2592, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x00bd, 0x0192, 0x00bc, 0x00be, 0x00ab, + 0x00bb, 0x2026, ERROR , 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x2518, 0x2510, 0x250c, 0x2514, 0x00f7, 0x2022, + 0x00ff, 0x0178, 0x253c, 0x20ac, 0x00d0, 0x00f0, 0x00fe, 0x00de, + 0x00fd, 0x00b7, 0x23ba, 0x23bb, 0x2500, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + ERROR , 0x00d2, 0x00da, 0x00db, 0x00d9, 0x23bc, 0x23bd, 0x251c, + 0x2524, 0x2534, 0x252c, 0x2502, ERROR , ERROR , ERROR , ERROR + }, + { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0xc1, + 0xa2, 0xa3, 0xb4, 0xba, 0xa4, 0xac, 0xa9, 0xbb, + 0xc7, 0xc2, 0xa8, 0xa1, 0xb1, 0xb7, 0xb8, 0xab, + 0xb5, 0xa6, 0xe1, 0xa5, 0xb6, 0xbc, 0xc8, 0xc5, + 0xc3, 0xc6, 0xc0, 0xcb, 0xe7, 0xe5, 0xcc, 0x80, + 0x81, 0xae, 0x82, 0xe9, 0x83, 0xe6, 0xe8, 0xed, + 0xea, 0xeb, 0xec, 0xdc, 0x84, 0xf1, 0xee, 0xef, + 0xcd, 0x85, 0xb0, 0xaf, 0xf4, 0xf2, 0xf3, 0x86, + 0xa0, 0xdf, 0xa7, 0x88, 0x87, 0x89, 0x8b, 0x8a, + 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, 0x93, + 0x92, 0x94, 0x95, 0xdd, 0x96, 0x98, 0x97, 0x99, + 0x9b, 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, 0x9f, + 0xe0, 0xde, 0xd8, 0xce, 0xcf, 0xd9, 0xc4, 0xb9, + 0xd0, 0xd1, 0xd7, 0xc9, 0xdb, 0xaa, 0xad, 0xb2, + 0xb3, 0xe2, 0xe3, 0xf5, 0xf6, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x7f, 0xe4, 0xfb, + 0xd4, 0xd3, 0xd5, 0xd2, 0xf7, 0xf8, 0xfa, 0xf9, + 0xda, 0xbd + }, + 250 +}; +const charset_spec charset_CS_MAC_VT100 = { + CS_MAC_VT100, read_sbcs, write_sbcs, &data_CS_MAC_VT100 +}; + +static const sbcs_data data_CS_MAC_VT100_OLD = { + { + 0x2400, 0x2401, 0x2402, 0x2403, 0x2404, 0x2405, 0x2406, 0x2407, + 0x2408, 0x2409, 0x240a, 0x240b, 0x240c, 0x240d, 0x240e, 0x240f, + 0x2410, 0x2411, 0x2412, 0x2413, 0x2414, 0x2415, 0x2416, 0x2417, + 0x2418, 0x2419, 0x241a, 0x241b, 0x241c, 0x241d, 0x241e, 0x241f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2421, + 0x00c4, 0x00c5, 0x00c7, 0x00c9, 0x00d1, 0x00d6, 0x00dc, 0x00e1, + 0x00e0, 0x00e2, 0x00e4, 0x00e3, 0x00e5, 0x00e7, 0x00e9, 0x00e8, + 0x00ea, 0x00eb, 0x00ed, 0x00ec, 0x00ee, 0x00ef, 0x00f1, 0x00f3, + 0x00f2, 0x00f4, 0x00f6, 0x00f5, 0x00fa, 0x00f9, 0x00fb, 0x00fc, + 0x00dd, 0x00b0, 0x00a2, 0x00a3, 0x00a7, 0x00b8, 0x00b6, 0x00df, + 0x00ae, 0x00a9, 0x2122, 0x00b4, 0x00a8, 0x2260, 0x00c6, 0x00d8, + 0x00d7, 0x00b1, 0x2264, 0x2265, 0x00a5, 0x00b5, 0x00b9, 0x00b2, + 0x00b3, 0x03c0, 0x00a6, 0x00aa, 0x00ba, 0x2592, 0x00e6, 0x00f8, + 0x00bf, 0x00a1, 0x00ac, 0x00bd, 0x0192, 0x00bc, 0x00be, 0x00ab, + 0x00bb, 0x2026, ERROR , 0x00c0, 0x00c3, 0x00d5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x2518, 0x2510, 0x250c, 0x2514, 0x00f7, 0x2022, + 0x00ff, 0x0178, 0x253c, 0x00a4, 0x00d0, 0x00f0, 0x00fe, 0x00de, + 0x00fd, 0x00b7, 0x23ba, 0x23bb, 0x2500, 0x00c2, 0x00ca, 0x00c1, + 0x00cb, 0x00c8, 0x00cd, 0x00ce, 0x00cf, 0x00cc, 0x00d3, 0x00d4, + ERROR , 0x00d2, 0x00da, 0x00db, 0x00d9, 0x23bc, 0x23bd, 0x251c, + 0x2524, 0x2534, 0x252c, 0x2502, ERROR , ERROR , ERROR , ERROR + }, + { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0xc1, + 0xa2, 0xa3, 0xdb, 0xb4, 0xba, 0xa4, 0xac, 0xa9, + 0xbb, 0xc7, 0xc2, 0xa8, 0xa1, 0xb1, 0xb7, 0xb8, + 0xab, 0xb5, 0xa6, 0xe1, 0xa5, 0xb6, 0xbc, 0xc8, + 0xc5, 0xc3, 0xc6, 0xc0, 0xcb, 0xe7, 0xe5, 0xcc, + 0x80, 0x81, 0xae, 0x82, 0xe9, 0x83, 0xe6, 0xe8, + 0xed, 0xea, 0xeb, 0xec, 0xdc, 0x84, 0xf1, 0xee, + 0xef, 0xcd, 0x85, 0xb0, 0xaf, 0xf4, 0xf2, 0xf3, + 0x86, 0xa0, 0xdf, 0xa7, 0x88, 0x87, 0x89, 0x8b, + 0x8a, 0x8c, 0xbe, 0x8d, 0x8f, 0x8e, 0x90, 0x91, + 0x93, 0x92, 0x94, 0x95, 0xdd, 0x96, 0x98, 0x97, + 0x99, 0x9b, 0x9a, 0xd6, 0xbf, 0x9d, 0x9c, 0x9e, + 0x9f, 0xe0, 0xde, 0xd8, 0xce, 0xcf, 0xd9, 0xc4, + 0xb9, 0xd0, 0xd1, 0xd7, 0xc9, 0xaa, 0xad, 0xb2, + 0xb3, 0xe2, 0xe3, 0xf5, 0xf6, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x7f, 0xe4, 0xfb, + 0xd4, 0xd3, 0xd5, 0xd2, 0xf7, 0xf8, 0xfa, 0xf9, + 0xda, 0xbd + }, + 250 +}; +const charset_spec charset_CS_MAC_VT100_OLD = { + CS_MAC_VT100_OLD, read_sbcs, write_sbcs, &data_CS_MAC_VT100_OLD +}; + +static const sbcs_data data_CS_VISCII = { + { + 0x0000, 0x0001, 0x1eb2, 0x0003, 0x0004, 0x1eb4, 0x1eaa, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x1ef6, 0x0015, 0x0016, 0x0017, + 0x0018, 0x1ef8, 0x001a, 0x001b, 0x001c, 0x001d, 0x1ef4, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x1ea0, 0x1eae, 0x1eb0, 0x1eb6, 0x1ea4, 0x1ea6, 0x1ea8, 0x1eac, + 0x1ebc, 0x1eb8, 0x1ebe, 0x1ec0, 0x1ec2, 0x1ec4, 0x1ec6, 0x1ed0, + 0x1ed2, 0x1ed4, 0x1ed6, 0x1ed8, 0x1ee2, 0x1eda, 0x1edc, 0x1ede, + 0x1eca, 0x1ece, 0x1ecc, 0x1ec8, 0x1ee6, 0x0168, 0x1ee4, 0x1ef2, + 0x00d5, 0x1eaf, 0x1eb1, 0x1eb7, 0x1ea5, 0x1ea7, 0x1ea8, 0x1ead, + 0x1ebd, 0x1eb9, 0x1ebf, 0x1ec1, 0x1ec3, 0x1ec5, 0x1ec7, 0x1ed1, + 0x1ed3, 0x1ed5, 0x1ed7, 0x1ee0, 0x01a0, 0x1ed9, 0x1edd, 0x1edf, + 0x1ecb, 0x1ef0, 0x1ee8, 0x1eea, 0x1eec, 0x01a1, 0x1edb, 0x01af, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x1ea2, 0x0102, 0x1eb3, 0x1eb5, + 0x00c8, 0x00c9, 0x00ca, 0x1eba, 0x00cc, 0x00cd, 0x0128, 0x1ef3, + 0x0110, 0x1ee9, 0x00d2, 0x00d3, 0x00d4, 0x1ea1, 0x1ef7, 0x1eeb, + 0x1eed, 0x00d9, 0x00da, 0x1ef9, 0x1ef5, 0x00dd, 0x1ee1, 0x01b0, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x1ea3, 0x0103, 0x1eef, 0x1eab, + 0x00e8, 0x00e9, 0x00ea, 0x1ebb, 0x00ec, 0x00ed, 0x0129, 0x1ec9, + 0x0111, 0x1ef1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x1ecf, 0x1ecd, + 0x1ee5, 0x00f9, 0x00fa, 0x0169, 0x1ee7, 0x00fd, 0x1ee3, 0x1eee + }, + { + 0x00, 0x01, 0x03, 0x04, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x1c, + 0x1d, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, + 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, + 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, + 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, + 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, + 0x7e, 0x7f, 0xc0, 0xc1, 0xc2, 0xc3, 0xc8, 0xc9, + 0xca, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xa0, 0xd9, + 0xda, 0xdd, 0xe0, 0xe1, 0xe2, 0xe3, 0xe8, 0xe9, + 0xea, 0xec, 0xed, 0xf2, 0xf3, 0xf4, 0xf5, 0xf9, + 0xfa, 0xfd, 0xc5, 0xe5, 0xd0, 0xf0, 0xce, 0xee, + 0x9d, 0xfb, 0xb4, 0xbd, 0xbf, 0xdf, 0x80, 0xd5, + 0xc4, 0xe4, 0x84, 0xa4, 0x85, 0xa5, 0x86, 0x06, + 0xe7, 0x87, 0xa7, 0x81, 0xa1, 0x82, 0xa2, 0x02, + 0xc6, 0x05, 0xc7, 0x83, 0xa3, 0x89, 0xa9, 0xcb, + 0xeb, 0x88, 0xa8, 0x8a, 0xaa, 0x8b, 0xab, 0x8c, + 0xac, 0x8d, 0xad, 0x8e, 0xae, 0x9b, 0xef, 0x98, + 0xb8, 0x9a, 0xf7, 0x99, 0xf6, 0x8f, 0xaf, 0x90, + 0xb0, 0x91, 0xb1, 0x92, 0xb2, 0x93, 0xb5, 0x95, + 0xbe, 0x96, 0xb6, 0x97, 0xb7, 0xb3, 0xde, 0x94, + 0xfe, 0x9e, 0xf8, 0x9c, 0xfc, 0xba, 0xd1, 0xbb, + 0xd7, 0xbc, 0xd8, 0xff, 0xe6, 0xb9, 0xf1, 0x9f, + 0xcf, 0x1e, 0xdc, 0x14, 0xd6, 0x19, 0xdb + }, + 255 +}; +const charset_spec charset_CS_VISCII = { + CS_VISCII, read_sbcs, write_sbcs, &data_CS_VISCII +}; + +static const sbcs_data data_CS_HP_ROMAN8 = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + 0x00a0, 0x00c0, 0x00c2, 0x00c8, 0x00ca, 0x00cb, 0x00ce, 0x00cf, + 0x00b4, 0x02cb, 0x02c6, 0x00a8, 0x02dc, 0x00d9, 0x00db, 0x20a4, + 0x00af, 0x00dd, 0x00fd, 0x00b0, 0x00c7, 0x00e7, 0x00d1, 0x00f1, + 0x00a1, 0x00bf, 0x00a4, 0x00a3, 0x00a5, 0x00a7, 0x0192, 0x00a2, + 0x00e2, 0x00ea, 0x00f4, 0x00fb, 0x00e1, 0x00e9, 0x00f3, 0x00fa, + 0x00e0, 0x00e8, 0x00f2, 0x00f9, 0x00e4, 0x00eb, 0x00f6, 0x00fc, + 0x00c5, 0x00ee, 0x00d8, 0x00c6, 0x00e5, 0x00ed, 0x00f8, 0x00e6, + 0x00c4, 0x00ec, 0x00d6, 0x00dc, 0x00c9, 0x00ef, 0x00df, 0x00d4, + 0x00c1, 0x00c3, 0x00e3, 0x00d0, 0x00f0, 0x00cd, 0x00cc, 0x00d3, + 0x00d2, 0x00d5, 0x00f5, 0x0160, 0x0161, 0x00da, 0x0178, 0x00ff, + 0x00de, 0x00fe, 0x00b7, 0x00b5, 0x00b6, 0x00be, 0x2014, 0x00bc, + 0x00bd, 0x00aa, 0x00ba, 0x00ab, 0x25a0, 0x00bb, 0x00b1, ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xb8, 0xbf, 0xbb, 0xba, 0xbc, 0xbd, 0xab, + 0xf9, 0xfb, 0xb0, 0xb3, 0xfe, 0xa8, 0xf3, 0xf4, + 0xf2, 0xfa, 0xfd, 0xf7, 0xf8, 0xf5, 0xb9, 0xa1, + 0xe0, 0xa2, 0xe1, 0xd8, 0xd0, 0xd3, 0xb4, 0xa3, + 0xdc, 0xa4, 0xa5, 0xe6, 0xe5, 0xa6, 0xa7, 0xe3, + 0xb6, 0xe8, 0xe7, 0xdf, 0xe9, 0xda, 0xd2, 0xad, + 0xed, 0xae, 0xdb, 0xb1, 0xf0, 0xde, 0xc8, 0xc4, + 0xc0, 0xe2, 0xcc, 0xd4, 0xd7, 0xb5, 0xc9, 0xc5, + 0xc1, 0xcd, 0xd9, 0xd5, 0xd1, 0xdd, 0xe4, 0xb7, + 0xca, 0xc6, 0xc2, 0xea, 0xce, 0xd6, 0xcb, 0xc7, + 0xc3, 0xcf, 0xb2, 0xf1, 0xef, 0xeb, 0xec, 0xee, + 0xbe, 0xaa, 0xa9, 0xac, 0xf6, 0xaf, 0xfc + }, + 255 +}; +const charset_spec charset_CS_HP_ROMAN8 = { + CS_HP_ROMAN8, read_sbcs, write_sbcs, &data_CS_HP_ROMAN8 +}; + +static const sbcs_data data_CS_DEC_MCS = { + { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, + 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, + ERROR , 0x00a1, 0x00a2, 0x00a3, ERROR , 0x00a5, ERROR , 0x00a7, + 0x00a4, 0x00a9, 0x00aa, 0x00ab, ERROR , ERROR , ERROR , ERROR , + 0x00b0, 0x00b1, 0x00b2, 0x00b3, ERROR , 0x00b5, 0x00b6, 0x00b7, + ERROR , 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, ERROR , 0x00bf, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + ERROR , 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x0152, + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0178, ERROR , 0x00df, + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, + ERROR , 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x0153, + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00ff, ERROR , ERROR + }, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa1, 0xa2, 0xa3, 0xa8, 0xa5, 0xa7, 0xa9, 0xaa, + 0xab, 0xb0, 0xb1, 0xb2, 0xb3, 0xb5, 0xb6, 0xb7, + 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbf, 0xc0, 0xc1, + 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd1, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd8, 0xd9, 0xda, 0xdb, + 0xdc, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, + 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, + 0xee, 0xef, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xd7, 0xf7, + 0xdd + }, + 241 +}; +const charset_spec charset_CS_DEC_MCS = { + CS_DEC_MCS, read_sbcs, write_sbcs, &data_CS_DEC_MCS +}; + +#else /* ENUM_CHARSETS */ + +ENUM_CHARSET(CS_ISO8859_1) +ENUM_CHARSET(CS_ISO8859_2) +ENUM_CHARSET(CS_ISO8859_3) +ENUM_CHARSET(CS_ISO8859_4) +ENUM_CHARSET(CS_ISO8859_5) +ENUM_CHARSET(CS_ISO8859_6) +ENUM_CHARSET(CS_ISO8859_7) +ENUM_CHARSET(CS_ISO8859_8) +ENUM_CHARSET(CS_ISO8859_9) +ENUM_CHARSET(CS_ISO8859_10) +ENUM_CHARSET(CS_ISO8859_11) +ENUM_CHARSET(CS_ISO8859_13) +ENUM_CHARSET(CS_ISO8859_14) +ENUM_CHARSET(CS_ISO8859_15) +ENUM_CHARSET(CS_ISO8859_16) +ENUM_CHARSET(CS_ISO8859_1_X11) +ENUM_CHARSET(CS_CP437) +ENUM_CHARSET(CS_CP850) +ENUM_CHARSET(CS_CP866) +ENUM_CHARSET(CS_CP852) +ENUM_CHARSET(CS_CP1250) +ENUM_CHARSET(CS_CP1251) +ENUM_CHARSET(CS_CP1252) +ENUM_CHARSET(CS_CP1253) +ENUM_CHARSET(CS_CP1254) +ENUM_CHARSET(CS_CP1255) +ENUM_CHARSET(CS_CP1256) +ENUM_CHARSET(CS_CP1257) +ENUM_CHARSET(CS_CP1258) +ENUM_CHARSET(CS_KOI8_R) +ENUM_CHARSET(CS_KOI8_U) +ENUM_CHARSET(CS_MAC_ROMAN) +ENUM_CHARSET(CS_MAC_TURKISH) +ENUM_CHARSET(CS_MAC_CROATIAN) +ENUM_CHARSET(CS_MAC_ICELAND) +ENUM_CHARSET(CS_MAC_ROMANIAN) +ENUM_CHARSET(CS_MAC_GREEK) +ENUM_CHARSET(CS_MAC_CYRILLIC) +ENUM_CHARSET(CS_MAC_THAI) +ENUM_CHARSET(CS_MAC_CENTEURO) +ENUM_CHARSET(CS_MAC_SYMBOL) +ENUM_CHARSET(CS_MAC_DINGBATS) +ENUM_CHARSET(CS_MAC_ROMAN_OLD) +ENUM_CHARSET(CS_MAC_CROATIAN_OLD) +ENUM_CHARSET(CS_MAC_ICELAND_OLD) +ENUM_CHARSET(CS_MAC_ROMANIAN_OLD) +ENUM_CHARSET(CS_MAC_GREEK_OLD) +ENUM_CHARSET(CS_MAC_CYRILLIC_OLD) +ENUM_CHARSET(CS_MAC_UKRAINE) +ENUM_CHARSET(CS_MAC_VT100) +ENUM_CHARSET(CS_MAC_VT100_OLD) +ENUM_CHARSET(CS_VISCII) +ENUM_CHARSET(CS_HP_ROMAN8) +ENUM_CHARSET(CS_DEC_MCS) + +#endif /* ENUM_CHARSETS */ diff --git a/0.77_My_PuTTY/charset/sbcsgen.pl b/0.77_My_PuTTY/charset/sbcsgen.pl new file mode 100644 index 0000000..335c8e0 --- /dev/null +++ b/0.77_My_PuTTY/charset/sbcsgen.pl @@ -0,0 +1,110 @@ +#!/usr/bin/env perl -w + +# This script generates sbcsdat.c (the data for all the SBCSes) from its +# source form sbcs.dat. + +$infile = "sbcs.dat"; +$outfile = "sbcsdat.c"; + +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; + 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/0.77_My_PuTTY/charset/slookup.c b/0.77_My_PuTTY/charset/slookup.c new file mode 100644 index 0000000..7a4d7c2 --- /dev/null +++ b/0.77_My_PuTTY/charset/slookup.c @@ -0,0 +1,29 @@ +/* + * slookup.c - static lookup of character sets. + */ + +#include "charset.h" +#include "internal.h" + +#define ENUM_CHARSET(x) extern charset_spec const charset_##x; +#include "enum.c" +#undef ENUM_CHARSET + +static charset_spec const *const cs_table[] = { + +#define ENUM_CHARSET(x) &charset_##x, +#include "enum.c" +#undef ENUM_CHARSET + +}; + +charset_spec const *charset_find_spec(int charset) +{ + int i; + + for (i = 0; i < (int)lenof(cs_table); i++) + if (cs_table[i]->charset == charset) + return cs_table[i]; + + return NULL; +} diff --git a/0.77_My_PuTTY/charset/toucs.c b/0.77_My_PuTTY/charset/toucs.c new file mode 100644 index 0000000..1df788a --- /dev/null +++ b/0.77_My_PuTTY/charset/toucs.c @@ -0,0 +1,90 @@ +/* + * toucs.c - convert charsets to Unicode. + */ + +#include "charset.h" +#include "internal.h" + +struct unicode_emit_param { + wchar_t *output; + int outlen; + const wchar_t *errstr; + int errlen; + int stopped; +}; + +static void unicode_emit(void *ctx, long int output) +{ + struct unicode_emit_param *param = (struct unicode_emit_param *)ctx; + wchar_t outval; + wchar_t const *p; + int outlen; + + if (output == ERROR) { + if (param->errstr) { + p = param->errstr; + outlen = param->errlen; + } else { + outval = 0xFFFD; /* U+FFFD REPLACEMENT CHARACTER */ + p = &outval; + outlen = 1; + } + } else { + outval = output; + p = &outval; + outlen = 1; + } + + if (param->outlen >= outlen) { + while (outlen > 0) { + *param->output++ = *p++; + param->outlen--; + outlen--; + } + } else { + param->stopped = 1; + } +} + +int charset_to_unicode(const char **input, int *inlen, + wchar_t *output, int outlen, + int charset, charset_state *state, + const wchar_t *errstr, int errlen) +{ + charset_spec const *spec = charset_find_spec(charset); + charset_state localstate; + struct unicode_emit_param param; + + param.output = output; + param.outlen = outlen; + param.errstr = errstr; + param.errlen = errlen; + param.stopped = 0; + + if (!state) { + localstate.s0 = 0; + } else { + localstate = *state; /* structure copy */ + } + + while (*inlen > 0) { + int lenbefore = param.output - output; + spec->read(spec, (unsigned char)**input, &localstate, + unicode_emit, ¶m); + if (param.stopped) { + /* + * The emit function has _tried_ to output some + * characters, but ran up against the end of the + * buffer. Leave immediately, and return what happened + * _before_ attempting to process this character. + */ + return lenbefore; + } + if (state) + *state = localstate; /* structure copy */ + (*input)++; + (*inlen)--; + } + + return param.output - output; +} diff --git a/0.77_My_PuTTY/charset/utf8.c b/0.77_My_PuTTY/charset/utf8.c new file mode 100644 index 0000000..13c5baa --- /dev/null +++ b/0.77_My_PuTTY/charset/utf8.c @@ -0,0 +1,877 @@ +/* + * 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/0.77_My_PuTTY/charset/xenc.c b/0.77_My_PuTTY/charset/xenc.c new file mode 100644 index 0000000..444af70 --- /dev/null +++ b/0.77_My_PuTTY/charset/xenc.c @@ -0,0 +1,94 @@ +/* + * xenc.c - translate our internal character set codes to and from + * X11 character encoding names. + * + */ + +#include +#include "charset.h" +#include "internal.h" + +static const struct { + const char *name; + int charset; +} xencs[] = { + /* + * Officially registered encoding names. This list is derived + * from the font encodings section of + * + * http://ftp.x.org/pub/DOCS/registry + * + * Where multiple encoding names map to the same encoding id + * (such as iso8859-15 and fcd8859-15), the first is considered + * canonical and will be returned when translating the id to a + * string. + */ + { "iso8859-1", CS_ISO8859_1 }, + { "iso8859-2", CS_ISO8859_2 }, + { "iso8859-3", CS_ISO8859_3 }, + { "iso8859-4", CS_ISO8859_4 }, + { "iso8859-5", CS_ISO8859_5 }, + { "iso8859-6", CS_ISO8859_6 }, + { "iso8859-7", CS_ISO8859_7 }, + { "iso8859-8", CS_ISO8859_8 }, + { "iso8859-9", CS_ISO8859_9 }, + { "iso8859-10", CS_ISO8859_10 }, + { "iso8859-13", CS_ISO8859_13 }, + { "iso8859-14", CS_ISO8859_14 }, + { "iso8859-15", CS_ISO8859_15 }, + { "fcd8859-15", CS_ISO8859_15 }, + { "hp-roman8", CS_HP_ROMAN8 }, + { "koi8-r", CS_KOI8_R }, + /* + * Unofficial encoding names found in the wild. + */ + { "iso8859-16", CS_ISO8859_16 }, + { "koi8-u", CS_KOI8_U }, + { "ibm-cp437", CS_CP437 }, + { "ibm-cp850", CS_CP850 }, + { "ibm-cp852", CS_CP852 }, + { "ibm-cp866", CS_CP866 }, + { "microsoft-cp1250", CS_CP1250 }, + { "microsoft-cp1251", CS_CP1251 }, + { "microsoft-cp1252", CS_CP1252 }, + { "microsoft-cp1253", CS_CP1253 }, + { "microsoft-cp1254", CS_CP1254 }, + { "microsoft-cp1255", CS_CP1255 }, + { "microsoft-cp1256", CS_CP1256 }, + { "microsoft-cp1257", CS_CP1257 }, + { "microsoft-cp1258", CS_CP1258 }, + { "mac-roman", CS_MAC_ROMAN }, + { "viscii1.1-1", CS_VISCII }, + { "viscii1-1", CS_VISCII }, +}; + +const char *charset_to_xenc(int charset) +{ + int i; + + for (i = 0; i < (int)lenof(xencs); i++) + if (charset == xencs[i].charset) + return xencs[i].name; + + return NULL; /* not found */ +} + +int charset_from_xenc(const char *name) +{ + int i; + + for (i = 0; i < (int)lenof(xencs); i++) { + const char *p, *q; + p = name; + q = xencs[i].name; + while (*p || *q) { + if (tolower((unsigned char)*p) != tolower((unsigned char)*q)) + break; + p++; q++; + } + if (!*p && !*q) + return xencs[i].charset; + } + + return CS_NONE; /* not found */ +} diff --git a/0.77_My_PuTTY/clicons.c b/0.77_My_PuTTY/clicons.c new file mode 100644 index 0000000..cfcc16c --- /dev/null +++ b/0.77_My_PuTTY/clicons.c @@ -0,0 +1,14 @@ +/* + * 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/0.77_My_PuTTY/cmdgen.c b/0.77_My_PuTTY/cmdgen.c new file mode 100644 index 0000000..570cf26 --- /dev/null +++ b/0.77_My_PuTTY/cmdgen.c @@ -0,0 +1,1362 @@ +/* + * 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" + " 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" + " --dump equivalent to `-O text'\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 + +/* 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 } 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; + 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, "-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, "-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 { + 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 { + 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) { + 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); + } + + /* ------------------------------------------------------------------ + * 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 dss_key *dsskey = snew(struct dss_key); + dsa_generate(dsskey, bits, pgc, &cmdgen_progress); + ssh2key = snew(ssh2_userkey); + ssh2key->key = &dsskey->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(); + int ret; + p->to_server = false; + p->from_server = false; + p->name = dupstr("SSH key passphrase"); + add_prompt(p, dupstr("Enter passphrase to load key: "), false); + ret = console_get_userpass_input(p); + assert(ret >= 0); + if (!ret) { + free_prompts(p); + perror("puttygen: unable to read passphrase"); + 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); + } + } + + /* + * 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(); + int ret; + + 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); + ret = console_get_userpass_input(p); + assert(ret >= 0); + if (!ret) { + free_prompts(p); + perror("puttygen: unable to read new passphrase"); + 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); + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(ssh2blob)); + ptrlen algname = get_string(src); + 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++) { + if (kc->components[i].is_mp_int) { + char *hex = mp_get_hex(kc->components[i].mp); + fprintf(fp, "%s=0x%s\n", kc->components[i].name, hex); + smemclr(hex, strlen(hex)); + sfree(hex); + } else { + fprintf(fp, "%s=\"", kc->components[i].name); + write_c_string_literal(fp, ptrlen_from_asciz( + kc->components[i].text)); + fputs("\"\n", fp); + } + } + + if (outfile) + fclose(fp); + key_components_free(kc); + 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/0.77_My_PuTTY/cmdline.c b/0.77_My_PuTTY/cmdline.c new file mode 100644 index 0000000..b351219 --- /dev/null +++ b/0.77_My_PuTTY/cmdline.c @@ -0,0 +1,1076 @@ +/* + * 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.) + */ + +#ifdef MOD_PERSO +#include "kitty_crypt.h" +#include "kitty.h" +int decode64(char *buffer) ; +void SetAutoStoreSSHKey( void ) ; +void load_open_settings_forced(char *filename, Conf *conf) ; +void SetPasswordInConfig( const char * password ) ; +int GetCryptSaltFlag() ; +extern char CurrentFolder[1024] ; +// Manage connect string as: user:pass@@@hostname:port/cmd +void ManageConnectString( Conf *cf, char * hostname ) { + int i, j ; + char *p, *us, *pw, *hn, *po, *cm ; + us = (char*)malloc(strlen(hostname)+1); strcpy(us,""); + pw = (char*)malloc(strlen(hostname)+1); strcpy(pw,""); + hn = (char*)malloc(strlen(hostname)+1); strcpy(hn,hostname); + po = (char*)malloc(strlen(hostname)+1); strcpy(po,""); + cm = (char*)malloc(strlen(hostname)+1); strcpy(cm,""); + for( i=0 ; i0 ) { + if( (p = strstr( us, ":" )) != NULL ) { + strcpy( pw, p+1 ) ; + p[0] = '\0' ; + while( (p = strstr( pw, "@@" )) != NULL ) { // Manage @ in password ( @ => double @@) + do { p[0] = p[1] ; p++ ; } while( p[0] != '\0' ) ; + } + } + if( strlen(pw)>0 ) { + SetPasswordInConfig( pw ) ; + } + sprintf( hostname, "%s@%s", us, hn ) ; + } else { + strcpy( hostname, hn ) ; + } + if( strlen(po)>0 ) { + conf_set_int( cf, CONF_port, atoi(po) ) ; + } + if( strlen(cm)>0 ) { + if( cm[0] == '#' ) { + decryptstring( GetCryptSaltFlag(), (char*)cm+1, MASTER_PASSWORD ) ; + conf_set_str( cf, CONF_autocommand, cm+1 ) ; + } else { + char *s = (char*)malloc( strlen(cm)+1 ) ; + strcpy( s, cm ) ; + int i = decode64(s) ; + s[i] = '\0' ; + conf_set_str( cf,CONF_autocommand,s ) ; + free(s) ; + } + } + free(cm);free(po);free(hn);free(pw);free(us); +} +#endif +#ifdef MOD_ADB +int GetADBFlag(void) ; +#endif + +#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 + * -1 return means that we aren't capable of processing the prompt and + * someone else should do it. + */ +int cmdline_get_passwd_input(prompts_t *p) +{ + static bool tried_once = false; + + /* + * We only handle prompts which don't echo (which we assume to be + * passwords), and (currently) we only cope with a password prompt + * that comes in a prompt-set on its own. + */ + if (!cmdline_password || p->n_prompts != 1 || p->prompts[0]->echo) { + return -1; + } + + /* + * If we've tried once, return utter failure (no more passwords left + * to try). + */ + if (tried_once) + return 0; + + prompt_set_result(p->prompts[0], cmdline_password); + smemclr(cmdline_password, strlen(cmdline_password)); + sfree(cmdline_password); + cmdline_password = NULL; + tried_once = true; + return 1; +} + +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); + } +#ifdef MOD_PERSO + } else if (!strncmp(p, "ssh:", 4)) { + char *pst = (char*)malloc(strlen(p)+1) ; strcpy(pst,p); + char *q = (char*)pst ; + /* + * If the hostname starts with "ssh:", + * set the protocol to SSH and process + * the string as a SSH URL + */ + q += 4; + if (q[0] == '/' && q[1] == '/') + q += 2; + //while (*q && *q != ':' && *q != '/') q++ ; + conf_set_int( conf, CONF_protocol, PROT_SSH); + conf_set_int( conf, CONF_port, 22); + ManageConnectString( conf, q ) ; + + conf_set_str( conf, CONF_host, q ) ; + seen_hostname_argument = true ; + free(pst); + } else if (!strncmp(p, "putty:", 4)) { + char * q = (char*)p ; + int ret = 0; + q += 6; + if (q[0] == '/' && q[1] == '/') q += 2; + if (q[strlen(q) - 1] == '/') q[strlen(q) - 1] = '\0'; + p = q; + ret = cmdline_process_param("-load", (char*)p, 1, conf); + assert(ret == 2); +#endif + } 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; +#ifdef MOD_PERSO + // Manage connect string user:pass@hostname + ManageConnectString( conf, hostname ) ; +#endif + 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; + } + } + + if (!strcmp(p, "-load")) { + RETURN(2); + /* This parameter must be processed immediately rather than being + * saved. */ +#if MOD_PERSO + if( IniFileFlag == SAVEMODE_REG ) { + do_defaults(value, conf); + loaded_session = true; + } else { + char *pst = strstr( value, "/" ) ; + if( pst==NULL ) { + do_defaults(value, conf); + loaded_session = true; + } else { + char *name = (char*)malloc( strlen(value)+1 ) ; + strcpy(name,value) ; + pst = strstr( name, "/" ) ; + pst[0]='\0'; + conf_set_str(conf,CONF_folder,name) ; + SetSessPath( name ) ; + strcpy( CurrentFolder, name ) ; + do_defaults(pst+1, conf); + loaded_session = true; + free(value) ; + } + } +#else + do_defaults(value, conf); + loaded_session = true; +#endif + return 2; + } + +#ifdef MOD_PERSO + if (!strcmp(p, "-kload")) { + RETURN(2); + /* This parameter must be processed immediately rather than being + * saved. */ + load_open_settings_forced( value, conf ) ; + loaded_session = true; + //cmdline_session_name = dupstr(value); + return 2; + } + if (!strcmp(p, "-knock")) { + RETURN(2); + conf_set_str( conf, CONF_portknockingoptions, value ) ; + } + if ( !strcmp(p, "-auto_store_sshkey") || !strcmp(p, "-auto-store-sshkey") ) { + RETURN(1); + SetAutoStoreSSHKey(); + } +#endif +#ifdef MOD_ADB + if (!strcmp(p, "-adb") && GetADBFlag()) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(0); + //default_protocol = PROT_ADB; + conf_set_int(conf, CONF_protocol, PROT_ADB); + } +#endif + 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 { + cmdline_password = dupstr(value); + /* Assuming that `value' is directly from argv, make a good faith + * attempt to trample it, to stop it showing up in `ps' output + * on Unix-like systems. Not guaranteed, of course. */ + smemclr(value, strlen(value)); + } + } + + if (!strcmp(p, "-agent") || !strcmp(p, "-pagent") || + !strcmp(p, "-pageant")) { + RETURN(1); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + conf_set_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, "-4") || !strcmp(p, "-ipv4")) { + RETURN(1); + SAVEABLE(1); + conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV4); + } + if (!strcmp(p, "-6") || !strcmp(p, "-ipv6")) { + RETURN(1); + SAVEABLE(1); + conf_set_int(conf, CONF_addressfamily, ADDRTYPE_IPV6); + } + if (!strcmp(p, "-sercfg")) { + char* nextitem; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER | TOOLTYPE_NONNETWORK); + SAVEABLE(1); + if (conf_get_int(conf, CONF_protocol) != PROT_SERIAL) + cmdline_error("the -sercfg option can only be used with the " + "serial protocol"); + /* Value[0] contains one or more , separated values, like 19200,8,n,1,X */ + nextitem = value; + while (nextitem[0] != '\0') { + int length, skip; + char *end = strchr(nextitem, ','); + if (!end) { + length = strlen(nextitem); + skip = 0; + } else { + length = end - nextitem; + nextitem[length] = '\0'; + skip = 1; + } + if (length == 1) { + switch (*nextitem) { + case '1': + case '2': + conf_set_int(conf, CONF_serstopbits, 2 * (*nextitem-'0')); + break; + + case '5': + case '6': + case '7': + case '8': + case '9': + conf_set_int(conf, CONF_serdatabits, *nextitem-'0'); + break; + + case 'n': + conf_set_int(conf, CONF_serparity, SER_PAR_NONE); + break; + case 'o': + conf_set_int(conf, CONF_serparity, SER_PAR_ODD); + break; + case 'e': + conf_set_int(conf, CONF_serparity, SER_PAR_EVEN); + break; + case 'm': + conf_set_int(conf, CONF_serparity, SER_PAR_MARK); + break; + case 's': + conf_set_int(conf, CONF_serparity, SER_PAR_SPACE); + break; + + case 'N': + conf_set_int(conf, CONF_serflow, SER_FLOW_NONE); + break; + case 'X': + conf_set_int(conf, CONF_serflow, SER_FLOW_XONXOFF); + break; + case 'R': + conf_set_int(conf, CONF_serflow, SER_FLOW_RTSCTS); + break; + case 'D': + conf_set_int(conf, CONF_serflow, SER_FLOW_DSRDTR); + break; + + default: + cmdline_error("Unrecognised suboption \"-sercfg %c\"", + *nextitem); + } + } else if (length == 3 && !strncmp(nextitem,"1.5",3)) { + /* Messy special case */ + conf_set_int(conf, CONF_serstopbits, 3); + } else { + int serspeed = atoi(nextitem); + if (serspeed != 0) { + conf_set_int(conf, CONF_serspeed, serspeed); + } else { + cmdline_error("Unrecognised suboption \"-sercfg %s\"", + nextitem); + } + } + nextitem += length + skip; + } + } + + if (!strcmp(p, "-sessionlog")) { + Filename *fn; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_FILETRANSFER); + /* but available even in TOOLTYPE_NONNETWORK, cf pterm "-log" */ + SAVEABLE(0); + fn = filename_from_str(value); + conf_set_filename(conf, CONF_logfilename, fn); + conf_set_int(conf, CONF_logtype, LGTYP_DEBUG); + filename_free(fn); + } + + if (!strcmp(p, "-sshlog") || + !strcmp(p, "-sshrawlog")) { + Filename *fn; + RETURN(2); + UNAVAILABLE_IN(TOOLTYPE_NONNETWORK); + SAVEABLE(0); + fn = filename_from_str(value); + conf_set_filename(conf, CONF_logfilename, fn); + conf_set_int(conf, CONF_logtype, + !strcmp(p, "-sshlog") ? LGTYP_PACKETS : + /* !strcmp(p, "-sshrawlog") ? */ LGTYP_SSHRAW); + filename_free(fn); + } + + 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/0.77_My_PuTTY/conf.c b/0.77_My_PuTTY/conf.c new file mode 100644 index 0000000..c27187c --- /dev/null +++ b/0.77_My_PuTTY/conf.c @@ -0,0 +1,614 @@ +/* + * conf.c: implementation of the internal storage format used for + * the configuration of a PuTTY session. + */ + +#include +#include +#include + +#include "tree234.h" +#include "putty.h" + +/* + * Enumeration of types used in keys and values. + */ +typedef enum { + TYPE_NONE, TYPE_BOOL, TYPE_INT, TYPE_STR, TYPE_FILENAME, TYPE_FONT +} Type; + +/* + * Arrays which allow us to look up the subkey and value types for a + * given primary key id. + */ +#define CONF_SUBKEYTYPE_DEF(valtype, keytype, keyword) TYPE_ ## keytype, +static int subkeytypes[] = { CONFIG_OPTIONS(CONF_SUBKEYTYPE_DEF) }; +#define CONF_VALUETYPE_DEF(valtype, keytype, keyword) TYPE_ ## valtype, +static int valuetypes[] = { CONFIG_OPTIONS(CONF_VALUETYPE_DEF) }; + +/* + * Configuration keys are primarily integers (big enum of all the + * different configurable options); some keys have string-designated + * subkeys, such as the list of environment variables (subkeys + * defined by the variable names); some have integer-designated + * subkeys (wordness, colours, preference lists). + */ +struct key { + int primary; + union { + int i; + char *s; + } secondary; +}; + +/* 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 (subkeytypes[a->primary]) { + case TYPE_INT: + if (a->secondary.i < b->secondary.i) + return -1; + else if (a->secondary.i > b->secondary.i) + return +1; + return 0; + case TYPE_STR: + return strcmp(a->secondary.s, b->secondary.s); + default: + return 0; + } +} + +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 (subkeytypes[a->primary]) { + case TYPE_INT: + if (a->secondary.i < b->secondary.i) + return -1; + else if (a->secondary.i > b->secondary.i) + return +1; + return 0; + case TYPE_STR: + return strcmp(a->secondary.s, b->secondary.s); + default: + return 0; + } +} + +/* + * Free any dynamic data items pointed to by a 'struct key'. We + * don't free the structure itself, since it's probably part of a + * larger allocated block. + */ +static void free_key(struct key *key) +{ + if (subkeytypes[key->primary] == TYPE_STR) + sfree(key->secondary.s); +} + +/* + * Copy a 'struct key' into another one, copying its dynamic data + * if necessary. + */ +static void copy_key(struct key *to, struct key *from) +{ + to->primary = from->primary; + switch (subkeytypes[to->primary]) { + case TYPE_INT: + to->secondary.i = from->secondary.i; + break; + case TYPE_STR: + to->secondary.s = dupstr(from->secondary.s); + break; + } +} + +/* + * Free any dynamic data items pointed to by a 'struct value'. We + * don't free the value itself, since it's probably part of a larger + * allocated block. + */ +static void free_value(struct value *val, int type) +{ + if (type == TYPE_STR) + sfree(val->u.stringval); + else if (type == TYPE_FILENAME) + filename_free(val->u.fileval); + else if (type == TYPE_FONT) + fontspec_free(val->u.fontval); +} + +/* + * Copy a 'struct value' into another one, copying its dynamic data + * if necessary. + */ +static void copy_value(struct value *to, struct value *from, int type) +{ + switch (type) { + case TYPE_BOOL: + to->u.boolval = from->u.boolval; + break; + case TYPE_INT: + to->u.intval = from->u.intval; + break; + case TYPE_STR: + to->u.stringval = dupstr(from->u.stringval); + break; + case TYPE_FILENAME: + to->u.fileval = filename_copy(from->u.fileval); + break; + case TYPE_FONT: + to->u.fontval = fontspec_copy(from->u.fontval); + break; + } +} + +/* + * Free an entire 'struct conf_entry' and its dynamic data. + */ +static void free_entry(struct conf_entry *entry) +{ + free_key(&entry->key); + free_value(&entry->value, valuetypes[entry->key.primary]); + sfree(entry); +} + +Conf *conf_new(void) +{ + Conf *conf = snew(struct conf_tag); + + conf->tree = newtree234(conf_cmp); + + return conf; +} + +static void conf_clear(Conf *conf) +{ + struct conf_entry *entry; + + while ((entry = delpos234(conf->tree, 0)) != NULL) + free_entry(entry); +} + +void conf_free(Conf *conf) +{ + conf_clear(conf); + freetree234(conf->tree); + sfree(conf); +} + +static void conf_insert(Conf *conf, struct conf_entry *entry) +{ + struct conf_entry *oldentry = add234(conf->tree, entry); + if (oldentry && oldentry != entry) { + del234(conf->tree, oldentry); + free_entry(oldentry); + oldentry = add234(conf->tree, entry); + assert(oldentry == entry); + } +} + +void conf_copy_into(Conf *newconf, Conf *oldconf) +{ + struct conf_entry *entry, *entry2; + int i; + + conf_clear(newconf); + + for (i = 0; (entry = index234(oldconf->tree, i)) != NULL; i++) { + entry2 = snew(struct conf_entry); + copy_key(&entry2->key, &entry->key); + copy_value(&entry2->value, &entry->value, + valuetypes[entry->key.primary]); + add234(newconf->tree, entry2); + } +} + +Conf *conf_copy(Conf *oldconf) +{ + Conf *newconf = conf_new(); + + conf_copy_into(newconf, oldconf); + + return newconf; +} + +bool conf_get_bool(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; +#ifdef MOD_PERSO + if( valuetypes[primary] == TYPE_INT ) { + int i = conf_get_int( conf, primary ) ; + if( i==0 ) { return false ; } + return true ; + } +#endif + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == 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; +#ifdef MOD_PERSO + if( valuetypes[primary] == TYPE_BOOL ) { + return (conf_get_bool( conf, primary ) ? 1 : 0) ; + } +#endif + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_INT); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.intval; +} + +int conf_get_int_int(Conf *conf, int primary, int secondary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_INT); + assert(valuetypes[primary] == TYPE_INT); + key.primary = primary; + key.secondary.i = secondary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.intval; +} + +char *conf_get_str(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.stringval; +} + +char *conf_get_str_str_opt(Conf *conf, int primary, const char *secondary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + key.secondary.s = (char *)secondary; + entry = find234(conf->tree, &key, NULL); + return entry ? entry->value.u.stringval : NULL; +} + +char *conf_get_str_str(Conf *conf, int primary, const char *secondary) +{ + char *ret = conf_get_str_str_opt(conf, primary, secondary); + assert(ret); + return ret; +} + +char *conf_get_str_strs(Conf *conf, int primary, + char *subkeyin, char **subkeyout) +{ + struct constkey key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + if (subkeyin) { + key.secondary.s = subkeyin; + entry = findrel234(conf->tree, &key, NULL, REL234_GT); + } else { + key.secondary.s = ""; + entry = findrel234(conf->tree, &key, 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(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == 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(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FILENAME); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.fileval; +} + +FontSpec *conf_get_fontspec(Conf *conf, int primary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FONT); + key.primary = primary; + entry = find234(conf->tree, &key, NULL); + assert(entry); + return entry->value.u.fontval; +} + +void conf_set_bool(Conf *conf, int primary, bool value) +{ + struct conf_entry *entry = snew(struct conf_entry); +#ifdef MOD_PERSO + if( valuetypes[primary] == TYPE_INT ) { + conf_set_int( conf, primary, (value?1:0) ); + return ; + } +#endif + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == 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); +#ifdef MOD_PERSO + if( valuetypes[primary] == TYPE_BOOL ) { + conf_set_bool( conf, primary, (value==0?false:true) ); + return ; + } +#endif + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_INT); + entry->key.primary = primary; + entry->value.u.intval = value; + conf_insert(conf, entry); +} + +void conf_set_int_int(Conf *conf, int primary, + int secondary, int value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_INT); + assert(valuetypes[primary] == TYPE_INT); + entry->key.primary = primary; + entry->key.secondary.i = secondary; + entry->value.u.intval = value; + conf_insert(conf, entry); +} + +void conf_set_str(Conf *conf, int primary, const char *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_STR); + entry->key.primary = primary; + entry->value.u.stringval = dupstr(value); + conf_insert(conf, entry); +} + +void conf_set_str_str(Conf *conf, int primary, const char *secondary, + const char *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + entry->key.primary = primary; + entry->key.secondary.s = dupstr(secondary); + entry->value.u.stringval = dupstr(value); + conf_insert(conf, entry); +} + +void conf_del_str_str(Conf *conf, int primary, const char *secondary) +{ + struct key key; + struct conf_entry *entry; + + assert(subkeytypes[primary] == TYPE_STR); + assert(valuetypes[primary] == TYPE_STR); + key.primary = primary; + key.secondary.s = (char *)secondary; + entry = find234(conf->tree, &key, NULL); + if (entry) { + del234(conf->tree, entry); + free_entry(entry); + } + } + +void conf_set_filename(Conf *conf, int primary, const Filename *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FILENAME); + entry->key.primary = primary; + entry->value.u.fileval = filename_copy(value); + conf_insert(conf, entry); +} + +void conf_set_fontspec(Conf *conf, int primary, const FontSpec *value) +{ + struct conf_entry *entry = snew(struct conf_entry); + + assert(subkeytypes[primary] == TYPE_NONE); + assert(valuetypes[primary] == TYPE_FONT); + entry->key.primary = primary; + entry->value.u.fontval = fontspec_copy(value); + conf_insert(conf, entry); +} + +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 (subkeytypes[entry->key.primary]) { + case TYPE_INT: + put_uint32(bs, entry->key.secondary.i); + break; + case TYPE_STR: + put_asciz(bs, entry->key.secondary.s); + break; + } + switch (valuetypes[entry->key.primary]) { + case TYPE_BOOL: + put_bool(bs, entry->value.u.boolval); + break; + case TYPE_INT: + put_uint32(bs, entry->value.u.intval); + break; + case TYPE_STR: + put_asciz(bs, entry->value.u.stringval); + break; + case TYPE_FILENAME: + filename_serialise(bs, entry->value.u.fileval); + break; + case 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 (subkeytypes[entry->key.primary]) { + case TYPE_INT: + entry->key.secondary.i = toint(get_uint32(src)); + break; + case TYPE_STR: + entry->key.secondary.s = dupstr(get_asciz(src)); + break; + } + + switch (valuetypes[entry->key.primary]) { + case TYPE_BOOL: + entry->value.u.boolval = get_bool(src); + break; + case TYPE_INT: + entry->value.u.intval = toint(get_uint32(src)); + break; + case TYPE_STR: + entry->value.u.stringval = dupstr(get_asciz(src)); + break; + case TYPE_FILENAME: + entry->value.u.fileval = filename_deserialise(src); + break; + case 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/0.77_My_PuTTY/config.c b/0.77_My_PuTTY/config.c new file mode 100644 index 0000000..51e274e --- /dev/null +++ b/0.77_My_PuTTY/config.c @@ -0,0 +1,4579 @@ +/* + * config.c - the platform-independent parts of the PuTTY + * configuration box. + */ + +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "storage.h" + + +#ifdef MOD_RUTTY +#include "script.h" +#endif /* rutty */ +#ifdef MOD_PERSO +#include "kitty.h" +#include "kitty_store.h" +union control * ctrlHostnameEdit = NULL ; +void MASKPASS( const int mode, char * password ) ; +int stricmp(const char *s1, const char *s2) ; +int GetReadOnlyFlag(void) ; +#endif +#ifdef MOD_ZMODEM +#define ATOFFSET(data, offset) ( (void *) ( (char *)(data) + (offset) ) ) +#endif + +#define PRINTER_DISABLED_STRING "None (printing disabled)" + +#define HOST_BOX_TITLE "Host Name (or IP address)" +#define PORT_BOX_TITLE "Port" + +void conf_radiobutton_handler(union control *ctrl, 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->radio.context.i); + for (button = 0; button < ctrl->radio.nbuttons; button++) + if (val == ctrl->radio.buttondata[button].i) + break; + /* We expected that `break' to happen, in all circumstances. */ + assert(button < ctrl->radio.nbuttons); + dlg_radiobutton_set(ctrl, dlg, button); + } else if (event == EVENT_VALCHANGE) { + button = dlg_radiobutton_get(ctrl, dlg); + assert(button >= 0 && button < ctrl->radio.nbuttons); + conf_set_int(conf, ctrl->radio.context.i, + ctrl->radio.buttondata[button].i); + } +} + +void conf_radiobutton_bool_handler(union control *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->radio.context.i); + for (button = 0; button < ctrl->radio.nbuttons; button++) + if (val == ctrl->radio.buttondata[button].i) + break; + /* We expected that `break' to happen, in all circumstances. */ + assert(button < ctrl->radio.nbuttons); + dlg_radiobutton_set(ctrl, dlg, button); + } else if (event == EVENT_VALCHANGE) { + button = dlg_radiobutton_get(ctrl, dlg); + assert(button >= 0 && button < ctrl->radio.nbuttons); + conf_set_bool(conf, ctrl->radio.context.i, + ctrl->radio.buttondata[button].i); + } +} + +#define CHECKBOX_INVERT (1<<30) +void conf_checkbox_handler(union control *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->checkbox.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); + } +} + +void conf_editbox_handler(union control *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 indicates the type of this field: + * + * - if context2 > 0, the field is a string. + * - if context2 == -1, the field is an int and the edit box + * is numeric. + * - if context2 < -1, the field is an int and the edit box is + * _floating_, and (-context2) gives the scale. (E.g. if + * context2 == -1000, then typing 1.2 into the box will set + * the field to 1200.) + */ + int key = ctrl->editbox.context.i; + int length = ctrl->editbox.context2.i; + Conf *conf = (Conf *)data; + + if (length > 0) { + if (event == EVENT_REFRESH) { + char *field = conf_get_str(conf, key); + dlg_editbox_set(ctrl, dlg, field); + } else if (event == EVENT_VALCHANGE) { + char *field = dlg_editbox_get(ctrl, dlg); + conf_set_str(conf, key, field); + sfree(field); + } + } else if (length < 0) { + if (event == EVENT_REFRESH) { + char str[80]; + int value = conf_get_int(conf, key); + if (length == -1) + sprintf(str, "%d", value); + else + sprintf(str, "%g", (double)value / (double)(-length)); + dlg_editbox_set(ctrl, dlg, str); + } else if (event == EVENT_VALCHANGE) { + char *str = dlg_editbox_get(ctrl, dlg); + if (length == -1) + conf_set_int(conf, key, atoi(str)); + else + conf_set_int(conf, key, (int)((-length) * atof(str))); + sfree(str); + } + } +} + +void conf_filesel_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + int key = ctrl->fileselect.context.i; + Conf *conf = (Conf *)data; + + if (event == EVENT_REFRESH) { + dlg_filesel_set( + ctrl, dlg, conf_get_filename(conf, key)); + } else if (event == EVENT_VALCHANGE) { + Filename *filename = dlg_filesel_get(ctrl, dlg); + conf_set_filename(conf, key, filename); + filename_free(filename); + } +} + +#ifdef MOD_PERSO +void ReadAutoCommandFromFile( const char * filename ) ; +void ReadInitScript( const char * filename ) ; +int existfile( const char * filename ); +union control * ctrlScriptFileContentEdit = NULL, * ctrlAutoCommandContentEdit = NULL ; +void conf_scriptfilesel_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + int key = ctrl->fileselect.context.i; + Conf *conf = (Conf *)data; + + if (event == EVENT_REFRESH) { + dlg_filesel_set(ctrl, dlg, conf_get_filename(conf, key)); + } else if (event == EVENT_VALCHANGE) { + Filename *filename = dlg_filesel_get(ctrl, dlg); + if( filename && existfile(filename->path) ) { + //conf_set_filename(conf, key, filename); + if( (filename->path[strlen(filename->path)-1]=='h') && (filename->path[strlen(filename->path)-2]=='s') && (filename->path[strlen(filename->path)-3]=='.') ) { + ReadAutoCommandFromFile(filename->path); + if( ctrlAutoCommandContentEdit ) dlg_editbox_set( ctrlAutoCommandContentEdit, dlg, conf_get_str(conf,CONF_autocommand)); + } else { + ReadInitScript(filename->path); + if( ctrlScriptFileContentEdit ) dlg_editbox_set(ctrlScriptFileContentEdit, dlg, conf_get_str(conf,CONF_scriptfilecontent)); + } + Filename * fn = filename_from_str( "" ) ; + conf_set_filename(conf,CONF_scriptfile,fn); + dlg_filesel_set(ctrl, dlg, fn ) ; + filename_free(fn); + } + filename_free(filename); + } +} +#endif + +void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + int key = ctrl->fontselect.context.i; + Conf *conf = (Conf *)data; + + if (event == EVENT_REFRESH) { + dlg_fontsel_set( + ctrl, dlg, conf_get_fontspec(conf, key)); + } else if (event == EVENT_VALCHANGE) { + FontSpec *fontspec = dlg_fontsel_get(ctrl, dlg); + conf_set_fontspec(conf, key, fontspec); + fontspec_free(fontspec); + } +} + +static void config_host_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; +#ifdef MOD_PERSO + ctrlHostnameEdit = ctrl ; +#endif + + /* + * 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)); +#ifdef MOD_ADB + } else if (conf_get_int(conf, CONF_protocol) == PROT_ADB) { + char *saved_host = conf_get_str(conf, CONF_host); + dlg_label_change(ctrl, dlg, "-a: any, -d: usb, -e: emulator, or :serial"); + if (!saved_host || !*saved_host) + saved_host = "-a"; + dlg_editbox_set(ctrl, dlg, saved_host); +#endif + } else { + dlg_label_change(ctrl, dlg, HOST_BOX_TITLE); + dlg_editbox_set(ctrl, dlg, conf_get_str(conf, CONF_host)); + } + } else if (event == EVENT_VALCHANGE) { + char *s = dlg_editbox_get(ctrl, dlg); + if (conf_get_int(conf, CONF_protocol) == PROT_SERIAL) + conf_set_str(conf, CONF_serline, s); + else + conf_set_str(conf, CONF_host, s); + sfree(s); + } +} + +static void config_port_handler(union control *ctrl, 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 { + union control *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(union control *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->generic.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, + 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(union control *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(union control *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(union control *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 }, + { "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(union control *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(union control *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", KEX_DHGROUP1 }, + { "Diffie-Hellman group 14", KEX_DHGROUP14 }, + { "Diffie-Hellman group exchange", KEX_DHGEX }, + { "RSA-based key exchange", KEX_RSA }, + { "ECDH key exchange", KEX_ECDH }, + { "-- 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(union control *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(union control *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); +#ifdef MOD_PRINTCLIP + dlg_listbox_add(ctrl, dlg, PRINT_TO_CLIPBOARD_STRING); +#endif + 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); + } +#ifdef MOD_PRINTCLIP + printer = conf_get_str(conf, CONF_printer); + if ( printer ) + dlg_editbox_set(ctrl, dlg, conf_get_str(conf,CONF_printer) ) ; + else if ( conf_get_int(conf,CONF_printclip) ) + dlg_editbox_set(ctrl, dlg, PRINT_TO_CLIPBOARD_STRING); + else + dlg_editbox_set(ctrl, dlg, PRINTER_DISABLED_STRING); + dlg_update_done(ctrl, dlg); +#else + printer = conf_get_str(conf, CONF_printer); + if (!printer) + printer = PRINTER_DISABLED_STRING; + dlg_editbox_set(ctrl, dlg, printer); + dlg_update_done(ctrl, dlg); +#endif + } else if (event == EVENT_VALCHANGE) { + char *printer = dlg_editbox_get(ctrl, dlg); +#ifdef MOD_PRINTCLIP + if (!strcmp(printer, PRINTER_DISABLED_STRING)) { + //printer[0] = '\0'; + conf_set_int(conf,CONF_printclip,0); + } else if (!strcmp( printer, PRINT_TO_CLIPBOARD_STRING)) { + //printer[0] = '\0'; + conf_set_int(conf,CONF_printclip,1); + } + conf_set_str(conf, CONF_printer, printer); + sfree(printer); +#else + if (!strcmp(printer, PRINTER_DISABLED_STRING)) + printer[0] = '\0'; + conf_set_str(conf, CONF_printer, printer); + sfree(printer); +#endif + } +} + +static void codepage_handler(union control *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(union control *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->listbox.context.i); + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + dlg_listbox_addwithid(ctrl, dlg, "Auto", AUTO); + dlg_listbox_addwithid(ctrl, dlg, "Off", FORCE_OFF); + dlg_listbox_addwithid(ctrl, dlg, "On", FORCE_ON); + switch (oldconf) { + case AUTO: dlg_listbox_select(ctrl, dlg, 0); break; + case FORCE_OFF: dlg_listbox_select(ctrl, dlg, 1); break; + case FORCE_ON: dlg_listbox_select(ctrl, dlg, 2); break; + } + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) + i = AUTO; + else + i = dlg_listbox_getid(ctrl, dlg, i); + conf_set_int(conf, ctrl->listbox.context.i, i); + } +} + +#ifdef MOD_PERSO +#include "kitty.h" +int dlg_listbox_get(union control *ctrl, void *dlg, int index, char * pstr, int maxcount) ; +int dlg_listbox_gettext(union control *ctrl, void *dlg, int index, char * pstr, int maxcount) ; + +int StringList_Add( char **list, const char *str ) ; +int StringList_Exist( const char **list, const char * name ) ; +void StringList_Del( char **list, const char * name ) ; +void StringList_Up( char **list, const char * name ) ; + +void UpFolderInList( char * name ) ; + +int CreateFolderInPath( const char * d ) ; +static void sessionsaver_handler(union control *ctrl, dlgparam *dlg, void *data, int event) ; +char *stristr (const char *meule_de_foin, const char *aiguille) ; +void RunPuTTY( HWND hwnd, char * param ) ; +void RunConfig( Conf * conf ) ; +int RunSession( HWND hwnd, const char * folder_in, char * session_in ) ; + +extern char ** FolderList ; + +static char * selectedsession = NULL ; + +extern char CurrentFolder[1024] ; +union control * ctrlSessionList = NULL ; +union control * ctrlSessionEdit = NULL ; +union control * ctrlFolderList = NULL ; +union control * ctrlProxyList = NULL ; + +int get_param( const char * val ) ; +#ifndef SAVEMODE_REG +#define SAVEMODE_REG 0 +#endif +#ifndef SAVEMODE_DIR +#define SAVEMODE_DIR 2 +#endif + +void SetInitCurrentFolder( const char * name ) { if( StringList_Exist((const char **)FolderList, name) ) strcpy( CurrentFolder, name ) ; } + +static void folder_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + int inc = 1 ; + int pfold = 0 ; + char buffer[1024] ; + + Conf * conf = conf_copy( data ) ; + ctrlFolderList = ctrl ; + + if( FolderList== NULL ) InitFolderList() ; + + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + + dlg_listbox_addwithid(ctrl, dlg, "Default", 0 ) ; + while( FolderList[inc] != NULL ) { + if( !strcmp( CurrentFolder, FolderList[inc] ) ) pfold=inc ; + if( strlen(FolderList[inc]) > 0 ) + dlg_listbox_addwithid(ctrl, dlg, FolderList[inc], inc ) ; + inc++; + } + if( inc == 1 ) { + dlg_listbox_select(ctrl, dlg, 0) ; + } + else { + dlg_listbox_select(ctrl, dlg, pfold) ; + } + dlg_update_done(ctrl, dlg); + } + else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + dlg_listbox_get(ctrl, dlg, i, buffer, 1024) ; + conf_set_str( conf, CONF_folder, buffer ) ; + strcpy( CurrentFolder, buffer ) ; + + if (i < 0) + i = AUTO; + else + i = dlg_listbox_getid(ctrl, dlg, i); + strcpy( buffer, dlg_editbox_get( ctrlSessionEdit, dlg ) ) ; + + if( !strcmp( buffer, "" ) ) + sessionsaver_handler( ctrlSessionList, dlg, data, EVENT_REFRESH ) ; + } + else if (event == EVENT_VALCHANGE) { + int i = dlg_listbox_index( ctrl, dlg ) ; + dlg_listbox_get(ctrl, dlg, i, buffer, 1024) ; + + conf_set_str( conf, CONF_folder, buffer ) ; //strcpy( cfg->folder, buffer ) ; + strcpy( CurrentFolder, buffer ) ; + } + conf_free( conf ) ; + } +#endif +#ifdef MOD_PROXY +#include "kitty_proxy.h" +void proxy_selection_handler(union control *ctrl, dlgparam *dlg, void *data, int event) { + int i, j; + Conf *conf = (Conf *)data; + ctrlProxyList = ctrl ; + 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. */ + char * oldproxy = conf_get_str(conf, CONF_proxyselection); + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < lenof(proxies); i++) { + if( proxies[i].name==NULL ) break ; + dlg_listbox_addwithid(ctrl, dlg, proxies[i].name, proxies[i].val); + } + for (i =0, j = 0 ; i < lenof(proxies); i++) { + if( proxies[i].name==NULL ) break ; + if ( !strcmp(oldproxy,proxies[i].name) ) { + dlg_listbox_select(ctrl, dlg, i); + break; + } + j++; + } + if (i == lenof(proxies)) { /* an unsupported setting was chosen */ + dlg_listbox_select(ctrl, dlg, 0); + oldproxy = NULL; + } + dlg_update_done(ctrl, dlg); + if( oldproxy!=NULL ) conf_set_str(conf, CONF_proxyselection, oldproxy); /* restore */ + } else if (event == EVENT_SELCHANGE) { + int i = dlg_listbox_index(ctrl, dlg); + if (i < 0) + i = 0; + else + i = dlg_listbox_getid(ctrl, dlg, i); + conf_set_str(conf, CONF_proxyselection, proxies[i].name); + } +} +#endif + +struct sessionsaver_data { +#ifdef MOD_PERSO + union control *editbox, *listbox, *clearbutton, *loadbutton, *savebutton, *delbutton, *createbutton, *delfolderbutton, *arrangebutton +#if (defined MOD_PERSO) && (!defined FLJ) && (defined MOD_STARTBUTTON) + , *startbutton +#endif + ; + union control *folderlist ; +#else + union control *editbox, *listbox, *loadbutton, *savebutton, *delbutton; +#endif + union control *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); +#ifdef MOD_PERSO + { + char sessionname[1024] ; + int j; + dlg_listbox_gettext(ctrlSessionList, dlg, i, sessionname, 1024 ) ; + + if( selectedsession!=NULL ) { free(selectedsession); } + selectedsession = (char*)malloc(strlen(sessionname)+1); + strcpy(selectedsession,sessionname); + + for( j=0; jsesslist.nsessions; j++ ) { + if( !strcmp( ssd->sesslist.sessions[j], sessionname ) ) i = j ; + } + if( (get_param("INIFILE")==2/*SAVEMODE_DIR*/) && GetDirectoryBrowseFlag() + && (sessionname[0]==' ')&&(sessionname[1]=='[')&&(sessionname[strlen(sessionname)-1]==']') ) { // On gère un sous-répertoire + sessionname[strlen(sessionname)-1]='\0'; + + strcpy( CurrentFolder, SetSessPath( sessionname+2 ) ) ; + if( strlen(CurrentFolder) == 0 ) strcpy( CurrentFolder, "Default" ) ; + conf_set_str(conf,CONF_folder,CurrentFolder); + + ssd->savedsession[0]='\0'; + get_sesslist(&ssd->sesslist, FALSE); + get_sesslist(&ssd->sesslist, TRUE); + dlg_refresh(ssd->listbox, dlg); + sessionsaver_handler( ctrlSessionList, dlg, conf, EVENT_REFRESH ) ; + return false ; + } + } +#endif + bool isdef; + if (i < 0) { + dlg_beep(dlg); + return false; + } + isdef = !strcmp(ssd->sesslist.sessions[i], "Default Settings"); +#ifdef MOD_PERSO + if( (get_param("INIFILE")==SAVEMODE_DIR) && GetDirectoryBrowseFlag() ) { // On charge en mode portable + if( !strcmp(FileExtension,"") ) { + load_settings(ssd->sesslist.sessions[i], conf); + sfree(ssd->savedsession); + ssd->savedsession = dupstr(isdef ? "" : ssd->sesslist.sessions[i]); + } else { + int j; + char sessionname[1024] ; + dlg_listbox_gettext(ctrlSessionList, dlg, i, sessionname, 1024 ) ; + for( j=0; jsesslist.nsessions; j++ ) { + if( !strcmp( ssd->sesslist.sessions[j], sessionname ) ) i = j ; + } + strcat(sessionname,FileExtension); + load_settings(sessionname, conf); + sfree(ssd->savedsession); + sessionname[strlen(sessionname)-strlen(FileExtension)]='\0'; + ssd->savedsession = dupstr(isdef ? "" : sessionname); + } + conf_set_str(conf,CONF_folder,CurrentFolder) ; + } else { // On charge depuis le registre ou alors sans browse directory + load_settings(ssd->sesslist.sessions[i], conf); + strcpy( CurrentFolder, "Default" ) ; + if( conf_get_str(conf,CONF_folder) ) + if( strlen( conf_get_str(conf,CONF_folder) )>0 ) { + strcpy( CurrentFolder, conf_get_str(conf,CONF_folder) ) ; + StringList_Add( FolderList, conf_get_str(conf,CONF_folder) ) ; + } + sfree(ssd->savedsession); + ssd->savedsession = dupstr(isdef ? "" : ssd->sesslist.sessions[i]); + } +#else + load_settings(ssd->sesslist.sessions[i], conf); + sfree(ssd->savedsession); + ssd->savedsession = dupstr(isdef ? "" : ssd->sesslist.sessions[i]); +#endif + 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; +} + +#ifdef MOD_PERSO +int filter_sessionname( const char * pattern, const char * sessionname, const char * folder, const char * savedsession ) { + char buffer[4092] ; + int lenpattern = strlen(pattern) ; + + const char * searchpattern = savedsession+lenpattern ; + while( ((searchpattern[0]==' ')||(searchpattern[0]==' '))&&(searchpattern[0]!='\0') ) searchpattern++ ; + if( stristr( savedsession, pattern ) != savedsession ) return 0 ; + if( strlen(searchpattern)==0 ) return 0 ; + + if( stristr( savedsession, "host:" ) == savedsession ) { + GetSessionField( sessionname, "", "HostName", buffer ) ; if( stristr( buffer, searchpattern )!=NULL ) return 1 ; + GetSessionField( sessionname, "", "SFTPConnect", buffer ) ; if( stristr( buffer, searchpattern )!=NULL ) return 1 ; + GetSessionField( sessionname, "", "RemoteCommand", buffer ) ; if( stristr( buffer, searchpattern )!=NULL ) return 1 ; + GetSessionField( sessionname, "", "PortForwardings", buffer ) ; if( stristr( buffer, searchpattern )!=NULL ) return 1 ; + GetSessionField( sessionname, "", "WinTitle", buffer ) ; if( stristr( buffer, searchpattern )!=NULL ) return 1 ; + } + if( stristr( savedsession, "user:" ) == savedsession ) { + GetSessionField( sessionname, "", "HostName", buffer ) ; if( stristr( buffer, searchpattern )!=NULL ) return 1 ; + GetSessionField( sessionname, "", "UserName", buffer ) ; if( stristr( buffer, searchpattern )!=NULL ) return 1 ; + GetSessionField( sessionname, "", "LocalUserName", buffer ) ; if( stristr( buffer, searchpattern )!=NULL ) return 1 ; + GetSessionField( sessionname, "", "SFTPConnect", buffer ) ; if( stristr( buffer, searchpattern )!=NULL ) return 1 ; + } + if( stristr( savedsession, "comment:" ) == savedsession ) { + GetSessionField( sessionname, "", "Comment", buffer ) ; if( stristr( buffer, searchpattern )!=NULL ) return 1 ; + + } + if( stristr( savedsession, "title:" ) == savedsession ) { + GetSessionField( sessionname, "", "WinTitle", buffer ) ; if( stristr( buffer, searchpattern )!=NULL ) return 1 ; + + } + if( stristr( savedsession, "class:" ) == savedsession ) { + GetSessionField( sessionname, "", "WindowClass", buffer ) ; if( stristr( buffer, searchpattern )!=NULL ) return 1 ; + + } + return 0 ; +} + +int isSessionExist( struct sessionsaver_data *ssd, char * name ) { + int res = 0 ; + int j; + for( j=0; jsesslist.nsessions; j++ ){ + if( !strcmp( ssd->sesslist.sessions[j], name ) ) { res = 1 ; break ; } + } + return res ; + } + +void sessionlist_add( char** sessionslist, const char* val, int* pos, bool checkfext) { + if( !checkfext || !strcmp(FileExtension,"") ) { + sessionslist[*pos] = (char*)malloc( strlen(val)+1 ) ; + strcpy(sessionslist[*pos],val) ; + *pos = *pos + 1 ; + } else { + if( !strcmp(FileExtension,val+strlen(val)-strlen(FileExtension)) ) { + sessionslist[*pos] = (char*)malloc( strlen(val)+1 ) ; + strcpy(sessionslist[*pos],val) ; + sessionslist[*pos][strlen(sessionslist[*pos])-strlen(FileExtension)]='\0' ; // Remove extension + *pos = *pos + 1 ; + } + } +} + +void filter_session_portable(union control *ctrl, dlgparam *dlg, const int nb, const char** sessionslist, const char * savedsession, const char* CurrentFolder) { + int i, j=0; char **s ; bool * tabb ; + char folder[1024] ; + // init + s = (char**)malloc( nb*sizeof(char*)) ; + tabb = (bool*)malloc(nb*sizeof(bool)) ; + for( i=0 ; igeneric.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == ssd->editbox) { +#ifdef MOD_PERSO + ctrlSessionEdit = ctrl ; + if( selectedsession==NULL ) { selectedsession=(char*)malloc(1);strcpy(selectedsession,""); } +#endif + dlg_editbox_set(ctrl, dlg, ssd->savedsession); + } else if (ctrl == ssd->listbox) { + int i; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); +#ifdef MOD_PERSO + if( ssd->savedsession!=NULL ) strcpy( ssd->savedsession, dlg_editbox_get( ssd->editbox, dlg ) ) ; + ctrlSessionList = ctrl ; + //if(get_param("INIFILE")==SAVEMODE_DIR) CleanFolderName( CurrentFolder ) ; + int j; + if( get_param("INIFILE")==SAVEMODE_DIR ) { + filter_session_portable(ctrl, dlg, ssd->sesslist.nsessions,ssd->sesslist.sessions, ssd->savedsession, CurrentFolder ) ; + } + else + for (i = 0, j=0; i < ssd->sesslist.nsessions; i++) { + char folder[1024] ; + strcpy( folder, "" ) ; + if( (get_param("INIFILE")==SAVEMODE_REG) || ((get_param("INIFILE")==SAVEMODE_DIR) && !GetDirectoryBrowseFlag()) ) { // Registry mode + GetSessionFolderName( ssd->sesslist.sessions[i], folder ) ; + } + if( GetPuttyFlag() ) { // In PuTTY mode => all sessions + dlg_listbox_add(ctrl, dlg, ssd->sesslist.sessions[i]); + } + else if( (ssd->sesslist.sessions[i][0]=='_') && (ssd->sesslist.sessions[i][1]=='_') ) { } // Do nothing if session name starts with __ + else if( !GetSessionFilterFlag() ) // In registry mode without filter => all sessions + dlg_listbox_add(ctrl, dlg, ssd->sesslist.sessions[i]); + else { // In registry mode with filter + if( (!strcmp(CurrentFolder, "Default" )&&GetSessionsInDefaultFlag()) + || (!strcmp(CurrentFolder,folder)) ) { + if( (!strcmp( ssd->savedsession, "" )) + || ( strlen(ssd->savedsession)<=1 ) + || ( filter_sessionname( "host:", ssd->sesslist.sessions[i], folder, ssd->savedsession ) ) /* Filtre sur le nom du host */ + || ( filter_sessionname( "user:", ssd->sesslist.sessions[i], folder, ssd->savedsession ) ) + || ( filter_sessionname( "comment:", ssd->sesslist.sessions[i], folder, ssd->savedsession ) ) + || ( filter_sessionname( "title:", ssd->sesslist.sessions[i], folder, ssd->savedsession ) ) + || ( filter_sessionname( "class:", ssd->sesslist.sessions[i], folder, ssd->savedsession ) ) + || ( stristr(ssd->sesslist.sessions[i],ssd->savedsession)!=NULL) ) { + dlg_listbox_add(ctrl, dlg, ssd->sesslist.sessions[i]) ; + if( !strcmp(ssd->sesslist.sessions[i],selectedsession) ) { dlg_listbox_select(ctrl, dlg, j); } + j++; + } + } + } + + } + dlg_update_done(ctrl, dlg); + //if( get_param("INIFILE")!=SAVEMODE_DIR ) { dlg_listbox_select(ctrl,dlg,0) ; } +#else + for (i = 0; i < ssd->sesslist.nsessions; i++) + dlg_listbox_add(ctrl, dlg, ssd->sesslist.sessions[i]); + dlg_update_done(ctrl, dlg); +#endif + + } + } else if (event == EVENT_VALCHANGE) { + int top, bottom, halfway, i; + if (ctrl == ssd->editbox) { + sfree(ssd->savedsession); + ssd->savedsession = dlg_editbox_get(ctrl, dlg); +#ifdef MOD_PERSO + sessionsaver_handler( ctrlSessionList, dlg, data, EVENT_REFRESH ) ; +#endif + 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. + */ +#ifdef MOD_PERSO + if (load_selected_session(ssd, dlg, conf, &mbl) && + (mbl && ctrl == ssd->listbox && conf_launchable(conf))) { + if( conf_get_int(conf, CONF_protocol) != PROT_SERIAL ) { + char buffer[1024] ; + strcpy( buffer, conf_get_str( conf, CONF_password) ) ; + MASKPASS( GetCryptSaltFlag(), buffer ) ; + conf_set_str( conf, CONF_password, buffer ) ; + memset( buffer, 0, strlen(buffer) ) ; + } + if( GetDblClickFlag()==1 ) { +#if (!defined FLJ) && (defined MOD_STARTBUTTON) + sessionsaver_handler( ssd->startbutton, dlg, data, EVENT_ACTION ) ; +#endif + sessionsaver_handler( ssd->clearbutton, dlg, data, EVENT_ACTION ) ; + sessionsaver_handler( ctrlSessionList, dlg, data, EVENT_REFRESH ) ; + } else + dlg_end(dlg, 1); /* it's all over, and succeeded */ + } + if( conf_get_int(conf, CONF_protocol) != PROT_SERIAL ) { + char buffer[1024] ; + strcpy( buffer, conf_get_str( conf, CONF_password) ) ; + MASKPASS( GetCryptSaltFlag(), buffer ) ; + conf_set_str( conf, CONF_password, buffer ) ; + memset( buffer, 0, strlen(buffer) ) ; + } +#else + 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 */ + } +#endif + } 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]); + } + { +#ifdef MOD_PERSO + conf_set_str( conf, CONF_folder, CurrentFolder ) ; + if( conf_get_int(conf, CONF_protocol) != PROT_SERIAL ) { + char buffer[1024] ; + strcpy( buffer, conf_get_str( conf, CONF_password) ) ; + MASKPASS( GetCryptSaltFlag(), buffer ) ; + conf_set_str( conf, CONF_password, buffer ) ; + memset( buffer, 0, strlen(buffer) ) ; + } + char sessionname[1024]; + strcpy(sessionname,ssd->savedsession); + if( strcmp(FileExtension,"") ) { strcat(sessionname,FileExtension); } + char *errmsg = save_settings(sessionname, conf); + + if( conf_get_int(conf, CONF_protocol) != PROT_SERIAL ) { + char buffer[1024] ; + strcpy( buffer, conf_get_str( conf, CONF_password) ) ; + MASKPASS( GetCryptSaltFlag(), buffer ) ; + conf_set_str( conf, CONF_password, buffer ) ; + memset( buffer, 0, strlen(buffer) ) ; + } +#else + char *errmsg = save_settings(ssd->savedsession, conf); +#endif + 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); +#ifdef MOD_PERSO + RepliqueToPuTTY( PUTTY_REG_POS ) ; +#endif + } else if (!ssd->midsession && + ssd->delbutton && ctrl == ssd->delbutton) { + int i = dlg_listbox_index(ssd->listbox, dlg); +#ifdef MOD_PERSO + // Pour recuperer le bon index de la session a supprimer quand il y le filtre des sessions + char sessionname[1024] ; + int j; + dlg_listbox_gettext(ctrlSessionList, dlg, i, sessionname, 1024 ) ; + for( j=0; jsesslist.nsessions; j++ ){ + if( !strcmp( ssd->sesslist.sessions[j], sessionname ) ) i = j ; + } + + if (i <= 0) { + dlg_beep(dlg); + } else { + if( strcmp(FileExtension,"") ) { strcat(sessionname,FileExtension) ; } + del_settings(sessionname); +#else + if (i <= 0) { + dlg_beep(dlg); + } else { + del_settings(ssd->sesslist.sessions[i]); + +#endif + get_sesslist(&ssd->sesslist, false); + get_sesslist(&ssd->sesslist, true); + dlg_refresh(ssd->listbox, dlg); + } +#ifdef MOD_PERSO + ssd->savedsession[0]='\0' ; + dlg_refresh(ssd->editbox, dlg) ; + sessionsaver_handler( ctrlSessionList, dlg, data, EVENT_REFRESH ) ; +#endif + } else if (ctrl == ssd->okbutton) { + if (ssd->midsession) { + /* In a mid-session Change Settings, Apply is always OK. */ + dlg_end(dlg, 1); + return; + } +#ifdef MOD_PERSO + if( conf_get_int(conf, CONF_protocol) != PROT_SERIAL ) { + char buffer[1024] ; + strcpy( buffer, conf_get_str( conf, CONF_password) ) ; + MASKPASS( GetCryptSaltFlag(), buffer ) ; + conf_set_str( conf, CONF_password, buffer ) ; + memset( buffer, 0, strlen(buffer) ) ; + } +#endif + /* + * 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); + } +#ifdef MOD_PERSO +#if (defined MOD_PERSO) && (!defined FLJ) && (defined MOD_STARTBUTTON) + else if (ctrl == ssd->startbutton) { + char sessionname[1024]; + if( conf_launchable(conf) ) { + RunConfig(conf) ; + } else if( (strlen(ssd->savedsession)>0) && isSessionExist(ssd,ssd->savedsession) ) { + strcpy( sessionname, ssd->savedsession ) ; + if( strcmp(FileExtension,"") ) { strcat(sessionname,FileExtension); } + //if( !RunSession( hwnd, CurrentFolder, sessionname ) ) { dlg_beep(dlg) ; } + if( !RunSession( GetMainHwnd(), CurrentFolder, sessionname ) ) { dlg_beep(dlg) ; } + } else if( dlg_last_focused(ctrl, dlg) == ssd->listbox ) { + Conf *conf2 = conf_new() ; + bool mbl = false; + char *oldsavedsession ; + oldsavedsession=(char*)malloc(strlen(ssd->savedsession)+1);strcpy(oldsavedsession,ssd->savedsession); + if (!load_selected_session(ssd, dlg, conf2, &mbl)) { dlg_beep(dlg); } + // If at this point we have a valid session, go! + if (mbl && conf_launchable(conf2)) { + conf_copy_into(conf,conf2); // structure copy + conf_set_str(conf,CONF_remote_cmd,""); + } else + dlg_beep(dlg); + + strcpy( sessionname, ssd->savedsession ) ; + if( strcmp(FileExtension,"") ) { strcat(sessionname,FileExtension); } + //if (conf_launchable(conf)) { RunSession( hwnd, CurrentFolder, sessionname ) ; } + if (conf_launchable(conf)) { RunSession( GetMainHwnd(), CurrentFolder, sessionname ) ; } + strcpy(ssd->savedsession,oldsavedsession); + free(oldsavedsession); + conf_free(conf2); + } else { dlg_beep(dlg) ; + } + + } +#endif + else if (!ssd->midsession && // vide le nom de la session + ssd->clearbutton && ctrl == ssd->clearbutton) { + SetInitCurrentFolder( "Default" ); + folder_handler( ctrlFolderList, dlg, data, EVENT_REFRESH ) ; + char sessionname[1024]; + strcpy(sessionname,"Default Settings"); + if( strcmp(FileExtension,"") ) { strcat(sessionname,FileExtension); } + if(selectedsession!=NULL) { free(selectedsession) ; } + selectedsession = (char*)malloc(strlen(sessionname)+1) ; + strcpy(selectedsession,sessionname); + do_defaults(sessionname, conf) ; + if( !GetPuttyFlag() && GetProxySelectionFlag() ) { + conf_set_str(conf,CONF_proxyselection,"- Session defined proxy -"); + proxy_selection_handler(ctrlProxyList, dlg, data, EVENT_REFRESH ) ; + } + conf_set_str(conf,CONF_host,""); + dlg_editbox_set(ctrlHostnameEdit, dlg,""); + if( strlen(ssd->savedsession) > 0 ) { + ssd->savedsession[0]='\0' ; + dlg_refresh(ssd->editbox, dlg) ; + } + sessionsaver_handler( ctrlSessionList, dlg, data, EVENT_REFRESH ) ; + } + else if (!ssd->midsession && // creer un nouveau folder + ssd->createbutton && ctrl == ssd->createbutton) { + if( strlen(ssd->savedsession) > 0 ) { + if( !stricmp(ssd->savedsession,"Default") ) { + MessageBox( NULL, "Your are not allowed to create a folder called Default !", "Error", MB_OK|MB_ICONERROR ) ; + } else + if( !GetDirectoryBrowseFlag() ) { + InitFolderList() ; + StringList_Add( FolderList, ssd->savedsession ) ; + SaveFolderList(); + folder_handler( ctrlFolderList, dlg, data, EVENT_REFRESH ) ; + ssd->savedsession[0] = '\0'; + dlg_editbox_set( ctrlSessionEdit, dlg, "" ) ; + } + else { + if( !CreateFolderInPath( ssd->savedsession ) ) { + MessageBox( NULL, "Your are not allowed to create sessions folder directory", "Error", MB_OK|MB_ICONERROR ) ; + } + ssd->savedsession[0] = '\0'; + dlg_editbox_set( ctrlSessionEdit, dlg, "" ) ; + get_sesslist(&ssd->sesslist, FALSE); + get_sesslist(&ssd->sesslist, TRUE); + dlg_refresh(ssd->listbox, dlg); + } + } + } + else if (!ssd->midsession && // supprimer un folder + ssd->delfolderbutton && ctrl == ssd->delfolderbutton) { + if( strlen(CurrentFolder) > 0 ) { + if( !strcmp( CurrentFolder, "Default" ) ) + MessageBox( NULL, "To delete Default folder is not allowed", "Deleting error", MB_OK|MB_ICONERROR ); + else { + StringList_Del( FolderList, CurrentFolder ) ; + SaveFolderList() ; + InitFolderList() ; + strcpy( CurrentFolder,"Default" ) ; + folder_handler( ctrlFolderList, dlg, data, EVENT_REFRESH ) ; + ssd->savedsession[0] = '\0'; + dlg_editbox_set( ctrlSessionEdit, dlg, "" ) ; + } + } + } + else if (!ssd->midsession && // Reordonner les folders + ssd->arrangebutton && ctrl == ssd->arrangebutton) { + if( strlen(CurrentFolder) > 0 ) + if( strcmp( CurrentFolder, "Default" ) ) { + StringList_Up( FolderList, CurrentFolder ) ; + SaveFolderList() ; + InitFolderList() ; + folder_handler( ctrlFolderList, dlg, data, EVENT_REFRESH ) ; + ssd->savedsession[0] = '\0'; + dlg_editbox_set( ctrlSessionEdit, dlg, "" ) ; + } + } +#endif + } +} + +struct charclass_data { + union control *listbox, *editbox, *button; +}; + +static void charclass_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct charclass_data *ccd = + (struct charclass_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == ccd->listbox) { + int i; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < 128; i++) { + char str[100]; + sprintf(str, "%d\t(0x%02X)\t%c\t%d", i, i, + (i >= 0x21 && i != 0x7F) ? i : ' ', + conf_get_int_int(conf, CONF_wordness, i)); + dlg_listbox_add(ctrl, dlg, str); + } + dlg_update_done(ctrl, dlg); + } + } else if (event == EVENT_ACTION) { + if (ctrl == ccd->button) { + char *str; + int i, n; + str = dlg_editbox_get(ccd->editbox, dlg); + n = atoi(str); + sfree(str); + for (i = 0; i < 128; i++) { + if (dlg_listbox_issel(ccd->listbox, dlg, i)) + conf_set_int_int(conf, CONF_wordness, i, n); + } + dlg_refresh(ccd->listbox, dlg); + } + } +} + +struct colour_data { +#ifdef MOD_TUTTYCOLOR + union control *listbox, *redit, *gedit, *bedit, *button, + *bold_checkbox, *underline_checkbox, *selected_checkbox; +#else + union control *listbox, *redit, *gedit, *bedit, *button; +#endif +}; +/* 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 +}; + +#ifdef MOD_TUTTYCOLOR +void dlg_control_enable(union control *ctrl, void *dlg, int enable); +static const int itemcolour[] = { + 0, 1, 2, 0, 1, 2, 0, 0, 3, 3, + 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, + 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, + 2, 0, 1, 2 +}; + +static const int idxcolour[2][2][2][34] = { + { + { + {0, 2, 4, 5, 6, 8, 10, 12, 14, 16, + 18, 20, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1}, /* 0, 0, 0: !bold && !under && !selected */ + {0, 2, 4, 5, 32, 33, 6, 8, 10, 12, + 18, 20, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1} /* 0, 0, 1: !bold && !under && selected */ + }, + { + {0, 22, 2, 23, 4, 5, 6, 24, 8, 25, + 10, 26, 12, 27, 14, 28, 16, 29, 18, 30, + 20, 31, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1}, /* 0, 1, 0: !bold && under && !selected */ + {0, 22, 2, 23, 4, 5, 32, 33, 6, 24, + 8, 25, 10, 26, 12, 27, 14, 28, 16, 29, + 18, 30, 20, 31, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1} /* 0, 1, 1: !bold && under && selected */ + }, + }, + { + { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1}, /* 1, 0, 0: bold && !under && !selected */ + {0, 1, 2, 3, 4, 5, 32, 33, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1} /* 1, 0, 1: bold && !under && selected */ + }, + { + {0, 1, 22, 2, 3, 23, 4, 5, 6, 7, + 24, 8, 9, 25, 10, 11, 26, 12, 13, 27, + 14, 15, 28, 16, 17, 29, 18, 19, 30, 20, + 21, 31, -1, -1}, /* 1, 1, 0: bold && under && !selected */ + {0, 1, 22, 2, 3, 23, 4, 5, 32, 33, + 6, 7, 24, 8, 9, 25, 10, 11, 26, 12, + 13, 27, 14, 15, 28, 16, 17, 29, 18, 19, + 30, 20, 21, 31} /* 1, 1, 1: bold && under && selected */ + }, + } +}; +#endif + +static void colour_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct colour_data *cd = + (struct colour_data *)ctrl->generic.context.p; + bool update = false, clear = false; + int r, g, b; + +#ifdef MOD_TUTTYCOLOR + if (ctrl == cd->bold_checkbox) { + conf_set_int(conf,CONF_bold_colour,1); + dlg_control_enable(cd->bold_checkbox, dlg,0); + switch (event) { + case EVENT_REFRESH: + dlg_checkbox_set(cd->bold_checkbox, dlg, conf_get_int(conf,CONF_bold_colour) ); + break; + case EVENT_VALCHANGE: + conf_set_int(conf,CONF_bold_colour, dlg_checkbox_get(cd->bold_checkbox, dlg)); + dlg_refresh(cd->listbox, dlg); + break; + }; + } else if (ctrl == cd->underline_checkbox) { + switch (event) { + case EVENT_REFRESH: + dlg_checkbox_set(cd->underline_checkbox, dlg, + conf_get_int(conf,CONF_under_colour) ); + break; + case EVENT_VALCHANGE: + conf_set_int( conf, CONF_under_colour, dlg_checkbox_get(cd->underline_checkbox, dlg) ) ; + dlg_refresh(cd->listbox, dlg); + break; + }; + } else if (ctrl == cd->selected_checkbox) { + switch (event) { + case EVENT_REFRESH: + dlg_checkbox_set(cd->selected_checkbox, dlg, conf_get_int(conf,CONF_sel_colour) ); + break; + case EVENT_VALCHANGE: + conf_set_int( conf, CONF_sel_colour, dlg_checkbox_get(cd->selected_checkbox, dlg) ) ; + dlg_refresh(cd->listbox, dlg); + break; + }; + }; +#endif + 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++) +#ifdef MOD_TUTTYCOLOR + /* This allows us to hide list items we don't need to + * see: if bold-as-colour (or underline) turned off, we just hide those bold + * choices to decrease user confusion. And, of course, it looks + * lots cooler to have "jumping" controls. Feels more interactive. :) + */ + { + switch (itemcolour[i]) { + case 0: + dlg_listbox_add(ctrl, dlg, colours[i]); + break; + case 1: + if (conf_get_int(conf,CONF_bold_colour)) + dlg_listbox_add(ctrl, dlg, colours[i]); + break; + case 2: + if (conf_get_int(conf,CONF_under_colour)) + dlg_listbox_add(ctrl, dlg, colours[i]); + break; + case 3: + if (conf_get_int(conf,CONF_sel_colour)) + dlg_listbox_add(ctrl, dlg, colours[i]); + }; + }; +#else + dlg_listbox_add(ctrl, dlg, colours[i]); +#endif + 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; +#ifdef MOD_TUTTYCOLOR + /* I know this looks a bit weird, but I just had no + * other choice. Other way it would break existing code and + * worse yet, existing saved session structure. + * This way, underline colours are stored in last 10 + * positions of the colours array, not breaking anything. + * But the price is some weird transformations based on + * predefined index array. + */ + i = idxcolour[conf_get_int(conf,CONF_bold_colour)][conf_get_int(conf,CONF_under_colour)][conf_get_int(conf,CONF_sel_colour)][i]; +#endif + 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) { +#ifdef MOD_TUTTYCOLOR + i = idxcolour[conf_get_int(conf,CONF_bold_colour)][conf_get_int(conf,CONF_under_colour)][conf_get_int(conf,CONF_sel_colour)][i]; + +#endif + 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. + */ +#ifdef MOD_TUTTYCOLOR + i = idxcolour[conf_get_int(conf,CONF_bold_colour)][conf_get_int(conf,CONF_under_colour)][conf_get_int(conf,CONF_sel_colour)][i]; +#endif + 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). + */ +#ifdef MOD_TUTTYCOLOR + i = idxcolour[conf_get_int(conf,CONF_bold_colour)][conf_get_int(conf,CONF_under_colour)][conf_get_int(conf,CONF_sel_colour)][i]; +#endif + if (dlg_coloursel_results(ctrl, dlg, &r, &g, &b)) { + conf_set_int_int(conf, CONF_colours, i*3+0, r); + conf_set_int_int(conf, CONF_colours, i*3+1, g); + conf_set_int_int(conf, CONF_colours, i*3+2, b); + clear = false; + update = true; + } + } + } + + if (update) { + if (clear) { + dlg_editbox_set(cd->redit, dlg, ""); + dlg_editbox_set(cd->gedit, dlg, ""); + dlg_editbox_set(cd->bedit, dlg, ""); + } else { + char buf[40]; + sprintf(buf, "%d", r); dlg_editbox_set(cd->redit, dlg, buf); + sprintf(buf, "%d", g); dlg_editbox_set(cd->gedit, dlg, buf); + sprintf(buf, "%d", b); dlg_editbox_set(cd->bedit, dlg, buf); + } + } +} + +struct ttymodes_data { + union control *valradio, *valbox, *setbutton, *listbox; +}; + +static void ttymodes_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct ttymodes_data *td = + (struct ttymodes_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == td->listbox) { + char *key, *val; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (val = conf_get_str_strs(conf, CONF_ttymodes, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_ttymodes, key, &key)) { + char *disp = dupprintf("%s\t%s", key, + (val[0] == 'A') ? "(auto)" : + ((val[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 { + union control *varbox, *valbox, *addbutton, *rembutton, *listbox; +}; + +static void environ_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct environ_data *ed = + (struct environ_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == ed->listbox) { + char *key, *val; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (val = conf_get_str_strs(conf, CONF_environmt, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_environmt, key, &key)) { + char *p = dupprintf("%s\t%s", key, val); + dlg_listbox_add(ctrl, dlg, p); + sfree(p); + } + dlg_update_done(ctrl, dlg); + } + } else if (event == EVENT_ACTION) { + if (ctrl == ed->addbutton) { + char *key, *val, *str; + key = dlg_editbox_get(ed->varbox, dlg); + if (!*key) { + sfree(key); + dlg_beep(dlg); + return; + } + val = dlg_editbox_get(ed->valbox, dlg); + if (!*val) { + sfree(key); + sfree(val); + dlg_beep(dlg); + return; + } + conf_set_str_str(conf, CONF_environmt, key, val); + str = dupcat(key, "\t", val); + dlg_editbox_set(ed->varbox, dlg, ""); + dlg_editbox_set(ed->valbox, dlg, ""); + sfree(str); + sfree(key); + sfree(val); + dlg_refresh(ed->listbox, dlg); + } else if (ctrl == ed->rembutton) { + int i = dlg_listbox_index(ed->listbox, dlg); + if (i < 0) { + dlg_beep(dlg); + } else { + char *key, *val; + + key = conf_get_str_nthstrkey(conf, CONF_environmt, i); + if (key) { + /* Populate controls with the entry we're about to delete + * for ease of editing */ + val = conf_get_str_str(conf, CONF_environmt, key); + dlg_editbox_set(ed->varbox, dlg, key); + dlg_editbox_set(ed->valbox, dlg, val); + /* And delete it */ + conf_del_str_str(conf, CONF_environmt, key); + } + } + dlg_refresh(ed->listbox, dlg); + } + } +} + +struct portfwd_data { + union control *addbutton, *rembutton, *listbox; + union control *sourcebox, *destbox, *direction; +#ifndef NO_IPV6 + union control *addressfamily; +#endif +}; + +static void portfwd_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct portfwd_data *pfd = + (struct portfwd_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == pfd->listbox) { + char *key, *val; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) { + char *p; + if (!strcmp(val, "D")) { + char *L; + /* + * A dynamic forwarding is stored as L12345=D or + * 6L12345=D (since it's mutually exclusive with + * L12345=anything else), but displayed as D12345 + * to match the fiction that 'Local', 'Remote' and + * 'Dynamic' are three distinct modes and also to + * align with OpenSSH's command line option syntax + * that people will already be used to. So, for + * display purposes, find the L in the key string + * and turn it into a D. + */ + p = dupprintf("%s\t", key); + L = strchr(p, 'L'); + if (L) *L = 'D'; + } else + p = dupprintf("%s\t%s", key, val); + dlg_listbox_add(ctrl, dlg, p); + sfree(p); + } + dlg_update_done(ctrl, dlg); + } else if (ctrl == pfd->direction) { + /* + * Default is Local. + */ + dlg_radiobutton_set(ctrl, dlg, 0); +#ifndef NO_IPV6 + } else if (ctrl == pfd->addressfamily) { + dlg_radiobutton_set(ctrl, dlg, 0); +#endif + } + } else if (event == EVENT_ACTION) { + if (ctrl == pfd->addbutton) { + 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 { + union control *addbutton, *rembutton, *listbox, *keybox; +}; + +static void manual_hostkey_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct manual_hostkey_data *mh = + (struct manual_hostkey_data *)ctrl->generic.context.p; + + if (event == EVENT_REFRESH) { + if (ctrl == mh->listbox) { + char *key, *val; + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (val = conf_get_str_strs(conf, CONF_ssh_manual_hostkeys, + NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_ssh_manual_hostkeys, + key, &key)) { + dlg_listbox_add(ctrl, dlg, key); + } + dlg_update_done(ctrl, dlg); + } + } else if (event == EVENT_ACTION) { + if (ctrl == mh->addbutton) { + char *key; + + key = dlg_editbox_get(mh->keybox, dlg); + if (!*key) { + dlg_error_msg(dlg, "You need to specify a host key or " + "fingerprint"); + sfree(key); + return; + } + + if (!validate_manual_hostkey(key)) { + dlg_error_msg(dlg, "Host key is not in a valid format"); + } else if (conf_get_str_str_opt(conf, CONF_ssh_manual_hostkeys, + key)) { + dlg_error_msg(dlg, "Specified host key is already listed"); + } else { + conf_set_str_str(conf, CONF_ssh_manual_hostkeys, key, ""); + } + + sfree(key); + dlg_refresh(mh->listbox, dlg); + } else if (ctrl == mh->rembutton) { + int i = dlg_listbox_index(mh->listbox, dlg); + if (i < 0) { + dlg_beep(dlg); + } else { + char *key; + + key = conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, i); + if (key) { + dlg_editbox_set(mh->keybox, dlg, key); + /* And delete it */ + conf_del_str_str(conf, CONF_ssh_manual_hostkeys, key); + } + } + dlg_refresh(mh->listbox, dlg); + } + } +} + +static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + int setting = ctrl->generic.context.i; +#ifdef NAMED_CLIPBOARDS + int strsetting = ctrl->editbox.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, intorptr 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(union control *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->listbox.context.i; + int i, j; + Conf *conf = (Conf *)data; + + if (event == EVENT_REFRESH) { + /* Fetching this once at the start of the function ensures we + * remember what the right value is supposed to be when + * operations below cause reentrant calls to this function. */ + int oldparity = conf_get_int(conf, CONF_serparity); + + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < lenof(parities); i++) { + if (mask & (1 << 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(union control *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->listbox.context.i; + int i, j; + Conf *conf = (Conf *)data; + + if (event == EVENT_REFRESH) { + /* Fetching this once at the start of the function ensures we + * remember what the right value is supposed to be when + * operations below cause reentrant calls to this function. */ + int oldflow = conf_get_int(conf, CONF_serflow); + + dlg_update_start(ctrl, dlg); + dlg_listbox_clear(ctrl, dlg); + for (i = 0; i < lenof(flows); i++) { + if (mask & (1 << 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 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; + union control *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; +#ifdef MOD_PERSO + if( GetConfigBoxHeight() > 7 ) ssd->okbutton->generic.column = 0 ; else +#endif + ssd->okbutton->generic.column = 3; +#if (defined MOD_PERSO) && (!defined FLJ) && (defined MOD_STARTBUTTON) + if( (!midsession)&&(!GetPuttyFlag()) ) { + ssd->startbutton = ctrl_pushbutton(s, "Start", NO_SHORTCUT, HELPCTX(no_help), sessionsaver_handler, P(ssd)); + if( GetConfigBoxHeight() > 7 ) ssd->startbutton->generic.column = 0 ; else ssd->startbutton->generic.column = 3; + } +#endif + ssd->cancelbutton = ctrl_pushbutton(s, "Cancel", 'c', HELPCTX(no_help), + sessionsaver_handler, P(ssd)); + ssd->cancelbutton->button.iscancel = true; +#ifdef MOD_PERSO +/* + if( strlen(conf_get_str(conf,CONF_host))==0 ) { + if( strlen(conf_get_str(conf,CONF_host_alt))>0 ) { + conf_set_str( conf, CONF_host, conf_get_str( conf, CONF_host_alt ) ) ; + conf_set_str( conf, CONF_host_alt, "" ) ; + } + } +*/ + if( GetConfigBoxHeight() > 7 ) ssd->cancelbutton->generic.column = 0 ; else +#endif + ssd->cancelbutton->generic.column = 4; + /* We carefully don't close the 5-column part, so that platform- + * specific add-ons can put extra buttons alongside Open and Cancel. */ + + /* + * The Session panel. + */ + str = dupprintf("Basic options for your %s session", appname); + ctrl_settitle(b, "Session", str); + sfree(str); + + if (!midsession) { + struct hostport *hp = (struct hostport *) + ctrl_alloc(b, sizeof(struct hostport)); + 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->generic.column = 0; + hp->host = c; + c = ctrl_editbox(s, PORT_BOX_TITLE, 'p', 100, + HELPCTX(session_hostname), + config_port_handler, I(0), I(0)); + c->generic.column = 1; + hp->port = c; + ctrl_columns(s, 1, 100); + + 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), NULL); + c->generic.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); + 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->generic.column = 1; + + /* Vertically centre the two protocol controls w.r.t. each other */ + hp->protlist->generic.align_next_to = hp->protradio; + + ctrl_columns(s, 1, 100); + } +#ifdef MOD_PROXY + if( !GetPuttyFlag() && GetProxySelectionFlag() ) { + if (!midsession) { + ctrl_droplist(s, "Proxy choice", NO_SHORTCUT, 60, + HELPCTX(no_help), + proxy_selection_handler, I(0)); + } + } +#endif + + /* + * 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); +#ifdef MOD_PERSO + ssd->editbox = ctrl_editbox(s, "Saved Sessions/New Folder", 'e', 100, + HELPCTX(session_saved), + sessionsaver_handler, P(ssd), P(NULL)); + + ssd->editbox->generic.column = 0; + if( !midsession && ( (get_param("INIFILE")!=2)||(!GetDirectoryBrowseFlag()) ) ) { + ssd->clearbutton = ctrl_pushbutton(s, "Clear", NO_SHORTCUT, + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + + ssd->clearbutton->generic.column = 1; + } else { + ssd->clearbutton = NULL ; + } +#else + ssd->editbox = ctrl_editbox(s, "Saved Sessions", 'e', 100, + HELPCTX(session_saved), + sessionsaver_handler, P(ssd), P(NULL)); + ssd->editbox->generic.column = 0; +#endif + /* Reset columns so that the buttons are alongside the list, rather + * than alongside that edit box. */ + ctrl_columns(s, 1, 100); + ctrl_columns(s, 2, 75, 25); + ssd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->listbox->generic.column = 0; +#ifdef MOD_PERSO + ssd->listbox->listbox.height = GetConfigBoxHeight() ; + ssd->listbox->listbox.hscroll = true ; /* Does nothing */ +#else + ssd->listbox->listbox.height = 7; +#endif + if (!midsession) { + ssd->loadbutton = ctrl_pushbutton(s, "Load", 'l', + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->loadbutton->generic.column = 1; + } else { + /* We can't offer the Load button mid-session, as it would allow the + * user to load and subsequently save settings they can't see. (And + * also change otherwise immutable settings underfoot; that probably + * shouldn't be a problem, but.) */ + ssd->loadbutton = NULL; + } + /* "Save" button is permitted mid-session. */ +#ifdef MOD_PERSO + if( !GetReadOnlyFlag() ) { + if( get_param("INIFILE") == 1 ) + ssd->savebutton = ctrl_pushbutton(s, "Save (f)", 'v', HELPCTX(session_saved), sessionsaver_handler, P(ssd)); + else if( get_param("INIFILE") == 2 ) + ssd->savebutton = ctrl_pushbutton(s, "Save (d)", 'v', HELPCTX(session_saved), sessionsaver_handler, P(ssd)); + else + ssd->savebutton = ctrl_pushbutton(s, "Save", 'v', + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->savebutton->generic.column = 1; + } else { ssd->savebutton = NULL ; } + + if( !midsession && !GetReadOnlyFlag() ) { + ssd->delbutton = ctrl_pushbutton(s, "Delete", 'd', + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->delbutton->generic.column = 1; + } else { + /* Disable the Delete button mid-session too, for UI consistency. */ + ssd->delbutton = NULL; + } + + if( GetConfigBoxHeight() > 7 ) { // On n'affiche les boutons KiTTY que si la taille de la config box le permet + if (!midsession && !GetReadOnlyFlag()) { // Bouton de creation d'un folder + ssd->createbutton = ctrl_pushbutton(s, "New folder", NO_SHORTCUT, + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->createbutton->generic.column = 1; + } else { + ssd->createbutton = NULL ; + } + + if( !GetDirectoryBrowseFlag() ) { + if (!midsession && !GetReadOnlyFlag()) { // Bouton de suppression d'un folder + ssd->delfolderbutton = ctrl_pushbutton(s, "Del folder", NO_SHORTCUT, + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->delfolderbutton->generic.column = 1; + } else { + ssd->delfolderbutton = NULL ; + } + } + + if( !GetDirectoryBrowseFlag() ) { + if ( !midsession && !GetReadOnlyFlag() ) { // Bouton d'arrange de l'ordre de la liste des folders + ssd->arrangebutton = ctrl_pushbutton(s, "Up folder", NO_SHORTCUT, + HELPCTX(no_help), + sessionsaver_handler, P(ssd)); + ssd->arrangebutton->generic.column = 1; + } else { + ssd->arrangebutton = NULL ; + } + } + + if( !GetDirectoryBrowseFlag() ) + ctrl_droplist(s, "Folder", NO_SHORTCUT, 80, + HELPCTX(no_help), + folder_handler, I(CONF_folder)) ; + } +#else + ssd->savebutton = ctrl_pushbutton(s, "Save", 'v', + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->savebutton->generic.column = 1; + if (!midsession) { + ssd->delbutton = ctrl_pushbutton(s, "Delete", 'd', + HELPCTX(session_saved), + sessionsaver_handler, P(ssd)); + ssd->delbutton->generic.column = 1; + } else { + /* Disable the Delete button mid-session too, for UI consistency. */ + ssd->delbutton = NULL; + } +#endif + ctrl_columns(s, 1, 100); + + s = ctrl_getset(b, "Session", "otheropts", NULL); + ctrl_radiobuttons(s, "Close window on exit:", 'x', 4, + HELPCTX(session_coe), + conf_radiobutton_handler, + I(CONF_close_on_exit), + "Always", I(FORCE_ON), + "Never", I(FORCE_OFF), + "Only on clean exit", I(AUTO), NULL); + + /* + * The Session/Logging panel. + */ + ctrl_settitle(b, "Session/Logging", "Options controlling session logging"); + + s = ctrl_getset(b, "Session/Logging", "main", NULL); + /* + * The logging buttons change depending on whether SSH packet + * logging can sensibly be available. + */ + { + 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), + NULL); + } + ctrl_filesel(s, "Log file name:", 'f', + NULL, true, "Select session log file name", + HELPCTX(logging_filename), + conf_filesel_handler, I(CONF_logfilename)); + ctrl_text(s, "(Log file name can contain &Y, &M, &D for date," + " &T for time, &H for host name, and &P for port number)", + HELPCTX(logging_filename)); +#ifdef MOD_PERSO + ctrl_editbox(s, "Log rotation delay (sec)", NO_SHORTCUT, 50, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_logtimerotation), I(-1) ) ; + ctrl_editbox(s, "Timestamp", NO_SHORTCUT, 100, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_logtimestamp), I(1) ) ; +#endif + ctrl_radiobuttons(s, "What to do if the log file already exists:", 'e', 1, + HELPCTX(logging_exists), + conf_radiobutton_handler, I(CONF_logxfovr), + "Always overwrite it", I(LGXF_OVR), + "Always append to the end of it", I(LGXF_APN), + "Ask the user every time", I(LGXF_ASK), NULL); + ctrl_checkbox(s, "Flush log file frequently", 'u', + HELPCTX(logging_flush), + conf_checkbox_handler, I(CONF_logflush)); + 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)); + } + +/* rutty: Session/Scripting panel */ +#ifdef MOD_RUTTY + if( !GetPuttyFlag() && (GetRuttyFlag()>0) ) { + ctrl_settitle(b, "Session/Scripting", "Scripting (RuTTY patch)"); + s = ctrl_getset(b, "Session/Scripting", "Start", NULL); + ctrl_filesel(s, "Script filename:", 'f', + "Scr Files (*.scr, *.txt)\0*.scr;*.txt\0All Files (*.*)\0*\0\0\0" + , TRUE, "Select filename for script replay", HELPCTX(no_help), + conf_filesel_handler, I(CONF_script_filename)); + ctrl_radiobuttons(s, NULL, 'm', 4, HELPCTX(no_help), + conf_radiobutton_handler, I(CONF_script_mode), + "Off", I(SCRIPT_STOP), + "Replay", I(SCRIPT_PLAY), + "Record", I(SCRIPT_RECORD), + NULL); + + s = ctrl_getset(b, "Session/Scripting", "Scripting", NULL); + + ctrl_editbox(s, "line delay (ms)", 'd', 40, HELPCTX(no_help), + conf_editbox_handler, I(CONF_script_line_delay), I(-1)); + + ctrl_editbox(s, "character delay (ms)", 'b', 40, HELPCTX(no_help), + conf_editbox_handler, I(CONF_script_char_delay), I(-1)); + + ctrl_editbox(s, "start of condition/comment line", 'l', 40, HELPCTX(no_help), + conf_editbox_handler, I(CONF_script_cond_line), I(1)); + + ctrl_radiobuttons(s, "CR/LF translation:", 't', 4, HELPCTX(no_help), + conf_radiobutton_handler,I(CONF_script_crlf), + "Off", I(SCRIPT_OFF), + "no LF", I(SCRIPT_NOLF), + "CR", I(SCRIPT_CR), + "Rec", I(SCRIPT_REC), + NULL); + + ctrl_editbox(s, "halt on", 'x', 80, HELPCTX(no_help), + conf_editbox_handler, I(CONF_script_halton), I(1)); + ctrl_checkbox(s, "wait for response from host", 'r', HELPCTX(no_help), + conf_checkbox_handler, I(CONF_script_enable)); + ctrl_checkbox(s, "except for first command", 's', HELPCTX(no_help), + conf_checkbox_handler, I(CONF_script_except)); + ctrl_checkbox(s, "use conditions from file", 'v', HELPCTX(no_help), + conf_checkbox_handler, I(CONF_script_cond_use)); + + ctrl_editbox(s, "timeout (sec)", 'u', 40, HELPCTX(no_help), + conf_editbox_handler, I(CONF_script_timeout),I(-1)); + + ctrl_editbox(s, "wait for", 'k', 80, HELPCTX(no_help), + conf_editbox_handler, I(CONF_script_waitfor),I(1)); + } +#endif /* rutty */ + + /* + * The Terminal panel. + */ + ctrl_settitle(b, "Terminal", "Options controlling the terminal emulation"); + + s = ctrl_getset(b, "Terminal", "general", "Set various terminal options"); + ctrl_checkbox(s, "Auto wrap mode initially on", 'w', + HELPCTX(terminal_autowrap), + conf_checkbox_handler, I(CONF_wrap_mode)); + ctrl_checkbox(s, "DEC Origin Mode initially on", 'd', + HELPCTX(terminal_decom), + conf_checkbox_handler, I(CONF_dec_om)); + ctrl_checkbox(s, "Implicit CR in every LF", 'r', + HELPCTX(terminal_lfhascr), + conf_checkbox_handler, I(CONF_lfhascr)); + ctrl_checkbox(s, "Implicit LF in every CR", 'f', + HELPCTX(terminal_crhaslf), + conf_checkbox_handler, I(CONF_crhaslf)); + ctrl_checkbox(s, "Use background colour to erase screen", 'e', + HELPCTX(terminal_bce), + conf_checkbox_handler, I(CONF_bce)); + ctrl_checkbox(s, "Enable blinking text", 'n', + HELPCTX(terminal_blink), + conf_checkbox_handler, I(CONF_blinktext)); + ctrl_editbox(s, "Answerback to ^E:", 's', 100, + HELPCTX(terminal_answerback), + conf_editbox_handler, I(CONF_answerback), I(1)); + + s = ctrl_getset(b, "Terminal", "ldisc", "Line discipline options"); + ctrl_radiobuttons(s, "Local echo:", 'l', 3, + HELPCTX(terminal_localecho), + conf_radiobutton_handler,I(CONF_localecho), + "Auto", I(AUTO), + "Force on", I(FORCE_ON), + "Force off", I(FORCE_OFF), NULL); + ctrl_radiobuttons(s, "Local line editing:", 't', 3, + HELPCTX(terminal_localedit), + conf_radiobutton_handler,I(CONF_localedit), + "Auto", I(AUTO), + "Force on", I(FORCE_ON), + "Force off", I(FORCE_OFF), NULL); + + s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing"); + ctrl_combobox(s, "Printer to send ANSI printer output to:", 'p', 100, + HELPCTX(terminal_printing), + printerbox_handler, P(NULL), P(NULL)); + + /* + * The Terminal/Keyboard panel. + */ + ctrl_settitle(b, "Terminal/Keyboard", + "Options controlling the effects of keys"); + + s = ctrl_getset(b, "Terminal/Keyboard", "mappings", + "Change the sequences sent by:"); + ctrl_radiobuttons(s, "The Backspace key", 'b', 2, + HELPCTX(keyboard_backspace), + conf_radiobutton_bool_handler, + I(CONF_bksp_is_delete), + "Control-H", I(0), "Control-? (127)", I(1), NULL); +#ifdef MOD_PERSO + ctrl_radiobuttons(s, "The Enter key", 'y', 2, + HELPCTX(no_help), + conf_radiobutton_handler, + I(CONF_enter_sends_crlf), + "CR", I(0), "CR LF", I(1), NULL); + ctrl_radiobuttons(s, "The Home and End keys", 'e', 3, + HELPCTX(keyboard_homeend), + conf_radiobutton_handler, + I(CONF_rxvt_homeend), + "Standard", I(0), "rxvt", I(1), "urxvt", I(2), + "xterm", I(3), "FreeBSD1", I(4), "FreeBSD2", I(5), + NULL); +#else + 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), NULL); +#endif + ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 3, + HELPCTX(keyboard_funkeys), + conf_radiobutton_handler, + I(CONF_funky_type), + "ESC[n~", I(0), "Linux", I(1), "Xterm R6", I(2), + "VT400", I(3), "VT100+", I(4), "SCO", I(5), +#ifdef MOD_TUTTY +// "AT&T 513", I(6), +// "Sun Xterm", I(8), +#endif + NULL); + + s = ctrl_getset(b, "Terminal/Keyboard", "appkeypad", + "Application keypad settings:"); + ctrl_radiobuttons(s, "Initial state of cursor keys:", 'r', 3, + HELPCTX(keyboard_appcursor), + conf_radiobutton_bool_handler, + I(CONF_app_cursor), + "Normal", I(0), "Application", I(1), NULL); + ctrl_radiobuttons(s, "Initial state of numeric keypad:", 'n', 3, + HELPCTX(keyboard_appkeypad), + numeric_keypad_handler, P(NULL), + "Normal", I(0), "Application", I(1), "NetHack", I(2), + NULL); + + /* + * The Terminal/Bell panel. + */ + ctrl_settitle(b, "Terminal/Bell", + "Options controlling the terminal bell"); + + s = ctrl_getset(b, "Terminal/Bell", "style", "Set the style of bell"); + ctrl_radiobuttons(s, "Action to happen when a bell occurs:", 'b', 1, + HELPCTX(bell_style), + conf_radiobutton_handler, I(CONF_beep), + "None (bell disabled)", I(BELL_DISABLED), + "Make default system alert sound", I(BELL_DEFAULT), + "Visual bell (flash window)", I(BELL_VISUAL), NULL); + +#ifdef MOD_PERSO + ctrl_checkbox(s, "Put window on foreground on bell", NO_SHORTCUT, + HELPCTX(no_help), + conf_checkbox_handler, I(CONF_foreground_on_bell) ) ; +#endif + +s = ctrl_getset(b, "Terminal/Bell", "overload", + "Control the bell overload behaviour"); + ctrl_checkbox(s, "Bell is temporarily disabled when over-used", 'd', + HELPCTX(bell_overload), + conf_checkbox_handler, I(CONF_bellovl)); + ctrl_editbox(s, "Over-use means this many bells...", 'm', 20, + HELPCTX(bell_overload), + conf_editbox_handler, I(CONF_bellovl_n), I(-1)); + ctrl_editbox(s, "... in this many seconds", 't', 20, + HELPCTX(bell_overload), + conf_editbox_handler, I(CONF_bellovl_t), + I(-TICKSPERSEC)); + ctrl_text(s, "The bell is re-enabled after a few seconds of silence.", + HELPCTX(bell_overload)); + ctrl_editbox(s, "Seconds of silence required", 's', 20, + HELPCTX(bell_overload), + conf_editbox_handler, I(CONF_bellovl_s), + I(-TICKSPERSEC)); + + /* + * The Terminal/Features panel. + */ + ctrl_settitle(b, "Terminal/Features", + "Enabling and disabling advanced terminal features"); + + s = ctrl_getset(b, "Terminal/Features", "main", NULL); + ctrl_checkbox(s, "Disable application cursor keys mode", 'u', + HELPCTX(features_application), + conf_checkbox_handler, I(CONF_no_applic_c)); + ctrl_checkbox(s, "Disable application keypad mode", 'k', + HELPCTX(features_application), + conf_checkbox_handler, I(CONF_no_applic_k)); + ctrl_checkbox(s, "Disable xterm-style mouse reporting", 'x', + HELPCTX(features_mouse), + conf_checkbox_handler, I(CONF_no_mouse_rep)); + ctrl_checkbox(s, "Disable remote-controlled terminal resizing", 's', + HELPCTX(features_resize), + conf_checkbox_handler, + I(CONF_no_remote_resize)); + ctrl_checkbox(s, "Disable switching to alternate terminal screen", 'w', + HELPCTX(features_altscreen), + conf_checkbox_handler, I(CONF_no_alt_screen)); + ctrl_checkbox(s, "Disable remote-controlled window title changing", 't', + HELPCTX(features_retitle), + conf_checkbox_handler, + I(CONF_no_remote_wintitle)); + ctrl_radiobuttons(s, "Response to remote title query (SECURITY):", 'q', 3, + HELPCTX(features_qtitle), + conf_radiobutton_handler, + I(CONF_remote_qtitle_action), + "None", I(TITLE_NONE), + "Empty string", I(TITLE_EMPTY), + "Window title", I(TITLE_REAL), NULL); + ctrl_checkbox(s, "Disable 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)); +#ifdef MOD_PERSO + ctrl_checkbox(s, "Disable focus reporting", + NO_SHORTCUT, HELPCTX(no_help), conf_checkbox_handler, + I(CONF_no_focus_rep)); +#endif + /* + * 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), I(-1)); + c->generic.column = 0; + c = ctrl_editbox(s, "Rows", 'r', 100, + HELPCTX(window_size), + conf_editbox_handler, I(CONF_height),I(-1)); + c->generic.column = 1; + ctrl_columns(s, 1, 100); + } + + s = ctrl_getset(b, "Window", "scrollback", + "Control the scrollback in the window"); + ctrl_editbox(s, "Lines of scrollback", 's', 50, + HELPCTX(window_scrollback), + conf_editbox_handler, I(CONF_savelines), I(-1)); +#if MOD_PERSO + /* + * PuttyFeatures: Scroll Lines + */ + ctrl_text(s, "Set the number of lines to scroll back per wheel turn." + " Use -1 for half screen, -2 full screen", + HELPCTX(colours_config)); + ctrl_editbox(s, "Lines scrolled per turn", 'l', 50, + HELPCTX(window_scrollback), + conf_editbox_handler, I(CONF_scrolllines), I(-1)); +#endif + ctrl_checkbox(s, "Display scrollbar", 'd', + HELPCTX(window_scrollback), + conf_checkbox_handler, I(CONF_scrollbar)); + ctrl_checkbox(s, "Reset scrollback on keypress", 'k', + HELPCTX(window_scrollback), + conf_checkbox_handler, I(CONF_scroll_on_key)); + ctrl_checkbox(s, "Reset scrollback on display activity", 'p', + HELPCTX(window_scrollback), + conf_checkbox_handler, I(CONF_scroll_on_disp)); + ctrl_checkbox(s, "Push erased text into scrollback", 'e', + HELPCTX(window_erased), + conf_checkbox_handler, + I(CONF_erase_to_scrollback)); + + /* + * The Window/Appearance panel. + */ + str = dupprintf("Configure the appearance of %s's window", appname); + ctrl_settitle(b, "Window/Appearance", str); + sfree(str); + + s = ctrl_getset(b, "Window/Appearance", "cursor", + "Adjust the use of the cursor"); + ctrl_radiobuttons(s, "Cursor appearance:", NO_SHORTCUT, 3, + HELPCTX(appearance_cursor), + conf_radiobutton_handler, + I(CONF_cursor_type), + "Block", 'l', I(0), + "Underline", 'u', I(1), + "Vertical line", 'v', I(2), NULL); + ctrl_checkbox(s, "Cursor blinks", 'b', + HELPCTX(appearance_cursor), + conf_checkbox_handler, I(CONF_blink_cur)); + + s = ctrl_getset(b, "Window/Appearance", "font", + "Font settings"); + ctrl_fontsel(s, "Font used in the terminal window", 'n', + HELPCTX(appearance_font), + conf_fontsel_handler, I(CONF_font)); + + s = ctrl_getset(b, "Window/Appearance", "mouse", + "Adjust the use of the mouse pointer"); + ctrl_checkbox(s, "Hide mouse pointer when typing in window", 'p', + HELPCTX(appearance_hidemouse), + conf_checkbox_handler, I(CONF_hide_mouseptr)); + + s = ctrl_getset(b, "Window/Appearance", "border", + "Adjust the window border"); + ctrl_editbox(s, "Gap between text and window edge:", 'e', 20, + HELPCTX(appearance_border), + conf_editbox_handler, + I(CONF_window_border), I(-1)); + +#ifdef MOD_PERSO + if( !GetPuttyFlag() ) { + /* + * Brad's The Window/Position & Icon panel. + */ + /* BKG */ + s = ctrl_getset(b, "Window/Appearance", "position", + "Remember Window Position"); + /**/ + ctrl_checkbox(s, "Remember Window Positions", NO_SHORTCUT, + HELPCTX(no_help), + conf_checkbox_handler, I(CONF_save_windowpos) ) ; + ctrl_editbox(s, "Top:", NO_SHORTCUT, 20, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_ypos), I(-1) ) ; + ctrl_editbox(s, "Left:", NO_SHORTCUT, 20, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_xpos), I(-1) ) ; + } + + if( !GetPuttyFlag() && (GetIconeFlag()>0) ) { + s = ctrl_getset(b, "Window/Appearance", "icon", + "Define the window icon"); + c = ctrl_editbox(s, "Icon (from internal resources)", NO_SHORTCUT, 40, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_icone), I(-1) ) ; + ctrl_filesel(s, "External icon file:", NO_SHORTCUT, + FILTER_ICON_FILES, FALSE, "Select icon file", + HELPCTX(no_help), + conf_filesel_handler, I(CONF_iconefile) ) ; + + } +#endif +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + if( !GetPuttyFlag() && GetBackgroundImageFlag() ) { + /* + * The Window/Background panel. + * (This would really belong in Appearance but we overflowed -- to much + * stuff on one page otherwise). + */ + str = dupprintf("Configure the background of %s's window", appname); + ctrl_settitle(b, "Window/Back.&Image", str); + sfree(str); + + s = ctrl_getset(b, "Window/Back.&Image", "bg_style", + "Background settings"); + ctrl_radiobuttons(s, "Background Style:", NO_SHORTCUT, 3, + HELPCTX(no_help), + conf_radiobutton_handler, + I(CONF_bg_type), + "Solid", NO_SHORTCUT, I(0), // TODO: Define shortcuts for these. + "Desktop", NO_SHORTCUT, I(1), + "Image", NO_SHORTCUT, I(2), + NULL); + + s = ctrl_getset(b, "Window/Back.&Image", "bg_wp_img_settings", + "Desktop and image settings"); + ctrl_editbox(s, "Opacity: (negative with Image for gradient)", NO_SHORTCUT, 20, + HELPCTX(no_help), + conf_editbox_handler, + I(CONF_bg_opacity), I(-1) ) ; + ctrl_editbox(s, "Slideshow:", NO_SHORTCUT, 20, + HELPCTX(no_help), + conf_editbox_handler, + I(CONF_bg_slideshow), I(-1) ) ; + + s = ctrl_getset(b, "Window/Back.&Image", "bg_img_settings", + "Image settings"); + ctrl_filesel(s, "Image file: (or #RRGGBB for gradient)", NO_SHORTCUT, + FILTER_IMAGE_FILES, FALSE, "Select background image file", + HELPCTX(no_help), + conf_filesel_handler, I(CONF_bg_image_filename) ); + ctrl_radiobuttons(s, "Image placement:", NO_SHORTCUT, 3, + HELPCTX(no_help), + conf_radiobutton_handler, + I(CONF_bg_image_style), + "Tile", NO_SHORTCUT, I(0), // TODO: Define shortcuts for these. + "Center", NO_SHORTCUT, I(1), + "Stretch", NO_SHORTCUT, I(2), + "Absolute (X,Y)", NO_SHORTCUT, I(3), + "Blank back.", NO_SHORTCUT, I(4), + "Stretch+", NO_SHORTCUT, I(5), + NULL); + + ctrl_editbox(s, "Absolute Left (X):", NO_SHORTCUT, 20, + HELPCTX(no_help), + conf_editbox_handler, + I(CONF_bg_image_abs_x), I(-1) ) ; + ctrl_editbox(s, "Absolute Top (Y):", NO_SHORTCUT, 20, + HELPCTX(no_help), + conf_editbox_handler, + I(CONF_bg_image_abs_y), I(-1) ) ; + ctrl_radiobuttons(s, "Image placement is relative to:", NO_SHORTCUT, 2, + HELPCTX(no_help), + conf_radiobutton_handler, + I(CONF_bg_image_abs_fixed), + "Desktop", NO_SHORTCUT, I(0), // TODO: Define shortcuts for these. + "Terminal Window", NO_SHORTCUT, I(1), + NULL); + + } +#endif +#ifdef MOD_PERSO + static char transTitle[256]="Window/Transparency" ; + if( !GetPuttyFlag() ) { + if( GetTransparencyFlag() ) ctrl_settitle(b, transTitle, "Options controlling transparency"); + + } + + if( !GetPuttyFlag() && GetTransparencyFlag() ) { + s = ctrl_getset(b, transTitle, "bg_transparency", + "Transparency setting"); + ctrl_editbox(s, "Transparency:", NO_SHORTCUT, 20, + HELPCTX(no_help), + conf_editbox_handler, + I(CONF_transparencynumber), I(-1) ) ; + ctrl_text(s, " from 0 (visible) to 255 (transparent)", HELPCTX(no_help)); + ctrl_text(s, " -1 to disable completely", HELPCTX(no_help)); + } +#endif + + /* + * The Window/Behaviour panel. + */ + str = dupprintf("Configure the behaviour of %s's window", appname); + ctrl_settitle(b, "Window/Behaviour", str); + sfree(str); + + s = ctrl_getset(b, "Window/Behaviour", "title", + "Adjust the behaviour of the window title"); + ctrl_editbox(s, "Window title:", 't', 100, + HELPCTX(appearance_title), + conf_editbox_handler, I(CONF_wintitle), I(1)); +#ifdef MOD_PERSO +if( !GetPuttyFlag() ) { + ctrl_text(s, " %%f: folder name", HELPCTX(appearance_title)); + ctrl_text(s, " %%h: hostname", HELPCTX(appearance_title)); + ctrl_text(s, " %%p: port number", HELPCTX(appearance_title)); + ctrl_text(s, " %%P: protocol name", HELPCTX(appearance_title)); + ctrl_text(s, " %%s: session name", HELPCTX(appearance_title)); + ctrl_text(s, " %%u: username", HELPCTX(appearance_title)); + ctrl_text(s, " %%l: forwarded local ports list", HELPCTX(appearance_title)); + ctrl_text(s, " %%d: forwarded dynamic ports list", HELPCTX(appearance_title)); +} +#endif + 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)); +#ifdef MOD_DISABLEALTGR +if( !GetPuttyFlag() ) { + ctrl_checkbox(s, "Disable AltGr menu", NO_SHORTCUT, + HELPCTX(no_help), + conf_checkbox_handler, I(CONF_disablealtgr)); +} +#endif + + /* + * The Window/Translation panel. + */ + ctrl_settitle(b, "Window/Translation", + "Options controlling character set translation"); + + s = ctrl_getset(b, "Window/Translation", "trans", + "Character set translation"); + ctrl_combobox(s, "Remote character set:", + 'r', 100, HELPCTX(translation_codepage), + codepage_handler, P(NULL), P(NULL)); + + s = ctrl_getset(b, "Window/Translation", "tweaks", NULL); + ctrl_checkbox(s, "Treat CJK ambiguous characters as wide", 'w', + HELPCTX(translation_cjk_ambig_wide), + conf_checkbox_handler, I(CONF_cjk_ambig_wide)); + + str = dupprintf("Adjust how %s handles line drawing characters", appname); + s = ctrl_getset(b, "Window/Translation", "linedraw", str); + sfree(str); + ctrl_radiobuttons(s, "Handling of line drawing characters:", NO_SHORTCUT,1, + HELPCTX(translation_linedraw), + conf_radiobutton_handler, + I(CONF_vtmode), + "Use Unicode line drawing code points",'u',I(VT_UNICODE), + "Poor man's line drawing (+, - and |)",'p',I(VT_POORMAN), + NULL); + ctrl_checkbox(s, "Copy and paste line drawing characters as lqqqk",'d', + HELPCTX(selection_linedraw), + conf_checkbox_handler, I(CONF_rawcnp)); + 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), NULL); + + 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->generic.column = 0; + ccd->button = ctrl_pushbutton(s, "Set", 's', + HELPCTX(copy_charclasses), + charclass_handler, P(ccd)); + ccd->button->generic.column = 1; + ctrl_columns(s, 1, 100); + + /* + * The Window/Colours panel. + */ + ctrl_settitle(b, "Window/Colours", "Options controlling use of colours"); + + s = ctrl_getset(b, "Window/Colours", "general", + "General options for colour usage"); + ctrl_checkbox(s, "Allow terminal to specify ANSI colours", 'i', + HELPCTX(colours_ansi), + conf_checkbox_handler, I(CONF_ansi_colour)); + ctrl_checkbox(s, "Allow terminal to use xterm 256-colour mode", '2', + HELPCTX(colours_xterm256), conf_checkbox_handler, + I(CONF_xterm_256_colour)); + ctrl_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), + NULL); + +#ifdef MOD_TUTTYCOLOR + cd = (struct colour_data *) ctrl_alloc(b, sizeof(struct colour_data)); + memset(cd , 0, sizeof(*cd )); + cd->bold_checkbox = + ctrl_checkbox(s, "Bolded text is a different colour", NO_SHORTCUT, + HELPCTX(no_help), colour_handler, P(cd)); + cd->underline_checkbox = + ctrl_checkbox(s, "Underlined text is a different colour", NO_SHORTCUT, + HELPCTX(no_help), colour_handler, P(cd)); + cd->selected_checkbox = + ctrl_checkbox(s, "Selected text is a different colour", NO_SHORTCUT, + HELPCTX(no_help), colour_handler, P(cd)); +#endif + + 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); +#ifndef MOD_TUTTYCOLOR + cd = (struct colour_data *)ctrl_alloc(b, sizeof(struct colour_data)); +#endif + cd->listbox = ctrl_listbox(s, "Select a colour to adjust:", 'u', + HELPCTX(colours_config), colour_handler, P(cd)); + cd->listbox->generic.column = 0; + cd->listbox->listbox.height = 7; + c = ctrl_text(s, "RGB value:", HELPCTX(colours_config)); + c->generic.column = 1; + cd->redit = ctrl_editbox(s, "Red", 'r', 50, HELPCTX(colours_config), + colour_handler, P(cd), P(NULL)); + cd->redit->generic.column = 1; + cd->gedit = ctrl_editbox(s, "Green", 'n', 50, HELPCTX(colours_config), + colour_handler, P(cd), P(NULL)); + cd->gedit->generic.column = 1; + cd->bedit = ctrl_editbox(s, "Blue", 'e', 50, HELPCTX(colours_config), + colour_handler, P(cd), P(NULL)); + cd->bedit->generic.column = 1; + cd->button = ctrl_pushbutton(s, "Modify", 'm', HELPCTX(colours_config), + colour_handler, P(cd)); + cd->button->generic.column = 1; + ctrl_columns(s, 1, 100); + + /* + * The Connection panel. This doesn't show up if we're in a + * non-network utility such as pterm. We tell this by being + * passed a protocol < 0. + */ + if (protocol >= 0) { + ctrl_settitle(b, "Connection", "Options controlling the connection"); + + s = ctrl_getset(b, "Connection", "keepalive", + "Sending of null packets to keep session active"); + ctrl_editbox(s, "Seconds between keepalives (0 to turn off)", 'k', 20, + HELPCTX(connection_keepalive), + conf_editbox_handler, I(CONF_ping_interval), + I(-1)); +#ifdef MOD_PERSO + ctrl_editbox(s, "Anti-idle string", NO_SHORTCUT, 50, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_antiidle), I(1) ) ; +#endif + + if (!midsession) { + s = ctrl_getset(b, "Connection", "tcp", + "Low-level TCP connection options"); + ctrl_checkbox(s, "Disable Nagle's algorithm (TCP_NODELAY option)", + 'n', HELPCTX(connection_nodelay), + conf_checkbox_handler, + I(CONF_tcp_nodelay)); + ctrl_checkbox(s, "Enable TCP keepalives (SO_KEEPALIVE option)", + 'p', HELPCTX(connection_tcpkeepalive), + conf_checkbox_handler, + I(CONF_tcp_keepalives)); +#ifndef NO_IPV6 + s = ctrl_getset(b, "Connection", "ipversion", + "Internet protocol version"); + ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, + HELPCTX(connection_ipversion), + conf_radiobutton_handler, + I(CONF_addressfamily), + "Auto", 'u', I(ADDRTYPE_UNSPEC), + "IPv4", '4', I(ADDRTYPE_IPV4), + "IPv6", '6', I(ADDRTYPE_IPV6), + NULL); +#endif + +#ifdef MOD_RECONNECT + if( !GetPuttyFlag() && GetAutoreconnectFlag() ) { + s = ctrl_getset(b, "Connection", "reconnect", "Reconnect options"); + ctrl_checkbox(s, "Attempt to reconnect on system wakeup", NO_SHORTCUT, HELPCTX(no_help), conf_checkbox_handler, I(CONF_wakeup_reconnect)) ; + ctrl_checkbox(s, "Attempt to reconnect on connection failure", NO_SHORTCUT, HELPCTX(no_help), conf_checkbox_handler, I(CONF_failure_reconnect)) ; + } +#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), I(1)); + } +#ifdef MOD_PORTKNOCKING + // port knocking panel + if( !GetPuttyFlag() ) { +// ctrl_settitle(b, "Connection/Port knocking", "Options controlling port knocking") ; + s = ctrl_getset(b, "Connection", "PortKnocking", + "Port knocking sequence"); + ctrl_editbox(s, "Sequence:", NO_SHORTCUT, 100, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_portknockingoptions), I(1)); + ctrl_text(s, "The sequence is a list of port:protocol separated by comma. Valid protocols are tcp and udp.",HELPCTX(no_help)); + ctrl_text(s, "Special protocol 's' is used to include pause between knocks.",HELPCTX(no_help)); + ctrl_text(s, "Ex: 2001:tcp, 1:s, 2002:udp",HELPCTX(no_help)); + } +#endif + + } + + /* + * A sub-panel Connection/Data, containing options that + * decide on data to send to the server. + */ + if (!midsession) { + ctrl_settitle(b, "Connection/Data", "Data to send to the server"); + + s = ctrl_getset(b, "Connection/Data", "login", + "Login details"); + ctrl_editbox(s, "Auto-login username", 'u', 50, + HELPCTX(connection_username), + conf_editbox_handler, I(CONF_username), I(1)); + { + /* We assume the local username is sufficiently stable + * to include on the dialog box. */ + char *user = get_username(); + char *userlabel = dupprintf("Use system username (%s)", + user ? user : ""); + sfree(user); + ctrl_radiobuttons(s, "When username is not specified:", 'n', 4, + HELPCTX(connection_username_from_env), + conf_radiobutton_bool_handler, + I(CONF_username_from_env), + "Prompt", I(false), + userlabel, I(true), + NULL); + sfree(userlabel); + } + +#ifdef MOD_PERSO + if( !GetPuttyFlag() ) { +#ifndef MOD_NOPASSWORD + c = ctrl_editbox(s, "Auto-login password", NO_SHORTCUT, 50, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_password), I(1) ) ; + //if( !debug_flag ) + c->editbox.password = 1; +#endif + ctrlAutoCommandContentEdit = ctrl_editbox(s, "Command", NO_SHORTCUT, 74, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_autocommand), I(1) ) ; + ctrl_filesel(s, "Auto-command/Login script file:", NO_SHORTCUT, + "Auto-command Files (*.sh)\0*.sh\0Login script Files (*.scr, *.txt)\0*.scr;*.txt\0All Files (*.*)\0*\0\0\0" + ,FALSE, "Select the login script file to load", + HELPCTX(no_help), + conf_scriptfilesel_handler, I(CONF_scriptfile) ) ; + ctrlScriptFileContentEdit = ctrl_editbox(s, "Login script content:", NO_SHORTCUT, 60, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_scriptfilecontent), I(1) ) ; + } +#endif + + s = ctrl_getset(b, "Connection/Data", "term", + "Terminal details"); + ctrl_editbox(s, "Terminal-type string", 't', 50, + HELPCTX(connection_termtype), + conf_editbox_handler, I(CONF_termtype), I(1)); + ctrl_editbox(s, "Terminal speeds", 's', 50, + HELPCTX(connection_termspeed), + conf_editbox_handler, I(CONF_termspeed), I(1)); + + s = ctrl_getset(b, "Connection/Data", "env", + "Environment variables"); + ctrl_columns(s, 2, 80, 20); + ed = (struct environ_data *) + ctrl_alloc(b, sizeof(struct environ_data)); + ed->varbox = ctrl_editbox(s, "Variable", 'v', 60, + HELPCTX(telnet_environ), + environ_handler, P(ed), P(NULL)); + ed->varbox->generic.column = 0; + ed->valbox = ctrl_editbox(s, "Value", 'l', 60, + HELPCTX(telnet_environ), + environ_handler, P(ed), P(NULL)); + ed->valbox->generic.column = 0; + ed->addbutton = ctrl_pushbutton(s, "Add", 'd', + HELPCTX(telnet_environ), + environ_handler, P(ed)); + ed->addbutton->generic.column = 1; + ed->rembutton = ctrl_pushbutton(s, "Remove", 'r', + HELPCTX(telnet_environ), + environ_handler, P(ed)); + ed->rembutton->generic.column = 1; + ctrl_columns(s, 1, 100); + ed->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(telnet_environ), + environ_handler, P(ed)); + ed->listbox->listbox.height = 3; + ed->listbox->listbox.ncols = 2; + ed->listbox->listbox.percentages = snewn(2, int); + ed->listbox->listbox.percentages[0] = 30; + ed->listbox->listbox.percentages[1] = 70; + } + + } + +#ifdef MOD_PROXY + if (!midsession || debug_flag ) { +#else + if (!midsession) { +#endif + /* + * The Connection/Proxy panel. + */ + ctrl_settitle(b, "Connection/Proxy", + "Options controlling proxy usage"); + + s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); + ctrl_radiobuttons(s, "Proxy type:", 't', 3, + HELPCTX(proxy_type), + conf_radiobutton_handler, + I(CONF_proxy_type), + "None", I(PROXY_NONE), + "SOCKS 4", I(PROXY_SOCKS4), + "SOCKS 5", I(PROXY_SOCKS5), + "HTTP", I(PROXY_HTTP), + "Telnet", I(PROXY_TELNET), + NULL); + ctrl_columns(s, 2, 80, 20); + c = ctrl_editbox(s, "Proxy hostname", 'y', 100, + HELPCTX(proxy_main), + conf_editbox_handler, + I(CONF_proxy_host), I(1)); + c->generic.column = 0; + c = ctrl_editbox(s, "Port", 'p', 100, + HELPCTX(proxy_main), + conf_editbox_handler, + I(CONF_proxy_port), + I(-1)); + c->generic.column = 1; + ctrl_columns(s, 1, 100); + ctrl_editbox(s, "Exclude Hosts/IPs", 'e', 100, + HELPCTX(proxy_exclude), + conf_editbox_handler, + I(CONF_proxy_exclude_list), I(1)); + ctrl_checkbox(s, "Consider proxying local host connections", 'x', + HELPCTX(proxy_exclude), + conf_checkbox_handler, + I(CONF_even_proxy_localhost)); + ctrl_radiobuttons(s, "Do DNS name lookup at proxy end:", 'd', 3, + HELPCTX(proxy_dns), + conf_radiobutton_handler, + I(CONF_proxy_dns), + "No", I(FORCE_OFF), + "Auto", I(AUTO), + "Yes", I(FORCE_ON), NULL); + ctrl_editbox(s, "Username", 'u', 60, + HELPCTX(proxy_auth), + conf_editbox_handler, + I(CONF_proxy_username), I(1)); + c = ctrl_editbox(s, "Password", 'w', 60, + HELPCTX(proxy_auth), + conf_editbox_handler, + I(CONF_proxy_password), I(1)); + c->editbox.password = true; + ctrl_editbox(s, "Telnet command", 'm', 100, + HELPCTX(proxy_command), + conf_editbox_handler, + I(CONF_proxy_telnet_command), I(1)); + + 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), NULL); + } + + /* + * 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), I(1)); + + s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); + ctrl_checkbox(s, "Don't start a shell or command at all", 'n', + HELPCTX(ssh_noshell), + conf_checkbox_handler, + I(CONF_ssh_no_shell)); + } + + if (!midsession || !(protcfginfo == 1 || protcfginfo == -1)) { + s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); + + ctrl_checkbox(s, "Enable compression", 'e', + HELPCTX(ssh_compress), + conf_checkbox_handler, + I(CONF_compression)); + } + + if (!midsession) { + s = ctrl_getset(b, "Connection/SSH", "sharing", "Sharing an SSH connection between PuTTY tools"); + + ctrl_checkbox(s, "Share SSH connections if possible", 's', + HELPCTX(ssh_share), + conf_checkbox_handler, + I(CONF_ssh_connection_sharing)); + + ctrl_text(s, "Permitted roles in a shared connection:", + HELPCTX(ssh_share)); + ctrl_checkbox(s, "Upstream (connecting to the real server)", 'u', + HELPCTX(ssh_share), + conf_checkbox_handler, + I(CONF_ssh_connection_sharing_upstream)); + ctrl_checkbox(s, "Downstream (connecting to the upstream PuTTY)", 'd', + HELPCTX(ssh_share), + conf_checkbox_handler, + I(CONF_ssh_connection_sharing_downstream)); + } + + if (!midsession) { + s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options"); + + ctrl_radiobuttons(s, "SSH protocol version:", NO_SHORTCUT, 2, + HELPCTX(ssh_protocol), + conf_radiobutton_handler, + I(CONF_sshprot), + "2", '2', I(3), + "1 (INSECURE)", '1', I(0), NULL); + } + + /* + * The Connection/SSH/Kex panel. (Owing to repeat key + * exchange, much of this is meaningful in mid-session _if_ + * we're using SSH-2 and are not a connection-sharing + * downstream, or haven't decided yet.) + */ + if (protcfginfo != 1 && protcfginfo != -1) { + ctrl_settitle(b, "Connection/SSH/Kex", + "Options controlling SSH key exchange"); + + s = ctrl_getset(b, "Connection/SSH/Kex", "main", + "Key exchange algorithm options"); + c = ctrl_draglist(s, "Algorithm selection policy:", 's', + HELPCTX(ssh_kexlist), + kexlist_handler, P(NULL)); + c->listbox.height = 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), + I(-1)); +#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), + I(-1)); +#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), + I(16)); + 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->generic.column = 0; + /* You want to select from the list, _then_ hit Remove. So + * tab order should be that way round. */ + mh = (struct manual_hostkey_data *) + ctrl_alloc(b,sizeof(struct manual_hostkey_data)); + mh->rembutton = ctrl_pushbutton(s, "Remove", 'r', + HELPCTX(ssh_kex_manual_hostkeys), + manual_hostkey_handler, P(mh)); + mh->rembutton->generic.column = 1; + mh->rembutton->generic.tabdelay = 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->generic.column = 0; + mh->addbutton = ctrl_pushbutton(s, "Add key", 'y', + HELPCTX(ssh_kex_manual_hostkeys), + manual_hostkey_handler, P(mh)); + mh->addbutton->generic.column = 1; + ctrl_columns(s, 1, 100); + } + + if (!midsession || !(protcfginfo == 1 || protcfginfo == -1)) { + /* + * The Connection/SSH/Cipher panel. + */ + ctrl_settitle(b, "Connection/SSH/Cipher", + "Options controlling SSH encryption"); + + s = ctrl_getset(b, "Connection/SSH/Cipher", + "encryption", "Encryption options"); + c = ctrl_draglist(s, "Encryption cipher selection policy:", 's', + HELPCTX(ssh_ciphers), + cipherlist_handler, P(NULL)); + c->listbox.height = 6; + + ctrl_checkbox(s, "Enable legacy use of single-DES in SSH-2", 'i', + HELPCTX(ssh_ciphers), + conf_checkbox_handler, + I(CONF_ssh2_des_cbc)); + } + + if (!midsession) { + + /* + * The Connection/SSH/Auth panel. + */ + ctrl_settitle(b, "Connection/SSH/Auth", + "Options controlling SSH authentication"); + + s = ctrl_getset(b, "Connection/SSH/Auth", "main", NULL); + ctrl_checkbox(s, "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", "params", + "Authentication parameters"); + ctrl_checkbox(s, "Allow agent forwarding", 'f', + HELPCTX(ssh_auth_agentfwd), + conf_checkbox_handler, I(CONF_agentfwd)); + ctrl_checkbox(s, "Allow attempted changes of username in SSH-2", NO_SHORTCUT, + HELPCTX(ssh_auth_changeuser), + conf_checkbox_handler, + I(CONF_change_username)); + ctrl_filesel(s, "Private key file for authentication:", 'k', + FILTER_KEY_FILES, false, "Select private key file", + HELPCTX(ssh_auth_privkey), + conf_filesel_handler, I(CONF_keyfile)); + +#ifndef NO_GSSAPI + /* + * Connection/SSH/Auth/GSSAPI, which sadly won't fit on + * the main Auth panel. + */ + ctrl_settitle(b, "Connection/SSH/Auth/GSSAPI", + "Options controlling GSSAPI authentication"); + s = ctrl_getset(b, "Connection/SSH/Auth/GSSAPI", "gssapi", NULL); + + ctrl_checkbox(s, "Attempt GSSAPI authentication (SSH-2 only)", + 't', HELPCTX(ssh_gssapi), + conf_checkbox_handler, + I(CONF_try_gssapi_auth)); + + ctrl_checkbox(s, "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->generic.column = 0; + td->setbutton = ctrl_pushbutton(s, "Set", 's', + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td)); + td->setbutton->generic.column = 1; + td->setbutton->generic.tabdelay = 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), + NULL); + td->valradio->generic.column = 0; + td->valbox = ctrl_editbox(s, NULL, NO_SHORTCUT, 100, + HELPCTX(ssh_ttymodes), + ttymodes_handler, P(td), P(NULL)); + td->valbox->generic.column = 1; + td->valbox->generic.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), I(1)); + ctrl_radiobuttons(s, "Remote X11 authentication protocol", 'u', 2, + HELPCTX(ssh_tunnels_x11auth), + conf_radiobutton_handler, + I(CONF_x11_auth), + "MIT-Magic-Cookie-1", I(X11_MIT), + "XDM-Authorization-1", I(X11_XDM), NULL); + } + + /* + * The Tunnels panel _is_ still available in mid-session. + */ + ctrl_settitle(b, "Connection/SSH/Tunnels", + "Options controlling SSH port forwarding"); + + s = ctrl_getset(b, "Connection/SSH/Tunnels", "portfwd", + "Port forwarding"); + ctrl_checkbox(s, "Local ports accept connections from other hosts",'t', + HELPCTX(ssh_tunnels_portfwd_localhost), + conf_checkbox_handler, + I(CONF_lport_acceptall)); + ctrl_checkbox(s, "Remote ports do the same (SSH-2 only)", 'p', + HELPCTX(ssh_tunnels_portfwd_localhost), + conf_checkbox_handler, + I(CONF_rport_acceptall)); +#ifdef MOD_PERSO + ctrl_checkbox(s, "Print Dynamic ports in window title",NO_SHORTCUT, + HELPCTX(no_help), + conf_checkbox_handler, + I(CONF_ssh_tunnel_print_in_title)); +#endif + + ctrl_columns(s, 3, 55, 20, 25); + c = ctrl_text(s, "Forwarded ports:", HELPCTX(ssh_tunnels_portfwd)); + c->generic.column = COLUMN_FIELD(0,2); + /* You want to select from the list, _then_ hit Remove. So tab order + * should be that way round. */ + pfd = (struct portfwd_data *)ctrl_alloc(b,sizeof(struct portfwd_data)); + pfd->rembutton = ctrl_pushbutton(s, "Remove", 'r', + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd)); + pfd->rembutton->generic.column = 2; + pfd->rembutton->generic.tabdelay = true; + pfd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT, + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd)); +#ifdef MOD_PERSO + pfd->listbox->listbox.height = 12 ; +#else + pfd->listbox->listbox.height = 3; +#endif + pfd->listbox->listbox.ncols = 2; + pfd->listbox->listbox.percentages = snewn(2, int); + pfd->listbox->listbox.percentages[0] = 20; + pfd->listbox->listbox.percentages[1] = 80; + ctrl_tabdelay(s, pfd->rembutton); + ctrl_text(s, "Add new forwarded port:", HELPCTX(ssh_tunnels_portfwd)); + /* You want to enter source, destination and type, _then_ hit Add. + * Again, we adjust the tab order to reflect this. */ + pfd->addbutton = ctrl_pushbutton(s, "Add", 'd', + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd)); + pfd->addbutton->generic.column = 2; + pfd->addbutton->generic.tabdelay = true; + pfd->sourcebox = ctrl_editbox(s, "Source port", 's', 40, + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd), P(NULL)); + pfd->sourcebox->generic.column = 0; + pfd->destbox = ctrl_editbox(s, "Destination", 'i', 67, + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd), P(NULL)); + pfd->direction = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, + HELPCTX(ssh_tunnels_portfwd), + portfwd_handler, P(pfd), + "Local", 'l', P(NULL), + "Remote", 'm', P(NULL), + "Dynamic", 'y', P(NULL), + NULL); +#ifndef NO_IPV6 + pfd->addressfamily = + ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 3, + HELPCTX(ssh_tunnels_portfwd_ipversion), + portfwd_handler, P(pfd), + "Auto", 'u', I(ADDRTYPE_UNSPEC), + "IPv4", '4', I(ADDRTYPE_IPV4), + "IPv6", '6', I(ADDRTYPE_IPV6), + NULL); +#endif + ctrl_tabdelay(s, pfd->addbutton); + ctrl_columns(s, 1, 100); + + if (!midsession) { + /* + * The Connection/SSH/Bugs panels. + */ + ctrl_settitle(b, "Connection/SSH/Bugs", + "Workarounds for SSH server bugs"); + + s = ctrl_getset(b, "Connection/SSH/Bugs", "main", + "Detection of known bugs in SSH servers"); + ctrl_droplist(s, "Chokes on SSH-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)); + + 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)); + } + +#ifdef MOD_PERSO + if( !GetPuttyFlag() ) { + ctrl_settitle(b, "Connection/SSH/PSCP and WinSCP", + "PSCP and WinSCP integration"); + + s = ctrl_getset(b, "Connection/SSH/PSCP and WinSCP", "winSCPproto", "General protocol setting") ; + ctrl_radiobuttons(s, "Prefered protocol:", NO_SHORTCUT, 4, + HELPCTX(no_help), + conf_radiobutton_handler, + I(CONF_winscpprot), + "scp", NO_SHORTCUT, I(0), + "sftp", NO_SHORTCUT, I(1), + "ftp", NO_SHORTCUT, I(2), + "ftps", NO_SHORTCUT, I(3), + "ftpes", NO_SHORTCUT, I(4), + "http", NO_SHORTCUT, I(5), + "https", NO_SHORTCUT, I(6), + NULL); + + s = ctrl_getset(b, "Connection/SSH/PSCP and WinSCP", "pscp", "PSCP integration") ; + ctrl_checkbox(s, "Send file in current directory", NO_SHORTCUT, + HELPCTX(no_help), + conf_checkbox_handler, + I(CONF_scp_auto_pwd)); + ctrl_editbox(s, "Remote directory (exclusive with previous setting)", NO_SHORTCUT, 100, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_pscpremotedir), + I(1)); + ctrl_editbox(s, "PSCP options", NO_SHORTCUT, 100, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_pscpoptions), + I(1)); + + s = ctrl_getset(b, "Connection/SSH/PSCP and WinSCP", "WinSCP", "WinSCP integration") ; + ctrl_editbox(s, "SFTP connect ([user@]hostname[:port])", NO_SHORTCUT, 100, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_sftpconnect), + I(1)); + ctrl_editbox(s, "WinSCP additional options", NO_SHORTCUT, 100, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_winscpoptions), + I(1)); + ctrl_editbox(s, "WinSCP additional rawsettings", NO_SHORTCUT, 100, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_winscprawsettings), + I(1)); + ctrl_editbox(s, "Shell (scp mode only)", NO_SHORTCUT, 100, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_pscpshell), + I(1)); + } +#endif + + } +#ifdef MOD_ZMODEM + // z-modem panel + if( (!GetPuttyFlag())&&GetZModemFlag() ) { + ctrl_settitle(b, "Connection/ZModem", + "Options controlling Z Modem transfers"); + s = ctrl_getset(b, "Connection/ZModem", "download", + "Download folder"); + ctrl_editbox(s, "Location:", NO_SHORTCUT, 100, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_zdownloaddir), I(1)); + + ctrl_settitle(b, "Connection/ZModem/rz", + "rz path and options"); + + s = ctrl_getset(b, "Connection/ZModem/rz", "receive", + "Receive command"); + + ctrl_filesel(s, "Command rz:", NO_SHORTCUT, + FILTER_EXE_FILES, FALSE, "Select command to receive zmodem data", + HELPCTX(no_help), + conf_filesel_handler, I(CONF_rzcommand) ) ; + + ctrl_editbox(s, "Options", NO_SHORTCUT, + 50, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_rzoptions), I(1)); + ctrl_text(s, "Ctrl+X to quit rz before completing", + HELPCTX(no_help)); + + ctrl_settitle(b, "Connection/ZModem/sz", + "sz path and options"); + + s = ctrl_getset(b, "Connection/ZModem/sz", "send", + "Send command"); + + ctrl_filesel(s, "Command sz:", NO_SHORTCUT, + FILTER_EXE_FILES, FALSE, "Select command to send zmodem data", + HELPCTX(no_help), + conf_filesel_handler, I(CONF_szcommand) ) ; + + ctrl_editbox(s, "Options", NO_SHORTCUT, + 50, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_szoptions), I(1)); +/**/ + } +#endif +#ifdef MOD_PERSO + if( !GetPuttyFlag() ) { + ctrl_settitle(b, "Comment", "Options comments"); + s = ctrl_getset(b, "Comment", "freecomment", + "Now you can add comments on your session"); + ctrl_editbox(s, "Comment", NO_SHORTCUT, 100, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_comment), + I(1)); + } +#endif + 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), I(1)); + } + + s = ctrl_getset(b, "Connection/Serial", "sercfg", "Configure the serial line"); + ctrl_editbox(s, "Speed (baud)", 's', 40, + HELPCTX(serial_speed), + conf_editbox_handler, I(CONF_serspeed), I(-1)); + ctrl_editbox(s, "Data bits", 'b', 40, + HELPCTX(serial_databits), + conf_editbox_handler, I(CONF_serdatabits), I(-1)); + /* + * Stop bits come in units of one half. + */ + ctrl_editbox(s, "Stop bits", 't', 40, + HELPCTX(serial_stopbits), + conf_editbox_handler, I(CONF_serstopbits), I(-2)); + ctrl_droplist(s, "Parity", 'p', 40, + HELPCTX(serial_parity), serial_parity_handler, + I(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), NULL); + 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), NULL); + } + ctrl_checkbox(s, "Keyboard sends Telnet special commands", 'k', + HELPCTX(telnet_specialkeys), + conf_checkbox_handler, + I(CONF_telnet_keyboard)); + ctrl_checkbox(s, "Return key sends Telnet New Line instead of ^M", + 'm', HELPCTX(telnet_newline), + conf_checkbox_handler, + I(CONF_telnet_newline)); + } + + if (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), I(1)); + + } + + 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), + I(1)); + + 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), NULL); + + 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)); + } +} + +#ifdef MOD_ZMODEM +/* + * The standard directory-selector handler expects the main `context' + * field to contain the `offsetof' a Filename field in the + * structure pointed to by `data'. + */ +void conf_directorysel_handler(union control *ctrl, void *dlg, void *data, int event) { + /* + * The standard file-selector handler expects the `context' + * field to contain the `offsetof' a Filename field in the + * structure pointed to by `data'. + */ + int offset = ctrl->directoryselect.context.i; + + if (event == EVENT_REFRESH) { + dlg_directorysel_set(ctrl, dlg, *(Filename *)ATOFFSET(data, offset)); + } else if (event == EVENT_VALCHANGE) { + dlg_directorysel_get(ctrl, dlg, (Filename *)ATOFFSET(data, offset)); + } +} +#endif diff --git a/0.77_My_PuTTY/console.c b/0.77_My_PuTTY/console.c new file mode 100644 index 0000000..006b4e6 --- /dev/null +++ b/0.77_My_PuTTY/console.c @@ -0,0 +1,113 @@ +/* + * Common pieces between the platform console frontend modules. + */ + +#include +#include + +#include "putty.h" +#include "misc.h" +#include "console.h" + +const char hk_absentmsg_common_fmt[] = + "The server's host key is not cached. You have no guarantee\n" + "that the server is the computer you think it is.\n" + "The server's %s key fingerprint is:\n" + "%s\n"; +const char hk_absentmsg_interactive_intro[] = + "If you trust this host, enter \"y\" to add the key to\n" + "PuTTY's cache and carry on connecting.\n" + "If you want to carry on connecting just once, without\n" + "adding the key to the cache, enter \"n\".\n" + "If you do not trust this host, press Return to abandon the\n" + "connection.\n"; +const char hk_absentmsg_interactive_prompt[] = + "Store key in cache? (y/n, Return cancels connection, " + "i for more info) "; + +const char hk_wrongmsg_common_fmt[] = + "WARNING - POTENTIAL SECURITY BREACH!\n" + "The server's host key does not match the one PuTTY has\n" + "cached. This means that either the server administrator\n" + "has changed the host key, or you have actually connected\n" + "to another computer pretending to be the server.\n" + "The new %s key fingerprint is:\n" + "%s\n"; +const char hk_wrongmsg_interactive_intro[] = + "If you were expecting this change and trust the new key,\n" + "enter \"y\" to update PuTTY's cache and continue connecting.\n" + "If you want to carry on connecting but without updating\n" + "the cache, enter \"n\".\n" + "If you want to abandon the connection completely, press\n" + "Return to cancel. Pressing Return is the ONLY guaranteed\n" + "safe choice.\n"; +const char hk_wrongmsg_interactive_prompt[] = + "Update cached key? (y/n, Return cancels connection, " + "i for more info) "; + +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"; + +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/0.77_My_PuTTY/console.h b/0.77_My_PuTTY/console.h new file mode 100644 index 0000000..80b77bf --- /dev/null +++ b/0.77_My_PuTTY/console.h @@ -0,0 +1,17 @@ +/* + * Common pieces between the platform console frontend modules. + */ + +extern const char hk_absentmsg_common_fmt[]; +extern const char hk_absentmsg_interactive_intro[]; +extern const char hk_absentmsg_interactive_prompt[]; +extern const char hk_wrongmsg_common_fmt[]; +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/0.77_My_PuTTY/cproxy.c b/0.77_My_PuTTY/cproxy.c new file mode 100644 index 0000000..0c2e62f --- /dev/null +++ b/0.77_My_PuTTY/cproxy.c @@ -0,0 +1,179 @@ +/* + * 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" + +static void hmacmd5_chap(const unsigned char *challenge, int challen, + const char *passwd, unsigned char *response) +{ + mac_simple(&ssh_hmac_md5, ptrlen_from_asciz(passwd), + make_ptrlen(challenge, challen), response); +} + +void proxy_socks5_offerencryptedauth(BinarySink *bs) +{ + put_byte(bs, 0x03); /* CHAP */ +} + +int proxy_socks5_handlechap (ProxySocket *p) +{ + + /* CHAP authentication reply format: + * version number (1 bytes) = 1 + * number of commands (1 byte) + * + * For each command: + * command identifier (1 byte) + * data length (1 byte) + */ + unsigned char data[260]; + unsigned char outbuf[20]; + + while(p->chap_num_attributes == 0 || + p->chap_num_attributes_processed < p->chap_num_attributes) { + if (p->chap_num_attributes == 0 || + p->chap_current_attribute == -1) { + /* CHAP normally reads in two bytes, either at the + * beginning or for each attribute/value pair. But if + * we're waiting for the value's data, we might not want + * to read 2 bytes. + */ + + if (bufchain_size(&p->pending_input_data) < 2) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 2); + bufchain_consume(&p->pending_input_data, 2); + } + + if (p->chap_num_attributes == 0) { + /* If there are no attributes, this is our first msg + * with the server, where we negotiate version and + * number of attributes + */ + if (data[0] != 0x01) { + plug_closing(p->plug, "Proxy error: SOCKS proxy wants" + " a different CHAP version", + PROXY_ERROR_GENERAL, 0); + return 1; + } + if (data[1] == 0x00) { + plug_closing(p->plug, "Proxy error: SOCKS proxy won't" + " negotiate CHAP with us", + PROXY_ERROR_GENERAL, 0); + return 1; + } + p->chap_num_attributes = data[1]; + } else { + if (p->chap_current_attribute == -1) { + /* We have to read in each attribute/value pair - + * those we don't understand can be ignored, but + * there are a few we'll need to handle. + */ + p->chap_current_attribute = data[0]; + p->chap_current_datalen = data[1]; + } + if (bufchain_size(&p->pending_input_data) < + p->chap_current_datalen) + return 1; /* not got everything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, + p->chap_current_datalen); + + bufchain_consume(&p->pending_input_data, + p->chap_current_datalen); + + switch (p->chap_current_attribute) { + case 0x00: + /* Successful authentication */ + if (data[0] == 0x00) + p->state = 2; + else { + plug_closing(p->plug, "Proxy error: SOCKS proxy" + " refused CHAP authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + break; + case 0x03: + outbuf[0] = 0x01; /* Version */ + outbuf[1] = 0x01; /* One attribute */ + outbuf[2] = 0x04; /* Response */ + outbuf[3] = 0x10; /* Length */ + hmacmd5_chap(data, p->chap_current_datalen, + conf_get_str(p->conf, CONF_proxy_password), + &outbuf[4]); + sk_write(p->sub_socket, outbuf, 20); + break; + case 0x11: + /* Chose a protocol */ + if (data[0] != 0x85) { + plug_closing(p->plug, "Proxy error: Server chose " + "CHAP of other than HMAC-MD5 but we " + "didn't offer it!", + PROXY_ERROR_GENERAL, 0); + return 1; + } + break; + } + p->chap_current_attribute = -1; + p->chap_num_attributes_processed++; + } + if (p->state == 8 && + p->chap_num_attributes_processed >= p->chap_num_attributes) { + p->chap_num_attributes = 0; + p->chap_num_attributes_processed = 0; + p->chap_current_datalen = 0; + } + } + return 0; +} + +int proxy_socks5_selectchap(ProxySocket *p) +{ + char *username = conf_get_str(p->conf, CONF_proxy_username); + char *password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + char chapbuf[514]; + int ulen; + chapbuf[0] = '\x01'; /* Version */ + chapbuf[1] = '\x02'; /* Number of attributes sent */ + chapbuf[2] = '\x11'; /* First attribute - algorithms list */ + chapbuf[3] = '\x01'; /* Only one CHAP algorithm */ + chapbuf[4] = '\x85'; /* ...and it's HMAC-MD5, the core one */ + chapbuf[5] = '\x02'; /* Second attribute - username */ + + ulen = strlen(username); + if (ulen > 255) ulen = 255; + if (ulen < 1) ulen = 1; + + chapbuf[6] = ulen; + memcpy(chapbuf+7, username, ulen); + + sk_write(p->sub_socket, chapbuf, ulen + 7); + p->chap_num_attributes = 0; + p->chap_num_attributes_processed = 0; + p->chap_current_attribute = -1; + p->chap_current_datalen = 0; + + p->state = 8; + } else + plug_closing(p->plug, "Proxy error: Server chose " + "CHAP authentication but we didn't offer it!", + PROXY_ERROR_GENERAL, 0); + return 1; +} diff --git a/0.77_My_PuTTY/defs.h b/0.77_My_PuTTY/defs.h new file mode 100644 index 0000000..35661cb --- /dev/null +++ b/0.77_My_PuTTY/defs.h @@ -0,0 +1,221 @@ +/* + * 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 + +#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); +#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 Backend Backend; +typedef struct BackendVtable BackendVtable; + +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 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 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 dlgparam dlgparam; + +typedef struct settings_w settings_w; +typedef struct settings_r settings_r; +typedef struct settings_e settings_e; + +typedef struct SessionSpecial SessionSpecial; + +typedef struct StripCtrlChars StripCtrlChars; + +/* + * 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; + +/* 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 + +/* ---------------------------------------------------------------------- + * Platform-specific definitions. + * + * Most of these live in the per-platform header files, of which + * puttyps.h selects the appropriate one. But some of the sources + * (particularly standalone test applications) would prefer not to + * have to include a per-platform header at all, because that makes it + * more portable to platforms not supported by the code base as a + * whole (for example, compiling purely computational parts of the + * code for specialist platforms for test and analysis purposes). So + * any definition that has to affect even _those_ modules will have to + * go here, with the key constraint being that this code has to come + * to _some_ decision even if the compilation platform is not a + * recognised one at all. + */ + +/* Purely computational code uses smemclr(), so we have to make the + * decision here about whether that's provided by utils.c or by a + * platform implementation. We define PLATFORM_HAS_SMEMCLR to suppress + * utils.c's definition. */ +#ifdef _WINDOWS +/* Windows provides the API function 'SecureZeroMemory', which we use + * unless the user has told us not to by defining NO_SECUREZEROMEMORY. */ +#ifndef NO_SECUREZEROMEMORY +#define PLATFORM_HAS_SMEMCLR +#endif +#endif + +#endif /* PUTTY_DEFS_H */ diff --git a/0.77_My_PuTTY/dialog.c b/0.77_My_PuTTY/dialog.c new file mode 100644 index 0000000..9bf7bff --- /dev/null +++ b/0.77_My_PuTTY/dialog.c @@ -0,0 +1,485 @@ +/* + * 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 union control *ctrl_new(struct controlset *s, int type, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = snew(union control); + sgrowarray(s->ctrls, s->ctrlsize, s->ncontrols); + s->ctrls[s->ncontrols++] = c; + /* + * Fill in the standard fields. + */ + c->generic.type = type; + c->generic.tabdelay = false; + c->generic.column = COLUMN_FIELD(0, s->ncolumns); + c->generic.helpctx = helpctx; + c->generic.handler = handler; + c->generic.context = context; + c->generic.label = NULL; + c->generic.align_next_to = NULL; + return c; +} + +/* `ncolumns' is followed by that many percentages, as integers. */ +union control *ctrl_columns(struct controlset *s, int ncolumns, ...) +{ + union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL)); + assert(s->ncolumns == 1 || ncolumns == 1); + c->columns.ncols = ncolumns; + s->ncolumns = ncolumns; + if (ncolumns == 1) { + c->columns.percentages = NULL; + } else { + va_list ap; + int i; + c->columns.percentages = snewn(ncolumns, int); + va_start(ap, ncolumns); + for (i = 0; i < ncolumns; i++) + c->columns.percentages[i] = va_arg(ap, int); + va_end(ap); + } + return c; +} + +union control *ctrl_editbox(struct controlset *s, const char *label, + char shortcut, int percentage, + intorptr helpctx, handler_fn handler, + intorptr context, intorptr context2) +{ + union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); + c->editbox.label = label ? dupstr(label) : NULL; + c->editbox.shortcut = shortcut; + c->editbox.percentwidth = percentage; + c->editbox.password = false; + c->editbox.has_list = false; + c->editbox.context2 = context2; + return c; +} + +union control *ctrl_combobox(struct controlset *s, const char *label, + char shortcut, int percentage, + intorptr helpctx, handler_fn handler, + intorptr context, intorptr context2) +{ + union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context); + c->editbox.label = label ? dupstr(label) : NULL; + c->editbox.shortcut = shortcut; + c->editbox.percentwidth = percentage; + c->editbox.password = false; + c->editbox.has_list = true; + c->editbox.context2 = context2; + return c; +} + +/* + * `ncolumns' is followed by (alternately) radio button titles and + * intorptrs, until a NULL in place of a title string is seen. Each + * title is expected to be followed by a shortcut _iff_ `shortcut' + * is NO_SHORTCUT. + */ +union control *ctrl_radiobuttons(struct controlset *s, const char *label, + char shortcut, int ncolumns, intorptr helpctx, + handler_fn handler, intorptr context, ...) +{ + va_list ap; + int i; + union control *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context); + c->radio.label = label ? dupstr(label) : NULL; + c->radio.shortcut = shortcut; + c->radio.ncolumns = ncolumns; + /* + * Initial pass along variable argument list to count the + * buttons. + */ + va_start(ap, context); + i = 0; + while (va_arg(ap, char *) != NULL) { + i++; + if (c->radio.shortcut == NO_SHORTCUT) + (void)va_arg(ap, int); /* char promotes to int in arg lists */ + (void)va_arg(ap, intorptr); + } + va_end(ap); + c->radio.nbuttons = i; + if (c->radio.shortcut == NO_SHORTCUT) + c->radio.shortcuts = snewn(c->radio.nbuttons, char); + else + c->radio.shortcuts = NULL; + c->radio.buttons = snewn(c->radio.nbuttons, char *); + c->radio.buttondata = snewn(c->radio.nbuttons, intorptr); + /* + * Second pass along variable argument list to actually fill in + * the structure. + */ + va_start(ap, context); + for (i = 0; i < c->radio.nbuttons; i++) { + c->radio.buttons[i] = dupstr(va_arg(ap, char *)); + if (c->radio.shortcut == NO_SHORTCUT) + c->radio.shortcuts[i] = va_arg(ap, int); + /* char promotes to int in arg lists */ + c->radio.buttondata[i] = va_arg(ap, intorptr); + } + va_end(ap); + return c; +} + +union control *ctrl_pushbutton(struct controlset *s, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context) +{ + union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context); + c->button.label = label ? dupstr(label) : NULL; + c->button.shortcut = shortcut; + c->button.isdefault = false; + c->button.iscancel = false; + return c; +} + +union control *ctrl_listbox(struct controlset *s, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context) +{ + union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + c->listbox.label = label ? dupstr(label) : NULL; + c->listbox.shortcut = shortcut; + c->listbox.height = 5; /* *shrug* a plausible default */ + c->listbox.draglist = false; + c->listbox.multisel = 0; + c->listbox.percentwidth = 100; + c->listbox.ncols = 0; + c->listbox.percentages = NULL; + return c; +} + +union control *ctrl_droplist(struct controlset *s, const char *label, + char shortcut, int percentage, intorptr helpctx, + handler_fn handler, intorptr context) +{ + union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + c->listbox.label = label ? dupstr(label) : NULL; + c->listbox.shortcut = shortcut; + c->listbox.height = 0; /* means it's a drop-down list */ + c->listbox.draglist = false; + c->listbox.multisel = 0; + c->listbox.percentwidth = percentage; + c->listbox.ncols = 0; + c->listbox.percentages = NULL; + c->listbox.hscroll = false; + return c; +} + +union control *ctrl_draglist(struct controlset *s, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context) +{ + union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context); + c->listbox.label = label ? dupstr(label) : NULL; + c->listbox.shortcut = shortcut; + c->listbox.height = 5; /* *shrug* a plausible default */ + c->listbox.draglist = true; + c->listbox.multisel = 0; + c->listbox.percentwidth = 100; + c->listbox.ncols = 0; + c->listbox.percentages = NULL; + c->listbox.hscroll = false; + return c; +} + +union control *ctrl_filesel(struct controlset *s, const char *label, + char shortcut, const char *filter, bool write, + const char *title, intorptr helpctx, + handler_fn handler, intorptr context) +{ + union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context); + c->fileselect.label = label ? dupstr(label) : NULL; + c->fileselect.shortcut = shortcut; + c->fileselect.filter = filter; + c->fileselect.for_writing = write; + c->fileselect.title = dupstr(title); + return c; +} + +union control *ctrl_fontsel(struct controlset *s, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context) +{ + union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context); + c->fontselect.label = label ? dupstr(label) : NULL; + c->fontselect.shortcut = shortcut; + return c; +} + +union control *ctrl_tabdelay(struct controlset *s, union control *ctrl) +{ + union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL)); + c->tabdelay.ctrl = ctrl; + return c; +} + +union control *ctrl_text(struct controlset *s, const char *text, + intorptr helpctx) +{ + union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL)); + c->text.label = dupstr(text); + return c; +} + +union control *ctrl_checkbox(struct controlset *s, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context) +{ + union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context); + c->checkbox.label = label ? dupstr(label) : NULL; + c->checkbox.shortcut = shortcut; + return c; +} + +void ctrl_free(union control *ctrl) +{ + int i; + + sfree(ctrl->generic.label); + switch (ctrl->generic.type) { + case CTRL_RADIO: + for (i = 0; i < ctrl->radio.nbuttons; i++) + sfree(ctrl->radio.buttons[i]); + sfree(ctrl->radio.buttons); + sfree(ctrl->radio.shortcuts); + sfree(ctrl->radio.buttondata); + break; + case CTRL_COLUMNS: + sfree(ctrl->columns.percentages); + break; + case CTRL_LISTBOX: + sfree(ctrl->listbox.percentages); + break; + case CTRL_FILESELECT: + sfree(ctrl->fileselect.title); + break; +#ifdef MOD_ZMODEM + case CTRL_DIRECTORYSELECT: + sfree(ctrl->fileselect.title); + break; +#endif + } + sfree(ctrl); +} + +#ifdef MOD_ZMODEM +union control *ctrl_directorysel(struct controlset *s,char *label,char shortcut, + char *title, + intorptr helpctx, handler_fn handler, + intorptr context) +{ + union control *c = ctrl_new(s, CTRL_DIRECTORYSELECT, helpctx, handler, context); + c->fileselect.label = label ? dupstr(label) : NULL; + c->fileselect.shortcut = shortcut; + c->fileselect.title = dupstr(title); + return c; +} +#endif diff --git a/0.77_My_PuTTY/dialog.h b/0.77_My_PuTTY/dialog.h new file mode 100644 index 0000000..ad48bb8 --- /dev/null +++ b/0.77_My_PuTTY/dialog.h @@ -0,0 +1,700 @@ +/* + * 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 */ +#ifdef MOD_ZMODEM + CTRL_DIRECTORYSELECT, /* label plus directory selector */ +#endif + CTRL_TABDELAY /* see `tabdelay' below */ +}; + +/* + * Many controls have `intorptr' unions for storing user data, + * since the user might reasonably want to store either an integer + * or a void * pointer. Here I define a union, and two convenience + * functions to create that union from actual integers or pointers. + * + * The convenience functions are declared as inline if possible. + * Otherwise, they're declared here and defined when this header is + * included with DEFINE_INTORPTR_FNS defined. This is a total pain, + * but such is life. + */ +typedef union { void *p; int i; } intorptr; + +#ifndef INLINE +intorptr I(int i); +intorptr P(void *p); +#endif + +#if defined DEFINE_INTORPTR_FNS || defined INLINE +#ifdef INLINE +#define PREFIX INLINE +#else +#define PREFIX +#endif +PREFIX intorptr I(int i) { intorptr ret; ret.i = i; return ret; } +PREFIX intorptr P(void *p) { intorptr ret; ret.p = p; return ret; } +#undef PREFIX +#endif + +/* + * Each control has an `int' field specifying which columns it + * occupies in a multi-column part of the dialog box. These macros + * pack and unpack that field. + * + * If a control belongs in exactly one column, just specifying the + * column number is perfectly adequate. + */ +#define COLUMN_FIELD(start, span) ( (((span)-1) << 16) + (start) ) +#define COLUMN_START(field) ( (field) & 0xFFFF ) +#define COLUMN_SPAN(field) ( (((field) >> 16) & 0xFFFF) + 1 ) + +union control; + +/* + * The number of event types is being deliberately kept small, on + * the grounds that not all platforms might be able to report a + * large number of subtle events. We have: + * - the special REFRESH event, called when a control's value + * needs setting + * - the ACTION event, called when the user does something that + * positively requests action (double-clicking a list box item, + * or pushing a push-button) + * - the VALCHANGE event, called when the user alters the setting + * of the control in a way that is usually considered to alter + * the underlying data (toggling a checkbox or radio button, + * moving the items around in a drag-list, editing an edit + * control) + * - the SELCHANGE event, called when the user alters the setting + * of the control in a more minor way (changing the selected + * item in a list box). + * - the CALLBACK event, which happens after the handler routine + * has requested a subdialog (file selector, font selector, + * colour selector) and it has come back with information. + */ +enum { + EVENT_REFRESH, + EVENT_ACTION, + EVENT_VALCHANGE, + EVENT_SELCHANGE, + EVENT_CALLBACK +}; +typedef void (*handler_fn)(union control *ctrl, dlgparam *dp, + void *data, int event); + +#define STANDARD_PREFIX \ + int type; \ + char *label; \ + bool tabdelay; \ + int column; \ + handler_fn handler; \ + intorptr context; \ + intorptr helpctx; \ + union control *align_next_to + +union control { + /* + * The first possibility in this union is the generic header + * shared by all the structures, which we are therefore allowed + * to access through any one of them. + */ + struct { + int type; + /* + * Every control except CTRL_COLUMNS has _some_ sort of + * label. By putting it in the `generic' union as well as + * everywhere else, we avoid having to have an irritating + * switch statement when we go through and deallocate all + * the memory in a config-box structure. + * + * Yes, this does mean that any non-NULL value in this + * field is expected to be dynamically allocated and + * freeable. + * + * For CTRL_COLUMNS, this field MUST be NULL. + */ + char *label; + /* + * If `tabdelay' is non-zero, it indicates that this + * particular control should not yet appear in the tab + * order. A subsequent CTRL_TABDELAY entry will place it. + */ + bool tabdelay; + /* + * Indicate which column(s) this control occupies. This can + * be unpacked into starting column and column span by the + * COLUMN macros above. + */ + int column; + /* + * Most controls need to provide a function which gets + * called when that control's setting is changed, or when + * the control's setting needs initialising. + * + * The `data' parameter points to the writable data being + * modified as a result of the configuration activity; for + * example, the PuTTY `Conf' structure, although not + * necessarily. + * + * The `dlg' parameter is passed back to the platform- + * specific routines to read and write the actual control + * state. + */ + handler_fn handler; + /* + * Almost all of the above functions will find it useful to + * be able to store a piece of `void *' or `int' data. + */ + intorptr context; + /* + * For any control, we also allow the storage of a piece of + * data for use by context-sensitive help. For example, on + * Windows you can click the magic question mark and then + * click a control, and help for that control should spring + * up. Hence, here is a slot in which to store per-control + * data that a particular platform-specific driver can use + * to ensure it brings up the right piece of help text. + */ + intorptr helpctx; + /* + * Setting this to non-NULL coerces two 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 the _second_ control of the pair (in + * terms of order in the data structure), so that when it's + * instantiated, the first one is already there to be referred + * to. + */ + union control *align_next_to; + } generic; + struct { + STANDARD_PREFIX; + union control *ctrl; + } tabdelay; + struct { + STANDARD_PREFIX; + } text; + struct { + STANDARD_PREFIX; + char shortcut; /* keyboard shortcut */ + /* + * Percentage of the dialog-box width used by the edit box. + * If this is set to 100, the label is on its own line; + * otherwise the label is on the same line as the box + * itself. + */ + int percentwidth; + 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; + /* + * Edit boxes tend to need two items of context, so here's + * a spare. + */ + intorptr context2; + } editbox; + struct { + STANDARD_PREFIX; + /* + * `shortcut' here is a single keyboard shortcut which is + * expected to select the whole group of radio buttons. It + * can be NO_SHORTCUT if required, and there is also a way + * to place individual shortcuts on each button; see below. + */ + char shortcut; + /* + * There are separate fields for `ncolumns' and `nbuttons' + * for several reasons. + * + * Firstly, we sometimes want the last of a set of buttons + * to have a longer label than the rest; we achieve this by + * setting `ncolumns' higher than `nbuttons', and the + * layout code is expected to understand that the final + * button should be given all the remaining space on the + * line. This sounds like a ludicrously specific special + * case (if we're doing this sort of thing, why not have + * the general ability to have a particular button span + * more than one column whether it's the last one or not?) + * but actually it's reasonably common for the sort of + * three-way control you get a lot of in PuTTY: `yes' + * versus `no' versus `some more complex way to decide'. + * + * Secondly, setting `nbuttons' higher than `ncolumns' lets + * us have more than one line of radio buttons for a single + * setting. A very important special case of this is + * setting `ncolumns' to 1, so that each button is on its + * own line. + */ + int ncolumns; + int nbuttons; + /* + * This points to a dynamically allocated array of `char *' + * pointers, each of which points to a dynamically + * allocated string. + */ + char **buttons; /* `nbuttons' button labels */ + /* + * This points to a dynamically allocated array of `char' + * giving the individual keyboard shortcuts for each radio + * button. The array may be NULL if none are required. + */ + char *shortcuts; /* `nbuttons' shortcuts; may be NULL */ + /* + * This points to a dynamically allocated array of + * intorptr, giving helpful data for each button. + */ + intorptr *buttondata; /* `nbuttons' entries; may be NULL */ + } radio; + struct { + STANDARD_PREFIX; + char shortcut; + } checkbox; + struct { + STANDARD_PREFIX; + char shortcut; + /* + * At least Windows has the concept of a `default push + * button', which gets implicitly pressed when you hit + * Return even if it doesn't have the input focus. + */ + bool isdefault; + /* + * Also, the reverse of this: a default cancel-type button, + * which is implicitly pressed when you hit Escape. + */ + bool iscancel; + } button; + struct { + STANDARD_PREFIX; + char shortcut; /* keyboard shortcut */ + /* + * Height of the list box, in approximate number of lines. + * If this is zero, the list is a drop-down list. + */ + int height; /* height in lines */ + /* + * If this is set, the list elements can be reordered by + * the user (by drag-and-drop or by Up and Down buttons, + * whatever the per-platform implementation feels + * comfortable with). This is not guaranteed to work on a + * drop-down list, so don't try it! + */ + 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 { + STANDARD_PREFIX; + char shortcut; + /* + * `filter' dictates what type of files will be selected by + * default; for example, when selecting private key files + * the file selector would do well to only show .PPK files + * (on those systems where this is the chosen extension). + * + * The precise contents of `filter' are platform-defined, + * unfortunately. The special value NULL means `all files' + * and is always a valid fallback. + * + * Unlike almost all strings in this structure, this value + * is NOT expected to require freeing (although of course + * you can always use ctrl_alloc if you do need to create + * one on the fly). This is because the likely mode of use + * is to define string constants in a platform-specific + * header file, and directly reference those. Or worse, a + * particular platform might choose to cast integers into + * this pointer type... + */ + char const *filter; + /* + * Some systems like to know whether a file selector is + * choosing a file to read or one to write (and possibly + * create). + */ + 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; + } fileselect; + struct { + /* In this variant, `label' MUST be NULL. */ + STANDARD_PREFIX; + int ncols; /* number of columns */ + int *percentages; /* % width of each column */ + /* + * Every time this control type appears, exactly one of + * `ncols' and the previous number of columns MUST be one. + * Attempting to allow a seamless transition from a four- + * to a five-column layout, for example, would be way more + * trouble than it was worth. If you must lay things out + * like that, define eight unevenly sized columns and use + * column-spanning a lot. But better still, just don't. + * + * `percentages' may be NULL if ncols==1, to save space. + */ + } columns; + struct { + STANDARD_PREFIX; + char shortcut; + } fontselect; +#ifdef MOD_ZMODEM + struct { + STANDARD_PREFIX; + char shortcut; + /* + * 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; + } directoryselect; +#endif +}; + +#undef STANDARD_PREFIX + +/* + * `controlset' is a container holding an array of `union control' + * structures, together with a panel name and a title for the whole + * set. In Windows and any similar-looking GUI, each `controlset' + * in the config will be a container box within a panel. + * + * Special case: if `boxname' is NULL, the control set gives an + * overall title for an entire panel of controls. + */ +struct controlset { + char *pathname; /* panel path, e.g. "SSH/Tunnels" */ + char *boxname; /* internal short name of controlset */ + char *boxtitle; /* title of container box */ + int ncolumns; /* current no. of columns at bottom */ + size_t ncontrols; /* number of `union control' in array */ + size_t ctrlsize; /* allocated size of array */ + union control **ctrls; /* actual array */ +}; + +typedef void (*ctrl_freefn_t)(void *); /* used by ctrl_alloc_with_free */ + +/* + * This is the container structure which holds a complete set of + * controls. + */ +struct controlbox { + 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(union control *); + +/* + * This function works like `malloc', but the memory it returns + * will be automatically freed when the controlbox is freed. Note + * that a controlbox is a dialog-box _template_, not an instance, + * and so data allocated through this function is better not used + * to hold modifiable per-instance things. It's mostly here for + * allocating structures to be passed as control handler params. + * + * ctrl_alloc_with_free also allows you to provide a function to free + * the structure, in case there are other dynamically allocated bits + * and pieces dangling off it. + */ +void *ctrl_alloc(struct controlbox *b, size_t size); +void *ctrl_alloc_with_free(struct controlbox *b, size_t size, + ctrl_freefn_t freefunc); + +/* + * Individual routines to create `union control' structures in a controlset. + * + * Most of these routines allow the most common fields to be set + * directly, and put default values in the rest. Each one returns a + * pointer to the `union control' it created, so that final tweaks + * can be made. + */ + +/* `ncolumns' is followed by that many percentages, as integers. */ +union control *ctrl_columns(struct controlset *, int ncolumns, ...); +union control *ctrl_editbox(struct controlset *, const char *label, + char shortcut, int percentage, intorptr helpctx, + handler_fn handler, + intorptr context, intorptr context2); +union control *ctrl_combobox(struct controlset *, const char *label, + char shortcut, int percentage, intorptr helpctx, + handler_fn handler, + intorptr context, intorptr context2); +/* + * `ncolumns' is followed by (alternately) radio button titles and + * intorptrs, until a NULL in place of a title string is seen. Each + * title is expected to be followed by a shortcut _iff_ `shortcut' + * is NO_SHORTCUT. + */ +union control *ctrl_radiobuttons(struct controlset *, const char *label, + char shortcut, int ncolumns, intorptr helpctx, + handler_fn handler, intorptr context, ...); +union control *ctrl_pushbutton(struct controlset *, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_listbox(struct controlset *, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_droplist(struct controlset *, const char *label, + char shortcut, int percentage, intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_draglist(struct controlset *, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_filesel(struct controlset *, const char *label, + char shortcut, const char *filter, bool write, + const char *title, intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_fontsel(struct controlset *, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_text(struct controlset *, const char *text, + intorptr helpctx); +union control *ctrl_checkbox(struct controlset *, const char *label, + char shortcut, intorptr helpctx, + handler_fn handler, intorptr context); +union control *ctrl_tabdelay(struct controlset *, union control *); +#ifdef MOD_ZMODEM +union control *ctrl_directorysel(struct controlset *,char *label,char shortcut, + char *title, + intorptr helpctx, + handler_fn handler, intorptr context); +#endif + +/* + * Routines the platform-independent dialog code can call to read + * and write the values of controls. + */ +void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton); +int dlg_radiobutton_get(union control *ctrl, dlgparam *dp); +void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked); +bool dlg_checkbox_get(union control *ctrl, dlgparam *dp); +void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text); +char *dlg_editbox_get(union control *ctrl, dlgparam *dp); /* result must be freed by caller */ +/* The `listbox' functions can also apply to combo boxes. */ +void dlg_listbox_clear(union control *ctrl, dlgparam *dp); +void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index); +void dlg_listbox_add(union control *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(union control *ctrl, dlgparam *dp, + char const *text, int id); +int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index); +/* dlg_listbox_index returns <0 if no single element is selected. */ +int dlg_listbox_index(union control *ctrl, dlgparam *dp); +bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index); +void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index); +void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text); +void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn); +Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp); +void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fn); +FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp); +#ifdef MOD_ZMODEM +void dlg_directorysel_set(union control *ctrl, void *dlg, Filename fn); +void dlg_directorysel_get(union control *ctrl, void *dlg, Filename *fn); +#endif +/* + * Bracketing a large set of updates in these two functions will + * cause the front end (if possible) to delay updating the screen + * until it's all complete, thus avoiding flicker. + */ +void dlg_update_start(union control *ctrl, dlgparam *dp); +void dlg_update_done(union control *ctrl, dlgparam *dp); +/* + * Set input focus into a particular control. + */ +void dlg_set_focus(union control *ctrl, dlgparam *dp); +/* + * Change the label text on a control. + */ +void dlg_label_change(union control *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! + */ +union control *dlg_last_focused(union control *ctrl, dlgparam *dp); +/* + * Find out whether a particular control is currently visible. + */ +bool dlg_is_visible(union control *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(union control *ctrl, dlgparam *dp, + int r, int g, int b); +bool dlg_coloursel_results(union control *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(union control *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); + +#ifdef MOD_ZMODEM +/* + * The standard directory-selector handler expects the main `context' + * field to contain the `offsetof' a Filename field in the + * structure pointed to by `data'. + */ +void conf_directorysel_handler(union control *ctrl, void *dlg, void *data, int event); +#endif diff --git a/0.77_My_PuTTY/ecc.c b/0.77_My_PuTTY/ecc.c new file mode 100644 index 0000000..0901f27 --- /dev/null +++ b/0.77_My_PuTTY/ecc.c @@ -0,0 +1,1167 @@ +#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/0.77_My_PuTTY/ecc.h b/0.77_My_PuTTY/ecc.h new file mode 100644 index 0000000..89eae6c --- /dev/null +++ b/0.77_My_PuTTY/ecc.h @@ -0,0 +1,243 @@ +#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 *wc); +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 *ec); +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/0.77_My_PuTTY/empty.h b/0.77_My_PuTTY/empty.h new file mode 100644 index 0000000..52cebd7 --- /dev/null +++ b/0.77_My_PuTTY/empty.h @@ -0,0 +1 @@ +/* Empty file touched by automake makefile to force rebuild of version.o */ diff --git a/0.77_My_PuTTY/errsock.c b/0.77_My_PuTTY/errsock.c new file mode 100644 index 0000000..fd56913 --- /dev/null +++ b/0.77_My_PuTTY/errsock.c @@ -0,0 +1,74 @@ +/* + * 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/0.77_My_PuTTY/fuzzterm.c b/0.77_My_PuTTY/fuzzterm.c new file mode 100644 index 0000000..b84e7d9 --- /dev/null +++ b/0.77_My_PuTTY/fuzzterm.c @@ -0,0 +1,215 @@ +#include +#include +#include + +#include "putty.h" +#include "dialog.h" +#include "terminal.h" + +/* For Unix in particular, but harmless if this main() is reused elsewhere */ +const bool buildinfo_gtk_relevant = false; + +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(&ucsdata, conf_get_str(conf, CONF_line_codepage), + conf_get_bool(conf, CONF_utf8_override), + CS_NONE, conf_get_int(conf, CONF_vtmode)); + + 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, false, 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) {} +static void fuzz_set_icon_title(TermWin *tw, const char *icontitle) {} +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 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, +}; + +void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {} +void ldisc_echoedit_update(Ldisc *ldisc) {} +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 and sercfg.c */ + +void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton) { } +int dlg_radiobutton_get(union control *ctrl, dlgparam *dp) { return 0; } +void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked) { } +bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) { return false; } +void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) { } +char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +{ return dupstr("moo"); } +void dlg_listbox_clear(union control *ctrl, dlgparam *dp) { } +void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index) { } +void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text) { } +void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp, + char const *text, int id) { } +int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index) +{ return 0; } +int dlg_listbox_index(union control *ctrl, dlgparam *dp) { return -1; } +bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +{ return false; } +void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index) { } +void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text) { } +void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn) { } +Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) { return NULL; } +void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fn) { } +FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp) { return NULL; } +void dlg_update_start(union control *ctrl, dlgparam *dp) { } +void dlg_update_done(union control *ctrl, dlgparam *dp) { } +void dlg_set_focus(union control *ctrl, dlgparam *dp) { } +void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) { } +union control *dlg_last_focused(union control *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(union control *ctrl, dlgparam *dp, + int r, int g, int b) { } +bool dlg_coloursel_results(union control *ctrl, dlgparam *dp, + int *r, int *g, int *b) { return false; } +void dlg_refresh(union control *ctrl, dlgparam *dp) { } +bool dlg_is_visible(union control *ctrl, dlgparam *dp) { return false; } + +const char *const appname = "FuZZterm"; +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(""); +} + +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/0.77_My_PuTTY/import.c b/0.77_My_PuTTY/import.c new file mode 100644 index 0000000..0c81654 --- /dev/null +++ b/0.77_My_PuTTY/import.c @@ -0,0 +1,2325 @@ +/* + * 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; +} + +/* + * Strip trailing CRs and LFs at the end of a line of text. + */ +void strip_crlf(char *str) +{ + char *p = str + strlen(str); + + while (p > str && (p[-1] == '\r' || p[-1] == '\n')) + *--p = '\0'; +} + +/* ---------------------------------------------------------------------- + * Helper routines. (The base64 ones are defined in sshpubk.c.) + */ + +#define isbase64(c) ( ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z') || \ + ((c) >= '0' && (c) <= '9') || \ + (c) == '+' || (c) == '/' || (c) == '=' \ + ) + +/* + * Read an ASN.1/BER identifier and length pair. + * + * Flags are a combination of the #defines listed below. + * + * Returns -1 if unsuccessful; otherwise returns the number of + * bytes used out of the source data. + */ + +/* ASN.1 tag classes. */ +#define ASN1_CLASS_UNIVERSAL (0 << 6) +#define ASN1_CLASS_APPLICATION (1 << 6) +#define ASN1_CLASS_CONTEXT_SPECIFIC (2 << 6) +#define ASN1_CLASS_PRIVATE (3 << 6) +#define ASN1_CLASS_MASK (3 << 6) + +/* Primitive versus constructed bit. */ +#define ASN1_CONSTRUCTED (1 << 5) + +/* + * 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; +}; + +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_dss); + 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 *key, 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]; + + /* + * 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)); + spareblob = NULL; + + outblob = strbuf_new_nm(); + + /* + * Encode the OpenSSH key blob, and also decide on the header + * line. + */ + if (ssh_key_alg(key->key) == &ssh_rsa || + ssh_key_alg(key->key) == &ssh_dss) { + strbuf *seq; + + /* + * The RSA and DSS 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->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->key) == &ssh_ecdsa_nistp256 || + ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 || + ssh_key_alg(key->key) == &ssh_ecdsa_nistp521) { + const unsigned char *oid; + struct ecdsa_key *ec = container_of(key->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->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, outblob->u, outblob->len, 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(passphrase, + key->kdfopts.bcrypt.salt.ptr, + key->kdfopts.bcrypt.salt.len, + 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 *key, 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; + + /* + * Fetch the key blobs and find out the lengths of things. + */ + pubblob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob)); + privblob = strbuf_new_nm(); + ssh_key_openssh_blob(key->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->key)); + put_data(cpblob, privblob->s, privblob->len); + + /* Comment. */ + put_stringz(cpblob, key->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(passphrase, + 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, cblob->u, cblob->len, 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. + */ + if (ssh_key_alg(key->key) == &ssh_dss || + ssh_key_alg(key->key) == &ssh_rsa || + ssh_key_alg(key->key) == &ssh_ecdsa_nistp256 || + ssh_key_alg(key->key) == &ssh_ecdsa_nistp384 || + ssh_key_alg(key->key) == &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; +} + +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_dss; + 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_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] = 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, outblob->u, outblob->len, 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/0.77_My_PuTTY/ldisc.c b/0.77_My_PuTTY/ldisc.c new file mode 100644 index 0000000..4b800ad --- /dev/null +++ b/0.77_My_PuTTY/ldisc.c @@ -0,0 +1,375 @@ +/* + * ldisc.c: PuTTY line discipline. Sits between the input coming + * from keypresses in the window, and the output channel leading to + * the back end. Implements echo and/or local line editing, + * depending on what's currently configured. + */ + +#include +#include +#include + +#include "putty.h" +#include "terminal.h" +#include "ldisc.h" + +#define ECHOING (ldisc->localecho == FORCE_ON || \ + (ldisc->localecho == AUTO && \ + (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)))) + +/* rutty: special entry point for local data (in windows.c) */ +#ifdef MOD_RUTTY +int GetPuttyFlag(void) ; +int GetRuttyFlag(void) ; + +size_t win_seat_output_local(Seat *seat, bool is_stderr, const void *data, size_t len) ; + +static size_t seat_stdout_local(void *frontend, const char *data, size_t len) { + return win_seat_output_local(frontend,false,(const void *)data,len); +} +void c_write(Ldisc *ldisc, const void *buf, int len) +{ + if( !GetPuttyFlag() && (GetRuttyFlag()>0) ) { + //from_backend_local(ldisc->frontend, 0, buf, len); + seat_stdout_local(ldisc->seat, buf, len); + } else { + seat_stdout(ldisc->seat, buf, len); + } +} +#else +static void c_write(Ldisc *ldisc, const void *buf, int len) +{ + seat_stdout(ldisc->seat, buf, len); +} +#endif + +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); +} + +#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; + + 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) +{ + if (ldisc->term) + ldisc->term->ldisc = NULL; + if (ldisc->backend) + backend_provide_ldisc(ldisc->backend, NULL); + if (ldisc->buf) + sfree(ldisc->buf); + sfree(ldisc); +} + +/* rutty: */ +#ifdef MOD_RUTTY +#include "script.h" +extern ScriptData scriptdata; /* in window.c */ +#endif /* rutty */ + +void ldisc_echoedit_update(Ldisc *ldisc) +{ + seat_echoedit_update(ldisc->seat, ECHOING, EDITING); +} + +void ldisc_send(Ldisc *ldisc, const void *vbuf, int len, bool interactive) +{ + const char *buf = (const char *)vbuf; + int keyflag = 0; + + assert(ldisc->term); + +/* rutty: */ +#ifdef MOD_RUTTY + if( !GetPuttyFlag() && (GetRuttyFlag()>0) ) { script_local(&scriptdata, buf,len); } +#endif /* rutty */ + + 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--; + } + backend_special(ldisc->backend, 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')) + backend_special(ldisc->backend, SS_IP, 0); + if (c == CTRL('Z')) + backend_special(ldisc->backend, SS_SUSP, 0); + if (c == CTRL('\\')) + backend_special(ldisc->backend, 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) { + backend_special(ldisc->backend, SS_EOF, 0); + } else { + backend_send(ldisc->backend, 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) + backend_send(ldisc->backend, + ldisc->buf, ldisc->buflen); + if (ldisc->protocol == PROT_RAW) + backend_send(ldisc->backend, "\r\n", 2); + else if (ldisc->protocol == PROT_TELNET && ldisc->telnet_newline) + backend_special(ldisc->backend, SS_EOL, 0); + else + backend_send(ldisc->backend, "\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) { + backend_send(ldisc->backend, 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) + backend_special(ldisc->backend, SS_EOL, 0); + else + backend_send(ldisc->backend, "\r", 1); + break; + case CTRL('?'): + case CTRL('H'): + if (ldisc->telnet_keyboard) { + backend_special(ldisc->backend, SS_EC, 0); + break; + } + case CTRL('C'): + if (ldisc->telnet_keyboard) { + backend_special(ldisc->backend, SS_IP, 0); + break; + } + case CTRL('Z'): + if (ldisc->telnet_keyboard) { + backend_special(ldisc->backend, SS_SUSP, 0); + break; + } + + default: + backend_send(ldisc->backend, buf, len); + break; + } + } else + backend_send(ldisc->backend, buf, len); + } + } +} diff --git a/0.77_My_PuTTY/ldisc.h b/0.77_My_PuTTY/ldisc.h new file mode 100644 index 0000000..770b4b0 --- /dev/null +++ b/0.77_My_PuTTY/ldisc.h @@ -0,0 +1,27 @@ +/* + * ldisc.h: defines the Ldisc data structure used by ldisc.c and + * ldiscucs.c. (Unfortunately it was necessary to split the ldisc + * module in two, to avoid unnecessarily linking in the Unicode + * stuff in tools that don't require it.) + */ + +#ifndef PUTTY_LDISC_H +#define PUTTY_LDISC_H + +struct Ldisc_tag { + Terminal *term; + Backend *backend; + Seat *seat; + + /* + * Values cached out of conf. + */ + bool telnet_keyboard, telnet_newline; + int protocol, localecho, localedit; + + char *buf; + size_t buflen, bufsiz; + bool quotenext; +}; + +#endif /* PUTTY_LDISC_H */ diff --git a/0.77_My_PuTTY/licence.h b/0.77_My_PuTTY/licence.h new file mode 100644 index 0000000..3b2520b --- /dev/null +++ b/0.77_My_PuTTY/licence.h @@ -0,0 +1,19 @@ +/* + * licence.h - macro definitions for the PuTTY licence. + * + * Generated by licence.pl from LICENCE. + * You should edit those files rather than editing this one. + */ + +#define LICENCE_TEXT(parsep) \ + "PuTTY is copyright 1997-2022 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-2022 Simon Tatham" diff --git a/0.77_My_PuTTY/logging.c b/0.77_My_PuTTY/logging.c new file mode 100644 index 0000000..dc77dd8 --- /dev/null +++ b/0.77_My_PuTTY/logging.c @@ -0,0 +1,664 @@ +/* + * 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); + +#ifdef MOD_PERSO +int GetPuttyFlag(void) ; +int mkdir(const char *path, int mode); +int insert( char * ch, const char * c, const int ipos ) ; +int del( char * ch, const int start, const int length ) ; +int poss( const char * c, const char * ch ) ; +int posi( const char * c, const char * ch, const int ipos ) ; +static int LogMode = 1 ; +int SwitchLogMode(void) { LogMode = abs( LogMode - 1 ) ; return LogMode ; } + +// Test l'existance du répertoire, sinon le créé +void test_dir( Filename *filename ) { + int i ; char * name ; + if( filename == NULL ) return ; + if( strlen( filename_to_str(filename) ) == 0 ) return ; + + if( ( name = (char*) malloc( strlen( filename_to_str(filename) ) + 1 ) ) != NULL ) { + strcpy( name, filename_to_str(filename) ) ; + for( i=strlen(name)-1; i>=0 ; i-- ) + { if( (name[i] == '\\') || (name[i]=='/' ) ) break ; } + if( i > 0 ) { + name[i] = '\0'; + mkdir( name, 777 ) ; + } + free( name ) ; + } + } + +/* Procedure perso de conversion date (time_t) -> char* */ +size_t m_strftime( char *s, size_t max, const char *format, const struct tm *tm) { + char * nfor, b[128] ; + int p, i = 1 ; + size_t res ; + + if( (nfor = (char*) malloc( strlen( format ) + 1024 )) == NULL ) return 0 ; + strcpy( nfor, format ) ; + + sprintf( b, "%lu", mktime((struct tm*)tm) ) ; + while( (p=posi( "%s", nfor, i )) > 0 ) { + if( p==1 ) { del(nfor,p,2) ; insert(nfor,b,p); } + else if( nfor[p-2] != '%' ) { del(nfor,p,2) ; insert(nfor,b,p); } + i = p + 1 ; + } + while( (p=posi( "%t", nfor, i )) > 0 ) { + if( p==1 ) { del(nfor,p,2) ; insert(nfor," ",p); } + else if( nfor[p-2] != '%' ) { del(nfor,p,2) ; insert(nfor," ",p); } + i = p + 1 ; + } + + res = strftime( s, max, nfor, tm ) ; + + free( nfor ) ; + return res ; + } + +/* Procedure perso de conversion date (struct _SYSTEMTIME) -> char* */ +size_t t_strftime( char *s, size_t max, const char *format, const struct tm tm, const SYSTEMTIME st ) { + char * nfor, b[128] ; + int p, i = 1 ; + //struct tm tm ; + size_t res = 0 ; +/* + 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 ; + tm.tm_wday = st.wDayOfWeek - 1 ; + tm.tm_yday = 0 ; + tm.tm_isdst = 0 ; +*/ + if( (nfor = (char*) malloc( strlen( format ) + 1024 )) == NULL ) return 0 ; + strcpy( nfor, format ) ; + + sprintf( b, "%03u", st.wMilliseconds ) ; + while( (p=posi( "%f", nfor, i )) > 0 ) { + if( p==1 ) { del(nfor,p,2) ; insert(nfor,b,p); } + else if( nfor[p-2] != '%' ) { del(nfor,p,2) ; insert(nfor,b,p); } + i = p + 1 ; + } + + res = m_strftime( s, max, nfor, &tm ) ; + + free( nfor ) ; + + return res ; + } + +int log_writetimestamp( struct LogContext *ctx ) { +// "%m/%d/%Y %H:%M:%S " + if( strlen(conf_get_str(ctx->conf,CONF_logtimestamp) )==0 ) return 1 ; + char buf[128] = "" ; + + if( poss( "%f", conf_get_str(ctx->conf,CONF_logtimestamp) ) ) { + SYSTEMTIME sysTime ; + GetLocalTime( &sysTime ) ; + time_t temps = time( 0 ) ; + struct tm tm = * localtime( &temps ) ; + t_strftime( buf, 127, conf_get_str(ctx->conf,CONF_logtimestamp), tm, sysTime ) ; + } + else { + time_t temps = time( 0 ) ; + struct tm tm = * localtime( &temps ) ; + m_strftime( buf, 127, conf_get_str(ctx->conf,CONF_logtimestamp), &tm ) ; + } + + fwrite(buf, 1, strlen(buf), ctx->lgfp); + return 1; + } + +static int timestamp_newline = 1 ; +static int timestamp_newfile = 0 ; + +void timestamp_change_filename( void ) { timestamp_newfile = 1 ; timestamp_newline = 1 ; } +static void logfopen_callback(void *handle, int mode) ; +void logfile_reinit(void *handle) { if(handle!=NULL) { struct LogContext *ctx = (struct LogContext *)handle; logfclose(ctx) ; logfopen_callback(ctx,2) ; } } +#endif +/* + * 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. + */ +#ifdef MOD_PERSO + if( !LogMode ) return ; + if( timestamp_newfile ) { + if (ctx->state == L_OPEN) { logfclose(ctx);} + timestamp_newfile = 0 ; + } +#endif + + 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); +#ifdef MOD_PERSO + if( !GetPuttyFlag() ) { + if( timestamp_newline ) { log_writetimestamp( ctx ) ; timestamp_newline = 0 ; } + char * c = (char*)(data.ptr+data.len-1) ; + if( c[0]=='\n' ) timestamp_newline = 1 ; + } +#endif + 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); +} + +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); +#ifdef MOD_PERSO + test_dir( ctx->currlogfilename ) ; +#endif + + + 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) +{ +#ifdef MOD_PERSO + char buf[100], *bufp; +#else + char buf[32], *bufp; +#endif + 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; +#ifdef MOD_PERSO + case 'h': + strcpy(buf,hostname) ; + int i ; while( (i=poss(":",buf))>0 ) { buf[i-1]='-' ; } // Pour gerer IPv6 + bufp=buf ; +#else + case 'h': + bufp = hostname; +#endif + 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/0.77_My_PuTTY/mainchan.c b/0.77_My_PuTTY/mainchan.c new file mode 100644 index 0000000..8185a3d --- /dev/null +++ b/0.77_My_PuTTY/mainchan.c @@ -0,0 +1,538 @@ +/* + * SSH main session channel handling. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshppl.h" +#include "sshchan.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"); + + 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(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(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_ppl_got_user_input(mc->ppl); /* 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 "sshsignals.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 "sshsignals.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 "sshsignals.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 "sshsignals.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/0.77_My_PuTTY/marshal.c b/0.77_My_PuTTY/marshal.c new file mode 100644 index 0000000..191233e --- /dev/null +++ b/0.77_My_PuTTY/marshal.c @@ -0,0 +1,318 @@ +#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, struct 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; +} + +/* ---------------------------------------------------------------------- */ + +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); +} diff --git a/0.77_My_PuTTY/marshal.h b/0.77_My_PuTTY/marshal.h new file mode 100644 index 0000000..c95408d --- /dev/null +++ b/0.77_My_PuTTY/marshal.h @@ -0,0 +1,340 @@ +#ifndef PUTTY_MARSHAL_H +#define PUTTY_MARSHAL_H + +#include "defs.h" + +#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); + 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_->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) + +/* + * 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); +struct strbuf; +void BinarySink_put_stringsb(BinarySink *, struct 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); + +/* ---------------------------------------------------------------------- */ + +/* + * 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/0.77_My_PuTTY/memory.c b/0.77_My_PuTTY/memory.c new file mode 100644 index 0000000..144b0d9 --- /dev/null +++ b/0.77_My_PuTTY/memory.c @@ -0,0 +1,134 @@ +/* + * 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/0.77_My_PuTTY/millerrabin.c b/0.77_My_PuTTY/millerrabin.c new file mode 100644 index 0000000..4f5058b --- /dev/null +++ b/0.77_My_PuTTY/millerrabin.c @@ -0,0 +1,214 @@ +/* + * 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; + + size_t k; + mp_int *q; + + mp_int *two, *pm1, *m_pm1; +}; + +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->k = 1; + while (!mp_get_bit(p, mr->k)) + mr->k++; + mr->q = mp_rshift_safe(p, mr->k); + + mr->two = mp_from_integer(2); + + mr->pm1 = mp_unsafe_copy(p); + mp_sub_integer_into(mr->pm1, mr->pm1, 1); + + 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->q); + mp_free(mr->two); + mp_free(mr->pm1); + mp_free(mr->m_pm1); + monty_free(mr->mc); + smemclr(mr, sizeof(*mr)); + sfree(mr); +} + +struct mr_result { + bool passed; + bool potential_primitive_root; +}; + +static struct mr_result miller_rabin_test_inner(MillerRabin *mr, mp_int *w) +{ + /* + * Compute w^q mod p. + */ + mp_int *wqp = monty_pow(mr->mc, w, mr->q); + + /* + * See if this is 1, or if it is -1, or if it becomes -1 + * when squared at most k-1 times. + */ + struct mr_result result; + result.passed = false; + result.potential_primitive_root = false; + + if (mp_cmp_eq(wqp, monty_identity(mr->mc))) { + result.passed = true; + } else { + for (size_t i = 0; i < mr->k; i++) { + if (mp_cmp_eq(wqp, mr->m_pm1)) { + result.passed = true; + result.potential_primitive_root = (i == mr->k - 1); + break; + } + if (i == mr->k - 1) + break; + monty_mul_into(mr->mc, wqp, wqp, wqp); + } + } + + mp_free(wqp); + + 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/0.77_My_PuTTY/minibidi.c b/0.77_My_PuTTY/minibidi.c new file mode 100644 index 0000000..654f47e --- /dev/null +++ b/0.77_My_PuTTY/minibidi.c @@ -0,0 +1,2025 @@ +/************************************************************************ + * + * ------------ + * Description: + * ------------ + * This is an implementation of Unicode's Bidirectional Algorithm + * (known as UAX #9). + * + * http://www.unicode.org/reports/tr9/ + * + * Author: Ahmad Khalifa + * + * (www.arabeyes.org - under MIT license) + * + ************************************************************************/ + +/* + * TODO: + * ===== + * - Explicit marks need to be handled (they are not 100% now) + * - Ligatures + */ + +#include /* definition of wchar_t*/ + +#include "putty.h" +#include "misc.h" + +#define LMASK 0x3F /* Embedding Level mask */ +#define OMASK 0xC0 /* Override mask */ +#define OISL 0x80 /* Override is L */ +#define OISR 0x40 /* Override is R */ + +/* For standalone compilation in a testing mode. + * Still depends on the PuTTY headers for snewn and sfree, but can avoid + * _linking_ with any other PuTTY code. */ +#ifdef TEST_GETTYPE +#define safemalloc malloc +#define safefree free +#endif + +/* Shaping Helpers */ +#define STYPE(xh) ((((xh) >= SHAPE_FIRST) && ((xh) <= SHAPE_LAST)) ? \ +shapetypes[(xh)-SHAPE_FIRST].type : SU) /*))*/ +#define SISOLATED(xh) (shapetypes[(xh)-SHAPE_FIRST].form_b) +#define SFINAL(xh) ((xh)+1) +#define SINITIAL(xh) ((xh)+2) +#define SMEDIAL(ch) ((ch)+3) + +#define leastGreaterOdd(x) ( ((x)+1) | 1 ) +#define leastGreaterEven(x) ( ((x)+2) &~ 1 ) + +/* function declarations */ +static void flipThisRun( + bidi_char *from, unsigned char *level, int max, int count); +static int findIndexOfRun( + unsigned char *level, int start, int count, int tlevel); +static unsigned char getType(int ch); +static unsigned char setOverrideBits( + unsigned char level, unsigned char override); +static int getPreviousLevel(unsigned char *level, int from); +static void doMirror(unsigned int *ch); + +/* character types */ +enum { + L, + LRE, + LRO, + R, + AL, + RLE, + RLO, + PDF, + EN, + ES, + ET, + AN, + CS, + NSM, + BN, + B, + S, + WS, + ON +}; + +/* Shaping Types */ +enum { + SL, /* Left-Joining, doesn't exist in U+0600 - U+06FF */ + SR, /* Right-Joining, ie has Isolated, Final */ + SD, /* Dual-Joining, ie has Isolated, Final, Initial, Medial */ + SU, /* Non-Joining */ + SC /* Join-Causing, like U+0640 (TATWEEL) */ +}; + +typedef struct { + char type; + wchar_t form_b; +} shape_node; + +/* Kept near the actual table, for verification. */ +#define SHAPE_FIRST 0x621 +#define SHAPE_LAST (SHAPE_FIRST + lenof(shapetypes) - 1) + +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}, +}; + +/* + * Flips the text buffer, according to max level, and + * all higher levels + * + * Input: + * from: text buffer, on which to apply flipping + * level: resolved levels buffer + * max: the maximum level found in this line (should be unsigned char) + * count: line size in bidi_char + */ +static void flipThisRun( + bidi_char *from, unsigned char *level, int max, int count) +{ + int i, j, k, tlevel; + bidi_char temp; + + j = i = 0; + while (i j; k--, j++) { + temp = from[k]; + from[k] = from[j]; + from[j] = temp; + } + } +} + +/* + * Finds the index of a run with level equals tlevel + */ +static int findIndexOfRun( + unsigned char *level , int start, int count, int tlevel) +{ + int i; + for (i=start; i 1) { + k = (i + j) / 2; + if (ch < lookup[k].first) + j = k; + else if (ch > lookup[k].last) + i = k; + else + return lookup[k].type; + } + + /* + * If we reach here, the character was not in any of the + * intervals listed in the lookup table. This means we return + * ON (`Other Neutrals'). This is the appropriate code for any + * character genuinely not listed in the Unicode table, and + * also the table above has deliberately left out any + * characters _explicitly_ listed as ON (to save space!). + */ + return ON; +} + +/* + * Function exported to front ends to allow them to identify + * bidi-active characters (in case, for example, the platform's + * text display function can't conveniently be prevented from doing + * its own bidi and so special treatment is required for characters + * that would cause the bidi algorithm to activate). + * + * This function is passed a single Unicode code point, and returns + * nonzero if the presence of this code point can possibly cause + * the bidi algorithm to do any reordering. Thus, any string + * composed entirely of characters for which is_rtl() returns zero + * should be safe to pass to a bidi-active platform display + * function without fear. + * + * (is_rtl() must therefore also return true for any character + * which would be affected by Arabic shaping, but this isn't + * important because all such characters are right-to-left so it + * would have flagged them anyway.) + */ +bool is_rtl(int c) +{ + /* + * After careful reading of the Unicode bidi algorithm (URL as + * given at the top of this file) I believe that the only + * character classes which can possibly cause trouble are R, + * AL, RLE and RLO. I think that any string containing no + * character in any of those classes will be displayed + * uniformly left-to-right by the Unicode bidi algorithm. + */ + const int mask = (1< 0) { + unsigned char current = level[--from]; + + while (from >= 0 && level[from] == current) + from--; + + if (from >= 0) + return level[from]; + + return -1; + } else + return -1; +} + +/* The Main shaping function, and the only one to be used + * by the outside world. + * + * line: buffer to apply shaping to. this must be passed by doBidi() first + * to: output buffer for the shaped data + * count: number of characters in line + */ +int do_shape(bidi_char *line, bidi_char *to, int count) +{ + int i, tempShape; + 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; +} + +/* + * The Main Bidi Function, and the only function that should + * be used by the outside world. + * + * line: a buffer of size count containing text to apply + * the Bidirectional algorithm to. + */ + +int do_bidi(bidi_char *line, int count) +{ + unsigned char* types; + unsigned char* levels; + unsigned char paragraphLevel; + unsigned char currentEmbedding; + unsigned char currentOverride; + unsigned char tempType; + int i, j; + bool yes, bover; + + /* Check the presence of R or AL types as optimization */ + yes = false; + for (i=0; i= 0) { + if (types[j] == AL) { + types[i] = AN; + break; + } else if (types[j] == R || types[j] == L) { + break; + } + j--; + } + } + } + + /* Rule (W3) + * W3. Change all ALs to R. + * + * Optimization: on Rule Xn, we might set a flag on AL type + * to prevent this loop in L R lines only... + */ + for (i=0; i 0 && types[i-1] == EN) { + types[i] = EN; + continue; + } else if (i < count-1 && types[i+1] == EN) { + types[i] = EN; + continue; + } else if (i < count-1 && types[i+1] == ET) { + j=i; + while (j < count-1 && types[j] == ET) { + j++; + } + if (types[j] == EN) + types[i] = EN; + } + } + } + + /* Rule (W6) + * W6. Otherwise, separators and terminators change to Other Neutral: + */ + for (i=0; i= 0) { + if (types[j] == L) { + types[i] = L; + break; + } else if (types[j] == R || types[j] == AL) { + break; + } + j--; + } + } + } + + /* Rule (N1) + * N1. A sequence of neutrals takes the direction of the surrounding + * strong text if the text on both sides has the same direction. European + * and Arabic numbers are treated as though they were R. + */ + if (count >= 2 && types[0] == ON) { + if ((types[1] == R) || (types[1] == EN) || (types[1] == AN)) + types[0] = R; + else if (types[1] == L) + types[0] = L; + } + for (i=1; i<(count-1); i++) { + if (types[i] == ON) { + if (types[i-1] == L) { + j=i; + while (j<(count-1) && types[j] == ON) { + j++; + } + if (types[j] == L) { + while (i= 2 && types[count-1] == ON) { + if (types[count-2] == R || types[count-2] == EN || types[count-2] == AN) + types[count-1] = R; + else if (types[count-2] == L) + types[count-1] = L; + } + + /* Rule (N2) + * N2. Any remaining neutrals take the embedding direction. + */ + for (i=0; i0 && (getType(line[j].wc) == WS)) { + j--; + } + if (j < (count-1)) { + for (j++; j=i ; j--) { + levels[j] = paragraphLevel; + } + } + } else if (tempType == B || tempType == S) { + levels[i] = paragraphLevel; + } + } + + /* Rule (L4) NOT IMPLEMENTED + * L4. A character that possesses the mirrored property as specified by + * Section 4.7, Mirrored, must be depicted by a mirrored glyph if the + * resolved directionality of that character is R. + */ + /* Note: this is implemented before L2 for efficiency */ + for (i=0; i tempType) + tempType = levels[i]; + i++; + } + /* maximum level in tempType. */ + while (tempType > 0) { /* loop from highest level to the least odd, */ + /* which i assume is 1 */ + flipThisRun(line, levels, tempType, count); + tempType--; + } + + /* Rule (L3) NOT IMPLEMENTED + * L3. Combining marks applied to a right-to-left base character will at + * this point precede their base character. If the rendering engine + * expects them to follow the base characters in the final display + * process, then the ordering of the marks and the base character must + * be reversed. + */ + sfree(types); + sfree(levels); + return R; +} + + +/* + * Bad, Horrible function + * takes a pointer to a character that is checked for + * having a mirror glyph. + */ +static void doMirror(unsigned int *ch) +{ + if ((*ch & 0xFF00) == 0) { + switch (*ch) { + case 0x0028: *ch = 0x0029; break; + case 0x0029: *ch = 0x0028; break; + case 0x003C: *ch = 0x003E; break; + case 0x003E: *ch = 0x003C; break; + case 0x005B: *ch = 0x005D; break; + case 0x005D: *ch = 0x005B; break; + case 0x007B: *ch = 0x007D; break; + case 0x007D: *ch = 0x007B; break; + case 0x00AB: *ch = 0x00BB; break; + case 0x00BB: *ch = 0x00AB; break; + } + } else if ((*ch & 0xFF00) == 0x2000) { + switch (*ch) { + case 0x2039: *ch = 0x203A; break; + case 0x203A: *ch = 0x2039; break; + case 0x2045: *ch = 0x2046; break; + case 0x2046: *ch = 0x2045; break; + case 0x207D: *ch = 0x207E; break; + case 0x207E: *ch = 0x207D; break; + case 0x208D: *ch = 0x208E; break; + case 0x208E: *ch = 0x208D; break; + } + } else if ((*ch & 0xFF00) == 0x2200) { + switch (*ch) { + case 0x2208: *ch = 0x220B; break; + case 0x2209: *ch = 0x220C; break; + case 0x220A: *ch = 0x220D; break; + case 0x220B: *ch = 0x2208; break; + case 0x220C: *ch = 0x2209; break; + case 0x220D: *ch = 0x220A; break; + case 0x2215: *ch = 0x29F5; break; + case 0x223C: *ch = 0x223D; break; + case 0x223D: *ch = 0x223C; break; + case 0x2243: *ch = 0x22CD; break; + case 0x2252: *ch = 0x2253; break; + case 0x2253: *ch = 0x2252; break; + case 0x2254: *ch = 0x2255; break; + case 0x2255: *ch = 0x2254; break; + case 0x2264: *ch = 0x2265; break; + case 0x2265: *ch = 0x2264; break; + case 0x2266: *ch = 0x2267; break; + case 0x2267: *ch = 0x2266; break; + case 0x2268: *ch = 0x2269; break; + case 0x2269: *ch = 0x2268; break; + case 0x226A: *ch = 0x226B; break; + case 0x226B: *ch = 0x226A; break; + case 0x226E: *ch = 0x226F; break; + case 0x226F: *ch = 0x226E; break; + case 0x2270: *ch = 0x2271; break; + case 0x2271: *ch = 0x2270; break; + case 0x2272: *ch = 0x2273; break; + case 0x2273: *ch = 0x2272; break; + case 0x2274: *ch = 0x2275; break; + case 0x2275: *ch = 0x2274; break; + case 0x2276: *ch = 0x2277; break; + case 0x2277: *ch = 0x2276; break; + case 0x2278: *ch = 0x2279; break; + case 0x2279: *ch = 0x2278; break; + case 0x227A: *ch = 0x227B; break; + case 0x227B: *ch = 0x227A; break; + case 0x227C: *ch = 0x227D; break; + case 0x227D: *ch = 0x227C; break; + case 0x227E: *ch = 0x227F; break; + case 0x227F: *ch = 0x227E; break; + case 0x2280: *ch = 0x2281; break; + case 0x2281: *ch = 0x2280; break; + case 0x2282: *ch = 0x2283; break; + case 0x2283: *ch = 0x2282; break; + case 0x2284: *ch = 0x2285; break; + case 0x2285: *ch = 0x2284; break; + case 0x2286: *ch = 0x2287; break; + case 0x2287: *ch = 0x2286; break; + case 0x2288: *ch = 0x2289; break; + case 0x2289: *ch = 0x2288; break; + case 0x228A: *ch = 0x228B; break; + case 0x228B: *ch = 0x228A; break; + case 0x228F: *ch = 0x2290; break; + case 0x2290: *ch = 0x228F; break; + case 0x2291: *ch = 0x2292; break; + case 0x2292: *ch = 0x2291; break; + case 0x2298: *ch = 0x29B8; break; + case 0x22A2: *ch = 0x22A3; break; + case 0x22A3: *ch = 0x22A2; break; + case 0x22A6: *ch = 0x2ADE; break; + case 0x22A8: *ch = 0x2AE4; break; + case 0x22A9: *ch = 0x2AE3; break; + case 0x22AB: *ch = 0x2AE5; break; + case 0x22B0: *ch = 0x22B1; break; + case 0x22B1: *ch = 0x22B0; break; + case 0x22B2: *ch = 0x22B3; break; + case 0x22B3: *ch = 0x22B2; break; + case 0x22B4: *ch = 0x22B5; break; + case 0x22B5: *ch = 0x22B4; break; + case 0x22B6: *ch = 0x22B7; break; + case 0x22B7: *ch = 0x22B6; break; + case 0x22C9: *ch = 0x22CA; break; + case 0x22CA: *ch = 0x22C9; break; + case 0x22CB: *ch = 0x22CC; break; + case 0x22CC: *ch = 0x22CB; break; + case 0x22CD: *ch = 0x2243; break; + case 0x22D0: *ch = 0x22D1; break; + case 0x22D1: *ch = 0x22D0; break; + case 0x22D6: *ch = 0x22D7; break; + case 0x22D7: *ch = 0x22D6; break; + case 0x22D8: *ch = 0x22D9; break; + case 0x22D9: *ch = 0x22D8; break; + case 0x22DA: *ch = 0x22DB; break; + case 0x22DB: *ch = 0x22DA; break; + case 0x22DC: *ch = 0x22DD; break; + case 0x22DD: *ch = 0x22DC; break; + case 0x22DE: *ch = 0x22DF; break; + case 0x22DF: *ch = 0x22DE; break; + case 0x22E0: *ch = 0x22E1; break; + case 0x22E1: *ch = 0x22E0; break; + case 0x22E2: *ch = 0x22E3; break; + case 0x22E3: *ch = 0x22E2; break; + case 0x22E4: *ch = 0x22E5; break; + case 0x22E5: *ch = 0x22E4; break; + case 0x22E6: *ch = 0x22E7; break; + case 0x22E7: *ch = 0x22E6; break; + case 0x22E8: *ch = 0x22E9; break; + case 0x22E9: *ch = 0x22E8; break; + case 0x22EA: *ch = 0x22EB; break; + case 0x22EB: *ch = 0x22EA; break; + case 0x22EC: *ch = 0x22ED; break; + case 0x22ED: *ch = 0x22EC; break; + case 0x22F0: *ch = 0x22F1; break; + case 0x22F1: *ch = 0x22F0; break; + case 0x22F2: *ch = 0x22FA; break; + case 0x22F3: *ch = 0x22FB; break; + case 0x22F4: *ch = 0x22FC; break; + case 0x22F6: *ch = 0x22FD; break; + case 0x22F7: *ch = 0x22FE; break; + case 0x22FA: *ch = 0x22F2; break; + case 0x22FB: *ch = 0x22F3; break; + case 0x22FC: *ch = 0x22F4; break; + case 0x22FD: *ch = 0x22F6; break; + case 0x22FE: *ch = 0x22F7; break; + } + } else if ((*ch & 0xFF00) == 0x2300) { + switch (*ch) { + case 0x2308: *ch = 0x2309; break; + case 0x2309: *ch = 0x2308; break; + case 0x230A: *ch = 0x230B; break; + case 0x230B: *ch = 0x230A; break; + case 0x2329: *ch = 0x232A; break; + case 0x232A: *ch = 0x2329; break; + } + } else if ((*ch & 0xFF00) == 0x2700) { + switch (*ch) { + case 0x2768: *ch = 0x2769; break; + case 0x2769: *ch = 0x2768; break; + case 0x276A: *ch = 0x276B; break; + case 0x276B: *ch = 0x276A; break; + case 0x276C: *ch = 0x276D; break; + case 0x276D: *ch = 0x276C; break; + case 0x276E: *ch = 0x276F; break; + case 0x276F: *ch = 0x276E; break; + case 0x2770: *ch = 0x2771; break; + case 0x2771: *ch = 0x2770; break; + case 0x2772: *ch = 0x2773; break; + case 0x2773: *ch = 0x2772; break; + case 0x2774: *ch = 0x2775; break; + case 0x2775: *ch = 0x2774; break; + case 0x27D5: *ch = 0x27D6; break; + case 0x27D6: *ch = 0x27D5; break; + case 0x27DD: *ch = 0x27DE; break; + case 0x27DE: *ch = 0x27DD; break; + case 0x27E2: *ch = 0x27E3; break; + case 0x27E3: *ch = 0x27E2; break; + case 0x27E4: *ch = 0x27E5; break; + case 0x27E5: *ch = 0x27E4; break; + case 0x27E6: *ch = 0x27E7; break; + case 0x27E7: *ch = 0x27E6; break; + case 0x27E8: *ch = 0x27E9; break; + case 0x27E9: *ch = 0x27E8; break; + case 0x27EA: *ch = 0x27EB; break; + case 0x27EB: *ch = 0x27EA; break; + } + } else if ((*ch & 0xFF00) == 0x2900) { + switch (*ch) { + case 0x2983: *ch = 0x2984; break; + case 0x2984: *ch = 0x2983; break; + case 0x2985: *ch = 0x2986; break; + case 0x2986: *ch = 0x2985; break; + case 0x2987: *ch = 0x2988; break; + case 0x2988: *ch = 0x2987; break; + case 0x2989: *ch = 0x298A; break; + case 0x298A: *ch = 0x2989; break; + case 0x298B: *ch = 0x298C; break; + case 0x298C: *ch = 0x298B; break; + case 0x298D: *ch = 0x2990; break; + case 0x298E: *ch = 0x298F; break; + case 0x298F: *ch = 0x298E; break; + case 0x2990: *ch = 0x298D; break; + case 0x2991: *ch = 0x2992; break; + case 0x2992: *ch = 0x2991; break; + case 0x2993: *ch = 0x2994; break; + case 0x2994: *ch = 0x2993; break; + case 0x2995: *ch = 0x2996; break; + case 0x2996: *ch = 0x2995; break; + case 0x2997: *ch = 0x2998; break; + case 0x2998: *ch = 0x2997; break; + case 0x29B8: *ch = 0x2298; break; + case 0x29C0: *ch = 0x29C1; break; + case 0x29C1: *ch = 0x29C0; break; + case 0x29C4: *ch = 0x29C5; break; + case 0x29C5: *ch = 0x29C4; break; + case 0x29CF: *ch = 0x29D0; break; + case 0x29D0: *ch = 0x29CF; break; + case 0x29D1: *ch = 0x29D2; break; + case 0x29D2: *ch = 0x29D1; break; + case 0x29D4: *ch = 0x29D5; break; + case 0x29D5: *ch = 0x29D4; break; + case 0x29D8: *ch = 0x29D9; break; + case 0x29D9: *ch = 0x29D8; break; + case 0x29DA: *ch = 0x29DB; break; + case 0x29DB: *ch = 0x29DA; break; + case 0x29F5: *ch = 0x2215; break; + case 0x29F8: *ch = 0x29F9; break; + case 0x29F9: *ch = 0x29F8; break; + case 0x29FC: *ch = 0x29FD; break; + case 0x29FD: *ch = 0x29FC; break; + } + } else if ((*ch & 0xFF00) == 0x2A00) { + switch (*ch) { + case 0x2A2B: *ch = 0x2A2C; break; + case 0x2A2C: *ch = 0x2A2B; break; + case 0x2A2D: *ch = 0x2A2C; break; + case 0x2A2E: *ch = 0x2A2D; break; + case 0x2A34: *ch = 0x2A35; break; + case 0x2A35: *ch = 0x2A34; break; + case 0x2A3C: *ch = 0x2A3D; break; + case 0x2A3D: *ch = 0x2A3C; break; + case 0x2A64: *ch = 0x2A65; break; + case 0x2A65: *ch = 0x2A64; break; + case 0x2A79: *ch = 0x2A7A; break; + case 0x2A7A: *ch = 0x2A79; break; + case 0x2A7D: *ch = 0x2A7E; break; + case 0x2A7E: *ch = 0x2A7D; break; + case 0x2A7F: *ch = 0x2A80; break; + case 0x2A80: *ch = 0x2A7F; break; + case 0x2A81: *ch = 0x2A82; break; + case 0x2A82: *ch = 0x2A81; break; + case 0x2A83: *ch = 0x2A84; break; + case 0x2A84: *ch = 0x2A83; break; + case 0x2A8B: *ch = 0x2A8C; break; + case 0x2A8C: *ch = 0x2A8B; break; + case 0x2A91: *ch = 0x2A92; break; + case 0x2A92: *ch = 0x2A91; break; + case 0x2A93: *ch = 0x2A94; break; + case 0x2A94: *ch = 0x2A93; break; + case 0x2A95: *ch = 0x2A96; break; + case 0x2A96: *ch = 0x2A95; break; + case 0x2A97: *ch = 0x2A98; break; + case 0x2A98: *ch = 0x2A97; break; + case 0x2A99: *ch = 0x2A9A; break; + case 0x2A9A: *ch = 0x2A99; break; + case 0x2A9B: *ch = 0x2A9C; break; + case 0x2A9C: *ch = 0x2A9B; break; + case 0x2AA1: *ch = 0x2AA2; break; + case 0x2AA2: *ch = 0x2AA1; break; + case 0x2AA6: *ch = 0x2AA7; break; + case 0x2AA7: *ch = 0x2AA6; break; + case 0x2AA8: *ch = 0x2AA9; break; + case 0x2AA9: *ch = 0x2AA8; break; + case 0x2AAA: *ch = 0x2AAB; break; + case 0x2AAB: *ch = 0x2AAA; break; + case 0x2AAC: *ch = 0x2AAD; break; + case 0x2AAD: *ch = 0x2AAC; break; + case 0x2AAF: *ch = 0x2AB0; break; + case 0x2AB0: *ch = 0x2AAF; break; + case 0x2AB3: *ch = 0x2AB4; break; + case 0x2AB4: *ch = 0x2AB3; break; + case 0x2ABB: *ch = 0x2ABC; break; + case 0x2ABC: *ch = 0x2ABB; break; + case 0x2ABD: *ch = 0x2ABE; break; + case 0x2ABE: *ch = 0x2ABD; break; + case 0x2ABF: *ch = 0x2AC0; break; + case 0x2AC0: *ch = 0x2ABF; break; + case 0x2AC1: *ch = 0x2AC2; break; + case 0x2AC2: *ch = 0x2AC1; break; + case 0x2AC3: *ch = 0x2AC4; break; + case 0x2AC4: *ch = 0x2AC3; break; + case 0x2AC5: *ch = 0x2AC6; break; + case 0x2AC6: *ch = 0x2AC5; break; + case 0x2ACD: *ch = 0x2ACE; break; + case 0x2ACE: *ch = 0x2ACD; break; + case 0x2ACF: *ch = 0x2AD0; break; + case 0x2AD0: *ch = 0x2ACF; break; + case 0x2AD1: *ch = 0x2AD2; break; + case 0x2AD2: *ch = 0x2AD1; break; + case 0x2AD3: *ch = 0x2AD4; break; + case 0x2AD4: *ch = 0x2AD3; break; + case 0x2AD5: *ch = 0x2AD6; break; + case 0x2AD6: *ch = 0x2AD5; break; + case 0x2ADE: *ch = 0x22A6; break; + case 0x2AE3: *ch = 0x22A9; break; + case 0x2AE4: *ch = 0x22A8; break; + case 0x2AE5: *ch = 0x22AB; break; + case 0x2AEC: *ch = 0x2AED; break; + case 0x2AED: *ch = 0x2AEC; break; + case 0x2AF7: *ch = 0x2AF8; break; + case 0x2AF8: *ch = 0x2AF7; break; + case 0x2AF9: *ch = 0x2AFA; break; + case 0x2AFA: *ch = 0x2AF9; break; + } + } else if ((*ch & 0xFF00) == 0x3000) { + switch (*ch) { + case 0x3008: *ch = 0x3009; break; + case 0x3009: *ch = 0x3008; break; + case 0x300A: *ch = 0x300B; break; + case 0x300B: *ch = 0x300A; break; + case 0x300C: *ch = 0x300D; break; + case 0x300D: *ch = 0x300C; break; + case 0x300E: *ch = 0x300F; break; + case 0x300F: *ch = 0x300E; break; + case 0x3010: *ch = 0x3011; break; + case 0x3011: *ch = 0x3010; break; + case 0x3014: *ch = 0x3015; break; + case 0x3015: *ch = 0x3014; break; + case 0x3016: *ch = 0x3017; break; + case 0x3017: *ch = 0x3016; break; + case 0x3018: *ch = 0x3019; break; + case 0x3019: *ch = 0x3018; break; + case 0x301A: *ch = 0x301B; break; + case 0x301B: *ch = 0x301A; break; + } + } else if ((*ch & 0xFF00) == 0xFF00) { + switch (*ch) { + case 0xFF08: *ch = 0xFF09; break; + case 0xFF09: *ch = 0xFF08; break; + case 0xFF1C: *ch = 0xFF1E; break; + case 0xFF1E: *ch = 0xFF1C; break; + case 0xFF3B: *ch = 0xFF3D; break; + case 0xFF3D: *ch = 0xFF3B; break; + case 0xFF5B: *ch = 0xFF5D; break; + case 0xFF5D: *ch = 0xFF5B; break; + case 0xFF5F: *ch = 0xFF60; break; + case 0xFF60: *ch = 0xFF5F; break; + case 0xFF62: *ch = 0xFF63; break; + case 0xFF63: *ch = 0xFF62; break; + } + } +} + +#ifdef TEST_GETTYPE + +#include +#include + +int main(int argc, char **argv) +{ + static const struct { int type; char *name; } typetoname[] = { +#define TYPETONAME(X) { X , #X } + TYPETONAME(L), + TYPETONAME(LRE), + TYPETONAME(LRO), + TYPETONAME(R), + TYPETONAME(AL), + TYPETONAME(RLE), + TYPETONAME(RLO), + TYPETONAME(PDF), + TYPETONAME(EN), + TYPETONAME(ES), + TYPETONAME(ET), + TYPETONAME(AN), + TYPETONAME(CS), + TYPETONAME(NSM), + TYPETONAME(BN), + TYPETONAME(B), + TYPETONAME(S), + TYPETONAME(WS), + TYPETONAME(ON), +#undef TYPETONAME + }; + int i; + + for (i = 1; i < argc; i++) { + unsigned long chr = strtoul(argv[i], NULL, 0); + int type = getType(chr); + assert(typetoname[type].type == type); + printf("U+%04x: %s\n", (unsigned)chr, typetoname[type].name); + } + + return 0; +} + +#endif diff --git a/0.77_My_PuTTY/misc.c b/0.77_My_PuTTY/misc.c new file mode 100644 index 0000000..e0421f7 --- /dev/null +++ b/0.77_My_PuTTY/misc.c @@ -0,0 +1,422 @@ +/* + * Platform-independent routines shared between all PuTTY programs. + * + * This file contains functions that use the kind of infrastructure + * like conf.c that tends to only live in the main applications, or + * that do things that only something like a main PuTTY application + * would need. So standalone test programs should generally be able to + * avoid linking against it. + * + * More standalone functions that depend on nothing but the C library + * live in utils.c. + */ + +#include +#include +#include +#include +#include +#include + +#include "defs.h" +#include "putty.h" +#include "misc.h" + +#define BASE64_CHARS_NOEQ \ + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/" +#define BASE64_CHARS_ALL BASE64_CHARS_NOEQ "=" + +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 */ +} + +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->to_server = true; /* to be on the safe side */ + p->name = p->instruction = NULL; + p->name_reqd = p->instr_reqd = false; + return p; +} +void add_prompt(prompts_t *p, char *promptstr, 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_datapl(pr->result, ptrlen_from_asciz(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; + 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); +} + +/* + * Determine whether or not a Conf represents a session which can + * sensibly be launched right now. + */ +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; +} + +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); +} + +/* + * 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. + */ +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 (strlen(r) == 43 && r[strspn(r, BASE64_CHARS_NOEQ)] == 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(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 */ + + strcpy(key, q); + return true; + } + not_ssh2_blob:; + } + + return false; +} + +char *buildinfo(const char *newline) +{ + strbuf *buf = strbuf_new(); + + strbuf_catf(buf, "Build platform: %d-bit %s", + (int)(CHAR_BIT * sizeof(void *)), + BUILDINFO_PLATFORM); + +#ifdef __clang_version__ +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: clang %s", newline, __clang_version__); +#elif defined __GNUC__ && defined __VERSION__ +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: gcc %s", newline, __VERSION__); +#endif + +#if defined _MSC_VER +#ifndef FOUND_COMPILER +#define FOUND_COMPILER + strbuf_catf(buf, "%sCompiler: ", newline); +#else + strbuf_catf(buf, ", emulating "); +#endif + strbuf_catf(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. + */ +#elif _MSC_VER == 1928 && _MSC_FULL_VER >= 192829500 + /* + * 16.9 and 16.8 have the same _MSC_VER value, and have to be + * distinguished by _MSC_FULL_VER. As of 2021-03-04 that is not + * mentioned on the above page, but see e.g. + * https://developercommunity.visualstudio.com/t/the-169-cc-compiler-still-uses-the-same-version-nu/1335194#T-N1337120 + * which says that 16.9 builds will have versions starting at + * 19.28.29500.* and going up. Hence, 19 28 29500 is what we + * compare _MSC_FULL_VER against above. + */ + strbuf_catf(buf, " 2019 (16.9)"); +#elif _MSC_VER == 1928 + strbuf_catf(buf, " 2019 (16.8)"); +#elif _MSC_VER == 1927 + strbuf_catf(buf, " 2019 (16.7)"); +#elif _MSC_VER == 1926 + strbuf_catf(buf, " 2019 (16.6)"); +#elif _MSC_VER == 1925 + strbuf_catf(buf, " 2019 (16.5)"); +#elif _MSC_VER == 1924 + strbuf_catf(buf, " 2019 (16.4)"); +#elif _MSC_VER == 1923 + strbuf_catf(buf, " 2019 (16.3)"); +#elif _MSC_VER == 1922 + strbuf_catf(buf, " 2019 (16.2)"); +#elif _MSC_VER == 1921 + strbuf_catf(buf, " 2019 (16.1)"); +#elif _MSC_VER == 1920 + strbuf_catf(buf, " 2019 (16.0)"); +#elif _MSC_VER == 1916 + strbuf_catf(buf, " 2017 version 15.9"); +#elif _MSC_VER == 1915 + strbuf_catf(buf, " 2017 version 15.8"); +#elif _MSC_VER == 1914 + strbuf_catf(buf, " 2017 version 15.7"); +#elif _MSC_VER == 1913 + strbuf_catf(buf, " 2017 version 15.6"); +#elif _MSC_VER == 1912 + strbuf_catf(buf, " 2017 version 15.5"); +#elif _MSC_VER == 1911 + strbuf_catf(buf, " 2017 version 15.3"); +#elif _MSC_VER == 1910 + strbuf_catf(buf, " 2017 RTW (15.0)"); +#elif _MSC_VER == 1900 + strbuf_catf(buf, " 2015 (14.0)"); +#elif _MSC_VER == 1800 + strbuf_catf(buf, " 2013 (12.0)"); +#elif _MSC_VER == 1700 + strbuf_catf(buf, " 2012 (11.0)"); +#elif _MSC_VER == 1600 + strbuf_catf(buf, " 2010 (10.0)"); +#elif _MSC_VER == 1500 + strbuf_catf(buf, " 2008 (9.0)"); +#elif _MSC_VER == 1400 + strbuf_catf(buf, " 2005 (8.0)"); +#elif _MSC_VER == 1310 + strbuf_catf(buf, " .NET 2003 (7.1)"); +#elif _MSC_VER == 1300 + strbuf_catf(buf, " .NET 2002 (7.0)"); +#elif _MSC_VER == 1200 + strbuf_catf(buf, " 6.0"); +#else + strbuf_catf(buf, ", unrecognised version"); +#endif + strbuf_catf(buf, ", _MSC_VER=%d", (int)_MSC_VER); +#endif + +#ifdef BUILDINFO_GTK + { + char *gtk_buildinfo = buildinfo_gtk_version(); + if (gtk_buildinfo) { + strbuf_catf(buf, "%sCompiled against GTK version %s", + newline, gtk_buildinfo); + sfree(gtk_buildinfo); + } + } +#endif +#if defined _WINDOWS + { + int echm = has_embedded_chm(); + if (echm >= 0) + strbuf_catf(buf, "%sEmbedded HTML Help file: %s", newline, + echm ? "yes" : "no"); + } +#endif + +#if defined _WINDOWS && defined MINEFIELD + strbuf_catf(buf, "%sBuild option: MINEFIELD", newline); +#endif +#ifdef NO_SECURITY + strbuf_catf(buf, "%sBuild option: NO_SECURITY", newline); +#endif +#ifdef NO_SECUREZEROMEMORY + strbuf_catf(buf, "%sBuild option: NO_SECUREZEROMEMORY", newline); +#endif +#ifdef NO_IPV6 + strbuf_catf(buf, "%sBuild option: NO_IPV6", newline); +#endif +#ifdef NO_GSSAPI + strbuf_catf(buf, "%sBuild option: NO_GSSAPI", newline); +#endif +#ifdef STATIC_GSSAPI + strbuf_catf(buf, "%sBuild option: STATIC_GSSAPI", newline); +#endif +#ifdef UNPROTECT + strbuf_catf(buf, "%sBuild option: UNPROTECT", newline); +#endif +#ifdef FUZZING + strbuf_catf(buf, "%sBuild option: FUZZING", newline); +#endif +#ifdef DEBUG + strbuf_catf(buf, "%sBuild option: DEBUG", newline); +#endif + + strbuf_catf(buf, "%sSource commit: %s", newline, commitid); + + return strbuf_to_str(buf); +} + +size_t nullseat_output( + Seat *seat, bool is_stderr, const void *data, size_t len) { return 0; } +bool nullseat_eof(Seat *seat) { return true; } +int nullseat_get_userpass_input( + Seat *seat, prompts_t *p, bufchain *input) { return 0; } +void nullseat_notify_remote_exit(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) { return NULL; } +void nullseat_set_busy_status(Seat *seat, BusyStatus status) {} +int nullseat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, + void (*callback)(void *ctx, int result), void *ctx) { return 0; } +int nullseat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) { return 0; } +int nullseat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) { return 0; } +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;} +bool nullseat_set_trust_status(Seat *seat, bool tr) { return false; } +bool nullseat_set_trust_status_vacuously(Seat *seat, bool tr) { return true; } +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; } + +bool null_lp_verbose_no(LogPolicy *lp) { return false; } +bool null_lp_verbose_yes(LogPolicy *lp) { return true; } + +void sk_free_peer_info(SocketPeerInfo *pi) +{ + if (pi) { + sfree((char *)pi->addr_text); + sfree((char *)pi->log_text); + sfree(pi); + } +} + +void out_of_memory(void) +{ + modalfatalbox("Out of memory"); +} diff --git a/0.77_My_PuTTY/misc.h b/0.77_My_PuTTY/misc.h new file mode 100644 index 0000000..cdbb184 --- /dev/null +++ b/0.77_My_PuTTY/misc.h @@ -0,0 +1,442 @@ +/* + * Header for misc.c. + */ + +#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 misc.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); + +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 */ +void strbuf_catf(strbuf *buf, const char *fmt, ...) PRINTF_LIKE(2, 3); +void strbuf_catfv(strbuf *buf, const char *fmt, va_list ap); +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. + * + * These actually live in miscucs.c, not misc.c (the distinction being + * that the former is only linked into tools that also have the main + * Unicode support). */ +wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len); +wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string); + +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); + +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_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 utils.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 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); +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)) + +/* 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 false for mismatch or true for equality (unlike memcmp), + * hinted at by the 'eq' in the name. */ +bool 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); + +/* 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); +} + +/* 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); + +#endif diff --git a/0.77_My_PuTTY/miscucs.c b/0.77_My_PuTTY/miscucs.c new file mode 100644 index 0000000..7785f9b --- /dev/null +++ b/0.77_My_PuTTY/miscucs.c @@ -0,0 +1,28 @@ +/* + * Centralised Unicode-related helper functions, separate from misc.c + * so that they can be omitted from tools that aren't including + * Unicode handling. + */ + +#include "putty.h" +#include "misc.h" + +wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len) +{ + int mult; + for (mult = 1 ;; mult++) { + wchar_t *ret = snewn(mult*len + 2, wchar_t); + int outlen; + outlen = mb_to_wc(codepage, flags, string, len, ret, mult*len + 1); + if (outlen < mult*len+1) { + ret[outlen] = L'\0'; + return ret; + } + sfree(ret); + } +} + +wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string) +{ + return dup_mb_to_wc_c(codepage, flags, string, strlen(string)); +} diff --git a/0.77_My_PuTTY/mpint.c b/0.77_My_PuTTY/mpint.c new file mode 100644 index 0000000..069c3b2 --- /dev/null +++ b/0.77_My_PuTTY/mpint.c @@ -0,0 +1,2646 @@ +#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_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; +} + +static void monty_reduce(MontyContext *mc, mp_int *x) +{ + mp_int reduced = monty_reduce_internal(mc, x, *mc->scratch); + mp_copy_into(x, &reduced); + mp_clear(mc->scratch); +} + +mp_int *monty_pow(MontyContext *mc, mp_int *base, mp_int *exponent) +{ + /* square builds up powers of the form base^{2^i}. */ + mp_int *square = mp_copy(base); + size_t i = 0; + + /* out accumulates the output value. Starts at 1 (in Montgomery + * representation) and we multiply in each base^{2^i}. */ + mp_int *out = mp_copy(mc->powers_of_r_mod_m[0]); + + /* tmp holds each product we compute and reduce. */ + mp_int *tmp = mp_make_sized(mc->rw * 2); + + while (true) { + mp_mul_into(tmp, out, square); + monty_reduce(mc, tmp); + mp_select_into(out, out, tmp, mp_get_bit(exponent, i)); + + if (++i >= exponent->nw * BIGNUM_INT_BITS) + break; + + mp_mul_into(tmp, square, square); + monty_reduce(mc, tmp); + mp_copy_into(square, tmp); + } + + mp_free(square); + mp_free(tmp); + 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); + mp_add_integer_into_shifted_by_words( + two_R, two_R, (BignumInt)1 << ((log2_R+1) % BIGNUM_INT_BITS), + (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; +} + +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/0.77_My_PuTTY/mpint.h b/0.77_My_PuTTY/mpint.h new file mode 100644 index 0000000..b64e05b --- /dev/null +++ b/0.77_My_PuTTY/mpint.h @@ -0,0 +1,445 @@ +#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); + +/* + * 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); + +/* + * 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 random.c, and the macro wrappers automate that. + * + * (This is a bit of a dodge to avoid mpint.c having a link-time + * dependency on random.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/0.77_My_PuTTY/mpint_i.h b/0.77_My_PuTTY/mpint_i.h new file mode 100644 index 0000000..6cb925d --- /dev/null +++ b/0.77_My_PuTTY/mpint_i.h @@ -0,0 +1,324 @@ +/* + * 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 bignum.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/0.77_My_PuTTY/mpunsafe.c b/0.77_My_PuTTY/mpunsafe.c new file mode 100644 index 0000000..8f3dd3e --- /dev/null +++ b/0.77_My_PuTTY/mpunsafe.c @@ -0,0 +1,59 @@ +#include +#include +#include + +#include "defs.h" +#include "misc.h" +#include "puttymem.h" + +#include "mpint.h" +#include "mpint_i.h" + +/* + * This global symbol is also defined in ssh2kex-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.) + */ +#ifndef MOD_PERSO +const int deliberate_symbol_clash = 12345; +#endif + +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; +} + +uint32_t mp_unsafe_mod_integer(mp_int *x, uint32_t modulus) +{ + uint64_t accumulator = 0; + for (size_t i = mp_max_bytes(x); i-- > 0 ;) { + accumulator = 0x100 * accumulator + mp_get_byte(x, i); + accumulator %= modulus; + } + return accumulator; +} diff --git a/0.77_My_PuTTY/mpunsafe.h b/0.77_My_PuTTY/mpunsafe.h new file mode 100644 index 0000000..6d382f4 --- /dev/null +++ b/0.77_My_PuTTY/mpunsafe.h @@ -0,0 +1,46 @@ +/* + * 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); + +/* + * Compute the residue of x mod m. This is implemented in the most + * obvious way using the C % operator, which won't be constant-time on + * many C implementations. + */ +uint32_t mp_unsafe_mod_integer(mp_int *x, uint32_t m); + +#endif /* PUTTY_MPINT_UNSAFE_H */ diff --git a/0.77_My_PuTTY/network.h b/0.77_My_PuTTY/network.h new file mode 100644 index 0000000..b2a5e4c --- /dev/null +++ b/0.77_My_PuTTY/network.h @@ -0,0 +1,314 @@ +/* + * 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; + +struct PlugVtable { + void (*log)(Plug *p, PlugLogType type, SockAddr *addr, int port, + const char *error_msg, int error_code); + /* + * Passes the client progress reports on the process of setting + * up the connection. + * + * - 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 + * connecting to address `addr'. + * + * - 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. + */ + void (*closing) + (Plug *p, const char *error_msg, int error_code, bool calling_back); + /* error_msg is NULL iff it is not an error (ie it closed normally) */ + /* calling_back != 0 iff there is a Plug function */ + /* currently running (would cure the fixme in try_send()) */ + void (*receive) (Plug *p, int urgent, const char *data, size_t len); + /* + * - urgent==0. `data' points to `len' bytes of perfectly + * ordinary data. + * + * - urgent==1. `data' points to `len' bytes of data, + * which were read from before an Urgent pointer. + * + * - urgent==2. `data' points to `len' bytes of data, + * the first of which was the one at the Urgent mark. + */ + void (*sent) (Plug *p, size_t bufsize); + /* + * The `sent' function is called when the pending send backlog + * on a socket is cleared or partially cleared. The new backlog + * size is passed in the `bufsize' parameter. + */ + int (*accepting)(Plug *p, accept_fn_t constructor, accept_ctx_t ctx); + /* + * `accepting' is called only on listener-type sockets, and is + * passed a constructor function+context that will create a fresh + * Socket describing the connection. It returns nonzero if it + * doesn't want the connection for some reason, or 0 on success. + */ +}; + +/* proxy indirection layer */ +/* NB, control of 'addr' is passed via new_connection, which takes + * responsibility for freeing it */ +Socket *new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf); +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); + +/* 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, const char *msg, int code, bool calling_back) +{ p->vt->closing(p, msg, code, calling_back); } +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 misc.c). + */ +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 ssh.c. Returns the + * port number, in host byte order (suitable for printf and so on). + * Returns 0 on failure. Any platform not supporting getservbyname + * can just return 0 - this function is not required to handle + * numeric port specifications. + */ +int net_service_lookup(char *service); + +/* + * Look up the local hostname; return value needs freeing. + * May return NULL. + */ +char *get_hostname(void); + +/* + * Trivial socket implementation which just stores an error. Found in + * errsock.c. + * + * 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; + +/* ---------------------------------------------------------------------- + * 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. + */ + +/* + * Exports from be_misc.c. + */ +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; +} ProxyStderrBuf; +void psb_init(ProxyStderrBuf *psb); +void log_proxy_stderr( + Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len); + +#endif diff --git a/0.77_My_PuTTY/nocmdline.c b/0.77_My_PuTTY/nocmdline.c new file mode 100644 index 0000000..846c589 --- /dev/null +++ b/0.77_My_PuTTY/nocmdline.c @@ -0,0 +1,37 @@ +/* + * nocmdline.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. + */ +int cmdline_get_passwd_input(prompts_t *p) +{ + return -1; +} + +/* + * The main cmdline_process_param function is normally called from + * applications' main(). An application linking against this stub + * module shouldn't have a main() that calls it in the first place :-) + * but it is just occasionally called by other supporting functions, + * such as one in uxputty.c which sometimes handles a non-option + * argument by making up equivalent options and passing them back to + * this function. So we have to provide a link-time stub of this + * function, but it had better not end up being called at run time. + */ +int cmdline_process_param(const char *p, char *value, + int need_save, Conf *conf) +{ + unreachable("cmdline_process_param should never be called"); +} diff --git a/0.77_My_PuTTY/nocproxy.c b/0.77_My_PuTTY/nocproxy.c new file mode 100644 index 0000000..45e36a3 --- /dev/null +++ b/0.77_My_PuTTY/nocproxy.c @@ -0,0 +1,35 @@ +/* + * 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" + +void proxy_socks5_offerencryptedauth(BinarySink *bs) +{ + /* For telnet, don't add any new encrypted authentication routines */ +} + +int proxy_socks5_handlechap (ProxySocket *p) +{ + + plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" + " in telnet-only build", + PROXY_ERROR_GENERAL, 0); + return 1; +} + +int proxy_socks5_selectchap(ProxySocket *p) +{ + plug_closing(p->plug, "Proxy error: Trying to handle a SOCKS5 CHAP request" + " in telnet-only build", + PROXY_ERROR_GENERAL, 0); + return 1; +} diff --git a/0.77_My_PuTTY/nogss.c b/0.77_My_PuTTY/nogss.c new file mode 100644 index 0000000..844a132 --- /dev/null +++ b/0.77_My_PuTTY/nogss.c @@ -0,0 +1,11 @@ +/* + * Stub definitions of the GSSAPI library list, for Unix pterm and + * any other application that needs the symbols defined but has no + * use for them. + */ + +#include "putty.h" + +const int ngsslibs = 0; +const char *const gsslibnames[1] = { "dummy" }; +const struct keyvalwhere gsslibkeywords[1] = { { "dummy", 0, -1, -1 } }; diff --git a/0.77_My_PuTTY/noprint.c b/0.77_My_PuTTY/noprint.c new file mode 100644 index 0000000..d17350b --- /dev/null +++ b/0.77_My_PuTTY/noprint.c @@ -0,0 +1,38 @@ +/* + * Stub implementation of the printing interface for PuTTY, for the + * benefit of non-printing terminal applications. + */ + +#include +#include +#include "putty.h" + +struct printer_job_tag { + int dummy; +}; + +printer_job *printer_start_job(char *printer) +{ + return NULL; +} + +void printer_job_data(printer_job *pj, 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/0.77_My_PuTTY/noproxy.c b/0.77_My_PuTTY/noproxy.c new file mode 100644 index 0000000..e751088 --- /dev/null +++ b/0.77_My_PuTTY/noproxy.c @@ -0,0 +1,32 @@ +/* + * 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) +{ + 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/0.77_My_PuTTY/norand.c b/0.77_My_PuTTY/norand.c new file mode 100644 index 0000000..9a6ce39 --- /dev/null +++ b/0.77_My_PuTTY/norand.c @@ -0,0 +1,22 @@ +/* + * 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/0.77_My_PuTTY/noshare.c b/0.77_My_PuTTY/noshare.c new file mode 100644 index 0000000..3cddc1a --- /dev/null +++ b/0.77_My_PuTTY/noshare.c @@ -0,0 +1,25 @@ +/* + * Stub implementation of SSH connection-sharing IPC, for any + * platform which can't support it at all. + */ + +#include +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "ssh.h" +#include "network.h" + +int platform_ssh_share(const char *name, Conf *conf, + Plug *downplug, Plug *upplug, Socket **sock, + char **logtext, char **ds_err, char **us_err, + bool can_upstream, bool can_downstream) +{ + return SHARE_NONE; +} + +void platform_ssh_share_cleanup(const char *name) +{ +} diff --git a/0.77_My_PuTTY/noterm.c b/0.77_My_PuTTY/noterm.c new file mode 100644 index 0000000..4ca99fa --- /dev/null +++ b/0.77_My_PuTTY/noterm.c @@ -0,0 +1,11 @@ +/* + * Stubs of functions in terminal.c, for use in programs that don't + * have a terminal. + */ + +#include "putty.h" +#include "terminal.h" + +void term_nopaste(Terminal *term) +{ +} diff --git a/0.77_My_PuTTY/notiming.c b/0.77_My_PuTTY/notiming.c new file mode 100644 index 0000000..5fe4403 --- /dev/null +++ b/0.77_My_PuTTY/notiming.c @@ -0,0 +1,21 @@ +/* + * notiming.c: stub version of timing API. + * + * Used in any tool which needs a subsystem linked against the + * timing API but doesn't want to actually provide timing. For + * example, key generation tools need the random number generator, + * but they don't want the hassle of calling noise_regular() at + * regular intervals - and they don't _need_ it either, since they + * have their own rigorous and different means of noise collection. + */ + +#include "putty.h" + +unsigned long schedule_timer(int ticks, timer_fn_t fn, void *ctx) +{ + return 0; +} + +void expire_timer_context(void *ctx) +{ +} diff --git a/0.77_My_PuTTY/nullplug.c b/0.77_My_PuTTY/nullplug.c new file mode 100644 index 0000000..6011437 --- /dev/null +++ b/0.77_My_PuTTY/nullplug.c @@ -0,0 +1,42 @@ +/* + * 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" + +static void nullplug_socket_log(Plug *plug, PlugLogType type, SockAddr *addr, + int port, const char *err_msg, int err_code) +{ +} + +static void nullplug_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ +} + +static void nullplug_receive( + Plug *plug, int urgent, const char *data, size_t len) +{ +} + +static void nullplug_sent(Plug *plug, size_t bufsize) +{ +} + +static const PlugVtable nullplug_plugvt = { + .log = nullplug_socket_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/0.77_My_PuTTY/pageant.c b/0.77_My_PuTTY/pageant.c new file mode 100644 index 0000000..79b9986 --- /dev/null +++ b/0.77_My_PuTTY/pageant.c @@ -0,0 +1,2573 @@ +/* + * 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. + */ + #ifndef MOD_INTEGRATED_AGENT +int random_byte(void) +{ + modalfatalbox("Internal error: attempt to use random numbers in Pageant"); + exit(0); + return 0; /* unreachable, but placate optimiser */ +} +void random_read(void *buf, size_t size) +{ + modalfatalbox("Internal error: attempt to use random numbers in Pageant"); +} +#endif + +static bool pageant_local = false; + +struct PageantClientDialogId { + int dummy; +}; + +typedef struct PageantKeySort PageantKeySort; +typedef struct PageantKey PageantKey; +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 list of all the keys we have stored, in any form at all. + */ +static tree234 *keytree; +struct PageantKeySort { + /* Prefix of the main PageantKey structure which contains all the + * data that the sorting order depends on. Also simple enough that + * you can construct one for lookup purposes. */ + int ssh_version; /* 1 or 2; primary sort key */ + ptrlen public_blob; /* secondary sort key */ +}; +struct PageantKey { + PageantKeySort sort; + strbuf *public_blob; /* the true owner of sort.public_blob */ + char *comment; /* stored separately, whether or not in rkey/skey */ + union { + RSAKey *rkey; /* if ssh_version == 1 */ + ssh2_userkey *skey; /* if ssh_version == 2 */ + }; + strbuf *encrypted_key_file; + bool decryption_prompt_active; + PageantKeyRequestNode blocked_requests; + PageantClientDialogId dlgid; +}; + +typedef struct PageantSignOp PageantSignOp; +struct PageantSignOp { + PageantKey *pk; + 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 void failure(PageantClient *pc, PageantClientRequestId *reqid, + strbuf *sb, unsigned char type, const char *fmt, ...); +static void fail_requests_for_key(PageantKey *pk, const char *reason); +static PageantKey *pageant_nth_key(int ssh_version, int i); + +static void pk_free(PageantKey *pk) +{ + if (pk->public_blob) strbuf_free(pk->public_blob); + sfree(pk->comment); + if (pk->sort.ssh_version == 1 && pk->rkey) { + freersakey(pk->rkey); + sfree(pk->rkey); + } + if (pk->sort.ssh_version == 2 && pk->skey) { + sfree(pk->skey->comment); + ssh_key_free(pk->skey->key); + sfree(pk->skey); + } + if (pk->encrypted_key_file) strbuf_free(pk->encrypted_key_file); + fail_requests_for_key(pk, "key deleted from Pageant while signing " + "request was pending"); + sfree(pk); +} + +static int cmpkeys(void *av, void *bv) +{ + PageantKeySort *a = (PageantKeySort *)av, *b = (PageantKeySort *)bv; + + if (a->ssh_version != b->ssh_version) + return a->ssh_version < b->ssh_version ? -1 : +1; + else + return ptrlen_strcmp(a->public_blob, b->public_blob); +} + +static inline PageantKeySort keysort(int version, ptrlen blob) +{ + PageantKeySort sort; + sort.ssh_version = version; + sort.public_blob = blob; + return sort; +} + +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 *makeblob2(ssh2_userkey *skey) +{ + strbuf *blob = strbuf_new(); + ssh_key_public_blob(skey->key, BinarySink_UPCAST(blob)); + return blob; +} + +static PageantKey *findkey1(RSAKey *reqkey) +{ + strbuf *blob = makeblob1(reqkey); + PageantKeySort sort = keysort(1, ptrlen_from_strbuf(blob)); + PageantKey *toret = find234(keytree, &sort, NULL); + strbuf_free(blob); + return toret; +} + +static PageantKey *findkey2(ptrlen blob) +{ + PageantKeySort sort = keysort(2, blob); + return find234(keytree, &sort, NULL); +} + +static int find_first_key_for_version(int ssh_version) +{ + PageantKeySort sort = keysort(ssh_version, PTRLEN_LITERAL("")); + int pos; + if (findrelpos234(keytree, &sort, NULL, REL234_GE, &pos)) + return pos; + return count234(keytree); +} + +static int count_keys(int ssh_version) +{ + return (find_first_key_for_version(ssh_version + 1) - + find_first_key_for_version(ssh_version)); +} +int pageant_count_ssh1_keys(void) { return count_keys(1); } +int pageant_count_ssh2_keys(void) { return count_keys(2); } + +static bool pageant_add_ssh1_key(RSAKey *rkey) +{ + PageantKey *pk = snew(PageantKey); + memset(pk, 0, sizeof(PageantKey)); + pk->sort.ssh_version = 1; + pk->public_blob = makeblob1(rkey); + pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); + pk->blocked_requests.next = pk->blocked_requests.prev = + &pk->blocked_requests; + + if (add234(keytree, pk) == pk) { + pk->rkey = rkey; + if (rkey->comment) + pk->comment = dupstr(rkey->comment); + return true; + } else { + pk_free(pk); + return false; + } +} + +static bool pageant_add_ssh2_key(ssh2_userkey *skey) +{ + PageantKey *pk = snew(PageantKey); + memset(pk, 0, sizeof(PageantKey)); + pk->sort.ssh_version = 2; + pk->public_blob = makeblob2(skey); + pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); + pk->blocked_requests.next = pk->blocked_requests.prev = + &pk->blocked_requests; + + PageantKey *pk_in_tree = add234(keytree, pk); + if (pk_in_tree == pk) { + /* The key wasn't in the tree at all, and we've just added it. */ + pk->skey = skey; + if (skey->comment) + pk->comment = dupstr(skey->comment); + return true; + } else if (!pk_in_tree->skey) { + /* The key was only stored encrypted, and now we have an + * unencrypted version to add to the existing record. */ + pk_in_tree->skey = skey; + pk_free(pk); + return true; + } else { + /* The key was already in the tree in full. */ + pk_free(pk); + return false; + } +} + +static void remove_all_keys(int ssh_version) +{ + int start = find_first_key_for_version(ssh_version); + int end = find_first_key_for_version(ssh_version + 1); + while (end > start) { + PageantKey *pk = delpos234(keytree, --end); + assert(pk->sort.ssh_version == ssh_version); + pk_free(pk); + } +} + +static void list_keys(BinarySink *bs, int ssh_version, bool extended) +{ + int i; + PageantKey *pk; + + put_uint32(bs, count_keys(ssh_version)); + for (i = find_first_key_for_version(ssh_version); + NULL != (pk = index234(keytree, i)); i++) { + if (pk->sort.ssh_version != ssh_version) + break; + + if (ssh_version > 1) + put_stringpl(bs, pk->sort.public_blob); + else + put_datapl(bs, pk->sort.public_blob); /* no header */ + + put_stringpl(bs, ptrlen_from_asciz(pk->comment)); + + if (extended) { + /* + * 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. + */ + strbuf *sb = strbuf_new(); + + uint32_t flags = 0; + if (!pk->skey) + flags |= LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY; + if (pk->encrypted_key_file) + flags |= LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE; + put_uint32(sb, flags); + + put_stringsb(bs, sb); + } + } +} + +#ifdef MOD_PERSO +int GetScrumbleKeyFlag(void) ; +static int * tab_int = NULL ; +void scrumble_int(void) { + int count = pageant_count_ssh2_keys(), s, i, j ; + bool test ; + if( count>0 ) { + if( GetScrumbleKeyFlag() ) { + if( tab_int != NULL ) { free(tab_int) ; tab_int = NULL ; } + tab_int = (int*)malloc( count*sizeof(int) ) ; + if( count==1 ) { tab_int[0] = 0 ; } + else { + tab_int[0] = rand() % count ; + for( i=1 ; iinfo = 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(PageantSignOp *so) +{ + assert(!so->pkr.prev); + assert(!so->pkr.next); + + so->pkr.prev = so->pk->blocked_requests.prev; + so->pkr.next = &so->pk->blocked_requests; + 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; + } 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, PageantKey *pk) +{ + if (!pk->decryption_prompt_active) { + assert(!gui_request_in_progress); + + bool created_dlg = pageant_client_ask_passphrase( + pc, &pk->dlgid, pk->comment); + + if (!created_dlg) + return false; + + gui_request_in_progress = true; + pk->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->pk->skey && gui_request_in_progress) + crReturnV; + + if (!so->pk->skey) { + assert(so->pk->encrypted_key_file); + + if (!request_passphrase(so->pao.info->pc, so->pk)) { + 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(so); + crReturnV; + signop_unlink(so); + } + + uint32_t supported_flags = ssh_key_alg(so->pk->skey->key)->supported_flags; + 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->pk->skey->key, 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->pk->skey->key, 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(PageantKey *pk, const char *reason) +{ + while (pk->blocked_requests.next != &pk->blocked_requests) { + PageantSignOp *so = container_of(pk->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(PageantKey *pk) +{ + for (PageantKeyRequestNode *pkr = pk->blocked_requests.next; + pkr != &pk->blocked_requests; pkr = pkr->next) { + PageantSignOp *so = container_of(pk->blocked_requests.next, + PageantSignOp, pkr); + queue_toplevel_callback(pageant_async_op_callback, &so->pao); + } +} + +void pageant_passphrase_request_success(PageantClientDialogId *dlgid, + ptrlen passphrase) +{ + PageantKey *pk = container_of(dlgid, PageantKey, dlgid); + + assert(gui_request_in_progress); + gui_request_in_progress = false; + pk->decryption_prompt_active = false; + + if (!pk->skey) { + const char *error; + + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( + pk->encrypted_key_file)); + + strbuf *ppsb = strbuf_new_nm(); + put_datapl(ppsb, passphrase); + + pk->skey = ppk_load_s(src, ppsb->s, &error); + + strbuf_free(ppsb); + + if (!pk->skey) { + fail_requests_for_key(pk, "unable to decrypt key"); + return; + } else if (pk->skey == SSH2_WRONG_PASSPHRASE) { + pk->skey = NULL; + + /* + * Find a PageantClient to use for another attempt at + * request_passphrase. + */ + PageantKeyRequestNode *pkr = pk->blocked_requests.next; + if (pkr == &pk->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(pk->blocked_requests.next, + PageantSignOp, pkr); + + pk->decryption_prompt_active = false; + if (!request_passphrase(so->pao.info->pc, pk)) { + fail_requests_for_key(pk, "unable to continue creating " + "passphrase prompts"); + } + return; + } else { + keylist_update(); + } + } + + unblock_requests_for_key(pk); +} + +void pageant_passphrase_request_refused(PageantClientDialogId *dlgid) +{ + PageantKey *pk = container_of(dlgid, PageantKey, dlgid); + + assert(gui_request_in_progress); + gui_request_in_progress = false; + pk->decryption_prompt_active = false; + + fail_requests_for_key(pk, "user refused to supply passphrase"); +} + +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(PageantKey *pk) +{ + if (pk->sort.ssh_version != 2) { + /* + * We don't support storing SSH-1 keys in encrypted form at + * all. + */ + return false; + } + + if (!pk->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 pk->skey if it exists. But we return success + * regardless, so that 'please ensure this key isn't stored + * decrypted' is idempotent. */ + if (pk->skey) { + sfree(pk->skey->comment); + ssh_key_free(pk->skey->key); + sfree(pk->skey); + pk->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; + PageantKey *pk; + for (i = 0; NULL != (pk = pageant_nth_key(1, i)); i++) { + char *fingerprint = rsa_ssh1_fingerprint(pk->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; + PageantKey *pk; + for (i = 0; NULL != (pk = pageant_nth_key(2, i)); i++) { + char *fingerprint = ssh2_fingerprint_blob( + ptrlen_from_strbuf(pk->public_blob), SSH_FPTYPE_DEFAULT); + pageant_client_log(pc, reqid, "returned key: %s %s", + fingerprint, pk->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; + PageantKey *pk; + 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 ((pk = findkey1(&reqkey)) == NULL) { + fail("key not found"); + goto challenge1_cleanup; + } +#ifdef MOD_PERSO + char *fingerprint; + fingerprint = rsa_ssh1_fingerprint(pk->rkey); + if (! confirm_key_usage(fingerprint, pk->rkey->comment)) { + goto challenge1_cleanup; + } +#endif + response = rsa_ssh1_decrypt(challenge, pk->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. + */ + PageantKey *pk; + 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_fingerprint_blob( + keyblob, SSH_FPTYPE_DEFAULT); + pageant_client_log(pc, reqid, "requested key: %s", fingerprint); + sfree(fingerprint); + } + if ((pk = findkey2(keyblob)) == NULL) { + fail("key not found"); + goto responded; + } +#ifdef MOD_PERSO +void debug_log( const char *fmt, ... ) { + char filename[4096]="" ; + va_list ap; + FILE *fp ; + strcpy(filename,"kitty.log"); + + va_start( ap, fmt ) ; + //vfprintf( stdout, fmt, ap ) ; // Ecriture a l'ecran + if( ( fp = fopen( filename, "ab" ) ) != NULL ) { + vfprintf( fp, fmt, ap ) ; // ecriture dans un fichier + fclose( fp ) ; + } + + va_end( ap ) ; +} + + char* confirm_fingerprint; + confirm_fingerprint = ssh2_fingerprint_blob(keyblob, SSH_FPTYPE_DEFAULT); + if( confirm_fingerprint!=NULL ) { + if( pk->comment != NULL ) { + if (! confirm_key_usage( confirm_fingerprint, pk->comment)) { + if( confirm_fingerprint!=NULL ) sfree(confirm_fingerprint); + fail("unconfirmed key"); + pk=NULL; + goto responded; + //return NULL; + } + } /*else if( pk->rkey->comment != NULL ) { + if (! confirm_key_usage( confirm_fingerprint, pk->rkey->comment)) { + if( confirm_fingerprint!=NULL ) sfree(confirm_fingerprint); + fail("unconfirmed key"); + pk=NULL; + goto responded; + //return NULL; + } + }*/ + } + if( confirm_fingerprint!=NULL ) sfree(confirm_fingerprint); +#endif + + 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.reqid = reqid; + so->pk = pk; + so->pkr.prev = so->pkr.next = NULL; + so->data_to_sign = strbuf_new(); + put_datapl(so->data_to_sign, 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; + PageantKey *pk; + + 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); + } + + pk = findkey1(&reqkey); + freersakey(&reqkey); + if (pk) { + pageant_client_log(pc, reqid, "found with comment: %s", + pk->rkey->comment); + + del234(keytree, pk); + keylist_update(); + pk_free(pk); + 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. + */ + PageantKey *pk; + 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_fingerprint_blob( + blob, SSH_FPTYPE_DEFAULT); + pageant_client_log(pc, reqid, "unwanted key: %s", fingerprint); + sfree(fingerprint); + } + + pk = findkey2(blob); + if (!pk) { + fail("key not found"); + goto responded; + } + + pageant_client_log(pc, reqid, "found with comment: %s", pk->comment); + + del234(keytree, pk); + keylist_update(); + pk_free(pk); + 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; + } + + BinarySource src[1]; + const char *error; + + strbuf *public_blob = strbuf_new(); + char *comment; + + BinarySource_BARE_INIT_PL(src, keyfile); + if (!ppk_loadpub_s(src, NULL, BinarySink_UPCAST(public_blob), + &comment, &error)) { + fail("failed to extract public key blob: %s", error); + goto add_ppk_cleanup; + } + + if (!pc->suppress_logging) { + char *fingerprint = ssh2_fingerprint_blob( + ptrlen_from_strbuf(public_blob), 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; + } + + PageantKeySort sort = + keysort(2, ptrlen_from_strbuf(public_blob)); + + PageantKey *pk = find234(keytree, &sort, NULL); + if (pk) { + /* + * This public key blob already exists in the + * keytree. Add the encrypted key file to the + * existing record, if it doesn't have one already. + */ + if (!pk->encrypted_key_file) { + pk->encrypted_key_file = strbuf_new_nm(); + put_datapl(pk->encrypted_key_file, keyfile); + + keylist_update(); + put_byte(sb, SSH_AGENT_SUCCESS); + pageant_client_log( + pc, reqid, "reply: SSH_AGENT_SUCCESS (added encrypted" + " PPK to existing key record)"); + } else { + fail("key already present"); + } + } else { + /* + * We're adding a new key record containing only + * an encrypted key file. + */ + PageantKey *pk = snew(PageantKey); + memset(pk, 0, sizeof(PageantKey)); + pk->blocked_requests.next = pk->blocked_requests.prev = + &pk->blocked_requests; + pk->sort.ssh_version = 2; + pk->public_blob = public_blob; + public_blob = NULL; + pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob); + pk->comment = dupstr(comment); + pk->encrypted_key_file = strbuf_new_nm(); + put_datapl(pk->encrypted_key_file, keyfile); + + PageantKey *added = add234(keytree, pk); + assert(added == pk); (void)added; + + keylist_update(); + put_byte(sb, SSH_AGENT_SUCCESS); + pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS (made" + " new encrypted-only key record)"); + } + + add_ppk_cleanup: + if (public_blob) + strbuf_free(public_blob); + 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_fingerprint_blob( + blob, SSH_FPTYPE_DEFAULT); + pageant_client_log(pc, reqid, "key to re-encrypt: %s", + fingerprint); + sfree(fingerprint); + } + + PageantKey *pk = findkey2(blob); + if (!pk) { + fail("key not found"); + goto responded; + } + + pageant_client_log(pc, reqid, + "found with comment: %s", pk->comment); + + if (!reencrypt_key(pk)) { + 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; + PageantKey *pk; + + for (int i = 0; (pk = index234(keytree, i)) != NULL; i++) { + if (reencrypt_key(pk)) + 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; + PageantKey *pk; + for (i = 0; NULL != (pk = pageant_nth_key(2, i)); i++) { + char *fingerprint = ssh2_fingerprint_blob( + ptrlen_from_strbuf(pk->public_blob), + SSH_FPTYPE_DEFAULT); + pageant_client_log(pc, reqid, "returned key: %s %s", + fingerprint, pk->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.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; + keytree = newtree234(cmpkeys); +} + +static PageantKey *pageant_nth_key(int ssh_version, int i) +{ +#ifdef MOD_PERSO + PageantKey *pk = index234( + keytree, find_first_key_for_version(ssh_version) + get_scrumble_int(i)); +#else + PageantKey *pk = index234( + keytree, find_first_key_for_version(ssh_version) + i); +#endif + if (pk && pk->sort.ssh_version == ssh_version) + return pk; + else + return NULL; +} + +bool pageant_delete_nth_ssh1_key(int i) +{ + PageantKey *pk = delpos234(keytree, find_first_key_for_version(1) + i); + if (!pk) + return false; + pk_free(pk); + return true; +} + +bool pageant_delete_nth_ssh2_key(int i) +{ + PageantKey *pk = delpos234(keytree, find_first_key_for_version(2) + i); + if (!pk) + return false; + pk_free(pk); + return true; +} + +bool pageant_reencrypt_nth_ssh2_key(int i) +{ + PageantKey *pk = index234(keytree, find_first_key_for_version(2) + i); + if (!pk) + return false; + return reencrypt_key(pk); +} + +void pageant_delete_all(void) +{ + remove_all_keys(1); + remove_all_keys(2); +} + +void pageant_reencrypt_all(void) +{ + PageantKey *pk; + for (int i = 0; (pk = index234(keytree, i)) != NULL; i++) + reencrypt_key(pk); +} + +/* ---------------------------------------------------------------------- + * 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, const char *error_msg, + int error_code, bool calling_back) +{ + struct pageant_conn_state *pc = container_of( + plug, struct pageant_conn_state, plug); + if (error_msg) + 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, const char *error_msg, + int error_code, bool calling_back) +{ + struct pageant_listen_state *pl = container_of( + plug, struct pageant_listen_state, plug); + if (error_msg) + 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, +}; + +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, +}; + +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_new(); + put_datapl(cbkey.blob, 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_new(); + put_datapl(cbkey.blob, 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/0.77_My_PuTTY/pageant.h b/0.77_My_PuTTY/pageant.h new file mode 100644 index 0000000..9d31ca9 --- /dev/null +++ b/0.77_My_PuTTY/pageant.h @@ -0,0 +1,264 @@ +/* + * pageant.h: header for pageant.c. + */ + +#include + +#ifdef MOD_PERSO +NOTIFYICONDATA trayIcone ; +int GetAskConfirmationFlag(void) ; +int GetShowBalloonOnKeyUsage( void ) ; +int ShowBalloonTip( NOTIFYICONDATA tnid, TCHAR title[], TCHAR msg[] ) ; +#endif + +/* + * 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/0.77_My_PuTTY/pgssapi.c b/0.77_My_PuTTY/pgssapi.c new file mode 100644 index 0000000..9d33220 --- /dev/null +++ b/0.77_My_PuTTY/pgssapi.c @@ -0,0 +1,105 @@ +/* This file actually defines the GSSAPI function pointers for + * functions we plan to import from a GSSAPI library. + */ +#include "putty.h" + +#ifndef NO_GSSAPI + +#include "pgssapi.h" + +#ifndef NO_LIBDL + +/* Reserved static storage for GSS_oids. Comments are quotes from RFC 2744. */ +static const gss_OID_desc oids[] = { + /* The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x01"}, + /* corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) user_name(1)}. The constant + * GSS_C_NT_USER_NAME should be initialized to point + * to that gss_OID_desc. + + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"}, + /* corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) machine_uid_name(2)}. + * The constant GSS_C_NT_MACHINE_UID_NAME should be + * initialized to point to that gss_OID_desc. + + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x03"}, + /* corresponding to an object-identifier value of + * {iso(1) member-body(2) United States(840) mit(113554) + * infosys(1) gssapi(2) generic(1) string_uid_name(3)}. + * The constant GSS_C_NT_STRING_UID_NAME should be + * initialized to point to that gss_OID_desc. + * + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {6, (void *)"\x2b\x06\x01\x05\x06\x02"}, + /* corresponding to an object-identifier value of + * {iso(1) org(3) dod(6) internet(1) security(5) + * nametypes(6) gss-host-based-services(2))}. The constant + * GSS_C_NT_HOSTBASED_SERVICE_X should be initialized to point + * to that gss_OID_desc. This is a deprecated OID value, and + * implementations wishing to support hostbased-service names + * should instead use the GSS_C_NT_HOSTBASED_SERVICE OID, + * defined below, to identify such names; + * GSS_C_NT_HOSTBASED_SERVICE_X should be accepted a synonym + * for GSS_C_NT_HOSTBASED_SERVICE when presented as an input + * parameter, but should not be emitted by GSS-API + * implementations + * + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"}, + /* corresponding to an object-identifier value of {iso(1) + * member-body(2) Unites States(840) mit(113554) infosys(1) + * gssapi(2) generic(1) service_name(4)}. The constant + * GSS_C_NT_HOSTBASED_SERVICE should be initialized + * to point to that gss_OID_desc. + * + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {6, (void *)"\x2b\x06\01\x05\x06\x03"}, + /* corresponding to an object identifier value of + * {1(iso), 3(org), 6(dod), 1(internet), 5(security), + * 6(nametypes), 3(gss-anonymous-name)}. The constant + * and GSS_C_NT_ANONYMOUS should be initialized to point + * to that gss_OID_desc. + * + * The implementation must reserve static storage for a + * gss_OID_desc object containing the value */ + {6, (void *)"\x2b\x06\x01\x05\x06\x04"}, + /* corresponding to an object-identifier value of + * {1(iso), 3(org), 6(dod), 1(internet), 5(security), + * 6(nametypes), 4(gss-api-exported-name)}. The constant + * GSS_C_NT_EXPORT_NAME should be initialized to point + * to that gss_OID_desc. + */ +}; + +/* Here are the constants which point to the static structure above. + * + * Constants of the form GSS_C_NT_* are specified by rfc 2744. + */ +const_gss_OID GSS_C_NT_USER_NAME = oids+0; +const_gss_OID GSS_C_NT_MACHINE_UID_NAME = oids+1; +const_gss_OID GSS_C_NT_STRING_UID_NAME = oids+2; +const_gss_OID GSS_C_NT_HOSTBASED_SERVICE_X = oids+3; +const_gss_OID GSS_C_NT_HOSTBASED_SERVICE = oids+4; +const_gss_OID GSS_C_NT_ANONYMOUS = oids+5; +const_gss_OID GSS_C_NT_EXPORT_NAME = oids+6; + +#endif /* NO_LIBDL */ + +static gss_OID_desc gss_mech_krb5_desc = +{ 9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; +/* iso(1) member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) krb5(2)*/ +const gss_OID GSS_MECH_KRB5 = &gss_mech_krb5_desc; + +#endif /* NO_GSSAPI */ diff --git a/0.77_My_PuTTY/pgssapi.h b/0.77_My_PuTTY/pgssapi.h new file mode 100644 index 0000000..74d9c77 --- /dev/null +++ b/0.77_My_PuTTY/pgssapi.h @@ -0,0 +1,333 @@ +#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/0.77_My_PuTTY/pinger.c b/0.77_My_PuTTY/pinger.c new file mode 100644 index 0000000..ddebc72 --- /dev/null +++ b/0.77_My_PuTTY/pinger.c @@ -0,0 +1,72 @@ +/* + * 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/0.77_My_PuTTY/pockle.c b/0.77_My_PuTTY/pockle.c new file mode 100644 index 0000000..49dea8b --- /dev/null +++ b/0.77_My_PuTTY/pockle.c @@ -0,0 +1,450 @@ +#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; + + strbuf_catf(sb, "[MPU - Primality Certificate]\nVersion 1.0\nBase 10\n\n" + "Proof for:\nN "); + mp_write_decimal(sb, p); + strbuf_catf(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) { + strbuf_catf(sb, "\nType Small\nN "); + mp_write_decimal(sb, pr->prime); + strbuf_catf(sb, "\n"); + } else { + assert(pr->witness); + strbuf_catf(sb, "\nType BLS5\nN "); + mp_write_decimal(sb, pr->prime); + strbuf_catf(sb, "\n"); + for (size_t i = 0; i < pr->nfactors; i++) { + strbuf_catf(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; + strbuf_catf(sb, "\n"); + } + for (size_t i = 0; i < pr->nfactors + 1; i++) { + strbuf_catf(sb, "A[%"SIZEu"] ", i); + mp_write_decimal(sb, pr->witness); + strbuf_catf(sb, "\n"); + } + strbuf_catf(sb, "----\n"); + } + } + sfree(needed); + + return sb; +} diff --git a/0.77_My_PuTTY/portfwd.c b/0.77_My_PuTTY/portfwd.c new file mode 100644 index 0000000..f935b3d --- /dev/null +++ b/0.77_My_PuTTY/portfwd.c @@ -0,0 +1,1212 @@ +/* + * SSH port forwarding. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshchan.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, const char *error_msg, int error_code, + bool calling_back) +{ + struct PortForwarding *pf = + container_of(plug, struct PortForwarding, plug); + + if (error_msg) { + /* + * 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, const char *error_msg, int error_code, + bool calling_back) +{ + 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 4: + pf->socks_state = SOCKS_4; + break; + case 5: + 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 == 4 && message_type == 1) { + /* CONNECT message */ + bool name_based = false; + + port = get_uint16(src); + ipv4 = get_uint32(src); + if (ipv4 > 0x00000000 && ipv4 < 0x00000100) { + /* + * 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, 0); /* reply version */ + put_byte(output, 90); /* SOCKS 4 'request granted' */ + 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, 0); /* reply version */ + put_byte(output, 91); /* SOCKS 4 'request rejected' */ + 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 = 0xFF; /* means 'no usable method found' */ + { + int i; + for (i = 0; i < methods.len; i++) { + if (((const unsigned char *)methods.ptr)[i] == 0 ) { + method = 0; /* no auth */ + break; + } + } + } + + if (get_err(src) == BSE_OUT_OF_DATA) + return; + if (get_err(src)) + method = 0xFF; + + output = strbuf_new(); + put_byte(output, 5); /* SOCKS version */ + put_byte(output, method); /* selected auth method */ + sk_write(pf->s, output->u, output->len); + strbuf_free(output); + + if (method == 0xFF) { + 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 == 5 && message_type == 1 && + reserved_byte == 0) { + + reply_code = 0; /* success */ + + switch (get_byte(src)) { + case 1: /* IPv4 */ + pf->hostname = ipv4_to_string(get_uint32(src)); + break; + case 4: /* IPv6 */ + pf->hostname = ipv6_to_string(get_data(src, 16)); + break; + case 3: /* unresolved domain name */ + pf->hostname = mkstr(get_pstring(src)); + break; + default: + pf->hostname = NULL; + reply_code = 8; /* address type not supported */ + break; + } + + pf->port = get_uint16(src); + } else { + reply_code = 7; /* command not supported */ + } + + if (get_err(src) == BSE_OUT_OF_DATA) + return; + if (get_err(src)) + reply_code = 1; /* general server failure */ + + output = strbuf_new(); + put_byte(output, 5); /* SOCKS version */ + put_byte(output, reply_code); + put_byte(output, 0); /* reserved */ + put_byte(output, 1); /* 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 != 0) { + 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); +} + +#ifdef MOD_PERSO +// Manage sftpconnect that ends with :* (create a local forward port dynamically) +void AddDynamicSFTPConnect( Conf *conf ) { + char * p = conf_get_str( conf, CONF_sftpconnect ) ; + if( p[0]==':' ) { + if( (conf_get_str( conf, CONF_username )!=NULL) && (strlen(conf_get_str( conf, CONF_username ))>0) ) { + char *b ; + b = (char*) malloc( strlen(conf_get_str( conf, CONF_username )) + 20 ) ; + sprintf( b, "%s@localhost%s", conf_get_str( conf, CONF_username ),p ) ; + conf_set_str( conf, CONF_sftpconnect, b ) ; + p = conf_get_str( conf, CONF_sftpconnect ) ; + free(b); + } else if( strstr(conf_get_str( conf, CONF_host ),"@")!=NULL ) { + char *b ; + b = (char*) malloc( strlen(conf_get_str( conf, CONF_host )) + 20 ) ; + strcpy(b,conf_get_str( conf, CONF_host )); + strstr(b,"@")[0]='\0'; + strcat(b,"@localhost"); + strcat(b,p); + conf_set_str( conf, CONF_sftpconnect, b ) ; + p = conf_get_str( conf, CONF_sftpconnect ) ; + free(b) ; + } + } + if( (p[strlen(p)-1]=='*') && (p[strlen(p)-2]==':') ) { + char key[10], val[20] ; + p[strlen(p)-2]='\0' ; + int rnd = (rand() % (65000 - 60000)) + 60000 ; + sprintf(key,"L%d",rnd); + sprintf(val,"localhost:%d",conf_get_int(conf, CONF_port)); + conf_set_str_str(conf, CONF_portfwd, key, val) ; + char *b = (char*) malloc( strlen(p)+20 ); + sprintf(b,"%s:%d",p,rnd) ; + conf_set_str( conf, CONF_sftpconnect, b ) ; + free(b); + } +} +#endif + +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); + 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/0.77_My_PuTTY/pproxy.c b/0.77_My_PuTTY/pproxy.c new file mode 100644 index 0000000..4b08606 --- /dev/null +++ b/0.77_My_PuTTY/pproxy.c @@ -0,0 +1,17 @@ +/* + * pproxy.c: dummy implementation of platform_new_connection(), to + * be supplanted on any platform which has its own local proxy + * method. + */ + +#include "putty.h" +#include "network.h" +#include "proxy.h" + +Socket *platform_new_connection(SockAddr *addr, const char *hostname, + int port, int privport, + int oobinline, int nodelay, int keepalive, + Plug *plug, Conf *conf) +{ + return NULL; +} diff --git a/0.77_My_PuTTY/primecandidate.c b/0.77_My_PuTTY/primecandidate.c new file mode 100644 index 0000000..0bf582f --- /dev/null +++ b/0.77_My_PuTTY/primecandidate.c @@ -0,0 +1,445 @@ +/* + * 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_unsafe_mod_integer(s->addend, mod); + factor_m = mp_unsafe_mod_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_unsafe_mod_integer(x, mod); + } + + if (x_res == avoid_res) { + ok = false; + break; + } + } + + if (!ok) { + mp_free(x); + 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/0.77_My_PuTTY/proxy.c b/0.77_My_PuTTY/proxy.c new file mode 100644 index 0000000..c458754 --- /dev/null +++ b/0.77_My_PuTTY/proxy.c @@ -0,0 +1,1513 @@ +/* + * 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)) + +/* + * Call this when proxy negotiation is complete, so that this + * socket can begin working normally. + */ +void proxy_activate (ProxySocket *p) +{ + size_t output_before, output_after; + + p->state = PROXY_STATE_ACTIVE; + + /* we want to ignore new receive events until we have sent + * all of our buffered receive data. + */ + sk_set_frozen(p->sub_socket, true); + + /* how many bytes of output have we buffered? */ + output_before = bufchain_size(&p->pending_oob_output_data) + + bufchain_size(&p->pending_output_data); + /* and keep track of how many bytes do not get sent. */ + output_after = 0; + + /* send buffered OOB writes */ + while (bufchain_size(&p->pending_oob_output_data) > 0) { + ptrlen data = bufchain_prefix(&p->pending_oob_output_data); + output_after += sk_write_oob(p->sub_socket, data.ptr, data.len); + bufchain_consume(&p->pending_oob_output_data, data.len); + } + + /* send buffered normal writes */ + while (bufchain_size(&p->pending_output_data) > 0) { + ptrlen data = bufchain_prefix(&p->pending_output_data); + output_after += sk_write(p->sub_socket, data.ptr, data.len); + bufchain_consume(&p->pending_output_data, data.len); + } + + /* if we managed to send any data, let the higher levels know. */ + if (output_after < output_before) + plug_sent(p->plug, output_after); + + /* if we have a pending EOF to send, send it */ + if (p->pending_eof) sk_write_eof(p->sub_socket); + + /* if the backend wanted the socket unfrozen, try to unfreeze. + * our set_frozen handler will flush buffered receive data before + * unfreezing the actual underlying socket. + */ + if (!p->freeze) + sk_set_frozen(&p->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->remote_addr); + 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->state != PROXY_STATE_ACTIVE) { + bufchain_add(&ps->pending_output_data, data, len); + return bufchain_size(&ps->pending_output_data); + } + return sk_write(ps->sub_socket, data, len); +} + +static size_t sk_proxy_write_oob (Socket *s, const void *data, size_t len) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + if (ps->state != PROXY_STATE_ACTIVE) { + bufchain_clear(&ps->pending_output_data); + bufchain_clear(&ps->pending_oob_output_data); + bufchain_add(&ps->pending_oob_output_data, data, len); + return len; + } + return sk_write_oob(ps->sub_socket, data, len); +} + +static void sk_proxy_write_eof (Socket *s) +{ + ProxySocket *ps = container_of(s, ProxySocket, sock); + + if (ps->state != PROXY_STATE_ACTIVE) { + 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->state != PROXY_STATE_ACTIVE) { + ps->freeze = is_frozen; + return; + } + + /* handle any remaining buffered recv data first */ + if (bufchain_size(&ps->pending_input_data) > 0) { + ps->freeze = is_frozen; + + /* loop while we still have buffered data, and while we are + * unfrozen. the plug_receive call in the loop could result + * in a call back into this function refreezing the socket, + * so we have to check each time. + */ + while (!ps->freeze && bufchain_size(&ps->pending_input_data) > 0) { + 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, const char *error_msg, + int error_code, bool calling_back) +{ + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->closing_error_msg = error_msg; + ps->closing_error_code = error_code; + ps->closing_calling_back = calling_back; + ps->negotiate(ps, PROXY_CHANGE_CLOSING); + } else { + plug_closing(ps->plug, error_msg, error_code, calling_back); + } +} + +static void plug_proxy_receive( + Plug *p, int urgent, const char *data, size_t len) +{ + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); + + if (ps->state != PROXY_STATE_ACTIVE) { + /* we will lose the urgentness of this data, but since most, + * if not all, of this data will be consumed by the negotiation + * process, hopefully it won't affect the protocol above us + */ + bufchain_add(&ps->pending_input_data, data, len); + ps->receive_urgent = (urgent != 0); + ps->receive_data = data; + ps->receive_len = len; + ps->negotiate(ps, PROXY_CHANGE_RECEIVE); + } 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->state != PROXY_STATE_ACTIVE) { + ps->negotiate(ps, PROXY_CHANGE_SENT); + return; + } + plug_sent(ps->plug, bufsize); +} + +static int plug_proxy_accepting(Plug *p, + accept_fn_t constructor, accept_ctx_t ctx) +{ + ProxySocket *ps = container_of(p, ProxySocket, plugimpl); + + if (ps->state != PROXY_STATE_ACTIVE) { + ps->accepting_constructor = constructor; + ps->accepting_ctx = ctx; + return ps->negotiate(ps, PROXY_CHANGE_ACCEPTING); + } + return plug_accepting(ps->plug, constructor, ctx); +} + +/* + * This function can accept a NULL pointer as `addr', in which case + * it will only check the host name. + */ +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 +}; + +Socket *new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf) +{ + if (conf_get_int(conf, CONF_proxy_type) != PROXY_NONE && + proxy_for_destination(addr, hostname, port, conf)) + { + ProxySocket *ret; + SockAddr *proxy_addr; + char *proxy_canonical_name; + const char *proxy_type; + Socket *sret; + int type; + + if ((sret = platform_new_connection(addr, hostname, port, privport, + oobinline, nodelay, keepalive, + plug, conf)) != NULL) + return sret; + + ret = snew(ProxySocket); + ret->sock.vt = &ProxySocket_sockvt; + ret->plugimpl.vt = &ProxySocket_plugvt; + ret->conf = conf_copy(conf); + ret->plug = plug; + ret->remote_addr = addr; /* will need to be freed on close */ + ret->remote_port = port; + + ret->error = NULL; + ret->pending_eof = false; + ret->freeze = false; + + bufchain_init(&ret->pending_input_data); + bufchain_init(&ret->pending_output_data); + bufchain_init(&ret->pending_oob_output_data); + + ret->sub_socket = NULL; + ret->state = PROXY_STATE_NEW; + ret->negotiate = NULL; + + type = conf_get_int(conf, CONF_proxy_type); + if (type == PROXY_HTTP) { + ret->negotiate = proxy_http_negotiate; + proxy_type = "HTTP"; + } else if (type == PROXY_SOCKS4) { + ret->negotiate = proxy_socks4_negotiate; + proxy_type = "SOCKS 4"; + } else if (type == PROXY_SOCKS5) { + ret->negotiate = proxy_socks5_negotiate; + proxy_type = "SOCKS 5"; + } else if (type == PROXY_TELNET) { + ret->negotiate = proxy_telnet_negotiate; + proxy_type = "Telnet"; + } else { + ret->error = "Proxy error: Unknown proxy method"; + return &ret->sock; + } + + { + char *logmsg = dupprintf("Will use %s proxy at %s:%d to connect" + " to %s:%d", proxy_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) { + ret->error = "Proxy error: Unable to resolve proxy host name"; + sk_addr_free(proxy_addr); + return &ret->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", + proxy_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. + */ + ret->sub_socket = sk_new(proxy_addr, + conf_get_int(conf, CONF_proxy_port), + privport, oobinline, + nodelay, keepalive, &ret->plugimpl); + if (sk_socket_error(ret->sub_socket) != NULL) + return &ret->sock; + + /* start the proxy negotiation process... */ + sk_set_frozen(ret->sub_socket, false); + ret->negotiate(ret, PROXY_CHANGE_NEW); + + return &ret->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); +} + +/* ---------------------------------------------------------------------- + * HTTP CONNECT proxy type. + */ + +static bool get_line_end(char *data, size_t len, size_t *out) +{ + size_t off = 0; + + while (off < len) + { + if (data[off] == '\n') { + /* we have a newline */ + off++; + + /* is that the only thing on this line? */ + if (off <= 2) { + *out = off; + return true; + } + + /* if not, then there is the possibility that this header + * continues onto the next line, if it starts with a space + * or a tab. + */ + + if (off + 1 < len && data[off+1] != ' ' && data[off+1] != '\t') { + *out = off; + return true; + } + + /* the line does continue, so we have to keep going + * until we see an the header's "real" end of line. + */ + off++; + } + + off++; + } + + return false; +} + +int proxy_http_negotiate (ProxySocket *p, int change) +{ + if (p->state == PROXY_STATE_NEW) { + /* we are just beginning the proxy negotiate process, + * so we'll send off the initial bits of the request. + * for this proxy method, it's just a simple HTTP + * request + */ + char *buf, dest[512]; + char *username, *password; + + sk_getaddr(p->remote_addr, dest, lenof(dest)); + + buf = dupprintf("CONNECT %s:%i HTTP/1.1\r\nHost: %s:%i\r\n", + dest, p->remote_port, dest, p->remote_port); + sk_write(p->sub_socket, buf, strlen(buf)); + sfree(buf); + + username = conf_get_str(p->conf, CONF_proxy_username); + password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + char *buf, *buf2; + int i, j, len; + buf = dupprintf("%s:%s", username, password); + len = strlen(buf); + buf2 = snewn(len * 4 / 3 + 100, char); + sprintf(buf2, "Proxy-Authorization: Basic "); + for (i = 0, j = strlen(buf2); i < len; i += 3, j += 4) + base64_encode_atom((unsigned char *)(buf+i), + (len-i > 3 ? 3 : len-i), buf2+j); + strcpy(buf2+j, "\r\n"); + sk_write(p->sub_socket, buf2, strlen(buf2)); + sfree(buf); + sfree(buf2); + } + + sk_write(p->sub_socket, "\r\n", 2); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + char *data, *datap; + size_t len, eol; + + if (p->state == 1) { + + int min_ver, maj_ver, status; + + /* get the status line */ + len = bufchain_size(&p->pending_input_data); + assert(len > 0); /* or we wouldn't be here */ + data = snewn(len+1, char); + bufchain_fetch(&p->pending_input_data, data, len); + /* + * We must NUL-terminate this data, because Windows + * sscanf appears to require a NUL at the end of the + * string because it strlens it _first_. Sigh. + */ + data[len] = '\0'; + + if (!get_line_end(data, len, &eol)) { + sfree(data); + return 1; + } + + status = -1; + /* We can't rely on whether the %n incremented the sscanf return */ + if (sscanf((char *)data, "HTTP/%i.%i %n", + &maj_ver, &min_ver, &status) < 2 || status == -1) { + plug_closing(p->plug, "Proxy error: HTTP response was absent", + PROXY_ERROR_GENERAL, 0); + sfree(data); + return 1; + } + + /* remove the status line from the input buffer. */ + bufchain_consume(&p->pending_input_data, eol); + if (data[status] != '2') { + /* error */ + char *buf; + data[eol] = '\0'; + while (eol > status && + (data[eol-1] == '\r' || data[eol-1] == '\n')) + data[--eol] = '\0'; + buf = dupprintf("Proxy error: %s", data+status); + plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0); + sfree(buf); + sfree(data); + return 1; + } + + sfree(data); + + p->state = 2; + } + + if (p->state == 2) { + + /* get headers. we're done when we get a + * header of length 2, (ie. just "\r\n") + */ + + len = bufchain_size(&p->pending_input_data); + assert(len > 0); /* or we wouldn't be here */ + data = snewn(len, char); + datap = data; + bufchain_fetch(&p->pending_input_data, data, len); + + if (!get_line_end(datap, len, &eol)) { + sfree(data); + return 1; + } + while (eol > 2) { + bufchain_consume(&p->pending_input_data, eol); + datap += eol; + len -= eol; + if (!get_line_end(datap, len, &eol)) + eol = 0; /* terminate the loop */ + } + + if (eol == 2) { + /* we're done */ + bufchain_consume(&p->pending_input_data, 2); + proxy_activate(p); + /* proxy activate will have dealt with + * whatever is left of the buffer */ + sfree(data); + return 1; + } + + sfree(data); + return 1; + } + } + + plug_closing(p->plug, "Proxy error: unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} + +/* ---------------------------------------------------------------------- + * SOCKS proxy type. + */ + +/* SOCKS version 4 */ +int proxy_socks4_negotiate (ProxySocket *p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + + /* request format: + * version number (1 byte) = 4 + * command code (1 byte) + * 1 = CONNECT + * 2 = BIND + * dest. port (2 bytes) [network order] + * dest. address (4 bytes) + * user ID (variable length, null terminated string) + */ + + strbuf *command = strbuf_new(); + char hostname[512]; + bool write_hostname = false; + + put_byte(command, 4); /* SOCKS version 4 */ + put_byte(command, 1); /* CONNECT command */ + put_uint16(command, p->remote_port); + + switch (sk_addrtype(p->remote_addr)) { + case ADDRTYPE_IPV4: { + char addr[4]; + sk_addrcopy(p->remote_addr, addr); + put_data(command, addr, 4); + break; + } + case ADDRTYPE_NAME: + sk_getaddr(p->remote_addr, hostname, lenof(hostname)); + put_uint32(command, 1); + write_hostname = true; + break; + case ADDRTYPE_IPV6: + p->error = "Proxy error: SOCKS version 4 does not support IPv6"; + strbuf_free(command); + return 1; + } + + put_asciz(command, conf_get_str(p->conf, CONF_proxy_username)); + if (write_hostname) + put_asciz(command, hostname); + sk_write(p->sub_socket, command->s, command->len); + strbuf_free(command); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + if (p->state == 1) { + /* response format: + * version number (1 byte) = 4 + * reply code (1 byte) + * 90 = request granted + * 91 = request rejected or failed + * 92 = request rejected due to lack of IDENTD on client + * 93 = request rejected due to difference in user ID + * (what we sent vs. what IDENTD said) + * dest. port (2 bytes) + * dest. address (4 bytes) + */ + + char data[8]; + + if (bufchain_size(&p->pending_input_data) < 8) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 8); + + if (data[0] != 0) { + plug_closing(p->plug, "Proxy error: SOCKS proxy responded with " + "unexpected reply code version", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] != 90) { + + switch (data[1]) { + case 92: + plug_closing(p->plug, "Proxy error: SOCKS server wanted IDENTD on client", + PROXY_ERROR_GENERAL, 0); + break; + case 93: + plug_closing(p->plug, "Proxy error: Username and IDENTD on client don't agree", + PROXY_ERROR_GENERAL, 0); + break; + case 91: + default: + plug_closing(p->plug, "Proxy error: Error while communicating with proxy", + PROXY_ERROR_GENERAL, 0); + break; + } + + return 1; + } + bufchain_consume(&p->pending_input_data, 8); + + /* we're done */ + proxy_activate(p); + /* proxy activate will have dealt with + * whatever is left of the buffer */ + return 1; + } + } + + plug_closing(p->plug, "Proxy error: unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} + +/* SOCKS version 5 */ +int proxy_socks5_negotiate (ProxySocket *p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + + /* initial command: + * version number (1 byte) = 5 + * number of available authentication methods (1 byte) + * available authentication methods (1 byte * previous value) + * authentication methods: + * 0x00 = no authentication + * 0x01 = GSSAPI + * 0x02 = username/password + * 0x03 = CHAP + */ + + strbuf *command; + char *username, *password; + int method_count_offset, methods_start; + + command = strbuf_new(); + put_byte(command, 5); /* SOCKS version 5 */ + username = conf_get_str(p->conf, CONF_proxy_username); + password = conf_get_str(p->conf, CONF_proxy_password); + + method_count_offset = command->len; + put_byte(command, 0); + methods_start = command->len; + + put_byte(command, 0x00); /* no authentication */ + + if (username[0] || password[0]) { + proxy_socks5_offerencryptedauth(BinarySink_UPCAST(command)); + put_byte(command, 0x02); /* username/password */ + } + + command->u[method_count_offset] = command->len - methods_start; + + sk_write(p->sub_socket, command->s, command->len); + strbuf_free(command); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + if (p->state == 1) { + + /* initial response: + * version number (1 byte) = 5 + * authentication method (1 byte) + * authentication methods: + * 0x00 = no authentication + * 0x01 = GSSAPI + * 0x02 = username/password + * 0x03 = CHAP + * 0xff = no acceptable methods + */ + char data[2]; + + if (bufchain_size(&p->pending_input_data) < 2) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 2); + + if (data[0] != 5) { + plug_closing(p->plug, "Proxy error: SOCKS proxy returned unexpected version", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] == 0x00) p->state = 2; /* no authentication needed */ + else if (data[1] == 0x01) p->state = 4; /* GSSAPI authentication */ + else if (data[1] == 0x02) p->state = 5; /* username/password authentication */ + else if (data[1] == 0x03) p->state = 6; /* CHAP authentication */ + else { + plug_closing(p->plug, "Proxy error: SOCKS proxy did not accept our authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + bufchain_consume(&p->pending_input_data, 2); + } + + if (p->state == 7) { + + /* password authentication reply format: + * version number (1 bytes) = 1 + * reply code (1 byte) + * 0 = succeeded + * >0 = failed + */ + char data[2]; + + if (bufchain_size(&p->pending_input_data) < 2) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 2); + + if (data[0] != 1) { + plug_closing(p->plug, "Proxy error: SOCKS password " + "subnegotiation contained wrong version number", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] != 0) { + + plug_closing(p->plug, "Proxy error: SOCKS proxy refused" + " password authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + bufchain_consume(&p->pending_input_data, 2); + p->state = 2; /* now proceed as authenticated */ + } + + if (p->state == 8) { + int ret; + ret = proxy_socks5_handlechap(p); + if (ret) return ret; + } + + if (p->state == 2) { + + /* request format: + * version number (1 byte) = 5 + * command code (1 byte) + * 1 = CONNECT + * 2 = BIND + * 3 = UDP ASSOCIATE + * reserved (1 byte) = 0x00 + * address type (1 byte) + * 1 = IPv4 + * 3 = domainname (first byte has length, no terminating null) + * 4 = IPv6 + * dest. address (variable) + * dest. port (2 bytes) [network order] + */ + + strbuf *command = strbuf_new(); + put_byte(command, 5); /* SOCKS version 5 */ + put_byte(command, 1); /* CONNECT command */ + put_byte(command, 0x00); /* reserved byte */ + + switch (sk_addrtype(p->remote_addr)) { + case ADDRTYPE_IPV4: + put_byte(command, 1); /* IPv4 */ + sk_addrcopy(p->remote_addr, strbuf_append(command, 4)); + break; + case ADDRTYPE_IPV6: + put_byte(command, 4); /* IPv6 */ + sk_addrcopy(p->remote_addr, strbuf_append(command, 16)); + break; + case ADDRTYPE_NAME: { + char hostname[512]; + put_byte(command, 3); /* domain name */ + sk_getaddr(p->remote_addr, hostname, lenof(hostname)); + if (!put_pstring(command, hostname)) { + p->error = "Proxy error: SOCKS 5 cannot " + "support host names longer than 255 chars"; + strbuf_free(command); + return 1; + } + break; + } + } + + put_uint16(command, p->remote_port); + + sk_write(p->sub_socket, command->s, command->len); + + strbuf_free(command); + + p->state = 3; + return 1; + } + + if (p->state == 3) { + + /* reply format: + * version number (1 bytes) = 5 + * reply code (1 byte) + * 0 = succeeded + * 1 = general SOCKS server failure + * 2 = connection not allowed by ruleset + * 3 = network unreachable + * 4 = host unreachable + * 5 = connection refused + * 6 = TTL expired + * 7 = command not supported + * 8 = address type not supported + * reserved (1 byte) = x00 + * address type (1 byte) + * 1 = IPv4 + * 3 = domainname (first byte has length, no terminating null) + * 4 = IPv6 + * server bound address (variable) + * server bound port (2 bytes) [network order] + */ + char data[5]; + int len; + + /* First 5 bytes of packet are enough to tell its length. */ + if (bufchain_size(&p->pending_input_data) < 5) + return 1; /* not got anything yet */ + + /* get the response */ + bufchain_fetch(&p->pending_input_data, data, 5); + + if (data[0] != 5) { + plug_closing(p->plug, "Proxy error: SOCKS proxy returned wrong version number", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (data[1] != 0) { + char buf[256]; + + strcpy(buf, "Proxy error: "); + + switch (data[1]) { + case 1: strcat(buf, "General SOCKS server failure"); break; + case 2: strcat(buf, "Connection not allowed by ruleset"); break; + case 3: strcat(buf, "Network unreachable"); break; + case 4: strcat(buf, "Host unreachable"); break; + case 5: strcat(buf, "Connection refused"); break; + case 6: strcat(buf, "TTL expired"); break; + case 7: strcat(buf, "Command not supported"); break; + case 8: strcat(buf, "Address type not supported"); break; + default: sprintf(buf+strlen(buf), + "Unrecognised SOCKS error code %d", + data[1]); + break; + } + plug_closing(p->plug, buf, PROXY_ERROR_GENERAL, 0); + + return 1; + } + + /* + * Eat the rest of the reply packet. + */ + len = 6; /* first 4 bytes, last 2 */ + switch (data[3]) { + case 1: len += 4; break; /* IPv4 address */ + case 4: len += 16; break;/* IPv6 address */ + case 3: len += 1+(unsigned char)data[4]; break; /* domain name */ + default: + plug_closing(p->plug, "Proxy error: SOCKS proxy returned " + "unrecognised address format", + PROXY_ERROR_GENERAL, 0); + return 1; + } + if (bufchain_size(&p->pending_input_data) < len) + return 1; /* not got whole reply yet */ + bufchain_consume(&p->pending_input_data, len); + + /* we're done */ + proxy_activate(p); + return 1; + } + + if (p->state == 4) { + /* TODO: Handle GSSAPI authentication */ + plug_closing(p->plug, "Proxy error: We don't support GSSAPI authentication", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (p->state == 5) { + const char *username = conf_get_str(p->conf, CONF_proxy_username); + const char *password = conf_get_str(p->conf, CONF_proxy_password); + if (username[0] || password[0]) { + strbuf *auth = strbuf_new_nm(); + put_byte(auth, 1); /* version number of subnegotiation */ + if (!put_pstring(auth, username)) { + p->error = "Proxy error: SOCKS 5 authentication cannot " + "support usernames longer than 255 chars"; + strbuf_free(auth); + return 1; + } + if (!put_pstring(auth, password)) { + p->error = "Proxy error: SOCKS 5 authentication cannot " + "support passwords longer than 255 chars"; + strbuf_free(auth); + return 1; + } + sk_write(p->sub_socket, auth->s, auth->len); + strbuf_free(auth); + p->state = 7; + } else + plug_closing(p->plug, "Proxy error: Server chose " + "username/password authentication but we " + "didn't offer it!", + PROXY_ERROR_GENERAL, 0); + return 1; + } + + if (p->state == 6) { + int ret; + ret = proxy_socks5_selectchap(p); + if (ret) return ret; + } + + } + + plug_closing(p->plug, "Proxy error: Unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} + +/* ---------------------------------------------------------------------- + * `Telnet' proxy type. + * + * (This is for ad-hoc proxies where you connect to the proxy's + * telnet port and send a command such as `connect host port'. The + * command is configurable, since this proxy type is typically not + * standardised or at all well-defined.) + */ + +char *format_telnet_command(SockAddr *addr, int port, Conf *conf) +{ + char *fmt = conf_get_str(conf, CONF_proxy_telnet_command); + int so = 0, eo = 0; + strbuf *buf = strbuf_new(); + + /* 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) { + strbuf_catf(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; + } + 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; + } + 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); + strbuf_catf(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); + } + + return strbuf_to_str(buf); +} + +int proxy_telnet_negotiate (ProxySocket *p, int change) +{ + if (p->state == PROXY_CHANGE_NEW) { + char *formatted_cmd; + + formatted_cmd = format_telnet_command(p->remote_addr, p->remote_port, + p->conf); + + { + /* + * Re-escape control chars in the command, for logging. + */ + char *reescaped = snewn(4*strlen(formatted_cmd) + 1, char); + const char *in; + char *out; + char *logmsg; + + for (in = formatted_cmd, out = reescaped; *in; in++) { + if (*in == '\n') { + *out++ = '\\'; *out++ = 'n'; + } else if (*in == '\r') { + *out++ = '\\'; *out++ = 'r'; + } else if (*in == '\t') { + *out++ = '\\'; *out++ = 't'; + } else if (*in == '\\') { + *out++ = '\\'; *out++ = '\\'; + } else if ((unsigned)(((unsigned char)*in) - 0x20) < + (0x7F-0x20)) { + *out++ = *in; + } else { + out += sprintf(out, "\\x%02X", (unsigned)*in & 0xFF); + } + } + *out = '\0'; + + logmsg = dupprintf("Sending Telnet proxy command: %s", reescaped); + plug_log(p->plug, PLUGLOG_PROXY_MSG, NULL, 0, logmsg, 0); + sfree(logmsg); + sfree(reescaped); + } + + sk_write(p->sub_socket, formatted_cmd, strlen(formatted_cmd)); + sfree(formatted_cmd); + + p->state = 1; + return 0; + } + + if (change == PROXY_CHANGE_CLOSING) { + /* if our proxy negotiation process involves closing and opening + * new sockets, then we would want to intercept this closing + * callback when we were expecting it. if we aren't anticipating + * a socket close, then some error must have occurred. we'll + * just pass those errors up to the backend. + */ + plug_closing(p->plug, p->closing_error_msg, p->closing_error_code, + p->closing_calling_back); + return 0; /* ignored */ + } + + if (change == PROXY_CHANGE_SENT) { + /* some (or all) of what we wrote to the proxy was sent. + * we don't do anything new, however, until we receive the + * proxy's response. we might want to set a timer so we can + * timeout the proxy negotiation after a while... + */ + return 0; + } + + if (change == PROXY_CHANGE_ACCEPTING) { + /* we should _never_ see this, as we are using our socket to + * connect to a proxy, not accepting inbound connections. + * what should we do? close the socket with an appropriate + * error message? + */ + return plug_accepting(p->plug, + p->accepting_constructor, p->accepting_ctx); + } + + if (change == PROXY_CHANGE_RECEIVE) { + /* we have received data from the underlying socket, which + * we'll need to parse, process, and respond to appropriately. + */ + + /* we're done */ + proxy_activate(p); + /* proxy activate will have dealt with + * whatever is left of the buffer */ + return 1; + } + + plug_closing(p->plug, "Proxy error: Unexpected proxy error", + PROXY_ERROR_UNEXPECTED, 0); + return 1; +} diff --git a/0.77_My_PuTTY/proxy.h b/0.77_My_PuTTY/proxy.h new file mode 100644 index 0000000..e409017 --- /dev/null +++ b/0.77_My_PuTTY/proxy.h @@ -0,0 +1,111 @@ +/* + * Network proxy abstraction in PuTTY + * + * A proxy layer, if necessary, wedges itself between the + * network code and the higher level backend. + * + * Supported proxies: HTTP CONNECT, generic telnet, SOCKS 4 & 5 + */ + +#ifndef PUTTY_PROXY_H +#define PUTTY_PROXY_H + +#define PROXY_ERROR_GENERAL 8000 +#define PROXY_ERROR_UNEXPECTED 8001 + +typedef struct ProxySocket ProxySocket; + +struct ProxySocket { + const char *error; + + Socket *sub_socket; + Plug *plug; + SockAddr *remote_addr; + int remote_port; + + bufchain pending_output_data; + bufchain pending_oob_output_data; + bufchain pending_input_data; + bool pending_eof; + +#define PROXY_STATE_NEW -1 +#define PROXY_STATE_ACTIVE 0 + + int state; /* proxy states greater than 0 are implementation + * dependent, but represent various stages/states + * of the initialization/setup/negotiation with the + * proxy server. + */ + 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. + */ + +#define PROXY_CHANGE_NEW -1 +#define PROXY_CHANGE_CLOSING 0 +#define PROXY_CHANGE_SENT 1 +#define PROXY_CHANGE_RECEIVE 2 +#define PROXY_CHANGE_ACCEPTING 3 + + /* something has changed (a call from the sub socket + * layer into our Proxy Plug layer, or we were just + * created, etc), so the proxy layer needs to handle + * this change (the type of which is the second argument) + * and further the proxy negotiation process. + */ + + int (*negotiate) (ProxySocket * /* this */, int /* change type */); + + /* current arguments of plug handlers + * (for use by proxy's negotiate function) + */ + + /* closing */ + const char *closing_error_msg; + int closing_error_code; + bool closing_calling_back; + + /* receive */ + bool receive_urgent; + const char *receive_data; + int receive_len; + + /* accepting */ + accept_fn_t accepting_constructor; + accept_ctx_t accepting_ctx; + + /* configuration, used to look up proxy settings */ + Conf *conf; + + /* CHAP transient data */ + int chap_num_attributes; + int chap_num_attributes_processed; + int chap_current_attribute; + int chap_current_datalen; + + Socket sock; + Plug plugimpl; +}; + +extern void proxy_activate (ProxySocket *); + +extern int proxy_http_negotiate (ProxySocket *, int); +extern int proxy_telnet_negotiate (ProxySocket *, int); +extern int proxy_socks4_negotiate (ProxySocket *, int); +extern int proxy_socks5_negotiate (ProxySocket *, int); + +/* + * This may be reused by local-command proxies on individual + * platforms. + */ +char *format_telnet_command(SockAddr *addr, int port, Conf *conf); + +/* + * These are implemented in cproxy.c or nocproxy.c, depending on + * whether encrypted proxy authentication is available. + */ +extern void proxy_socks5_offerencryptedauth(BinarySink *); +extern int proxy_socks5_handlechap (ProxySocket *); +extern int proxy_socks5_selectchap(ProxySocket *); + +#endif diff --git a/0.77_My_PuTTY/pscp.c b/0.77_My_PuTTY/pscp.c new file mode 100644 index 0000000..1d3dbc5 --- /dev/null +++ b/0.77_My_PuTTY/pscp.c @@ -0,0 +1,2387 @@ +/* + * scp.c - Scp (Secure Copy) client for PuTTY. + * Joris van Rantwijk, Simon Tatham + * + * This is mainly based on ssh-1.2.26/scp.c by Timo Rinne & Tatu Ylonen. + * They, in turn, used stuff from BSD rcp. + * + * (SGT, 2001-09-10: Joris van Rantwijk assures me that although + * this file as originally submitted was inspired by, and + * _structurally_ based on, ssh-1.2.26's scp.c, there wasn't any + * actual code duplicated, so the above comment shouldn't give rise + * to licensing issues.) + */ + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "psftp.h" +#include "ssh.h" +#include "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; +#ifdef MOD_PERSO +Conf *conf; +#else +static Conf *conf; +#endif +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); + +#ifdef MOD_PERSO +void SetAutoStoreSSHKey( void ) ; +#if (defined MOD_PERSO) && (!defined FLJ) +char *appname = "PSCP"; +#else +const char *const appname = "PSCP"; +#endif +#ifdef MOD_PORTKNOCKING +int ManagePortKnocking( char* host, char *portstr ) ; +#endif +#endif + +/* + * 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) { } + +static size_t pscp_output(Seat *, bool is_stderr, const void *, size_t); +static bool pscp_eof(Seat *); + +static const SeatVtable pscp_seat_vt = { + .output = pscp_output, + .eof = pscp_eof, + .get_userpass_input = filexfer_get_userpass_input, + .notify_remote_exit = nullseat_notify_remote_exit, + .connection_fatal = console_connection_fatal, + .update_specials_menu = nullseat_update_specials_menu, + .get_ttymode = nullseat_get_ttymode, + .set_busy_status = nullseat_set_busy_status, + .verify_ssh_host_key = console_verify_ssh_host_key, + .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .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_vacuously, + .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, bool is_stderr, const void *data, size_t len) +{ + /* + * stderr data is just spouted to local stderr (optionally via a + * sanitiser) and otherwise ignored. + */ + if (is_stderr) { + 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); + +#ifdef MOD_PORTKNOCKING + ManagePortKnocking(conf_get_str(conf,CONF_host),conf_get_str(conf,CONF_portknockingoptions)); +#endif + + 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 { + int bufsize = backend_send(backend, data, len); + + /* + * If the network transfer is backing up - that is, the + * remote site is not accepting data as fast as we can + * produce it - then we must loop on network events until + * we have space in the buffer again. + */ + while (bufsize > MAX_SCP_BUFSIZE) { + if (ssh_sftp_loop_iteration() < 0) + return 1; + bufsize = 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, act.name) { + 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(" -pw passw login with specified password\n"); + printf(" -1 -2 force use of particular SSH protocol version\n"); + printf(" -4 -6 force use of IPv4 or IPv6\n"); + printf(" -C enable compression\n"); + printf(" -i key private key file for user authentication\n"); + printf(" -noagent disable use of Pageant\n"); + printf(" -agent enable use of Pageant\n"); + printf(" -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"); +#ifdef MOD_PERSO + printf(" -auto-store-sshkey\n"); + printf(" store automatically the servers ssh keys\n"); +#endif + 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/0.77_My_PuTTY/psftp.c b/0.77_My_PuTTY/psftp.c new file mode 100644 index 0000000..2cb50fe --- /dev/null +++ b/0.77_My_PuTTY/psftp.c @@ -0,0 +1,2929 @@ +/* + * 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 "sftp.h" + +#ifdef MOD_PERSO +void SetAutoStoreSSHKey( void ) ; +#if (defined MOD_PERSO) && (!defined FLJ) +char *appname = "PSFTP"; +#else +const char *const appname = "PSFTP"; +#endif +#ifdef MOD_PORTKNOCKING +int ManagePortKnocking( char* host, char *portstr ) ; +#endif +#endif + +/* + * 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; +#ifdef MOD_PERSO +Conf *conf; +#else +static Conf *conf; +#endif +static bool sent_eof = false; + +/* ------------------------------------------------------------ + * Seat vtable. + */ + +static size_t psftp_output(Seat *, bool is_stderr, const void *, size_t); +static bool psftp_eof(Seat *); + +static const SeatVtable psftp_seat_vt = { + .output = psftp_output, + .eof = psftp_eof, + .get_userpass_input = filexfer_get_userpass_input, + .notify_remote_exit = nullseat_notify_remote_exit, + .connection_fatal = console_connection_fatal, + .update_specials_menu = nullseat_update_specials_menu, + .get_ttymode = nullseat_get_ttymode, + .set_busy_status = nullseat_set_busy_status, + .verify_ssh_host_key = console_verify_ssh_host_key, + .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .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_vacuously, + .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) { } + +/* + * 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, bool is_stderr, const void *data, size_t len) +{ + /* + * stderr data is just spouted to local stderr (optionally via a + * sanitiser) and otherwise ignored. + */ + if (is_stderr) { + 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(" -pw passw login with specified password\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"); +#ifdef MOD_PERSO + printf(" -auto-store-sshkey\n"); + printf(" store automatically the servers ssh keys\n"); +#endif + 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, ret; + 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 ret; + if (argv[i][0] != '-') { + if (userhost) + usage(); + else + userhost = dupstr(argv[i]); + continue; + } + ret = cmdline_process_param(argv[i], i+1mutable 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/0.77_My_PuTTY/psftpcommon.c b/0.77_My_PuTTY/psftpcommon.c new file mode 100644 index 0000000..804ac3e --- /dev/null +++ b/0.77_My_PuTTY/psftpcommon.c @@ -0,0 +1,102 @@ +/* + * 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 "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/0.77_My_PuTTY/psocks.c b/0.77_My_PuTTY/psocks.c new file mode 100644 index 0000000..864c8d1 --- /dev/null +++ b/0.77_My_PuTTY/psocks.c @@ -0,0 +1,569 @@ +/* + * Platform-independent parts of a standalone SOCKS server program + * based on the PuTTY SOCKS code. + */ + +#include +#include + +#include "putty.h" +#include "misc.h" +#include "ssh.h" +#include "sshchan.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, const char *error_msg, + int error_code, bool calling_back); +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, const char *error_msg, + int error_code, bool calling_back) +{ + 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 verify_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/0.77_My_PuTTY/psocks.h b/0.77_My_PuTTY/psocks.h new file mode 100644 index 0000000..96dc888 --- /dev/null +++ b/0.77_My_PuTTY/psocks.h @@ -0,0 +1,28 @@ +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/0.77_My_PuTTY/putty.h b/0.77_My_PuTTY/putty.h new file mode 100644 index 0000000..e227fb7 --- /dev/null +++ b/0.77_My_PuTTY/putty.h @@ -0,0 +1,2730 @@ +#ifndef PUTTY_PUTTY_H +#define PUTTY_PUTTY_H + +#include /* for wchar_t */ +#include /* for INT_MAX */ + +#include "defs.h" +#include "puttyps.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)) + +#ifdef MOD_PERSO +extern int debug_flag ; +#define ATTR_ITALIC 0x1000000U +#include +void debug_log( const char *fmt, ...) ; +int switch_private_key_flag( void ) ; + +/* Ajout de declaration de DLL manquante dans MinGW*/ +//DLL_DIRECTORY_COOKIE WINAPI AddDllDirectory( PCWSTR NewDirectory ); +BOOL WINAPI __declspec(dllimport) SetDefaultDllDirectories( DWORD DirectoryFlags ) ; +BOOL WINAPI __declspec(dllimport) CryptProtectMemory( LPVOID pData, DWORD cbData, DWORD dwFlags ); +#endif +#ifdef MOD_PRINTCLIP +#define PRINT_TO_CLIPBOARD_STRING "Windows clipboard" +#endif +#ifdef MOD_HYPERLINK +/* + * HACK: PuttyTray / Nutty + * Hyperlink stuff: define + */ +#define CHAR_MASK 0x000000FFUL +extern const char* urlhack_default_regex ; +extern const char* urlhack_liberal_regex ; +enum { + URLHACK_UNDERLINE_ALWAYS, + URLHACK_UNDERLINE_HOVER, + URLHACK_UNDERLINE_NEVER +}; +#endif + +/* + * 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 "2018" +#define PGP_MASTER_KEY_DETAILS "RSA, 4096-bit" +#define PGP_MASTER_KEY_FP \ + "24E1 B1C5 75EA 3C9F F752 A922 76BC 7FE4 EBFD 2D9E" +#define PGP_PREV_MASTER_KEY_YEAR "2015" +#define PGP_PREV_MASTER_KEY_DETAILS "RSA, 4096-bit" +#define PGP_PREV_MASTER_KEY_FP \ + "440D E3B5 B7A1 CA85 B3CC 1718 AB58 5DC6 0467 6F7C" + +/* + * 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. + */ + +#ifdef MOD_TUTTYCOLOR /* 4+8 more special TUTTY colors */ +#define CONF_NCOLOURS 34 /* 16 + 18 (6+4+8) special ones */ +#define OSCP_NCOLOURS 34 /* same as CONF, but different order */ +#define OSC4_NCOLOURS 274 /* 256 + the same 18 (6+4+8) special ones */ +#else +#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 */ +#endif + +/* The list macro for the conf colours also gives the textual names + * used in the GUI configurer */ +#ifdef MOD_TUTTYCOLOR +#define CONF_COLOUR_LIST(X) \ + X(fg, "Default Foreground") \ + X(fg_bold, "Default Bold Foreground") \ + X(fg_underline,"Default Underlined Foreground") \ + X(bg, "Default Background") \ + X(bg_bold, "Default Bold Background") \ + X(bg_underline,"Default Underlined Background") \ + X(cursor_fg, "Cursor Text") \ + X(cursor_bg, "Cursor Colour") \ + X(sel_fg,"Selected Text Foreground") \ + X(sel_bg,"Selected Text Background") \ + X(black, "ANSI Black") \ + X(black_bold, "ANSI Black Bold") \ + X(black_underline,"ANSI Black Underlined") \ + X(red, "ANSI Red") \ + X(red_bold, "ANSI Red Bold") \ + X(red_underline,"ANSI Red Underlined") \ + X(green, "ANSI Green") \ + X(green_bold, "ANSI Green Bold") \ + X(green_underline,"ANSI Green Underlined") \ + X(yellow, "ANSI Yellow") \ + X(yellow_bold, "ANSI Yellow Bold") \ + X(yellow_underline,"ANSI Yellow Underlined") \ + X(blue, "ANSI Blue") \ + X(blue_bold, "ANSI Blue Bold") \ + X(blue_underline,"ANSI Blue Underlined") \ + X(magenta, "ANSI Magenta") \ + X(magenta_bold, "ANSI Magenta Bold") \ + X(magenta_underline,"ANSI Magenta Underlined") \ + X(cyan, "ANSI Cyan") \ + X(cyan_bold, "ANSI Cyan Bold") \ + X(cyan_underline,"ANSI Cyan Underlined") \ + X(white, "ANSI White") \ + X(white_bold, "ANSI White Bold") \ + X(white_underline,"ANSI White Underlined") \ + /* 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) \ + X(fg_underline) \ + X(bg_underline) \ + X(sel_fg) \ + X(sel_bg) \ + X(black_underline) \ + X(red_underline) \ + X(green_underline) \ + X(yellow_underline) \ + X(blue_underline) \ + X(magenta_underline) \ + X(cyan_underline) \ + X(white_underline) \ + /* end of list */ +#else +#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 */ +#endif + +/* 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 + +#ifdef MOD_TUTTY +#define ATTR_SELECTED 0x02000000U +#endif + +#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 { + char **uni_tbl; + 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 sshsignals.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 "sshsignals.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 sshchan.h and sshppl.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 { +#ifdef MOD_PERSO + MA_NOTHING, MA_CLICK, MA_2CLK, MA_3CLK, MA_DRAG, MA_MOVE, MA_RELEASE +#else + MA_NOTHING, MA_CLICK, MA_2CLK, MA_3CLK, MA_DRAG, MA_RELEASE +#endif +} Mouse_Action; + +/* Keyboard modifiers -- keys the user is actually holding down */ + +#define PKM_SHIFT 0x01 +#define PKM_CONTROL 0x02 +#define PKM_META 0x04 +#define PKM_ALT 0x08 + +/* Keyboard flags that aren't really modifiers */ +#define PKF_CAPSLOCK 0x10 +#define PKF_NUMLOCK 0x20 +#define PKF_REPEAT 0x40 + +/* Stand-alone keysyms for function keys */ + +typedef enum { + PK_NULL, /* No symbol for this key */ + /* Main keypad keys */ + PK_ESCAPE, PK_TAB, PK_BACKSPACE, PK_RETURN, PK_COMPOSE, + /* Editing keys */ + PK_HOME, PK_INSERT, PK_DELETE, PK_END, PK_PAGEUP, PK_PAGEDOWN, + /* Cursor keys */ + PK_UP, PK_DOWN, PK_RIGHT, PK_LEFT, PK_REST, + /* Numeric keypad */ /* Real one looks like: */ + PK_PF1, PK_PF2, PK_PF3, PK_PF4, /* PF1 PF2 PF3 PF4 */ + PK_KPCOMMA, PK_KPMINUS, PK_KPDECIMAL, /* 7 8 9 - */ + PK_KP0, PK_KP1, PK_KP2, PK_KP3, PK_KP4, /* 4 5 6 , */ + PK_KP5, PK_KP6, PK_KP7, PK_KP8, PK_KP9, /* 1 2 3 en- */ + PK_KPBIGPLUS, PK_KPENTER, /* 0 . ter */ + /* Top row */ + PK_F1, PK_F2, PK_F3, PK_F4, PK_F5, + PK_F6, PK_F7, PK_F8, PK_F9, PK_F10, + PK_F11, PK_F12, PK_F13, PK_F14, PK_F15, + PK_F16, PK_F17, PK_F18, PK_F19, PK_F20, + PK_PAUSE +} Key_Sym; + +#define PK_ISEDITING(k) ((k) >= PK_HOME && (k) <= PK_PAGEDOWN) +#define PK_ISCURSOR(k) ((k) >= PK_UP && (k) <= PK_REST) +#define PK_ISKEYPAD(k) ((k) >= PK_PF1 && (k) <= PK_KPENTER) +#define PK_ISFKEY(k) ((k) >= PK_F1 && (k) <= PK_F20) + +enum { + VT_XWINDOWS, VT_OEMANSI, VT_OEMONLY, VT_POORMAN, VT_UNICODE +}; + +enum { + /* + * SSH-2 key exchange algorithms + */ + KEX_WARN, + KEX_DHGROUP1, + KEX_DHGROUP14, + KEX_DHGEX, + KEX_RSA, + KEX_ECDH, + 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_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_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, +#ifdef MOD_ADB + PROT_ADB, +#endif + /* 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 +#ifdef MOD_TUTTY + , + FUNKY_ATT513, + FUNKY_ATT4410, + FUNKY_SUNXTERM +#endif +}; + +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 */ + +struct Backend { + const BackendVtable *vt; +}; +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); + /* send() returns the current amount of buffered data. */ + size_t (*send) (Backend *be, const char *buf, size_t len); + /* sendbuffer() does the same thing but without attempting a send */ + 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). */ + 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' is a human-readable name + * for error messages. */ + const char *id, *displayname; + + 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 size_t backend_send(Backend *be, const char *buf, size_t len) +{ return 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. + */ +#if (defined MOD_PERSO) && (!defined FLJ) +extern char *appname; +#else +extern const char *const appname; +#endif + +/* + * 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 { + /* + * 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 */ +} prompts_t; +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; + +/* + * 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. 'is_stderr' indicates + * that the output should be sent to a separate error message + * channel, if the seat has one. But combining both 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, bool is_stderr, 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); + + /* + * Try to get answers from a set of interactive login prompts. The + * prompts are provided in 'p'; the bufchain 'input' holds the + * data currently outstanding in the session's normal standard- + * input channel. Seats may implement this function by consuming + * data from 'input' (e.g. password prompts in GUI PuTTY, + * displayed in the same terminal as the subsequent session), or + * by doing something entirely different (e.g. directly + * interacting with standard I/O, or putting up a dialog box). + * + * A positive return value means that all prompts have had answers + * filled in. A zero return means that the user performed a + * deliberate 'cancel' UI action. A negative return means that no + * answer can be given yet but please try again later. + * + * (FIXME: it would be nice to distinguish two classes of cancel + * 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.) + * + * (Also FIXME: currently, backends' only response to the 'try + * again later' is to try again when more input data becomes + * available, because they assume that a seat is returning that + * value because it's consuming keyboard input. But a seat that + * handled this function by putting up a dialog box might want to + * put it up non-modally, and therefore would want to proactively + * notify the backend to retry once the dialog went away. So if I + * ever do want to move password prompts into a dialog box, I'll + * want a backend method for sending that notification.) + */ + int (*get_userpass_input)(Seat *seat, prompts_t *p, bufchain *input); + + /* + * 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 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 may return immediately after checking saved configuration + * or command-line options, or it may have to present a prompt to + * the user and return asynchronously later. + * + * Return values: + * + * - +1 means `key was OK' (either already known or the user just + * approved it) `so continue with the connection' + * + * - 0 means `key was not OK, abandon the connection' + * + * - -1 means `I've initiated enquiries, please wait to be called + * back via the provided function with a result that's either 0 + * or +1'. + */ + int (*verify_ssh_host_key)( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, + void (*callback)(void *ctx, int 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 + * verify_ssh_host_key above. + */ + int (*confirm_weak_crypto_primitive)( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int 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). + */ + int (*confirm_weak_cached_hostkey)( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx); + + /* + * 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. + * + * 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 (*set_trust_status)(Seat *seat, bool trusted); + + /* + * 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, bool err, const void *data, size_t len) +{ return seat->vt->output(seat, err, data, len); } +static inline bool seat_eof(Seat *seat) +{ return seat->vt->eof(seat); } +static inline int seat_get_userpass_input( + Seat *seat, prompts_t *p, bufchain *input) +{ return seat->vt->get_userpass_input(seat, p, input); } +static inline void seat_notify_remote_exit(Seat *seat) +{ seat->vt->notify_remote_exit(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 int seat_verify_ssh_host_key( + Seat *seat, const char *h, int p, const char *ktyp, char *kstr, + const char *kdsp, char **fps, void (*cb)(void *ctx, int result), void *ctx) +{ return seat->vt->verify_ssh_host_key(seat, h, p, ktyp, kstr, kdsp, fps, + cb, ctx); } +static inline int seat_confirm_weak_crypto_primitive( + Seat *seat, const char *atyp, const char *aname, + void (*cb)(void *ctx, int result), void *ctx) +{ return seat->vt->confirm_weak_crypto_primitive(seat, atyp, aname, cb, ctx); } +static inline int seat_confirm_weak_cached_hostkey( + Seat *seat, const char *aname, const char *better, + void (*cb)(void *ctx, int result), void *ctx) +{ return seat->vt->confirm_weak_cached_hostkey(seat, aname, better, cb, ctx); } +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 bool seat_set_trust_status(Seat *seat, bool trusted) +{ return seat->vt->set_trust_status(seat, trusted); } +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 misc.c. */ +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, false, data, len); } +static inline size_t seat_stdout_pl(Seat *seat, ptrlen data) +{ return seat_output(seat, false, data.ptr, data.len); } +static inline size_t seat_stderr(Seat *seat, const void *data, size_t len) +{ return seat_output(seat, true, data, len); } +static inline size_t seat_stderr_pl(Seat *seat, ptrlen data) +{ return seat_output(seat, true, data.ptr, data.len); } + +/* + * 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, bool is_stderr, const void *data, size_t len); +bool nullseat_eof(Seat *seat); +int nullseat_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); +void nullseat_notify_remote_exit(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); +int nullseat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, + void (*callback)(void *ctx, int result), void *ctx); +int nullseat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx); +int nullseat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx); +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); +bool nullseat_set_trust_status(Seat *seat, bool trusted); +bool nullseat_set_trust_status_vacuously(Seat *seat, bool trusted); +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 (wincons.c, uxcons.c). + */ + +void console_connection_fatal(Seat *seat, const char *message); +int console_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, + void (*callback)(void *ctx, int result), void *ctx); +int console_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx); +int console_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx); +StripCtrlChars *console_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic); +bool console_set_trust_status(Seat *seat, bool trusted); + +/* + * Other centralised seat functions. + */ +int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input); +bool cmdline_seat_verbose(Seat *seat); + +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); +#ifdef MOD_PERSO + void (*set_focus_reporting_mode)(TermWin *, bool enable); +#endif + 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 *); + + void (*request_resize)(TermWin *, int w, int h); + + void (*set_title)(TermWin *, const char *title); + void (*set_icon_title)(TermWin *, const char *icontitle); + /* 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 *); +}; + +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); } +#ifdef MOD_PERSO +static inline void win_set_focus_reporting_mode(TermWin *win, bool enable) +{ win->vt->set_focus_reporting_mode(win, enable); } +#endif +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) +{ win->vt->set_title(win, title); } +static inline void win_set_icon_title(TermWin *win, const char *icontitle) +{ win->vt->set_icon_title(win, icontitle); } +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); } + + +/* + * 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) \ + /* \ + * 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) \ +/* #ifdef MOD_PERSO */ \ + X(INT, NONE, enter_sends_crlf) \ + X(INT, NONE, rxvt_homeend) \ +/* #else */ \ +/* X(BOOL, NONE, rxvt_homeend) */\ +/* #endif */ \ + X(INT, NONE, funky_type) /* FUNKY_XTERM, FUNKY_LINUX, ... */ \ + 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) \ + /* \ + * 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) \ +/* #ifdef MOD_PERSO */ \ + X(STR, NONE, host_alt) \ + X(INT, NONE, bcdelay) \ + X(INT, NONE, initdelay) \ + X(INT, NONE, transparencynumber) \ + X(INT, NONE, sendtotray) \ + X(INT, NONE, maximize) \ + X(INT, NONE, fullscreen) \ + X(BOOL, NONE, saveonexit) \ + X(INT, NONE, icone) \ + X(FILENAME, NONE, iconefile) \ + X(INT, NONE, winscpprot) \ + X(STR, NONE, sftpconnect) \ + X(STR, NONE, pscpoptions) \ + X(STR, NONE, pscpshell) \ + X(STR, NONE, pscpremotedir) \ + X(STR, NONE, winscpoptions) \ + X(STR, NONE, winscprawsettings) \ + X(STR, NONE, folder) \ + X(STR, NONE, password) \ + X(STR, NONE, autocommand) \ + X(STR, NONE, autocommandout) \ + X(STR, NONE, sessionname) \ + X(STR, NONE, antiidle) \ + X(INT, NONE, logtimerotation) \ + X(STR, NONE, logtimestamp) \ + X(FILENAME, NONE, scriptfile) \ + X(STR, NONE, scriptfilecontent) \ + X(BOOL, NONE, save_windowpos) \ + X(INT, NONE, xpos) \ + X(INT, NONE, ypos) \ + X(INT, NONE, windowstate) \ + X(BOOL, NONE, foreground_on_bell) \ + X(INT, NONE, ctrl_tab_switch) /* switch PuTTY windows with CtrlTab */ \ + X(STR, NONE, comment) \ + X(INT, NONE, scp_auto_pwd) \ + X(BOOL, NONE, no_focus_rep) /* totally disable mouse reporting */ \ + X(INT, NONE, scrolllines) /* Options for Scroll Lines per Wheel */ \ + X(BOOL, NONE, ssh_tunnel_print_in_title) /* print dynamic port number in window title */ \ +/* #endif */ \ +/* #ifdef MOD_PROXY */ \ + X(STR, NONE, proxyselection) /* Options for proxy selection */ \ +/* #endif */ \ +/* #ifdef MOD_PRINTCLIP */ \ + X(INT, NONE, printclip) \ +/* #endif */ \ +/* rutty: scripting options */ \ +/* #ifdef MOD_RUTTY*/ \ + X(FILENAME, NONE, script_filename) \ + X(INT, NONE, script_mode) \ + X(INT, NONE, script_line_delay) \ + X(INT, NONE, script_char_delay) \ + X(STR, NONE, script_cond_line) \ + X(INT, NONE, script_cond_use) \ + X(INT, NONE, script_crlf) \ + X(INT, NONE, script_enable) \ + X(INT, NONE, script_except) \ + X(INT, NONE, script_timeout) \ + X(STR, NONE, script_waitfor) \ + X(STR, NONE, script_halton) \ +/* #endif */ \ +/* #if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) */\ + X(INT, NONE, bg_opacity) \ + X(INT, NONE, bg_slideshow) \ + X(INT, NONE, bg_type) /* 0=solid 1=wallpaper 2=bitmap */ \ + X(FILENAME, NONE, bg_image_filename) \ + X(INT, NONE, bg_image_style) /* 0=tile 1=center 2=stretch 3=(x,y) 4=Blanck background 5=Stretch Plus*/ \ + X(INT, NONE, bg_image_abs_x) \ + X(INT, NONE, bg_image_abs_y) \ + X(INT, NONE, bg_image_abs_fixed) /* 0=fixed to desktop, 1=fixed to term wnd */ \ +/* #endif */ \ +/* #ifdef MOD_RECONNECT */ \ + X(INT, NONE, wakeup_reconnect) \ + X(INT, NONE, failure_reconnect) \ +/* #endif */ \ +/* #ifdef MOD_HYPERLINK */ \ + /* \ + * HACK: PuttyTray / Nutty \ + * Hyperlink stuff: Underline settings \ + */ \ + X(INT, NONE, url_ctrl_click) \ + X(INT, NONE, url_underline) \ + X(INT, NONE, url_defbrowser) \ + X(INT, NONE, url_defregex) \ + X(FILENAME, NONE, url_browser) \ + X(STR, NONE, url_regex) \ +/* #endif */ \ +/* #ifdef MOD_TUTTY */ \ + X(INT, NONE, window_closable) \ + X(INT, NONE, window_minimizable) \ + X(INT, NONE, window_maximizable) \ + X(INT, NONE, window_has_sysmenu) \ + X(INT, NONE, bottom_buttons) \ + X(INT, NONE, bold_colour) \ + X(INT, NONE, under_colour) \ + X(INT, NONE, sel_colour) \ +/* #endif */ \ +/* #ifdef MOD_ZMODEM */ \ + /* Z modem options */ \ + X(FILENAME, NONE, rzcommand) \ + X(STR, NONE, rzoptions) \ + X(FILENAME, NONE, szcommand) \ + X(STR, NONE, szoptions) \ + X(STR, NONE, zdownloaddir) \ +/* #endif */ \ +/* #ifdef MOD_PORTKNOCKING */ \ + /* port knocking options */ \ + X(STR, NONE, portknockingoptions) \ +/* #endif */ \ +/* #ifdef MOD_DISABLEALTGR */ \ + /* Disable AltGr button */ \ + X(INT, NONE, disablealtgr) \ +/* #endif */ \ + /* 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 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); +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_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_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 *, bool is_stderr, 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); +int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input); +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; + +#ifdef MOD_KEYMAPPING +int format_arrow_key(char *buf, Terminal *term, int xkey, int modifier, bool alt); +int format_function_key(char *buf, Terminal *term, int key_number, int modifier, bool alt); +int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key, int modifier, bool alt); +#else +int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl); +int format_function_key(char *buf, Terminal *term, int key_number, + bool shift, bool ctrl); +int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key); +#endif + +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 conscli.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); +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; + +#ifdef MOD_ADB +/* + * Exports from adb.c. + */ + +//extern Backend adb_backend; +extern const struct BackendVtable adb_backend ; +#endif + +/* + * Exports from rlogin.c. + */ + +extern const struct BackendVtable rlogin_backend; + +/* + * Exports from telnet.c. + */ + +extern const struct BackendVtable telnet_backend; + +/* + * Exports from 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 *); + +/* + * 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 misc.c. + */ + +#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 sercfg.c. + */ +void ser_setup_config_box(struct controlbox *b, bool midsession, + int parity_mask, int flow_mask); + +/* + * Exports from version.c. + */ +extern const char ver[]; +extern const char commitid[]; + +/* + * Exports from unicode.c. + */ +#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, + struct unicode_data *ucsdata); +wchar_t xlat_uskbd2cyrllic(int ch); +int check_compose(int first, int second); +int decode_codepage(char *cp_name); +const char *cp_enumerate (int index); +const char *cp_name(int codepage); +void get_unitab(int codepage, wchar_t * unitab, int ftype); + +/* + * Exports from wcwidth.c + */ +int mk_wcwidth(unsigned int ucs); +int mk_wcswidth(const unsigned int *pwcs, size_t n); +int mk_wcwidth_cjk(unsigned int ucs); +int mk_wcswidth_cjk(const unsigned int *pwcs, size_t n); + +/* + * Exports from pageantc.c. + * + * 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() 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 (windlg.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 (wincons.c, uxcons.c) + * that aren't equivalents to things in windlg.c et al. + */ +extern bool console_batch_mode, console_antispoof_prompt; +int 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. + */ +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'. + */ +int cmdline_process_param(const char *, char *, int, Conf *); +void cmdline_run_saved(Conf *); +void cmdline_cleanup(void); +int cmdline_get_passwd_input(prompts_t *p); +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; +union control; +void conf_radiobutton_handler(union control *ctrl, dlgparam *dlg, + void *data, int event); +#define CHECKBOX_INVERT (1<<30) +void conf_checkbox_handler(union control *ctrl, dlgparam *dlg, + void *data, int event); +void conf_editbox_handler(union control *ctrl, dlgparam *dlg, + void *data, int event); +void conf_filesel_handler(union control *ctrl, dlgparam *dlg, + void *data, int event); +void conf_fontsel_handler(union control *ctrl, dlgparam *dlg, + void *data, int event); + +void setup_config_box(struct controlbox *b, bool midsession, + int protocol, int protcfginfo); + +/* + * Exports from minibidi.c. + */ +#define BIDI_CHAR_INDEX_NONE ((unsigned short)-1) +typedef struct bidi_char { + unsigned int origwc, wc; + unsigned short index, nchars; +} bidi_char; +int do_bidi(bidi_char *line, int 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[]; /* declared in x11fwd.c */ + +/* + * 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. + */ +typedef void (*toplevel_callback_fn_t)(void *ctx); +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); + +/* + * 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/0.77_My_PuTTY/puttymem.h b/0.77_My_PuTTY/puttymem.h new file mode 100644 index 0000000..ac1bd68 --- /dev/null +++ b/0.77_My_PuTTY/puttymem.h @@ -0,0 +1,128 @@ +/* + * 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 misc.c + * 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 winmisc.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/0.77_My_PuTTY/puttyps.h b/0.77_My_PuTTY/puttyps.h new file mode 100644 index 0000000..27916d2 --- /dev/null +++ b/0.77_My_PuTTY/puttyps.h @@ -0,0 +1,18 @@ +/* + * Find the platform-specific header for this platform. + */ + +#ifndef PUTTY_PUTTYPS_H +#define PUTTY_PUTTYPS_H + +#ifdef _WINDOWS + +#include "winstuff.h" + +#else + +#include "unix.h" + +#endif + +#endif diff --git a/0.77_My_PuTTY/raw.c b/0.77_My_PuTTY/raw.c new file mode 100644 index 0000000..59f64a8 --- /dev/null +++ b/0.77_My_PuTTY/raw.c @@ -0,0 +1,330 @@ +/* + * "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; + bool sent_console_eof, sent_socket_eof, session_started; + + Conf *conf; + + Plug plug; + Backend backend; +}; + +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->session_started); +} + +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); + } + } +} + +static void raw_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + Raw *raw = container_of(plug, Raw, plug); + + if (error_msg) { + /* A socket error has occurred. */ + if (raw->s) { + sk_close(raw->s); + raw->s = NULL; + raw->closed_on_socket_error = true; + seat_notify_remote_exit(raw->seat); + } + logevent(raw->logctx, error_msg); + 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); + /* We count 'session start', for proxy logging purposes, as being + * when data is received from the network and printed. */ + raw->session_started = true; +} + +static void raw_sent(Plug *plug, size_t bufsize) +{ + Raw *raw = container_of(plug, Raw, plug); + raw->bufsize = bufsize; +} + +static const PlugVtable Raw_plugvt = { + .log = raw_log, + .closing = raw_closing, + .receive = raw_receive, + .sent = raw_sent, +}; + +/* + * 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; + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + raw = snew(Raw); + raw->plug.vt = &Raw_plugvt; + raw->backend.vt = vt; + 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->session_started = false; + raw->conf = conf_copy(conf); + + 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); + if ((err = sk_socket_error(raw->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 raw_free(Backend *be) +{ + Raw *raw = container_of(be, Raw, backend); + + if (raw->s) + sk_close(raw->s); + conf_free(raw->conf); + 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 size_t raw_send(Backend *be, const char *buf, size_t len) +{ + Raw *raw = container_of(be, Raw, backend); + + if (raw->s == NULL) + return 0; + + raw->bufsize = sk_write(raw->s, buf, len); + + return raw->bufsize; +} + +/* + * Called to query the current socket sendability status. + */ +static 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) { + 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) +{ + return true; +} + +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) +{ + /* This is a stub. */ +} + +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 = "Raw", + .protocol = PROT_RAW, + .default_port = 0, +}; diff --git a/0.77_My_PuTTY/resource.h b/0.77_My_PuTTY/resource.h new file mode 100644 index 0000000..07d5008 --- /dev/null +++ b/0.77_My_PuTTY/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by win_res.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/0.77_My_PuTTY/rlogin.c b/0.77_My_PuTTY/rlogin.c new file mode 100644 index 0000000..f0e15c1 --- /dev/null +++ b/0.77_My_PuTTY/rlogin.c @@ -0,0 +1,428 @@ +/* + * 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 firstbyte; + bool cansize; + int term_width, term_height; + Seat *seat; + LogContext *logctx; + + Conf *conf; + + /* In case we need to read a username from the terminal before starting */ + prompts_t *prompt; + + Plug plug; + Backend backend; +}; + +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->firstbyte); +} + +static void rlogin_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + 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); + } + if (error_msg) { + /* A socket error has occurred. */ + logevent(rlogin->logctx, error_msg); + 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; +} + +static void rlogin_startup(Rlogin *rlogin, const char *ruser) +{ + char z = 0; + char *p; + + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_localusername); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, &z, 1); + sk_write(rlogin->s, ruser, strlen(ruser)); + sk_write(rlogin->s, &z, 1); + p = conf_get_str(rlogin->conf, CONF_termtype); + sk_write(rlogin->s, p, strlen(p)); + sk_write(rlogin->s, "/", 1); + p = conf_get_str(rlogin->conf, CONF_termspeed); + sk_write(rlogin->s, p, strspn(p, "0123456789")); + rlogin->bufsize = sk_write(rlogin->s, &z, 1); + + rlogin->prompt = NULL; +} + +static const PlugVtable Rlogin_plugvt = { + .log = rlogin_log, + .closing = rlogin_closing, + .receive = rlogin_receive, + .sent = rlogin_sent, +}; + +/* + * 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; + char *ruser; + int addressfamily; + char *loghost; + + rlogin = snew(Rlogin); + rlogin->plug.vt = &Rlogin_plugvt; + rlogin->backend.vt = vt; + 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->firstbyte = true; + rlogin->cansize = false; + rlogin->prompt = NULL; + rlogin->conf = conf_copy(conf); + *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); + 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'; + } + + /* + * Send local username, remote username, terminal type and + * terminal speed - unless we don't have the remote username yet, + * in which case we prompt for it and may end up deferring doing + * anything else until the local prompt mechanism returns. + */ + if ((ruser = get_remote_username(conf)) != NULL) { + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + rlogin_startup(rlogin, ruser); + sfree(ruser); + } else { + int ret; + + rlogin->prompt = new_prompts(); + rlogin->prompt->to_server = true; + rlogin->prompt->from_server = false; + rlogin->prompt->name = dupstr("Rlogin login name"); + add_prompt(rlogin->prompt, dupstr("rlogin username: "), true); + ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, NULL); + if (ret >= 0) { + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + rlogin_startup(rlogin, prompt_get_result_ref( + rlogin->prompt->prompts[0])); + } + } + + return NULL; +} + +static void rlogin_free(Backend *be) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + + if (rlogin->prompt) + free_prompts(rlogin->prompt); + if (rlogin->s) + sk_close(rlogin->s); + conf_free(rlogin->conf); + sfree(rlogin); +} + +/* + * Stub routine (we don't have any need to reconfigure this backend). + */ +static void rlogin_reconfig(Backend *be, Conf *conf) +{ +} + +/* + * Called to send data down the rlogin connection. + */ +static size_t rlogin_send(Backend *be, const char *buf, size_t len) +{ + Rlogin *rlogin = container_of(be, Rlogin, backend); + bufchain bc; + + if (rlogin->s == NULL) + return 0; + + bufchain_init(&bc); + bufchain_add(&bc, buf, len); + + if (rlogin->prompt) { + /* + * We're still prompting for a username, and aren't talking + * directly to the network connection yet. + */ + int ret = seat_get_userpass_input(rlogin->seat, rlogin->prompt, &bc); + if (ret >= 0) { + /* Next terminal output will come from server */ + seat_set_trust_status(rlogin->seat, false); + rlogin_startup(rlogin, prompt_get_result_ref( + rlogin->prompt->prompts[0])); + /* that nulls out rlogin->prompt, so then we'll start sending + * data down the wire in the obvious way */ + } + } + + if (!rlogin->prompt) { + while (bufchain_size(&bc) > 0) { + ptrlen data = bufchain_prefix(&bc); + rlogin->bufsize = sk_write(rlogin->s, data.ptr, data.len); + bufchain_consume(&bc, len); + } + } + + bufchain_clear(&bc); + + return rlogin->bufsize; +} + +/* + * 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) +{ + /* Rlogin *rlogin = container_of(be, Rlogin, backend); */ + return true; +} + +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) +{ + /* This is a stub. */ +} + +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 = "Rlogin", + .protocol = PROT_RLOGIN, + .default_port = 513, +}; diff --git a/0.77_My_PuTTY/scpserver.c b/0.77_My_PuTTY/scpserver.c new file mode 100644 index 0000000..c4c2125 --- /dev/null +++ b/0.77_My_PuTTY/scpserver.c @@ -0,0 +1,1399 @@ +/* + * Server side of the old-school SCP protocol. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshcr.h" +#include "sshchan.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(); + strbuf_catf(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 */ + strbuf_catf(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(); + strbuf_catf(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 { + ptrlen subpath; + subpath.len = node->pathname.len + 1 + scp->reply.name.len; + char *subpath_space = snewn(subpath.len, char); + subpath.ptr = subpath_space; + memcpy(subpath_space, node->pathname.ptr, node->pathname.len); + subpath_space[node->pathname.len] = '/'; + memcpy(subpath_space + node->pathname.len + 1, + scp->reply.name.ptr, scp->reply.name.len); + + scp->head = node; /* put back the unfinished READDIR */ + node = NULL; /* and prevent it being freed */ + scp_source_push_name(scp, subpath, scp->reply.attrs, NULL); + + sfree(subpath_space); + } + } 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/0.77_My_PuTTY/sesschan.c b/0.77_My_PuTTY/sesschan.c new file mode 100644 index 0000000..3555ca8 --- /dev/null +++ b/0.77_My_PuTTY/sesschan.c @@ -0,0 +1,787 @@ +/* + * Implement the "session" channel type for the SSH server. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshchan.h" +#include "sshserver.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 *, bool is_stderr, 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, + .get_userpass_input = nullseat_get_userpass_input, + .notify_remote_exit = sesschan_notify_remote_exit, + .connection_fatal = sesschan_connection_fatal, + .update_specials_menu = nullseat_update_specials_menu, + .get_ttymode = nullseat_get_ttymode, + .set_busy_status = nullseat_set_busy_status, + .verify_ssh_host_key = nullseat_verify_ssh_host_key, + .confirm_weak_crypto_primitive = nullseat_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = nullseat_confirm_weak_cached_hostkey, + .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, + .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 uxpty.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; + + return backend_send(sess->backend, data, length); +} + +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) +{ + /* I don't think we need to do anything here */ +} + +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 uxpty.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, const char *error_msg, int error_code, + bool calling_back) +{ /* 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; + char screensuffix[32]; + + if (oneshot) + return false; /* not supported */ + + snprintf(screensuffix, sizeof(screensuffix), ".%u", screen_number); + + /* + * 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(hex[i]) || !isxdigit(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; + + 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); + + 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 uxpty.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 "sshsignals.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, bool is_stderr, const void *data, size_t len) +{ + sesschan *sess = container_of(seat, sesschan, seat); + return sshfwd_write_ext(sess->c, is_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/0.77_My_PuTTY/sessprep.c b/0.77_My_PuTTY/sessprep.c new file mode 100644 index 0000000..55d55ba --- /dev/null +++ b/0.77_My_PuTTY/sessprep.c @@ -0,0 +1,84 @@ +/* + * 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/0.77_My_PuTTY/settings.c b/0.77_My_PuTTY/settings.c new file mode 100644 index 0000000..0211980 --- /dev/null +++ b/0.77_My_PuTTY/settings.c @@ -0,0 +1,1788 @@ +/* + * settings.c: read and write saved sessions. (platform-independent) + */ + +#include +#include +#include +#include "putty.h" +#include "storage.h" +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.h" +#endif + +/* rutty: */ +#ifdef MOD_RUTTY +#include "script.h" +#endif /* rutty */ +#ifdef MOD_HYPERLINK +#include "urlhack.h" +#endif + +#ifdef MOD_PERSO +#ifndef SAVEMODE_DIR +#define SAVEMODE_DIR 2 +#endif +extern char FileExtension[15] ; +int cryptpassword( int mode, char * password, const char * host, const char * termtype ) ; +int decryptpassword( int mode, char * password, const char * host, const char * termtype ) ; +int get_param( const char * val ) ; +void MASKPASS( const int mode, char * password ) ; +int GetBackgroundImageFlag(void) ; +int GetCryptSaltFlag() ; +int DebugGetPassword( Conf *conf, const char *pwd ) ; +void debug_logevent( const char *fmt, ... ) ; +#endif +#ifdef MOD_PROXY +#include "kitty_proxy.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 }, + { "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[] = { + { "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 }, + { "dh-group14-sha1", KEX_DHGROUP14, -1, -1 }, + { "dh-group1-sha1", KEX_DHGROUP1, KEX_WARN, +1 }, + { "rsa", KEX_RSA, KEX_WARN, -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.c, 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_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)); +#ifdef MOD_PERSO + write_setting_i(sesskey, "EnterSendsCrLf", conf_get_int(conf, CONF_enter_sends_crlf)); + write_setting_i(sesskey, "RXVTHomeEnd", conf_get_int(conf, CONF_rxvt_homeend)); +#else + write_setting_b(sesskey, "RXVTHomeEnd", conf_get_bool(conf, CONF_rxvt_homeend)); +#endif + write_setting_i(sesskey, "LinuxFunctionKeys", conf_get_int(conf, CONF_funky_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); + +#ifdef MOD_TUTTY + write_setting_i(sesskey, "WindowClosable", conf_get_int(conf, CONF_window_closable) ); + write_setting_i(sesskey, "WindowMinimizable", conf_get_int(conf, CONF_window_minimizable) ); + write_setting_i(sesskey, "WindowMaximizable", conf_get_int(conf, CONF_window_maximizable) ); + write_setting_i(sesskey, "WindowHasSysMenu", conf_get_int(conf, CONF_window_has_sysmenu) ); + write_setting_i(sesskey, "DisableBottomButtons", conf_get_int(conf, CONF_bottom_buttons) ); +#endif +#ifdef MOD_TUTTYCOLOR + write_setting_i(sesskey, "BoldAsColourTest", conf_get_int(conf, CONF_bold_colour) ); + write_setting_i(sesskey, "UnderlinedAsColour", conf_get_int(conf, CONF_under_colour) ); + write_setting_i(sesskey, "SelectedAsColour", conf_get_int(conf, CONF_sel_colour) ); + for (i = 0; i < 34; i++) { +#else + for (i = 0; i < 22; i++) { +#endif + 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_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)); + +#ifdef MOD_PROXY + write_setting_s(sesskey, "ProxySelection", conf_get_str(conf, CONF_proxyselection)); +#endif +/* rutty: */ +#ifdef MOD_RUTTY + write_setting_filename(sesskey, "ScriptFileName", conf_get_filename(conf, CONF_script_filename)); + write_setting_i(sesskey, "ScriptMode", (conf_get_int(conf, CONF_script_mode)!=SCRIPT_PLAY)?SCRIPT_STOP:conf_get_int(conf, CONF_script_mode)); /* dont save with record on */ + write_setting_i(sesskey, "ScriptLineDelay", conf_get_int(conf, CONF_script_line_delay)); + write_setting_i(sesskey, "ScriptCharDelay", conf_get_int(conf, CONF_script_char_delay)); + write_setting_s(sesskey, "ScriptCondLine", conf_get_str(conf, CONF_script_cond_line)); + write_setting_i(sesskey, "ScriptCondUse", conf_get_int(conf, CONF_script_cond_use)); + write_setting_i(sesskey, "ScriptCRLF", conf_get_int(conf, CONF_script_crlf)); + write_setting_i(sesskey, "ScriptEnable", conf_get_int(conf, CONF_script_enable)); + write_setting_i(sesskey, "ScriptExcept", conf_get_int(conf, CONF_script_except)); + write_setting_i(sesskey, "ScriptTimeout", conf_get_int(conf, CONF_script_timeout)); + write_setting_s(sesskey, "ScriptWait", conf_get_str(conf, CONF_script_waitfor)); + write_setting_s(sesskey, "ScriptHalt", conf_get_str(conf, CONF_script_halton)); +#endif /* rutty */ +#ifdef MOD_RECONNECT + write_setting_i(sesskey, "WakeupReconnect", conf_get_int(conf,CONF_wakeup_reconnect) ); + write_setting_i(sesskey, "FailureReconnect", conf_get_int(conf,CONF_failure_reconnect) ); +#endif +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + if( GetBackgroundImageFlag() ) { + write_setting_i(sesskey, "BgOpacity", conf_get_int(conf, CONF_bg_opacity) ); + write_setting_i(sesskey, "BgSlideshow", conf_get_int(conf, CONF_bg_slideshow) ); + write_setting_i(sesskey, "BgType", conf_get_int(conf, CONF_bg_type) ); + write_setting_filename(sesskey, "BgImageFile", conf_get_filename(conf, CONF_bg_image_filename) ); + write_setting_i(sesskey, "BgImageStyle", conf_get_int(conf, CONF_bg_image_style) ); + write_setting_i(sesskey, "BgImageAbsoluteX", conf_get_int(conf, CONF_bg_image_abs_x) ); + write_setting_i(sesskey, "BgImageAbsoluteY", conf_get_int(conf, CONF_bg_image_abs_y) ); + write_setting_i(sesskey, "BgImagePlacement", conf_get_int(conf, CONF_bg_image_abs_fixed) ); + } +#endif +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + * Hyperlink stuff: Save hyperlink settings + */ + write_setting_i(sesskey, "HyperlinkUnderline", conf_get_int(conf, CONF_url_underline)); + write_setting_i(sesskey, "HyperlinkUseCtrlClick", conf_get_int(conf, CONF_url_ctrl_click)); + write_setting_i(sesskey, "HyperlinkBrowserUseDefault", conf_get_int(conf, CONF_url_defbrowser)); + write_setting_filename(sesskey, "HyperlinkBrowser", conf_get_filename(conf, CONF_url_browser)); + write_setting_i(sesskey, "HyperlinkRegularExpressionUseDefault", conf_get_int(conf, CONF_url_defregex)); +#ifndef MOD_NOHYPERLINK + if( !strcmp(conf_get_str(conf, CONF_url_regex),"@°@°@NO REGEX--") ) + write_setting_s(sesskey, "HyperlinkRegularExpression", urlhack_default_regex ) ; + else + write_setting_s(sesskey, "HyperlinkRegularExpression", conf_get_str(conf, CONF_url_regex)); +#else + write_setting_s(sesskey, "HyperlinkRegularExpression", conf_get_str(conf, CONF_url_regex)); +#endif +#endif +#ifdef MOD_ZMODEM + write_setting_filename(sesskey, "rzCommand", conf_get_filename(conf, CONF_rzcommand) ); + write_setting_s(sesskey, "rzOptions", conf_get_str(conf, CONF_rzoptions) ); + write_setting_filename(sesskey, "szCommand", conf_get_filename(conf, CONF_szcommand) ); + write_setting_s(sesskey, "szOptions", conf_get_str(conf, CONF_szoptions) ); + write_setting_s(sesskey, "zDownloadDir", conf_get_str(conf, CONF_zdownloaddir) ); +#endif +#ifdef MOD_PERSO + if( conf_get_int(conf, CONF_transparencynumber)<-1 ) conf_set_int(conf, CONF_transparencynumber,-1) ; + if( conf_get_int(conf, CONF_transparencynumber)>255 ) conf_set_int(conf, CONF_transparencynumber,255) ; + write_setting_s(sesskey, "HostAlt", conf_get_str(conf, CONF_host_alt) ); + write_setting_i(sesskey, "TransparencyValue", conf_get_int(conf, CONF_transparencynumber) ) ; + write_setting_i(sesskey, "SendToTray", conf_get_int(conf, CONF_sendtotray) ); + write_setting_i(sesskey, "Maximize", conf_get_int(conf, CONF_maximize) ); + write_setting_i(sesskey, "Fullscreen", conf_get_int(conf, CONF_fullscreen) ); + write_setting_b(sesskey, "SaveOnExit", conf_get_bool(conf, CONF_saveonexit) ); + write_setting_i(sesskey, "Icone", conf_get_int(conf, CONF_icone) ); + write_setting_filename(sesskey, "IconeFile", conf_get_filename(conf, CONF_iconefile) ); + write_setting_i(sesskey, "WinSCPProtocol", conf_get_int(conf, CONF_winscpprot) ); + write_setting_s(sesskey, "SFTPConnect", conf_get_str(conf, CONF_sftpconnect) ); + write_setting_s(sesskey, "PSCPOptions", conf_get_str(conf, CONF_pscpoptions) ); + write_setting_s(sesskey, "PSCPShell", conf_get_str(conf, CONF_pscpshell) ); + write_setting_s(sesskey, "PSCPRemoteDir", conf_get_str(conf, CONF_pscpremotedir) ); + write_setting_s(sesskey, "WinSCPOptions", conf_get_str(conf, CONF_winscpoptions) ); + write_setting_s(sesskey, "WinSCPRawSettings", conf_get_str(conf, CONF_winscprawsettings) ); + Filename * fn = filename_from_str( "" ) ; + conf_set_filename(conf,CONF_scriptfile,fn); + write_setting_filename(sesskey, "Scriptfile", conf_get_filename(conf, CONF_scriptfile) ); // C'est le contenu uniquement qui est important a sauvegarder + filename_free(fn); + write_setting_s(sesskey, "ScriptfileContent", conf_get_str(conf, CONF_scriptfilecontent) ); + write_setting_s(sesskey, "AntiIdle", conf_get_str(conf, CONF_antiidle) ); + write_setting_s(sesskey, "LogTimestamp", conf_get_str(conf, CONF_logtimestamp) ); + write_setting_s(sesskey, "Autocommand", conf_get_str(conf, CONF_autocommand) ); + write_setting_s(sesskey, "AutocommandOut", conf_get_str(conf, CONF_autocommandout) ); + write_setting_s(sesskey, "Folder", conf_get_str(conf, CONF_folder) ) ; + write_setting_i(sesskey, "LogTimeRotation", conf_get_int(conf, CONF_logtimerotation) ) ; + write_setting_i(sesskey, "TermXPos", conf_get_int(conf, CONF_xpos) ) ; + write_setting_i(sesskey, "TermYPos", conf_get_int(conf, CONF_ypos) ) ; + write_setting_i(sesskey, "WindowState", conf_get_int(conf, CONF_windowstate) ) ; + write_setting_b(sesskey, "SaveWindowPos", conf_get_bool(conf, CONF_save_windowpos) ); /* BKG */ + write_setting_b(sesskey, "ForegroundOnBell", conf_get_bool(conf, CONF_foreground_on_bell) ); + +#ifndef MOD_NOPASSWORD + char pst[4096] ; + strcpy( pst, conf_get_str(conf, CONF_password ) ); + MASKPASS(GetCryptSaltFlag(), pst); + cryptpassword( GetCryptSaltFlag(), pst, conf_get_str(conf, CONF_host), conf_get_str(conf, CONF_termtype) ) ; + write_setting_s(sesskey, "Password", pst); + memset(pst,0,strlen(pst)); +#endif + write_setting_i(sesskey, "CtrlTabSwitch", conf_get_int(conf, CONF_ctrl_tab_switch)); + write_setting_s(sesskey, "Comment", conf_get_str(conf, CONF_comment) ); + write_setting_i(sesskey, "SCPAutoPwd", conf_get_int(conf, CONF_scp_auto_pwd)); + write_setting_b(sesskey, "NoFocusReporting", conf_get_bool(conf, CONF_no_focus_rep)); + write_setting_i(sesskey, "LinesAtAScroll", conf_get_int(conf, CONF_scrolllines)); + write_setting_b(sesskey, "SSHTunnelInTitle", conf_get_bool(conf, CONF_ssh_tunnel_print_in_title)); +#endif +#ifdef MOD_PORTKNOCKING + write_setting_s(sesskey, "PortKnocking", conf_get_str(conf, CONF_portknockingoptions) ); +#endif +#ifdef MOD_DISABLEALTGR + write_setting_i(sesskey, "DisableAltGr", conf_get_int(conf, CONF_disablealtgr)); +#endif +} + +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); +#ifdef MOD_PERSO + if( section != NULL ) conf_set_str( conf, CONF_sessionname, section ) ; + else conf_set_str( conf, CONF_sessionname, "" ) ; +#endif + close_settings_r(sesskey); +#ifdef MOD_PERSO + if (exists && conf_launchable(conf)) { + if( get_param("INIFILE") == SAVEMODE_DIR ) { + char *name = NULL ; + if( (section!=NULL) && (strlen(section)>0) && (strlen(conf_get_str( conf, CONF_folder))>0) ) { + if( strcmp(conf_get_str( conf, CONF_folder), "Default" ) ) { + name = (char*)malloc( strlen(section)+strlen(conf_get_str( conf, CONF_folder)) + 2 ) ; + sprintf( name, "%s/%s", conf_get_str( conf, CONF_folder), section ) ; + } + } else if( (section!=NULL) && (strlen(section)>0) ) { + name = (char*)malloc( strlen(section)+1 ) ; + sprintf( name, "%s", section ) ; + } + if( name!=NULL ) { + //add_session_to_jumplist(section) ; + add_session_to_jumplist(name) ; + free(name) ; + } else { + add_session_to_jumplist(section) ; + } + } else { + add_session_to_jumplist(section); + } + } +#else + if (exists && conf_launchable(conf)) + add_session_to_jumplist(section); +#endif + return exists; +} + +void load_open_settings(settings_r *sesskey, Conf *conf) +{ + int i; + char *prot; + +#ifdef MOD_PERSO + /* + * HACK: PuTTY-url + * Set font quality to cleartype on Windows Vista and above + */ + OSVERSIONINFO versioninfo; + versioninfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&versioninfo); +#endif + 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-group14-sha1,rsa," + "WARN,dh-group1-sha1", + *bugdhgex2_default = "ecdh,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); + 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); +#ifdef MOD_PERSO + gppi(sesskey, "EnterSendsCrLf", 0, conf, CONF_enter_sends_crlf); + gppi(sesskey, "RXVTHomeEnd", 0, conf, CONF_rxvt_homeend); +#else + gppb(sesskey, "RXVTHomeEnd", false, conf, CONF_rxvt_homeend); +#endif + gppi(sesskey, "LinuxFunctionKeys", 0, conf, CONF_funky_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); +#if (defined MOD_PERSO) && (!defined FLJ) + gpps(sesskey, "Answerback", "KiTTY", conf, CONF_answerback); +#else + gpps(sesskey, "Answerback", "PuTTY", conf, CONF_answerback); +#endif + 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); +#ifdef MOD_FAR2L + gppi(sesskey, "CurType", 1, conf, CONF_cursor_type); + gppb(sesskey, "BlinkCur", true, conf, CONF_blink_cur); +#else + gppi(sesskey, "CurType", 0, conf, CONF_cursor_type); + gppb(sesskey, "BlinkCur", false, conf, CONF_blink_cur); +#endif + /* 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 + ); +#ifdef MOD_HYPERLINK + gppi(sesskey, "ScrollbackLines", 10000, conf, CONF_savelines); +#else + gppi(sesskey, "ScrollbackLines", 2000, conf, CONF_savelines); +#endif + 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); +#ifdef MOD_PERSO + /* + * HACK: PuTTY-url + * Set font quality to cleartype on Windows Vista and higher + */ + if (versioninfo.dwMajorVersion >= 6) { + gppi(sesskey, "FontQuality", FQ_CLEARTYPE, conf, CONF_font_quality); + } else { + gppi(sesskey, "FontQuality", FQ_DEFAULT, conf, CONF_font_quality); + } +#else + gppi(sesskey, "FontQuality", FQ_DEFAULT, conf, CONF_font_quality); +#endif + 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); + +#ifdef MOD_TUTTY + gppi(sesskey, "WindowClosable", 1, conf, CONF_window_closable); + gppi(sesskey, "WindowMinimizable", 1, conf, CONF_window_minimizable); + gppi(sesskey, "WindowMaximizable", 1, conf, CONF_window_maximizable); + gppi(sesskey, "WindowHasSysMenu", 1, conf, CONF_window_has_sysmenu); + gppi(sesskey, "DisableBottomButtons", 1, conf, CONF_bottom_buttons); +#endif +#ifdef MOD_TUTTYCOLOR + gppi(sesskey, "BoldAsColourTest", 1, conf, CONF_bold_colour); + gppi(sesskey, "UnderlinedAsColour", 0, conf, CONF_under_colour); + gppi(sesskey, "SelectedAsColour", 0, conf, CONF_sel_colour); + for (i = 0; i < 34; i++) { + static const char *const defaults[34] = { + "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", + "187,187,187", + "0,0,0", "0,0,0", "187,0,0", "0,187,0" + , "187,187,0", "0,0,187", + "187,0,187", "0,187,187", "187,187,187", "0,0,0", "187,187,187" + }; +#else + 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" + }; +#endif + 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); +#ifdef MOD_PRINTCLIP + if( !strcmp( conf_get_str(conf,CONF_printer),PRINT_TO_CLIPBOARD_STRING) ) { conf_set_int(conf,CONF_printclip,1) ; } + else { conf_set_int(conf,CONF_printclip,0) ; } +#endif + 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); + 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); + +/* rutty: */ +#ifdef MOD_RUTTY + gppfile(sesskey, "ScriptFileName", conf, CONF_script_filename); + gppi(sesskey, "ScriptMode", 0, conf, CONF_script_mode); + gppi(sesskey, "ScriptLineDelay", 0, conf, CONF_script_line_delay); + gppi(sesskey, "ScriptCharDelay", 0, conf, CONF_script_char_delay); + gpps(sesskey, "ScriptCondLine", ":", conf, CONF_script_cond_line); + gppi(sesskey, "ScriptCondUse", 0, conf, CONF_script_cond_use); + gppi(sesskey, "ScriptCRLF", SCRIPT_NOLF, conf, CONF_script_crlf); + gppi(sesskey, "ScriptEnable", 0, conf, CONF_script_enable); + gppi(sesskey, "ScriptExcept", 0, conf, CONF_script_except); + gppi(sesskey, "ScriptTimeout", 30, conf, CONF_script_timeout); + gpps(sesskey, "ScriptWait", "", conf, CONF_script_waitfor); + gpps(sesskey, "ScriptHalt", "", conf, CONF_script_halton); +#endif /* rutty */ +#ifdef MOD_RECONNECT + gppi(sesskey, "WakeupReconnect", 0, conf, CONF_wakeup_reconnect ); + gppi(sesskey, "FailureReconnect", 0, conf, CONF_failure_reconnect ); +#endif +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + gppi(sesskey, "BgOpacity", 50, conf, CONF_bg_opacity ); + gppi(sesskey, "BgSlideshow", 0, conf, CONF_bg_slideshow ); + gppi(sesskey, "BgType", 0, conf, CONF_bg_type ); + gppfile(sesskey, "BgImageFile", conf, CONF_bg_image_filename ); + gppi(sesskey, "BgImageStyle", 0, conf, CONF_bg_image_style ); + gppi(sesskey, "BgImageAbsoluteX", 0, conf, CONF_bg_image_abs_x ); + gppi(sesskey, "BgImageAbsoluteY", 0, conf, CONF_bg_image_abs_y ); + gppi(sesskey, "BgImagePlacement", 0, conf, CONF_bg_image_abs_fixed ); +#endif +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + * Hyperlink stuff: Load hyperlink settings + */ + gppi(sesskey, "HyperlinkUnderline", 0, conf, CONF_url_underline); + gppi(sesskey, "HyperlinkUseCtrlClick", 1, conf, CONF_url_ctrl_click); + gppi(sesskey, "HyperlinkBrowserUseDefault", 1, conf, CONF_url_defbrowser); + gppfile(sesskey, "HyperlinkBrowser", conf, CONF_url_browser); + gppi(sesskey, "HyperlinkRegularExpressionUseDefault", 1, conf, CONF_url_defregex); + +#ifndef MOD_NOHYPERLINK + gpps(sesskey, "HyperlinkRegularExpression", urlhack_default_regex, conf, CONF_url_regex); +#endif +#endif +#ifdef MOD_ZMODEM + gppfile(sesskey, "rzCommand", conf, CONF_rzcommand ); + gpps(sesskey, "rzOptions", "-e -v", conf, CONF_rzoptions ); + gppfile(sesskey, "szCommand", conf, CONF_szcommand ); + gpps(sesskey, "szOptions", "-e -v", conf, CONF_szoptions ); + gpps(sesskey, "zDownloadDir", "C:\\", conf, CONF_zdownloaddir ); +#endif +#ifdef MOD_PERSO + gpps(sesskey, "HostAlt", "", conf, CONF_host_alt ); + gppi(sesskey, "TransparencyValue", 0, conf, CONF_transparencynumber ) ; + if( conf_get_int( conf, CONF_transparencynumber) < -1 ) conf_set_int( conf,CONF_transparencynumber,-1) ; + if( conf_get_int( conf, CONF_transparencynumber) > 255 ) conf_set_int( conf,CONF_transparencynumber,255) ; + gppi(sesskey, "SendToTray", 0, conf, CONF_sendtotray ); + gppi(sesskey, "Maximize", 0, conf, CONF_maximize ); + gppi(sesskey, "Fullscreen", 0, conf, CONF_fullscreen ); + gppb(sesskey, "SaveOnExit", false, conf, CONF_saveonexit ); + gppi(sesskey, "Icone", 1, conf, CONF_icone ); + gppfile(sesskey, "IconeFile", conf, CONF_iconefile ); + gppi(sesskey, "WinSCPProtocol", 1, conf, CONF_winscpprot ); + gpps(sesskey, "SFTPConnect", "", conf, CONF_sftpconnect ); + gpps(sesskey, "PSCPOptions", "-r", conf, CONF_pscpoptions ); + gpps(sesskey, "PSCPShell", "", conf, CONF_pscpshell ); + gpps(sesskey, "PSCPRemoteDir", "", conf, CONF_pscpremotedir ); + gpps(sesskey, "WinSCPOptions", "", conf, CONF_winscpoptions ); + gpps(sesskey, "WinSCPRawSettings", "", conf, CONF_winscprawsettings ); + gppfile(sesskey, "Scriptfile", conf, CONF_scriptfile ); + Filename * fn = filename_from_str( "" ) ; + conf_set_filename(conf,CONF_scriptfile,fn); + filename_free(fn); + gpps(sesskey, "ScriptfileContent", "", conf, CONF_scriptfilecontent ); + gpps(sesskey, "AntiIdle", "", conf, CONF_antiidle ); + gpps(sesskey, "LogTimestamp", "", conf, CONF_logtimestamp ); + gpps(sesskey, "Autocommand", "", conf, CONF_autocommand ); + gpps(sesskey, "AutocommandOut", "", conf, CONF_autocommandout ); + gpps(sesskey, "Folder", "", conf, CONF_folder ); + if( strlen(conf_get_str(conf, CONF_folder)) == 0 ) { conf_set_str( conf, CONF_folder, "Default" ) ; } + gppi(sesskey, "LogTimeRotation", 0, conf, CONF_logtimerotation ); + gppi(sesskey, "TermXPos", -1, conf, CONF_xpos ); + gppi(sesskey, "TermYPos", -1, conf, CONF_ypos ); + gppi(sesskey, "WindowState", 0, conf, CONF_windowstate ); + gppb(sesskey, "SaveWindowPos", false, conf, CONF_save_windowpos ); /* BKG */ + gppb(sesskey, "ForegroundOnBell", false, conf, CONF_foreground_on_bell ); +#ifndef MOD_NOPASSWORD + gpps(sesskey, "Password", "", conf, CONF_password ); + if( strlen(conf_get_str(conf, CONF_password))>0 ) { + char pst[4096] ; + if( strlen(conf_get_str(conf, CONF_password))<=4095 ) { strcpy( pst, conf_get_str(conf, CONF_password) ) ; } + else { memcpy( pst, conf_get_str( conf, CONF_password ), 4095 ) ; pst[4095]='\0'; } + decryptpassword( GetCryptSaltFlag(), pst, conf_get_str(conf, CONF_host), conf_get_str(conf, CONF_termtype) ) ; + if( strlen( pst ) > 0 ) { DebugGetPassword( conf, pst ) ; } + MASKPASS(GetCryptSaltFlag(), pst); + conf_set_str( conf, CONF_password, pst ) ; + memset(pst,0,strlen(pst)); + } +#else + conf_set_str( conf, CONF_password, "" ) ; +#endif + gppi(sesskey, "CtrlTabSwitch", 0, conf, CONF_ctrl_tab_switch); + gpps(sesskey, "Comment", "", conf, CONF_comment ); + gppi(sesskey, "SCPAutoPwd", 0, conf, CONF_scp_auto_pwd); + gppb(sesskey, "NoFocusReporting", true, conf, CONF_no_focus_rep); + gppi(sesskey, "LinesAtAScroll", -1, conf, CONF_scrolllines); + gppb(sesskey, "SSHTunnelInTitle", false, conf, CONF_ssh_tunnel_print_in_title); +#endif +#ifdef MOD_PORTKNOCKING + gpps(sesskey, "PortKnocking", "", conf, CONF_portknockingoptions ); +#endif +#ifdef MOD_DISABLEALTGR + gppi(sesskey, "DisableAltGr", 0, conf, CONF_disablealtgr); +#endif +#ifdef MOD_PROXY + gpps(sesskey, "ProxySelection", "- Session defined proxy -", conf, CONF_proxyselection); +#endif +} + +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 */ +} + +void get_sesslist(struct sesslist *list, bool allocate) +{ + int i; + char *p; + settings_e *handle; + + if (allocate) { + strbuf *sb = strbuf_new(); + + 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; + } +} +#ifdef MOD_PERSO +/* Print current password if kitty.password exists */ +int DebugGetPassword( Conf *conf, const char *pwd ) { + FILE *fp ; + if( pwd==NULL ) return 0; + if( strlen(pwd) == 0 ) return 0; + if( ( fp = fopen( "kitty.password", "r") ) != NULL ) { + char * pst = (char*) malloc( strlen(pwd)+1 ) ; + fclose( fp ) ; + if( ( fp = fopen( "kitty.password", "w" ) ) != NULL ) { + fprintf( fp, "encpass=%s\n", conf_get_str( conf, CONF_password ) ) ; + fprintf( fp, "host=%s\n", conf_get_str( conf, CONF_host ) ) ; + fprintf( fp, "term=%s\n", conf_get_str( conf, CONF_termtype ) ) ; + fprintf( fp, "pass=%s\n", pwd ) ; + strcpy( pst, pwd ) ; + MASKPASS( GetCryptSaltFlag(), pst ) ; + fprintf( fp, "maskpass=%s\n", pst ) ; + MASKPASS( GetCryptSaltFlag(), pst ) ; + fprintf( fp, "clearpass=%s\n", pst ) ; + memset( pst, 0, strlen(pwd) ) ; + fclose( fp ) ; + } + free(pst); + return 1 ; + } + return 0 ; +} +int DebugAddPassword( const char*fct, const char*pwd ) { + FILE *fp ; + if( ( fp = fopen( "kitty.password", "r") ) != NULL ) { + fclose( fp ) ; + if( ( fp = fopen( "kitty.password", "a" ) ) != NULL ) { + fprintf( fp, "%s=%s\n", fct, pwd ) ; + fclose(fp) ; + } + return 1 ; + } + return 0 ; +} +/* Fonction de creation d'une session */ +void create_settings( const char * name ) { + char *nname = NULL ; + Conf *cfg = conf_new() ; + do_defaults(name,cfg); + nname = (char*)malloc( strlen(name)+6 ) ; + if( !strcmp(FileExtension,"") ) { + strcpy(nname,name); + } else { + sprintf(nname,"%s%s",name,FileExtension); + } + save_settings(nname, cfg); + free(nname) ; + conf_free(cfg); +} +#include "../kitty_settings.c" +#endif diff --git a/0.77_My_PuTTY/sftp.c b/0.77_My_PuTTY/sftp.c new file mode 100644 index 0000000..a078056 --- /dev/null +++ b/0.77_My_PuTTY/sftp.c @@ -0,0 +1,1205 @@ +/* + * 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. + */ + +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 *ret; + 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; + } + + ret = snew(struct fxp_names); + ret->nnames = i; + ret->names = snewn(ret->nnames, struct fxp_name); + for (i = 0; i < (unsigned long)ret->nnames; i++) { + ret->names[i].filename = mkstr(get_string(pktin)); + ret->names[i].longname = mkstr(get_string(pktin)); + get_fxp_attrs(pktin, &ret->names[i].attrs); + } + + if (get_err(pktin)) { + fxp_internal_error("malformed FXP_NAME packet"); + for (i = 0; i < (unsigned long)ret->nnames; i++) { + sfree(ret->names[i].filename); + sfree(ret->names[i].longname); + } + sfree(ret->names); + sfree(ret); + sfree(pktin); + return NULL; + } + sftp_pkt_free(pktin); + return ret; + } else { + fxp_got_status(pktin); + sftp_pkt_free(pktin); + return NULL; + } +} + +/* + * Write to a file. Returns 0 on error, 1 on OK. + */ +struct sftp_request *fxp_write_send(struct fxp_handle *handle, + 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 *name) +{ + struct fxp_name *ret; + ret = snew(struct fxp_name); + ret->filename = dupstr(name->filename); + ret->longname = dupstr(name->longname); + ret->attrs = name->attrs; /* structure copy */ + return ret; +} + +/* + * Free up an fxp_name structure. + */ +void fxp_free_name(struct fxp_name *name) +{ + sfree(name->filename); + sfree(name->longname); + sfree(name); +} + +/* + * Store user data in an sftp_request structure. + */ +void *fxp_get_userdata(struct sftp_request *req) +{ + return req->userdata; +} + +void fxp_set_userdata(struct sftp_request *req, void *data) +{ + req->userdata = data; +} + +/* + * A wrapper to go round fxp_read_* and fxp_write_*, which manages + * the queueing of multiple read/write requests. + */ + +struct req { + char *buffer; + int len, retlen, complete; + uint64_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/0.77_My_PuTTY/sftp.h b/0.77_My_PuTTY/sftp.h new file mode 100644 index 0000000..0b4ad92 --- /dev/null +++ b/0.77_My_PuTTY/sftp.h @@ -0,0 +1,544 @@ +/* + * 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 appropiate + * 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/0.77_My_PuTTY/sftpcommon.c b/0.77_My_PuTTY/sftpcommon.c new file mode 100644 index 0000000..43b6575 --- /dev/null +++ b/0.77_My_PuTTY/sftpcommon.c @@ -0,0 +1,139 @@ +/* + * 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/0.77_My_PuTTY/sftpserver.c b/0.77_My_PuTTY/sftpserver.c new file mode 100644 index 0000000..9a48e87 --- /dev/null +++ b/0.77_My_PuTTY/sftpserver.c @@ -0,0 +1,279 @@ +/* + * 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/0.77_My_PuTTY/smallprimes.c b/0.77_My_PuTTY/smallprimes.c new file mode 100644 index 0000000..b90557a --- /dev/null +++ b/0.77_My_PuTTY/smallprimes.c @@ -0,0 +1,44 @@ +/* + * 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/0.77_My_PuTTY/ssh.c b/0.77_My_PuTTY/ssh.c new file mode 100644 index 0000000..2a5d3ab --- /dev/null +++ b/0.77_My_PuTTY/ssh.c @@ -0,0 +1,1251 @@ +/* + * 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 "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.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; + + 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; + + 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->user_input = &ssh->user_input; + ppl->seat = ssh->seat; + 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->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->fullhostname, + conf_get_filename(ssh->conf, CONF_keyfile), + 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 + ); + 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->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->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; + } +} + +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; + } + + 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; + + /* 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; + + /* 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; + + 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; + + 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; + + /* 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, const char *error_msg, int error_code, + bool calling_back) +{ + Ssh *ssh = container_of(plug, Ssh, plug); + if (error_msg) { + 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); + } +} + +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, +}; + +/* + * 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 **realhost, + bool nodelay, bool keepalive) +{ + SockAddr *addr; + const char *err; + char *loghost; + int addressfamily, sshprot; + + ssh_hostport_setup(host, port, ssh->conf, + &ssh->savedhost, &ssh->savedport, &loghost); + + 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); + if ((err = sk_socket_error(ssh->s)) != NULL) { + ssh->s = NULL; + seat_notify_remote_exit(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; + *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; + + 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, 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); + + 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); + + 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); + + 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 size_t ssh_send(Backend *be, const char *buf, size_t len) +{ + Ssh *ssh = container_of(be, Ssh, backend); + + if (ssh == NULL || ssh->s == NULL) + return 0; + + bufchain_add(&ssh->user_input, buf, len); + if (ssh->base_layer) + ssh_ppl_got_user_input(ssh->base_layer); + + return backend_sendbuffer(&ssh->backend); +} + +/* + * 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; +} + +/* + * 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->base_layer && ssh_ppl_want_user_input(ssh->base_layer); +} + +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 scp.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 = "SSH", + .protocol = PROT_SSH, + .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 = "Bare ssh-connection", + .protocol = PROT_SSHCONN, +}; diff --git a/0.77_My_PuTTY/ssh.h b/0.77_My_PuTTY/ssh.h new file mode 100644 index 0000000..624c266 --- /dev/null +++ b/0.77_My_PuTTY/ssh.h @@ -0,0 +1,1709 @@ +#include +#include + +#include "puttymem.h" +#include "tree234.h" +#include "network.h" +#include "misc.h" + +#ifdef MOD_PERSO +void SetUsernameInConfig( const char * username ) ; +char * ManagePassPhrase( const char * st ) ; +void SetPasswordInConfig( const char * password ) ; +void GetPasswordInConfig( char * p ) ; +void CleanPassword( char * p ) ; +int IsPasswordInConf(void) ; +#endif + +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 to the connection layer whether the main session + * channel currently wants user input. */ + void (*set_wants_user_input)(ConnectionLayer *cl, bool wanted); +}; + +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); } + +/* 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_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_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); + +/* 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 dss_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 struct key_components { + size_t ncomponents, componentsize; + struct { + char *name; + bool is_mp_int; + union { + char *text; + mp_int *mp; + }; + } *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_mp(key_components *kc, + const char *name, mp_int *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 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); + +/* + * SSH2 ECDH key exchange functions + */ +const char *ssh_ecdhkex_curve_textname(const ssh_kex *kex); +ecdh_key *ssh_ecdhkex_newkey(const ssh_kex *kex); +void ssh_ecdhkex_freekey(ecdh_key *key); +void ssh_ecdhkex_getpublic(ecdh_key *key, BinarySink *bs); +mp_int *ssh_ecdhkex_getkey(ecdh_key *key, ptrlen remoteKey); + +/* + * Helper function for k generation in DSA, reused in ECDSA + */ +mp_int *dss_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); + 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 const struct ssh_cipheralg *ssh_cipher_alg(ssh_cipher *c) +{ return c->vt; } + +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 *); + 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 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 sshmac.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); + +/* 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 } main_type; + const ssh_hashalg *hash; + const void *extra; /* private to the kex methods */ +}; + +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 *); + char *(*cache_str) (ssh_key *key); + key_components *(*components) (ssh_key *key); + + /* 'Class methods' that don't deal with an ssh_key at all */ + int (*pubkey_bits) (const ssh_keyalg *self, ptrlen blob); + + /* 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 */ + const unsigned supported_flags; /* signature-type flags we understand */ +}; + +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 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 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; } + +/* + * 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_hw; +extern const ssh_cipheralg ssh_aes256_sdctr_sw; +extern const ssh_cipheralg ssh_aes256_cbc; +extern const ssh_cipheralg ssh_aes256_cbc_hw; +extern const ssh_cipheralg ssh_aes256_cbc_sw; +extern const ssh_cipheralg ssh_aes192_sdctr; +extern const ssh_cipheralg ssh_aes192_sdctr_hw; +extern const ssh_cipheralg ssh_aes192_sdctr_sw; +extern const ssh_cipheralg ssh_aes192_cbc; +extern const ssh_cipheralg ssh_aes192_cbc_hw; +extern const ssh_cipheralg ssh_aes192_cbc_sw; +extern const ssh_cipheralg ssh_aes128_sdctr; +extern const ssh_cipheralg ssh_aes128_sdctr_hw; +extern const ssh_cipheralg ssh_aes128_sdctr_sw; +extern const ssh_cipheralg ssh_aes128_cbc; +extern const ssh_cipheralg ssh_aes128_cbc_hw; +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 ssh_hashalg ssh_md5; +extern const ssh_hashalg ssh_sha1; +extern const ssh_hashalg ssh_sha1_hw; +extern const ssh_hashalg ssh_sha1_sw; +extern const ssh_hashalg ssh_sha256; +extern const ssh_hashalg ssh_sha256_hw; +extern const ssh_hashalg ssh_sha256_sw; +extern const ssh_hashalg ssh_sha384; +extern const ssh_hashalg ssh_sha384_hw; +extern const ssh_hashalg ssh_sha384_sw; +extern const ssh_hashalg ssh_sha512; +extern const ssh_hashalg ssh_sha512_hw; +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_gex; +extern const ssh_kexes ssh_gssk5_sha1_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_keyalg ssh_dss; +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 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 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); + +/* + * 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_hw_available(void); +bool platform_sha256_hw_available(void); +bool platform_sha1_hw_available(void); +bool platform_sha512_hw_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 scp.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 sshprng.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 sshprng.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; +}; +void *x11_make_greeting(int endian, int protomajor, int protominor, + int auth_proto, const void *auth_data, int auth_len, + const char *peer_ip, int peer_port, + int *outlen); +int x11_authcmp(void *av, void *bv); /* for putting X11FakeAuth in a tree234 */ +/* + * x11_setup_display() parses the display variable and fills in an + * X11Display structure. Some remote auth details are invented; + * the supplied authtype parameter configures the preferred + * authorisation protocol to use at the remote end. The local auth + * details are looked up by calling platform_get_x11_auth. + * + * 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 */ +/* Callbacks in x11.c usable _by_ platform X11 functions */ +/* + * This function does the job of platform_get_x11_auth, provided + * it is told where to find a normally formatted .Xauthority file: + * it opens that file, parses it to find an auth record which + * matches the display details in "display", and fills in the + * localauth fields. + * + * It is expected that most implementations of + * platform_get_x11_auth() will work by finding their system's + * .Xauthority file, adjusting the display details if necessary + * for local oddities like Unix-domain socket transport, and + * calling this function to do the rest of the work. + */ +void x11_get_auth_from_authfile(struct X11Display *display, + const char *authfilename); +void x11_format_auth_for_authfile( + BinarySink *bs, SockAddr *addr, int display_no, + ptrlen authproto, ptrlen authdata); +int x11_identify_auth_proto(ptrlen protoname); +void *x11_dehexify(ptrlen hex, int *outlen); + +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 *, int nbits); +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_decode_atom(const char *atom, unsigned char *out); +extern int base64_lines(int datalen); +extern void base64_encode_atom(const unsigned char *data, int n, char *out); +extern void base64_encode(FILE *fp, const unsigned char *data, int datalen, + int cpl); + +/* 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); + +/* 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 { + SSH_FPTYPE_MD5, + SSH_FPTYPE_SHA256, +} FingerprintType; + +#define SSH_FPTYPE_DEFAULT SSH_FPTYPE_SHA256 +#define SSH_N_FPTYPES (SSH_FPTYPE_SHA256 + 1) + +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_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(const char *passphrase, + const unsigned char *salt, int saltbytes, + 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 "sshttymodes.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) \ + /* 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); +bool get_commasep_word(ptrlen *list, ptrlen *word); + +int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key); + +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); diff --git a/0.77_My_PuTTY/ssh1bpp.c b/0.77_My_PuTTY/ssh1bpp.c new file mode 100644 index 0000000..6f7851e --- /dev/null +++ b/0.77_My_PuTTY/ssh1bpp.c @@ -0,0 +1,387 @@ +/* + * Binary packet protocol for SSH-1. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.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; + } + } +} + +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/0.77_My_PuTTY/ssh1censor.c b/0.77_My_PuTTY/ssh1censor.c new file mode 100644 index 0000000..6fc6d2d --- /dev/null +++ b/0.77_My_PuTTY/ssh1censor.c @@ -0,0 +1,76 @@ +/* + * 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/0.77_My_PuTTY/ssh1connection-client.c b/0.77_My_PuTTY/ssh1connection-client.c new file mode 100644 index 0000000..00b4ebb --- /dev/null +++ b/0.77_My_PuTTY/ssh1connection-client.c @@ -0,0 +1,547 @@ +/* + * Client-specific parts of the SSH-1 connection layer. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" +#include "sshcr.h" +#include "ssh1connection.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) +{ + return !seat_set_trust_status(s->ppl.seat, false); +} diff --git a/0.77_My_PuTTY/ssh1connection-server.c b/0.77_My_PuTTY/ssh1connection-server.c new file mode 100644 index 0000000..17c81c5 --- /dev/null +++ b/0.77_My_PuTTY/ssh1connection-server.c @@ -0,0 +1,365 @@ +/* + * Server-specific parts of the SSH-1 connection layer. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" +#include "sshcr.h" +#include "ssh1connection.h" +#include "sshserver.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/0.77_My_PuTTY/ssh1connection.c b/0.77_My_PuTTY/ssh1connection.c new file mode 100644 index 0000000..182cde5 --- /dev/null +++ b/0.77_My_PuTTY/ssh1connection.c @@ -0,0 +1,822 @@ +/* + * Packet protocol layer for the SSH-1 'connection protocol', i.e. + * everything after authentication finishes. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" +#include "sshcr.h" +#include "ssh1connection.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 bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl); +static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl); +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, + .want_user_input = ssh1_connection_want_user_input, + .got_user_input = ssh1_connection_got_user_input, + .reconfigure = ssh1_connection_reconfigure, + .queued_data_size = ssh_ppl_default_queued_data_size, + .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 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, + /* 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, 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); + + /* 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 = new_prompts(); + 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( + s->ppl.seat, s->antispoof_prompt, NULL); +#ifndef MOD_PERSO + add_prompt( + s->antispoof_prompt, + dupstr("Access granted. Press Return to begin session. "), false); + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt, NULL); +#endif + while (1) { + while (s->antispoof_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt, s->ppl.user_input); + + if (s->antispoof_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + 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; +} + +static bool ssh1_connection_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_connection_state *s = + container_of(ppl, struct ssh1_connection_state, ppl); + + return s->want_user_input; +} + +static void ssh1_connection_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_connection_state *s = + container_of(ppl, struct ssh1_connection_state, ppl); + + while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) { + /* + * Add user input to the main channel's buffer. + */ + ptrlen data = bufchain_prefix(s->ppl.user_input); + if (data.len > 512) + data.len = 512; + sshfwd_write(&s->mainchan_sc, data.ptr, data.len); + bufchain_consume(s->ppl.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/0.77_My_PuTTY/ssh1connection.h b/0.77_My_PuTTY/ssh1connection.h new file mode 100644 index 0000000..569ee78 --- /dev/null +++ b/0.77_My_PuTTY/ssh1connection.h @@ -0,0 +1,123 @@ +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; + + 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; + int 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/0.77_My_PuTTY/ssh1login-server.c b/0.77_My_PuTTY/ssh1login-server.c new file mode 100644 index 0000000..f1d8bec --- /dev/null +++ b/0.77_My_PuTTY/ssh1login-server.c @@ -0,0 +1,447 @@ +/* + * Packet protocol layer for the SSH-1 login phase, from the server side. + */ + +#include + +#include "putty.h" +#include "mpint.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" +#include "sshserver.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; + bool auth_successful; + + 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 bool ssh1_login_server_want_user_input( + PacketProtocolLayer *ppl) { return false; } +static void ssh1_login_server_got_user_input(PacketProtocolLayer *ppl) {} +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, + .want_user_input = ssh1_login_server_want_user_input, + .got_user_input = ssh1_login_server_got_user_input, + .reconfigure = ssh1_login_server_reconfigure, + .queued_data_size = ssh_ppl_default_queued_data_size, + .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)); + + s->auth_successful = auth_none(s->authpolicy, s->username); + 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/0.77_My_PuTTY/ssh1login.c b/0.77_My_PuTTY/ssh1login.c new file mode 100644 index 0000000..23ef07a --- /dev/null +++ b/0.77_My_PuTTY/ssh1login.c @@ -0,0 +1,1257 @@ +/* + * 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 "sshbpp.h" +#include "sshppl.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; + int userpass_ret; + 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; + int dlgret; + Filename *keyfile; + RSAKey servkey, hostkey; + bool want_user_input; + + 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 *, int); +static void ssh1_login_special_cmd(PacketProtocolLayer *ppl, + SessionSpecialCode code, int arg); +static bool ssh1_login_want_user_input(PacketProtocolLayer *ppl); +static void ssh1_login_got_user_input(PacketProtocolLayer *ppl); +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, + .want_user_input = ssh1_login_want_user_input, + .got_user_input = ssh1_login_got_user_input, + .reconfigure = ssh1_login_reconfigure, + .queued_data_size = ssh_ppl_default_queued_data_size, + .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. + */ + { + /* + * First format the key into a string. + */ + char *keystr = rsastr_fmt(&s->hostkey); + char **fingerprints = rsa_ssh1_fake_all_fingerprints(&s->hostkey); + + /* First check against manually configured host keys. */ + s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprints, NULL); + if (s->dlgret == 0) { /* did not match */ + ssh2_free_all_fingerprints(fingerprints); + sfree(keystr); + ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually " + "configured list"); + return; + } else if (s->dlgret < 0) { /* none configured; use standard handling */ + char *keydisp = ssh1_pubkey_str(&s->hostkey); + s->dlgret = seat_verify_ssh_host_key( + s->ppl.seat, s->savedhost, s->savedport, "rsa", keystr, + keydisp, fingerprints, ssh1_login_dialog_callback, s); + sfree(keydisp); + ssh2_free_all_fingerprints(fingerprints); + sfree(keystr); +#ifdef FUZZING + s->dlgret = 1; +#endif + crMaybeWaitUntilV(s->dlgret >= 0); + + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, + "User aborted at host key verification"); + return; + } + } else { + ssh2_free_all_fingerprints(fingerprints); + sfree(keystr); + } + } + + 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->dlgret = seat_confirm_weak_crypto_primitive( + s->ppl.seat, "cipher", cipher_string, + ssh1_login_dialog_callback, s); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at 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 = new_prompts(); + 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->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * Failed to get a username. Terminate. + */ + ssh_user_close(s->ppl.ssh, "No username provided"); + return; + } + s->username = prompt_get_result(s->cur_prompt->prompts[0]); +#ifdef MOD_PERSO + SetUsernameInConfig( s->username ) ; +#endif + 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_new(); + put_datapl(s->agent_keys[i].comment, 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 = new_prompts(); + 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->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* Failed to get a passphrase. Terminate. */ + ssh_user_close(s->ppl.ssh, + "User aborted at 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 = new_prompts(); + + 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->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * Failed to get a password (for example + * because one was supplied on the command line + * which has already failed to work). Terminate. + */ + ssh_user_close(s->ppl.ssh, "User aborted at 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, int ret) +{ + struct ssh1_login_state *s = (struct ssh1_login_state *)loginv; + s->dlgret = ret; + 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 bool ssh1_login_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + return s->want_user_input; +} + +static void ssh1_login_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh1_login_state *s = + container_of(ppl, struct ssh1_login_state, ppl); + if (s->want_user_input) + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +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/0.77_My_PuTTY/ssh2bpp-bare.c b/0.77_My_PuTTY/ssh2bpp-bare.c new file mode 100644 index 0000000..146ad07 --- /dev/null +++ b/0.77_My_PuTTY/ssh2bpp-bare.c @@ -0,0 +1,204 @@ +/* + * 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 "sshbpp.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 sshcommon.c */ + + /* packet size limit, per protocol spec in sshshare.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); + } +} diff --git a/0.77_My_PuTTY/ssh2bpp.c b/0.77_My_PuTTY/ssh2bpp.c new file mode 100644 index 0000000..0f0986b --- /dev/null +++ b/0.77_My_PuTTY/ssh2bpp.c @@ -0,0 +1,982 @@ +/* + * Binary packet protocol for SSH-2. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.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 sshcommon.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) +{ + /* + * We must free the MAC before the cipher, because sometimes the + * MAC is not actually separately allocated but just a different + * facet of the same object as the cipher, in which case + * ssh2_mac_free does nothing and ssh_cipher_free does the actual + * freeing. So if we freed the cipher first and then tried to + * dereference the MAC's vtable pointer to find out how to free + * that too, we'd be accessing freed memory. + */ + 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) +{ + 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); + 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 (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) +{ + 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); + 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; + + /* 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++; + + 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 */ + + 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); + } + } +} diff --git a/0.77_My_PuTTY/ssh2censor.c b/0.77_My_PuTTY/ssh2censor.c new file mode 100644 index 0000000..31ad814 --- /dev/null +++ b/0.77_My_PuTTY/ssh2censor.c @@ -0,0 +1,107 @@ +/* + * 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/0.77_My_PuTTY/ssh2connection-client.c b/0.77_My_PuTTY/ssh2connection-client.c new file mode 100644 index 0000000..3fe38ab --- /dev/null +++ b/0.77_My_PuTTY/ssh2connection-client.c @@ -0,0 +1,505 @@ +/* + * Client-specific parts of the SSH-2 connection layer. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" +#include "sshcr.h" +#include "ssh2connection.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) +{ + bool success = seat_set_trust_status(s->ppl.seat, false); + return (!success && !ssh_is_bare(s->ppl.ssh)); +} diff --git a/0.77_My_PuTTY/ssh2connection-server.c b/0.77_My_PuTTY/ssh2connection-server.c new file mode 100644 index 0000000..ceccead --- /dev/null +++ b/0.77_My_PuTTY/ssh2connection-server.c @@ -0,0 +1,306 @@ +/* + * Server-specific parts of the SSH-2 connection layer. + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" +#include "sshcr.h" +#include "ssh2connection.h" +#include "sshserver.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/0.77_My_PuTTY/ssh2connection.c b/0.77_My_PuTTY/ssh2connection.c new file mode 100644 index 0000000..1bc6321 --- /dev/null +++ b/0.77_My_PuTTY/ssh2connection.c @@ -0,0 +1,1751 @@ +/* + * Packet protocol layer for the SSH-2 connection protocol (RFC 4254). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" +#include "sshcr.h" +#include "ssh2connection.h" + +#ifdef MOD_RECONNECT +int GetAutoreconnectFlag( void ) ; +#endif + +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 bool ssh2_connection_want_user_input(PacketProtocolLayer *ppl); +static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl); +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, + .want_user_input = ssh2_connection_want_user_input, + .got_user_input = ssh2_connection_got_user_input, + .reconfigure = ssh2_connection_reconfigure, + .queued_data_size = ssh_ppl_default_queued_data_size, + .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 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, +}; + +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; + 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, 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); + + /* 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); + } + + 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 sshshare.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 = new_prompts(); + s->antispoof_prompt->to_server = true; + s->antispoof_prompt->from_server = false; + s->antispoof_prompt->name = dupstr("Authentication successful"); +#ifndef MOD_PERSO + add_prompt( + s->antispoof_prompt, + dupstr("Access granted. Press Return to begin session. "), false); + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt, NULL); +#endif + while (1) { + while (s->antispoof_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->antispoof_ret = seat_get_userpass_input( + s->ppl.seat, s->antispoof_prompt, s->ppl.user_input); + + if (s->antispoof_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + 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); + + 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; +} + +static bool ssh2_connection_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_connection_state *s = + container_of(ppl, struct ssh2_connection_state, ppl); + return s->want_user_input; +} + +static void ssh2_connection_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_connection_state *s = + container_of(ppl, struct ssh2_connection_state, ppl); + + while (s->mainchan && bufchain_size(s->ppl.user_input) > 0) { + /* + * Add user input to the main channel's buffer. + */ + ptrlen data = bufchain_prefix(s->ppl.user_input); + sshfwd_write(s->mainchan_sc, data.ptr, data.len); + bufchain_consume(s->ppl.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/0.77_My_PuTTY/ssh2connection.h b/0.77_My_PuTTY/ssh2connection.h new file mode 100644 index 0000000..70e3bd9 --- /dev/null +++ b/0.77_My_PuTTY/ssh2connection.h @@ -0,0 +1,235 @@ +#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; + + 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; + int 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/0.77_My_PuTTY/ssh2kex-client.c b/0.77_My_PuTTY/ssh2kex-client.c new file mode 100644 index 0000000..55ee273 --- /dev/null +++ b/0.77_My_PuTTY/ssh2kex-client.c @@ -0,0 +1,932 @@ +/* + * Client side of key exchange for the SSH-2 transport protocol (RFC 4253). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" +#include "storage.h" +#include "ssh2transport.h" +#include "mpint.h" + +/* + * Another copy of the symbol defined in mpunsafe.c. See the comment + * there. + */ +#ifndef MOD_INTEGRATED_AGENT +const int deliberate_symbol_clash = 12345; +#endif + +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, s->nbits * 2); + 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; + } + } + s->K = dh_find_K(s->dh_ctx, s->f); + + /* 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) { + + ppl_logevent("Doing ECDH key exchange with curve %s and hash %s", + ssh_ecdhkex_curve_textname(s->kex_alg), + ssh_hash_alg(s->exhash)->text_name); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; + + s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg); + if (!s->ecdh_key) { + ssh_sw_abort(s->ppl.ssh, "Unable to generate key for ECDH"); + *aborted = true; + return; + } + + pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_KEX_ECDH_INIT); + { + strbuf *pubpoint = strbuf_new(); + ssh_ecdhkex_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(); + ssh_ecdhkex_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); + s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); + if (!get_err(pktin) && !s->K) { + 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; + } + + ssh_ecdhkex_freekey(s->ecdh_key); + s->ecdh_key = NULL; +#ifndef NO_GSSAPI + } else if (s->kex_alg->main_type == KEXTYPE_GSS) { + 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; + + 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", s->pbits); + 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\"", s->kex_alg->groupname); + } + + ppl_logevent("Doing GSSAPI (with Kerberos V5) Diffie-Hellman key " + "exchange with hash %s", ssh_hash_alg(s->exhash)->text_name); + /* Now generate e for Diffie-Hellman. */ + seat_set_busy_status(s->ppl.seat, BUSY_CPU); + s->e = dh_create_e(s->dh_ctx, s->nbits * 2); + + 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); + 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; + 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); + + { + 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; + } + } + s->K = dh_find_K(s->dh_ctx, s->f); + + /* 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 (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. + */ + + 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); + s->K = mp_power_2(nbits - 1); + mp_add_into(s->K, s->K, tmp); + mp_free(tmp); + + /* + * Encode this as an mpint. + */ + buf = strbuf_new_nm(); + put_mp_ssh2(buf, s->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 (s->kex_alg->main_type == KEXTYPE_GSS) { + 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. + * + * http://tools.ietf.org/html/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 (s->kex_alg->main_type != KEXTYPE_GSS) { + 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 (s->kex_alg->main_type == KEXTYPE_GSS) { + + /* + * We've just done a GSS key exchange. If it gave us a + * host key, store it. + */ + if (s->hkey) { + char *fingerprint = ssh2_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_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); + } + } + + /* + * Authenticate remote host: verify host key. (We've already + * checked the signature of the exchange hash.) + */ + 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]); + /* First check against manually configured host keys. */ + s->dlgret = verify_ssh_manual_host_key( + s->conf, fingerprints, s->hkey); + if (s->dlgret == 0) { /* did not match */ + ssh2_free_all_fingerprints(fingerprints); + ssh_sw_abort(s->ppl.ssh, "Host key did not appear in manually " + "configured list"); + *aborted = true; + return; + } else if (s->dlgret < 0) { /* none configured; use standard handling */ + ssh2_userkey uk = { .key = s->hkey, .comment = NULL }; + char *keydisp = ssh2_pubkey_openssh_str(&uk); + s->dlgret = seat_verify_ssh_host_key( + s->ppl.seat, s->savedhost, s->savedport, + ssh_key_cache_id(s->hkey), s->keystr, keydisp, + fingerprints, ssh2_transport_dialog_callback, s); + sfree(keydisp); + ssh2_free_all_fingerprints(fingerprints); +#ifdef FUZZING + s->dlgret = 1; +#endif + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, + "User aborted at host key verification"); + *aborted = true; + return; + } + } + + /* + * Save this host key, to check against the one presented in + * subsequent rekeys. + */ + s->hostkey_str = s->keystr; + s->keystr = NULL; + } else if (s->cross_certifying) { + assert(s->hkey); + assert(ssh_key_alg(s->hkey) == s->cross_certifying); + + char *fingerprint = ssh2_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->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. + */ + s->hostkey_str = s->keystr; + s->keystr = NULL; + } 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. + */ + assert(s->keystr); /* filled in by prior key exchange */ + if (strcmp(s->hostkey_str, s->keystr)) { +#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/0.77_My_PuTTY/ssh2kex-server.c b/0.77_My_PuTTY/ssh2kex-server.c new file mode 100644 index 0000000..37d4add --- /dev/null +++ b/0.77_My_PuTTY/ssh2kex-server.c @@ -0,0 +1,330 @@ +/* + * Server side of key exchange for the SSH-2 transport protocol (RFC 4253). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" +#include "sshserver.h" +#include "sshkeygen.h" +#include "storage.h" +#include "ssh2transport.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, s->nbits * 2); + + /* + * 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; + } + } + s->K = dh_find_K(s->dh_ctx, s->f); + + 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) { + ppl_logevent("Doing ECDH key exchange with curve %s and hash %s", + ssh_ecdhkex_curve_textname(s->kex_alg), + ssh_hash_alg(s->exhash)->text_name); + s->ppl.bpp->pls->kctx = SSH2_PKTCTX_ECDHKEX; + + s->ecdh_key = ssh_ecdhkex_newkey(s->kex_alg); + 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); + + s->K = ssh_ecdhkex_getkey(s->ecdh_key, keydata); + if (!get_err(pktin) && !s->K) { + 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(); + ssh_ecdhkex_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); + + ssh_ecdhkex_freekey(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; + } + + { + ptrlen encrypted_secret = get_string(pktin); + put_stringpl(s->exhash, encrypted_secret); + s->K = ssh_rsakex_decrypt( + s->rsa_kex_key, s->kex_alg->hash, encrypted_secret); + } + + if (!s->K) { + ssh_proto_error(s->ppl.ssh, "Unable to decrypt RSA kex secret"); + *aborted = true; + return; + } + + 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/0.77_My_PuTTY/ssh2transhk.c b/0.77_My_PuTTY/ssh2transhk.c new file mode 100644 index 0000000..eba6079 --- /dev/null +++ b/0.77_My_PuTTY/ssh2transhk.c @@ -0,0 +1,126 @@ +/* + * 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/0.77_My_PuTTY/ssh2transport.c b/0.77_My_PuTTY/ssh2transport.c new file mode 100644 index 0000000..2493099 --- /dev/null +++ b/0.77_My_PuTTY/ssh2transport.c @@ -0,0 +1,2181 @@ +/* + * Packet protocol layer for the SSH-2 transport protocol (RFC 4253). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" +#include "sshserver.h" +#include "storage.h" +#include "ssh2transport.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_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 +}; + +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 bool ssh2_transport_want_user_input(PacketProtocolLayer *ppl); +static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl); +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 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, + .want_user_input = ssh2_transport_want_user_input, + .got_user_input = ssh2_transport_got_user_input, + .reconfigure = ssh2_transport_reconfigure, + .queued_data_size = ssh2_transport_queued_data_size, + .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 int ssh2_transport_confirm_weak_crypto_primitive( + struct ssh2_transport_state *s, const char *type, const char *name, + const void *alg); + +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); + +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(); + + 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); + sfree(s->hostkey_str); + strbuf_free(s->hostkeyblob); + if (s->hkey && !s->hostkeys) { + ssh_key_free(s->hkey); + s->hkey = NULL; + } + if (s->f) mp_free(s->f); + if (s->p) mp_free(s->p); + if (s->g) mp_free(s->g); + if (s->K) mp_free(s->K); + 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) + ssh_ecdhkex_freekey(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, + mp_int *K, 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_mp_ssh2(h, K); + 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_mp_ssh2(h, K); + 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. + * This assumes that every time a particular name is passed in, it + * comes from the same string constant. If this isn't true, this + * function may need to be rewritten to use strcmp() instead. + */ +static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm + *list, const char *name) +{ + int i; + + for (i = 0; i < MAXKEXLIST; i++) + if (list[i].name == NULL || list[i].name == name) { + list[i].name = name; + return &list[i]; + } + + unreachable("Should never run out of space in KEXINIT list"); +} + +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; + + 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 kexlists[NKEXLIST][MAXKEXLIST], + 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, + 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 + 1]; /* +1 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_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_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_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_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++) + for (j = 0; j < MAXKEXLIST; j++) + kexlists[i][j].name = NULL; + /* 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; + + if (keyalg == &ssh_rsa) { + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + "rsa-sha2-256"); + alg->u.hk.hostkey = keyalg; + alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_256; + alg->u.hk.warn = false; + + alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], + "rsa-sha2-512"); + alg->u.hk.hostkey = keyalg; + alg->u.hk.hkflags = SSH_AGENT_RSA_SHA2_512; + 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. + */ + 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 (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) && + have_ssh_host_key(hk_host, hk_port, + ssh2_hostkey_algs[j].alg->cache_id)) { + 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; + } + } + } + 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; + 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; + } + } +#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 < MAXKEXLIST; j++) { + if (kexlists[i][j].name == NULL) break; + add_to_commasep(list, kexlists[i][j].name); + } + } + if (i == KEXLIST_KEX && first_time) { + if (our_hostkeys) /* we're the server */ + add_to_commasep(list, "ext-info-s"); + else /* we're the client */ + add_to_commasep(list, "ext-info-c"); + } + put_stringsb(pktout, list); + } + /* List client->server languages. Empty list. */ + put_stringz(pktout, ""); + /* List server->client languages. Empty list. */ + put_stringz(pktout, ""); +} + +static bool ssh2_scan_kexinits( + ptrlen client_kexinit, ptrlen server_kexinit, + struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST], + 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, + Ssh *ssh, bool *ignore_guess_cs_packet, bool *ignore_guess_sc_packet, + int *n_server_hostkeys, int server_hostkeys[MAXKEXLIST], unsigned *hkflags, + bool *can_send_ext_info) +{ + 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)) { + /* 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"); + return false; + } + + 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 < MAXKEXLIST; j++) { + if (kexlists[i][j].name && + ptrlen_eq_string(found, kexlists[i][j].name)) { + selected[i] = &kexlists[i][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. + */ + ssh_sw_abort(ssh, "Selected %s \"%.*s\" does not correspond to " + "any supported algorithm", + kexlist_descr[i], PTRLEN_PRINTF(found)); + return false; + } + + /* + * 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. + */ + ssh_sw_abort(ssh, "Couldn't agree a %s (available: %.*s)", + kexlist_descr[i], PTRLEN_PRINTF(slists[i])); + return false; + } + + 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. + */ + { + ptrlen extinfo_advert = + (server_hostkeys ? PTRLEN_LITERAL("ext-info-c") : + PTRLEN_LITERAL("ext-info-s")); + ptrlen list = (server_hostkeys ? clists[KEXLIST_KEX] : + slists[KEXLIST_KEX]); + for (ptrlen word; get_commasep_word(&list, &word) ;) + if (ptrlen_eq_ptrlen(word, extinfo_advert)) + *can_send_ext_info = 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. + */ + *n_server_hostkeys = 0; + + 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)) { + server_hostkeys[(*n_server_hostkeys)++] = i; + break; + } + } + } + + return true; +} + +void ssh2transport_finalise_exhash(struct ssh2_transport_state *s) +{ + put_mp_ssh2(s->exhash, s->K); + 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->client_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->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. + */ + 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 initial packet 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)); + + /* + * Work through the two KEXINIT packets in parallel to find the + * selected algorithm identifiers. + */ + { + int nhk, hks[MAXKEXLIST], i, j; + + if (!ssh2_scan_kexinits( + ptrlen_from_strbuf(s->client_kexinit), + ptrlen_from_strbuf(s->server_kexinit), + s->kexlists, &s->kex_alg, &s->hostkey_alg, s->cstrans, + s->sctrans, &s->warn_kex, &s->warn_hk, &s->warn_cscipher, + &s->warn_sccipher, s->ppl.ssh, NULL, &s->ignorepkt, &nhk, hks, + &s->hkflags, &s->can_send_ext_info)) + return; /* false means a fatal error function was called */ + + /* + * 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 (i = 0; i < nhk; i++) { + j = hks[i]; + if (ssh2_hostkey_algs[j].alg != s->hostkey_alg && + !have_ssh_host_key(s->savedhost, s->savedport, + ssh2_hostkey_algs[j].alg->cache_id)) { + s->uncert_hostkeys[s->n_uncert_hostkeys++] = j; + } + } + } + + if (s->warn_kex) { + s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s, "key-exchange algorithm", s->kex_alg->name, s->kex_alg); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at kex warning"); + return; + } + } + + if (s->warn_hk) { + int j, k; + char *betteralgs; + + /* + * 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. + */ + betteralgs = NULL; + 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) { + if (betteralgs) { + char *old_ba = betteralgs; + betteralgs = dupcat(betteralgs, ",", hktype->alg->ssh_id); + sfree(old_ba); + } else { + betteralgs = dupstr(hktype->alg->ssh_id); + } + } + } + if (betteralgs) { + /* Use the special warning prompt that lets us provide + * a list of better algorithms */ + s->dlgret = seat_confirm_weak_cached_hostkey( + s->ppl.seat, 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->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s, "host key type", s->hostkey_alg->ssh_id, + s->hostkey_alg); + } + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at host key warning"); + return; + } + } + + if (s->warn_cscipher) { + s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s, "client-to-server cipher", s->out.cipher->ssh2_id, + s->out.cipher); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); + return; + } + } + + if (s->warn_sccipher) { + s->dlgret = ssh2_transport_confirm_weak_crypto_primitive( + s, "server-to-client cipher", s->in.cipher->ssh2_id, + s->in.cipher); + crMaybeWaitUntilV(s->dlgret >= 0); + if (s->dlgret == 0) { + ssh_user_close(s->ppl.ssh, "User aborted at cipher warning"); + return; + } + } + + /* + * 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); + 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->K, s->exchange_hash, + 'A' + s->out.mkkey_adjust, s->out.cipher->blksize); + ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, + 'C' + s->out.mkkey_adjust, + s->out.cipher->padded_keybytes); + } + if (s->out.mac) { + ssh2_mkkey(s, mac_key, s->K, 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); + + 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); + + /* + * 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->K, s->exchange_hash, + 'A' + s->in.mkkey_adjust, s->in.cipher->blksize); + ssh2_mkkey(s, cipher_key, s->K, s->exchange_hash, + 'C' + s->in.mkkey_adjust, + s->in.cipher->padded_keybytes); + } + if (s->in.mac) { + ssh2_mkkey(s, mac_key, s->K, 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); + + strbuf_free(cipher_key); + strbuf_free(cipher_iv); + strbuf_free(mac_key); + } + + /* + * Free shared secret. + */ + mp_free(s->K); s->K = 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); + + /* 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 *loginv, int ret) +{ + struct ssh2_transport_state *s = (struct ssh2_transport_state *)loginv; + s->dlgret = ret; + 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, avoding 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 bool ssh2_transport_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s = + container_of(ppl, struct ssh2_transport_state, ppl); + + /* Just delegate this to the higher layer */ + return ssh_ppl_want_user_input(s->higher_layer); +} + +static void ssh2_transport_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_transport_state *s = + container_of(ppl, struct ssh2_transport_state, ppl); + + /* Just delegate this to the higher layer */ + ssh_ppl_got_user_input(s->higher_layer); +} + +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; +} + +/* + * Wrapper on seat_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 int ssh2_transport_confirm_weak_crypto_primitive( + struct ssh2_transport_state *s, const char *type, const char *name, + const void *alg) +{ + if (find234(s->weak_algorithms_consented_to, (void *)alg, NULL)) + return 1; + add234(s->weak_algorithms_consented_to, (void *)alg); + + return seat_confirm_weak_crypto_primitive( + s->ppl.seat, type, name, ssh2_transport_dialog_callback, s); +} + +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)); +} diff --git a/0.77_My_PuTTY/ssh2transport.h b/0.77_My_PuTTY/ssh2transport.h new file mode 100644 index 0000000..d49f659 --- /dev/null +++ b/0.77_My_PuTTY/ssh2transport.h @@ -0,0 +1,245 @@ +/* + * Header connecting the pieces of the SSH-2 transport layer. + */ + +#ifndef PUTTY_SSH2TRANSPORT_H +#define PUTTY_SSH2TRANSPORT_H + +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.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 + +#define MAXKEXLIST 16 +struct kexinit_algorithm { + const char *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; +}; + +#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_dss) \ + X(HK_RSA, ssh_rsa_sha512) \ + X(HK_RSA, ssh_rsa_sha256) \ + X(HK_RSA, 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; + char *hostkey_str; /* string representation, for easy checking in rekeys */ + 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; + 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; + + int nbits, pbits; + bool warn_kex, warn_hk, warn_cscipher, warn_sccipher; + mp_int *p, *g, *e, *f, *K; + 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; + 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; + int dlgret; + bool guessok; + bool ignorepkt; + struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST]; +#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 *, int); + +/* 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/0.77_My_PuTTY/ssh2userauth-server.c b/0.77_My_PuTTY/ssh2userauth-server.c new file mode 100644 index 0000000..2a9ffe0 --- /dev/null +++ b/0.77_My_PuTTY/ssh2userauth-server.c @@ -0,0 +1,377 @@ +/* + * Packet protocol layer for the server side of the SSH-2 userauth + * protocol (RFC 4252). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" +#include "sshserver.h" + +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.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, + .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_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); + } + + 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); + 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)); + 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/0.77_My_PuTTY/ssh2userauth.c b/0.77_My_PuTTY/ssh2userauth.c new file mode 100644 index 0000000..e03c30c --- /dev/null +++ b/0.77_My_PuTTY/ssh2userauth.c @@ -0,0 +1,2128 @@ +/* + * Packet protocol layer for the client side of the SSH-2 userauth + * protocol (RFC 4252). + */ + +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshcr.h" + +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.h" +#endif + +#define BANNER_LIMIT 131072 + +typedef struct agent_key { + strbuf *blob, *comment; + ptrlen algorithm; +} agent_key; + +#ifdef MOD_PERSO +#include "kitty.h" +void SetSSHConnected( int flag ); +void debug_logevent( const char *fmt, ... ) ; +size_t iso8859_1_to_utf8(char *content, size_t max_size) ; // Latin-1 = iso8859-1 +size_t utf8_to_iso8859_15(char *const output, const char *const input, const size_t length) ; // Latin-9 = iso8859-15 +char bufpass[1024]=""; +int auth_loop_num = 0 ; +#endif + +struct ssh2_userauth_state { + int crState; + + PacketProtocolLayer *transport_layer, *successor_layer; + Filename *keyfile; + bool show_banner, tryagent, notrivialauth, change_username; + char *hostname, *fullhostname; + 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; + int userpass_ret; + 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; + 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 want_user_input; + bool is_trivial_auth; + + agent_pending_query *auth_agent_query; + bufchain banner; + bufchain_sink banner_bs; + StripCtrlChars *banner_scc; + bool banner_scc_initialised; + + 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 bool ssh2_userauth_want_user_input(PacketProtocolLayer *ppl); +static void ssh2_userauth_got_user_input(PacketProtocolLayer *ppl); +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_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 void ssh2_userauth_antispoof_msg( + struct ssh2_userauth_state *s, const char *msg); + +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, + .want_user_input = ssh2_userauth_want_user_input, + .got_user_input = ssh2_userauth_got_user_input, + .reconfigure = ssh2_userauth_reconfigure, + .queued_data_size = ssh_ppl_default_queued_data_size, + .name = "ssh-userauth", +}; + +PacketProtocolLayer *ssh2_userauth_new( + PacketProtocolLayer *successor_layer, + const char *hostname, const char *fullhostname, + Filename *keyfile, 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) +{ + 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->fullhostname = dupstr(fullhostname); + s->keyfile = filename_copy(keyfile); + 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); + + 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); + sfree(s->default_username); + sfree(s->locally_allocated_username); + sfree(s->hostname); + sfree(s->fullhostname); + sfree(s->publickey_comment); + sfree(s->publickey_algorithm); + if (s->publickey_blob) + strbuf_free(s->publickey_blob); + 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); +} + +static void ssh2_userauth_filter_queue(struct ssh2_userauth_state *s) +{ + PktIn *pktin; + ptrlen string; + + while ((pktin = pq_peek(s->ppl.in_pq)) != NULL) { + switch (pktin->type) { + case SSH2_MSG_USERAUTH_BANNER: + if (!s->show_banner) { + pq_pop(s->ppl.in_pq); + break; + } + + 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); + 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 void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_state *s = + container_of(ppl, struct ssh2_userauth_state, ppl); + PktIn *pktin; + +#ifdef MOD_RECONNECT + //SetSSHConnected(0); +#endif + + 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; + } + } + + /* + * 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_new(); + put_datapl(s->agent_keys[i].blob, get_string(s->asrc)); + s->agent_keys[i].comment = strbuf_new(); + put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); + + /* Also, extract the algorithm string from the start + * of the public-key blob. */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( + s->agent_keys[i].blob)); + s->agent_keys[i].algorithm = get_string(src); + } + + 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:; + } + + /* + * We repeat this whole loop, including the username prompt, + * until we manage a successful authentication. If the user + * types the wrong _password_, they can be sent back to the + * beginning to try another username, if this is configured on. + * (If they specify a username in the config, they are never + * asked, even if they do give a wrong password.) + * + * I think this best serves the needs of + * + * - the people who have no configuration, no keys, and just + * want to try repeated (username,password) pairs until they + * type both correctly + * + * - people who have keys and configuration but occasionally + * need to fall back to passwords + * + * - people with a key held in Pageant, who might not have + * logged in to a particular machine before; so they want to + * type a username, and then _either_ their key will be + * accepted, _or_ they will type a password. If they mistype + * the username they will want to be able to get back and + * retype it! + */ + s->got_username = false; + while (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 = new_prompts(); + 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->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * seat_get_userpass_input() failed to get a username. + * Terminate. + */ + free_prompts(s->cur_prompt); + ssh_user_close(s->ppl.ssh, "No username provided"); + return; + } + sfree(s->locally_allocated_username); /* for change_username */ + s->username = s->locally_allocated_username = + prompt_get_result(s->cur_prompt->prompts[0]); +#ifdef MOD_PERSO + SetUsernameInConfig( (const char*)s->username ) ; +#endif + free_prompts(s->cur_prompt); + } 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. + */ + if (bufchain_size(&s->banner) && + (seat_verbose(s->ppl.seat) || seat_interactive(s->ppl.seat))) { + if (s->banner_scc) { + ssh2_userauth_antispoof_msg( + s, "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_stderr_pl(s->ppl.seat, 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_stderr_pl(s->ppl.seat, PTRLEN_LITERAL("\r\n")); + + if (s->banner_scc) { + seat_set_trust_status(s->ppl.seat, true); + ssh2_userauth_antispoof_msg( + s, "End of banner message from server"); + } + } + + 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"); +#ifdef MOD_PERSO + SetPasswordInConfig("") ; +#endif + } else { + assert(s->type == AUTH_TYPE_PASSWORD); + ppl_logevent("Password authentication failed"); + ppl_printf("Access denied\r\n"); +#ifdef MOD_PERSO + SetPasswordInConfig("") ; +#endif + + 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; + s->signflags = 0; + if (ptrlen_eq_string(s->agent_keyalg, "ssh-rsa")) { + /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family, + * if the server has announced support for them. */ + if (s->ppl.bpp->ext_info_rsa_sha512_ok) { + s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-512"); + s->signflags = SSH_AGENT_RSA_SHA2_512; + } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) { + s->agent_keyalg = PTRLEN_LITERAL("rsa-sha2-256"); + s->signflags = SSH_AGENT_RSA_SHA2_256; + } + } + + 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 */ + put_stringpl(s->pktout, s->agent_keyalg); + put_stringpl(s->pktout, 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 */ + put_stringpl(s->pktout, s->agent_keyalg); + put_stringpl(s->pktout, 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. + */ + if (!strcmp(s->publickey_algorithm, "ssh-rsa")) { + /* Try to upgrade ssh-rsa to one of the rsa-sha2-* family, + * if the server has announced support for them. */ + if (s->ppl.bpp->ext_info_rsa_sha512_ok) { + sfree(s->publickey_algorithm); + s->publickey_algorithm = dupstr("rsa-sha2-512"); + s->signflags = SSH_AGENT_RSA_SHA2_512; + } else if (s->ppl.bpp->ext_info_rsa_sha256_ok) { + sfree(s->publickey_algorithm); + s->publickey_algorithm = dupstr("rsa-sha2-256"); + s->signflags = SSH_AGENT_RSA_SHA2_256; + } + } + + /* + * 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 */ + put_stringz(s->pktout, s->publickey_algorithm); + put_string(s->pktout, s->publickey_blob->s, + s->publickey_blob->len); + 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"); +#ifdef MOD_RECONNECT + is_backend_first_connected = 0 ; +#endif + /* + * 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 = new_prompts(); + 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); +#ifdef MOD_PERSO + //if( GetAutoreconnectFlag() && (strlen(ManagePassPhrase(NULL))>0) ) { + if( strlen(ManagePassPhrase(NULL))>0 ) { + char *p = ManagePassPhrase(NULL) ; + bufchain bc; + bufchain_init(&bc); + bufchain_add(&bc, p, strlen(p)); + bufchain_add(&bc, "\n", 1); + s->userpass_ret = seat_get_userpass_input( s->ppl.seat, s->cur_prompt, &bc ); + logevent(NULL,"Test passphrase"); + ManagePassPhrase(""); + s->userpass_ret = 1 ; + + } else +#endif + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, + s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* Failed to get a passphrase. Terminate. */ + free_prompts(s->cur_prompt); + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_user_close(s->ppl.ssh, "User aborted at " + "passphrase prompt"); + return; + } + passphrase = + prompt_get_result(s->cur_prompt->prompts[0]); + free_prompts(s->cur_prompt); + } else { + passphrase = NULL; /* no passphrase needed */ + } + + /* + * Try decrypting the key. + */ + key = ppk_load_f(s->keyfile, passphrase, &error); + + if (passphrase) { + /* burn the evidence */ +#ifdef MOD_PERSO + if(strlen(passphrase)>0) { ManagePassPhrase(passphrase); } +#endif + 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 */ + } +#ifdef MOD_PERSO + ManagePassPhrase(""); +#endif + } 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 */ + put_stringz(s->pktout, s->publickey_algorithm); + pkblob = strbuf_new(); + ssh_key_public_blob(key->key, BinarySink_UPCAST(pkblob)); + put_string(s->pktout, pkblob->s, pkblob->len); + + /* + * 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->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 the server continues to send INFO_REQUESTs. + */ + while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { + + ptrlen name, inst; + strbuf *sb; + + /* + * We've got a fresh USERAUTH_INFO_REQUEST. + * Get the preamble and start building a prompt. + */ + name = get_string(pktin); + inst = get_string(pktin); + get_string(pktin); /* skip language tag */ + s->cur_prompt = new_prompts(); + s->cur_prompt->to_server = true; + s->cur_prompt->from_server = true; + + /* + * Get any prompt(s) from the packet. + */ + s->num_prompts = get_uint32(pktin); + for (uint32_t i = 0; i < s->num_prompts; i++) { + s->is_trivial_auth = false; + ptrlen prompt = get_string(pktin); + bool echo = get_bool(pktin); + + if (get_err(pktin)) { + ssh_proto_error( + s->ppl.ssh, "Server sent truncated " + "SSH_MSG_USERAUTH_INFO_REQUEST packet"); + return; + } + + sb = strbuf_new(); + if (!prompt.len) { + put_datapl(sb, PTRLEN_LITERAL( + ": ")); + } 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)) { + ssh2_userauth_antispoof_msg( + s, "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 { + 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); + /* + * Our prompts_t is fully constructed now. Get the + * user's response(s). + */ + +#ifdef MOD_PERSO + auth_loop_num++ ; +//char b[128];sprintf(b,"%d",auth_loop_num);MessageBox(NULL,b,"info",MB_OK); + if( debug_flag && !IsPasswordInConf() ) { debug_logevent( "No password in configuration" ) ; } + if( (auth_loop_num==1) && IsPasswordInConf() ) { + GetPasswordInConfig(bufpass) ; + while( ((bufpass[strlen(bufpass)-1]=='n')&&(bufpass[strlen(bufpass)-2]=='\\')) || ((bufpass[strlen(bufpass)-1]=='r')&&(bufpass[strlen(bufpass)-2]=='\\')) ) { + bufpass[strlen(bufpass)-2]='\0'; + bufpass[strlen(bufpass)-1]='\0'; + } + //if( debug_flag ) { debug_logevent( "Raw password: %s", bufpass ) ; } + iso8859_1_to_utf8( bufpass, 1024 ) ; + //if( debug_flag ) { debug_logevent( "UTF-8 password: %s", bufpass ) ; } + bufchain bc; + bufchain_init(&bc); + bufchain_add(&bc, bufpass, strlen(bufpass)); + bufchain_add(&bc,"\n",1); + s->userpass_ret = seat_get_userpass_input( s->ppl.seat, s->cur_prompt, &bc ) ; + + SetPasswordInConfig(""); + CleanPassword(bufpass); + { // Log de l'envoi du password + char *userlog = dupprintf("Send automatic password (Using keyboard-interactive authentication)" ); + ppl_logevent(userlog); // Affichage dans l'event log + // Affichage à l'écran + //if (flags & FLAG_INTERACTIVE && + //(flags & FLAG_VERBOSE)) { + if(seat_verbose(s->ppl.seat) && seat_interactive(s->ppl.seat)) { + ppl_printf("%s\r\n",userlog); + } + + sfree(userlog); + } + } else +#endif + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); +#ifdef MOD_PERSO + if( (auth_loop_num!=1) || !IsPasswordInConf() ) +#endif + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_user_close(s->ppl.ssh, "User aborted during " + "keyboard-interactive authentication"); + return; + } + + /* + * Send the response(s) to the server. + */ + s->pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE); + put_uint32(s->pktout, s->num_prompts); + for (uint32_t i = 0; i < s->num_prompts; i++) { +#ifdef MOD_PERSO + ppl_logevent("Sent a password"); + if( (auth_loop_num==1) && !GetUserPassSSHNoSave() ) { + if(s!=NULL) + if(s->cur_prompt->prompts!=NULL) + if(s->cur_prompt->prompts[i]->result!=NULL) { + ppl_logevent("Save a password"); + //SetPasswordInConfig( (const char *) s->cur_prompt->prompts[i]->result ) ; + SetPasswordInConfig( (const char *) prompt_get_result_ref(s->cur_prompt->prompts[i]) ) ; + } + } +#endif + put_stringz(s->pktout, prompt_get_result_ref( + s->cur_prompt->prompts[i])); + } + s->pktout->minlen = 256; + pq_push(s->ppl.out_pq, s->pktout); + + /* + * Free the prompts structure from this iteration. + * If there's another, a new one will be allocated + * when we return to the top of this while loop. + */ + free_prompts(s->cur_prompt); + + /* + * Get the next packet in case it's another + * INFO_REQUEST. + */ + 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); + ssh2_userauth_antispoof_msg( + s, "End of keyboard-interactive prompts from server"); + } + + /* + * We should have SUCCESS or FAILURE now. + */ + pq_push_front(s->ppl.in_pq, pktin); + + } 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 = new_prompts(); + 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->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); +#ifdef MOD_PERSO + if( !IsPasswordInConf() ) +#endif + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_bpp_queue_disconnect( + s->ppl.bpp, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + ssh_user_close(s->ppl.ssh, "User aborted during password " + "authentication"); + 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); + + /* + * 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); +#ifdef MOD_PERSO + if( debug_flag && !IsPasswordInConf() ) { debug_logevent( "No password in configuration" ) ; } + if( IsPasswordInConf() ) { + GetPasswordInConfig(bufpass); + while( ((bufpass[strlen(bufpass)-1]=='n')&&(bufpass[strlen(bufpass)-2]=='\\')) || ((bufpass[strlen(bufpass)-1]=='r')&&(bufpass[strlen(bufpass)-2]=='\\')) ) { + bufpass[strlen(bufpass)-2]='\0'; + bufpass[strlen(bufpass)-1]='\0'; + } + char bufpass2[1024]; + strcpy(bufpass2,bufpass); + //if( debug_flag ) { debug_logevent( "Raw password: %s", bufpass2 ) ; } + iso8859_1_to_utf8( bufpass2, 1024 ); + //if( debug_flag ) { debug_logevent( "UTF-8 password: %s", bufpass2 ) ; } + put_stringz(s->pktout, bufpass2); + memset(bufpass2,0,1024); + + SetPasswordInConfig(""); + { // Log de l'envoi du password + char *userlog = dupprintf("Send automatic password" ); + ppl_logevent(userlog); // Affichage dans l'event log + // Affichage à l'écran + //if (flags & FLAG_INTERACTIVE && + //(flags & FLAG_VERBOSE)) { + if(seat_verbose(s->ppl.seat) && seat_interactive(s->ppl.seat)) { + ppl_printf("\r\n%s\r\n",userlog); + } + sfree(userlog); + } + } else { + put_stringz(s->pktout, s->password); + } +#else + put_stringz(s->pktout, s->password); +#endif + 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); +#ifdef MOD_PERSO + if( !GetUserPassSSHNoSave() ) { + if( strlen(bufpass)>0 ) { + ppl_logevent("Save password"); + SetPasswordInConfig( bufpass ) ; + memset( bufpass, 0, strlen(bufpass) ) ; + } else if( s != NULL ) { + if( s->password != NULL ) { + char bufpass2[1024] ; + ppl_logevent("Save the password"); + utf8_to_iso8859_15( bufpass2, s->password, 1024 ) ; + SetPasswordInConfig( bufpass2 ) ; + memset(bufpass2,0,1024) ; + //SetPasswordInConfig( s->password ) ; + } + } + } + CleanPassword(bufpass); +#endif + 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 = new_prompts(); + 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->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, NULL); + while (1) { + while (s->userpass_ret < 0 && + bufchain_size(s->ppl.user_input) > 0) + s->userpass_ret = seat_get_userpass_input( + s->ppl.seat, s->cur_prompt, + s->ppl.user_input); + + if (s->userpass_ret >= 0) + break; + + s->want_user_input = true; + crReturnV; + s->want_user_input = false; + } + if (!s->userpass_ret) { + /* + * Failed to get responses. Terminate. + */ + /* burn the evidence */ + free_prompts(s->cur_prompt); + 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_user_close(s->ppl.ssh, "User aborted during " + "password changing"); + 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->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: +#ifdef MOD_PERSO + SetSSHConnected(1) ; + auth_loop_num=0; +#endif + 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_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 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 bool ssh2_userauth_want_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_state *s = + container_of(ppl, struct ssh2_userauth_state, ppl); + return s->want_user_input; +} + +static void ssh2_userauth_got_user_input(PacketProtocolLayer *ppl) +{ + struct ssh2_userauth_state *s = + container_of(ppl, struct ssh2_userauth_state, ppl); + if (s->want_user_input) + queue_idempotent_callback(&s->ppl.ic_process_queue); +} + +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_antispoof_msg( + struct ssh2_userauth_state *s, const char *msg) +{ + strbuf *sb = strbuf_new(); + if (seat_set_trust_status(s->ppl.seat, true)) { + /* + * 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_datapl(sb, ptrlen_from_asciz(msg)); + } else { + /* + * Otherwise, add enough padding around it that the server + * wouldn't be able to mimic it within our line-length + * constraint. + */ + strbuf_catf(sb, "-- %s ", msg); + while (sb->len < 78) + put_byte(sb, '-'); + } + put_datapl(sb, PTRLEN_LITERAL("\r\n")); + seat_stderr_pl(s->ppl.seat, ptrlen_from_strbuf(sb)); + strbuf_free(sb); +} diff --git a/0.77_My_PuTTY/sshaes.c b/0.77_My_PuTTY/sshaes.c new file mode 100644 index 0000000..55bac47 --- /dev/null +++ b/0.77_My_PuTTY/sshaes.c @@ -0,0 +1,1913 @@ +/* + * sshaes.c - implementation of AES + */ + +#include +#include + +#include "ssh.h" +#include "mpint_i.h" /* we reuse the BignumInt system */ + +/* + * Start by deciding whether we can support hardware AES at all. + */ +#define HW_AES_NONE 0 +#define HW_AES_NI 1 +#define HW_AES_NEON 2 + +#ifdef _FORCE_AES_NI +# define HW_AES HW_AES_NI +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__x86_64__) || defined(__i386)) +# define HW_AES HW_AES_NI +# endif +#elif defined(__GNUC__) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) && \ + (defined(__x86_64__) || defined(__i386)) +# define HW_AES HW_AES_NI +# endif +#elif defined (_MSC_VER) +# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 +# define HW_AES HW_AES_NI +# endif +#endif + +#ifdef _FORCE_AES_NEON +# define HW_AES HW_AES_NEON +#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + /* Arm can potentially support both endiannesses, but this code + * hasn't been tested on anything but little. If anyone wants to + * run big-endian, they'll need to fix it first. */ +#elif defined __ARM_FEATURE_CRYPTO + /* If the Arm crypto extension is available already, we can + * support NEON AES without having to enable anything by hand */ +# define HW_AES HW_AES_NEON +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__aarch64__)) + /* clang can enable the crypto extension in AArch64 using + * __attribute__((target)) */ +# define HW_AES HW_AES_NEON +# define USE_CLANG_ATTR_TARGET_AARCH64 +# endif +#elif defined _MSC_VER +# if defined _M_ARM64 +# define HW_AES HW_AES_NEON + /* 64-bit Visual Studio uses the header in place + * of the standard */ +# define USE_ARM64_NEON_H +# elif defined _M_ARM +# define HW_AES HW_AES_NEON + /* 32-bit Visual Studio uses the right header name, but requires + * this #define to enable a set of intrinsic definitions that + * do not omit one of the parameters for vaes[ed]q_u8 */ +# define _ARM_USE_NEW_NEON_INTRINSICS +# endif +#endif + +#if defined _FORCE_SOFTWARE_AES || !defined HW_AES +# undef HW_AES +# define HW_AES HW_AES_NONE +#endif + +#if HW_AES == HW_AES_NI +#define HW_NAME_SUFFIX " (AES-NI accelerated)" +#elif HW_AES == HW_AES_NEON +#define HW_NAME_SUFFIX " (NEON accelerated)" +#else +#define HW_NAME_SUFFIX " (!NONEXISTENT ACCELERATED VERSION!)" +#endif + +/* + * Vtable collection for AES. For each SSH-level cipher id (i.e. + * combination of key length and cipher mode), we provide three + * vtables: one for the pure software implementation, one using + * hardware acceleration (if available), and a top-level one which is + * never actually instantiated, and only contains a new() method whose + * job is to decide which of the other two to return an actual + * instance of. + */ + +static ssh_cipher *aes_select(const ssh_cipheralg *alg); +static ssh_cipher *aes_sw_new(const ssh_cipheralg *alg); +static void aes_sw_free(ssh_cipher *); +static void aes_sw_setiv_cbc(ssh_cipher *, const void *iv); +static void aes_sw_setiv_sdctr(ssh_cipher *, const void *iv); +static void aes_sw_setkey(ssh_cipher *, const void *key); +static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg); +static void aes_hw_free(ssh_cipher *); +static void aes_hw_setiv_cbc(ssh_cipher *, const void *iv); +static void aes_hw_setiv_sdctr(ssh_cipher *, const void *iv); +static void aes_hw_setkey(ssh_cipher *, const void *key); + +struct aes_extra { + const ssh_cipheralg *sw, *hw; +}; + +#define VTABLES_INNER(cid, pid, bits, name, encsuffix, \ + decsuffix, setivsuffix, flagsval) \ + static void cid##_sw##encsuffix(ssh_cipher *, void *blk, int len); \ + static void cid##_sw##decsuffix(ssh_cipher *, void *blk, int len); \ + const ssh_cipheralg ssh_##cid##_sw = { \ + .new = aes_sw_new, \ + .free = aes_sw_free, \ + .setiv = aes_sw_##setivsuffix, \ + .setkey = aes_sw_setkey, \ + .encrypt = cid##_sw##encsuffix, \ + .decrypt = cid##_sw##decsuffix, \ + .ssh2_id = pid, \ + .blksize = 16, \ + .real_keybits = bits, \ + .padded_keybytes = bits/8, \ + .flags = flagsval, \ + .text_name = name " (unaccelerated)", \ + }; \ + \ + static void cid##_hw##encsuffix(ssh_cipher *, void *blk, int len); \ + static void cid##_hw##decsuffix(ssh_cipher *, void *blk, int len); \ + const ssh_cipheralg ssh_##cid##_hw = { \ + .new = aes_hw_new, \ + .free = aes_hw_free, \ + .setiv = aes_hw_##setivsuffix, \ + .setkey = aes_hw_setkey, \ + .encrypt = cid##_hw##encsuffix, \ + .decrypt = cid##_hw##decsuffix, \ + .ssh2_id = pid, \ + .blksize = 16, \ + .real_keybits = bits, \ + .padded_keybytes = bits/8, \ + .flags = flagsval, \ + .text_name = name HW_NAME_SUFFIX, \ + }; \ + \ + static const struct aes_extra extra_##cid = { \ + &ssh_##cid##_sw, &ssh_##cid##_hw }; \ + \ + const ssh_cipheralg ssh_##cid = { \ + .new = aes_select, \ + .ssh2_id = pid, \ + .blksize = 16, \ + .real_keybits = bits, \ + .padded_keybytes = bits/8, \ + .flags = flagsval, \ + .text_name = name " (dummy selector vtable)", \ + .extra = &extra_##cid \ + }; \ + +#define VTABLES(keylen) \ + VTABLES_INNER(aes ## keylen ## _cbc, "aes" #keylen "-cbc", \ + keylen, "AES-" #keylen " CBC", _encrypt, _decrypt, \ + setiv_cbc, SSH_CIPHER_IS_CBC) \ + VTABLES_INNER(aes ## keylen ## _sdctr, "aes" #keylen "-ctr", \ + keylen, "AES-" #keylen " SDCTR",,, setiv_sdctr, 0) + +VTABLES(128) +VTABLES(192) +VTABLES(256) + +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 = 0, + .text_name = "AES-256 CBC (dummy selector vtable)", + .extra = &extra_aes256_cbc, +}; + +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 }; + +/* + * The actual query function that asks if hardware acceleration is + * available. + */ +static bool aes_hw_available(void); + +/* + * The top-level selection function, caching the results of + * aes_hw_available() so it only has to run once. + */ +static bool aes_hw_available_cached(void) +{ + static bool initialised = false; + static bool hw_available; + if (!initialised) { + hw_available = aes_hw_available(); + initialised = true; + } + return hw_available; +} + +static ssh_cipher *aes_select(const ssh_cipheralg *alg) +{ + const struct aes_extra *extra = (const struct aes_extra *)alg->extra; + const ssh_cipheralg *real_alg = + aes_hw_available_cached() ? extra->hw : extra->sw; + + return ssh_cipher_new(real_alg); +} + +/* ---------------------------------------------------------------------- + * Definitions likely to be helpful to multiple implementations. + */ + +#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 + +static const uint8_t key_setup_round_constants[] = { + /* 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, +}; + +#define MAXROUNDKEYS 15 + +/* ---------------------------------------------------------------------- + * 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. + */ + +#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(key_setup_round_constants)); + uint8_t rcon = 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; + } 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); +} + +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; + } +} + +#define SW_ENC_DEC(len) \ + static void aes##len##_cbc_sw_encrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_sw_encrypt(ciph, vblk, blklen); } \ + static void aes##len##_cbc_sw_decrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_sw_decrypt(ciph, vblk, blklen); } \ + static void aes##len##_sdctr_sw( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_sdctr_sw(ciph, vblk, blklen); } + +SW_ENC_DEC(128) +SW_ENC_DEC(192) +SW_ENC_DEC(256) + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of AES using x86 AES-NI. + */ + +#if HW_AES == HW_AES_NI + +/* + * Set target architecture for Clang and GCC + */ +#if !defined(__clang__) && defined(__GNUC__) +# pragma GCC target("aes") +# pragma GCC target("sse4.1") +#endif + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) +# define FUNC_ISA __attribute__ ((target("sse4.1,aes"))) +#else +# define FUNC_ISA +#endif + +#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 + +bool aes_hw_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 FUNC_ISA 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 FUNC_ISA 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(key_setup_round_constants)); + temp ^= 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 FUNC_ISA 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; +} + +/* + * 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 FUNC_ISA 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_hw_new(const ssh_cipheralg *alg) +{ + if (!aes_hw_available_cached()) + 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_hw_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_hw_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 FUNC_ISA void aes_hw_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 FUNC_ISA void aes_hw_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); +} + +typedef __m128i (*aes_ni_fn)(__m128i v, const __m128i *keysched); + +static FUNC_ISA 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 FUNC_ISA 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 FUNC_ISA 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); + } +} + +#define NI_ENC_DEC(len) \ + static FUNC_ISA void aes##len##_cbc_hw_encrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_ni_encrypt(ciph, vblk, blklen, aes_ni_##len##_e); } \ + static FUNC_ISA void aes##len##_cbc_hw_decrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_ni_decrypt(ciph, vblk, blklen, aes_ni_##len##_d); } \ + static FUNC_ISA void aes##len##_sdctr_hw( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_sdctr_ni(ciph, vblk, blklen, aes_ni_##len##_e); } \ + +NI_ENC_DEC(128) +NI_ENC_DEC(192) +NI_ENC_DEC(256) + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of AES using Arm NEON. + */ + +#elif HW_AES == HW_AES_NEON + +/* + * Manually set the target architecture, if we decided above that we + * need to. + */ +#ifdef USE_CLANG_ATTR_TARGET_AARCH64 +/* + * A spot of cheating: redefine some ACLE feature macros before + * including arm_neon.h. Otherwise we won't get the AES intrinsics + * defined by that header, because it will be looking at the settings + * for the whole translation unit rather than the ones we're going to + * put on some particular functions using __attribute__((target)). + */ +#define __ARM_NEON 1 +#define __ARM_FEATURE_CRYPTO 1 +#define __ARM_FEATURE_AES 1 +#define FUNC_ISA __attribute__ ((target("neon,crypto"))) +#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ + +#ifndef FUNC_ISA +#define FUNC_ISA +#endif + +#ifdef USE_ARM64_NEON_H +#include +#else +#include +#endif + +static bool aes_hw_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_hw_available(); +} + +/* + * Core NEON encrypt/decrypt functions, one per length and direction. + */ + +#define NEON_CIPHER(len, repmacro) \ + static FUNC_ISA 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 FUNC_ISA 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 FUNC_ISA 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(key_setup_round_constants)); + temp ^= 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 FUNC_ISA 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 FUNC_ISA 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)); +} + +/* + * 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_hw_new(const ssh_cipheralg *alg) +{ + if (!aes_hw_available_cached()) + return NULL; + + aes_neon_context *ctx = snew(aes_neon_context); + ctx->ciph.vt = alg; + return &ctx->ciph; +} + +static void aes_hw_free(ssh_cipher *ciph) +{ + aes_neon_context *ctx = container_of(ciph, aes_neon_context, ciph); + smemclr(ctx, sizeof(*ctx)); + sfree(ctx); +} + +static void aes_hw_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 FUNC_ISA void aes_hw_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 FUNC_ISA void aes_hw_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); +} + +typedef uint8x16_t (*aes_neon_fn)(uint8x16_t v, const uint8x16_t *keysched); + +static FUNC_ISA 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 FUNC_ISA 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 FUNC_ISA 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); + } +} + +#define NEON_ENC_DEC(len) \ + static FUNC_ISA void aes##len##_cbc_hw_encrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_neon_encrypt(ciph, vblk, blklen, aes_neon_##len##_e); } \ + static FUNC_ISA void aes##len##_cbc_hw_decrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_cbc_neon_decrypt(ciph, vblk, blklen, aes_neon_##len##_d); } \ + static FUNC_ISA void aes##len##_sdctr_hw( \ + ssh_cipher *ciph, void *vblk, int blklen) \ + { aes_sdctr_neon(ciph, vblk, blklen, aes_neon_##len##_e); } \ + +NEON_ENC_DEC(128) +NEON_ENC_DEC(192) +NEON_ENC_DEC(256) + +/* ---------------------------------------------------------------------- + * Stub functions if we have no hardware-accelerated AES. In this + * case, aes_hw_new returns NULL (though it should also never be + * selected by aes_select, so the only thing that should even be + * _able_ to call it is testcrypt). As a result, the remaining vtable + * functions should never be called at all. + */ + +#elif HW_AES == HW_AES_NONE + +bool aes_hw_available(void) +{ + return false; +} + +static ssh_cipher *aes_hw_new(const ssh_cipheralg *alg) +{ + return NULL; +} + +#define STUB_BODY { unreachable("Should never be called"); } + +static void aes_hw_free(ssh_cipher *ciph) STUB_BODY +static void aes_hw_setkey(ssh_cipher *ciph, const void *key) STUB_BODY +static void aes_hw_setiv_cbc(ssh_cipher *ciph, const void *iv) STUB_BODY +static void aes_hw_setiv_sdctr(ssh_cipher *ciph, const void *iv) STUB_BODY +#define STUB_ENC_DEC(len) \ + static void aes##len##_cbc_hw_encrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) STUB_BODY \ + static void aes##len##_cbc_hw_decrypt( \ + ssh_cipher *ciph, void *vblk, int blklen) STUB_BODY \ + static void aes##len##_sdctr_hw( \ + ssh_cipher *ciph, void *vblk, int blklen) STUB_BODY + +STUB_ENC_DEC(128) +STUB_ENC_DEC(192) +STUB_ENC_DEC(256) + +#endif /* HW_AES */ diff --git a/0.77_My_PuTTY/ssharcf.c b/0.77_My_PuTTY/ssharcf.c new file mode 100644 index 0000000..b4b1787 --- /dev/null +++ b/0.77_My_PuTTY/ssharcf.c @@ -0,0 +1,141 @@ +/* + * 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, + .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, + .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/0.77_My_PuTTY/sshargon2.c b/0.77_My_PuTTY/sshargon2.c new file mode 100644 index 0000000..b303a98 --- /dev/null +++ b/0.77_My_PuTTY/sshargon2.c @@ -0,0 +1,565 @@ +/* + * 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 inital 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/0.77_My_PuTTY/sshauxcrypt.c b/0.77_My_PuTTY/sshauxcrypt.c new file mode 100644 index 0000000..029eecf --- /dev/null +++ b/0.77_My_PuTTY/sshauxcrypt.c @@ -0,0 +1,166 @@ +/* + * sshauxcrypt.c: wrapper functions on crypto primitives for use in + * other contexts than the main SSH packet protocol, such as + * encrypting private key files and performing XDM-AUTHORIZATION-1. + * + * These all work through the standard cipher/hash/MAC APIs, so they + * don't need to live in the same actual source files as the ciphers + * they wrap, and I think it keeps things tidier to have them out of + * the way here instead. + */ + +#include "ssh.h" + +static ssh_cipher *aes256_pubkey_cipher(const void *key, const void *iv) +{ + /* + * PuTTY's own .PPK format for SSH-2 private key files is + * encrypted with 256-bit AES in CBC mode. + */ + 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); +} + +static ssh_cipher *des3_pubkey_cipher(const void *vkey) +{ + /* + * SSH-1 private key files are encrypted with triple-DES in SSH-1 + * style (three separate CBC layers), but the same key is used for + * the first and third layers. + */ + 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); +} + +static ssh_cipher *des3_pubkey_ossh_cipher(const void *vkey, const void *viv) +{ + /* + * OpenSSH PEM private key files are encrypted with triple-DES in + * SSH-2 style (one CBC layer), with three distinct keys, and an + * IV also generated from the passphrase. + */ + 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); +} + +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); +} + +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); +} + +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/0.77_My_PuTTY/sshbcrypt.c b/0.77_My_PuTTY/sshbcrypt.c new file mode 100644 index 0000000..7cdd44e --- /dev/null +++ b/0.77_My_PuTTY/sshbcrypt.c @@ -0,0 +1,119 @@ +/* + * '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 "sshblowf.h" + +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; +} + +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); +} + +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(const char *passphrase, + const unsigned char *salt, int saltbytes, + 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, ptrlen_from_asciz(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; + thissaltbytes = saltbytes; + 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/0.77_My_PuTTY/sshblake2.c b/0.77_My_PuTTY/sshblake2.c new file mode 100644 index 0000000..7ed3c31 --- /dev/null +++ b/0.77_My_PuTTY/sshblake2.c @@ -0,0 +1,223 @@ +/* + * 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/0.77_My_PuTTY/sshblowf.c b/0.77_My_PuTTY/sshblowf.c new file mode 100644 index 0000000..fcc6afd --- /dev/null +++ b/0.77_My_PuTTY/sshblowf.c @@ -0,0 +1,699 @@ +/* + * Blowfish implementation for PuTTY. + * + * Coded from scratch from the algorithm description. + */ + +#include +#include +#include "ssh.h" +#include "sshblowf.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, + .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, + .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, + .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/0.77_My_PuTTY/sshblowf.h b/0.77_My_PuTTY/sshblowf.h new file mode 100644 index 0000000..a9efe3d --- /dev/null +++ b/0.77_My_PuTTY/sshblowf.h @@ -0,0 +1,14 @@ +/* + * Header file shared between sshblowf.c and sshbcrypt.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/0.77_My_PuTTY/sshbpp.h b/0.77_My_PuTTY/sshbpp.h new file mode 100644 index 0000000..4f8860c --- /dev/null +++ b/0.77_My_PuTTY/sshbpp.h @@ -0,0 +1,179 @@ +/* + * 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); +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); + +/* + * 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/0.77_My_PuTTY/sshccp.c b/0.77_My_PuTTY/sshccp.c new file mode 100644 index 0000000..3b68025 --- /dev/null +++ b/0.77_My_PuTTY/sshccp.c @@ -0,0 +1,1062 @@ +/* + * 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; +}; + +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; + BinarySink_DELEGATE_INIT(&ctx->mac_if, ctx); + return &ctx->mac_if; +} + +static void poly_ssh2_free(ssh2_mac *mac) +{ + /* Not allocated, just forwarded, no need to free */ +} + +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, + .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; + return &ctx->ciph; +} + +static void ccp_free(ssh_cipher *cipher) +{ + struct ccp_context *ctx = container_of(cipher, struct ccp_context, ciph); + 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 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, + .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/0.77_My_PuTTY/sshchan.h b/0.77_My_PuTTY/sshchan.h new file mode 100644 index 0000000..38a628f --- /dev/null +++ b/0.77_My_PuTTY/sshchan.h @@ -0,0 +1,316 @@ +/* + * 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/0.77_My_PuTTY/sshcommon.c b/0.77_My_PuTTY/sshcommon.c new file mode 100644 index 0000000..76d7a9f --- /dev/null +++ b/0.77_My_PuTTY/sshcommon.c @@ -0,0 +1,946 @@ +/* + * 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 "sshbpp.h" +#include "sshppl.h" +#include "sshchan.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 "sshttymodes.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(strbuf *buf, const char *data) +{ + if (buf->len > 0) + put_byte(buf, ','); + put_data(buf, data, strlen(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->user_input = old->user_input; + 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; +} + +/* ---------------------------------------------------------------------- + * 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) : 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 SSH1_BITMAP_WORD + +/* ---------------------------------------------------------------------- + * Function to check a host key against any manually configured in Conf. + */ + +int verify_ssh_manual_host_key(Conf *conf, char **fingerprints, ssh_key *key) +{ + if (!conf_get_str_nthstrkey(conf, CONF_ssh_manual_hostkeys, 0)) + return -1; /* no manual keys configured */ + + 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 1; /* success */ + } + } + + 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 1; /* success */ + } + sfree(base64blob); + } + + return 0; +} + +/* ---------------------------------------------------------------------- + * 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); +} diff --git a/0.77_My_PuTTY/sshcr.h b/0.77_My_PuTTY/sshcr.h new file mode 100644 index 0000000..cdb832c --- /dev/null +++ b/0.77_My_PuTTY/sshcr.h @@ -0,0 +1,87 @@ +/* + * 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: + * + * - right-click ssh.c in the FileView + * - click Settings + * - select the C/C++ tab and the General category + * - under `Debug info:', select anything _other_ than `Program + * Database for Edit and Continue'. + */ + +#define crBegin(v) 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/0.77_My_PuTTY/sshcrc.c b/0.77_My_PuTTY/sshcrc.c new file mode 100644 index 0000000..b1dc833 --- /dev/null +++ b/0.77_My_PuTTY/sshcrc.c @@ -0,0 +1,108 @@ +/* + * CRC32 implementation, as used in SSH-1. + * + * 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/0.77_My_PuTTY/sshcrcda.c b/0.77_My_PuTTY/sshcrcda.c new file mode 100644 index 0000000..760f59a --- /dev/null +++ b/0.77_My_PuTTY/sshcrcda.c @@ -0,0 +1,171 @@ +/* $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 *ret = snew(struct crcda_ctx); + ret->h = NULL; + ret->n = HASH_MINSIZE / HASH_ENTRYSIZE; + return ret; +} + +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/0.77_My_PuTTY/sshdes.c b/0.77_My_PuTTY/sshdes.c new file mode 100644 index 0000000..f83d8f0 --- /dev/null +++ b/0.77_My_PuTTY/sshdes.c @@ -0,0 +1,1048 @@ +/* + * sshdes.c: 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, + .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, + .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, + .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, + .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, + .blksize = 8, + .real_keybits = 168, + .padded_keybytes = 24, + .flags = SSH_CIPHER_IS_CBC, + .text_name = "triple-DES inner-CBC", +}; diff --git a/0.77_My_PuTTY/sshdh.c b/0.77_My_PuTTY/sshdh.c new file mode 100644 index 0000000..8e82219 --- /dev/null +++ b/0.77_My_PuTTY/sshdh.c @@ -0,0 +1,272 @@ +/* + * 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) +{ + ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF); + ctx->g = mp_from_integer(2); +} + +static void dh_group14_construct(dh_ctx *ctx) +{ + ctx->p = MP_LITERAL(0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF); + ctx->g = mp_from_integer(2); +} + +static const struct dh_extra extra_group1 = { + false, dh_group1_construct, +}; + +static const ssh_kex ssh_diffiehellman_group1_sha1 = { + "diffie-hellman-group1-sha1", "group1", + KEXTYPE_DH, &ssh_sha1, &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_group14 = { + false, dh_group14_construct, +}; + +static const ssh_kex ssh_diffiehellman_group14_sha256 = { + "diffie-hellman-group14-sha256", "group14", + KEXTYPE_DH, &ssh_sha256, &extra_group14, +}; + +static const ssh_kex ssh_diffiehellman_group14_sha1 = { + "diffie-hellman-group14-sha1", "group14", + KEXTYPE_DH, &ssh_sha1, &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 = { + "diffie-hellman-group-exchange-sha256", NULL, + KEXTYPE_DH, &ssh_sha256, &extra_gex, +}; + +static const ssh_kex ssh_diffiehellman_gex_sha1 = { + "diffie-hellman-group-exchange-sha1", NULL, + KEXTYPE_DH, &ssh_sha1, &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 }; + +/* + * 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 + * pgssapi.c as GSS_MECH_KRB5.) + */ +#define GSS_KRB5_OID_HASH "toWM5Slw5Ew8Mqkay+al2g==" + +static const ssh_kex ssh_gssk5_diffiehellman_gex_sha1 = { + "gss-gex-sha1-" GSS_KRB5_OID_HASH, NULL, + KEXTYPE_GSS, &ssh_sha1, &extra_gex, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group14_sha1 = { + "gss-group14-sha1-" GSS_KRB5_OID_HASH, "group14", + KEXTYPE_GSS, &ssh_sha1, &extra_group14, +}; + +static const ssh_kex ssh_gssk5_diffiehellman_group1_sha1 = { + "gss-group1-sha1-" GSS_KRB5_OID_HASH, "group1", + KEXTYPE_GSS, &ssh_sha1, &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. + * + * If `nbits' is greater than zero, it is used as an upper limit + * for the number of bits in x. This is safe provided that (a) you + * use twice as many bits in x as the number of bits you expect to + * use in your session key, and (b) the DH group is a safe prime + * (which SSH demands that it must be). + * + * P. C. van Oorschot, M. J. Wiener + * "On Diffie-Hellman Key Agreement with Short Exponents". + * Advances in Cryptology: Proceedings of Eurocrypt '96 + * Springer-Verlag, May 1996. + */ +mp_int *dh_create_e(dh_ctx *ctx, int nbits) +{ + /* + * 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); + if (nbits) { + mp_int *pow2 = mp_power_2(nbits+1); + mp_min_into(pow2, pow2, hi); + mp_free(hi); + hi = pow2; + } + + /* + * 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/0.77_My_PuTTY/sshdss.c b/0.77_My_PuTTY/sshdss.c new file mode 100644 index 0000000..7d99b2c --- /dev/null +++ b/0.77_My_PuTTY/sshdss.c @@ -0,0 +1,503 @@ +/* + * Digital Signature Standard implementation for PuTTY. + */ + +#include +#include +#include + +#include "ssh.h" +#include "mpint.h" +#include "misc.h" + +static void dss_freekey(ssh_key *key); /* forward reference */ + +static ssh_key *dss_new_pub(const ssh_keyalg *self, ptrlen data) +{ + BinarySource src[1]; + struct dss_key *dss; + + BinarySource_BARE_INIT_PL(src, data); + if (!ptrlen_eq_string(get_string(src), "ssh-dss")) + return NULL; + + dss = snew(struct dss_key); + dss->sshk.vt = &ssh_dss; + dss->p = get_mp_ssh2(src); + dss->q = get_mp_ssh2(src); + dss->g = get_mp_ssh2(src); + dss->y = get_mp_ssh2(src); + dss->x = NULL; + + if (get_err(src) || + mp_eq_integer(dss->p, 0) || mp_eq_integer(dss->q, 0)) { + /* Invalid key. */ + dss_freekey(&dss->sshk); + return NULL; + } + + return &dss->sshk; +} + +static void dss_freekey(ssh_key *key) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + if (dss->p) + mp_free(dss->p); + if (dss->q) + mp_free(dss->q); + if (dss->g) + mp_free(dss->g); + if (dss->y) + mp_free(dss->y); + if (dss->x) + mp_free(dss->x); + sfree(dss); +} + +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 *dss_cache_str(ssh_key *key) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + strbuf *sb = strbuf_new(); + + if (!dss->p) { + strbuf_free(sb); + return NULL; + } + + append_hex_to_strbuf(sb, dss->p); + append_hex_to_strbuf(sb, dss->q); + append_hex_to_strbuf(sb, dss->g); + append_hex_to_strbuf(sb, dss->y); + + return strbuf_to_str(sb); +} + +static key_components *dss_components(ssh_key *key) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + key_components *kc = key_components_new(); + + key_components_add_text(kc, "key_type", "DSA"); + assert(dss->p); + key_components_add_mp(kc, "p", dss->p); + key_components_add_mp(kc, "q", dss->q); + key_components_add_mp(kc, "g", dss->g); + key_components_add_mp(kc, "public_y", dss->y); + if (dss->x) + key_components_add_mp(kc, "private_x", dss->x); + + return kc; +} + +static char *dss_invalid(ssh_key *key, unsigned flags) +{ + /* No validity criterion will stop us from using a DSA key at all */ + return NULL; +} + +static bool dss_verify(ssh_key *key, ptrlen sig, ptrlen data) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + BinarySource src[1]; + unsigned char hash[20]; + bool toret; + + if (!dss->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, dss->q); + invalid |= mp_cmp_hs(s, dss->q); + if (invalid) { + mp_free(r); + mp_free(s); + return false; + } + + /* + * Step 1. w <- s^-1 mod q. + */ + mp_int *w = mp_invert(s, dss->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, dss->q); + + /* + * Step 3. u2 <- r * w mod q. + */ + mp_int *u2 = mp_modmul(r, w, dss->q); + + /* + * Step 4. v <- (g^u1 * y^u2 mod p) mod q. + */ + mp_int *gu1p = mp_modpow(dss->g, u1, dss->p); + mp_int *yu2p = mp_modpow(dss->y, u2, dss->p); + mp_int *gu1yu2p = mp_modmul(gu1p, yu2p, dss->p); + mp_int *v = mp_mod(gu1yu2p, dss->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 dss_public_blob(ssh_key *key, BinarySink *bs) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + + put_stringz(bs, "ssh-dss"); + put_mp_ssh2(bs, dss->p); + put_mp_ssh2(bs, dss->q); + put_mp_ssh2(bs, dss->g); + put_mp_ssh2(bs, dss->y); +} + +static void dss_private_blob(ssh_key *key, BinarySink *bs) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + + put_mp_ssh2(bs, dss->x); +} + +static ssh_key *dss_new_priv(const ssh_keyalg *self, ptrlen pub, ptrlen priv) +{ + BinarySource src[1]; + ssh_key *sshk; + struct dss_key *dss; + ptrlen hash; + unsigned char digest[20]; + mp_int *ytest; + + sshk = dss_new_pub(self, pub); + if (!sshk) + return NULL; + + dss = container_of(sshk, struct dss_key, sshk); + BinarySource_BARE_INIT_PL(src, priv); + dss->x = get_mp_ssh2(src); + if (get_err(src)) { + dss_freekey(&dss->sshk); + return NULL; + } + + /* + * Check the obsolete hash in the old DSS key format. + */ + hash = get_string(src); + if (hash.len == 20) { + ssh_hash *h = ssh_hash_new(&ssh_sha1); + put_mp_ssh2(h, dss->p); + put_mp_ssh2(h, dss->q); + put_mp_ssh2(h, dss->g); + ssh_hash_final(h, digest); + if (!smemeq(hash.ptr, digest, 20)) { + dss_freekey(&dss->sshk); + return NULL; + } + } + + /* + * Now ensure g^x mod p really is y. + */ + ytest = mp_modpow(dss->g, dss->x, dss->p); + if (!mp_cmp_eq(ytest, dss->y)) { + mp_free(ytest); + dss_freekey(&dss->sshk); + return NULL; + } + mp_free(ytest); + + return &dss->sshk; +} + +static ssh_key *dss_new_priv_openssh(const ssh_keyalg *self, + BinarySource *src) +{ + struct dss_key *dss; + + dss = snew(struct dss_key); + dss->sshk.vt = &ssh_dss; + + dss->p = get_mp_ssh2(src); + dss->q = get_mp_ssh2(src); + dss->g = get_mp_ssh2(src); + dss->y = get_mp_ssh2(src); + dss->x = get_mp_ssh2(src); + + if (get_err(src) || + mp_eq_integer(dss->q, 0) || mp_eq_integer(dss->p, 0)) { + /* Invalid key. */ + dss_freekey(&dss->sshk); + return NULL; + } + + return &dss->sshk; +} + +static void dss_openssh_blob(ssh_key *key, BinarySink *bs) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + + put_mp_ssh2(bs, dss->p); + put_mp_ssh2(bs, dss->q); + put_mp_ssh2(bs, dss->g); + put_mp_ssh2(bs, dss->y); + put_mp_ssh2(bs, dss->x); +} + +static int dss_pubkey_bits(const ssh_keyalg *self, ptrlen pub) +{ + ssh_key *sshk; + struct dss_key *dss; + int ret; + + sshk = dss_new_pub(self, pub); + if (!sshk) + return -1; + + dss = container_of(sshk, struct dss_key, sshk); + ret = mp_get_nbits(dss->p); + dss_freekey(&dss->sshk); + + return ret; +} + +mp_int *dss_gen_k(const char *id_string, mp_int *modulus, + mp_int *private_key, + unsigned char *digest, int digest_len) +{ + /* + * The basic DSS signing algorithm is: + * + * - invent a random k between 1 and q-1 (exclusive). + * - Compute r = (g^k mod p) mod q. + * - Compute s = k^-1 * (hash + x*r) mod q. + * + * This has the dangerous properties that: + * + * - if an attacker in possession of the public key _and_ the + * signature (for example, the host you just authenticated + * to) can guess your k, he can reverse the computation of s + * and work out x = r^-1 * (s*k - hash) mod q. That is, he + * can deduce the private half of your key, and masquerade + * as you for as long as the key is still valid. + * + * - since r is a function purely of k and the public key, if + * the attacker only has a _range of possibilities_ for k + * it's easy for him to work through them all and check each + * one against r; he'll never be unsure of whether he's got + * the right one. + * + * - if you ever sign two different hashes with the same k, it + * will be immediately obvious because the two signatures + * will have the same r, and moreover an attacker in + * possession of both signatures (and the public key of + * course) can compute k = (hash1-hash2) * (s1-s2)^-1 mod q, + * and from there deduce x as before. + * + * - the Bleichenbacher attack on DSA makes use of methods of + * generating k which are significantly non-uniformly + * distributed; in particular, generating a 160-bit random + * number and reducing it mod q is right out. + * + * For this reason we must be pretty careful about how we + * generate our k. Since this code runs on Windows, with no + * particularly good system entropy sources, we can't trust our + * RNG itself to produce properly unpredictable data. Hence, we + * use a totally different scheme instead. + * + * What we do is to take a SHA-512 (_big_) hash of the private + * key x, and then feed this into another SHA-512 hash that + * also includes the message hash being signed. That is: + * + * proto_k = SHA512 ( SHA512(x) || SHA160(message) ) + * + * This number is 512 bits long, so reducing it mod q won't be + * noticeably non-uniform. So + * + * k = proto_k mod q + * + * This has the interesting property that it's _deterministic_: + * signing the same hash twice with the same key yields the + * same signature. + * + * Despite this determinism, it's still not predictable to an + * attacker, because in order to repeat the SHA-512 + * construction that created it, the attacker would have to + * know the private key value x - and by assumption he doesn't, + * because if he knew that he wouldn't be attacking k! + * + * (This trick doesn't, _per se_, protect against reuse of k. + * Reuse of k is left to chance; all it does is prevent + * _excessively high_ chances of reuse of k due to entropy + * problems.) + * + * Thanks to Colin Plumb for the general idea of using x to + * ensure k is hard to guess, and to the Cambridge University + * Computer Security Group for helping to argue out all the + * fine details. + */ + 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 dss_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs) +{ + struct dss_key *dss = container_of(key, struct dss_key, sshk); + unsigned char digest[20]; + int i; + + hash_simple(&ssh_sha1, data, digest); + + mp_int *k = dss_gen_k("DSA deterministic k generator", dss->q, dss->x, + digest, sizeof(digest)); + mp_int *kinv = mp_invert(k, dss->q); /* k^-1 mod q */ + + /* + * Now we have k, so just go ahead and compute the signature. + */ + mp_int *gkp = mp_modpow(dss->g, k, dss->p); /* g^k mod p */ + mp_int *r = mp_mod(gkp, dss->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(dss->x, r); + mp_int *hxr = mp_add(xr, hash); /* hash + x*r */ + mp_int *s = mp_modmul(kinv, hxr, dss->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); +} + +const ssh_keyalg ssh_dss = { + .new_pub = dss_new_pub, + .new_priv = dss_new_priv, + .new_priv_openssh = dss_new_priv_openssh, + .freekey = dss_freekey, + .invalid = dss_invalid, + .sign = dss_sign, + .verify = dss_verify, + .public_blob = dss_public_blob, + .private_blob = dss_private_blob, + .openssh_blob = dss_openssh_blob, + .cache_str = dss_cache_str, + .components = dss_components, + .pubkey_bits = dss_pubkey_bits, + .ssh_id = "ssh-dss", + .cache_id = "dss", +}; diff --git a/0.77_My_PuTTY/sshdssg.c b/0.77_My_PuTTY/sshdssg.c new file mode 100644 index 0000000..c6bfa0a --- /dev/null +++ b/0.77_My_PuTTY/sshdssg.c @@ -0,0 +1,103 @@ +/* + * DSS key generation. + */ + +#include "misc.h" +#include "ssh.h" +#include "sshkeygen.h" +#include "mpint.h" + +int dsa_generate(struct dss_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_dss; + + 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/0.77_My_PuTTY/sshecc.c b/0.77_My_PuTTY/sshecc.c new file mode 100644 index 0000000..fe72c91 --- /dev/null +++ b/0.77_My_PuTTY/sshecc.c @@ -0,0 +1,1706 @@ +/* + * Elliptic-curve crypto module for PuTTY + * Implements the three required curves, no optional curves + * + * NOTE: Only curves on prime field are handled by the maths functions + * in Weierstrass form using Jacobian co-ordinates. + * + * Montgomery form curves are supported for DH. (Curve25519) + * + * Edwards form curves are supported for DSA. (Ed25519, Ed448) + */ + +/* + * References: + * + * Elliptic curves in SSH are specified in RFC 5656: + * http://tools.ietf.org/html/rfc5656 + * + * That specification delegates details of public key formatting and a + * lot of underlying mechanism to SEC 1: + * http://www.secg.org/sec1-v2.pdf + * + * Montgomery maths from: + * Handbook of elliptic and hyperelliptic curve cryptography, Chapter 13 + * http://cs.ucsb.edu/~koc/ccs130h/2013/EllipticHyperelliptic-CohenFrey.pdf + * + * Curve25519 spec from libssh (with reference to other things in the + * libssh code): + * https://git.libssh.org/users/aris/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt + * + * Edwards DSA: + * http://ed25519.cr.yp.to/ed25519-20110926.pdf + */ + +#include +#include + +#include "ssh.h" +#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; + + /* 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.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) + strbuf_catf(sb, "%s,", curve_name); + + char *hx = mp_get_hex(x); + char *hy = mp_get_hex(y); + strbuf_catf(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 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 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 = dss_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 const struct ecsign_extra sign_extra_ed25519 = { + ec_ed25519, &ssh_sha512, + NULL, 0, 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, + .cache_str = eddsa_cache_str, + .components = eddsa_components, + .pubkey_bits = ec_shared_pubkey_bits, + .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, 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, + .cache_str = eddsa_cache_str, + .components = eddsa_components, + .pubkey_bits = ec_shared_pubkey_bits, + .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), +}; +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, + .cache_str = ecdsa_cache_str, + .components = ecdsa_components, + .pubkey_bits = ec_shared_pubkey_bits, + .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), +}; +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, + .cache_str = ecdsa_cache_str, + .components = ecdsa_components, + .pubkey_bits = ec_shared_pubkey_bits, + .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), +}; +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, + .cache_str = ecdsa_cache_str, + .components = ecdsa_components, + .pubkey_bits = ec_shared_pubkey_bits, + .ssh_id = "ecdsa-sha2-nistp521", + .cache_id = "ecdsa-sha2-nistp521", + .extra = &sign_extra_nistp521, +}; + +/* ---------------------------------------------------------------------- + * Exposed ECDH interface + */ + +struct eckex_extra { + struct ec_curve *(*curve)(void); + void (*setup)(ecdh_key *dh); + void (*cleanup)(ecdh_key *dh); + void (*getpublic)(ecdh_key *dh, BinarySink *bs); + mp_int *(*getkey)(ecdh_key *dh, ptrlen remoteKey); +}; + +struct ecdh_key { + const struct eckex_extra *extra; + const struct ec_curve *curve; + mp_int *private; + union { + WeierstrassPoint *w_public; + MontgomeryPoint *m_public; + }; +}; + +const char *ssh_ecdhkex_curve_textname(const ssh_kex *kex) +{ + const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; + struct ec_curve *curve = extra->curve(); + return curve->textname; +} + +static void ssh_ecdhkex_w_setup(ecdh_key *dh) +{ + mp_int *one = mp_from_integer(1); + dh->private = mp_random_in_range(one, dh->curve->w.G_order); + mp_free(one); + + dh->w_public = ecc_weierstrass_multiply(dh->curve->w.G, dh->private); +} + +static void ssh_ecdhkex_m_setup(ecdh_key *dh) +{ + strbuf *bytes = strbuf_new_nm(); + random_read(strbuf_append(bytes, dh->curve->fieldBytes), + dh->curve->fieldBytes); + + dh->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(dh->private, dh->curve->fieldBits); + mp_set_bit(dh->private, dh->curve->fieldBits - 1, 1); + + /* Clear a curve-specific number of low bits */ + for (unsigned bit = 0; bit < dh->curve->m.log2_cofactor; bit++) + mp_set_bit(dh->private, bit, 0); + + strbuf_free(bytes); + + dh->m_public = ecc_montgomery_multiply(dh->curve->m.G, dh->private); +} + +ecdh_key *ssh_ecdhkex_newkey(const ssh_kex *kex) +{ + const struct eckex_extra *extra = (const struct eckex_extra *)kex->extra; + const struct ec_curve *curve = extra->curve(); + + ecdh_key *dh = snew(ecdh_key); + dh->extra = extra; + dh->curve = curve; + dh->extra->setup(dh); + return dh; +} + +static void ssh_ecdhkex_w_getpublic(ecdh_key *dh, BinarySink *bs) +{ + put_wpoint(bs, dh->w_public, dh->curve, true); +} + +static void ssh_ecdhkex_m_getpublic(ecdh_key *dh, BinarySink *bs) +{ + mp_int *x; + ecc_montgomery_get_affine(dh->m_public, &x); + for (size_t i = 0; i < dh->curve->fieldBytes; ++i) + put_byte(bs, mp_get_byte(x, i)); + mp_free(x); +} + +void ssh_ecdhkex_getpublic(ecdh_key *dh, BinarySink *bs) +{ + dh->extra->getpublic(dh, bs); +} + +static mp_int *ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey) +{ + WeierstrassPoint *remote_p = ecdsa_decode(remoteKey, dh->curve); + if (!remote_p) + return NULL; + + if (ecc_weierstrass_is_identity(remote_p)) { + /* Not a sensible Diffie-Hellman input value */ + ecc_weierstrass_point_free(remote_p); + return NULL; + } + + WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dh->private); + + mp_int *x; + ecc_weierstrass_get_affine(p, &x, NULL); + + ecc_weierstrass_point_free(remote_p); + ecc_weierstrass_point_free(p); + + return x; +} + +static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey) +{ + 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, dh->curve->fieldBits); + + MontgomeryPoint *remote_p = ecc_montgomery_point_new( + dh->curve->m.mc, remote_x); + mp_free(remote_x); + + MontgomeryPoint *p = ecc_montgomery_multiply(remote_p, dh->private); + + if (ecc_montgomery_is_identity(p)) { + ecc_montgomery_point_free(remote_p); + ecc_montgomery_point_free(p); + return NULL; + } + + 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 < dh->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); + + return x; +} + +mp_int *ssh_ecdhkex_getkey(ecdh_key *dh, ptrlen remoteKey) +{ + return dh->extra->getkey(dh, remoteKey); +} + +static void ssh_ecdhkex_w_cleanup(ecdh_key *dh) +{ + ecc_weierstrass_point_free(dh->w_public); +} + +static void ssh_ecdhkex_m_cleanup(ecdh_key *dh) +{ + ecc_montgomery_point_free(dh->m_public); +} + +void ssh_ecdhkex_freekey(ecdh_key *dh) +{ + mp_free(dh->private); + dh->extra->cleanup(dh); + sfree(dh); +} + +static const struct eckex_extra kex_extra_curve25519 = { + ec_curve25519, + ssh_ecdhkex_m_setup, + ssh_ecdhkex_m_cleanup, + ssh_ecdhkex_m_getpublic, + ssh_ecdhkex_m_getkey, +}; +const ssh_kex ssh_ec_kex_curve25519 = { + "curve25519-sha256", NULL, KEXTYPE_ECDH, + &ssh_sha256, &kex_extra_curve25519, +}; +/* Pre-RFC alias */ +const ssh_kex ssh_ec_kex_curve25519_libssh = { + "curve25519-sha256@libssh.org", NULL, KEXTYPE_ECDH, + &ssh_sha256, &kex_extra_curve25519, +}; + +static const struct eckex_extra kex_extra_curve448 = { + ec_curve448, + ssh_ecdhkex_m_setup, + ssh_ecdhkex_m_cleanup, + ssh_ecdhkex_m_getpublic, + ssh_ecdhkex_m_getkey, +}; +const ssh_kex ssh_ec_kex_curve448 = { + "curve448-sha512", NULL, KEXTYPE_ECDH, + &ssh_sha512, &kex_extra_curve448, +}; + +static const struct eckex_extra kex_extra_nistp256 = { + ec_p256, + ssh_ecdhkex_w_setup, + ssh_ecdhkex_w_cleanup, + ssh_ecdhkex_w_getpublic, + ssh_ecdhkex_w_getkey, +}; +const ssh_kex ssh_ec_kex_nistp256 = { + "ecdh-sha2-nistp256", NULL, KEXTYPE_ECDH, + &ssh_sha256, &kex_extra_nistp256, +}; + +static const struct eckex_extra kex_extra_nistp384 = { + ec_p384, + ssh_ecdhkex_w_setup, + ssh_ecdhkex_w_cleanup, + ssh_ecdhkex_w_getpublic, + ssh_ecdhkex_w_getkey, +}; +const ssh_kex ssh_ec_kex_nistp384 = { + "ecdh-sha2-nistp384", NULL, KEXTYPE_ECDH, + &ssh_sha384, &kex_extra_nistp384, +}; + +static const struct eckex_extra kex_extra_nistp521 = { + ec_p521, + ssh_ecdhkex_w_setup, + ssh_ecdhkex_w_cleanup, + ssh_ecdhkex_w_getpublic, + ssh_ecdhkex_w_getkey, +}; +const ssh_kex ssh_ec_kex_nistp521 = { + "ecdh-sha2-nistp521", NULL, KEXTYPE_ECDH, + &ssh_sha512, &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 }; + +/* ---------------------------------------------------------------------- + * 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/0.77_My_PuTTY/sshecdsag.c b/0.77_My_PuTTY/sshecdsag.c new file mode 100644 index 0000000..91cbecd --- /dev/null +++ b/0.77_My_PuTTY/sshecdsag.c @@ -0,0 +1,37 @@ +/* + * 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/0.77_My_PuTTY/sshgss.h b/0.77_My_PuTTY/sshgss.h new file mode 100644 index 0000000..b1c04a3 --- /dev/null +++ b/0.77_My_PuTTY/sshgss.h @@ -0,0 +1,217 @@ +#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 wingss.c or sshgssc.c */ + +struct ssh_gss_library; + +/* + * Prepare a collection of GSSAPI libraries for use in a single SSH + * connection. Returns a structure containing a list of libraries, + * with their ids (see struct ssh_gss_library below) filled in so + * that the client can go through them in the SSH user's preferred + * order. + * + * Must always return non-NULL. (Even if no libraries are available, + * it must return an empty structure.) + * + * The free function cleans up the structure, and its associated + * libraries (if any). + */ +struct ssh_gss_liblist { + struct ssh_gss_library *libraries; + int nlibraries; +}; +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf); +void ssh_gss_cleanup(struct ssh_gss_liblist *list); + +/* + * Fills in buf with a string describing the GSSAPI mechanism in + * use. buf->data is not dynamically allocated. + */ +typedef Ssh_gss_stat (*t_ssh_gss_indicate_mech)(struct ssh_gss_library *lib, + Ssh_gss_buf *buf); + +/* + * Converts a name such as a hostname into a GSSAPI internal form, + * which is placed in "out". The result should be freed by + * ssh_gss_release_name(). + */ +typedef Ssh_gss_stat (*t_ssh_gss_import_name)(struct ssh_gss_library *lib, + char *in, Ssh_gss_name *out); + +/* + * Frees the contents of an Ssh_gss_name structure filled in by + * ssh_gss_import_name(). + */ +typedef Ssh_gss_stat (*t_ssh_gss_release_name)(struct ssh_gss_library *lib, + Ssh_gss_name *name); + +/* + * The main GSSAPI security context setup function. The "out" + * parameter will need to be freed by ssh_gss_free_tok. + */ +typedef Ssh_gss_stat (*t_ssh_gss_init_sec_context) + (struct ssh_gss_library *lib, + Ssh_gss_ctx *ctx, Ssh_gss_name name, int delegate, + Ssh_gss_buf *in, Ssh_gss_buf *out, 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/0.77_My_PuTTY/sshgssc.c b/0.77_My_PuTTY/sshgssc.c new file mode 100644 index 0000000..a1aca28 --- /dev/null +++ b/0.77_My_PuTTY/sshgssc.c @@ -0,0 +1,288 @@ +#include "putty.h" + +#include +#include +#include "sshgssc.h" +#include "misc.h" + +#ifndef NO_GSSAPI + +static Ssh_gss_stat ssh_gssapi_indicate_mech(struct ssh_gss_library *lib, + Ssh_gss_buf *mech) +{ + /* Copy constant into mech */ + mech->length = GSS_MECH_KRB5->length; + mech->value = GSS_MECH_KRB5->elements; + return SSH_GSS_OK; +} + +static Ssh_gss_stat ssh_gssapi_import_name(struct ssh_gss_library *lib, + char *host, + Ssh_gss_name *srv_name) +{ + struct gssapi_functions *gss = &lib->u.gssapi; + OM_uint32 min_stat,maj_stat; + gss_buffer_desc host_buf; + char *pStr; + + pStr = dupcat("host@", host); + + 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, + GSS_C_NO_NAME, + &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/0.77_My_PuTTY/sshgssc.h b/0.77_My_PuTTY/sshgssc.h new file mode 100644 index 0000000..07fac00 --- /dev/null +++ b/0.77_My_PuTTY/sshgssc.h @@ -0,0 +1,24 @@ +#ifndef PUTTY_SSHGSSC_H +#define PUTTY_SSHGSSC_H +#include "putty.h" +#ifndef NO_GSSAPI + +#include "pgssapi.h" +#include "sshgss.h" + +typedef struct gssapi_ssh_gss_ctx { + OM_uint32 maj_stat; + OM_uint32 min_stat; + gss_ctx_id_t ctx; + 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/0.77_My_PuTTY/sshhmac.c b/0.77_My_PuTTY/sshhmac.c new file mode 100644 index 0000000..7b83aa7 --- /dev/null +++ b/0.77_My_PuTTY/sshhmac.c @@ -0,0 +1,257 @@ +/* + * 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(); + strbuf_catf(ctx->text_name, "HMAC-%s%s", + ctx->hashalg->text_basename, extra->suffix); + if (extra->annotation || ctx->hashalg->annotation) { + strbuf_catf(ctx->text_name, " ("); + const char *sep = ""; + if (extra->annotation) { + strbuf_catf(ctx->text_name, "%s%s", sep, extra->annotation); + sep = ", "; + } + if (ctx->hashalg->annotation) { + strbuf_catf(ctx->text_name, "%s%s", sep, ctx->hashalg->annotation); + sep = ", "; + } + strbuf_catf(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_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, + .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, + .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, + .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, + .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, + .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, + .text_name = hmac_text_name, + .name = "hmac-sha1-96", + .len = 12, + .keylen = 16, + .extra = &ssh_hmac_sha1_96_buggy_extra, +}; diff --git a/0.77_My_PuTTY/sshkeygen.h b/0.77_My_PuTTY/sshkeygen.h new file mode 100644 index 0000000..d94c611 --- /dev/null +++ b/0.77_My_PuTTY/sshkeygen.h @@ -0,0 +1,292 @@ +/* + * 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 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 dss_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/0.77_My_PuTTY/sshmac.c b/0.77_My_PuTTY/sshmac.c new file mode 100644 index 0000000..c117d90 --- /dev/null +++ b/0.77_My_PuTTY/sshmac.c @@ -0,0 +1,43 @@ +/* + * 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/0.77_My_PuTTY/sshmd5.c b/0.77_My_PuTTY/sshmd5.c new file mode 100644 index 0000000..c6deef0 --- /dev/null +++ b/0.77_My_PuTTY/sshmd5.c @@ -0,0 +1,245 @@ +/* + * 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/0.77_My_PuTTY/sshnogss.c b/0.77_My_PuTTY/sshnogss.c new file mode 100644 index 0000000..fa4ac65 --- /dev/null +++ b/0.77_My_PuTTY/sshnogss.c @@ -0,0 +1,19 @@ +#include "putty.h" +#ifndef NO_GSSAPI + +/* For platforms not supporting GSSAPI */ + +struct ssh_gss_liblist *ssh_gss_setup(Conf *conf) +{ + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist *); + list->libraries = NULL; + list->nlibraries = 0; + return list; +} + +void ssh_gss_cleanup(struct ssh_gss_liblist *list) +{ + sfree(list); +} + +#endif /* NO_GSSAPI */ diff --git a/0.77_My_PuTTY/sshppl.h b/0.77_My_PuTTY/sshppl.h new file mode 100644 index 0000000..049b939 --- /dev/null +++ b/0.77_My_PuTTY/sshppl.h @@ -0,0 +1,174 @@ +/* + * 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); + bool (*want_user_input)(PacketProtocolLayer *ppl); + void (*got_user_input)(PacketProtocolLayer *ppl); + void (*reconfigure)(PacketProtocolLayer *ppl, Conf *conf); + size_t (*queued_data_size)(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; + + /* Bufchain of keyboard input from the user, for login prompts and + * similar. */ + bufchain *user_input; + + /* Logging and error-reporting facilities. */ + LogContext *logctx; + Seat *seat; /* for dialog boxes, session output etc */ + 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 bool ssh_ppl_want_user_input(PacketProtocolLayer *ppl) +{ return ppl->vt->want_user_input(ppl); } +static inline void ssh_ppl_got_user_input(PacketProtocolLayer *ppl) +{ ppl->vt->got_user_input(ppl); } +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); } + +/* 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); + +PacketProtocolLayer *ssh1_login_new( + Conf *conf, const char *host, int port, + PacketProtocolLayer *successor_layer); +PacketProtocolLayer *ssh1_connection_new( + Ssh *ssh, Conf *conf, 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, const char *fullhostname, + Filename *keyfile, 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); +PacketProtocolLayer *ssh2_connection_new( + Ssh *ssh, ssh_sharing_state *connshare, bool is_simple, + Conf *conf, const char *peer_verstring, 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 ssh2transport.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); + +/* 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/0.77_My_PuTTY/sshprime.c b/0.77_My_PuTTY/sshprime.c new file mode 100644 index 0000000..b631753 --- /dev/null +++ b/0.77_My_PuTTY/sshprime.c @@ -0,0 +1,762 @@ +/* + * 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/0.77_My_PuTTY/sshprng.c b/0.77_My_PuTTY/sshprng.c new file mode 100644 index 0000000..eec8a7d --- /dev/null +++ b/0.77_My_PuTTY/sshprng.c @@ -0,0 +1,287 @@ +/* + * sshprng.c: 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/0.77_My_PuTTY/sshpubk.c b/0.77_My_PuTTY/sshpubk.c new file mode 100644 index 0000000..d942d75 --- /dev/null +++ b/0.77_My_PuTTY/sshpubk.c @@ -0,0 +1,1982 @@ +/* + * 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_new_nm(); + put_datapl(buf, 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_dss, + &ssh_ecdsa_nistp256, + &ssh_ecdsa_nistp384, + &ssh_ecdsa_nistp521, + &ssh_ecdsa_ed25519, + &ssh_ecdsa_ed448, +}; +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)); +} + +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 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 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; +} + +static void base64_encode_s(BinarySink *bs, const unsigned char *data, + int datalen, int cpl) +{ + int linelen = 0; + char out[4]; + int n, i; + + while (datalen > 0) { + n = (datalen < 3 ? datalen : 3); + base64_encode_atom(data, n, out); + data += n; + datalen -= n; + for (i = 0; i < 4; i++) { + if (linelen >= cpl) { + linelen = 0; + put_byte(bs, '\n'); + } + put_byte(bs, out[i]); + linelen++; + } + } + put_byte(bs, '\n'); +} + +void base64_encode(FILE *fp, const unsigned char *data, int datalen, int cpl) +{ + stdio_sink ss; + stdio_sink_init(&ss, fp); + base64_encode_s(BinarySink_UPCAST(&ss), data, datalen, cpl); +} + +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(); + strbuf_catf(out, "PuTTY-User-Key-File-%u: %s\n", + params.fmt_version, ssh_key_ssh_id(key->key)); + strbuf_catf(out, "Encryption: %s\n", cipherstr); + strbuf_catf(out, "Comment: %s\n", key->comment); + strbuf_catf(out, "Public-Lines: %d\n", base64_lines(pub_blob->len)); + base64_encode_s(BinarySink_UPCAST(out), pub_blob->u, pub_blob->len, 64); + if (params.fmt_version == 3 && ciphertype->keylen != 0) { + strbuf_catf(out, "Key-Derivation: %s\n", + params.argon2_flavour == Argon2d ? "Argon2d" : + params.argon2_flavour == Argon2i ? "Argon2i" : "Argon2id"); + strbuf_catf(out, "Argon2-Memory: %"PRIu32"\n", params.argon2_mem); + assert(!params.argon2_passes_auto); + strbuf_catf(out, "Argon2-Passes: %"PRIu32"\n", params.argon2_passes); + strbuf_catf(out, "Argon2-Parallelism: %"PRIu32"\n", + params.argon2_parallelism); + strbuf_catf(out, "Argon2-Salt: "); + for (size_t i = 0; i < passphrase_salt->len; i++) + strbuf_catf(out, "%02x", passphrase_salt->u[i]); + strbuf_catf(out, "\n"); + } + strbuf_catf(out, "Private-Lines: %d\n", base64_lines(priv_encrypted_len)); + base64_encode_s(BinarySink_UPCAST(out), + priv_blob_encrypted, priv_encrypted_len, 64); + strbuf_catf(out, "Private-MAC: "); + for (i = 0; i < macalg->len; i++) + strbuf_catf(out, "%02x", priv_mac[i]); + strbuf_catf(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++) + strbuf_catf(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(); + + /* + * 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); + strbuf_catf(sb, "%.*s %d ", PTRLEN_PRINTF(algname), bits); + } else { + strbuf_catf(sb, "%.*s ", PTRLEN_PRINTF(algname)); + } + } + + switch (fptype) { + case SSH_FPTYPE_MD5: + ssh2_fingerprint_blob_md5(blob, sb); + break; + case SSH_FPTYPE_SHA256: + ssh2_fingerprint_blob_sha256(blob, sb); + break; + } + + return strbuf_to_str(sb); +} + +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_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"); + } +} + +key_components *key_components_new(void) +{ + key_components *kc = snew(key_components); + kc->ncomponents = 0; + kc->componentsize = 0; + kc->components = NULL; + return kc; +} + +void key_components_add_text(key_components *kc, + const char *name, const char *value) +{ + sgrowarray(kc->components, kc->componentsize, kc->ncomponents); + size_t n = kc->ncomponents++; + kc->components[n].name = dupstr(name); + kc->components[n].is_mp_int = false; + kc->components[n].text = dupstr(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].is_mp_int = true; + kc->components[n].mp = mp_copy(value); +} + +void key_components_free(key_components *kc) +{ + for (size_t i = 0; i < kc->ncomponents; i++) { + sfree(kc->components[i].name); + if (kc->components[i].is_mp_int) { + mp_free(kc->components[i].mp); + } else { + smemclr(kc->components[i].text, strlen(kc->components[i].text)); + sfree(kc->components[i].text); + } + } + sfree(kc->components); + sfree(kc); +} diff --git a/0.77_My_PuTTY/sshrand.c b/0.77_My_PuTTY/sshrand.c new file mode 100644 index 0000000..11c5d7e --- /dev/null +++ b/0.77_My_PuTTY/sshrand.c @@ -0,0 +1,162 @@ +/* + * sshrand.c: manage the global live PRNG instance. + */ + +#include "putty.h" +#include "ssh.h" +#include "storage.h" +#include + +#ifdef MOD_PERSO +int GetRandomActiveFlag() ; +#endif + +/* 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) +{ +#ifdef MOD_PERSO + if( !GetRandomActiveFlag() ) { return ; } +#endif + 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) +{ +#ifndef MOD_RECONNECT + assert(random_active > 0); +#endif + 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) +{ +#ifndef MOD_RECONNECT + assert(random_active > 0); +#endif + return prng_seed_bits(global_prng); +} + +#endif /* FUZZING */ diff --git a/0.77_My_PuTTY/sshrsa.c b/0.77_My_PuTTY/sshrsa.c new file mode 100644 index 0000000..dadc2d8 --- /dev/null +++ b/0.77_My_PuTTY/sshrsa.c @@ -0,0 +1,1109 @@ +/* + * 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; +} + +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(); + strbuf_catf(out, "%"SIZEu" ", mp_get_nbits(key->modulus)); + for (i = 0; i < 16; i++) + strbuf_catf(out, "%s%02x", i ? ":" : "", digest[i]); + if (key->comment) + strbuf_catf(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 **ret = snewn(SSH_N_FPTYPES, char *); + for (unsigned i = 0; i < SSH_N_FPTYPES; i++) + ret[i] = NULL; + ret[SSH_FPTYPE_MD5] = rsa_ssh1_fingerprint(key); + return ret; +} + +/* + * 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 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 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, \ + .cache_str = rsa2_cache_str, \ + .components = rsa2_components, \ + .pubkey_bits = rsa2_pubkey_bits, \ + .cache_id = "rsa2" + +const ssh_keyalg ssh_rsa = { + COMMON_KEYALG_FIELDS, + .ssh_id = "ssh-rsa", + .supported_flags = SSH_AGENT_RSA_SHA2_256 | SSH_AGENT_RSA_SHA2_512, + .extra = &rsa_extra, +}; + +const ssh_keyalg ssh_rsa_sha256 = { + COMMON_KEYALG_FIELDS, + .ssh_id = "rsa-sha2-256", + .supported_flags = 0, + .extra = &rsa_sha256_extra, +}; + +const ssh_keyalg ssh_rsa_sha512 = { + COMMON_KEYALG_FIELDS, + .ssh_id = "rsa-sha2-512", + .supported_flags = 0, + .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 = { + "rsa1024-sha1", NULL, KEXTYPE_RSA, + &ssh_sha1, &ssh_rsa_kex_extra_sha1, +}; + +static const ssh_kex ssh_rsa_kex_sha256 = { + "rsa2048-sha256", NULL, KEXTYPE_RSA, + &ssh_sha256, &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/0.77_My_PuTTY/sshrsag.c b/0.77_My_PuTTY/sshrsag.c new file mode 100644 index 0000000..04b59f0 --- /dev/null +++ b/0.77_My_PuTTY/sshrsag.c @@ -0,0 +1,292 @@ +/* + * 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/0.77_My_PuTTY/sshserver.c b/0.77_My_PuTTY/sshserver.c new file mode 100644 index 0000000..6aa8678 --- /dev/null +++ b/0.77_My_PuTTY/sshserver.c @@ -0,0 +1,591 @@ +/* + * Top-level code for SSH server implementation. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.h" +#include "sshppl.h" +#include "sshchan.h" +#include "sshserver.h" +#ifndef NO_GSSAPI +#include "sshgssc.h" +#include "sshgss.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 int server_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) { return 1; } +static int server_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) { return 1; } + +static const SeatVtable server_seat_vt = { + .output = nullseat_output, + .eof = nullseat_eof, + .get_userpass_input = nullseat_get_userpass_input, + .notify_remote_exit = nullseat_notify_remote_exit, + .connection_fatal = nullseat_connection_fatal, + .update_specials_menu = nullseat_update_specials_menu, + .get_ttymode = nullseat_get_ttymode, + .set_busy_status = nullseat_set_busy_status, + .verify_ssh_host_key = nullseat_verify_ssh_host_key, + .confirm_weak_crypto_primitive = server_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = server_confirm_weak_cached_hostkey, + .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, + .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, const char *error_msg, int error_code, + bool calling_back) +{ + server *srv = container_of(plug, server, plug); + if (error_msg) { + 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_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; +} + +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->user_input = &srv->dummy_user_input; + ppl->logctx = srv->logctx; + ppl->ssh = &srv->ssh; + ppl->seat = &srv->seat; + 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); + 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->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->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->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/0.77_My_PuTTY/sshserver.h b/0.77_My_PuTTY/sshserver.h new file mode 100644 index 0000000..b99d345 --- /dev/null +++ b/0.77_My_PuTTY/sshserver.h @@ -0,0 +1,143 @@ +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; +}; + +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 uxsftpserver.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/0.77_My_PuTTY/sshsh256.c b/0.77_My_PuTTY/sshsh256.c new file mode 100644 index 0000000..f8d99ac --- /dev/null +++ b/0.77_My_PuTTY/sshsh256.c @@ -0,0 +1,940 @@ +/* + * SHA-256 algorithm as described at + * + * http://csrc.nist.gov/cryptval/shs.html + */ + +#include "ssh.h" +#include + +/* + * Start by deciding whether we can support hardware SHA at all. + */ +#define HW_SHA256_NONE 0 +#define HW_SHA256_NI 1 +#define HW_SHA256_NEON 2 + +#ifdef _FORCE_SHA_NI +# define HW_SHA256 HW_SHA256_NI +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__x86_64__) || defined(__i386)) +# define HW_SHA256 HW_SHA256_NI +# endif +#elif defined(__GNUC__) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && \ + (defined(__x86_64__) || defined(__i386)) +# define HW_SHA256 HW_SHA256_NI +# endif +#elif defined (_MSC_VER) +# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 +# define HW_SHA256 HW_SHA256_NI +# endif +#endif + +#ifdef _FORCE_SHA_NEON +# define HW_SHA256 HW_SHA256_NEON +#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + /* Arm can potentially support both endiannesses, but this code + * hasn't been tested on anything but little. If anyone wants to + * run big-endian, they'll need to fix it first. */ +#elif defined __ARM_FEATURE_CRYPTO + /* If the Arm crypto extension is available already, we can + * support NEON SHA without having to enable anything by hand */ +# define HW_SHA256 HW_SHA256_NEON +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__aarch64__)) + /* clang can enable the crypto extension in AArch64 using + * __attribute__((target)) */ +# define HW_SHA256 HW_SHA256_NEON +# define USE_CLANG_ATTR_TARGET_AARCH64 +# endif +#elif defined _MSC_VER + /* Visual Studio supports the crypto extension when targeting + * AArch64, but as of VS2017, the AArch32 header doesn't quite + * manage it (declaring the shae/shad intrinsics without a round + * key operand). */ +# if defined _M_ARM64 +# define HW_SHA256 HW_SHA256_NEON +# if defined _M_ARM64 +# define USE_ARM64_NEON_H /* unusual header name in this case */ +# endif +# endif +#endif + +#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA256 +# undef HW_SHA256 +# define HW_SHA256 HW_SHA256_NONE +#endif + +/* + * The actual query function that asks if hardware acceleration is + * available. + */ +static bool sha256_hw_available(void); + +/* + * The top-level selection function, caching the results of + * sha256_hw_available() so it only has to run once. + */ +static bool sha256_hw_available_cached(void) +{ + static bool initialised = false; + static bool hw_available; + if (!initialised) { + hw_available = sha256_hw_available(); + initialised = true; + } + return hw_available; +} + +static ssh_hash *sha256_select(const ssh_hashalg *alg) +{ + const ssh_hashalg *real_alg = + sha256_hw_available_cached() ? &ssh_sha256_hw : &ssh_sha256_sw; + + return ssh_hash_new(real_alg); +} + +const ssh_hashalg ssh_sha256 = { + .new = sha256_select, + .hlen = 32, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-256", "dummy selector vtable"), +}; + +/* ---------------------------------------------------------------------- + * Definitions likely to be helpful to multiple implementations. + */ + +static const uint32_t sha256_initial_state[] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +}; + +static const uint32_t sha256_round_constants[] = { + 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, +}; + +#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"); +} + +/* ---------------------------------------------------------------------- + * Software implementation of SHA-256. + */ + +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]); +} + +const ssh_hashalg ssh_sha256_sw = { + .new = sha256_sw_new, + .reset = sha256_sw_reset, + .copyfrom = sha256_sw_copyfrom, + .digest = sha256_sw_digest, + .free = sha256_sw_free, + .hlen = 32, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-256", "unaccelerated"), +}; + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of SHA-256 using x86 SHA-NI. + */ + +#if HW_SHA256 == HW_SHA256_NI + +/* + * Set target architecture for Clang and GCC + */ +#if defined(__clang__) || defined(__GNUC__) +# define FUNC_ISA __attribute__ ((target("sse4.1,sha"))) +#if !defined(__clang__) +# pragma GCC target("sha") +# pragma GCC target("sse4.1") +#endif +#else +# define FUNC_ISA +#endif + +#include +#include +#include +#if defined(__clang__) || defined(__GNUC__) +#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_hw_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 +*/ +FUNC_ISA +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) +{ + if (!sha256_hw_available_cached()) + 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; +} + +FUNC_ISA 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); +} + +FUNC_ISA 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); +} + +const ssh_hashalg ssh_sha256_hw = { + .new = sha256_ni_new, + .reset = sha256_ni_reset, + .copyfrom = sha256_ni_copyfrom, + .digest = sha256_ni_digest, + .free = sha256_ni_free, + .hlen = 32, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-256", "SHA-NI accelerated"), +}; + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of SHA-256 using Arm NEON. + */ + +#elif HW_SHA256 == HW_SHA256_NEON + +/* + * Manually set the target architecture, if we decided above that we + * need to. + */ +#ifdef USE_CLANG_ATTR_TARGET_AARCH64 +/* + * A spot of cheating: redefine some ACLE feature macros before + * including arm_neon.h. Otherwise we won't get the SHA intrinsics + * defined by that header, because it will be looking at the settings + * for the whole translation unit rather than the ones we're going to + * put on some particular functions using __attribute__((target)). + */ +#define __ARM_NEON 1 +#define __ARM_FEATURE_CRYPTO 1 +#define __ARM_FEATURE_SHA2 1 +#define FUNC_ISA __attribute__ ((target("neon,crypto"))) +#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ + +#ifndef FUNC_ISA +#define FUNC_ISA +#endif + +#ifdef USE_ARM64_NEON_H +#include +#else +#include +#endif + +static bool sha256_hw_available(void) +{ + /* + * For Arm, we delegate to a per-platform detection function (see + * explanation in sshaes.c). + */ + return platform_sha256_hw_available(); +} + +typedef struct sha256_neon_core sha256_neon_core; +struct sha256_neon_core { + uint32x4_t abcd, efgh; +}; + +FUNC_ISA +static inline uint32x4_t sha256_neon_load_input(const uint8_t *p) +{ + return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p))); +} + +FUNC_ISA +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); +} + +FUNC_ISA +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; +} + +FUNC_ISA +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) +{ + if (!sha256_hw_available_cached()) + 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))); +} + +const ssh_hashalg ssh_sha256_hw = { + .new = sha256_neon_new, + .reset = sha256_neon_reset, + .copyfrom = sha256_neon_copyfrom, + .digest = sha256_neon_digest, + .free = sha256_neon_free, + .hlen = 32, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-256", "NEON accelerated"), +}; + +/* ---------------------------------------------------------------------- + * Stub functions if we have no hardware-accelerated SHA-256. In this + * case, sha256_hw_new returns NULL (though it should also never be + * selected by sha256_select, so the only thing that should even be + * _able_ to call it is testcrypt). As a result, the remaining vtable + * functions should never be called at all. + */ + +#elif HW_SHA256 == HW_SHA256_NONE + +static bool sha256_hw_available(void) +{ + return false; +} + +static ssh_hash *sha256_stub_new(const ssh_hashalg *alg) +{ + return NULL; +} + +#define STUB_BODY { unreachable("Should never be called"); } + +static void sha256_stub_reset(ssh_hash *hash) STUB_BODY +static void sha256_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY +static void sha256_stub_free(ssh_hash *hash) STUB_BODY +static void sha256_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY + +const ssh_hashalg ssh_sha256_hw = { + .new = sha256_stub_new, + .reset = sha256_stub_reset, + .copyfrom = sha256_stub_copyfrom, + .digest = sha256_stub_digest, + .free = sha256_stub_free, + .hlen = 32, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-256", "!NONEXISTENT ACCELERATED VERSION!"), +}; + +#endif /* HW_SHA256 */ diff --git a/0.77_My_PuTTY/sshsh512.c b/0.77_My_PuTTY/sshsh512.c new file mode 100644 index 0000000..552e907 --- /dev/null +++ b/0.77_My_PuTTY/sshsh512.c @@ -0,0 +1,836 @@ +/* + * SHA-512 algorithm as described at + * + * http://csrc.nist.gov/cryptval/shs.html + * + * Modifications made for SHA-384 also + */ + +#include +#include "ssh.h" + +/* + * Start by deciding whether we can support hardware SHA at all. + */ +#define HW_SHA512_NONE 0 +#define HW_SHA512_NEON 1 + +#ifdef _FORCE_SHA512_NEON +# define HW_SHA512 HW_SHA512_NEON +#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + /* Arm can potentially support both endiannesses, but this code + * hasn't been tested on anything but little. If anyone wants to + * run big-endian, they'll need to fix it first. */ +#elif defined __ARM_FEATURE_SHA512 + /* If the Arm SHA-512 extension is available already, we can + * support NEON SHA without having to enable anything by hand */ +# define HW_SHA512 HW_SHA512_NEON +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__aarch64__)) + /* clang can enable the crypto extension in AArch64 using + * __attribute__((target)) */ +# define HW_SHA512 HW_SHA512_NEON +# define USE_CLANG_ATTR_TARGET_AARCH64 +# endif +#endif + +#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA512 +# undef HW_SHA512 +# define HW_SHA512 HW_SHA512_NONE +#endif + +/* + * The actual query function that asks if hardware acceleration is + * available. + */ +static bool sha512_hw_available(void); + +/* + * The top-level selection function, caching the results of + * sha512_hw_available() so it only has to run once. + */ +static bool sha512_hw_available_cached(void) +{ + static bool initialised = false; + static bool hw_available; + if (!initialised) { + hw_available = sha512_hw_available(); + initialised = true; + } + return hw_available; +} + +struct sha512_select_options { + const ssh_hashalg *hw, *sw; +}; + +static ssh_hash *sha512_select(const ssh_hashalg *alg) +{ + const struct sha512_select_options *options = + (const struct sha512_select_options *)alg->extra; + + const ssh_hashalg *real_alg = + sha512_hw_available_cached() ? options->hw : options->sw; + + return ssh_hash_new(real_alg); +} + +const struct sha512_select_options ssh_sha512_select_options = { + &ssh_sha512_hw, &ssh_sha512_sw, +}; +const struct sha512_select_options ssh_sha384_select_options = { + &ssh_sha384_hw, &ssh_sha384_sw, +}; + +const ssh_hashalg ssh_sha512 = { + .new = sha512_select, + .hlen = 64, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-512", "dummy selector vtable"), + .extra = &ssh_sha512_select_options, +}; + +const ssh_hashalg ssh_sha384 = { + .new = sha512_select, + .hlen = 48, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-384", "dummy selector vtable"), + .extra = &ssh_sha384_select_options, +}; + +/* ---------------------------------------------------------------------- + * Definitions likely to be helpful to multiple implementations. + */ + +static const uint64_t sha512_initial_state[] = { + 0x6a09e667f3bcc908ULL, + 0xbb67ae8584caa73bULL, + 0x3c6ef372fe94f82bULL, + 0xa54ff53a5f1d36f1ULL, + 0x510e527fade682d1ULL, + 0x9b05688c2b3e6c1fULL, + 0x1f83d9abfb41bd6bULL, + 0x5be0cd19137e2179ULL, +}; + +static const uint64_t sha384_initial_state[] = { + 0xcbbb9d5dc1059ed8ULL, + 0x629a292a367cd507ULL, + 0x9159015a3070dd17ULL, + 0x152fecd8f70e5939ULL, + 0x67332667ffc00b31ULL, + 0x8eb44a8768581511ULL, + 0xdb0c2e0d64f98fa7ULL, + 0x47b5481dbefa4fa4ULL, +}; + +static const uint64_t sha512_round_constants[] = { + 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, +}; + +#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"); +} + +/* ---------------------------------------------------------------------- + * Software implementation of SHA-512. + */ + +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); + + /* The 'extra' field in the ssh_hashalg indicates which + * initialisation vector we're using */ + memcpy(s->core, hash->vt->extra, 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]); +} + +const ssh_hashalg ssh_sha512_sw = { + .new = sha512_sw_new, + .reset = sha512_sw_reset, + .copyfrom = sha512_sw_copyfrom, + .digest = sha512_sw_digest, + .free = sha512_sw_free, + .hlen = 64, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-512", "unaccelerated"), + .extra = sha512_initial_state, +}; + +const ssh_hashalg ssh_sha384_sw = { + .new = sha512_sw_new, + .reset = sha512_sw_reset, + .copyfrom = sha512_sw_copyfrom, + .digest = sha512_sw_digest, + .free = sha512_sw_free, + .hlen = 48, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-384", "unaccelerated"), + .extra = sha384_initial_state, +}; + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of SHA-512 using Arm NEON. + */ + +#if HW_SHA512 == HW_SHA512_NEON + +/* + * Manually set the target architecture, if we decided above that we + * need to. + */ +#ifdef USE_CLANG_ATTR_TARGET_AARCH64 +/* + * A spot of cheating: redefine some ACLE feature macros before + * including arm_neon.h. Otherwise we won't get the SHA intrinsics + * defined by that header, because it will be looking at the settings + * for the whole translation unit rather than the ones we're going to + * put on some particular functions using __attribute__((target)). + */ +#define __ARM_NEON 1 +#define __ARM_FEATURE_CRYPTO 1 +#define FUNC_ISA __attribute__ ((target("neon,sha3"))) +#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ + +#ifndef FUNC_ISA +#define FUNC_ISA +#endif + +#ifdef USE_ARM64_NEON_H +#include +#else +#include +#endif + +static bool sha512_hw_available(void) +{ + /* + * For Arm, we delegate to a per-platform detection function (see + * explanation in sshaes.c). + */ + return platform_sha512_hw_available(); +} + +#if defined __clang__ +/* + * As of 2020-12-24, I've found that clang doesn't provide the SHA-512 + * NEON intrinsics. So I define my own set using inline assembler, and + * use #define to effectively rename them over the top of the standard + * names. + * + * The aim of that #define technique is that it should avoid a build + * failure if these intrinsics _are_ defined in . + * Obviously it would be better in that situation to switch back to + * using the real intrinsics, but until I see a version of clang that + * supports them, I won't know what version number to test in the + * ifdef. + */ +static inline FUNC_ISA +uint64x2_t vsha512su0q_u64_asm(uint64x2_t x, uint64x2_t y) { + __asm__("sha512su0 %0.2D,%1.2D" : "+w" (x) : "w" (y)); + return x; +} +static inline FUNC_ISA +uint64x2_t vsha512su1q_u64_asm(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 FUNC_ISA +uint64x2_t vsha512hq_u64_asm(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 FUNC_ISA +uint64x2_t vsha512h2q_u64_asm(uint64x2_t x, uint64x2_t y, uint64x2_t z) { + __asm__("sha512h2 %0,%1,%2.2D" : "+w" (x) : "w" (y), "w" (z)); + return x; +} +#undef vsha512su0q_u64 +#define vsha512su0q_u64 vsha512su0q_u64_asm +#undef vsha512su1q_u64 +#define vsha512su1q_u64 vsha512su1q_u64_asm +#undef vsha512hq_u64 +#define vsha512hq_u64 vsha512hq_u64_asm +#undef vsha512h2q_u64 +#define vsha512h2q_u64 vsha512h2q_u64_asm +#endif /* defined __clang__ */ + +typedef struct sha512_neon_core sha512_neon_core; +struct sha512_neon_core { + uint64x2_t ab, cd, ef, gh; +}; + +FUNC_ISA +static inline uint64x2_t sha512_neon_load_input(const uint8_t *p) +{ + return vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p))); +} + +FUNC_ISA +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)); +} + +FUNC_ISA +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); +} + +FUNC_ISA +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) +{ + if (!sha512_hw_available_cached()) + 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 uint64_t *iv = (const uint64_t *)hash->vt->extra; + + s->core.ab = vld1q_u64(iv); + s->core.cd = vld1q_u64(iv+2); + s->core.ef = vld1q_u64(iv+4); + s->core.gh = vld1q_u64(iv+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))); +} + +const ssh_hashalg ssh_sha512_hw = { + .new = sha512_neon_new, + .reset = sha512_neon_reset, + .copyfrom = sha512_neon_copyfrom, + .digest = sha512_neon_digest, + .free = sha512_neon_free, + .hlen = 64, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-512", "NEON accelerated"), + .extra = sha512_initial_state, +}; + +const ssh_hashalg ssh_sha384_hw = { + .new = sha512_neon_new, + .reset = sha512_neon_reset, + .copyfrom = sha512_neon_copyfrom, + .digest = sha384_neon_digest, + .free = sha512_neon_free, + .hlen = 48, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-384", "NEON accelerated"), + .extra = sha384_initial_state, +}; + +/* ---------------------------------------------------------------------- + * Stub functions if we have no hardware-accelerated SHA-512. In this + * case, sha512_hw_new returns NULL (though it should also never be + * selected by sha512_select, so the only thing that should even be + * _able_ to call it is testcrypt). As a result, the remaining vtable + * functions should never be called at all. + */ + +#elif HW_SHA512 == HW_SHA512_NONE + +static bool sha512_hw_available(void) +{ + return false; +} + +static ssh_hash *sha512_stub_new(const ssh_hashalg *alg) +{ + return NULL; +} + +#define STUB_BODY { unreachable("Should never be called"); } + +static void sha512_stub_reset(ssh_hash *hash) STUB_BODY +static void sha512_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY +static void sha512_stub_free(ssh_hash *hash) STUB_BODY +static void sha512_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY + +const ssh_hashalg ssh_sha512_hw = { + .new = sha512_stub_new, + .reset = sha512_stub_reset, + .copyfrom = sha512_stub_copyfrom, + .digest = sha512_stub_digest, + .free = sha512_stub_free, + .hlen = 64, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-512", "!NONEXISTENT ACCELERATED VERSION!"), +}; + +const ssh_hashalg ssh_sha384_hw = { + .new = sha512_stub_new, + .reset = sha512_stub_reset, + .copyfrom = sha512_stub_copyfrom, + .digest = sha512_stub_digest, + .free = sha512_stub_free, + .hlen = 48, + .blocklen = 128, + HASHALG_NAMES_ANNOTATED("SHA-384", "!NONEXISTENT ACCELERATED VERSION!"), +}; + +#endif /* HW_SHA512 */ diff --git a/0.77_My_PuTTY/sshsha.c b/0.77_My_PuTTY/sshsha.c new file mode 100644 index 0000000..e61bdc6 --- /dev/null +++ b/0.77_My_PuTTY/sshsha.c @@ -0,0 +1,934 @@ +/* + * SHA-1 algorithm as described at + * + * http://csrc.nist.gov/cryptval/shs.html + */ + +#include "ssh.h" +#include + +/* + * Start by deciding whether we can support hardware SHA at all. + */ +#define HW_SHA1_NONE 0 +#define HW_SHA1_NI 1 +#define HW_SHA1_NEON 2 + +#ifdef _FORCE_SHA_NI +# define HW_SHA1 HW_SHA1_NI +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__x86_64__) || defined(__i386)) +# define HW_SHA1 HW_SHA1_NI +# endif +#elif defined(__GNUC__) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && \ + (defined(__x86_64__) || defined(__i386)) +# define HW_SHA1 HW_SHA1_NI +# endif +#elif defined (_MSC_VER) +# if (defined(_M_X64) || defined(_M_IX86)) && _MSC_FULL_VER >= 150030729 +# define HW_SHA1 HW_SHA1_NI +# endif +#endif + +#ifdef _FORCE_SHA_NEON +# define HW_SHA1 HW_SHA1_NEON +#elif defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + /* Arm can potentially support both endiannesses, but this code + * hasn't been tested on anything but little. If anyone wants to + * run big-endian, they'll need to fix it first. */ +#elif defined __ARM_FEATURE_CRYPTO + /* If the Arm crypto extension is available already, we can + * support NEON SHA without having to enable anything by hand */ +# define HW_SHA1 HW_SHA1_NEON +#elif defined(__clang__) +# if __has_attribute(target) && __has_include() && \ + (defined(__aarch64__)) + /* clang can enable the crypto extension in AArch64 using + * __attribute__((target)) */ +# define HW_SHA1 HW_SHA1_NEON +# define USE_CLANG_ATTR_TARGET_AARCH64 +# endif +#elif defined _MSC_VER + /* Visual Studio supports the crypto extension when targeting + * AArch64, but as of VS2017, the AArch32 header doesn't quite + * manage it (declaring the shae/shad intrinsics without a round + * key operand). */ +# if defined _M_ARM64 +# define HW_SHA1 HW_SHA1_NEON +# if defined _M_ARM64 +# define USE_ARM64_NEON_H /* unusual header name in this case */ +# endif +# endif +#endif + +#if defined _FORCE_SOFTWARE_SHA || !defined HW_SHA1 +# undef HW_SHA1 +# define HW_SHA1 HW_SHA1_NONE +#endif + +/* + * The actual query function that asks if hardware acceleration is + * available. + */ +static bool sha1_hw_available(void); + +/* + * The top-level selection function, caching the results of + * sha1_hw_available() so it only has to run once. + */ +static bool sha1_hw_available_cached(void) +{ + static bool initialised = false; + static bool hw_available; + if (!initialised) { + hw_available = sha1_hw_available(); + initialised = true; + } + return hw_available; +} + +static ssh_hash *sha1_select(const ssh_hashalg *alg) +{ + const ssh_hashalg *real_alg = + sha1_hw_available_cached() ? &ssh_sha1_hw : &ssh_sha1_sw; + + return ssh_hash_new(real_alg); +} + +const ssh_hashalg ssh_sha1 = { + .new = sha1_select, + .hlen = 20, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-1", "dummy selector vtable"), +}; + +/* ---------------------------------------------------------------------- + * Definitions likely to be helpful to multiple implementations. + */ + +static const uint32_t sha1_initial_state[] = { + 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0, +}; + +#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"); +} + +/* ---------------------------------------------------------------------- + * Software implementation of SHA-1. + */ + +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]); +} + +const ssh_hashalg ssh_sha1_sw = { + .new = sha1_sw_new, + .reset = sha1_sw_reset, + .copyfrom = sha1_sw_copyfrom, + .digest = sha1_sw_digest, + .free = sha1_sw_free, + .hlen = 20, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-1", "unaccelerated"), +}; + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of SHA-1 using x86 SHA-NI. + */ + +#if HW_SHA1 == HW_SHA1_NI + +/* + * Set target architecture for Clang and GCC + */ + +#if defined(__clang__) || defined(__GNUC__) +# define FUNC_ISA __attribute__ ((target("sse4.1,sha"))) +#if !defined(__clang__) +# pragma GCC target("sha") +# pragma GCC target("sse4.1") +#endif +#else +# define FUNC_ISA +#endif + +#include +#include +#include +#if defined(__clang__) || defined(__GNUC__) +#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_hw_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 +*/ +FUNC_ISA +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) +{ + if (!sha1_hw_available_cached()) + 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; +} + +FUNC_ISA 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); +} + +FUNC_ISA 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); +} + +const ssh_hashalg ssh_sha1_hw = { + .new = sha1_ni_new, + .reset = sha1_ni_reset, + .copyfrom = sha1_ni_copyfrom, + .digest = sha1_ni_digest, + .free = sha1_ni_free, + .hlen = 20, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-1", "SHA-NI accelerated"), +}; + +/* ---------------------------------------------------------------------- + * Hardware-accelerated implementation of SHA-1 using Arm NEON. + */ + +#elif HW_SHA1 == HW_SHA1_NEON + +/* + * Manually set the target architecture, if we decided above that we + * need to. + */ +#ifdef USE_CLANG_ATTR_TARGET_AARCH64 +/* + * A spot of cheating: redefine some ACLE feature macros before + * including arm_neon.h. Otherwise we won't get the SHA intrinsics + * defined by that header, because it will be looking at the settings + * for the whole translation unit rather than the ones we're going to + * put on some particular functions using __attribute__((target)). + */ +#define __ARM_NEON 1 +#define __ARM_FEATURE_CRYPTO 1 +#define __ARM_FEATURE_SHA2 1 +#define FUNC_ISA __attribute__ ((target("neon,crypto"))) +#endif /* USE_CLANG_ATTR_TARGET_AARCH64 */ + +#ifndef FUNC_ISA +#define FUNC_ISA +#endif + +#ifdef USE_ARM64_NEON_H +#include +#else +#include +#endif + +static bool sha1_hw_available(void) +{ + /* + * For Arm, we delegate to a per-platform detection function (see + * explanation in sshaes.c). + */ + return platform_sha1_hw_available(); +} + +typedef struct sha1_neon_core sha1_neon_core; +struct sha1_neon_core { + uint32x4_t abcd; + uint32_t e; +}; + +FUNC_ISA +static inline uint32x4_t sha1_neon_load_input(const uint8_t *p) +{ + return vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(p))); +} + +FUNC_ISA +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) \ + FUNC_ISA 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) + +FUNC_ISA +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) +{ + if (!sha1_hw_available_cached()) + 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); +} + +const ssh_hashalg ssh_sha1_hw = { + .new = sha1_neon_new, + .reset = sha1_neon_reset, + .copyfrom = sha1_neon_copyfrom, + .digest = sha1_neon_digest, + .free = sha1_neon_free, + .hlen = 20, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-1", "NEON accelerated"), +}; + +/* ---------------------------------------------------------------------- + * Stub functions if we have no hardware-accelerated SHA-1. In this + * case, sha1_hw_new returns NULL (though it should also never be + * selected by sha1_select, so the only thing that should even be + * _able_ to call it is testcrypt). As a result, the remaining vtable + * functions should never be called at all. + */ + +#elif HW_SHA1 == HW_SHA1_NONE + +static bool sha1_hw_available(void) +{ + return false; +} + +static ssh_hash *sha1_stub_new(const ssh_hashalg *alg) +{ + return NULL; +} + +#define STUB_BODY { unreachable("Should never be called"); } + +static void sha1_stub_reset(ssh_hash *hash) STUB_BODY +static void sha1_stub_copyfrom(ssh_hash *hash, ssh_hash *orig) STUB_BODY +static void sha1_stub_free(ssh_hash *hash) STUB_BODY +static void sha1_stub_digest(ssh_hash *hash, uint8_t *digest) STUB_BODY + +const ssh_hashalg ssh_sha1_hw = { + .new = sha1_stub_new, + .reset = sha1_stub_reset, + .copyfrom = sha1_stub_copyfrom, + .digest = sha1_stub_digest, + .free = sha1_stub_free, + .hlen = 20, + .blocklen = 64, + HASHALG_NAMES_ANNOTATED("SHA-1", "!NONEXISTENT ACCELERATED VERSION!"), +}; + +#endif /* HW_SHA1 */ diff --git a/0.77_My_PuTTY/sshsha3.c b/0.77_My_PuTTY/sshsha3.c new file mode 100644 index 0000000..5f46913 --- /dev/null +++ b/0.77_My_PuTTY/sshsha3.c @@ -0,0 +1,329 @@ +/* + * 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/0.77_My_PuTTY/sshshare.c b/0.77_My_PuTTY/sshshare.c new file mode 100644 index 0000000..b885db3 --- /dev/null +++ b/0.77_My_PuTTY/sshshare.c @@ -0,0 +1,2182 @@ +/* + * 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 + +#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, const char *error_msg, int error_code, + bool calling_back) +{ + struct ssh_sharing_connstate *cs = container_of( + plug, struct ssh_sharing_connstate, plug); + + if (error_msg) { +#ifdef BROKEN_PIPE_ERROR_CODE + /* + * 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 (error_code == BROKEN_PIPE_ERROR_CODE && !cs->got_verstring) + /* do nothing */; + else +#endif + 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; +} + +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); + } +} + +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); +} + +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 ssh.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 ssh.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 ssh.c's tree of remote + * port forwardings. If we can't, it's because another + * client sharing this connection has already allocated + * the identical port forwarding, so we take it on + * ourselves to manufacture a failure packet and send it + * back to downstream. + */ + 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 ssh.c 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); + goto dead; + } + cs->recvbuf[cs->recvlen++] = c; + } + + /* + * Now parse the version string to make sure it's at least vaguely + * sensible, and log it. + */ + if (cs->recvlen < sizeof(expected_verstring_prefix)-1 || + memcmp(cs->recvbuf, expected_verstring_prefix, + sizeof(expected_verstring_prefix) - 1)) { + char *buf = dupprintf("Version string did not have expected prefix\n"); + share_disconnect(cs, buf); + sfree(buf); + goto dead; + } + if (cs->recvlen > 0 && cs->recvbuf[cs->recvlen-1] == '\015') + cs->recvlen--; /* trim off \r before \n */ + 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); + goto dead; + } + while (cs->recvlen < cs->curr_packetlen) { + crGetChar(c); + cs->recvbuf[cs->recvlen++] = c; + } + + share_got_pkt_from_downstream(cs, cs->recvbuf[4], + cs->recvbuf + 5, cs->recvlen - 5); + } + + dead:; + 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, const char *error_msg, + int error_code, bool calling_back) +{ + ssh_sharing_state *sharestate = + container_of(plug, ssh_sharing_state, plug); + if (error_msg) + 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 ssh.c 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, +}; + +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. + */ +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, +}; + +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/0.77_My_PuTTY/sshsignals.h b/0.77_My_PuTTY/sshsignals.h new file mode 100644 index 0000000..b213c34 --- /dev/null +++ b/0.77_My_PuTTY/sshsignals.h @@ -0,0 +1,53 @@ +/* + * 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/0.77_My_PuTTY/sshttymodes.h b/0.77_My_PuTTY/sshttymodes.h new file mode 100644 index 0000000..8fffe1c --- /dev/null +++ b/0.77_My_PuTTY/sshttymodes.h @@ -0,0 +1,179 @@ +/* + * 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/0.77_My_PuTTY/sshutils.c b/0.77_My_PuTTY/sshutils.c new file mode 100644 index 0000000..9869429 --- /dev/null +++ b/0.77_My_PuTTY/sshutils.c @@ -0,0 +1,128 @@ +/* + * Supporting routines used in common by all the various components of + * the SSH system. + */ + +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshchan.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/0.77_My_PuTTY/sshverstring.c b/0.77_My_PuTTY/sshverstring.c new file mode 100644 index 0000000..77c3320 --- /dev/null +++ b/0.77_My_PuTTY/sshverstring.c @@ -0,0 +1,635 @@ +/* + * Code to handle the initial SSH version string exchange. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshbpp.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); + + 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. + */ +#ifdef MOD_PERSO + s->our_vstring = dupprintf( + "%.*s%s-%s", + (int)s->prefix_wanted.len, (const char *)s->prefix_wanted.ptr, + s->our_protoversion, sshver); +#else + 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); +#endif + 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] == '\r' || + s->vstring->s[s->vstring->len-1] == '\n')) + 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"); + } +} + +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/0.77_My_PuTTY/sshzlib.c b/0.77_My_PuTTY/sshzlib.c new file mode 100644 index 0000000..017520c --- /dev/null +++ b/0.77_My_PuTTY/sshzlib.c @@ -0,0 +1,1253 @@ +/* + * 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; +}; + +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; +} + +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); +} + +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; +}; + +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; +} + +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) ) + +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/0.77_My_PuTTY/storage.h b/0.77_My_PuTTY/storage.h new file mode 100644 index 0000000..41e4b5c --- /dev/null +++ b/0.77_My_PuTTY/storage.h @@ -0,0 +1,116 @@ +/* + * storage.h: interface defining functions for storage and recovery + * of PuTTY's persistent data. + */ + +#ifndef PUTTY_STORAGE_H +#define PUTTY_STORAGE_H + +/* ---------------------------------------------------------------------- + * Functions to save and restore PuTTY sessions. Note that this is + * only the low-level code to do the reading and writing. The + * higher-level code that translates an internal Conf structure into + * a set of (key,value) pairs in their external storage format is + * elsewhere, since it doesn't (mostly) change between platforms. + */ + +/* + * Write a saved session. The caller is expected to call + * open_setting_w() to get a `void *' handle, then pass that to a + * number of calls to write_setting_s() and write_setting_i(), and + * then close it using close_settings_w(). At the end of this call + * sequence the settings should have been written to the PuTTY + * persistent storage area. + * + * A given key will be written at most once while saving a session. + * Keys may be up to 255 characters long. String values have no length + * limit. + * + * Any returned error message must be freed after use. + */ +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 verify_host_key(const char *hostname, int port, + const char *keytype, const char *key); + +/* + * Write a host key into the database, overwriting any previous + * entry that might have been there. + */ +void store_host_key(const char *hostname, int port, + const char *keytype, const char *key); + +/* ---------------------------------------------------------------------- + * Functions to access PuTTY's random number seed file. + */ + +typedef void (*noise_consumer_t) (void *data, int len); + +/* + * Read PuTTY's random seed file and pass its contents to a noise + * consumer function. + */ +void read_random_seed(noise_consumer_t consumer); + +/* + * Write PuTTY's random seed file from a given chunk of noise. + */ +void write_random_seed(void *data, int len); + +/* ---------------------------------------------------------------------- + * Cleanup function: remove all of PuTTY's persistent state. + */ +void cleanup_all(void); + +#endif diff --git a/0.77_My_PuTTY/stripctrl.c b/0.77_My_PuTTY/stripctrl.c new file mode 100644 index 0000000..ee78960 --- /dev/null +++ b/0.77_My_PuTTY/stripctrl.c @@ -0,0 +1,471 @@ +/* + * 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. */ + } 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); + } + } + + 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); + + char outbuf[6]; + size_t produced; + + /* + * 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)) { + produced = encode_utf8(outbuf, wc); + } else { + outbuf[0] = wc; + produced = 1; + } + + if (produced > 0) + put_data(scc->bs_out, outbuf, produced); +} + +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; + + const char *previous_locale = 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); +} + +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/0.77_My_PuTTY/supdup.c b/0.77_My_PuTTY/supdup.c new file mode 100644 index 0000000..796fd01 --- /dev/null +++ b/0.77_My_PuTTY/supdup.c @@ -0,0 +1,923 @@ +/* +* 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 closed_on_socket_error; + + Seat *seat; + LogContext *logctx; + int term_width, term_height; + + 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; +}; + +#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. + strbuf_catf(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. + */ + strbuf_catf(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. + */ + strbuf_catf(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. + */ + strbuf_catf(outbuf, "\033[K"); + break; + + case TDDLF: + /* + Clear the character position the cursor is on. The + cursor does not move. + */ + strbuf_catf(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. + */ + strbuf_catf(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. + */ + + strbuf_catf(outbuf, "\033[C"); + break; + + case TDCLR: + /* + Erase the screen. Home the cursor to the top left hand + corner of the screen. + */ + strbuf_catf(outbuf, "\033[2J\033[H"); + break; + + case TDBEL: + /* + Generate an audio tone, bell, whatever. + */ + + strbuf_catf(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. + */ + strbuf_catf(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. + */ + strbuf_catf(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. + */ + strbuf_catf(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. + */ + strbuf_catf(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->state != CONNECTING); +} + +static void supdup_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + 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); + } + if (error_msg) { + logevent(supdup->logctx, error_msg); + 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; +} + +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 +} + +/* +* 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); + supdup->plug.vt = &fn_table; + supdup->backend.vt = &supdup_backend; + supdup->logctx = logctx; + supdup->conf = conf_copy(conf); + supdup->s = NULL; + 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; + *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); + 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 (supdup->s) + sk_close(supdup->s); + if (supdup->pinger) + pinger_free(supdup->pinger); + conf_free(supdup->conf); + 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 size_t 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 0; + + 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); + } + } + return supdup->bufsize; +} + +/* +* 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) +{ + return 1; +} + +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) +{ +} + +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 Dupdup 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 = "SUPDUP", + .protocol = PROT_SUPDUP, + .default_port = 0137, + .flags = BACKEND_RESIZE_FORBIDDEN | BACKEND_NEEDS_TERMINAL, +}; diff --git a/0.77_My_PuTTY/telnet.c b/0.77_My_PuTTY/telnet.c new file mode 100644 index 0000000..368cca2 --- /dev/null +++ b/0.77_My_PuTTY/telnet.c @@ -0,0 +1,1070 @@ +/* + * 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 closed_on_socket_error; + + Seat *seat; + LogContext *logctx; + Ldisc *ldisc; + int term_width, term_height; + + int opt_states[NUM_OPTS]; + + bool echoing, editing; + bool activated; + size_t bufsize; + bool in_synch; + int sb_opt; + strbuf *sb_buf; + bool session_started; + + enum { + TOP_LEVEL, SEENIAC, SEENWILL, SEENWONT, SEENDO, SEENDONT, + SEENSB, SUBNEGOT, SUBNEG_IAC, SEENCR + } state; + + Conf *conf; + + Pinger *pinger; + + Plug plug; + Backend backend; +}; + +#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:\t%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 *b, *p, *q; + int var, value, n, bsize; + char *e, *eval, *ekey, *user; + + 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); + b = snewn(20 + strlen(termspeed), unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = TELOPT_TSPEED; + b[3] = TELQUAL_IS; + strcpy((char *)(b + 4), termspeed); + n = 4 + strlen(termspeed); + b[n] = IAC; + b[n + 1] = SE; + telnet->bufsize = sk_write(telnet->s, b, n + 2); + logevent(telnet->logctx, "server:\tSB TSPEED SEND"); + logeventf(telnet->logctx, "client:\tSB TSPEED IS %s", termspeed); + sfree(b); + } else + logevent(telnet->logctx, "server:\tSB 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); + b = snewn(20 + strlen(termtype), unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = TELOPT_TTYPE; + b[3] = TELQUAL_IS; + for (n = 0; termtype[n]; n++) + b[n + 4] = (termtype[n] >= 'a' && termtype[n] <= 'z' ? + termtype[n] + 'A' - 'a' : + termtype[n]); + b[n + 4] = IAC; + b[n + 5] = SE; + telnet->bufsize = sk_write(telnet->s, b, n + 6); + b[n + 4] = 0; + logevent(telnet->logctx, "server:\tSB TTYPE SEND"); + logeventf(telnet->logctx, "client:\tSB TTYPE IS %s", b + 4); + sfree(b); + } else + logevent(telnet->logctx, "server:\tSB 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:\tSB %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; + } + bsize = 20; + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) + bsize += strlen(ekey) + strlen(eval) + 2; + user = get_remote_username(telnet->conf); + if (user) + bsize += 6 + strlen(user); + + b = snewn(bsize, unsigned char); + b[0] = IAC; + b[1] = SB; + b[2] = telnet->sb_opt; + b[3] = TELQUAL_IS; + n = 4; + for (eval = conf_get_str_strs(telnet->conf, CONF_environmt, + NULL, &ekey); + eval != NULL; + eval = conf_get_str_strs(telnet->conf, CONF_environmt, + ekey, &ekey)) { + b[n++] = var; + for (e = ekey; *e; e++) + b[n++] = *e; + b[n++] = value; + for (e = eval; *e; e++) + b[n++] = *e; + } + if (user) { + b[n++] = var; + b[n++] = 'U'; + b[n++] = 'S'; + b[n++] = 'E'; + b[n++] = 'R'; + b[n++] = value; + for (e = user; *e; e++) + b[n++] = *e; + } + b[n++] = IAC; + b[n++] = SE; + telnet->bufsize = sk_write(telnet->s, b, n); + if (n == 6) { + logeventf(telnet->logctx, "client:\tSB %s IS ", + telopt(telnet->sb_opt)); + } else { + logeventf(telnet->logctx, "client:\tSB %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, "\t%s=%s", ekey, eval); + } + if (user) + logeventf(telnet->logctx, "\tUSER=%s", user); + } + sfree(b); + 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->session_started); +} + +static void telnet_closing(Plug *plug, const char *error_msg, int error_code, + bool calling_back) +{ + 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); + } + if (error_msg) { + logevent(telnet->logctx, error_msg); + 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; + telnet->session_started = 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; +} + +static const PlugVtable Telnet_plugvt = { + .log = telnet_log, + .closing = telnet_closing, + .receive = telnet_receive, + .sent = telnet_sent, +}; + +/* + * 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; + + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + telnet = snew(Telnet); + telnet->plug.vt = &Telnet_plugvt; + telnet->backend.vt = vt; + telnet->conf = conf_copy(conf); + telnet->s = NULL; + telnet->closed_on_socket_error = false; + telnet->echoing = true; + telnet->editing = true; + telnet->activated = false; + telnet->sb_buf = 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->session_started = true; + *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); + if ((err = sk_socket_error(telnet->s)) != NULL) + return dupstr(err); + + 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); + + 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); +} +/* + * 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 size_t 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 0; + + p = (unsigned char *)buf; + end = (unsigned char *)(buf + len); + while (p < end) { + unsigned char *q = p; + + while (p < end && iswritable(*p)) + p++; + telnet->bufsize = sk_write(telnet->s, q, p - q); + + while (p < end && !iswritable(*p)) { + telnet->bufsize = + sk_write(telnet->s, *p == IAC ? iac : cr, 2); + p++; + } + } + + return telnet->bufsize; +} + +/* + * 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:\tSB 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 true; +} + +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 = "Telnet", + .protocol = PROT_TELNET, + .default_port = 23, +}; diff --git a/0.77_My_PuTTY/terminal.c b/0.77_My_PuTTY/terminal.c new file mode 100644 index 0000000..471492e --- /dev/null +++ b/0.77_My_PuTTY/terminal.c @@ -0,0 +1,8953 @@ +/* + * Terminal emulator. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include "putty.h" +#include "terminal.h" + +#ifdef MOD_FAR2L +/* base64 library - needed for far2l extensions support */ +#include <../far2l/cencode.h> +#include <../far2l/cdecode.h> +#endif +#ifdef MOD_PERSO +#include "charset.h" +int get_param( const char * val ) ; +int frontend_is_utf8(void *frontend) ; +int GetPuttyFlag(void) ; +int GetHyperlinkFlag(void) ; +int GetCursorType() ; +void SetCursorType( const int ct ) ; +#endif +#ifdef MOD_HYPERLINK +/* + * HACK: PuttyTray / Nutty + */ +#include "urlhack.h" +void wintw_clip_write(TermWin *tw, int clipboard, wchar_t *data, int *attr,truecolour *truecolour, int len, bool must_deselect); +#endif +#ifdef MOD_ZMODEM +int xyz_ReceiveData(Terminal *term, const u_char *buffer, int len); +int GetZModemFlag(void) ; +#endif + +#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) + +#ifdef MOD_PERSO +#define MM_NONE 0x00 /* No tracking */ +#define MM_NORMAL 0x01 /* Normal tracking mode */ +#define MM_BTN_EVENT 0x02 /* Button event mode */ +#define MM_ANY_EVENT 0x03 /* Any event mode */ + +/** Mouse tracking protocols */ +#define MP_NORMAL 0x00 /* CSI M Cb Cx Cy */ +#define MP_URXVT 0x01 /* CSI Db ; Dx ; Dy M */ +#define MP_SGR 0x02 /* CSI Db ; Dx ; Dy M/m */ +#define MP_XTERM 0x03 /* CSI M Cb WCx WCy */ +#endif + +#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; + +/* + * Fetch the character at a particular position in a line array, + * for purposes of `wordtype'. The reason this isn't just a simple + * array reference is that if the character we find is UCSWIDE, + * then we must look one space further to the left. + */ +#define UCSGET(a, x) \ + ( (x)>0 && (a)[(x)].chr == UCSWIDE ? (a)[(x)-1].chr : (a)[(x)].chr ) + +/* + * Detect the various aliases of U+0020 SPACE. + */ +#define IS_SPACE_CHR(chr) \ + ((chr) == 0x20 || (DIRECT_CHAR(chr) && ((chr) & 0xFF) == 0x20)) + +/* + * Spot magic CSETs. + */ +#define CSET_OF(chr) (DIRECT_CHAR(chr)||DIRECT_FONT(chr) ? (chr)&CSET_MASK : 0) + +/* + * Internal prototypes. + */ +static void resizeline(Terminal *, termline *, int); +static termline *lineptr(Terminal *, int, int, int); +static void unlineptr(termline *); +static void check_line_size(Terminal *, termline *); +static void do_paint(Terminal *); +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); +static void term_update_raw_mouse_mode(Terminal *term); + +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); + } +} + +static void unlineptr(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 +} + +/* + * 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_scrollback_line; + +static termline *decompressline(compressed_scrollback_line *line); + +static compressed_scrollback_line *compressline(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(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 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--) { +#ifndef MOD_PERSO + assert(n < ldata->cols); +#endif + 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(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; +} + +/* + * 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); +} + +/* + * Retrieve a line of the screen or of the scrollback, according to + * whether the y coordinate is non-negative or negative + * (respectively). + */ +static termline *lineptr(Terminal *term, int y, int lineno, int screen) +{ + termline *line; + tree234 *whichtree; + int treeindex; + + if (y >= 0) { + whichtree = term->screen; + treeindex = y; + } else { + int altlines = 0; +#ifndef MOD_PERSO + assert(!screen); +#endif + 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(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; +} + +#define lineptr(x) (lineptr)(term,x,__LINE__,0) +#define scrlineptr(x) (lineptr)(term,x,__LINE__,1) + +/* + * 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. + */ +static void seen_disp_event(Terminal *term) +{ + term->seen_disp_event = true; /* for scrollback-reset-on-activity */ + term_schedule_update(term); +} + +/* + * Call when the terminal's blinking-text settings change, or when + * a text blink has just occurred. + */ +static void term_schedule_tblink(Terminal *term) +{ + if (term->blink_is_real) { + if (!term->tblink_pending) + term->next_tblink = schedule_timer(TBLINK_DELAY, term_timer, term); + term->tblink_pending = true; + } else { + term->tblinker = 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 reset cursor blinking on new output. + */ +static void term_reset_cblink(Terminal *term) +{ + seen_disp_event(term); + term->cblinker = true; + term->cblink_pending = false; + term_schedule_cblink(term); +} + +/* + * 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); +#ifdef MOD_PERSO + term->xterm_mouse_mode = MM_NONE; + term->xterm_mouse_protocol = MP_NORMAL; +#else + term->xterm_mouse = 0; + term->xterm_extended_mouse = false; + term->urxvt_extended_mouse = false; +#endif + 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_request_resize(term->win, term->win_resize_pending_w, + term->win_resize_pending_h); + term->win_resize_pending = false; + } + 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->win_title_pending = false; + } + if (term->win_icon_title_pending) { + win_set_icon_title(term->win, term->icon_title); + 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)) { + bool need_sbar_update = term->seen_disp_event || + term->win_scrollbar_update_pending; + term->win_scrollbar_update_pending = false; + if (term->seen_disp_event && term->scroll_on_disp) { + term->disptop = 0; /* return to main screen */ + term->seen_disp_event = false; + need_sbar_update = true; + } + + if (need_sbar_update) + 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; /* return to main screen */ + seen_disp_event(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. + */ +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->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); +#ifdef MOD_PERSO + term->no_focus_rep = conf_get_bool(term->conf, CONF_no_focus_rep); +#endif + 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); +#ifdef MOD_PERSO + term->enter_sends_crlf = conf_get_int(term->conf, CONF_enter_sends_crlf); + term->rxvt_homeend = conf_get_int(term->conf, CONF_rxvt_homeend); +#else + term->rxvt_homeend = conf_get_bool(term->conf, CONF_rxvt_homeend); +#endif + 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); + int maxlen = strlen(answerback); + + term->answerback = snewn(maxlen, char); + term->answerbacklen = 0; + + while (*answerback) { + char *n; + char c = ctrlparse(answerback, &n); + if (n) { + term->answerback[term->answerbacklen++] = c; + answerback = n; + } else { + term->answerback[term->answerbacklen++] = *answerback++; + } + } + } +} + +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->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; + } +#ifdef MOD_PRINTCLIP + if (!conf_get_str(term->conf, CONF_printer) || conf_get_int(term->conf, CONF_printclip) ) { +#else + if (!conf_get_str(term->conf, CONF_printer)) { +#endif + 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->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); + term_schedule_update(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); +#ifdef MOD_FAR2L + /* far2l */ + term->far2l_ext = 0; // far2l extensions mode is enabled? + term->is_apc = 0; // currently processing incoming APC sequence? + term->clip_allowed = -1; // remote clipboard access is enabled? +#endif + term->win = win; + term->ucsdata = ucsdata; + term->conf = conf_copy(myconf); + term->logctx = NULL; + term->compatibility_level = TM_PUTTY; + strcpy(term->id_string, "\033[?6c"); + term->cblink_pending = term->tblink_pending = false; + term->paste_buffer = NULL; + term->paste_len = 0; + bufchain_init(&term->inbuf); + bufchain_init(&term->printer_buf); + term->printing = term->only_printing = false; + term->print_job = NULL; + term->vt52_mode = false; + term->cr_lf_return = false; + term->seen_disp_event = false; + term->mouse_is_down = 0; + term->reset_132 = false; + term->cblinker = false; + term->tblinker = false; + term->has_focus = true; + term->repeat_off = false; + term->termstate = TOPLEVEL; + term->selstate = NO_SELECTION; + term->curstype = 0; +#ifdef MOD_PERSO + term->no_focus_rep = true; + term->report_focus = false; +#endif + + term_copy_stuff_from_conf(term); + + term->screen = term->alt_screen = term->scrollback = NULL; + term->tempsblines = 0; + term->alt_sblines = 0; + term->disptop = 0; + term->disptext = NULL; + term->dispcursx = term->dispcursy = -1; + term->tabs = NULL; + deselect(term); + term->rows = term->cols = -1; + power_on(term, true); + term->beephead = term->beeptail = NULL; + term->nbeeps = 0; + term->lastbeep = false; + term->beep_overloaded = false; + term->attr_mask = 0xffffffff; + term->backend = NULL; + term->in_term_out = false; + term->ltemp = NULL; + term->ltemp_size = 0; + term->wcFrom = NULL; + term->wcTo = NULL; + term->wcFromTo_size = 0; + + term->window_update_pending = false; + term->window_update_cooldown = false; + + term->bidi_cache_size = 0; + term->pre_bidi_cache = term->post_bidi_cache = NULL; +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + */ + if( !GetPuttyFlag() && GetHyperlinkFlag() ) term->url_update = TRUE; +#endif + + /* FULL-TERMCHAR */ + term->basic_erase_char.chr = CSET_ASCII | ' '; + term->basic_erase_char.attr = ATTR_DEFAULT; + term->basic_erase_char.cc_next = 0; + term->basic_erase_char.truecolour.fg = optionalrgb_none; + term->basic_erase_char.truecolour.bg = optionalrgb_none; + term->erase_char = term->basic_erase_char; + + term->last_selected_text = NULL; + term->last_selected_attr = NULL; + term->last_selected_tc = NULL; + term->last_selected_len = 0; + /* 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->last_graphic_char = 0; + + term->trusted = true; +#ifdef MOD_ZMODEM + term->xyz_transfering = 0; + term->xyz_Internals = NULL; +#endif + + term->bracketed_paste_active = false; + + term->window_title = dupstr(""); + term->icon_title = dupstr(""); + term->minimised = false; + term->winpos_x = term->winpos_y = 0; + term->winpixsize_x = term->winpixsize_y = 0; + + term->win_move_pending = false; + term->win_resize_pending = false; + term->win_zorder_pending = false; + term->win_minimise_pending = false; + term->win_maximise_pending = false; + term->win_title_pending = false; + term->win_icon_title_pending = false; + term->win_pointer_shape_pending = false; + term->win_refresh_pending = false; + term->win_scrollbar_update_pending = false; + term->win_palette_pending = false; + + palette_reset(term, false); + + return term; +} + +void term_free(Terminal *term) +{ + termline *line; + struct beeptime *beep; + int i; + + while ((line = delpos234(term->scrollback, 0)) != NULL) + sfree(line); /* compressed data, not a termline */ + freetree234(term->scrollback); + while ((line = delpos234(term->screen, 0)) != NULL) + 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); + sfree(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); + + 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(cline); + sfree(cline); + line->temporary = false; /* reconstituted line is now real */ + term->tempsblines -= 1; + addpos234(term->screen, line, 0); + term->curs.y += 1; + term->savecurs.y += 1; + term->alt_y += 1; + term->alt_savecurs.y += 1; + } else { + /* Add a new blank line at the bottom of the screen. */ + line = 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(line), sblen++); + freetermline(line); + term->tempsblines += 1; + term->curs.y -= 1; + term->savecurs.y -= 1; + term->alt_y -= 1; + term->alt_savecurs.y -= 1; + } + term->rows -= 1; + } + assert(term->rows == newrows); + assert(count234(term->screen) == newrows); + + /* Delete any excess lines from the scrollback. */ + while (sblen > newsavelines) { + line = delpos234(term->scrollback, 0); + sfree(line); + sblen--; + } + if (sblen < term->tempsblines) + term->tempsblines = sblen; + assert(count234(term->scrollback) <= newsavelines); + assert(count234(term->scrollback) >= term->tempsblines); + term->disptop = 0; + + /* Make a new displayed text buffer. */ + newdisp = snewn(newrows, termline *); + for (i = 0; i < newrows; i++) { + newdisp[i] = 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); +} + +/* + * 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; +#ifdef MOD_HYPERLINK + assert(term->erase_char.cc_next == 0); +#endif + 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); + } +} + +/* + * 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) { + unsigned char *cline; + + sblen--; + cline = delpos234(term->scrollback, 0); + sfree(cline); + } else + term->tempsblines += 1; + + addpos234(term->scrollback, compressline(line), sblen); + + /* now `line' itself can be reused as the bottom line */ + + /* + * If the user is currently looking at part of the + * scrollback, and they haven't enabled any options + * that are going to reset the scrollback as a + * result of this movement, then the chances are + * they'd like to keep looking at the same line. So + * we move their viewpoint at the same rate as the + * scroll, at least until their viewpoint hits the + * top end of the scrollback buffer, at which point + * we don't have the choice any more. + * + * Thanks to Jan Holmen Holsten for the idea and + * initial implementation. + */ + if (term->disptop > -term->savelines && term->disptop < 0) + term->disptop--; + } + resizeline(term, line, term->cols); +#ifdef MOD_HYPERLINK + assert(term->erase_char.cc_next == 0); +#endif + clear_line(term, line); + check_trust_status(term, line); + 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; + } + } + } + } + } +} + +/* + * Move the cursor to a given position, clipping at boundaries. We + * may or may not want to clip at the scroll margin: marg_clip is 0 + * not to, 1 to disallow _passing_ the margins, and 2 to disallow + * even _being_ outside the margins. + */ +static void move(Terminal *term, int x, int y, int marg_clip) +{ + if (x < 0) + x = 0; + if (x >= term->cols) + x = term->cols - 1; + if (marg_clip) { + if ((term->curs.y >= term->marg_t || marg_clip == 2) && + y < term->marg_t) + y = term->marg_t; + if ((term->curs.y <= term->marg_b || marg_clip == 2) && + y > term->marg_b) + y = term->marg_b; + } + if (y < 0) + y = 0; + if (y >= term->rows) + y = term->rows - 1; + term->curs.x = x; + term->curs.y = y; + term->wrapnext = false; +} + +/* + * Save or restore the cursor and SGR mode. + */ +static void save_cursor(Terminal *term, 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); + } +} + +/* + * 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 { +#ifdef MOD_HYPERLINK + assert(term->erase_char.cc_next == 0); +#endif + 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; +} + +/* + * 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); +#ifdef MOD_HYPERLINK + assert(term->erase_char.cc_next == 0); +#endif + 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); +} + +/* + * 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->win_resize_pending = true; + term->win_resize_pending_w = state ? 132 : 80; + term->win_resize_pending_h = term->rows; + term_schedule_update(term); + } + 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; +#ifdef MOD_PERSO + case 1000: /* xterm mouse 1 (normal) */ + term->xterm_mouse_mode = state ? MM_NORMAL : MM_NONE; + term_update_raw_mouse_mode(term); + break; + case 1002: /* xterm mouse 2 (inc. button drags) */ + term->xterm_mouse_mode = state ? MM_BTN_EVENT : MM_NONE; + term_update_raw_mouse_mode(term); + break; + case 1003: /* xterm any event tracking */ + term->xterm_mouse_mode = state ? MM_ANY_EVENT : MM_NONE; + term_update_raw_mouse_mode(term); + break; + case 1004: + win_set_focus_reporting_mode(term->win, state); + break; + case 1005: /* use XTERM 1005 mouse protocol */ + term->xterm_mouse_protocol = state ? MP_XTERM : MP_NORMAL; + break; + case 1006: /* use SGR 1006 mouse protocol */ + term->xterm_mouse_protocol = state ? MP_SGR : MP_NORMAL; + break; + case 1015: /* use URXVT 1015 mouse protocol */ + term->xterm_mouse_protocol = state ? MP_URXVT : MP_NORMAL; + break; +#else + 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 1006: /* xterm extended mouse */ + term->xterm_extended_mouse = state; + break; + case 1015: /* urxvt extended mouse */ + term->urxvt_extended_mouse = state; + break; +#endif + 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) +{ +#ifdef MOD_FAR2L + /* far2l extensions support */ + if (term->is_apc) { + + #ifndef _WINDOWS + #define DWORD unsigned int + #define WORD unsigned short + #endif + + if (strncmp(term->osc_string, "far2l", 5) == 0) { + + // okay, this is far2l terminal extensions APC sequence + + // for more info about far2l terminal extensions please see: + // https://github.com/cyd01/KiTTY/issues/74 + // https://github.com/elfmz/far2l/blob/master/WinPort/FarTTY.h + + if (strncmp(term->osc_string+5, "1", 1) == 0) { + + // extensions mode on + + term->far2l_ext = 1; + + char *reply_buf = dupprintf( + "\x1b_far2lok\x07"); + ldisc_send(term->ldisc, reply_buf, strlen(reply_buf), + false); + sfree(reply_buf); + + // reset clipboard state; todo: do it on session init! + term->clip_allowed = -1; + + } else if (strncmp(term->osc_string+5, "0", 1) == 0) { + + // extensions mode off + + term->far2l_ext = 0; + + // reset clipboard state; todo: do it on session init! + term->clip_allowed = -1; + + } else if (strncmp(term->osc_string+5, ":", 1) == 0) { + + // processing actual payload + + // base64-decode payload + base64_decodestate _d_state; + base64_init_decodestate(&_d_state); + char* d_out = malloc(term->osc_strlen); + int d_count = base64_decode_block( + term->osc_string+6, term->osc_strlen-6, d_out, &_d_state); + + // last byte is id + BYTE id = d_out[d_count-1]; + char* reply = 0; + int reply_size = 0; + + if (term->osc_strlen == OSC_STR_MAX) { + + // it's possibly too large clipboard + + // we can't deal with it + // until implementing dynamic osc_string allocation + + #ifdef _WINDOWS + MessageBox(NULL, "Too large clipboard :(", "Error", MB_OK); + #endif + + // correct request id is lost forever + // so we can not prevent far2l from hanging + // so sad + + // fixme: good idea is to free all allocated memory here, though + exit(100); + } + + DWORD len; + + // next from the end byte is command + switch (d_out[d_count-2]) { + + case 'f':; // FARTTY_INTERRACT_SET_FKEY_TITLES + + reply_size = 5; + reply = malloc(reply_size); + + // fixme: unimplemented + DWORD zero = 0; + + memcpy(reply, &zero, sizeof(DWORD)); + + break; + + case 'n':; // FARTTY_INTERRACT_DESKTOP_NOTIFICATION + + /* // not ready yet + // todo: generate some reply + // todo: show notification only if window is out of focus + // todo: remove icon after notification timeout or by mouse click + // title length, source, utf8, no zero-terminate, bytes + DWORD len1; + memcpy(&len1, d_out+d_count-6, sizeof(len1)); + // text length, source, utf8, no zero-terminate, bytes + DWORD len2; + memcpy(&len2, d_out+d_count-6-4-len1, sizeof(len2)); + // destination (wide char) + LPWSTR text_wc, title_wc; + int textsz_wc, titlesz_wc; + // notification may contain file names in non-latin + // or may have localized title + // so we can not assume ascii here and should do + // full utf8->multibyte conversion + titlesz_wc = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)(d_out+len2+4), len1, 0, 0); + textsz_wc = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)d_out, len2, 0, 0); + if (titlesz_wc && textsz_wc) { + title_wc = malloc((titlesz_wc+1)*sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, (LPCCH)(d_out+len2+4), len1, title_wc, titlesz_wc); + text_wc = malloc((textsz_wc+1)*sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, (LPCCH)d_out, len2, text_wc, textsz_wc); + title_wc[titlesz_wc] = 0; + text_wc[textsz_wc] = 0; + NOTIFYICONDATAW pnid; + // todo: do this on window focus also + pnid.cbSize = sizeof(pnid); + pnid.hWnd = NULL; + pnid.hIcon = LoadIcon(0, IDI_APPLICATION); + pnid.uID = 200; + Shell_NotifyIconW(NIM_DELETE, &pnid); + // todo: use putty icon + pnid.cbSize = sizeof(pnid); + pnid.hWnd = NULL; + pnid.hIcon = LoadIcon(0, IDI_APPLICATION); + pnid.uID = 200; + pnid.uFlags = NIF_ICON | NIF_INFO | NIF_MESSAGE; + pnid.uCallbackMessage = (WM_USER + 200); + pnid.dwInfoFlags = NIIF_INFO | NIIF_NOSOUND; + memcpy(pnid.szInfoTitle, title_wc, (titlesz_wc+1)*sizeof(wchar_t)); + memcpy(pnid.szInfo, text_wc, (textsz_wc+1)*sizeof(wchar_t)); + Shell_NotifyIconW(NIM_ADD, &pnid); + free(text_wc); + free(title_wc); + } + */ + + break; + + case 'w': // FARTTY_INTERRACT_GET_WINDOW_MAXSIZE + + // get largest console window size + + reply_size = 5; + reply = malloc(reply_size); + + // fixme: unimplemented + // here should be short x and short y + DWORD none = 0; + + memcpy(reply, &none, sizeof(DWORD)); + + break; + + case 'c': // FARTTY_INTERRACT_CLIPBOARD + + // clipboard interaction + // next from the end byte is subcommand + + switch (d_out[d_count-3]) { + + case 'r':; // FARTTY_INTERRACT_CLIP_REGISTER_FORMAT + + // register format + + memcpy(&len, d_out + d_count - 3 - 4, sizeof(DWORD)); + d_out[len] = 0; // zero-terminate format name + + #ifdef _WINDOWS + // far2l sends format name as (utf8?) string, + // which actually containing ascii only + // so we can just call ascii function + uint32_t status = RegisterClipboardFormatA(d_out); + #endif + + reply_size = 5; + reply = malloc(reply_size); + + #ifdef _WINDOWS + memcpy(reply, &status, sizeof(uint32_t)); + #else + bzero(reply, sizeof(uint32_t)); + #endif + + break; + + case 'e':; // FARTTY_INTERRACT_CLIP_EMPTY + + #ifdef _WINDOWS + char ec_status = 0; + if (term->clip_allowed == 1) { + OpenClipboard(NULL); + ec_status = EmptyClipboard() ? 1 : 0; + CloseClipboard(); + } + #endif + + reply_size = 2; + reply = malloc(reply_size); + + #ifdef _WINDOWS + reply[0] = ec_status; + #else + reply[0] = 0; + #endif + + break; + + case 'a':; // FARTTY_INTERRACT_CLIP_ISAVAIL + + uint32_t a_fmt; + memcpy(&a_fmt, d_out + d_count - 3 - 4, sizeof(uint32_t)); + + #ifdef _WINDOWS + char out = IsClipboardFormatAvailable(a_fmt) ? 1 : 0; + #endif + + reply_size = 2; + reply = malloc(reply_size); + + #ifdef _WINDOWS + reply[0] = out; + #else + reply[0] = 0; + #endif + + break; + + case 'o':; // FARTTY_INTERRACT_CLIP_OPEN + + // open + // next from the end 4 bytes is client_id length + memcpy(&len, d_out + d_count - 3 - 4, sizeof(DWORD)); + d_out[len] = 0; // all remaining string is client id, make it zero terminated + + // todo: check/store client id, all that stuff + + reply_size = 2; + reply = malloc(reply_size); + + #ifdef _WINDOWS + if (term->clip_allowed == -1) { + int status = MessageBox(NULL, + "Allow far2l clipboard sync?", "PyTTY", MB_OKCANCEL); + if (status == IDOK) { + term->clip_allowed = 1; + } else { + // IDCANCEL + term->clip_allowed = 0; + } + } + + // status is first response byte + if (term->clip_allowed == 1) { + reply[0] = 1; + } else { + reply[0] = -1; + } + #else + reply[0] = -1; + #endif + + break; + + case 's':; // FARTTY_INTERRACT_CLIP_SETDATA + + // set data + + if (term->clip_allowed == 1 && d_count >= 4 + 4 + 3) { + + #ifdef _WINDOWS + + uint32_t fmt; + char* buffer = NULL; + int BufferSize = 0; + memcpy(&fmt, d_out + d_count - 3 - 4, sizeof(uint32_t)); + + // id, 'c', 's', 4-byte fmt, next goes 4-byte len + memcpy(&len, d_out + d_count - 3 - 4 - 4, sizeof(DWORD)); + if (len > d_count - 3 - 4 - 4) + len = d_count - 3 - 4 - 4; + + if (fmt == CF_TEXT) { + int cnt = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)d_out, len, NULL, 0); + if (cnt > 0) { + buffer = calloc(cnt + 1, sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, (LPCCH)d_out, len, (PWCHAR)buffer, cnt); + } + fmt = CF_UNICODETEXT; + BufferSize = (wcslen((PWCHAR)buffer) + 1) * sizeof(WCHAR); + + } else if (fmt == CF_UNICODETEXT) { + // very stupid utf32->utf16 'conversion' + buffer = calloc((len / sizeof(uint32_t)) + 1, sizeof(wchar_t)); + for (int i=0; i < len / sizeof(uint32_t); ++i) { + memcpy( + &buffer[i * sizeof(wchar_t)], + &d_out[i * sizeof(uint32_t)], + sizeof(wchar_t) + ); + } + BufferSize = (wcslen((PWCHAR)buffer) + 1) * sizeof(WCHAR); + + } else if (fmt >= 0xC000) { + // no transcoding - copy it as is + buffer = malloc(len); + memcpy(buffer, &d_out[0], len); + BufferSize = len; + } + + // clipboard stuff itself + + HGLOBAL hData; + void *GData; + + bool set_successful = 0; + + if (buffer && (hData=GlobalAlloc(GMEM_MOVEABLE,BufferSize))) { + + if ((GData=GlobalLock(hData))) { + + memcpy(GData,buffer,BufferSize); + GlobalUnlock(hData); + + if (OpenClipboard(NULL)) { + + if (!SetClipboardData(fmt, (HANDLE)hData)) { + GlobalFree(hData); + } else { + set_successful = 1; + } + + CloseClipboard(); + + } else { + GlobalFree(hData); + } + + } else { + GlobalFree(hData); + } + } + + free(buffer); + + #endif + + // prepare reply + reply_size = 2; + reply = malloc(reply_size); + + // first reply byte is status + #ifdef _WINDOWS + reply[0] = set_successful; + #else + reply[0] = 0; + #endif + + } else { + + reply_size = 2; + reply = malloc(reply_size); + + reply[0] = 0; + } + + break; + + case 'g':; // FARTTY_INTERRACT_CLIP_GETDATA + + if (term->clip_allowed == 1) { + + #ifdef _WINDOWS + + uint32_t gfmt; + memcpy(&gfmt, d_out + d_count - 3 - 4, sizeof(uint32_t)); + + // clipboard stuff itself + + void *ClipText = NULL; + int32_t ClipTextSize = 0; + + if ((gfmt == CF_TEXT || gfmt == CF_UNICODETEXT || gfmt >= 0xC000) && + OpenClipboard(NULL)) + { + HANDLE hClipData = GetClipboardData((gfmt == CF_TEXT) ? CF_UNICODETEXT : gfmt); + + if (hClipData) + { + void *pClipData=GlobalLock(hClipData); + + if (pClipData) + { + size_t n = GlobalSize(hClipData); + + if (gfmt == CF_TEXT) { + + ClipTextSize = WideCharToMultiByte( + CP_UTF8, + 0, + (wchar_t *)pClipData, + n, + NULL, + 0, + NULL, + NULL + ) + 1; + + if (ClipTextSize >= 0) { + n = wcsnlen((wchar_t *)pClipData, n / sizeof(wchar_t)); + ClipText = calloc(ClipTextSize + 1, 1); + if (ClipText) { + WideCharToMultiByte( + CP_UTF8, + 0, + (wchar_t *)pClipData, + n, + (char *)ClipText, + ClipTextSize, + NULL, + NULL + ); + ClipTextSize = strlen((char *)ClipText) + 1; + } + } + + } else if (gfmt == CF_UNICODETEXT) { + n = wcsnlen((wchar_t *)pClipData, n / sizeof(wchar_t)); + ClipText = calloc((n + 1), sizeof(uint32_t)); + if (ClipText) { + for (size_t i = 0; i < n; ++i) { + ((uint32_t *)ClipText)[i] = ((uint16_t *)pClipData)[i]; + } + ClipTextSize = (n + 1) * sizeof(uint32_t); + } + + } else { + ClipText = malloc(n); + if (ClipText) { + memcpy(ClipText, pClipData, n); + ClipTextSize = n; + } + } + + GlobalUnlock(hClipData); + } + + } else { + // todo: process errors + } + CloseClipboard(); + } + + + if (!ClipText || ClipTextSize <= 0) { + + // clipboard is empty + reply_size = 5; // 4 bytes for size and one for id + reply = calloc(1, reply_size); + + } else { + + // + length (4 bytes) + id (1 byte) + reply_size = ClipTextSize + 5; + reply = calloc(1, reply_size); + memcpy(reply, ClipText, ClipTextSize); + + // set size + memcpy(reply + ClipTextSize, &ClipTextSize, sizeof(ClipTextSize)); + } + + free(ClipText); + + #else + reply_size = 5; + reply = calloc(1, reply_size); + #endif + + } else { + + // we should never reach here + // anyway, mimic empty clipboard + + reply_size = 5; + reply = calloc(1, reply_size); + } + + break; + } + + break; + } + + free(d_out); + + if (reply_size > 0) { + // request is correct and we should send reply + + // last byte is always id + reply[reply_size-1] = id; + + // ok, let us try to answer something + + // base64-encode + // result in null-terminated char* out + base64_encodestate _state; + base64_init_encodestate(&_state); + + char* out = malloc(reply_size*2); + int count = base64_encode_block((char*)reply, reply_size, out, &_state); +#ifdef MOD_PERSO + count += base64_encode_blockend(out + count, &_state); +#else + // finishing '=' characters + char* next_char = out + count; + switch (_state.step) + { + case step_B: + *next_char++ = base64_encode_value(_state.result); + *next_char++ = '='; + *next_char++ = '='; + break; + case step_C: + *next_char++ = base64_encode_value(_state.result); + *next_char++ = '='; + break; + case step_A: + break; + } + count = next_char - out; + out[count] = 0; +#endif + + // send escape seq + + char* str = "\x1b_far2l"; + backend_send(term->backend, str, strlen(str)); + + backend_send(term->backend, out, count); + + char* str2 = "\x07"; + backend_send(term->backend, str2, strlen(str2)); + + // don't forget to free memory :) + free(reply); + free(out); + + } + } + } + + term->osc_strlen = 0; + term->is_apc = 0; + + } else +#endif + 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->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->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; + } + } +} + +#ifdef MOD_PRINTCLIP + /* + * Windows clipboard support + * Diomidis Spinellis, June 2003 + */ + static char *clip_b, *clip_bp; /* Buffer, pointer to buffer insertion point */ + static size_t clip_bsiz, clip_remsiz; /* Buffer, size, remaining size */ + static size_t clip_total; /* Total read */ + + #define CLIP_CHUNK 16384 + + static void clipboard_init(void) + { + if (clip_b) + sfree(clip_b); + clip_bp = clip_b = smalloc(clip_remsiz = clip_bsiz = CLIP_CHUNK); + clip_total = 0; + } + + static void clipboard_data(const void *buff, int len) + { + memcpy(clip_bp, buff, len); + clip_remsiz -= len; + clip_total += len; + clip_bp += len; + if (clip_remsiz < CLIP_CHUNK) { + clip_b = srealloc(clip_b, clip_bsiz *= 2); + clip_remsiz = clip_bsiz - clip_total; + clip_bp = clip_b + clip_total; + } + } + + static void clipboard_copy(void) + { + HANDLE hglb; + + if (!OpenClipboard(NULL)) + return; // error("Unable to open the clipboard"); + if (!EmptyClipboard()) { + CloseClipboard(); + return; // error("Unable to empty the clipboard"); + } + + hglb = GlobalAlloc(GMEM_DDESHARE, clip_total + 1); + if (hglb == NULL) { + CloseClipboard(); + return; // error("Unable to allocate clipboard memory"); + } + memcpy(hglb, clip_b, clip_total); + ((char *)hglb)[clip_total] = '\0'; + SetClipboardData(CF_TEXT, hglb); + CloseClipboard(); + } +#endif + +/* + * ANSI printing routines. + */ +static void term_print_setup(Terminal *term, char *printer) +{ + bufchain_clear(&term->printer_buf); +#ifdef MOD_PRINTCLIP + if ( conf_get_int(term->conf,CONF_printclip) ) + clipboard_init(); + else +#endif + 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; +#ifdef MOD_PRINTCLIP + if ( conf_get_int(term->conf,CONF_printclip) ) + clipboard_data(data.ptr, data.len); + else +#endif + 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 { +#ifdef MOD_PRINTCLIP + if ( conf_get_int(term->conf,CONF_printclip) ) + clipboard_data(&c, 1); + else +#endif + printer_job_data(term->print_job, &c, 1); + bufchain_consume(&term->printer_buf, 1); + } + } +#ifdef MOD_PRINTCLIP + if ( conf_get_int(term->conf,CONF_printclip) ) + clipboard_copy(); + else +#endif + 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; + + /* + * Preliminary check: if the terminal is only one character cell + * wide, then we cannot display any double-width character at all. + * Substitute single-width REPLACEMENT CHARACTER instead. + */ + if (width == 2 && linecols < 2) { + width = 1; + c = 0xFFFD; + } + + switch (width) { + case 2: + /* + * If we're about to display a double-width character starting + * in the rightmost column, then we do something special + * instead. We must print a space in the last column of the + * screen, then wrap; and we also set LATTR_WRAPPED2 which + * instructs subsequent cut-and-pasting not only to splice + * this line to the one after it, but to ignore the space in + * the last character position as well. (Because what was + * actually output to the terminal was presumably just a + * sequence of CJK characters, and we don't want a space to be + * pasted in the middle of those just because they had the + * misfortune to start in the wrong parity column. xterm + * concurs.) + */ + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+2, term->curs.y); + if (term->curs.x >= linecols-1) { + copy_termchar(cline, term->curs.x, + &term->erase_char); + cline->lattr |= LATTR_WRAPPED | LATTR_WRAPPED2; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, + 1, true); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->curs.x = 0; + cline = scrlineptr(term->curs.y); + /* Now we must check_boundary again, of course. */ + check_boundary(term, term->curs.x, term->curs.y); + check_boundary(term, term->curs.x+2, term->curs.y); + } + + /* FULL-TERMCHAR */ + clear_cc(cline, term->curs.x); + cline->chars[term->curs.x].chr = c; + cline->chars[term->curs.x].attr = term->curr_attr; + 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 = '.'; + } + } + + char utf8_chr[6]; + put_data(buf, utf8_chr, encode_utf8(utf8_chr, 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, term->ucsdata); + 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); + } + 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 { +#ifdef ASCPORT + /* unless we don't */ + if (!conf_get_bool(term->conf,CONF_acs_in_utf)) + return c | CSET_ASCII; +#else + return c | CSET_ASCII; +#endif + } + } 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) +{ + unsigned long c; + int unget; + unsigned char localbuf[256], *chars; + size_t nchars = 0; + + unget = -1; + + chars = NULL; /* placate compiler warnings */ + while (nchars > 0 || unget != -1 || bufchain_size(&term->inbuf) > 0) { + if (unget == -1) { + if (nchars == 0) { + ptrlen data = bufchain_prefix(&term->inbuf); + if (data.len > sizeof(localbuf)) + data.len = sizeof(localbuf); + memcpy(localbuf, data.ptr, data.len); + bufchain_consume(&term->inbuf, data.len); + nchars = data.len; + chars = localbuf; + assert(chars != NULL); + assert(nchars > 0); + } + c = *chars++; + nchars--; + + /* + * Optionally log the session traffic to a file. Useful for + * debugging and possibly also useful for actual logging. + */ + if (term->logtype == LGTYP_DEBUG && term->logctx) + logtraffic(term->logctx, (unsigned char) c, LGTYP_DEBUG); + } else { + c = unget; + unget = -1; + } + + /* Note only VT220+ are 8-bit VT102 is seven bit, it shouldn't even + * be able to display 8-bit characters, but I'll let that go 'cause + * of i18n. + */ + + /* + * If we're printing, add the character to the printer + * buffer. + */ + if (term->printing) { + bufchain_add(&term->printer_buf, &c, 1); + + /* + * If we're in print-only mode, we use a much simpler + * state machine designed only to recognise the ESC[4i + * termination sequence. + */ + if (term->only_printing) { + if (c == '\033') + term->print_state = 1; + else if (c == (unsigned char)'\233') + term->print_state = 2; + else if (c == '[' && term->print_state == 1) + term->print_state = 2; + else if (c == '4' && term->print_state == 2) + term->print_state = 3; + else if (c == 'i' && term->print_state == 3) + term->print_state = 4; + else + term->print_state = 0; + if (term->print_state == 4) { + term_print_finish(term); + } + continue; + } + } + + /* 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); +#ifdef MOD_HYPERLINK + assert(term->erase_char.cc_next == 0); +#endif + copy_termchar(scrlineptr(term->curs.y), + term->curs.x, &term->erase_char); + } + } else + /* Or normal C0 controls. */ + if ((c & ~0x1F) == 0 && term->termstate < DO_CTRLS) { + switch (c) { + case '\005': /* ENQ: terminal type query */ + /* + * Strictly speaking this is VT100 but a VT100 defaults to + * no response. Other terminals respond at their option. + * + * Don't put a CR in the default string as this tends to + * upset some weird software. + */ + compatibility(ANSIMIN); + if (term->ldisc) { + strbuf *buf = term_input_data_from_charset( + term, DEFAULT_CODEPAGE, + term->answerback, term->answerbacklen); + ldisc_send(term->ldisc, buf->s, buf->len, false); + strbuf_free(buf); + } + break; + case '\007': { /* BEL: Bell */ + struct beeptime *newbeep; + unsigned long ticks; + + ticks = GETTICKCOUNT(); + + if (!term->beep_overloaded) { + newbeep = snew(struct beeptime); + newbeep->ticks = ticks; + newbeep->next = NULL; + if (!term->beephead) + term->beephead = newbeep; + else + term->beeptail->next = newbeep; + term->beeptail = newbeep; + term->nbeeps++; + } + + /* + * Throw out any beeps that happened more than + * t seconds ago. + */ + while (term->beephead && + term->beephead->ticks < ticks - term->bellovl_t) { + struct beeptime *tmp = term->beephead; + term->beephead = tmp->next; + sfree(tmp); + if (!term->beephead) + term->beeptail = NULL; + term->nbeeps--; + } + + if (term->bellovl && term->beep_overloaded && + ticks - term->lastbeep >= (unsigned)term->bellovl_s) { + /* + * If we're currently overloaded and the + * last beep was more than s seconds ago, + * leave overload mode. + */ + term->beep_overloaded = false; + } else if (term->bellovl && !term->beep_overloaded && + term->nbeeps >= term->bellovl_n) { + /* + * Now, if we have n or more beeps + * remaining in the queue, go into overload + * mode. + */ + term->beep_overloaded = true; + } + term->lastbeep = ticks; + + /* + * Perform an actual beep if we're not overloaded. + */ + if (!term->bellovl || !term->beep_overloaded) { + 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->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--; + else if (term->wrapnext) + term->wrapnext = false; + else + term->curs.x--; + seen_disp_event(term); + break; + case '\016': /* LS1: Locking-shift one */ + compatibility(VT100); + term->cset = 1; + break; + case '\017': /* LS0: Locking-shift zero */ + compatibility(VT100); + term->cset = 0; + break; + case '\033': /* ESC: Escape */ + if (term->vt52_mode) + term->termstate = VT52_ESC; + else { + compatibility(ANSIMIN); + term->termstate = SEEN_ESC; + term->esc_query = 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; +#ifdef MOD_FAR2L + case '_': /* far2l: processing APC is almost the same as processing OSC */ + term->is_apc = 1; + //break; + //case SEEN_APC: + /* todo */ + // if (!WriteStr2TC(fdout, enable ? "\x1b_far2l1\x1b\\\x1b[5n" : "\x1b_far2l0\x07\x1b[5n")) + // if (!WriteStr2TC(fdout, enable ? "\x1b_far2l1\x1b\\\x1b[5n" : "\x1b_far2l0\x07\x1b[5n")) + //break; + case ']': /* OSC: xterm escape sequences */ +#else + case ']': /* OSC: xterm escape sequences */ +#endif + /* Compatibility is nasty here, xterm, linux, decterm yuk! */ + compatibility(OTHER); + term->termstate = SEEN_OSC; + 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); + seen_disp_event(term); + break; + case '=': /* DECKPAM: Keypad application mode */ + compatibility(VT100); + term->app_keypad_keys = true; + break; + case '>': /* DECKPNM: Keypad numeric mode */ + compatibility(VT100); + term->app_keypad_keys = false; + break; + case 'D': /* IND: exactly equivalent to LF */ + compatibility(VT100); + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, true); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->wrapnext = false; + seen_disp_event(term); + break; + case 'E': /* NEL: exactly equivalent to CR-LF */ + compatibility(VT100); + term->curs.x = 0; + if (term->curs.y == term->marg_b) + scroll(term, term->marg_t, term->marg_b, 1, true); + else if (term->curs.y < term->rows - 1) + term->curs.y++; + term->wrapnext = false; + seen_disp_event(term); + break; + case 'M': /* RI: reverse index - backwards LF */ + compatibility(VT100); + if (term->curs.y == term->marg_t) + scroll(term, term->marg_t, term->marg_b, -1, true); + else if (term->curs.y > 0) + term->curs.y--; + term->wrapnext = false; + seen_disp_event(term); + break; + case 'Z': /* DECID: terminal type query */ + compatibility(VT100); + if (term->ldisc) + ldisc_send(term->ldisc, term->id_string, + strlen(term->id_string), 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->win_resize_pending = true; + term->win_resize_pending_w = 80; + term->win_resize_pending_h = term->rows; + term_schedule_update(term); + } + 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; + 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; +#ifdef MOD_PERSO + } else if (c == 'q') { + // cursor shape type (DECSCUSR) + int cursor_shape = def(term->esc_args[0], 1); + // set cursor_type from shape (cursor_type: 0=block, 1=underline, 2=beam) + SetCursorType( (cursor_shape - 1) >> 1 ); + // set term->blink_cur from shape (odd numbers indicate blinking in DECSCUSR) + if (term->blink_cur != (cursor_shape & 1)) + { + term->blink_cur = cursor_shape & 1; + term_reset_cblink(term); + } + term->termstate = TOPLEVEL; +#endif + } 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 && +#ifdef MOD_PRINTCLIP + ((printer = conf_get_str(term->conf, + CONF_printer))[0] || conf_get_int(term->conf,CONF_printclip)) ) { +#else + (printer = conf_get_str(term->conf, + CONF_printer))[0]) { +#endif + 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; +#ifdef MOD_PERSO + case 3: /* enable italics */ + compatibility(ANSI); + term->curr_attr |= ATTR_ITALIC; + break; +#endif + 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; +#ifdef MOD_PERSO + case 23: /* disable italics */ + compatibility(ANSI); + term->curr_attr &= ~ATTR_ITALIC; + break; +#endif + 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); + seen_disp_event(term); + break; + case 't': /* DECSLPP: set page size - ie window height */ + /* + * VT340/VT420 sequence DECSLPP, DEC only allows values + * 24/25/36/48/72/144 other emulators (eg dtterm) use + * illegal values (eg first arg 1..9) for window changing + * and reports. + */ + if (term->esc_nargs <= 1 + && (term->esc_args[0] < 1 || + term->esc_args[0] >= 24)) { + compatibility(VT340TEXT); + if (!term->no_remote_resize) { + term->win_resize_pending = true; + term->win_resize_pending_w = term->cols; + term->win_resize_pending_h = + def(term->esc_args[0], 24); + term_schedule_update(term); + } + 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->win_resize_pending = true; + term->win_resize_pending_w = + def(term->esc_args[2], + term->conf_width); + term->win_resize_pending_h = + def(term->esc_args[1], + term->conf_height); + term_schedule_update(term); + } + 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; + seen_disp_event(term); + break; + case 'T': /* SD: Scroll down */ + CLAMP(term->esc_args[0], term->rows); + compatibility(SCOANSI); + scroll(term, term->marg_t, term->marg_b, + -def(term->esc_args[0], 1), true); + term->wrapnext = false; + seen_disp_event(term); + break; + case ANSI('|', '*'): /* DECSNLS */ + /* + * Set number of lines on screen + * VT420 uses VGA like hardware and can + * support any size in reasonable range + * (24..49 AIUI) with no default specified. + */ + compatibility(VT420); + if (term->esc_nargs == 1 && term->esc_args[0] > 0) { + if (!term->no_remote_resize) { + term->win_resize_pending = true; + term->win_resize_pending_w = term->cols; + term->win_resize_pending_h = + def(term->esc_args[0], term->conf_height); + term_schedule_update(term); + } + 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->win_resize_pending = true; + term->win_resize_pending_w = + def(term->esc_args[0], term->conf_width); + term->win_resize_pending_h = term->rows; + term_schedule_update(term); + } + 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->win_resize_pending = true; + term->win_resize_pending_w = + term->reset_132 ? 132 : 80; + term->win_resize_pending_h = 24; + term_schedule_update(term); + } + } +#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; + 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; +#ifdef MOD_FAR2L + /* far2l */ + if (term->is_apc) { + term->osc_string[term->osc_strlen++] = (char)c; + } +#endif + } + } + break; + case OSC_STRING: + /* + * This OSC stuff is EVIL. It takes just one character to get into + * sysline mode and it's not initially obvious how to get out. + * So I've added CR and LF as string aborts. + * This shouldn't effect compatibility as I believe embedded + * control characters are supposed to be interpreted (maybe?) + * and they don't display anything useful anyway. + * + * -- RDB + */ + if (c == '\012' || c == '\015') { + term->termstate = TOPLEVEL; + } else if (c == 0234 || c == '\007') { + /* + * These characters terminate the string; ST and BEL + * terminate the sequence and trigger instant + * processing of it, whereas ESC goes back to SEEN_ESC + * mode unless it is followed by \, in which case it is + * synonymous with ST in the first place. + */ + do_osc(term); + term->termstate = TOPLEVEL; + } else if (c == '\033') + term->termstate = OSC_MAYBE_ST; + else if (term->osc_strlen < OSC_STR_MAX) + term->osc_string[term->osc_strlen++] = (char)c; + break; + case SEEN_OSC_P: { + int max = (term->osc_strlen == 0 ? 21 : 15); + int val; + if ((int)c >= '0' && (int)c <= '9') + val = c - '0'; + else if ((int)c >= 'A' && (int)c <= 'A' + max - 10) + val = c - 'A' + 10; + else if ((int)c >= 'a' && (int)c <= 'a' + max - 10) + val = c - 'a' + 10; + else { + term->termstate = TOPLEVEL; + break; + } + term->osc_string[term->osc_strlen++] = val; + if (term->osc_strlen >= 7) { + 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; + default: + term->termstate = OSC_STRING; + term->osc_strlen = 0; + } + break; + case VT52_ESC: + term->termstate = TOPLEVEL; + seen_disp_event(term); + switch (c) { + case 'A': + move(term, term->curs.x, term->curs.y - 1, 1); + break; + case 'B': + move(term, term->curs.x, term->curs.y + 1, 1); + break; + case 'C': + move(term, term->curs.x + 1, term->curs.y, 1); + break; + case 'D': + move(term, term->curs.x - 1, term->curs.y, 1); + break; + /* + * From the VT100 Manual + * NOTE: The special graphics characters in the VT100 + * are different from those in the VT52 + * + * From VT102 manual: + * 137 _ Blank - Same + * 140 ` Reserved - Humm. + * 141 a Solid rectangle - Similar + * 142 b 1/ - Top half of fraction for the + * 143 c 3/ - subscript numbers below. + * 144 d 5/ + * 145 e 7/ + * 146 f Degrees - Same + * 147 g Plus or minus - Same + * 150 h Right arrow + * 151 i Ellipsis (dots) + * 152 j Divide by + * 153 k Down arrow + * 154 l Bar at scan 0 + * 155 m Bar at scan 1 + * 156 n Bar at scan 2 + * 157 o Bar at scan 3 - Similar + * 160 p Bar at scan 4 - Similar + * 161 q Bar at scan 5 - Similar + * 162 r Bar at scan 6 - Same + * 163 s Bar at scan 7 - Similar + * 164 t Subscript 0 + * 165 u Subscript 1 + * 166 v Subscript 2 + * 167 w Subscript 3 + * 170 x Subscript 4 + * 171 y Subscript 5 + * 172 z Subscript 6 + * 173 { Subscript 7 + * 174 | Subscript 8 + * 175 } Subscript 9 + * 176 ~ Paragraph + * + */ + case 'F': + term->cset_attr[term->cset = 0] = CSET_LINEDRW; + break; + case 'G': + term->cset_attr[term->cset = 0] = CSET_ASCII; + break; + case 'H': + move(term, 0, 0, 0); + break; + case 'I': + if (term->curs.y == 0) + scroll(term, 0, term->rows - 1, -1, true); + else if (term->curs.y > 0) + term->curs.y--; + term->wrapnext = false; + break; + case 'J': + erase_lots(term, false, false, true); + if (term->scroll_on_disp) + term->disptop = 0; + break; + case 'K': + erase_lots(term, true, false, true); + break; +#if 0 + case 'V': + /* XXX Print cursor line */ + break; + case 'W': + /* XXX Start controller mode */ + break; + case 'X': + /* XXX Stop controller mode */ + break; +#endif + case 'Y': + term->termstate = VT52_Y1; + break; + case 'Z': + if (term->ldisc) + ldisc_send(term->ldisc, "\033/Z", 3, 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; + break; + case 'f': + /* compatibility(ATARI) */ + term->cursor_on = false; + break; + /* case 'j': Save cursor position - broken on ST */ + /* case 'k': Restore cursor position */ + case 'l': + /* compatibility(ATARI) */ + erase_lots(term, true, true, true); + term->curs.x = 0; + term->wrapnext = false; + break; + case 'o': + /* compatibility(ATARI) */ + erase_lots(term, true, true, false); + break; + case 'p': + /* compatibility(ATARI) */ + term->curr_attr |= ATTR_REVERSE; + break; + case 'q': + /* compatibility(ATARI) */ + term->curr_attr &= ~ATTR_REVERSE; + break; + case 'v': /* wrap Autowrap on - Wyse style */ + /* compatibility(ATARI) */ + term->wrap = 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); + } + } + + term_print_flush(term); + if (term->logflush && term->logctx) + logflush(term->logctx); +} + +/* + * 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->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; +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + * Hyperlink stuff: Find visible hyperlinks + * + * TODO: We should find out somehow that the stuff on screen has changed since last + * paint. How to do it? + */ + int urlhack_underline_always = conf_get_int(term->conf, CONF_url_underline) == URLHACK_UNDERLINE_ALWAYS; + + int urlhack_underline = + conf_get_int(term->conf, CONF_url_underline) == URLHACK_UNDERLINE_ALWAYS || + (conf_get_int(term->conf, CONF_url_underline) == URLHACK_UNDERLINE_HOVER && (!conf_get_int(term->conf, CONF_url_ctrl_click) || urlhack_is_ctrl_pressed())) ? 1 : 0; + + int urlhack_is_link = 0, urlhack_hover_current = 0; + int urlhack_toggle_x = term->cols, urlhack_toggle_y = term->rows; + int urlhack_region_index = 0; + text_region urlhack_region; + + if( !GetPuttyFlag() && GetHyperlinkFlag() ) { + if (term->url_update) { + urlhack_reset(); + for (i = 0; i < term->rows; i++) { + termline *lp = lineptr(term->disptop + i); + + for (j = 0; j < term->cols; j++) { + unsigned long tchar = lp->chars[j].chr; + urlhack_putchar(tchar & CHAR_MASK ? (char)(tchar & CHAR_MASK) : ' '); + //urlhack_putchar((char)(lp->chars[j].chr & CHAR_MASK)); + } + unlineptr(lp); + } + urlhack_go_find_me_some_hyperlinks(term->cols); + } + urlhack_region = urlhack_get_link_region(urlhack_region_index); + urlhack_toggle_x = urlhack_region.x0; + urlhack_toggle_y = urlhack_region.y0; + if (urlhack_underline_always) + urlhack_hover_current = 1; + else + urlhack_hover_current = urlhack_is_in_this_link_region(urlhack_region, urlhack_mouse_old_x, urlhack_mouse_old_y); + } + /* HACK: PuttyTray / Nutty : END */ +#endif + + 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; +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + * Hyperlink stuff: Underline link regions if user has configured us so + */ + if( !GetPuttyFlag() && GetHyperlinkFlag() ) { + if (urlhack_underline) { + if (j == urlhack_toggle_x && i == urlhack_toggle_y) { + urlhack_is_link = urlhack_is_link == 1 ? 0 : 1; + + // Find next bound for the toggle + + if (urlhack_is_link == 1) { + urlhack_toggle_x = urlhack_region.x1; + urlhack_toggle_y = urlhack_region.y1; + + if (urlhack_toggle_x == term->cols - 1) { + // Handle special case where link ends at the last char of the row + urlhack_toggle_y++; + urlhack_toggle_x = 0; + } + } + else { + urlhack_region = urlhack_get_link_region(++urlhack_region_index); + + if (urlhack_underline_always) + urlhack_hover_current = 1; + else + urlhack_hover_current = urlhack_is_in_this_link_region(urlhack_region, urlhack_mouse_old_x, urlhack_mouse_old_y); + + urlhack_toggle_x = urlhack_region.x0; + urlhack_toggle_y = urlhack_region.y0; + } + } + if (urlhack_is_link == 1 && urlhack_hover_current == 1) { + tattr |= ATTR_UNDER; + } + + term->url_update = 0; + } + } + /* HACK: PuttyTray / Nutty : END */ +#endif + + /* 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; +#ifdef MOD_TUTTYCOLOR + tattr = (tattr ^ rv ^ (selected ? ATTR_SELECTED : 0)); +#else + tattr = (tattr ^ rv + ^ (selected ? ATTR_REVERSE : 0)); +#endif + + /* '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) + 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); +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + */ + if( !GetPuttyFlag() && GetHyperlinkFlag() ) term->url_update = TRUE; +#endif + + 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++; +} + +#ifdef MOD_HYPERLINK +static void clipme(Terminal *term, pos top, pos bottom, bool rect, bool desel, + const int *clipboards, int n_clipboards , + void (*output)(TermWin *, int, wchar_t *, int *, truecolour *, int, bool)) +#else +static void clipme(Terminal *term, pos top, pos bottom, bool rect, bool desel, + const int *clipboards, int n_clipboards) +#endif +{ + 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; +#ifdef MOD_HYPERLINK + clipme(term, top, bottom, false, true, clipboards, n_clipboards, wintw_clip_write); +#else + clipme(term, top, bottom, false, true, clipboards, n_clipboards); +#endif +} + +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; +#ifdef MOD_PERSO + bool raw_mouse = (term->xterm_mouse_mode && +#else + bool raw_mouse = (term->xterm_mouse && +#endif + !term->no_mouse_rep && + !(term->mouse_override && shift)); + int default_seltype; + + if (y < 0) { + y = 0; + if (a == MA_DRAG && !raw_mouse) + term_scroll(term, 0, -1); + } + if (y >= term->rows) { + y = term->rows - 1; + if (a == MA_DRAG && !raw_mouse) + term_scroll(term, 0, +1); + } + if (x < 0) { + if (y > 0 && !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; +#ifdef MOD_HYPERLINK + if( !GetPuttyFlag() && GetHyperlinkFlag() ) unlineptr(ldata); +#else + unlineptr(ldata); +#endif + + /* + * 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; +#ifdef MOD_PERSO + char abuf[64]; + char m; /* SGR 1006's postfix character ('M' or 'm') */ + int l = 0; +#else + char abuf[32]; + int len = 0; +#endif + + 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; +#ifdef MOD_PERSO + case MBT_NOTHING: /* for any event tracking */ + encstate = 0x03; + wheel = false; + break; +#endif + 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: +#ifdef MOD_HYPERLINK + if( !GetPuttyFlag() && GetHyperlinkFlag() ) { + if (term->xterm_mouse_mode == MM_NORMAL) {// HACK: ADDED FOR hyperlink stuff + unlineptr(ldata); + return; + } + } + else { + if (term->xterm_mouse_mode == MM_NORMAL) + return; + } +#else +#ifdef MOD_PERSO + if (term->xterm_mouse_mode == MM_NORMAL) +#else + if (term->xterm_mouse == 1) +#endif + return; +#endif + encstate += 0x20; + break; +#ifdef MOD_PERSO + case MA_MOVE: + if (term->xterm_mouse_mode != MM_ANY_EVENT) + return; + encstate += 0x20; /* add motion indicator */ + break; +#endif + case MA_RELEASE: + /* If multiple extensions are enabled, the xterm 1006 is used, so it's okay to check for only that */ +#ifdef MOD_PERSO + if (term->xterm_mouse_protocol != MP_SGR) +#else + if (!term->xterm_extended_mouse) +#endif + encstate = 0x03; + term->mouse_is_down = 0; + break; + case MA_CLICK: +#ifdef MOD_HYPERLINK + if( !GetPuttyFlag() && GetHyperlinkFlag() ) { +#ifdef MOD_PUTTYX + if (term->mouse_is_down == braw && braw != MBT_WHEEL_UP && braw != MBT_WHEEL_DOWN) {// HACK: ADDED FOR hyperlink stuff // MORE HACKING (@unphased: allow sequences of mouse wheel up and mouse wheel down to pass through) +#else + if (term->mouse_is_down == braw) {// HACK: ADDED FOR hyperlink stuff +#endif + unlineptr(ldata); + return; + } + } + else { +#ifdef MOD_PUTTYX + if (term->mouse_is_down == braw && braw != MBT_WHEEL_UP && braw != MBT_WHEEL_DOWN) // MORE HACKING (@unphased: allow sequences of mouse wheel up and mouse wheel down to pass through) +#else + if (term->mouse_is_down == braw) +#endif + return; + } +#else +#ifdef MOD_PUTTYX + if (term->mouse_is_down == braw && braw != MBT_WHEEL_UP && braw != MBT_WHEEL_DOWN) // MORE HACKING (@unphased: allow sequences of mouse wheel up and mouse wheel down to pass through) +#else + if (term->mouse_is_down == braw) +#endif + return; +#endif + term->mouse_is_down = braw; + break; + default: + return; + } + if (shift) + encstate += 0x04; + if (ctrl) + encstate += 0x10; + +#ifdef MOD_PERSO + switch (term->xterm_mouse_protocol) { + + case MP_NORMAL: + /* add safety to avoid sending garbage sequences */ + if (x < 223 && y < 223) { + encstate += 0x20; + + r = y + 33; + c = x + 33; + sprintf(abuf, "\033[M%c%c%c", encstate, c, r); + ldisc_send(term->ldisc, abuf, 6, 0); + } + break; + case MP_URXVT: + encstate += 0x20; + r = y + 1; + c = x + 1; + sprintf(abuf, "\033[%d;%d;%dM", encstate, c, r); + l = strlen(abuf); + ldisc_send(term->ldisc, abuf, l, 0); + break; + case MP_SGR: + r = y + 1; + c = x + 1; + m = a == MA_RELEASE ? 'm': 'M'; + sprintf(abuf, "\033[<%d;%d;%d%c", encstate, c, r, m); + l = strlen(abuf); + ldisc_send(term->ldisc, abuf, l, 0); + // debug("SGR: %s\n", abuf); // recompile with -DDEBUG + break; + case MP_XTERM: + /* add safety to avoid sending garbage sequences */ + if (x < 2015 && y < 2015) { + encstate += 0x20; + wchar_t input[2]; + wchar_t *inputp = input; + int inputlen = 2; + input[0] = x + 33; + input[1] = y + 33; + + l = sprintf(abuf, "\033[M%c", encstate); + l += charset_from_unicode((const wchar_t **)&inputp, &inputlen, + abuf + l, 4, + CS_UTF8, NULL, NULL, 0); + ldisc_send(term->ldisc, abuf, l, 0); + } + break; + default: break; /* placate gcc warning about enum use */ + } + } +#else + 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); + } +#endif + +#ifdef MOD_HYPERLINK + if( !GetPuttyFlag() && GetHyperlinkFlag() ) { + unlineptr(ldata); // HACK: ADDED FOR hyperlink stuff + } +#endif + 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; +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + * Hyperlink stuff: Check whether the click coordinates are inside link + * region, if so -> copy url to temporary buffer and launch it. Delete + * the temporary buffer. + */ + } else if( !GetPuttyFlag() && GetHyperlinkFlag() && bcooked == MBT_SELECT && a == MA_RELEASE && term->selstate == ABOUT_TO) { + deselect(term); + term->selstate = NO_SELECTION; + + if ((!conf_get_int(term->conf, CONF_url_ctrl_click) || (conf_get_int(term->conf, CONF_url_ctrl_click) && urlhack_is_ctrl_pressed())) && urlhack_is_in_link_region(x, y)) { + int i; + char *linkbuf = NULL; + text_region region = urlhack_get_link_bounds(x, y); + + if (region.y0 == region.y1) { + linkbuf = snewn(region.x1 - region.x0 + 2, char); + + for (i = region.x0; i < region.x1; i++) { + linkbuf[i - region.x0] = (char)(ldata->chars[i].chr); + } + + linkbuf[i - region.x0] = '\0'; + } + else { + termline *urldata = lineptr(region.y0 + term->disptop); + int linklen, /*pos = region.x0,*/ row = region.y0 + term->disptop; + + linklen = (term->cols - region.x0) + + ((region.y1 - region.y0 - 1) * term->cols) + region.x1 + 1; + + linkbuf = snewn(linklen, char); + + for (i = region.x0; i < linklen + region.x0; i++) { + linkbuf[i - region.x0] = (char)(urldata->chars[i % term->cols].chr); + + // Jump to next line? + if (((i + 1) % term->cols) == 0) { + row++; + urldata = lineptr(row); + } + } + + linkbuf[linklen - 1] = '\0'; + unlineptr(urldata); + } + + urlhack_launch_url(!conf_get_int(term->conf, CONF_url_defbrowser) ? conf_get_filename(term->conf, CONF_url_browser)->path : NULL, linkbuf); + + sfree(linkbuf); + } + /* HACK: PuttyTray / Nutty : END */ +#endif + } 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; + } +#ifdef MOD_HYPERLINK + if( !GetPuttyFlag() && GetHyperlinkFlag() ) { + if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint)) { // HACK: ADDED FOR HYPERLINK STUFF + unlineptr(ldata); + return; + } + } + else { + if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint)) + return; + } +#else + if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint)) + return; +#endif + 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, +#ifdef MOD_HYPERLINK + term->n_mouse_select_clipboards,wintw_clip_write); +#else + term->n_mouse_select_clipboards); +#endif + 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. + */ + if (term->selstate != DRAGGING) + term_out(term); + term_schedule_update(term); +} + +#ifdef MOD_KEYMAPPING +int get_xterm_modifier(bool shift, bool ctrl, bool alt) +{ + int xterm_modifier = 1; // no modifier key was pressed + if (shift) + xterm_modifier += 1; + if (alt) + xterm_modifier += 2; + if (ctrl) + xterm_modifier += 4; + return xterm_modifier; +} +int format_arrow_key(char *buf, Terminal *term, int xkey, int modifier, bool alt) +#else +int format_arrow_key(char *buf, Terminal *term, int xkey, bool ctrl) +#endif +{ + 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 +#ifdef MOD_KEYMAPPING + if( !GetPuttyFlag() ) { + if (modifier == 3 && alt == 1) + p += sprintf((char *) p, "\x1B[1;8%c", xkey); /*Control+Alt-Shift */ + else if (modifier == 2 && alt == 1) + p += sprintf((char *) p, "\x1B[1;7%c", xkey); /* Control+Alt */ + else if (modifier == 3) + p += sprintf((char *) p, "\x1B[1;6%c", xkey); /* Control+Shift*/ + else if (modifier == 2) + p += sprintf((char *) p, "\x1B[1;5%c", xkey); /* Control */ + else if (modifier == 1 && alt == 1) + p += sprintf((char *) p, "\x1B[1;4%c", xkey); /* Alt+Shift */ + else if (alt == 1) + p += sprintf((char *) p, "\x1B[1;3%c", xkey); /* Alt */ + else if (modifier == 1) + p += sprintf((char *) p, "\x1B[1;2%c", xkey); /* Shift */ + else if (app_flg) + p += sprintf((char *) p, "\x1BO%c", xkey); /* Application mode*/ + else + p += sprintf((char *) p, "\x1B[%c", xkey); /* Normal */ + } + else { + /* Useful mapping of Ctrl-arrows */ + if (modifier) + app_flg = !app_flg; + + if (app_flg) + p += sprintf((char *) p, "\x1BO%c", xkey); + else + p += sprintf((char *) p, "\x1B[%c", xkey); + } +#else + /* Useful mapping of Ctrl-arrows */ + if (ctrl) + app_flg = !app_flg; + + if (app_flg) + p += sprintf(p, "\x1BO%c", xkey); + else + p += sprintf(p, "\x1B[%c", xkey); +#endif + } + + return p - buf; +} + +#ifdef MOD_KEYMAPPING +int format_function_key(char *buf, Terminal *term, int key_number, int modifier, bool alt) +#else +int format_function_key(char *buf, Terminal *term, int key_number, + bool shift, bool ctrl) +#endif +{ + 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)); +#ifdef MOD_KEYMAPPING + bool shift = modifier & 1; + bool ctrl = modifier & 2; + if (term->funky_type == FUNKY_XTERM && !term->vt52_mode) + { + // XTerm mode + char prefix[20]; + char suffix[20]; + int xterm_modifier = get_xterm_modifier(shift, ctrl, alt); + if (xterm_modifier > 1) + { + sprintf(prefix, "[1;%d", xterm_modifier); + sprintf(suffix, ";%d", xterm_modifier); + } + else + { + sprintf(prefix, "O"); + strcpy(suffix, "");//sprintf(suffix, ""); + } + + int code = key_number_to_tilde_code[key_number]; + if (code >= 11 && code <= 14) + { + p += sprintf(p, "\x1B%s%c", prefix, code + 'P' - 11); + } + else + { + p += sprintf(p, "\x1B[%d%s~", code, suffix); + } + return p - buf; + } +#endif + + int index = (shift && key_number <= 10) ? key_number + 10 : key_number; + 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 && code >= 11 && code <= 14) { + if (term->vt52_mode) + p += sprintf(p, "\x1B%c", code + 'P' - 11); + else + p += sprintf(p, "\x1BO%c", code + 'P' - 11); + } else { + p += sprintf(p, "\x1B[%d~", code); + } + + return p - buf; +} + +#ifdef MOD_KEYMAPPING +int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key, int modifier, bool alt) +#else +int format_small_keypad_key(char *buf, Terminal *term, SmallKeypadKey key) +#endif +{ + 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"); + } +#ifdef MOD_KEYMAPPING + int home_end_type = conf_get_int(term->conf, CONF_rxvt_homeend); // standard(0), rxvt, urxvt, xterm(3), bsd1, bsd2 + bool shift = modifier & 1; + bool ctrl = modifier & 2; + if (term->funky_type == FUNKY_XTERM && !term->vt52_mode && (home_end_type == 0 || home_end_type == 3)) + { + // XTerm mode + char prefix[20]; + char suffix[20]; + bool app_flg = (term->app_cursor_keys && !term->no_applic_c); + int xterm_modifier = get_xterm_modifier(shift, ctrl, alt); + if (xterm_modifier > 1) + { + sprintf(prefix, "[1;%d", xterm_modifier); + sprintf(suffix, ";%d", xterm_modifier); + } + else + { + if (!app_flg) + sprintf(prefix, "["); + else + sprintf(prefix, "O"); + strcpy(suffix, "");//sprintf(suffix, ""); + } + + if ((xterm_modifier > 1 || app_flg || home_end_type == 3) && (code == 1 || code == 4)) + { + p += sprintf(p, "\x1B%s%c", prefix, code == 1 ? 'H' : 'F'); + } + else + { + p += sprintf(p, "\x1B[%d%s~", code, suffix); + } + + return p - buf; + } +#endif + + /* 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]); + } +#ifdef MOD_PERSO + } else if ((code == 1 || code == 4) && (conf_get_int(term->conf, CONF_rxvt_homeend) == 1) ) { +#else + } else if ((code == 1 || code == 4) && term->rxvt_homeend) { +#endif + p += sprintf(p, code == 1 ? "\x1B[H" : "\x1BOw"); + } else { +#ifdef MOD_PERSO + if ((code == 1 || code == 4) && + conf_get_int(term->conf, CONF_rxvt_homeend) == 2) { + // urxvt + p += sprintf((char *) p, code == 1 ? "\x1B[7~" : "\x1B[8~"); + } else if ((code == 1 || code == 4) && + conf_get_int(term->conf, CONF_rxvt_homeend) == 3) { + // xterm + p += sprintf((char *) p, code == 1 ? "\x1BOH" : "\x1BOF"); + } else if ((code == 1 || code == 4) && + conf_get_int(term->conf, CONF_rxvt_homeend) == 4) { + // FreeBSD1 + p += sprintf((char *) p, code == 1 ? "\x1B[H" : "\x1B[H"); + } else if ((code == 1 || code == 4) && + conf_get_int(term->conf, CONF_rxvt_homeend) == 5) { + // FreeBSD2 + p += sprintf((char *) p, code == 1 ? "\x1BOH" : "\x1B[?1l\x1B>"); + } else +#endif + + 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. + */ + if (term->selstate != DRAGGING) + term_out(term); +} + +static void term_added_data(Terminal *term) +{ + if (!term->in_term_out) { + term->in_term_out = true; + term_reset_cblink(term); + /* + * During drag-selects, we do not process terminal input, + * because the user will want the screen to hold still to + * be selected. + */ + if (term->selstate != DRAGGING) + term_out(term); + term->in_term_out = false; + } +} + +size_t term_data(Terminal *term, bool is_stderr, const void *data, size_t len) +{ +#ifdef MOD_ZMODEM + if ( GetZModemFlag() && term->xyz_transfering && !is_stderr) + { return xyz_ReceiveData(term, (const u_char *) data, len) ; } + else + { +#endif + bufchain_add(&term->inbuf, data, len); + term_added_data(term); + +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + */ + if( !GetPuttyFlag() && GetHyperlinkFlag() ) term->url_update = TRUE; +#endif +#ifdef MOD_ZMODEM + } +#endif + /* + * term_out() always completely empties inbuf. Therefore, + * there's no reason at all to return anything other than zero + * from this function, because there _can't_ be a question of + * the remote side needing to wait until term_out() has cleared + * a backlog. + * + * This is a slightly suboptimal way to deal with SSH-2 - in + * principle, the window mechanism would allow us to continue + * to accept data on forwarded ports and X connections even + * while the terminal processing was going slowly - but we + * can't do the 100% right thing without moving the terminal + * processing into a separate thread, and that might hurt + * portability. So we manage stdout buffering the old SSH-1 way: + * if the terminal processing goes slowly, the whole SSH + * connection stops accepting data until it's ready. + * + * In practice, I can't imagine this causing serious trouble. + */ + return 0; +} + +void term_provide_logctx(Terminal *term, LogContext *logctx) +{ + term->logctx = logctx; +} + +void term_set_focus(Terminal *term, bool has_focus) +{ +#ifdef MOD_PERSO + if( term->has_focus != has_focus) { + term->has_focus = has_focus; + + if(term->report_focus && term->ldisc) { + if (has_focus) + ldisc_send(term->ldisc, "\033[I", 3,false); + else + ldisc_send(term->ldisc, "\033[O", 3,false); + } + } +#else + term->has_focus = has_focus; +#endif + 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 { + size_t curr_prompt; + bool done_prompt; /* printed out prompt yet? */ +}; + +/* Tiny wrapper to make it easier to write lots of little strings */ +static inline void term_write(Terminal *term, ptrlen data) +{ + term_data(term, false, data.ptr, data.len); +} + +/* + * Process some terminal data in the course of username/password + * input. + */ +int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input) +{ + struct term_userpass_state *s = (struct term_userpass_state *)p->data; + if (!s) { + /* + * First call. Set some stuff up. + */ + p->data = s = snew(struct term_userpass_state); + s->curr_prompt = 0; + s->done_prompt = false; + /* 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], ""); + } + } + + while (s->curr_prompt < p->n_prompts) { + + prompt_t *pr = p->prompts[s->curr_prompt]; + bool finished_prompt = false; + + if (!s->done_prompt) { + term_write(term, ptrlen_from_asciz(pr->prompt)); + s->done_prompt = true; + } + + /* Breaking out here ensures that the prompt is printed even + * if we're now waiting for user data. */ + if (!input || !bufchain_size(input)) break; + + /* FIXME: should we be using local-line-editing code instead? */ + while (!finished_prompt && bufchain_size(input) > 0) { + char c; + bufchain_fetch_consume(input, &c, 1); + switch (c) { + case 10: + case 13: + term_write(term, PTRLEN_LITERAL("\r\n")); + /* go to next prompt, if any */ + s->curr_prompt++; + s->done_prompt = false; + finished_prompt = true; /* break out */ + break; + case 8: + case 127: + if (pr->result->len > 0) { + if (pr->echo) + term_write(term, PTRLEN_LITERAL("\b \b")); + strbuf_shrink_by(pr->result, 1); + } + break; + case 21: + case 27: + while (pr->result->len > 0) { + if (pr->echo) + term_write(term, PTRLEN_LITERAL("\b \b")); + strbuf_shrink_by(pr->result, 1); + } + break; + case 3: + case 4: + /* Immediate abort. */ + term_write(term, PTRLEN_LITERAL("\r\n")); + sfree(s); + p->data = NULL; + return 0; /* user abort */ + default: + /* + * This simplistic check for printability is disabled + * when we're doing password input, because some people + * have control characters in their passwords. + */ + if (!pr->echo || (c >= ' ' && c <= '~') || + ((unsigned char) c >= 160)) { + put_byte(pr->result, c); + if (pr->echo) + term_write(term, make_ptrlen(&c, 1)); + } + break; + } + } + + } + + if (s->curr_prompt < p->n_prompts) { + return -1; /* more data required */ + } else { + sfree(s); + p->data = NULL; + return +1; /* all done */ + } +} + +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/0.77_My_PuTTY/terminal.h b/0.77_My_PuTTY/terminal.h new file mode 100644 index 0000000..3549453 --- /dev/null +++ b/0.77_My_PuTTY/terminal.h @@ -0,0 +1,572 @@ +/* + * Internals of the Terminal structure, for those other modules + * which need to look inside it. It would be nice if this could be + * folded back into terminal.c in future, with an abstraction layer + * to handle everything that other modules need to know about it; + * but for the moment, this will do. + */ + +#ifndef PUTTY_TERMINAL_H +#define PUTTY_TERMINAL_H + +#include "tree234.h" + +struct beeptime { + struct beeptime *next; + unsigned long ticks; +}; + +#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 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 seen_disp_event; + bool big_cursor; + + bool xterm_mouse_forbidden; +#ifdef MOD_PERSO + int xterm_mouse_mode; /* mouse event mode */ + int xterm_mouse_protocol; /* mouse protocol */ +#endif + 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 */ + + 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) + +#ifdef MOD_FAR2L +/* far2l extensions support */ +//#define OSC_STR_MAX 2048 +// todo: allocate osc_string dynamically +#define OSC_STR_MAX 1048576 +#else +#define OSC_STR_MAX 2048 +#endif + int osc_strlen; + char osc_string[OSC_STR_MAX + 1]; + bool osc_w; + +#ifdef MOD_FAR2L + /* far2l */ + int far2l_ext; // extensions mode on + bool is_apc; // currently processing APC sequence + int clip_allowed; // remote clipboard access is allowed +#endif + + 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, +#ifdef MOD_FAR2L + /* far2l extensions support */ SEEN_APC, + VT52_ESC, +#else + VT52_ESC, +#endif + 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; + +#ifdef MOD_ZMODEM + int xyz_transfering; + struct zModemInternals *xyz_Internals; +#endif + /* + * 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; + char *answerback; + int answerbacklen; + 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; + 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; +#ifdef MOD_PERSO + bool no_focus_rep; + bool report_focus; +#endif + 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; +#ifdef MOD_PERSO + int enter_sends_crlf; + int rxvt_homeend; +#else + bool rxvt_homeend; +#endif + 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; + bool minimised; + + /* 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_resize_pending; + int win_resize_pending_w, win_resize_pending_h; + 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; + +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + */ + int url_update; +#endif +}; + +static inline bool in_utf(Terminal *term) +{ + return term->utf || term->ucsdata->line_codepage == CP_UTF8; +} + +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) + +#endif diff --git a/0.77_My_PuTTY/testback.c b/0.77_My_PuTTY/testback.c new file mode 100644 index 0000000..54834fa --- /dev/null +++ b/0.77_My_PuTTY/testback.c @@ -0,0 +1,214 @@ +/* + * 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 *null_init(const BackendVtable *, Seat *, Backend **, LogContext *, + Conf *, const char *, int, char **, bool, bool); +static char *loop_init(const BackendVtable *, Seat *, Backend **, LogContext *, + Conf *, const char *, int, char **, bool, bool); +static void null_free(Backend *); +static void loop_free(Backend *); +static void null_reconfig(Backend *, Conf *); +static size_t null_send(Backend *, const char *, size_t); +static size_t loop_send(Backend *, const char *, size_t); +static size_t null_sendbuffer(Backend *); +static void null_size(Backend *, int, int); +static void null_special(Backend *, SessionSpecialCode, int); +static const SessionSpecial *null_get_specials(Backend *); +static int null_connected(Backend *); +static int null_exitcode(Backend *); +static int null_sendok(Backend *); +static int 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 = null_init, + .free = null_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 = "null", + .protocol = -1, + .default_port = 0, +}; + +const BackendVtable loop_backend = { + .init = loop_init, + .free = loop_free, + .reconfig = null_reconfig, + .send = loop_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 = "loop", + .displayname = "loop", + .protocol = -1, + .default_port = 0, +}; + +struct loop_state { + Seat *seat; + Backend backend; +}; + +static char *null_init(const BackendVtable *vt, Seat *seat, + Backend **backend_handle, LogContext *logctx, + Conf *conf, const char *host, int port, + char **realhost, bool nodelay, bool keepalive) { + /* No local authentication phase in this protocol */ + seat_set_trust_status(seat, false); + + *backend_handle = NULL; + return NULL; +} + +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; + *backend_handle = &st->backend; + return NULL; +} + +static void null_free(Backend *be) +{ + +} + +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 size_t null_send(Backend *be, const char *buf, size_t len) { + + return 0; +} + +static size_t loop_send(Backend *be, const char *buf, size_t len) { + struct loop_state *st = container_of(be, struct loop_state, backend); + + return seat_output(st->seat, 0, buf, len); +} + +static size_t null_sendbuffer(Backend *be) { + + return 0; +} + +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 int null_connected(Backend *be) { + + return 0; +} + +static int null_exitcode(Backend *be) { + + return 0; +} + +static int null_sendok(Backend *be) { + + return 1; +} + +static void null_unthrottle(Backend *be, size_t backlog) { + +} + +static int null_ldisc(Backend *be, int option) { + + return 0; +} + +static void null_provide_ldisc (Backend *be, Ldisc *ldisc) { + +} + +static int null_cfg_info(Backend *be) +{ + return 0; +} + + +/* + * Emacs magic: + * Local Variables: + * c-file-style: "simon" + * End: + */ diff --git a/0.77_My_PuTTY/testcrypt.c b/0.77_My_PuTTY/testcrypt.c new file mode 100644 index 0000000..6da2fef --- /dev/null +++ b/0.77_My_PuTTY/testcrypt.c @@ -0,0 +1,1587 @@ +/* + * 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 "ecc.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"); } + +/* For platforms where getticks is defined within this code base */ +unsigned long (getticks)(void) +{ unreachable("this is a stub needed to link, and should never be called"); } + +FILE *f_open(const struct Filename *fn, char const *mode, bool private) +{ unreachable("f_open should never be called by this test program"); } + +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 *, ssh_ecdhkex_freekey(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)) \ + /* 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; +} + +static const ssh_hashalg *get_hashalg(BinarySource *in) +{ + static const struct { + const char *key; + const ssh_hashalg *value; + } algs[] = { + {"md5", &ssh_md5}, + {"sha1", &ssh_sha1}, + {"sha1_sw", &ssh_sha1_sw}, + {"sha1_hw", &ssh_sha1_hw}, + {"sha256", &ssh_sha256}, + {"sha256_sw", &ssh_sha256_sw}, + {"sha256_hw", &ssh_sha256_hw}, + {"sha384", &ssh_sha384}, + {"sha384_sw", &ssh_sha384_sw}, + {"sha384_hw", &ssh_sha384_hw}, + {"sha512", &ssh_sha512}, + {"sha512_sw", &ssh_sha512_sw}, + {"sha512_hw", &ssh_sha512_hw}, + {"sha3_224", &ssh_sha3_224}, + {"sha3_256", &ssh_sha3_256}, + {"sha3_384", &ssh_sha3_384}, + {"sha3_512", &ssh_sha3_512}, + {"shake256_114bytes", &ssh_shake256_114bytes}, + {"blake2b", &ssh_blake2b}, + }; + + ptrlen name = get_word(in); + for (size_t i = 0; i < lenof(algs); i++) + if (ptrlen_eq_string(name, algs[i].key)) + return algs[i].value; + + fatal_error("hashalg '%.*s': not found", PTRLEN_PRINTF(name)); +} + +static const ssh2_macalg *get_macalg(BinarySource *in) +{ + static const struct { + const char *key; + const ssh2_macalg *value; + } algs[] = { + {"hmac_md5", &ssh_hmac_md5}, + {"hmac_sha1", &ssh_hmac_sha1}, + {"hmac_sha1_buggy", &ssh_hmac_sha1_buggy}, + {"hmac_sha1_96", &ssh_hmac_sha1_96}, + {"hmac_sha1_96_buggy", &ssh_hmac_sha1_96_buggy}, + {"hmac_sha256", &ssh_hmac_sha256}, + {"poly1305", &ssh2_poly1305}, + }; + + ptrlen name = get_word(in); + for (size_t i = 0; i < lenof(algs); i++) + if (ptrlen_eq_string(name, algs[i].key)) + return algs[i].value; + + fatal_error("macalg '%.*s': not found", PTRLEN_PRINTF(name)); +} + +static const ssh_keyalg *get_keyalg(BinarySource *in) +{ + static const struct { + const char *key; + const ssh_keyalg *value; + } algs[] = { + {"dsa", &ssh_dss}, + {"rsa", &ssh_rsa}, + {"ed25519", &ssh_ecdsa_ed25519}, + {"ed448", &ssh_ecdsa_ed448}, + {"p256", &ssh_ecdsa_nistp256}, + {"p384", &ssh_ecdsa_nistp384}, + {"p521", &ssh_ecdsa_nistp521}, + }; + + ptrlen name = get_word(in); + for (size_t i = 0; i < lenof(algs); i++) + if (ptrlen_eq_string(name, algs[i].key)) + return algs[i].value; + + fatal_error("keyalg '%.*s': not found", PTRLEN_PRINTF(name)); +} + +static const ssh_cipheralg *get_cipheralg(BinarySource *in) +{ + static const struct { + const char *key; + const ssh_cipheralg *value; + } algs[] = { + {"3des_ctr", &ssh_3des_ssh2_ctr}, + {"3des_ssh2", &ssh_3des_ssh2}, + {"3des_ssh1", &ssh_3des_ssh1}, + {"des_cbc", &ssh_des}, + {"aes256_ctr", &ssh_aes256_sdctr}, + {"aes256_ctr_hw", &ssh_aes256_sdctr_hw}, + {"aes256_ctr_sw", &ssh_aes256_sdctr_sw}, + {"aes256_cbc", &ssh_aes256_cbc}, + {"aes256_cbc_hw", &ssh_aes256_cbc_hw}, + {"aes256_cbc_sw", &ssh_aes256_cbc_sw}, + {"aes192_ctr", &ssh_aes192_sdctr}, + {"aes192_ctr_hw", &ssh_aes192_sdctr_hw}, + {"aes192_ctr_sw", &ssh_aes192_sdctr_sw}, + {"aes192_cbc", &ssh_aes192_cbc}, + {"aes192_cbc_hw", &ssh_aes192_cbc_hw}, + {"aes192_cbc_sw", &ssh_aes192_cbc_sw}, + {"aes128_ctr", &ssh_aes128_sdctr}, + {"aes128_ctr_hw", &ssh_aes128_sdctr_hw}, + {"aes128_ctr_sw", &ssh_aes128_sdctr_sw}, + {"aes128_cbc", &ssh_aes128_cbc}, + {"aes128_cbc_hw", &ssh_aes128_cbc_hw}, + {"aes128_cbc_sw", &ssh_aes128_cbc_sw}, + {"blowfish_ctr", &ssh_blowfish_ssh2_ctr}, + {"blowfish_ssh2", &ssh_blowfish_ssh2}, + {"blowfish_ssh1", &ssh_blowfish_ssh1}, + {"arcfour256", &ssh_arcfour256_ssh2}, + {"arcfour128", &ssh_arcfour128_ssh2}, + {"chacha20_poly1305", &ssh2_chacha20_poly1305}, + }; + + ptrlen name = get_word(in); + for (size_t i = 0; i < lenof(algs); i++) + if (ptrlen_eq_string(name, algs[i].key)) + return algs[i].value; + + fatal_error("cipheralg '%.*s': not found", PTRLEN_PRINTF(name)); +} + +static const ssh_kex *get_dh_group(BinarySource *in) +{ + static const struct { + const char *key; + const ssh_kexes *value; + } algs[] = { + {"group1", &ssh_diffiehellman_group1}, + {"group14", &ssh_diffiehellman_group14}, + }; + + ptrlen name = get_word(in); + for (size_t i = 0; i < lenof(algs); i++) + if (ptrlen_eq_string(name, algs[i].key)) + return algs[i].value->list[0]; + + fatal_error("dh_group '%.*s': not found", PTRLEN_PRINTF(name)); +} + +static const ssh_kex *get_ecdh_alg(BinarySource *in) +{ + static const struct { + const char *key; + const ssh_kex *value; + } algs[] = { + {"curve25519", &ssh_ec_kex_curve25519}, + {"curve448", &ssh_ec_kex_curve448}, + {"nistp256", &ssh_ec_kex_nistp256}, + {"nistp384", &ssh_ec_kex_nistp384}, + {"nistp521", &ssh_ec_kex_nistp521}, + }; + + ptrlen name = get_word(in); + for (size_t i = 0; i < lenof(algs); i++) + if (ptrlen_eq_string(name, algs[i].key)) + return algs[i].value; + + fatal_error("ecdh_alg '%.*s': not found", PTRLEN_PRINTF(name)); +} + +static RsaSsh1Order get_rsaorder(BinarySource *in) +{ + static const struct { + const char *key; + RsaSsh1Order value; + } orders[] = { + {"exponent_first", RSA_SSH1_EXPONENT_FIRST}, + {"modulus_first", RSA_SSH1_MODULUS_FIRST}, + }; + + ptrlen name = get_word(in); + for (size_t i = 0; i < lenof(orders); i++) + if (ptrlen_eq_string(name, orders[i].key)) + return orders[i].value; + + fatal_error("rsaorder '%.*s': not found", PTRLEN_PRINTF(name)); +} + +static const PrimeGenerationPolicy *get_primegenpolicy(BinarySource *in) +{ + static const struct { + const char *key; + const PrimeGenerationPolicy *value; + } algs[] = { + {"probabilistic", &primegen_probabilistic}, + {"provable_fast", &primegen_provable_fast}, + {"provable_maurer_simple", &primegen_provable_maurer_simple}, + {"provable_maurer_complex", &primegen_provable_maurer_complex}, + }; + + ptrlen name = get_word(in); + for (size_t i = 0; i < lenof(algs); i++) + if (ptrlen_eq_string(name, algs[i].key)) + return algs[i].value; + + fatal_error("primegenpolicy '%.*s': not found", PTRLEN_PRINTF(name)); +} + +static Argon2Flavour get_argon2flavour(BinarySource *in) +{ + static const struct { + const char *key; + Argon2Flavour value; + } algs[] = { + {"d", Argon2d}, + {"i", Argon2i}, + {"id", Argon2id}, + /* I expect to forget which spelling I chose, so let's support many */ + {"argon2d", Argon2d}, + {"argon2i", Argon2i}, + {"argon2id", Argon2id}, + {"Argon2d", Argon2d}, + {"Argon2i", Argon2i}, + {"Argon2id", Argon2id}, + }; + + ptrlen name = get_word(in); + for (size_t i = 0; i < lenof(algs); i++) + if (ptrlen_eq_string(name, algs[i].key)) + return algs[i].value; + + fatal_error("Argon2 flavour '%.*s': not found", PTRLEN_PRINTF(name)); +} + +static FingerprintType get_fptype(BinarySource *in) +{ + static const struct { + const char *key; + FingerprintType value; + } ids[] = { + {"md5", SSH_FPTYPE_MD5}, + {"sha256", SSH_FPTYPE_SHA256}, + }; + + ptrlen name = get_word(in); + for (size_t i = 0; i < lenof(ids); i++) + if (ptrlen_eq_string(name, ids[i].key)) + return ids[i].value; + + fatal_error("fingerprint type '%.*s': not found", PTRLEN_PRINTF(name)); +} + +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; +} + +static void finaliser_return_uint(strbuf *out, void *ctx) +{ + unsigned *uval = (unsigned *)ctx; + strbuf_catf(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) + strbuf_catf(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) + strbuf_catf(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) +{ + strbuf_catf(out, "%"PRIdMAX"\n", u); +} + +static void return_uint(strbuf *out, uintmax_t u) +{ + strbuf_catf(out, "0x%"PRIXMAX"\n", u); +} + +static void return_boolean(strbuf *out, bool b) +{ + strbuf_catf(out, "%s\n", b ? "true" : "false"); +} + +static void return_pocklestatus(strbuf *out, PockleStatus status) +{ + switch (status) { + default: + strbuf_catf(out, "POCKLE_BAD_STATUS_VALUE\n"); + break; + +#define STATUS_CASE(id) \ + case id: \ + strbuf_catf(out, "%s\n", #id); \ + break; + + POCKLE_STATUSES(STATUS_CASE); + +#undef STATUS_CASE + + } +} + +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) \ + strbuf_catf(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_hash, ssh_hash *) +NULLABLE_RETURN_WRAPPER(val_key, ssh_key *) +NULLABLE_RETURN_WRAPPER(val_mpint, mp_int *) + +static void handle_hello(BinarySource *in, strbuf *out) +{ + strbuf_catf(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 { + strbuf_catf(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 ;) + strbuf_catf(out, "%02X", mp_get_byte(mp, i)); + put_byte(out, '\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)); +} +#define monty_identity monty_identity_wrapper + +mp_int *monty_modulus_wrapper(MontyContext *mc) +{ + return mp_copy(monty_modulus(mc)); +} +#define monty_modulus monty_modulus_wrapper + +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; +} +#undef ssh_hash_digest +#define ssh_hash_digest ssh_hash_digest_wrapper + +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; +} +#undef ssh_hash_final +#define ssh_hash_final ssh_hash_final_wrapper + +void ssh_cipher_setiv_wrapper(ssh_cipher *c, ptrlen key) +{ + if (key.len != ssh_cipher_alg(c)->blksize) + fatal_error("ssh_cipher_setiv: needs exactly %d bytes", + ssh_cipher_alg(c)->blksize); + ssh_cipher_setiv(c, key.ptr); +} +#undef ssh_cipher_setiv +#define ssh_cipher_setiv ssh_cipher_setiv_wrapper + +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); +} +#undef ssh_cipher_setkey +#define ssh_cipher_setkey ssh_cipher_setkey_wrapper + +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_new(); + put_datapl(sb, input); + ssh_cipher_encrypt(c, sb->u, sb->len); + return sb; +} +#undef ssh_cipher_encrypt +#define ssh_cipher_encrypt ssh_cipher_encrypt_wrapper + +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_new(); + put_datapl(sb, input); + ssh_cipher_decrypt(c, sb->u, sb->len); + return sb; +} +#undef ssh_cipher_decrypt +#define ssh_cipher_decrypt ssh_cipher_decrypt_wrapper + +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_new(); + put_datapl(sb, input); + ssh_cipher_encrypt_length(c, sb->u, sb->len, seq); + return sb; +} +#undef ssh_cipher_encrypt_length +#define ssh_cipher_encrypt_length ssh_cipher_encrypt_length_wrapper + +strbuf *ssh_cipher_decrypt_length_wrapper(ssh_cipher *c, ptrlen input, + unsigned long seq) +{ + if (input.len % ssh_cipher_alg(c)->blksize) + fatal_error("ssh_cipher_decrypt_length: needs exactly 4 bytes"); + strbuf *sb = strbuf_new(); + put_datapl(sb, input); + ssh_cipher_decrypt_length(c, sb->u, sb->len, seq); + return sb; +} +#undef ssh_cipher_decrypt_length +#define ssh_cipher_decrypt_length ssh_cipher_decrypt_length_wrapper + +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; +} +#undef ssh2_mac_genresult +#define ssh2_mac_genresult ssh2_mac_genresult_wrapper + +bool dh_validate_f_wrapper(dh_ctx *dh, mp_int *f) +{ + return dh_validate_f(dh, f) == NULL; +} +#define dh_validate_f dh_validate_f_wrapper + +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 *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; +} +#define rsa_ssh1_encrypt rsa_ssh1_encrypt_wrapper + +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; +} +#define rsa_ssh1_decrypt_pkcs1 rsa_ssh1_decrypt_pkcs1_wrapper + +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_new(); + put_datapl(sb, data); + des_encrypt_xdmauth(key.ptr, sb->u, sb->len); + return sb; +} +#define des_encrypt_xdmauth des_encrypt_xdmauth_wrapper + +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_new(); + put_datapl(sb, data); + des_decrypt_xdmauth(key.ptr, sb->u, sb->len); + return sb; +} +#define des_decrypt_xdmauth des_decrypt_xdmauth_wrapper + +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_new(); + put_datapl(sb, data); + des3_encrypt_pubkey(key.ptr, sb->u, sb->len); + return sb; +} +#define des3_encrypt_pubkey des3_encrypt_pubkey_wrapper + +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_new(); + put_datapl(sb, data); + des3_decrypt_pubkey(key.ptr, sb->u, sb->len); + return sb; +} +#define des3_decrypt_pubkey des3_decrypt_pubkey_wrapper + +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_new(); + put_datapl(sb, data); + des3_encrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); + return sb; +} +#define des3_encrypt_pubkey_ossh des3_encrypt_pubkey_ossh_wrapper + +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_new(); + put_datapl(sb, data); + des3_decrypt_pubkey_ossh(key.ptr, iv.ptr, sb->u, sb->len); + return sb; +} +#define des3_decrypt_pubkey_ossh des3_decrypt_pubkey_ossh_wrapper + +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_new(); + put_datapl(sb, data); + aes256_encrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); + return sb; +} +#define aes256_encrypt_pubkey aes256_encrypt_pubkey_wrapper + +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_new(); + put_datapl(sb, data); + aes256_decrypt_pubkey(key.ptr, iv.ptr, sb->u, sb->len); + return sb; +} +#define aes256_decrypt_pubkey aes256_decrypt_pubkey_wrapper + +strbuf *prng_read_wrapper(prng *pr, size_t size) +{ + strbuf *sb = strbuf_new(); + prng_read(pr, strbuf_append(sb, size), size); + return sb; +} +#define prng_read prng_read_wrapper + +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; +} +#define ppk_load_s ppk_load_s_wrapper + +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; +} +#define rsa1_load_s rsa1_load_s_wrapper + +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; +} +#define ppk_save_sb ppk_save_sb_wrapper + +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 rsa1_save_sb rsa1_save_sb_wrapper + +#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); +} +#define primegen_generate primegen_generate_wrapper + +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; +} +#define rsa_generate rsa_generate_wrapper + +ssh_key *dsa_generate_wrapper(int bits, PrimeGenerationContext *pgc) +{ + struct dss_key *dsskey = snew(struct dss_key); + dsa_generate(dsskey, bits, pgc, &null_progress); + return &dsskey->sshk; +} +#define dsa_generate dsa_generate_wrapper + +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; +} +#define ecdsa_generate ecdsa_generate_wrapper + +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; +} +#define eddsa_generate eddsa_generate_wrapper + +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); +} +const char *key_components_nth_str(key_components *kc, size_t n) +{ + return (n >= kc->ncomponents ? NULL : + kc->components[n].is_mp_int ? NULL : + kc->components[n].text); +} +mp_int *key_components_nth_mp(key_components *kc, size_t n) +{ + return (n >= kc->ncomponents ? NULL : + !kc->components[n].is_mp_int ? 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); +} +#define pockle_add_prime pockle_add_prime_wrapper + +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; +} +#define argon2 argon2_wrapper + +#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) + +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 ssh_hash *TD_consumed_val_hash; +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 PockleStatus TD_pocklestatus; +typedef Argon2Flavour TD_argon2flavour; +typedef FingerprintType TD_fptype; + +#define FUNC0(rettype, function) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + return_##rettype(out, function()); \ + } + +#define FUNC1(rettype, function, type1) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + TD_##type1 arg1 = get_##type1(in); \ + return_##rettype(out, function(arg1)); \ + } + +#define FUNC2(rettype, function, type1, type2) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + TD_##type1 arg1 = get_##type1(in); \ + TD_##type2 arg2 = get_##type2(in); \ + return_##rettype(out, function(arg1, arg2)); \ + } + +#define FUNC3(rettype, function, type1, type2, type3) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + TD_##type1 arg1 = get_##type1(in); \ + TD_##type2 arg2 = get_##type2(in); \ + TD_##type3 arg3 = get_##type3(in); \ + return_##rettype(out, function(arg1, arg2, arg3)); \ + } + +#define FUNC4(rettype, function, type1, type2, type3, type4) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + TD_##type1 arg1 = get_##type1(in); \ + TD_##type2 arg2 = get_##type2(in); \ + TD_##type3 arg3 = get_##type3(in); \ + TD_##type4 arg4 = get_##type4(in); \ + return_##rettype(out, function(arg1, arg2, arg3, arg4)); \ + } + +#define FUNC5(rettype, function, type1, type2, type3, type4, type5) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + TD_##type1 arg1 = get_##type1(in); \ + TD_##type2 arg2 = get_##type2(in); \ + TD_##type3 arg3 = get_##type3(in); \ + TD_##type4 arg4 = get_##type4(in); \ + TD_##type5 arg5 = get_##type5(in); \ + return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5)); \ + } + +#define FUNC6(rettype, function, type1, type2, type3, type4, type5, \ + type6) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + TD_##type1 arg1 = get_##type1(in); \ + TD_##type2 arg2 = get_##type2(in); \ + TD_##type3 arg3 = get_##type3(in); \ + TD_##type4 arg4 = get_##type4(in); \ + TD_##type5 arg5 = get_##type5(in); \ + TD_##type6 arg6 = get_##type6(in); \ + return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ + arg6)); \ + } + +#define FUNC7(rettype, function, type1, type2, type3, type4, type5, \ + type6, type7) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + TD_##type1 arg1 = get_##type1(in); \ + TD_##type2 arg2 = get_##type2(in); \ + TD_##type3 arg3 = get_##type3(in); \ + TD_##type4 arg4 = get_##type4(in); \ + TD_##type5 arg5 = get_##type5(in); \ + TD_##type6 arg6 = get_##type6(in); \ + TD_##type7 arg7 = get_##type7(in); \ + return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7)); \ + } + +#define FUNC8(rettype, function, type1, type2, type3, type4, type5, \ + type6, type7, type8) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + TD_##type1 arg1 = get_##type1(in); \ + TD_##type2 arg2 = get_##type2(in); \ + TD_##type3 arg3 = get_##type3(in); \ + TD_##type4 arg4 = get_##type4(in); \ + TD_##type5 arg5 = get_##type5(in); \ + TD_##type6 arg6 = get_##type6(in); \ + TD_##type7 arg7 = get_##type7(in); \ + TD_##type8 arg8 = get_##type8(in); \ + return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7, arg8)); \ + } + +#define FUNC9(rettype, function, type1, type2, type3, type4, type5, \ + type6, type7, type8, type9) \ + static void handle_##function(BinarySource *in, strbuf *out) { \ + TD_##type1 arg1 = get_##type1(in); \ + TD_##type2 arg2 = get_##type2(in); \ + TD_##type3 arg3 = get_##type3(in); \ + TD_##type4 arg4 = get_##type4(in); \ + TD_##type5 arg5 = get_##type5(in); \ + TD_##type6 arg6 = get_##type6(in); \ + TD_##type7 arg7 = get_##type7(in); \ + TD_##type8 arg8 = get_##type8(in); \ + TD_##type9 arg9 = get_##type9(in); \ + return_##rettype(out, function(arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7, arg8, arg9)); \ + } + +#include "testcrypt.h" + +#undef FUNC9 +#undef FUNC8 +#undef FUNC7 +#undef FUNC6 +#undef FUNC5 +#undef FUNC4 +#undef FUNC3 +#undef FUNC2 +#undef FUNC1 +#undef FUNC0 + +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); +#undef DISPATCH_COMMAND + +#define FUNC0(ret,fn) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC1(ret,fn,x) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC2(ret,fn,x,y) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC3(ret,fn,x,y,z) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC4(ret,fn,x,y,z,v) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC5(ret,fn,x,y,z,v,w) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC6(ret,fn,x,y,z,v,w,u) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC7(ret,fn,x,y,z,v,w,u,t) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC8(ret,fn,x,y,z,v,w,u,t,s) DISPATCH_INTERNAL(#fn,handle_##fn); +#define FUNC9(ret,fn,x,y,z,v,w,u,t,s,r) DISPATCH_INTERNAL(#fn,handle_##fn); + +#include "testcrypt.h" + +#undef FUNC9 +#undef FUNC8 +#undef FUNC7 +#undef FUNC6 +#undef FUNC5 +#undef FUNC4 +#undef FUNC3 +#undef FUNC2 +#undef FUNC1 +#undef FUNC0 + +#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/0.77_My_PuTTY/testcrypt.h b/0.77_My_PuTTY/testcrypt.h new file mode 100644 index 0000000..f6e5989 --- /dev/null +++ b/0.77_My_PuTTY/testcrypt.h @@ -0,0 +1,326 @@ +/* + * mpint.h functions. + */ +FUNC1(val_mpint, mp_new, uint) +FUNC1(void, mp_clear, val_mpint) +FUNC1(val_mpint, mp_from_bytes_le, val_string_ptrlen) +FUNC1(val_mpint, mp_from_bytes_be, val_string_ptrlen) +FUNC1(val_mpint, mp_from_integer, uint) +FUNC1(val_mpint, mp_from_decimal_pl, val_string_ptrlen) +FUNC1(val_mpint, mp_from_decimal, val_string_asciz) +FUNC1(val_mpint, mp_from_hex_pl, val_string_ptrlen) +FUNC1(val_mpint, mp_from_hex, val_string_asciz) +FUNC1(val_mpint, mp_copy, val_mpint) +FUNC1(val_mpint, mp_power_2, uint) +FUNC2(uint, mp_get_byte, val_mpint, uint) +FUNC2(uint, mp_get_bit, val_mpint, uint) +FUNC3(void, mp_set_bit, val_mpint, uint, uint) +FUNC1(uint, mp_max_bytes, val_mpint) +FUNC1(uint, mp_max_bits, val_mpint) +FUNC1(uint, mp_get_nbits, val_mpint) +FUNC1(val_string_asciz, mp_get_decimal, val_mpint) +FUNC1(val_string_asciz, mp_get_hex, val_mpint) +FUNC1(val_string_asciz, mp_get_hex_uppercase, val_mpint) +FUNC2(uint, mp_cmp_hs, val_mpint, val_mpint) +FUNC2(uint, mp_cmp_eq, val_mpint, val_mpint) +FUNC2(uint, mp_hs_integer, val_mpint, uint) +FUNC2(uint, mp_eq_integer, val_mpint, uint) +FUNC3(void, mp_min_into, val_mpint, val_mpint, val_mpint) +FUNC3(void, mp_max_into, val_mpint, val_mpint, val_mpint) +FUNC2(val_mpint, mp_min, val_mpint, val_mpint) +FUNC2(val_mpint, mp_max, val_mpint, val_mpint) +FUNC2(void, mp_copy_into, val_mpint, val_mpint) +FUNC4(void, mp_select_into, val_mpint, val_mpint, val_mpint, uint) +FUNC3(void, mp_add_into, val_mpint, val_mpint, val_mpint) +FUNC3(void, mp_sub_into, val_mpint, val_mpint, val_mpint) +FUNC3(void, mp_mul_into, val_mpint, val_mpint, val_mpint) +FUNC2(val_mpint, mp_add, val_mpint, val_mpint) +FUNC2(val_mpint, mp_sub, val_mpint, val_mpint) +FUNC2(val_mpint, mp_mul, val_mpint, val_mpint) +FUNC3(void, mp_and_into, val_mpint, val_mpint, val_mpint) +FUNC3(void, mp_or_into, val_mpint, val_mpint, val_mpint) +FUNC3(void, mp_xor_into, val_mpint, val_mpint, val_mpint) +FUNC3(void, mp_bic_into, val_mpint, val_mpint, val_mpint) +FUNC2(void, mp_copy_integer_into, val_mpint, uint) +FUNC3(void, mp_add_integer_into, val_mpint, val_mpint, uint) +FUNC3(void, mp_sub_integer_into, val_mpint, val_mpint, uint) +FUNC3(void, mp_mul_integer_into, val_mpint, val_mpint, uint) +FUNC4(void, mp_cond_add_into, val_mpint, val_mpint, val_mpint, uint) +FUNC4(void, mp_cond_sub_into, val_mpint, val_mpint, val_mpint, uint) +FUNC3(void, mp_cond_swap, val_mpint, val_mpint, uint) +FUNC2(void, mp_cond_clear, val_mpint, uint) +FUNC4(void, mp_divmod_into, val_mpint, val_mpint, opt_val_mpint, opt_val_mpint) +FUNC2(val_mpint, mp_div, val_mpint, val_mpint) +FUNC2(val_mpint, mp_mod, val_mpint, val_mpint) +FUNC3(val_mpint, mp_nthroot, val_mpint, uint, opt_val_mpint) +FUNC2(void, mp_reduce_mod_2to, val_mpint, uint) +FUNC2(val_mpint, mp_invert_mod_2to, val_mpint, uint) +FUNC2(val_mpint, mp_invert, val_mpint, val_mpint) +FUNC5(void, mp_gcd_into, val_mpint, val_mpint, opt_val_mpint, opt_val_mpint, opt_val_mpint) +FUNC2(val_mpint, mp_gcd, val_mpint, val_mpint) +FUNC2(uint, mp_coprime, val_mpint, val_mpint) +FUNC2(val_modsqrt, modsqrt_new, val_mpint, val_mpint) +/* The modsqrt functions' 'success' pointer becomes a second return value */ +FUNC3(val_mpint, mp_modsqrt, val_modsqrt, val_mpint, out_uint) +FUNC1(val_monty, monty_new, val_mpint) +FUNC1(val_mpint, monty_modulus, val_monty) +FUNC1(val_mpint, monty_identity, val_monty) +FUNC3(void, monty_import_into, val_monty, val_mpint, val_mpint) +FUNC2(val_mpint, monty_import, val_monty, val_mpint) +FUNC3(void, monty_export_into, val_monty, val_mpint, val_mpint) +FUNC2(val_mpint, monty_export, val_monty, val_mpint) +FUNC4(void, monty_mul_into, val_monty, val_mpint, val_mpint, val_mpint) +FUNC3(val_mpint, monty_add, val_monty, val_mpint, val_mpint) +FUNC3(val_mpint, monty_sub, val_monty, val_mpint, val_mpint) +FUNC3(val_mpint, monty_mul, val_monty, val_mpint, val_mpint) +FUNC3(val_mpint, monty_pow, val_monty, val_mpint, val_mpint) +FUNC2(val_mpint, monty_invert, val_monty, val_mpint) +FUNC3(val_mpint, monty_modsqrt, val_modsqrt, val_mpint, out_uint) +FUNC3(val_mpint, mp_modpow, val_mpint, val_mpint, val_mpint) +FUNC3(val_mpint, mp_modmul, val_mpint, val_mpint, val_mpint) +FUNC3(val_mpint, mp_modadd, val_mpint, val_mpint, val_mpint) +FUNC3(val_mpint, mp_modsub, val_mpint, val_mpint, val_mpint) +FUNC3(void, mp_lshift_safe_into, val_mpint, val_mpint, uint) +FUNC3(void, mp_rshift_safe_into, val_mpint, val_mpint, uint) +FUNC2(val_mpint, mp_rshift_safe, val_mpint, uint) +FUNC3(void, mp_lshift_fixed_into, val_mpint, val_mpint, uint) +FUNC3(void, mp_rshift_fixed_into, val_mpint, val_mpint, uint) +FUNC2(val_mpint, mp_rshift_fixed, val_mpint, uint) +FUNC1(val_mpint, mp_random_bits, uint) +FUNC2(val_mpint, mp_random_in_range, val_mpint, val_mpint) + +/* + * ecc.h functions. + */ +FUNC4(val_wcurve, ecc_weierstrass_curve, val_mpint, val_mpint, val_mpint, opt_val_mpint) +FUNC1(val_wpoint, ecc_weierstrass_point_new_identity, val_wcurve) +FUNC3(val_wpoint, ecc_weierstrass_point_new, val_wcurve, val_mpint, val_mpint) +FUNC3(val_wpoint, ecc_weierstrass_point_new_from_x, val_wcurve, val_mpint, uint) +FUNC1(val_wpoint, ecc_weierstrass_point_copy, val_wpoint) +FUNC1(uint, ecc_weierstrass_point_valid, val_wpoint) +FUNC2(val_wpoint, ecc_weierstrass_add_general, val_wpoint, val_wpoint) +FUNC2(val_wpoint, ecc_weierstrass_add, val_wpoint, val_wpoint) +FUNC1(val_wpoint, ecc_weierstrass_double, val_wpoint) +FUNC2(val_wpoint, ecc_weierstrass_multiply, val_wpoint, val_mpint) +FUNC1(uint, ecc_weierstrass_is_identity, val_wpoint) +/* The output pointers in get_affine all become extra output values */ +FUNC3(void, ecc_weierstrass_get_affine, val_wpoint, out_val_mpint, out_val_mpint) +FUNC3(val_mcurve, ecc_montgomery_curve, val_mpint, val_mpint, val_mpint) +FUNC2(val_mpoint, ecc_montgomery_point_new, val_mcurve, val_mpint) +FUNC1(val_mpoint, ecc_montgomery_point_copy, val_mpoint) +FUNC3(val_mpoint, ecc_montgomery_diff_add, val_mpoint, val_mpoint, val_mpoint) +FUNC1(val_mpoint, ecc_montgomery_double, val_mpoint) +FUNC2(val_mpoint, ecc_montgomery_multiply, val_mpoint, val_mpint) +FUNC2(void, ecc_montgomery_get_affine, val_mpoint, out_val_mpint) +FUNC1(boolean, ecc_montgomery_is_identity, val_mpoint) +FUNC4(val_ecurve, ecc_edwards_curve, val_mpint, val_mpint, val_mpint, opt_val_mpint) +FUNC3(val_epoint, ecc_edwards_point_new, val_ecurve, val_mpint, val_mpint) +FUNC3(val_epoint, ecc_edwards_point_new_from_y, val_ecurve, val_mpint, uint) +FUNC1(val_epoint, ecc_edwards_point_copy, val_epoint) +FUNC2(val_epoint, ecc_edwards_add, val_epoint, val_epoint) +FUNC2(val_epoint, ecc_edwards_multiply, val_epoint, val_mpint) +FUNC2(uint, ecc_edwards_eq, val_epoint, val_epoint) +FUNC3(void, ecc_edwards_get_affine, val_epoint, out_val_mpint, out_val_mpint) + +/* + * 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. + */ +FUNC1(opt_val_hash, ssh_hash_new, hashalg) +FUNC1(void, ssh_hash_reset, val_hash) +FUNC1(val_hash, ssh_hash_copy, val_hash) +FUNC1(val_string, ssh_hash_digest, val_hash) +FUNC1(val_string, ssh_hash_final, consumed_val_hash) +FUNC2(void, ssh_hash_update, val_hash, val_string_ptrlen) + +FUNC1(opt_val_hash, blake2b_new_general, uint) + +/* + * 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. + */ +FUNC2(val_mac, ssh2_mac_new, macalg, opt_val_cipher) +FUNC2(void, ssh2_mac_setkey, val_mac, val_string_ptrlen) +FUNC1(void, ssh2_mac_start, val_mac) +FUNC2(void, ssh2_mac_update, val_mac, val_string_ptrlen) +FUNC1(val_string, ssh2_mac_genresult, val_mac) +FUNC1(val_string_asciz_const, ssh2_mac_text_name, val_mac) + +/* + * 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. + */ +FUNC2(val_key, ssh_key_new_pub, keyalg, val_string_ptrlen) +FUNC3(opt_val_key, ssh_key_new_priv, keyalg, val_string_ptrlen, val_string_ptrlen) +FUNC2(opt_val_key, ssh_key_new_priv_openssh, keyalg, val_string_binarysource) +FUNC2(opt_val_string_asciz, ssh_key_invalid, val_key, uint) +FUNC4(void, ssh_key_sign, val_key, val_string_ptrlen, uint, out_val_string_binarysink) +FUNC3(boolean, ssh_key_verify, val_key, val_string_ptrlen, val_string_ptrlen) +FUNC2(void, ssh_key_public_blob, val_key, out_val_string_binarysink) +FUNC2(void, ssh_key_private_blob, val_key, out_val_string_binarysink) +FUNC2(void, ssh_key_openssh_blob, val_key, out_val_string_binarysink) +FUNC1(val_string_asciz, ssh_key_cache_str, val_key) +FUNC1(val_keycomponents, ssh_key_components, val_key) +FUNC2(uint, ssh_key_public_bits, keyalg, val_string_ptrlen) + +/* + * Accessors to retrieve the innards of a 'key_components'. + */ +FUNC1(uint, key_components_count, val_keycomponents) +FUNC2(opt_val_string_asciz_const, key_components_nth_name, val_keycomponents, uint) +FUNC2(opt_val_string_asciz_const, key_components_nth_str, val_keycomponents, uint) +FUNC2(opt_val_mpint, key_components_nth_mp, val_keycomponents, uint) + +/* + * 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. + */ +FUNC1(opt_val_cipher, ssh_cipher_new, cipheralg) +FUNC2(void, ssh_cipher_setiv, val_cipher, val_string_ptrlen) +FUNC2(void, ssh_cipher_setkey, val_cipher, val_string_ptrlen) +FUNC2(val_string, ssh_cipher_encrypt, val_cipher, val_string_ptrlen) +FUNC2(val_string, ssh_cipher_decrypt, val_cipher, val_string_ptrlen) +FUNC3(val_string, ssh_cipher_encrypt_length, val_cipher, val_string_ptrlen, uint) +FUNC3(val_string, ssh_cipher_decrypt_length, val_cipher, val_string_ptrlen, uint) + +/* + * Integer Diffie-Hellman. + */ +FUNC1(val_dh, dh_setup_group, dh_group) +FUNC2(val_dh, dh_setup_gex, val_mpint, val_mpint) +FUNC1(uint, dh_modulus_bit_size, val_dh) +FUNC2(val_mpint, dh_create_e, val_dh, uint) +FUNC2(boolean, dh_validate_f, val_dh, val_mpint) +FUNC2(val_mpint, dh_find_K, val_dh, val_mpint) + +/* + * Elliptic-curve Diffie-Hellman. + */ +FUNC1(val_ecdh, ssh_ecdhkex_newkey, ecdh_alg) +FUNC2(void, ssh_ecdhkex_getpublic, val_ecdh, out_val_string_binarysink) +FUNC2(opt_val_mpint, ssh_ecdhkex_getkey, val_ecdh, val_string_ptrlen) + +/* + * 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. + */ +FUNC1(val_rsakex, ssh_rsakex_newkey, val_string_ptrlen) +FUNC1(uint, ssh_rsakex_klen, val_rsakex) +FUNC3(val_string, ssh_rsakex_encrypt, val_rsakex, hashalg, val_string_ptrlen) +FUNC3(opt_val_mpint, ssh_rsakex_decrypt, val_rsakex, hashalg, val_string_ptrlen) +FUNC1(val_rsakex, get_rsa_ssh1_priv_agent, val_string_binarysource) + +/* + * 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. + */ +FUNC0(val_rsa, rsa_new) +FUNC3(void, get_rsa_ssh1_pub, val_string_binarysource, val_rsa, rsaorder) +FUNC2(void, get_rsa_ssh1_priv, val_string_binarysource, val_rsa) +FUNC2(opt_val_string, rsa_ssh1_encrypt, val_string_ptrlen, val_rsa) +FUNC2(val_mpint, rsa_ssh1_decrypt, val_mpint, val_rsa) +FUNC2(val_string, rsa_ssh1_decrypt_pkcs1, val_mpint, val_rsa) +FUNC1(val_string_asciz, rsastr_fmt, val_rsa) +FUNC1(val_string_asciz, rsa_ssh1_fingerprint, val_rsa) +FUNC3(void, rsa_ssh1_public_blob, out_val_string_binarysink, val_rsa, rsaorder) +FUNC1(int, rsa_ssh1_public_blob_len, val_string_ptrlen) +FUNC2(void, rsa_ssh1_private_blob_agent, out_val_string_binarysink, val_rsa) + +/* + * 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. + */ +FUNC1(val_prng, prng_new, hashalg) +FUNC1(void, prng_seed_begin, val_prng) +FUNC2(void, prng_seed_update, val_prng, val_string_ptrlen) +FUNC1(void, prng_seed_finish, val_prng) +FUNC2(val_string, prng_read, val_prng, uint) +FUNC3(void, prng_add_entropy, val_prng, uint, val_string_ptrlen) + +/* + * Key load/save functions, or rather, the BinarySource / strbuf API + * that sits just inside the file I/O versions. + */ +FUNC2(boolean, ppk_encrypted_s, val_string_binarysource, out_opt_val_string_asciz) +FUNC2(boolean, rsa1_encrypted_s, val_string_binarysource, out_opt_val_string_asciz) +FUNC5(boolean, ppk_loadpub_s, val_string_binarysource, out_opt_val_string_asciz, out_val_string_binarysink, out_opt_val_string_asciz, out_opt_val_string_asciz_const) +FUNC4(int, rsa1_loadpub_s, val_string_binarysource, out_val_string_binarysink, out_opt_val_string_asciz, out_opt_val_string_asciz_const) +FUNC4(opt_val_key, ppk_load_s, val_string_binarysource, out_opt_val_string_asciz, opt_val_string_asciz, out_opt_val_string_asciz_const) +FUNC5(int, rsa1_load_s, val_string_binarysource, val_rsa, out_opt_val_string_asciz, opt_val_string_asciz, out_opt_val_string_asciz_const) +FUNC8(val_string, ppk_save_sb, val_key, opt_val_string_asciz, opt_val_string_asciz, uint, argon2flavour, uint, uint, uint) +FUNC3(val_string, rsa1_save_sb, val_rsa, opt_val_string_asciz, opt_val_string_asciz) + +FUNC2(val_string_asciz, ssh2_fingerprint_blob, val_string_ptrlen, fptype) + +/* + * Password hashing. + */ +FUNC9(val_string, argon2, argon2flavour, uint, uint, uint, uint, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen) +FUNC2(val_string, argon2_long_hash, uint, val_string_ptrlen) + +/* + * Key generation functions. + */ +FUNC3(val_key, rsa_generate, uint, boolean, val_pgc) +FUNC2(val_key, dsa_generate, uint, val_pgc) +FUNC1(opt_val_key, ecdsa_generate, uint) +FUNC1(opt_val_key, eddsa_generate, uint) +FUNC3(val_rsa, rsa1_generate, uint, boolean, val_pgc) +FUNC1(val_pgc, primegen_new_context, primegenpolicy) +FUNC2(opt_val_mpint, primegen_generate, val_pgc, consumed_val_pcs) +FUNC2(val_string, primegen_mpu_certificate, val_pgc, val_mpint) +FUNC1(val_pcs, pcs_new, uint) +FUNC3(val_pcs, pcs_new_with_firstbits, uint, uint, uint) +FUNC3(void, pcs_require_residue, val_pcs, val_mpint, val_mpint) +FUNC2(void, pcs_require_residue_1, val_pcs, val_mpint) +FUNC2(void, pcs_require_residue_1_mod_prime, val_pcs, val_mpint) +FUNC3(void, pcs_avoid_residue_small, val_pcs, uint, uint) +FUNC1(void, pcs_try_sophie_germain, val_pcs) +FUNC1(void, pcs_set_oneshot, val_pcs) +FUNC1(void, pcs_ready, val_pcs) +FUNC4(void, pcs_inspect, val_pcs, out_val_mpint, out_val_mpint, out_val_mpint) +FUNC1(val_mpint, pcs_generate, val_pcs) +FUNC0(val_pockle, pockle_new) +FUNC1(uint, pockle_mark, val_pockle) +FUNC2(void, pockle_release, val_pockle, uint) +FUNC2(pocklestatus, pockle_add_small_prime, val_pockle, val_mpint) +FUNC4(pocklestatus, pockle_add_prime, val_pockle, val_mpint, mpint_list, val_mpint) +FUNC2(val_string, pockle_mpu, val_pockle, val_mpint) + +/* + * Miscellaneous. + */ +FUNC2(val_wpoint, ecdsa_public, val_mpint, keyalg) +FUNC2(val_epoint, eddsa_public, val_mpint, keyalg) +FUNC2(val_string, des_encrypt_xdmauth, val_string_ptrlen, val_string_ptrlen) +FUNC2(val_string, des_decrypt_xdmauth, val_string_ptrlen, val_string_ptrlen) +FUNC2(val_string, des3_encrypt_pubkey, val_string_ptrlen, val_string_ptrlen) +FUNC2(val_string, des3_decrypt_pubkey, val_string_ptrlen, val_string_ptrlen) +FUNC3(val_string, des3_encrypt_pubkey_ossh, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen) +FUNC3(val_string, des3_decrypt_pubkey_ossh, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen) +FUNC3(val_string, aes256_encrypt_pubkey, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen) +FUNC3(val_string, aes256_decrypt_pubkey, val_string_ptrlen, val_string_ptrlen, val_string_ptrlen) +FUNC1(uint, crc32_rfc1662, val_string_ptrlen) +FUNC1(uint, crc32_ssh1, val_string_ptrlen) +FUNC2(uint, crc32_update, uint, val_string_ptrlen) +FUNC2(boolean, crcda_detect, val_string_ptrlen, val_string_ptrlen) + +/* + * These functions aren't part of PuTTY's own API, but are additions + * by testcrypt itself for administrative purposes. + */ +FUNC1(void, random_queue, val_string_ptrlen) +FUNC0(uint, random_queue_len) +FUNC2(void, random_make_prng, hashalg, val_string_ptrlen) +FUNC0(void, random_clear) diff --git a/0.77_My_PuTTY/testsc.c b/0.77_My_PuTTY/testsc.c new file mode 100644 index 0000000..45365f0 --- /dev/null +++ b/0.77_My_PuTTY/testsc.c @@ -0,0 +1,1645 @@ +/* + * 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 "misc.h" +#include "mpint.h" +#include "ecc.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"); } +FILE *f_open(const Filename *filename, char const *mode, bool is_private) +{ unreachable("this is a stub needed to link, and should never be called"); } +void old_keyfile_warning(void) +{ unreachable("this is a stub needed to link, and should never be called"); } +/* For platforms where getticks is defined within this code base */ +unsigned long (getticks)(void) +{ unreachable("this is a stub needed to link, and should never be called"); } + +/* + * 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; +} + +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) { + 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); + } + *buf++ = random_buf[random_buf_limit--]; + } +} + +/* + * 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; +} + +/* 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_sdctr_hw) \ + X(Y, ssh_aes256_sdctr_sw) \ + X(Y, ssh_aes256_cbc) \ + X(Y, ssh_aes256_cbc_hw) \ + X(Y, ssh_aes256_cbc_sw) \ + X(Y, ssh_aes192_sdctr) \ + X(Y, ssh_aes192_sdctr_hw) \ + X(Y, ssh_aes192_sdctr_sw) \ + X(Y, ssh_aes192_cbc) \ + X(Y, ssh_aes192_cbc_hw) \ + X(Y, ssh_aes192_cbc_sw) \ + X(Y, ssh_aes128_sdctr) \ + X(Y, ssh_aes128_sdctr_hw) \ + X(Y, ssh_aes128_sdctr_sw) \ + X(Y, ssh_aes128_cbc) \ + X(Y, ssh_aes128_cbc_hw) \ + X(Y, ssh_aes128_cbc_sw) \ + X(Y, ssh2_chacha20_poly1305) \ + /* end of list */ + +#define CIPHER_TESTLIST(X, name) X(cipher_ ## name) + +#define 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) \ + /* 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_hw) \ + X(Y, ssh_sha1_sw) \ + X(Y, ssh_sha256) \ + X(Y, ssh_sha256_hw) \ + X(Y, ssh_sha256_sw) \ + X(Y, ssh_sha384) \ + X(Y, ssh_sha512) \ + 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) \ + MACS(MAC_TESTLIST, X) \ + HASHES(HASH_TESTLIST, X) \ + X(argon2) \ + /* 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) +{ + ssh2_mac *m = ssh2_mac_new(malg, NULL); + if (!m) { + test_skipped = true; + return; + } + + 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(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(); + 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(mkey); + sfree(data); + ssh2_mac_free(m); +} + +#define MAC_TESTFN(Y_unused, mac) \ + static void test_mac_##mac(void) { test_mac(&mac); } +MACS(MAC_TESTFN, Y_unused) + +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 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/0.77_My_PuTTY/testzlib.c b/0.77_My_PuTTY/testzlib.c new file mode 100644 index 0000000..ae6b792 --- /dev/null +++ b/0.77_My_PuTTY/testzlib.c @@ -0,0 +1,114 @@ +/* + * Main program to compile sshzlib.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/0.77_My_PuTTY/time.c b/0.77_My_PuTTY/time.c new file mode 100644 index 0000000..75b8953 --- /dev/null +++ b/0.77_My_PuTTY/time.c @@ -0,0 +1,16 @@ +/* + * Portable implementation of ltime() for any ISO-C platform where + * time_t behaves. (In practice, we've found that platforms such as + * Windows and Mac have needed their own specialised implementations.) + */ + +#include +#include + +struct tm ltime(void) +{ + time_t t; + time(&t); + assert (t != ((time_t)-1)); + return *localtime(&t); +} diff --git a/0.77_My_PuTTY/timing.c b/0.77_My_PuTTY/timing.c new file mode 100644 index 0000000..8036e43 --- /dev/null +++ b/0.77_My_PuTTY/timing.c @@ -0,0 +1,222 @@ +/* + * 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/0.77_My_PuTTY/tree234.c b/0.77_My_PuTTY/tree234.c new file mode 100644 index 0000000..17cdf64 --- /dev/null +++ b/0.77_My_PuTTY/tree234.c @@ -0,0 +1,1613 @@ +/* + * 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" + +#ifdef TEST +#define LOG(x) (printf x) +#define snew(type) ((type *)malloc(sizeof(type))) +#define snewn(n, type) ((type *)malloc((n) * sizeof(type))) +#define sresize(ptr, n, type) \ + ((type *)realloc(sizeof((type *)0 == (ptr)) ? (ptr) : (ptr), \ + (n) * sizeof(type))) +#define sfree(ptr) free(ptr) +#else +#include "puttymem.h" +#define LOG(x) +#endif + +typedef struct node234_Tag node234; + +struct tree234_Tag { + node234 *root; + cmpfn234 cmp; +}; + +struct node234_Tag { + node234 *parent; + node234 *kids[4]; + int counts[4]; + void *elems[3]; +}; + +/* + * Create a 2-3-4 tree. + */ +tree234 *newtree234(cmpfn234 cmp) +{ + tree234 *ret = snew(tree234); + LOG(("created tree %p\n", ret)); + ret->root = NULL; + ret->cmp = cmp; + return ret; +} + +/* + * Free a 2-3-4 tree (not including freeing the elements). + */ +static void freenode234(node234 * n) +{ + if (!n) + return; + freenode234(n->kids[0]); + freenode234(n->kids[1]); + freenode234(n->kids[2]); + freenode234(n->kids[3]); + sfree(n); +} + +void freetree234(tree234 * t) +{ + freenode234(t->root); + sfree(t); +} + +/* + * Internal function to count a node. + */ +static int countnode234(node234 * n) +{ + int count = 0; + int i; + if (!n) + return 0; + for (i = 0; i < 4; i++) + count += n->counts[i]; + for (i = 0; i < 3; i++) + if (n->elems[i]) + count++; + return count; +} + +/* + * 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. */ +#ifndef MOD_PERSO + assert(!(equal_permitted && !e)); +#endif + + 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 visted */ + 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, nkids); + } + + /* + * Add nelems to the running element count of the whole tree. + */ + ctx->elemcount += nelems; + + /* + * Check ordering property: all elements should be strictly > + * lowbound, strictly < highbound, and strictly < each other in + * sequence. (lowbound and highbound are NULL at edges of tree + * - both NULL at root node - and NULL is considered to be < + * everything and > everything. IYSWIM.) + */ + if (cmp) { + for (i = -1; i < nelems; i++) { + void *lower = (i == -1 ? lowbound : node->elems[i]); + void *higher = + (i + 1 == nelems ? highbound : node->elems[i + 1]); + if (lower && higher && cmp(lower, higher) >= 0) { + error("node %p: kid comparison [%d=%s,%d=%s] failed", + node, i, lower, i + 1, higher); + } + } + } + + /* + * Check parent pointers: all non-NULL kids should have a + * parent pointer coming back to this node. + */ + for (i = 0; i < nkids; i++) + if (node->kids[i]->parent != node) { + error("node %p kid %d: parent ptr is %p not %p", + node, i, node->kids[i]->parent, node); + } + + + /* + * Now (finally!) recurse into subtrees. + */ + count = nelems; + + for (i = 0; i < nkids; i++) { + void *lower = (i == 0 ? lowbound : node->elems[i - 1]); + void *higher = (i >= nelems ? highbound : node->elems[i]); + int subcount = + chknode(ctx, level + 1, node->kids[i], lower, higher); + if (node->counts[i] != subcount) { + error("node %p kid %d: count says %d, subtree really has %d", + node, i, node->counts[i], subcount); + } + count += subcount; + } + + return count; +} + +void verify(void) +{ + chkctx ctx[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); + } + printf("tree depth: %d\n", ctx->treedepth); + /* + * Enumerate the tree and ensure it matches up to the array. + */ + for (i = 0; NULL != (p = index234(tree, i)); i++) { + if (i >= arraylen) + error("tree contains more than %d elements", arraylen); + if (array[i] != p) + error("enum at position %d: array says %s, tree says %s", + i, array[i], p); + } + if (ctx->elemcount != i) { + error("tree really contains %d elements, enum gave %d", + ctx->elemcount, i); + } + if (i < arraylen) { + error("enum gave only %d elements, array has %d", i, arraylen); + } + i = count234(tree); + if (ctx->elemcount != i) { + error("tree really contains %d elements, count234 gave %d", + ctx->elemcount, i); + } +} + +void internal_addtest(void *elem, int index, void *realret) +{ + int i, j; + void *retval; + + if (arraysize < arraylen + 1) { + arraysize = arraylen + 1 + 256; + array = sresize(array, arraysize, void *); + } + + i = index; + /* now i points to the first element >= elem */ + retval = elem; /* expect elem returned (success) */ + for (j = arraylen; j > i; j--) + array[j] = array[j - 1]; + array[i] = elem; /* add elem to array */ + arraylen++; + + if (realret != retval) { + error("add: retval was %p expected %p", realret, retval); + } + + verify(); +} + +void addtest(void *elem) +{ + int i; + void *realret; + + realret = add234(tree, elem); + + i = 0; + while (i < arraylen && cmp(elem, array[i]) > 0) + i++; + if (i < arraylen && !cmp(elem, array[i])) { + void *retval = array[i]; /* expect that returned not elem */ + if (realret != retval) { + error("add: retval was %p expected %p", realret, retval); + } + } else + internal_addtest(elem, i, realret); +} + +void addpostest(void *elem, int i) +{ + void *realret; + + realret = addpos234(tree, elem, i); + + internal_addtest(elem, i, realret); +} + +void delpostest(int i) +{ + int index = i; + void *elem = array[i], *ret; + + /* i points to the right element */ + while (i < arraylen - 1) { + array[i] = array[i + 1]; + i++; + } + arraylen--; /* delete elem from array */ + + if (tree->cmp) + ret = del234(tree, elem); + else + ret = delpos234(tree, index); + + if (ret != elem) { + error("del returned %p, expected %p", ret, elem); + } + + verify(); +} + +void deltest(void *elem) +{ + int i; + + i = 0; + while (i < arraylen && cmp(elem, array[i]) > 0) + i++; + if (i >= arraylen || cmp(elem, array[i]) != 0) + return; /* don't do it! */ + delpostest(i); +} + +/* A sample data set and test utility. Designed for pseudo-randomness, + * and yet repeatability. */ + +/* + * This random number generator uses the `portable implementation' + * given in ANSI C99 draft N869. It assumes `unsigned' is 32 bits; + * change it if not. + */ +int randomnumber(unsigned *seed) +{ + *seed *= 1103515245; + *seed += 12345; + return ((*seed) / 65536) % 32768; +} + +int mycmp(void *av, void *bv) +{ + char const *a = (char const *) av; + char const *b = (char const *) bv; + return strcmp(a, b); +} + +#define lenof(x) ( sizeof((x)) / sizeof(*(x)) ) + +char *strings[] = { + "a", "ab", "absque", "coram", "de", + "palam", "clam", "cum", "ex", "e", + "sine", "tenus", "pro", "prae", + "banana", "carrot", "cabbage", "broccoli", "onion", "zebra", + "penguin", "blancmange", "pangolin", "whale", "hedgehog", + "giraffe", "peanut", "bungee", "foo", "bar", "baz", "quux", + "murfl", "spoo", "breen", "flarn", "octothorpe", + "snail", "tiger", "elephant", "octopus", "warthog", "armadillo", + "aardvark", "wyvern", "dragon", "elf", "dwarf", "orc", "goblin", + "pixie", "basilisk", "warg", "ape", "lizard", "newt", "shopkeeper", + "wand", "ring", "amulet" +}; + +#define NSTR lenof(strings) + +int findtest(void) +{ + const static int rels[] = { + REL234_EQ, REL234_GE, REL234_LE, REL234_LT, REL234_GT + }; + const static char *const relnames[] = { + "EQ", "GE", "LE", "LT", "GT" + }; + int i, j, rel, index; + char *p, *ret, *realret, *realret2; + int lo, hi, mid, c; + + for (i = 0; i < NSTR; i++) { + p = strings[i]; + for (j = 0; j < sizeof(rels) / sizeof(*rels); j++) { + rel = rels[j]; + + lo = 0; + hi = arraylen - 1; + while (lo <= hi) { + mid = (lo + hi) / 2; + c = strcmp(p, array[mid]); + if (c < 0) + hi = mid - 1; + else if (c > 0) + lo = mid + 1; + else + break; + } + + if (c == 0) { + if (rel == REL234_LT) + ret = (mid > 0 ? array[--mid] : NULL); + else if (rel == REL234_GT) + ret = (mid < arraylen - 1 ? array[++mid] : NULL); + else + ret = array[mid]; + } else { + assert(lo == hi + 1); + if (rel == REL234_LT || rel == REL234_LE) { + mid = hi; + ret = (hi >= 0 ? array[hi] : NULL); + } else if (rel == REL234_GT || rel == REL234_GE) { + mid = lo; + ret = (lo < arraylen ? array[lo] : NULL); + } else + ret = NULL; + } + + realret = findrelpos234(tree, p, NULL, rel, &index); + if (realret != ret) { + error("find(\"%s\",%s) gave %s should be %s", + p, relnames[j], realret, ret); + } + if (realret && index != mid) { + error("find(\"%s\",%s) gave %d should be %d", + p, relnames[j], index, mid); + } + if (realret && rel == REL234_EQ) { + realret2 = index234(tree, index); + if (realret2 != realret) { + error("find(\"%s\",%s) gave %s(%d) but %d -> %s", + p, relnames[j], realret, index, index, realret2); + } + } +#if 0 + printf("find(\"%s\",%s) gave %s(%d)\n", p, relnames[j], + realret, index); +#endif + } + } + + realret = findrelpos234(tree, NULL, NULL, REL234_GT, &index); + if (arraylen && (realret != array[0] || index != 0)) { + error("find(NULL,GT) gave %s(%d) should be %s(0)", + realret, index, array[0]); + } else if (!arraylen && (realret != NULL)) { + error("find(NULL,GT) gave %s(%d) should be NULL", realret, index); + } + + realret = findrelpos234(tree, NULL, NULL, REL234_LT, &index); + if (arraylen + && (realret != array[arraylen - 1] || index != arraylen - 1)) { + error("find(NULL,LT) gave %s(%d) should be %s(0)", realret, index, + array[arraylen - 1]); + } else if (!arraylen && (realret != NULL)) { + error("find(NULL,LT) gave %s(%d) should be NULL", realret, index); + } +} + +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 { + 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; + + 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; + + printf("beginning searchtest:"); + for (n = 0; (p = index234(tree, n)) != NULL; n++) { + expected[n] = p; + printf(" %d=%s", n, p); + } + printf(" count=%d\n", n); + + search234_start(&ss, tree); + searchtest_recurse(ss, 0, n, expected, directionbuf, directionbuf); +} + +int main(void) +{ + int in[NSTR]; + int i, j, k; + unsigned seed = 0; + + for (i = 0; i < NSTR; i++) + in[i] = 0; + array = NULL; + arraylen = arraysize = 0; + tree = newtree234(mycmp); + cmp = mycmp; + + verify(); + searchtest(); + for (i = 0; i < 10000; i++) { + j = randomnumber(&seed); + j %= NSTR; + printf("trial: %d\n", i); + if (in[j]) { + printf("deleting %s (%d)\n", strings[j], j); + deltest(strings[j]); + in[j] = 0; + } else { + printf("adding %s (%d)\n", strings[j], j); + addtest(strings[j]); + in[j] = 1; + } + findtest(); + 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++) { + printf("trial: %d\n", i); + j = randomnumber(&seed); + j %= NSTR; + k = randomnumber(&seed); + k %= count234(tree) + 1; + printf("adding string %s at index %d\n", strings[j], k); + addpostest(strings[j], k); + } + while (count234(tree) > 0) { + printf("cleanup: tree size %d\n", count234(tree)); + j = randomnumber(&seed); + j %= count234(tree); + printf("deleting string %s from index %d\n", + (const char *)array[j], j); + delpostest(j); + } + + printf("%d errors found\n", n_errors); + return (n_errors != 0); +} + +#endif diff --git a/0.77_My_PuTTY/tree234.h b/0.77_My_PuTTY/tree234.h new file mode 100644 index 0000000..55cbe36 --- /dev/null +++ b/0.77_My_PuTTY/tree234.h @@ -0,0 +1,195 @@ +/* + * 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/0.77_My_PuTTY/unix/unix.h b/0.77_My_PuTTY/unix/unix.h new file mode 100644 index 0000000..c77138c --- /dev/null +++ b/0.77_My_PuTTY/unix/unix.h @@ -0,0 +1,460 @@ +#ifndef PUTTY_UNIX_H +#define PUTTY_UNIX_H + +#ifdef HAVE_CONFIG_H +# include "uxconfig.h" /* Space to hide it from mkfiles.pl */ +#endif + +#include /* for FILENAME_MAX */ +#include /* C99 int types */ +#ifndef NO_LIBDL +#include /* Dynamic library loading */ +#endif /* NO_LIBDL */ +#include "charset.h" +#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); + +struct FontSpec { + char *name; /* may be "" to indicate no selected font at all */ +}; +struct FontSpec *fontspec_new(const char *name); + +extern const struct BackendVtable pty_backend; + +#define BROKEN_PIPE_ERROR_CODE EPIPE /* used in sshshare.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. + */ +#define HELPCTX(x) P(NULL) +#define FILTER_KEY_FILES NULL /* FIXME */ +#define FILTER_DYNLIB_FILES NULL /* FIXME */ + +/* + * Under X, selection data must not be NUL-terminated. + */ +#define SELECTION_NUL_TERMINATED 0 + +/* + * Under X, copying to the clipboard terminates lines with just LF. + */ +#define SEL_NL { 10 } + +/* Simple wraparound timer function */ +unsigned long getticks(void); +#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 gtkwin.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 gtkmain.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 gtkcomm.c */ +void gtkcomm_setup(void); + +/* Used to pass application-menu operations from gtkapp.c to gtkwin.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 gtkdlg.c needs from gtkwin.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 gtkwin.c needs from gtkdlg.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); +int gtk_seat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **fingerprints, + void (*callback)(void *ctx, int result), void *ctx); +int gtk_seat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx); +int gtk_seat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx); +#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 + +/* gtkwin.c needs this special function in xkeysym.c */ +int keysym_to_unicode(int keysym); + +/* Things uxstore.c needs from gtkwin.c */ +char *x_get_default(const char *key); + +/* Things uxstore.c provides to gtkwin.c */ +void provide_xrm_string(const char *string, const char *progname); + +/* Function that {gtkapp,gtkmain}.c needs from ux{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 uxpty.c to restore the original environment before + * launching its subprocess. + */ +extern char *pty_osx_envrestore_prefix; + +/* Things provided by uxcons.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); + +/* uxcfg.c */ +struct controlbox; +void unix_setup_config_box( + struct controlbox *b, bool midsession, int protocol); + +/* gtkcfg.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 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); + +/* uxmisc.c */ +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. + */ +struct unicode_data; +bool init_ucs(struct unicode_data *ucsdata, char *line_codepage, + bool utf8_override, int font_charset, int vtmode); + +/* + * Spare functions exported directly from uxnet.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 uxser.c. + */ +extern const struct BackendVtable serial_backend; + +/* + * uxpeer.c, wrapping getsockopt(SO_PEERCRED). + */ +bool so_peercred(int fd, int *pid, int *uid, int *gid); + +/* + * uxfdsock.c. + */ +Socket *make_fd_socket(int infd, int outfd, int inerrfd, Plug *plug); + +/* + * 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 + +/* + * uxpty.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; + +/* + * gtkask.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); +} + +/* + * uxsftpserver.c. + */ +extern const SftpServerVtable unix_live_sftpserver_vt; + +/* + * uxpoll.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; +} + +/* + * uxcliloop.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); + +#endif /* PUTTY_UNIX_H */ diff --git a/0.77_My_PuTTY/utils.c b/0.77_My_PuTTY/utils.c new file mode 100644 index 0000000..25d12c4 --- /dev/null +++ b/0.77_My_PuTTY/utils.c @@ -0,0 +1,1122 @@ +/* + * Platform-independent utility routines used throughout this code base. + * + * This file is linked into stand-alone test utilities which only want + * to include the things they really need, so functions in here should + * avoid depending on any functions outside it. Utility routines that + * are more tightly integrated into the main code should live in + * misc.c. + */ + +#include +#include +#include +#include +#include +#include + +#include "defs.h" +#include "misc.h" +#include "ssh.h" + +/* + * Parse a string block size specification. This is approximately a + * subset of the block size specs supported by GNU fileutils: + * "nk" = n kilobytes + * "nM" = n megabytes + * "nG" = n gigabytes + * All numbers are decimal, and suffixes refer to powers of two. + * Case-insensitive. + */ +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; +} + +/* + * Parse a ^C style character specification. + * Returns NULL in `next' if we didn't recognise it as a control character, + * in which case `c' should be ignored. + * The precise current parsing is an oddity inherited from the terminal + * answerback-string parsing code. All sequences start with ^; all except + * ^<123> are two characters. The ones that are worth keeping are probably: + * ^? 127 + * ^@A-Z[\]^_ 0-31 + * a-z 1-26 + * specified by number (decimal, 0octal, 0xHEX) + * ~ ^ escape + */ +char ctrlparse(char *s, char **next) +{ + char c = 0; + if (*s != '^') { + *next = NULL; + } else { + s++; + if (*s == '\0') { + *next = NULL; + } else if (*s == '<') { + s++; + c = (char)strtol(s, next, 0); + if ((*next == s) || (**next != '>')) { + c = 0; + *next = NULL; + } else + (*next)++; + } else if (*s >= 'a' && *s <= 'z') { + c = (*s - ('a' - 1)); + *next = s+1; + } else if ((*s >= '@' && *s <= '_') || *s == '?' || (*s & 0x80)) { + c = ('@' ^ *s); + *next = s+1; + } else if (*s == '~') { + c = '^'; + *next = s+1; + } + } + return c; +} + +/* + * Find a character in a string, unless it's a colon contained within + * square brackets. Used for untangling strings of the form + * 'host:port', where host can be an IPv6 literal. + * + * We provide several variants of this function, with semantics like + * various standard string.h functions. + */ +static const char *host_strchr_internal(const char *s, const char *set, + 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++; + } +} +size_t host_strcspn(const char *s, const char *set) +{ + const char *answer = host_strchr_internal(s, set, true); + if (answer) + return answer - s; + else + return strlen(s); +} +char *host_strchr(const char *s, int c) +{ + char set[2]; + set[0] = c; + set[1] = '\0'; + return (char *) host_strchr_internal(s, set, true); +} +char *host_strrchr(const char *s, int c) +{ + char set[2]; + set[0] = c; + set[1] = '\0'; + return (char *) host_strchr_internal(s, set, false); +} + +#ifdef TEST_HOST_STRFOO +int main(void) +{ + int passes = 0, fails = 0; + +#define TEST1(func, string, arg2, suffix, result) do \ + { \ + const char *str = string; \ + unsigned ret = func(string, arg2) suffix; \ + if (ret == result) { \ + passes++; \ + } else { \ + printf("fail: %s(%s,%s)%s = %u, expected %u\n", \ + #func, #string, #arg2, #suffix, ret, \ + (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; +} + +/* Stubs to stop the rest of this module causing compile failures. */ +static NORETURN void fatal_error(const char *p, ...) +{ + va_list ap; + fprintf(stderr, "host_string_test: "); + 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"); } + +#endif /* TEST_HOST_STRFOO */ + +/* + * Trim square brackets off the outside of an IPv6 address literal. + * Leave all other strings unchanged. Returns a fresh dynamically + * allocated string. + */ +char *host_strduptrim(const char *s) +{ + if (s[0] == '[') { + const char *p = s+1; + int colons = 0; + while (*p && *p != ']') { + if (isxdigit((unsigned char)*p)) + /* OK */; + else if (*p == ':') + colons++; + else + break; + p++; + } + if (*p == '%') { + /* + * 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); +} + +/* ---------------------------------------------------------------------- + * String handling routines. + */ + +char *dupstr(const char *s) +{ + char *p = NULL; + if (s) { + int len = strlen(s); + p = snewn(len + 1, char); + strcpy(p, s); + } + return p; +} + +/* Allocate the concatenation of N strings. Terminate arg list with NULL. */ +char *dupcat_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; +} + +void burnstr(char *string) /* sfree(str), only clear it first */ +{ + if (string) { + smemclr(string, strlen(string)); + sfree(string); + } +} + +int string_length_for_printf(size_t s) +{ + /* Truncate absurdly long strings (should one show up) to fit + * within a positive 'int', which is what the "%.*s" format will + * expect. */ + if (s > INT_MAX) + return INT_MAX; + return s; +} + +/* 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 + +/* + * 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. + */ +static 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; +} + +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 strbuf *strbuf_new_general(bool nm) +{ + struct strbuf_impl *buf = snew(struct strbuf_impl); + BinarySink_INIT(&buf->visible, strbuf_BinarySink_write); + 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; +} +void strbuf_catfv(strbuf *buf_o, const char *fmt, va_list ap) +{ + 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); +} +void strbuf_catf(strbuf *buf_o, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + strbuf_catfv(buf_o, fmt, ap); + va_end(ap); +} + +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); +} + +/* + * Read an entire line of text from a file. Return a buffer + * malloced to be as big as necessary (caller must free). + */ +char *fgetline(FILE *fp) +{ + char *ret = snewn(512, char); + 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; +} + +/* + * Read an entire file into a BinarySink. + */ +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); + } +} + +/* + * 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. + */ +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; +} + +/* ---------------------------------------------------------------------- + * Core base64 encoding and decoding routines. + */ + +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] = '='; +} + +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; +} + +/* ---------------------------------------------------------------------- + * Generic routines to deal with send buffers: a linked list of + * smallish blocks, with the operations + * + * - add an arbitrary amount of data to the end of the list + * - remove the first N bytes from the list + * - return a (pointer,length) pair giving some initial data in + * the list, suitable for passing to a send or write system + * call + * - retrieve a larger amount of initial data from the list + * - return the current size of the buffer chain in bytes + */ + +#define BUFFER_MIN_GRANULE 512 + +struct bufchain_granule { + struct bufchain_granule *next; + char *bufpos, *bufend, *bufmax; +}; + +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_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; +} + +/* ---------------------------------------------------------------------- + * Debugging routines. + */ + +#ifdef DEBUG +extern void dputs(const char *); /* defined in per-platform *misc.c */ + +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); + } +} + +#endif /* def DEBUG */ + +#ifndef PLATFORM_HAS_SMEMCLR +/* + * Securely wipe memory. + * + * The actual wiping is no different from what memset would do: the + * point of 'securely' is to try to be sure over-clever compilers + * won't optimise away memsets on variables that are about to be freed + * or go out of scope. See + * https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/771-BSI.html + * + * Some platforms (e.g. Windows) may provide their own version of this + * function. + */ +void smemclr(void *b, size_t n) { + volatile char *vp; + + if (b && n > 0) { + /* + * Zero out the memory. + */ + memset(b, 0, n); + + /* + * Perform a volatile access to the object, forcing the + * compiler to admit that the previous memset was important. + * + * This while loop should in practice run for zero iterations + * (since we know we just zeroed the object out), but in + * theory (as far as the compiler knows) it might range over + * the whole object. (If we had just written, say, '*vp = + * *vp;', a compiler could in principle have 'helpfully' + * optimised the memset into only zeroing out the first byte. + * This should be robust.) + */ + vp = b; + while (*vp) vp++; + } +} +#endif + +bool smemeq(const void *av, const void *bv, size_t len) +{ + const unsigned char *a = (const unsigned char *)av; + const unsigned char *b = (const unsigned char *)bv; + unsigned val = 0; + + while (len-- > 0) { + val |= *a++ ^ *b++; + } + /* Now val is 0 iff we want to return 1, and in the range + * 0x01..0xFF iff we want to return 0. So subtracting from 0x100 + * will clear bit 8 iff we want to return 0, and leave it set iff + * we want to return 1, so then we can just shift down. */ + return (0x100 - val) >> 8; +} + +int 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); +} + +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; +} + +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); +} + +size_t encode_utf8(void *output, unsigned long ch) +{ + unsigned char *start = (unsigned char *)output, *p = start; + + if (ch < 0x80) { + *p++ = ch; + } else if (ch < 0x800) { + *p++ = 0xC0 | (ch >> 6); + *p++ = 0x80 | (ch & 0x3F); + } else if (ch < 0x10000) { + *p++ = 0xE0 | (ch >> 12); + *p++ = 0x80 | ((ch >> 6) & 0x3F); + *p++ = 0x80 | (ch & 0x3F); + } else { + *p++ = 0xF0 | (ch >> 18); + *p++ = 0x80 | ((ch >> 12) & 0x3F); + *p++ = 0x80 | ((ch >> 6) & 0x3F); + *p++ = 0x80 | (ch & 0x3F); + } + return p - start; +} + +void write_c_string_literal(FILE *fp, ptrlen str) +{ + for (const char *p = str.ptr; p < (const char *)str.ptr + str.len; p++) { + char c = *p; + + if (c == '\n') + fputs("\\n", fp); + else if (c == '\r') + fputs("\\r", fp); + else if (c == '\t') + fputs("\\t", fp); + else if (c == '\b') + fputs("\\b", fp); + else if (c == '\\') + fputs("\\\\", fp); + else if (c == '"') + fputs("\\\"", fp); + else if (c >= 32 && c <= 126) + fputc(c, fp); + else + fprintf(fp, "\\%03o", (unsigned char)c); + } +} + +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++; + } + } +} + +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/0.77_My_PuTTY/version.c b/0.77_My_PuTTY/version.c new file mode 100644 index 0000000..570d148 --- /dev/null +++ b/0.77_My_PuTTY/version.c @@ -0,0 +1,43 @@ +/* + * 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" + +#ifdef SOURCE_COMMIT +#include "empty.h" +#endif + +#include "version.h" + +#ifdef MOD_PERSO +#include +#include +const char ver[] = "Custom build" ; +const char sshver[] = "PuTTY-KiTTY\0 " ; +void set_sshver( char * vers ) { + if(strlen(vers)<40 ) { + strcpy( sshver, vers ) ; + } else { + vers[40]='\0'; + memcpy( sshver,vers, 40 ) ; + } +} +#else +const char ver[] = TEXTVER; +const char sshver[] = SSHVER; +#endif + +const char commitid[] = SOURCE_COMMIT; + +/* + * 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/0.77_My_PuTTY/version.h b/0.77_My_PuTTY/version.h new file mode 100644 index 0000000..9c72066 --- /dev/null +++ b/0.77_My_PuTTY/version.h @@ -0,0 +1,5 @@ +#define RELEASE 0.76 +#define TEXTVER "Release 0.76" +#define SSHVER "-Release-0.76" +#define BINARY_VERSION 0,76,0,8 +#define SOURCE_COMMIT "unavailable" diff --git a/0.77_My_PuTTY/wcwidth.c b/0.77_My_PuTTY/wcwidth.c new file mode 100644 index 0000000..66cf66d --- /dev/null +++ b/0.77_My_PuTTY/wcwidth.c @@ -0,0 +1,558 @@ +/* + * 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 */ + /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ + static const struct interval combining[] = { + { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, + { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, + { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, + { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, + { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, + { 0xE0100, 0xE01EF } + }; + + /* A sorted list of intervals of double-width characters generated by: + * https://raw.githubusercontent.com/GNOME/glib/37d4c2941bd0326b8b6e6bb22c81bd424fcc040b/glib/gen-unicode-tables.pl + * from the Unicode 9.0.0 data files available at: + * https://www.unicode.org/Public/13.0.0/ucd/ + */ + static const struct interval wide[] = { + {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}, + {0x1B000, 0x1B11E}, + {0x1B150, 0x1B152}, + {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}, + {0x1F6EB, 0x1F6EC}, + {0x1F6F4, 0x1F6FC}, + {0x1F7E0, 0x1F7EB}, + {0x1F90C, 0x1F93A}, + {0x1F93C, 0x1F945}, + {0x1F947, 0x1F978}, + {0x1F97A, 0x1F9CB}, + {0x1F9CD, 0x1F9FF}, + {0x1FA70, 0x1FA74}, + {0x1FA78, 0x1FA7A}, + {0x1FA80, 0x1FA86}, + {0x1FA90, 0x1FAA8}, + {0x1FAB0, 0x1FAB6}, + {0x1FAC0, 0x1FAC2}, + {0x1FAD0, 0x1FAD6}, + {0x20000, 0x2FFFD}, + {0x30000, 0x3FFFD}, + }; + + /* 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 generated by: + * https://raw.githubusercontent.com/GNOME/glib/37d4c2941bd0326b8b6e6bb22c81bd424fcc040b/glib/gen-unicode-tables.pl + * from the Unicode 9.0.0 data files available at: + * http://www.unicode.org/Public/9.0.0/ucd/ + */ + static const struct interval ambiguous[] = { + {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}, + }; + + /* 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/0.77_My_PuTTY/wildcard.c b/0.77_My_PuTTY/wildcard.c new file mode 100644 index 0000000..9c560d0 --- /dev/null +++ b/0.77_My_PuTTY/wildcard.c @@ -0,0 +1,486 @@ +/* + * 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 TESTMODE + +struct test { + const char *wildcard; + const char *target; + int expected_result; +}; + +const struct test fragment_tests[] = { + /* + * We exhaustively unit-test the fragment matching routine + * itself, which should save us the need to test all its + * intricacies during the full wildcard tests. + */ + {"abc", "abc", 1}, + {"abc", "abd", 0}, + {"abc", "abcd", 1}, + {"abcd", "abc", 0}, + {"ab[cd]", "abc", 1}, + {"ab[cd]", "abd", 1}, + {"ab[cd]", "abe", 0}, + {"ab[^cd]", "abc", 0}, + {"ab[^cd]", "abd", 0}, + {"ab[^cd]", "abe", 1}, + {"ab\\", "abc", -WC_TRAILINGBACKSLASH}, + {"ab\\*", "ab*", 1}, + {"ab\\?", "ab*", 0}, + {"ab?", "abc", 1}, + {"ab?", "ab", 0}, + {"ab[", "abc", -WC_UNCLOSEDCLASS}, + {"ab[c-", "abb", -WC_UNCLOSEDCLASS}, + {"ab[c-]", "abb", -WC_INVALIDRANGE}, + {"ab[c-e]", "abb", 0}, + {"ab[c-e]", "abc", 1}, + {"ab[c-e]", "abd", 1}, + {"ab[c-e]", "abe", 1}, + {"ab[c-e]", "abf", 0}, + {"ab[e-c]", "abb", 0}, + {"ab[e-c]", "abc", 1}, + {"ab[e-c]", "abd", 1}, + {"ab[e-c]", "abe", 1}, + {"ab[e-c]", "abf", 0}, + {"ab[^c-e]", "abb", 1}, + {"ab[^c-e]", "abc", 0}, + {"ab[^c-e]", "abd", 0}, + {"ab[^c-e]", "abe", 0}, + {"ab[^c-e]", "abf", 1}, + {"ab[^e-c]", "abb", 1}, + {"ab[^e-c]", "abc", 0}, + {"ab[^e-c]", "abd", 0}, + {"ab[^e-c]", "abe", 0}, + {"ab[^e-c]", "abf", 1}, + {"ab[a^]", "aba", 1}, + {"ab[a^]", "ab^", 1}, + {"ab[a^]", "abb", 0}, + {"ab[^a^]", "aba", 0}, + {"ab[^a^]", "ab^", 0}, + {"ab[^a^]", "abb", 1}, + {"ab[-c]", "ab-", 1}, + {"ab[-c]", "abc", 1}, + {"ab[-c]", "abd", 0}, + {"ab[^-c]", "ab-", 0}, + {"ab[^-c]", "abc", 0}, + {"ab[^-c]", "abd", 1}, + {"ab[\\[-\\]]", "abZ", 0}, + {"ab[\\[-\\]]", "ab[", 1}, + {"ab[\\[-\\]]", "ab\\", 1}, + {"ab[\\[-\\]]", "ab]", 1}, + {"ab[\\[-\\]]", "ab^", 0}, + {"ab[^\\[-\\]]", "abZ", 1}, + {"ab[^\\[-\\]]", "ab[", 0}, + {"ab[^\\[-\\]]", "ab\\", 0}, + {"ab[^\\[-\\]]", "ab]", 0}, + {"ab[^\\[-\\]]", "ab^", 1}, + {"ab[a-fA-F]", "aba", 1}, + {"ab[a-fA-F]", "abF", 1}, + {"ab[a-fA-F]", "abZ", 0}, +}; + +const struct test full_tests[] = { + {"a", "argh", 0}, + {"a", "ba", 0}, + {"a", "a", 1}, + {"a*", "aardvark", 1}, + {"a*", "badger", 0}, + {"*a", "park", 0}, + {"*a", "pArka", 1}, + {"*a", "parka", 1}, + {"*a*", "park", 1}, + {"*a*", "perk", 0}, + {"?b*r?", "abracadabra", 1}, + {"?b*r?", "abracadabr", 0}, + {"?b*r?", "abracadabzr", 0}, +}; + +int main(void) +{ + int i; + int fails, passes; + + fails = passes = 0; + + for (i = 0; i < sizeof(fragment_tests)/sizeof(*fragment_tests); i++) { + const char *f, *t; + int eret, aret; + f = fragment_tests[i].wildcard; + t = fragment_tests[i].target; + eret = fragment_tests[i].expected_result; + aret = wc_match_fragment(&f, &t, 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 diff --git a/0.77_My_PuTTY/windows/MAKEFILE.MINGW b/0.77_My_PuTTY/windows/MAKEFILE.MINGW new file mode 100644 index 0000000..7df5b37 --- /dev/null +++ b/0.77_My_PuTTY/windows/MAKEFILE.MINGW @@ -0,0 +1,2388 @@ +# Makefile for putty under MinGW, Cygwin, or Winelib. +# +# 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. + +# You can define this path to point at your tools if you need to +# TOOLPATH = c:\cygwin\bin\ # or similar, if you're running Windows +# TOOLPATH = /pkg/mingw32msvc/i386-mingw32msvc/bin/ +# TOOLPATH = i686-w64-mingw32- +# make -e TOOLPATH=/usr/bin/i686-w64-mingw32- -f MAKEFILE.MINGW putty.exe +CC = $(TOOLPATH)gcc +RC = $(TOOLPATH)windres +# Uncomment the following two lines to compile under Winelib +# CC = winegcc +# RC = wrc +# You may also need to tell windres where to find include files: +# RCINC = --include-dir c:\cygwin\include\ + +# CFLAGS = -Wall -O2 -std=gnu99 -Wvla -D_WINDOWS -DWIN32S_COMPAT \ + +CFLAGS = -Wall -O2 -std=gnu99 -Wvla -D_WINDOWS -DWIN32S_COMPAT \ + -D_NO_OLDNAMES -D__USE_MINGW_ANSI_STDIO=1 -I.././ \ + -I../charset/ -I../windows/ -I../unix/ +LDFLAGS = -s +RCFLAGS = $(RCINC) --define WIN32=1 --define _WIN32=1 --define WINVER=0x0400 \ + -I.././ -I../charset/ -I../windows/ -I../unix/ + +# _WIN32_IE is required to expose identifiers that only make sense on +# systems with IE5+ installed, such as some arguments to SHGetFolderPath(). +# WINVER etc perform a similar function for FlashWindowEx(). +CFLAGS += -D_WIN32_IE=0x0500 +#CFLAGS += -DWINVER=0x0500 -D_WIN32_WINDOWS=0x0410 -D_WIN32_WINNT=0x0500 + +# Extended mouse support (> 223 characters) +UTF8MOUSE_OBJS = fromucs.o slookup.o sbcsdat.o sbcs.o utf8.o + +################################################################################ +# BEGIN MOD_PERSO +# Ne pas oublier de commenter 2 lignes CFLAGS au-dessus (remplacé par en-dessous) +################################################################################ +CFLAGS += -DWINVER=0x0501 -D_WIN32_WINDOWS=0x0410 -D_WIN32_WINNT=0x0501 + +# -DMOD_PERSO -DMOD_BACKGROUNDIMAGE -DMOD_RECONNECT -DMOD_HYPERLINK -DMOD_ZMODEM -DMOD_PROXY -DMOD_STARTBUTTON -DMOD_LAUNCHER -DMOD_SAVEDUMP -DMOD_KEYMAPPING -DMOD_TUTTY -DMOD_TUTTYCOLOR -DMOD_PORTKNOCKING -DMOD_PUTTYX -DMOD_WTS -DMOD_PRINTCLIP -DMOD_RUTTY -DMOD_ADB \ + + +# Flag special FLJ +FLJFLAG = FLJ1 + +# Ajouter -g -ggdb a CFLAGS pour avoir plus de debug !!! + +# Ajout du DEBUG +# CFLAGS += -DDEBUG + +RCFLAGS += -I ../../ -DMOD_PERSO -D$(FLJFLAG) + +# Flag pour éviter les crashes sur processeurs AMD => Attention, fait planter la compilation 64bits +# CFLAGS += -mms-bitfields -march=i386 + +CFLAGS += -DNO_SECUREZEROMEMORY -DCOVERITY -DNO_HTMLHELP +CFLAGS += -D$(FLJFLAG) +CFLAGS += \ + -DMOD_WTS \ + -DMOD_PERSO \ + -DMOD_STARTBUTTON \ + -DMOD_SAVEDUMP \ + -DMOD_DISABLEALTGR \ + -DMOD_KEYMAPPING \ + -DMOD_PUTTYX \ + -DMOD_PORTKNOCKING \ + -DMOD_HYPERLINK \ + -DMOD_PRINTCLIP \ + -DMOD_LAUNCHER \ + -DMOD_RECONNECT \ + -DMOD_RUTTY \ + -DMOD_TUTTY \ + -DMOD_ADB \ + -DMOD_BACKGROUNDIMAGE \ + -DMOD_ZMODEM \ + -DMOD_PROXY \ + -DMOD_FAR2L \ + -I.././ -I../../ \ + -I../charset/ -I../windows/ -I../unix/ -I../mac/ \ + -I../macosx \ + -I../../base64 \ + -I../../bcrypt \ + -I../../md5 -I../../regex -I../../url -I../../rutty + + +# -DMOD_TUTTYCOLOR + + +# Autre flag a gérer +# Ajouter -DMOD_NOPASSWORD \ pour compiler une version sans possibilité de sauvegarder le mot de passe +# Ajouter -DMOD_NOTRANSPARENCY \ pour compiler une version desactivant la transparence +# Ajouter -DMOD_NOHYPERLINK \ pour compiler une version desactivant la gestion des liens hyperlink +# Ajouter -DMOD_PORTABLE \ pour forcer le mode portable + +# Autre flags pour les fichiers .o uniquement (pas au global dans CFLAGS) +# MOD_INTEGRATED_AGENT: intègre l'agent directement +# MOD_INTEGRATED_KEYGEN: intègre le key generator directement + +CFLAGS_PLINK = -Wall -O2 -D_WINDOWS -DWIN32S_COMPAT \ + -DMOD_PERSO -DMOD_RECONNECT -DNO_MOD_RUTTY \ + -D_NO_OLDNAMES -DNO_HTMLHELP -I../. \ + -I../charset -I../windows -I../unix -I../mac -I.\ + -I../macosx \ + -I../../base64 -I../../bcrypt -I../../md5 -I../../regex -I../../url +################################################################################ +# END MOD_PERSO +################################################################################ + +.SUFFIXES: + +all: pageant.exe plink.exe pscp.exe psftp.exe psocks.exe putty.exe \ + puttygen.exe puttytel.exe testcrypt.exe + +pageant.exe: aqsync.o be_misc.o callback.o conf.o ecc.o errsock.o marshal.o \ + memory.o misc.o mpint.o pageant.o pageant.res.o sshaes.o \ + sshargon2.o sshauxcrypt.o sshblake2.o sshdes.o sshdss.o \ + sshecc.o sshhmac.o sshmd5.o sshpubk.o sshrsa.o sshsh256.o \ + sshsh512.o sshsha.o sshsha3.o stripctrl.o tree234.o utils.o \ + version.o wcwidth.o wincapi.o winhandl.o winhelp.o \ + winhsock.o winmisc.o winmiscs.o winnet.o winnpc.o winnps.o \ + winpgnt.o winpgntc.o winsecur.o winselgui.o winutils.o \ + notiming.o \ + kitty_commun.o kitty_crypt.o kitty_registry.o + $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,pageant.map aqsync.o \ + be_misc.o callback.o conf.o ecc.o errsock.o marshal.o \ + memory.o misc.o mpint.o pageant.o pageant.res.o sshaes.o \ + sshargon2.o sshauxcrypt.o sshblake2.o sshdes.o sshdss.o \ + sshecc.o sshhmac.o sshmd5.o sshpubk.o sshrsa.o sshsh256.o \ + sshsh512.o sshsha.o sshsha3.o stripctrl.o tree234.o utils.o \ + version.o wcwidth.o wincapi.o winhandl.o winhelp.o \ + winhsock.o winmisc.o winmiscs.o winnet.o winnpc.o winnps.o \ + winpgnt.o winpgntc.o winsecur.o winselgui.o winutils.o \ + notiming.o \ + kitty_commun.o kitty_crypt.o kitty_registry.o \ + ../../bcrypt/bcrypt.a ../../mini/mini.a \ + -ladvapi32 -lcomdlg32 -lgdi32 -limm32 -lole32 -lshell32 \ + -luser32 + +plink.exe: agentf.o aqsync.o \ + be_all_s_plink.o \ + be_misc.o callback.o clicons.o \ + cmdline.o conf.o console.o cproxy.o ecc.o errsock.o \ + ldisc_plink.o \ + logging.o mainchan.o marshal.o memory.o misc.o miscucs.o \ + mpint.o noterm.o nullplug.o pgssapi.o pinger.o plink.res.o \ + portfwd.o proxy.o raw.o rlogin.o sessprep.o settings.o ssh.o \ + ssh1bpp.o ssh1censor.o ssh1connection.o \ + ssh1connection-client.o ssh1login.o ssh2bpp.o ssh2bpp-bare.o \ + ssh2censor.o ssh2connection.o ssh2connection-client.o \ + ssh2kex-client.o ssh2transhk.o ssh2transport.o \ + ssh2userauth.o sshaes.o ssharcf.o sshargon2.o sshauxcrypt.o \ + sshblake2.o sshblowf.o sshccp.o sshcommon.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshecc.o sshgssc.o \ + sshhmac.o sshmac.o sshmd5.o sshprng.o sshpubk.o sshrand.o \ + sshrsa.o sshsh256.o sshsh512.o sshsha.o sshsha3.o sshshare.o \ + sshutils.o sshverstring.o sshzlib.o stripctrl.o supdup.o \ + telnet.o timing.o tree234.o utils.o version.o wcwidth.o \ + wildcard.o wincapi.o wincliloop.o wincons.o windefs.o \ + wingss.o winhandl.o winhsock.o winmisc.o winmiscs.o winnet.o \ + winnohlp.o winnoise.o winnojmp.o winnpc.o winnps.o \ + winpgntc.o winplink.o winproxy.o winsecur.o winselcli.o \ + winser.o winshare.o winstore.o wintime.o winucs.o winx11.o \ + x11fwd.o \ + adb.o kitty_registry.o kitty_commun.o kitty_proxy.o kitty_ssh.o kitty_store.o kitty_tools.o + $(CC) $(LDFLAGS) -o $@ -Wl,-Map,plink.map agentf.o aqsync.o \ + be_all_s_plink.o \ + be_misc.o callback.o clicons.o cmdline.o conf.o \ + console.o cproxy.o ecc.o errsock.o \ + ldisc_plink.o \ + logging.o \ + mainchan.o marshal.o memory.o misc.o miscucs.o mpint.o \ + noterm.o nullplug.o pgssapi.o pinger.o plink.res.o portfwd.o \ + proxy.o raw.o rlogin.o sessprep.o settings.o ssh.o ssh1bpp.o \ + ssh1censor.o ssh1connection.o ssh1connection-client.o \ + ssh1login.o ssh2bpp.o ssh2bpp-bare.o ssh2censor.o \ + ssh2connection.o ssh2connection-client.o ssh2kex-client.o \ + ssh2transhk.o ssh2transport.o ssh2userauth.o sshaes.o \ + ssharcf.o sshargon2.o sshauxcrypt.o sshblake2.o sshblowf.o \ + sshccp.o sshcommon.o sshcrc.o sshcrcda.o sshdes.o sshdh.o \ + sshdss.o sshecc.o sshgssc.o sshhmac.o sshmac.o sshmd5.o \ + sshprng.o sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o \ + sshsha.o sshsha3.o sshshare.o sshutils.o sshverstring.o \ + sshzlib.o stripctrl.o supdup.o telnet.o timing.o tree234.o \ + utils.o version.o wcwidth.o wildcard.o wincapi.o \ + wincliloop.o wincons.o windefs.o wingss.o winhandl.o \ + winhsock.o winmisc.o winmiscs.o winnet.o winnohlp.o \ + winnoise.o winnojmp.o winnpc.o winnps.o winpgntc.o \ + winplink.o winproxy.o winsecur.o winselcli.o winser.o \ + winshare.o winstore.o wintime.o winucs.o winx11.o x11fwd.o \ + adb.o kitty_registry.o kitty_commun.o kitty_proxy.o kitty_ssh.o kitty_store.o kitty_tools.o \ + ../../base64/base64.a ../../bcrypt/bcrypt.a ../../mini/mini.a \ + -ladvapi32 -lcomdlg32 -lgdi32 -limm32 -lole32 -lshell32 \ + -luser32 \ + -lwsock32 + +pscp.exe: agentf.o aqsync.o be_misc.o be_ssh.o callback.o clicons.o \ + cmdline.o conf.o console.o cproxy.o ecc.o errsock.o \ + logging.o mainchan.o marshal.o memory.o misc.o miscucs.o \ + mpint.o nullplug.o pgssapi.o pinger.o portfwd.o proxy.o \ + pscp.o pscp.res.o psftpcommon.o settings.o sftp.o \ + sftpcommon.o ssh.o ssh1bpp.o ssh1censor.o ssh1connection.o \ + ssh1connection-client.o ssh1login.o ssh2bpp.o ssh2bpp-bare.o \ + ssh2censor.o ssh2connection.o ssh2connection-client.o \ + ssh2kex-client.o ssh2transhk.o ssh2transport.o \ + ssh2userauth.o sshaes.o ssharcf.o sshargon2.o sshauxcrypt.o \ + sshblake2.o sshblowf.o sshccp.o sshcommon.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshecc.o sshgssc.o \ + sshhmac.o sshmac.o sshmd5.o sshprng.o sshpubk.o sshrand.o \ + sshrsa.o sshsh256.o sshsh512.o sshsha.o sshsha3.o sshshare.o \ + sshutils.o sshverstring.o sshzlib.o stripctrl.o timing.o \ + tree234.o utils.o version.o wcwidth.o wildcard.o wincapi.o \ + wincliloop.o wincons.o windefs.o wingss.o winhandl.o \ + winhsock.o winmisc.o winmiscs.o winnet.o winnohlp.o \ + winnoise.o winnojmp.o winnpc.o winnps.o winpgntc.o \ + winproxy.o winsecur.o winselcli.o winsftp.o winshare.o \ + winstore.o wintime.o winucs.o x11fwd.o \ + kitty_commun.o kitty_ssh.o kitty_tools.o kitty_registry.o kitty_store.o + $(CC) $(LDFLAGS) -o $@ -Wl,-Map,pscp.map agentf.o aqsync.o be_misc.o \ + be_ssh.o callback.o clicons.o cmdline.o conf.o console.o \ + cproxy.o ecc.o errsock.o logging.o mainchan.o marshal.o \ + memory.o misc.o miscucs.o mpint.o nullplug.o pgssapi.o \ + pinger.o portfwd.o proxy.o pscp.o pscp.res.o psftpcommon.o \ + settings.o sftp.o sftpcommon.o ssh.o ssh1bpp.o ssh1censor.o \ + ssh1connection.o ssh1connection-client.o ssh1login.o \ + ssh2bpp.o ssh2bpp-bare.o ssh2censor.o ssh2connection.o \ + ssh2connection-client.o ssh2kex-client.o ssh2transhk.o \ + ssh2transport.o ssh2userauth.o sshaes.o ssharcf.o \ + sshargon2.o sshauxcrypt.o sshblake2.o sshblowf.o sshccp.o \ + sshcommon.o sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o \ + sshecc.o sshgssc.o sshhmac.o sshmac.o sshmd5.o sshprng.o \ + sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o \ + sshsha3.o sshshare.o sshutils.o sshverstring.o sshzlib.o \ + stripctrl.o timing.o tree234.o utils.o version.o wcwidth.o \ + wildcard.o wincapi.o wincliloop.o wincons.o windefs.o \ + wingss.o winhandl.o winhsock.o winmisc.o winmiscs.o winnet.o \ + winnohlp.o winnoise.o winnojmp.o winnpc.o winnps.o \ + winpgntc.o winproxy.o winsecur.o winselcli.o winsftp.o \ + winshare.o winstore.o wintime.o winucs.o x11fwd.o \ + kitty_commun.o kitty_ssh.o kitty_tools.o kitty_registry.o kitty_store.o \ + ../../base64/base64.a ../../bcrypt/bcrypt.a ../../mini/mini.a \ + -ladvapi32 -lcomdlg32 -lgdi32 -limm32 \ + -lole32 -lshell32 -luser32 \ + -lwsock32 + +psftp.exe: agentf.o aqsync.o be_misc.o be_ssh.o callback.o clicons.o \ + cmdline.o conf.o console.o cproxy.o ecc.o errsock.o \ + logging.o mainchan.o marshal.o memory.o misc.o miscucs.o \ + mpint.o nullplug.o pgssapi.o pinger.o portfwd.o proxy.o \ + psftp.o psftp.res.o psftpcommon.o settings.o sftp.o \ + sftpcommon.o ssh.o ssh1bpp.o ssh1censor.o ssh1connection.o \ + ssh1connection-client.o ssh1login.o ssh2bpp.o ssh2bpp-bare.o \ + ssh2censor.o ssh2connection.o ssh2connection-client.o \ + ssh2kex-client.o ssh2transhk.o ssh2transport.o \ + ssh2userauth.o sshaes.o ssharcf.o sshargon2.o sshauxcrypt.o \ + sshblake2.o sshblowf.o sshccp.o sshcommon.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshecc.o sshgssc.o \ + sshhmac.o sshmac.o sshmd5.o sshprng.o sshpubk.o sshrand.o \ + sshrsa.o sshsh256.o sshsh512.o sshsha.o sshsha3.o sshshare.o \ + sshutils.o sshverstring.o sshzlib.o stripctrl.o timing.o \ + tree234.o utils.o version.o wcwidth.o wildcard.o wincapi.o \ + wincliloop.o wincons.o windefs.o wingss.o winhandl.o \ + winhsock.o winmisc.o winmiscs.o winnet.o winnohlp.o \ + winnoise.o winnojmp.o winnpc.o winnps.o winpgntc.o \ + winproxy.o winsecur.o winselcli.o winsftp.o winshare.o \ + winstore.o wintime.o winucs.o x11fwd.o \ + kitty_commun.o kitty_ssh.o kitty_tools.o kitty_registry.o kitty_store.o + $(CC) $(LDFLAGS) -o $@ -Wl,-Map,psftp.map agentf.o aqsync.o \ + be_misc.o be_ssh.o callback.o clicons.o cmdline.o conf.o \ + console.o cproxy.o ecc.o errsock.o logging.o mainchan.o \ + marshal.o memory.o misc.o miscucs.o mpint.o nullplug.o \ + pgssapi.o pinger.o portfwd.o proxy.o psftp.o psftp.res.o \ + psftpcommon.o settings.o sftp.o sftpcommon.o ssh.o ssh1bpp.o \ + ssh1censor.o ssh1connection.o ssh1connection-client.o \ + ssh1login.o ssh2bpp.o ssh2bpp-bare.o ssh2censor.o \ + ssh2connection.o ssh2connection-client.o ssh2kex-client.o \ + ssh2transhk.o ssh2transport.o ssh2userauth.o sshaes.o \ + ssharcf.o sshargon2.o sshauxcrypt.o sshblake2.o sshblowf.o \ + sshccp.o sshcommon.o sshcrc.o sshcrcda.o sshdes.o sshdh.o \ + sshdss.o sshecc.o sshgssc.o sshhmac.o sshmac.o sshmd5.o \ + sshprng.o sshpubk.o sshrand.o sshrsa.o sshsh256.o sshsh512.o \ + sshsha.o sshsha3.o sshshare.o sshutils.o sshverstring.o \ + sshzlib.o stripctrl.o timing.o tree234.o utils.o version.o \ + wcwidth.o wildcard.o wincapi.o wincliloop.o wincons.o \ + windefs.o wingss.o winhandl.o winhsock.o winmisc.o \ + winmiscs.o winnet.o winnohlp.o winnoise.o winnojmp.o \ + winnpc.o winnps.o winpgntc.o winproxy.o winsecur.o \ + winselcli.o winsftp.o winshare.o winstore.o wintime.o \ + winucs.o x11fwd.o \ + -ladvapi32 -lcomdlg32 -lgdi32 -limm32 \ + -lole32 -lshell32 -luser32 \ + kitty_commun.o kitty_ssh.o kitty_tools.o kitty_registry.o kitty_store.o \ + ../../base64/base64.a ../../bcrypt/bcrypt.a ../../mini/mini.a \ + -lwsock32 + +psocks.exe: be_misc.o callback.o conf.o console.o errsock.o logging.o \ + marshal.o memory.o misc.o nocproxy.o norand.o portfwd.o \ + proxy.o psocks.o sshutils.o stripctrl.o time.o timing.o \ + tree234.o utils.o version.o wcwidth.o wincliloop.o wincons.o \ + winhandl.o winhsock.o winmisc.o winmiscs.o winnet.o \ + winnohlp.o winproxy.o winselcli.o winsocks.o + $(CC) $(LDFLAGS) -o $@ -Wl,-Map,psocks.map be_misc.o callback.o \ + conf.o console.o errsock.o logging.o marshal.o memory.o \ + misc.o nocproxy.o norand.o portfwd.o proxy.o psocks.o \ + sshutils.o stripctrl.o time.o timing.o tree234.o utils.o \ + version.o wcwidth.o wincliloop.o wincons.o winhandl.o \ + winhsock.o winmisc.o winmiscs.o winnet.o winnohlp.o \ + kitty_commun.o kitty_tools.o \ + winproxy.o winselcli.o winsocks.o -ladvapi32 -lcomdlg32 \ + -lgdi32 -limm32 -lole32 -lshell32 -luser32 \ + ../../mini/mini.a + +putty.exe: agentf.o aqsync.o be_all_s.o be_misc.o callback.o cmdline.o \ + conf.o config.o cproxy.o dialog.o ecc.o errsock.o ldisc.o \ + logging.o mainchan.o marshal.o memory.o minibidi.o misc.o \ + miscucs.o mpint.o nullplug.o pgssapi.o pinger.o portfwd.o \ + proxy.o putty.res.o raw.o rlogin.o sessprep.o settings.o \ + sizetip.o smallprimes.o ssh.o ssh1bpp.o ssh1censor.o ssh1connection.o \ + ssh1connection-client.o ssh1login.o ssh2bpp.o ssh2bpp-bare.o \ + ssh2censor.o ssh2connection.o ssh2connection-client.o \ + ssh2kex-client.o ssh2transhk.o ssh2transport.o \ + ssh2userauth.o sshaes.o ssharcf.o sshargon2.o sshauxcrypt.o \ + sshblake2.o sshblowf.o sshccp.o sshcommon.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshecc.o sshgssc.o \ + sshhmac.o sshmac.o sshmd5.o sshprng.o sshpubk.o sshrand.o \ + sshrsa.o sshsh256.o sshsh512.o sshsha.o sshsha3.o sshshare.o \ + sshutils.o sshverstring.o sshzlib.o stripctrl.o supdup.o \ + telnet.o terminal.o timing.o tree234.o utils.o version.o \ + wcwidth.o wildcard.o wincapi.o wincfg.o winctrls.o windefs.o \ + windlg.o window.o wingss.o winhandl.o winhelp.o winhsock.o \ + winjump.o winmisc.o winmiscs.o winnet.o winnoise.o winnpc.o \ + winnps.o winpgntc.o winprint.o winproxy.o winsecur.o \ + winselgui.o winser.o winshare.o winstore.o wintime.o \ + winucs.o winutils.o winx11.o x11fwd.o \ + adb.o \ + cencode.o cdecode.o \ + kitty.o kitty_commun.o kitty_crypt.o kitty_image.o kitty_proxy.o kitty_registry.o kitty_ssh.o \ + kitty_store.o kitty_tools.o kitty_win.o \ + urlhack.o pageant_integrated.o winpgnt_integrated.o winpgen_integrated.o winpzmodem.o \ + import.o sshrsag.o sshdssg.o sshprime.o sshecdsag.o sshbcrypt.o pockle.o primecandidate.o millerrabin.o mpunsafe.o \ + void.o $(UTF8MOUSE_OBJS) + $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,putty.map agentf.o \ + aqsync.o be_all_s.o be_misc.o callback.o cmdline.o conf.o \ + config.o cproxy.o dialog.o ecc.o errsock.o ldisc.o logging.o \ + mainchan.o marshal.o memory.o minibidi.o misc.o miscucs.o \ + mpint.o nullplug.o pgssapi.o pinger.o portfwd.o proxy.o \ + putty.res.o raw.o rlogin.o sessprep.o settings.o sizetip.o smallprimes.o \ + ssh.o ssh1bpp.o ssh1censor.o ssh1connection.o \ + ssh1connection-client.o ssh1login.o ssh2bpp.o ssh2bpp-bare.o \ + ssh2censor.o ssh2connection.o ssh2connection-client.o \ + ssh2kex-client.o ssh2transhk.o ssh2transport.o \ + ssh2userauth.o sshaes.o ssharcf.o sshargon2.o sshauxcrypt.o \ + sshblake2.o sshblowf.o sshccp.o sshcommon.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshecc.o sshgssc.o \ + sshhmac.o sshmac.o sshmd5.o sshprng.o sshpubk.o sshrand.o \ + sshrsa.o sshsh256.o sshsh512.o sshsha.o sshsha3.o sshshare.o \ + sshutils.o sshverstring.o sshzlib.o stripctrl.o supdup.o \ + telnet.o terminal.o timing.o tree234.o utils.o version.o \ + wcwidth.o wildcard.o wincapi.o wincfg.o winctrls.o windefs.o \ + windlg.o window.o wingss.o winhandl.o winhelp.o winhsock.o \ + winjump.o winmisc.o winmiscs.o winnet.o winnoise.o winnpc.o \ + winnps.o winpgntc.o winprint.o winproxy.o winsecur.o \ + winselgui.o winser.o winshare.o winstore.o wintime.o \ + winucs.o winutils.o winx11.o x11fwd.o \ + adb.o \ + cencode.o cdecode.o \ + kitty.o kitty_commun.o kitty_crypt.o kitty_image.o kitty_proxy.o kitty_registry.o kitty_ssh.o \ + kitty_store.o kitty_tools.o kitty_win.o \ + urlhack.o pageant_integrated.o winpgnt_integrated.o winpgen_integrated.o winpzmodem.o \ + import.o sshrsag.o sshdssg.o sshprime.o sshecdsag.o sshbcrypt.o pockle.o primecandidate.o millerrabin.o mpunsafe.o \ + void.o $(UTF8MOUSE_OBJS) \ + ../../base64/base64.a ../../bcrypt/bcrypt.a ../../blocnote/notepad.a ../../jpeg/libjpeg.a \ + ../../md5/MD5check.a ../../mini/mini.a ../../regex/libregex.a \ + -ladvapi32 -lcomdlg32 -lgdi32 -limm32 -lpsapi -lole32 -lshell32 \ + -luser32 -lwsock32 -lwtsapi32 \ + #-lcrypt32 \ + #/c/Windows/system32/crypt32.dll /c/windows/system32/ncrypt.dll \ + # -lcomctl32 -lwinmm -lwinspool -lole32 + +puttygen.exe: conf.o ecc.o import.o marshal.o memory.o millerrabin.o misc.o \ + mpint.o mpunsafe.o notiming.o pockle.o primecandidate.o \ + puttygen.res.o smallprimes.o sshaes.o sshargon2.o \ + sshauxcrypt.o sshbcrypt.o sshblake2.o sshblowf.o sshdes.o \ + sshdss.o sshdssg.o sshecc.o sshecdsag.o sshhmac.o sshmd5.o \ + sshprime.o sshprng.o sshpubk.o sshrand.o sshrsa.o sshrsag.o \ + sshsh256.o sshsh512.o sshsha.o sshsha3.o stripctrl.o \ + tree234.o utils.o version.o wcwidth.o winctrls.o winhelp.o \ + winmisc.o winmiscs.o winnoise.o winnojmp.o winpgen.o \ + winsecur.o winstore.o wintime.o winutils.o \ + kitty_commun.o kitty_crypt.o kitty_registry.o kitty_keygen.o kitty_store.o kitty_tools.o + $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,puttygen.map conf.o ecc.o \ + import.o marshal.o memory.o millerrabin.o misc.o mpint.o \ + mpunsafe.o notiming.o pockle.o primecandidate.o \ + puttygen.res.o smallprimes.o sshaes.o sshargon2.o \ + sshauxcrypt.o sshbcrypt.o sshblake2.o sshblowf.o sshdes.o \ + sshdss.o sshdssg.o sshecc.o sshecdsag.o sshhmac.o sshmd5.o \ + sshprime.o sshprng.o sshpubk.o sshrand.o sshrsa.o sshrsag.o \ + sshsh256.o sshsh512.o sshsha.o sshsha3.o stripctrl.o \ + tree234.o utils.o version.o wcwidth.o winctrls.o winhelp.o \ + winmisc.o winmiscs.o winnoise.o winnojmp.o winpgen.o \ + winsecur.o winstore.o wintime.o winutils.o \ + kitty_commun.o kitty_crypt.o kitty_registry.o kitty_keygen.o kitty_store.o kitty_tools.o \ + ../../bcrypt/bcrypt.a ../../mini/mini.a \ + -ladvapi32 \ + -lcomdlg32 -lgdi32 -limm32 -lole32 -lshell32 -luser32 + +puttytel.exe: be_misc.o be_nos_s.o callback.o cmdline.o conf.o config.o \ + dialog.o errsock.o ldisc.o logging.o marshal.o memory.o \ + minibidi.o misc.o miscucs.o nocproxy.o nogss.o norand.o \ + pinger.o proxy.o puttytel.res.o raw.o rlogin.o sessprep.o \ + settings.o sizetip.o stripctrl.o supdup.o telnet.o \ + terminal.o timing.o tree234.o utils.o version.o wcwidth.o \ + wincfg.o winctrls.o windefs.o windlg.o window.o winhandl.o \ + winhelp.o winhsock.o winjump.o winmisc.o winmiscs.o winnet.o \ + winprint.o winproxy.o winsecur.o winselgui.o winser.o \ + winstore.o wintime.o winucs.o winutils.o + $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,puttytel.map be_misc.o \ + be_nos_s.o callback.o cmdline.o conf.o config.o dialog.o \ + errsock.o ldisc.o logging.o marshal.o memory.o minibidi.o \ + misc.o miscucs.o nocproxy.o nogss.o norand.o pinger.o \ + proxy.o puttytel.res.o raw.o rlogin.o sessprep.o settings.o \ + sizetip.o stripctrl.o supdup.o telnet.o terminal.o timing.o \ + tree234.o utils.o version.o wcwidth.o wincfg.o winctrls.o \ + windefs.o windlg.o window.o winhandl.o winhelp.o winhsock.o \ + winjump.o winmisc.o winmiscs.o winnet.o winprint.o \ + winproxy.o winsecur.o winselgui.o winser.o winstore.o \ + wintime.o winucs.o winutils.o \ + -ladvapi32 -lcomdlg32 -lgdi32 \ + -limm32 -lole32 -lshell32 -luser32 + +testcrypt.exe: ecc.o marshal.o memory.o millerrabin.o mpint.o mpunsafe.o \ + pockle.o primecandidate.o smallprimes.o sshaes.o ssharcf.o \ + sshargon2.o sshauxcrypt.o sshblake2.o sshblowf.o sshccp.o \ + sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o sshdssg.o \ + sshecc.o sshecdsag.o sshhmac.o sshmd5.o sshprime.o sshprng.o \ + sshpubk.o sshrsa.o sshrsag.o sshsh256.o sshsh512.o sshsha.o \ + sshsha3.o testcrypt.o tree234.o utils.o winmiscs.o + $(CC) $(LDFLAGS) -o $@ -Wl,-Map,testcrypt.map ecc.o marshal.o \ + memory.o millerrabin.o mpint.o mpunsafe.o pockle.o \ + primecandidate.o smallprimes.o sshaes.o ssharcf.o \ + sshargon2.o sshauxcrypt.o sshblake2.o sshblowf.o sshccp.o \ + sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o sshdssg.o \ + sshecc.o sshecdsag.o sshhmac.o sshmd5.o sshprime.o sshprng.o \ + sshpubk.o sshrsa.o sshrsag.o sshsh256.o sshsh512.o sshsha.o \ + sshsha3.o testcrypt.o tree234.o utils.o winmiscs.o + +agentf.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../agentf.c + +aqsync.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../aqsync.c + +be_all_s.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_all_s.c + +be_misc.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_misc.c + +be_none.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_none.c + +be_nos_s.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_nos_s.c + +be_ssh.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../be_ssh.c + +callback.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../callback.c + +cgtest.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cgtest.c + +clicons.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../clicons.c + +cmdgen.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cmdgen.c + +cmdline.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cmdline.c -DMASTER_PASSWORD=`cat ../../masterpassword.txt` + +conf.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../conf.c + +config.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../config.c + +console.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../console.c + +cproxy.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../cproxy.c + +dialog.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../dialog.c + +ecc.o: ../ecc.c ../ssh.h ../mpint.h ../ecc.h ../puttymem.h ../tree234.h \ + ../network.h ../misc.h ../sshttymodes.h ../defs.h \ + ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ecc.c + +errsock.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../errsock.c + +fromucs.o: ../charset/fromucs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/fromucs.c + +fuzzterm.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../fuzzterm.c + +gtkapp.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkapp.c + +gtkask.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkask.c + +gtkcfg.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkcfg.c + +gtkcols.o: ../unix/gtkcols.c ../defs.h ../unix/gtkcompat.h ../unix/gtkcols.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkcols.c + +gtkcomm.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkcomm.c + +gtkdlg.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkdlg.c + +gtkfont.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkfont.c + +gtkmain.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkmain.c + +gtkmisc.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkmisc.c + +gtkwin.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/gtkwin.c + +import.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../import.c + +ldisc.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ldisc.c + +localenc.o: ../charset/localenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/localenc.c + +logging.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../logging.c + +macenc.o: ../charset/macenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/macenc.c + +mainchan.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../mainchan.c + +marshal.o: ../marshal.c ../marshal.h ../misc.h ../defs.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../marshal.c + +memory.o: ../memory.c ../defs.h ../puttymem.h ../misc.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../memory.c + +millerrabin.o: ../millerrabin.c ../ssh.h ../sshkeygen.h ../mpint.h \ + ../mpunsafe.h ../puttymem.h ../tree234.h ../network.h \ + ../misc.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../millerrabin.c + +mimeenc.o: ../charset/mimeenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/mimeenc.c + +minibidi.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../minibidi.c + +misc.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../misc.c + +miscucs.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../miscucs.c + +mpint.o: ../mpint.c ../defs.h ../misc.h ../puttymem.h ../mpint.h \ + ../mpint_i.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../mpint.c + +mpunsafe.o: ../mpunsafe.c ../defs.h ../misc.h ../puttymem.h ../mpint.h \ + ../mpint_i.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../mpunsafe.c + +nocmdline.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../nocmdline.c + +nocproxy.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../nocproxy.c + +nogss.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../nogss.c + +norand.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../norand.c + +noterm.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../noterm.c + +notiming.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../notiming.c + +nullplug.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../nullplug.c + +osxlaunch.o: ../unix/osxlaunch.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/osxlaunch.c + +pageant.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pageant.c + +pageant.res.o: ../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 + $(RC) $(RCFL) $(RCFLAGS) ../windows/pageant.rc -o pageant.res.o + +pgssapi.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pgssapi.c + +pinger.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pinger.c + +plink.res.o: ../windows/plink.rc ../windows/rcstuff.h ../windows/putty.ico \ + ../windows/version.rc2 ../version.h ../licence.h + $(RC) $(RCFL) $(RCFLAGS) ../windows/plink.rc -o plink.res.o + +pockle.o: ../pockle.c ../ssh.h ../sshkeygen.h ../mpint.h ../mpunsafe.h \ + ../tree234.h ../puttymem.h ../network.h ../misc.h \ + ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pockle.c + +portfwd.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../portfwd.c + +primecandidate.o: ../primecandidate.c ../ssh.h ../mpint.h ../mpunsafe.h \ + ../sshkeygen.h ../puttymem.h ../tree234.h ../network.h \ + ../misc.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../primecandidate.c + +procnet.o: ../unix/procnet.c ../misc.h ../defs.h ../puttymem.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/procnet.c + +proxy.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../proxy.c + +pscp.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../pscp.c + +pscp.res.o: ../windows/pscp.rc ../windows/rcstuff.h ../windows/pscp.ico \ + ../windows/version.rc2 ../version.h ../licence.h + $(RC) $(RCFL) $(RCFLAGS) ../windows/pscp.rc -o pscp.res.o + +psftp.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../psftp.c + +psftp.res.o: ../windows/psftp.rc ../windows/rcstuff.h ../windows/pscp.ico \ + ../windows/version.rc2 ../version.h ../licence.h + $(RC) $(RCFL) $(RCFLAGS) ../windows/psftp.rc -o psftp.res.o + +psftpcommon.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../psftpcommon.c + +psocks.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../psocks.c + +putty.res.o: ../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 \ + ../../blocnote/notepad.rc ../windows/kitty.mft + $(RC) $(RCFL) $(RCFLAGS) ../windows/putty.rc -o putty.res.o -DMOD_INTEGRATED_AGENT -DMOD_INTEGRATED_KEYGEN + +puttygen.res.o: ../windows/puttygen.rc ../windows/rcstuff.h \ + ../windows/winhelp.rc2 ../windows/puttygen.ico \ + ../windows/version.rc2 ../windows/puttygen.mft \ + ../windows/win_res.h ../version.h ../licence.h + $(RC) $(RCFL) $(RCFLAGS) ../windows/puttygen.rc -o puttygen.res.o -DMOD_INTEGRATED_KEYGEN + +puttytel.res.o: ../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 + $(RC) $(RCFL) $(RCFLAGS) ../windows/puttytel.rc -o puttytel.res.o + +raw.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../raw.c + +rlogin.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../rlogin.c + +sbcs.o: ../charset/sbcs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/sbcs.c + +sbcsdat.o: ../charset/sbcsdat.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/sbcsdat.c + +scpserver.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../scpserver.c + +sesschan.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sesschan.c + +sessprep.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sessprep.c + +settings.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../settings.c -DMASTER_PASSWORD=`cat ../../masterpassword.txt` + +sftp.o: ../sftp.c ../misc.h ../tree234.h ../sftp.h ../defs.h ../puttymem.h \ + ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sftp.c + +sftpcommon.o: ../sftpcommon.c ../misc.h ../sftp.h ../defs.h ../puttymem.h \ + ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sftpcommon.c + +sftpserver.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sftpserver.c + +sizetip.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/sizetip.c + +slookup.o: ../charset/slookup.c ../charset/charset.h ../charset/internal.h \ + ../charset/enum.c ../charset/sbcsdat.c ../charset/utf8.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/slookup.c + +smallprimes.o: ../smallprimes.c ../ssh.h ../sshkeygen.h ../puttymem.h \ + ../tree234.h ../network.h ../misc.h ../sshttymodes.h \ + ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../smallprimes.c + +ssh.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh.c + +ssh1bpp.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh1bpp.c + +ssh1censor.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh1censor.c + +ssh1connection.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh1connection.c + +ssh1connection-client.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh1connection-client.c + +ssh1connection-server.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh1connection-server.c + +ssh1login.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh1login.c + +ssh1login-server.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh1login-server.c + +ssh2bpp.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh2bpp.c + +ssh2bpp-bare.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh2bpp-bare.c + +ssh2censor.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh2censor.c + +ssh2connection.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh2connection.c + +ssh2connection-client.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh2connection-client.c + +ssh2connection-server.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh2connection-server.c + +ssh2kex-client.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh2kex-client.c + +ssh2kex-server.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh2kex-server.c + +ssh2transhk.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh2transhk.c + +ssh2transport.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh2transport.c + +ssh2userauth.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh2userauth.c + +ssh2userauth-server.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssh2userauth-server.c + +sshaes.o: ../sshaes.c ../ssh.h ../mpint_i.h ../puttymem.h ../tree234.h \ + ../network.h ../misc.h ../sshttymodes.h ../defs.h \ + ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshaes.c + +ssharcf.o: ../ssharcf.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../misc.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../ssharcf.c + +sshargon2.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshargon2.c + +sshauxcrypt.o: ../sshauxcrypt.c ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../misc.h ../sshttymodes.h ../defs.h \ + ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshauxcrypt.c + +sshbcrypt.o: ../sshbcrypt.c ../ssh.h ../sshblowf.h ../puttymem.h \ + ../tree234.h ../network.h ../misc.h ../sshttymodes.h \ + ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshbcrypt.c + +sshblake2.o: ../sshblake2.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../misc.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshblake2.c + +sshblowf.o: ../sshblowf.c ../ssh.h ../sshblowf.h ../puttymem.h ../tree234.h \ + ../network.h ../misc.h ../sshttymodes.h ../defs.h \ + ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshblowf.c + +sshccp.o: ../sshccp.c ../ssh.h ../mpint_i.h ../puttymem.h ../tree234.h \ + ../network.h ../misc.h ../sshttymodes.h ../defs.h \ + ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshccp.c + +sshcommon.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshcommon.c + +sshcrc.o: ../sshcrc.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../misc.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshcrc.c + +sshcrcda.o: ../sshcrcda.c ../misc.h ../ssh.h ../defs.h ../puttymem.h \ + ../marshal.h ../tree234.h ../network.h ../sshttymodes.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshcrcda.c + +sshdes.o: ../sshdes.c ../ssh.h ../mpint_i.h ../puttymem.h ../tree234.h \ + ../network.h ../misc.h ../sshttymodes.h ../defs.h \ + ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdes.c + +sshdh.o: ../sshdh.c ../ssh.h ../misc.h ../mpint.h ../puttymem.h ../tree234.h \ + ../network.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdh.c + +sshdss.o: ../sshdss.c ../ssh.h ../mpint.h ../misc.h ../puttymem.h \ + ../tree234.h ../network.h ../sshttymodes.h ../defs.h \ + ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdss.c + +sshdssg.o: ../sshdssg.c ../misc.h ../ssh.h ../sshkeygen.h ../mpint.h \ + ../defs.h ../puttymem.h ../marshal.h ../tree234.h \ + ../network.h ../sshttymodes.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshdssg.c + +sshecc.o: ../sshecc.c ../ssh.h ../mpint.h ../ecc.h ../puttymem.h \ + ../tree234.h ../network.h ../misc.h ../sshttymodes.h \ + ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshecc.c + +sshecdsag.o: ../sshecdsag.c ../ssh.h ../sshkeygen.h ../mpint.h ../puttymem.h \ + ../tree234.h ../network.h ../misc.h ../sshttymodes.h \ + ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshecdsag.c + +sshgssc.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshgssc.c + +sshhmac.o: ../sshhmac.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../misc.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshhmac.c + +sshmac.o: ../sshmac.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../misc.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshmac.c + +sshmd5.o: ../sshmd5.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../misc.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshmd5.c + +sshprime.o: ../sshprime.c ../ssh.h ../mpint.h ../mpunsafe.h ../sshkeygen.h \ + ../puttymem.h ../tree234.h ../network.h ../misc.h \ + ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshprime.c + +sshprng.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshprng.c + +sshpubk.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshpubk.c + +sshrand.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrand.c + +sshrsa.o: ../sshrsa.c ../ssh.h ../mpint.h ../misc.h ../puttymem.h \ + ../tree234.h ../network.h ../sshttymodes.h ../defs.h \ + ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrsa.c + +sshrsag.o: ../sshrsag.c ../ssh.h ../sshkeygen.h ../mpint.h ../puttymem.h \ + ../tree234.h ../network.h ../misc.h ../sshttymodes.h \ + ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshrsag.c + +sshserver.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshserver.c + +sshsh256.o: ../sshsh256.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../misc.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsh256.c + +sshsh512.o: ../sshsh512.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../misc.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsh512.c + +sshsha.o: ../sshsha.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../misc.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsha.c + +sshsha3.o: ../sshsha3.c ../ssh.h ../puttymem.h ../tree234.h ../network.h \ + ../misc.h ../sshttymodes.h ../defs.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshsha3.c + +sshshare.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshshare.c + +sshutils.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshutils.c + +sshverstring.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshverstring.c + +sshzlib.o: ../sshzlib.c ../defs.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../misc.h ../sshttymodes.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../sshzlib.c + +stripctrl.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../stripctrl.c + +supdup.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../supdup.c + +telnet.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../telnet.c + +terminal.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../terminal.c + +testcrypt.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../testcrypt.c + +testsc.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../testsc.c + +testzlib.o: ../testzlib.c ../defs.h ../ssh.h ../puttymem.h ../tree234.h \ + ../network.h ../misc.h ../sshttymodes.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../testzlib.c + +time.o: ../time.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../time.c + +timing.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../timing.c + +toucs.o: ../charset/toucs.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/toucs.c + +tree234.o: ../tree234.c ../defs.h ../tree234.h ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../tree234.c + +utf8.o: ../charset/utf8.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/utf8.c + +utils.o: ../utils.c ../defs.h ../misc.h ../ssh.h ../puttymem.h ../marshal.h \ + ../tree234.h ../network.h ../sshttymodes.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../utils.c + +ux_x11.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/ux_x11.c + +uxagentc.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxagentc.c + +uxagentsock.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxagentsock.c + +uxcfg.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxcfg.c + +uxcliloop.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxcliloop.c + +uxcons.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxcons.c + +uxfdsock.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxfdsock.c + +uxgen.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxgen.c + +uxgss.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxgss.c + +uxmisc.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxmisc.c + +uxnet.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxnet.c + +uxnogtk.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxnogtk.c + +uxnoise.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxnoise.c + +uxpeer.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpeer.c + +uxpgnt.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpgnt.c + +uxplink.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxplink.c + +uxpoll.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpoll.c + +uxprint.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxprint.c + +uxproxy.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxproxy.c + +uxpsusan.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpsusan.c + +uxpterm.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpterm.c + +uxpty.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxpty.c + +uxputty.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxputty.c + +uxsel.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsel.c + +uxser.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxser.c + +uxserver.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxserver.c + +uxsftp.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsftp.c + +uxsftpserver.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsftpserver.c + +uxshare.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxshare.c + +uxsignal.o: ../unix/uxsignal.c ../defs.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsignal.c + +uxsocks.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxsocks.c + +uxstore.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxstore.c + +uxucs.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxucs.c + +uxutils.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/uxutils.c + +version.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../version.c + +wcwidth.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../wcwidth.c + +wildcard.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../wildcard.c + +wincapi.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wincapi.c + +wincfg.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wincfg.c + +wincliloop.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wincliloop.c + +wincons.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wincons.c + +winctrls.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winctrls.c + +windefs.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/windefs.c + +windlg.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/windlg.c + +window.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/window.c \ + -DBUILD_TIME=`date +'"%d/%m/%Y-%H:%M:%S(%Z)"'` \ + -DBUILD_VERSION=`cat version_major.txt` \ + -DBUILD_SUBVERSION=`cat version_minor.txt` \ + -DMASTER_PASSWORD=`cat ../../masterpassword.txt` \ + -DMOD_INTEGRATED_AGENT -DMOD_INTEGRATED_KEYGEN + +wingss.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wingss.c + +winhandl.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winhandl.c + +winhelp.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winhelp.c + +winhsock.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winhsock.c + +winjump.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winjump.c + +winmisc.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winmisc.c + +winmiscs.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winmiscs.c + +winnet.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnet.c + +winnohlp.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnohlp.c + +winnoise.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnoise.c + +winnojmp.o: ../windows/winnojmp.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnojmp.c + +winnpc.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnpc.c + +winnps.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winnps.c + +winpgen.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgen.c + +winpgnt.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgnt.c + +winpgntc.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winpgntc.c + +winplink.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winplink.c -DMASTER_PASSWORD=`cat ../../masterpassword.txt` + +winprint.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winprint.c + +winproxy.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winproxy.c + +winsecur.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winsecur.c + +winselcli.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winselcli.c + +winselgui.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winselgui.c + +winser.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winser.c + +winsftp.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winsftp.c -DMASTER_PASSWORD=`cat ../../masterpassword.txt` + +winshare.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winshare.c + +winsocks.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winsocks.c + +winstore.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winstore.c + +wintime.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/wintime.c + +winucs.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winucs.c + +winutils.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winutils.c + +winx11.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../windows/winx11.c + +x11fwd.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../x11fwd.c + +x11misc.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/x11misc.c + +xenc.o: ../charset/xenc.c ../charset/charset.h ../charset/internal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../charset/xenc.c + +xkeysym.o: ../unix/xkeysym.c ../misc.h ../defs.h ../puttymem.h ../marshal.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xkeysym.c + +xpmptcfg.o: ../unix/xpmptcfg.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmptcfg.c + +xpmpterm.o: ../unix/xpmpterm.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmpterm.c + +xpmpucfg.o: ../unix/xpmpucfg.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmpucfg.c + +xpmputty.o: ../unix/xpmputty.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../unix/xpmputty.c + + +FORCE: + +################################################################################ +# BEGIN MOD_PERSO +################################################################################ +be_all_s_plink.o: ../be_all_s.c ../putty.h ../puttyps.h ../network.h ../misc.h \ + ../windows/winstuff.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS_PLINK) $(XFLAGS) -o be_all_s_plink.o -c ../be_all_s.c + +ldisc_plink.o: ../ldisc.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \ + ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \ + ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS_PLINK) $(XFLAGS) -o ldisc_plink.o -c ../ldisc.c + +pageant_integrated.o: ../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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -o pageant_integrated.o -c ../pageant.c -DMOD_INTEGRATED_AGENT + +settings_nohyperlink.o: ../settings.c ../putty.h ../storage.h ../puttyps.h ../network.h \ + ../misc.h ../windows/winstuff.h \ + ../unix/unix.h ../puttymem.h ../tree234.h \ + ../windows/winhelp.h ../charset/charset.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -o settings_nohyperlink.o -c ../settings.c -DMOD_NOHYPERLINK \ + -DMASTER_PASSWORD=`cat ../../masterpassword.txt` + +winpgen_integrated.o: ../windows/winpgen.c ../putty.h ../ssh.h ../licence.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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -o winpgen_integrated.o -c ../windows/winpgen.c -DMOD_INTEGRATED_KEYGEN + +winpgnt_integrated.o: ../windows/winpgnt.c ../putty.h ../ssh.h ../misc.h ../tree234.h \ + ../windows/winsecur.h ../pageant.h ../licence.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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -o winpgnt_integrated.o -c ../windows/winpgnt.c -DMASTER_PASSWORD=`cat ../../masterpassword.txt` -DMOD_INTEGRATED_AGENT + +adb.o: ../putty.h ../../adb/adb.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../../adb/adb.c -I../../adb -I../../ + +cencode.o: ../../far2l/cencode.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../../far2l/cencode.c -I../../far2l -I../../ + +cdecode.o: ../../far2l/cdecode.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../../far2l/cdecode.c -I../../far2l -I../../ + +kitty.o: ../../kitty.c ../../kitty.h ../../kitty_savedump.c ../../kitty_launcher.c ../../kitty_launcher.h ../windows/window.c ../../kitty_ini.h ../../kitty_help.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty.c \ + -DBUILD_TIME=`date +'"%d/%m/%Y-%H:%M:%S(%Z)"'` \ + -DBUILD_VERSION=`cat version_major.txt` \ + -DBUILD_SUBVERSION=`cat version_minor.txt` \ + -DMASTER_PASSWORD=`cat ../../masterpassword.txt` + +kitty_portable.o: ../../kitty.c ../../kitty.h ../../kitty_savedump.c ../../kitty_launcher.c ../../kitty_launcher.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -o kitty_portable.o -c ../../kitty.c \ + -DMOD_PORTABLE \ + -DBUILD_TIME=`date +'"%d/%m/%Y-%H:%M:%S(%Z)"'` \ + -DBUILD_VERSION=`cat version_major.txt` \ + -DBUILD_SUBVERSION=`cat version_minor.txt` \ + -DMASTER_PASSWORD=`cat ../../masterpassword.txt` + +kitty_nohyperlink.o: ../../kitty.c ../../kitty.h ../../kitty_savedump.c ../../kitty_launcher.c ../../kitty_launcher.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -o kitty_nohyperlink.o -c ../../kitty.c \ + -DMOD_NOHYPERLINK \ + -DBUILD_TIME=`date +'"%d/%m/%Y-%H:%M:%S(%Z)"'` \ + -DBUILD_VERSION=`cat version_major.txt` \ + -DBUILD_SUBVERSION=`cat version_minor.txt` \ + -DMASTER_PASSWORD=`cat ../../masterpassword.txt` + +kitty_commun.o: ../../kitty_commun.c ../../kitty_commun.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty_commun.c + +kitty_commun_portable.o: ../../kitty_commun.c ../../kitty_commun.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -DMOD_PORTABLE -o kitty_commun_portable.o -c ../../kitty_commun.c + +kitty_crypt.o: ../../kitty_crypt.c ../../kitty_crypt.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -DMASTER_PASSWORD=`cat ../../masterpassword.txt` -c ../../kitty_crypt.c + +kitty_image.o: ../../kitty_image.c + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty_image.c + +kitty_iv.o: ../../kitty_iv.c ../../kitty_iv.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty_iv.c + +kitty_keygen.o: ../../kitty_keygen.c + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty_keygen.c + +kitty_launcher.o: ../../kitty_launcher.c + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty_launcher.c + +kitty_proxy.o: ../../kitty_proxy.c ../../kitty_proxy.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty_proxy.c + +kitty_regex.o: ../../kitty_regex.c ../../kitty_regex.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty_regex.c + +kitty_registry.o: ../../kitty_registry.c ../../kitty_registry.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty_registry.c + +kitty_ssh.o: ../../kitty_ssh.c ../../kitty_ssh.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty_ssh.c + +kitty_store.o: ../../kitty_store.c ../../kitty_store.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty_store.c + +kitty_tools.o: ../../kitty_tools.c ../../kitty_tools.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty_tools.c + +kitty_win.o: ../../kitty_win.c ../../kitty_win.h + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../kitty_win.c + +urlhack.o: ../../url/urlhack.c ../../url/urlhack.h \ + ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -c ../../url/urlhack.c -DEXTERN_REGEXLIB + +urlhack_nohyperlink.o: ../../url/urlhack.c ../../url/urlhack.h \ + ../puttymem.h + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -o urlhack_nohyperlink.o -c ../../url/urlhack.c \ + -DMOD_NOHYPERLINK + +void.o: ../../void.c + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../void.c + +window_notrans.o: ../windows/window.c ../putty.h ../terminal.h ../storage.h \ + ../windows/win_res.h ../windows/winsecur.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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -o window_notrans.o -c ../windows/window.c \ + -DMOD_NOTRANSPARENCY \ + -DBUILD_TIME=`date +'"%d/%m/%Y-%H:%M:%S(%Z)"'` \ + -DBUILD_VERSION=`cat version_major.txt` \ + -DBUILD_SUBVERSION=`cat version_minor.txt` \ + -DMASTER_PASSWORD=`cat ../../masterpassword.txt` \ + -DMOD_INTEGRATED_AGENT -DMOD_INTEGRATED_KEYGEN + +window_nohyperlink.o: ../windows/window.c ../putty.h ../terminal.h ../storage.h \ + ../windows/win_res.h ../puttyps.h ../network.h ../misc.h \ + ../tree234.h ../windows/winstuff.h ../macosx/osx.h \ + ../macosx/osx.h ../unix/unix.h ../puttymem.h \ + ../windows/winhelp.h ../charset/charset.h \ + ../../kitty.c ../../kitty_launcher.c ../../kitty_image.c ../../kitty_crypt.c ../../kitty_savedump.c + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -o window_nohyperlink.o -c ../windows/window.c \ + -DMOD_NOHYPERLINK \ + -DBUILD_TIME=`date +'"%d/%m/%Y-%H:%M:%S(%Z)"'` \ + -DBUILD_VERSION=`cat version_major.txt` \ + -DBUILD_SUBVERSION=`cat version_minor.txt` \ + -DMASTER_PASSWORD=`cat ../../masterpassword.txt` + +window_portable.o: ../windows/window.c ../putty.h ../terminal.h ../storage.h \ + ../windows/win_res.h ../windows/winsecur.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 + $(CC) $(COMPAT) $(CFLAGS) $(XFLAGS) -o window_portable.o -c ../windows/window.c \ + -DBUILD_TIME=`date +'"%d/%m/%Y-%H:%M:%S(%Z)"'` \ + -DMOD_PORTABLE \ + -DBUILD_VERSION=`cat version_major.txt` \ + -DBUILD_SUBVERSION=`cat version_minor.txt` \ + -DMASTER_PASSWORD=`cat ../../masterpassword.txt` \ + -DMOD_INTEGRATED_AGENT -DMOD_INTEGRATED_KEYGEN + +winpzmodem.o: ../../zmodem/winpzmodem.c + $(CC) $(COMPAT) $(XFLAGS) $(CFLAGS) -c ../../zmodem/winpzmodem.c + +kitty_dll.res.o: ../../kitty_dll.rc + windres ../../kitty_dll.rc kitty_dll.res.o + +kitty.dll: ../../kitty_dll.c kitty_dll.res.o + $(CPP) -shared -o kitty.dll kitty_dll.res.o ../../kitty_dll.c \ + -Wl,--whole-archive ../../base64/base64.a ../../regex/libregex.a ../../bcrypt/bcrypt.a ../../jpeg/libjpeg.a ../../md5/MD5check.a ../../blocnote/notepad.a ../../mini/mini.a -Wl,--no-whole-archive \ + -DBUILD_DLL -static-libgcc -lcomdlg32 -lgdi32 + upx --best --compress-icons=2 kitty.dll + +../../kitty_ini.h: ../../kitty_ini.txt + ( printf 'char default_init_file_content[] = "' ; sed 's/\\/\\\\/g' ../../kitty_ini.txt | sed 's/$$/\\n\\/' ; printf '" ;\n' ) > ../../kitty_ini.h + +../../kitty_help.h: ../../docs/pages/CommandLine.md + ( printf 'static char *default_help_file_content = "' ; sed -n '/CmdLineOptions_begin/,/CmdLineOptions_end/p' ../../docs/pages/CommandLine.md|sed 's///'|sed 's/\*\*//g'|sed 's/$$/\\r\\n\\/' ; printf '" ;\n' ) > ../../kitty_help.h + +nohyperlink: putty_nohyperlink.exe + +notrans: putty_notrans.exe + +portable: putty_portable.exe + +zmodem: putty_zmodem.exe + +kitty: putty.exe + mv putty.exe kitty.exe + +cleanall: clean FORCE + rm -f *.o *.exe *.res.o *.map *.dmp *.bak ../*.bak ../../*.bak kitty.log ../*.o kitty.dll + +version: + expr `cat version_minor.txt|tr -d '\r\n '` + 1 > version_minor.txt + cat version_major.txt version_minor.txt|tr -d '\r\n '|sed 's/^.//'|sed 's/"/./' > version.txt + v=`cat version_major.txt|tr -d '"\r\n '|sed 's/\.[0-9]*$$//'` ; printf '#define RELEASE '$${v}'\n#define TEXTVER "Release '$${v}'"\n#define SSHVER "-Release-'$${v}'"\n#define BINARY_VERSION '`cat version.txt|sed 's/\./,/g'`'\n#define SOURCE_COMMIT "unavailable"\n' > ../version.h + cat version.txt + +xml: + cat ../../kitty.xml | sed 's/.*<\/Program_Version>/'`cat version.txt`'<\/Program_Version>/' | sed 's/.*<\/Program_Release_Month>/'`date +"%m"`'<\/Program_Release_Month>/' | sed 's/.*<\/Program_Release_Day>/'`date +"%d"`'<\/Program_Release_Day>/' | sed 's/.*<\/Program_Release_Year>/'`date +"%Y"`'<\/Program_Release_Year>/' | sed 's/.*<\/File_Size_Bytes>/'`ls -l putty.exe|awk '{print $5}'`'<\/File_Size_Bytes>/' > kitty.xml + +putty_notrans.exe: agentf.o aqsync.o be_all_s.o be_misc.o callback.o cmdline.o \ + conf.o config.o cproxy.o dialog.o ecc.o errsock.o ldisc.o \ + ldiscucs.o logging.o mainchan.o marshal.o memory.o \ + minibidi.o misc.o miscucs.o mpint.o nullplug.o pgssapi.o \ + pinger.o portfwd.o proxy.o putty.res.o raw.o rlogin.o \ + sercfg.o sessprep.o settings.o sizetip.o ssh.o ssh1bpp.o \ + ssh1censor.o ssh1connection.o ssh1connection-client.o \ + ssh1login.o ssh2bpp.o ssh2bpp-bare.o ssh2censor.o \ + ssh2connection.o ssh2connection-client.o ssh2kex-client.o \ + ssh2transhk.o ssh2transport.o ssh2userauth.o sshaes.o \ + ssharcf.o sshauxcrypt.o sshblowf.o sshccp.o sshcommon.o \ + sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o sshecc.o \ + sshgssc.o sshhmac.o sshmac.o sshmd5.o sshprng.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshshare.o \ + sshverstring.o sshzlib.o stripctrl.o telnet.o terminal.o \ + timing.o tree234.o utils.o version.o wcwidth.o wildcard.o \ + wincapi.o wincfg.o winctrls.o windefs.o windlg.o \ + window_notrans.o \ + wingss.o winhandl.o winhelp.o winhsock.o winjump.o winmisc.o \ + winmiscs.o winnet.o winnoise.o winnpc.o winnps.o winpgntc.o \ + winprint.o winproxy.o winsecur.o winser.o winshare.o \ + winstore.o wintime.o winucs.o winutils.o winx11.o x11fwd.o \ + kitty_portable.o kitty_commun_portable.o kitty_crypt.o kitty_registry.o kitty_ssh.o kitty_tools.o kitty_win.o \ + urlhack.o pageant_integrated.o winpgnt_integrated.o winpgen_integrated.o \ + import.o sshrsag.o sshdssg.o sshprime.o sshecdsag.o sshbcrypt.o \ + void.o $(UTF8MOUSE_OBJS) + $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,putty.map agentf.o \ + aqsync.o be_all_s.o be_misc.o callback.o cmdline.o conf.o \ + config.o cproxy.o dialog.o ecc.o errsock.o ldisc.o \ + ldiscucs.o logging.o mainchan.o marshal.o memory.o \ + minibidi.o misc.o miscucs.o mpint.o nullplug.o pgssapi.o \ + pinger.o portfwd.o proxy.o putty.res.o raw.o rlogin.o \ + sercfg.o sessprep.o settings.o sizetip.o ssh.o ssh1bpp.o \ + ssh1censor.o ssh1connection.o ssh1connection-client.o \ + ssh1login.o ssh2bpp.o ssh2bpp-bare.o ssh2censor.o \ + ssh2connection.o ssh2connection-client.o ssh2kex-client.o \ + ssh2transhk.o ssh2transport.o ssh2userauth.o sshaes.o \ + ssharcf.o sshauxcrypt.o sshblowf.o sshccp.o sshcommon.o \ + sshcrc.o sshcrcda.o sshdes.o sshdh.o sshdss.o sshecc.o \ + sshgssc.o sshhmac.o sshmac.o sshmd5.o sshprng.o sshpubk.o \ + sshrand.o sshrsa.o sshsh256.o sshsh512.o sshsha.o sshshare.o \ + sshverstring.o sshzlib.o stripctrl.o telnet.o terminal.o \ + timing.o tree234.o utils.o version.o wcwidth.o wildcard.o \ + wincapi.o wincfg.o winctrls.o windefs.o windlg.o \ + window_notrans.o \ + wingss.o winhandl.o winhelp.o winhsock.o winjump.o winmisc.o \ + winmiscs.o winnet.o winnoise.o winnpc.o winnps.o winpgntc.o \ + winprint.o winproxy.o winsecur.o winser.o winshare.o \ + winstore.o wintime.o winucs.o winutils.o winx11.o x11fwd.o \ + kitty_portable.o kitty_commun_portable.o kitty_crypt.o kitty_registry.o kitty_ssh.o kitty_tools.o kitty_win.o \ + urlhack.o pageant_integrated.o winpgnt_integrated.o winpgen_integrated.o \ + import.o sshrsag.o sshdssg.o sshprime.o sshecdsag.o sshbcrypt.o \ + void.o $(UTF8MOUSE_OBJS) \ + ../../base64/base64.a ../../bcrypt/bcrypt.a ../../blocnote/notepad.a ../../md5/MD5check.a ../../mini/mini.a \ + ../../regex/libregex.a \ + -ladvapi32 -lcomdlg32 -lgdi32 -limm32 -lole32 -lshell32 \ + -luser32 \ + -lwsock32 -lpsapi + +putty_portable.exe: agentf.o aqsync.o be_all_s.o be_misc.o callback.o cmdline.o \ + conf.o config.o cproxy.o dialog.o ecc.o errsock.o ldisc.o \ + logging.o mainchan.o marshal.o memory.o minibidi.o misc.o \ + miscucs.o mpint.o nullplug.o pgssapi.o pinger.o portfwd.o \ + proxy.o putty.res.o raw.o rlogin.o sessprep.o settings.o \ + sizetip.o smallprimes.o ssh.o ssh1bpp.o ssh1censor.o ssh1connection.o \ + ssh1connection-client.o ssh1login.o ssh2bpp.o ssh2bpp-bare.o \ + ssh2censor.o ssh2connection.o ssh2connection-client.o \ + ssh2kex-client.o ssh2transhk.o ssh2transport.o \ + ssh2userauth.o sshaes.o ssharcf.o sshargon2.o sshauxcrypt.o \ + sshblake2.o sshblowf.o sshccp.o sshcommon.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshecc.o sshgssc.o \ + sshhmac.o sshmac.o sshmd5.o sshprng.o sshpubk.o sshrand.o \ + sshrsa.o sshsh256.o sshsh512.o sshsha.o sshsha3.o sshshare.o \ + sshutils.o sshverstring.o sshzlib.o stripctrl.o supdup.o \ + telnet.o terminal.o timing.o tree234.o utils.o version.o \ + wcwidth.o wildcard.o wincapi.o wincfg.o winctrls.o windefs.o \ + windlg.o \ + window_portable.o \ + wingss.o winhandl.o winhelp.o winhsock.o \ + winjump.o winmisc.o winmiscs.o winnet.o winnoise.o winnpc.o \ + winnps.o winpgntc.o winprint.o winproxy.o winsecur.o \ + winselgui.o winser.o winshare.o winstore.o wintime.o \ + winucs.o winutils.o winx11.o x11fwd.o \ + adb.o \ + kitty_portable.o kitty_commun_portable.o kitty_crypt.o kitty_image.o kitty_proxy.o kitty_registry.o kitty_ssh.o \ + kitty_store.o kitty_tools.o kitty_win.o \ + urlhack.o pageant_integrated.o winpgnt_integrated.o winpgen_integrated.o winpzmodem.o \ + import.o sshrsag.o sshdssg.o sshprime.o sshecdsag.o sshbcrypt.o pockle.o primecandidate.o millerrabin.o mpunsafe.o \ + void.o $(UTF8MOUSE_OBJS) + $(CC) -mwindows $(LDFLAGS) -o $@ -Wl,-Map,putty.map agentf.o \ + aqsync.o be_all_s.o be_misc.o callback.o cmdline.o conf.o \ + config.o cproxy.o dialog.o ecc.o errsock.o ldisc.o logging.o \ + mainchan.o marshal.o memory.o minibidi.o misc.o miscucs.o \ + mpint.o nullplug.o pgssapi.o pinger.o portfwd.o proxy.o \ + putty.res.o raw.o rlogin.o sessprep.o settings.o sizetip.o smallprimes.o \ + ssh.o ssh1bpp.o ssh1censor.o ssh1connection.o \ + ssh1connection-client.o ssh1login.o ssh2bpp.o ssh2bpp-bare.o \ + ssh2censor.o ssh2connection.o ssh2connection-client.o \ + ssh2kex-client.o ssh2transhk.o ssh2transport.o \ + ssh2userauth.o sshaes.o ssharcf.o sshargon2.o sshauxcrypt.o \ + sshblake2.o sshblowf.o sshccp.o sshcommon.o sshcrc.o \ + sshcrcda.o sshdes.o sshdh.o sshdss.o sshecc.o sshgssc.o \ + sshhmac.o sshmac.o sshmd5.o sshprng.o sshpubk.o sshrand.o \ + sshrsa.o sshsh256.o sshsh512.o sshsha.o sshsha3.o sshshare.o \ + sshutils.o sshverstring.o sshzlib.o stripctrl.o supdup.o \ + telnet.o terminal.o timing.o tree234.o utils.o version.o \ + wcwidth.o wildcard.o wincapi.o wincfg.o winctrls.o windefs.o \ + windlg.o \ + window_portable.o \ + wingss.o winhandl.o winhelp.o winhsock.o \ + winjump.o winmisc.o winmiscs.o winnet.o winnoise.o winnpc.o \ + winnps.o winpgntc.o winprint.o winproxy.o winsecur.o \ + winselgui.o winser.o winshare.o winstore.o wintime.o \ + winucs.o winutils.o winx11.o x11fwd.o \ + adb.o \ + kitty_portable.o kitty_commun_portable.o kitty_crypt.o kitty_image.o kitty_proxy.o kitty_registry.o kitty_ssh.o \ + kitty_store.o kitty_tools.o kitty_win.o \ + urlhack.o pageant_integrated.o winpgnt_integrated.o winpgen_integrated.o winpzmodem.o \ + import.o sshrsag.o sshdssg.o sshprime.o sshecdsag.o sshbcrypt.o pockle.o primecandidate.o millerrabin.o mpunsafe.o \ + void.o $(UTF8MOUSE_OBJS) \ + ../../base64/base64.a ../../bcrypt/bcrypt.a ../../blocnote/notepad.a ../../jpeg/libjpeg.a \ + ../../md5/MD5check.a ../../mini/mini.a ../../regex/libregex.a \ + -ladvapi32 -lcomdlg32 -lgdi32 -limm32 -lpsapi -lole32 -lshell32 \ + -luser32 -lwsock32 -lwtsapi32 + # -lcomctl32 -lwinmm -lwinspool -lole32 -lcrypt32 + +.PHONY: clean 9bis + +clean: + rm -f *.o *.exe *.res.o *.so *.map + +cross: + sed -i 's#^typedef enum _WTS_VIRTUAL_CLASS #// typedef enum _WTS_VIRTUAL_CLASS #' window.c + sed -i 's/0/999/' version_major.txt + make -e TOOLPATH=/usr/bin/i686-w64-mingw32- -f MAKEFILE.MINGW putty.exe plink.exe pscp.exe psftp.exe pageant.exe puttygen.exe + make -e TOOLPATH=/usr/bin/i686-w64-mingw32- -f MAKEFILE.MINGW portable + cp putty.exe putty_nocompress.exe + upx --best --compress-icons=2 --compress-exports=0 putty.exe putty_portable.exe + mv putty.exe /builds/kitty.exe + mv putty_portable.exe /builds/kitty_portable.exe + mv putty_nocompress.exe /builds/kitty_nocompress.exe + mv plink.exe /builds/klink.exe + mv pscp.exe /builds/kscp.exe + mv psftp.exe /builds/ksftp.exe + mv pageant.exe /builds/kageant.exe + mv puttygen.exe /builds/kittygen.exe + rm *.o + +cross64: + sed -i 's#^typedef enum _WTS_VIRTUAL_CLASS #// typedef enum _WTS_VIRTUAL_CLASS #' window.c + sed -i 's/0/999/' version_major.txt + make -e TOOLPATH=/usr/bin/x86_64-w64-mingw32- -f MAKEFILE.MINGW putty.exe || /usr/bin/x86_64-w64-mingw32-gcc -o kitty64.exe *.o ../../base64/base64_64.a ../../bcrypt/bcrypt_64.a ../../blocnote/notepad_64.a ../../jpeg/libjpeg_64.a ../../md5/MD5check_64.a ../../mini/mini_64.a ../../regex/libregex_64.a -ladvapi32 -lcomdlg32 -lgdi32 -limm32 -lpsapi -lole32 -lshell32 -luser32 -lwsock32 -lwtsapi32 -mwindows + cp kitty64.exe /builds/kitty64_nocompress.exe + cp ../../mini/mini_64.a ../../mini/mini.a + cp ../../bcrypt/bcrypt_64.a ../../bcrypt/bcrypt.a + cp ../../base64/base64_64.a ../../base64/base64.a + make -e TOOLPATH=/usr/bin/x86_64-w64-mingw32- -f MAKEFILE.MINGW plink.exe pscp.exe psftp.exe pageant.exe puttygen.exe + upx --best --compress-icons=2 --compress-exports=0 kitty64.exe + mv kitty64.exe /builds/kitty64.exe + mv plink.exe /builds/klink64.exe + mv pscp.exe /builds/kscp64.exe + mv psftp.exe /builds/ksftp64.exe + mv pageant.exe /builds/kageant64.exe + mv puttygen.exe /builds/kittygen64.exe + rm *.o + +9bis: putty.exe putty_portable.exe plink.exe pscp.exe psftp.exe pageant.exe puttygen.exe + +# AJOUTER +# -DMASTER_PASSWORD=`cat ../../masterpassword.txt` à la compilation de cmdline.o +# -DMASTER_PASSWORD=`cat ../../masterpassword.txt` à la compilation de settings.o +# -DMASTER_PASSWORD=`cat ../../masterpassword.txt` à la compilation de winplink.o +# -DMASTER_PASSWORD=`cat ../../masterpassword.txt` à la compilation de winsftp.o +# -DBUILD_TIME=`date +'"%d/%m/%Y-%H:%M:%S(%Z)"'` \ à la compilation de window.o +# -DBUILD_VERSION=`cat version_major.txt` \ +# -DBUILD_SUBVERSION=`cat version_minor.txt` \ +# -DMASTER_PASSWORD=`cat ../../masterpassword.txt` \ +# -DMOD_INTEGRATED_AGENT à la compilation de putty.res.o +# -DMOD_INTEGRATED_AGENT à la compilation de window.o +# Ajouter winpgnt_integrated.o à la compilation de putty.exe +# Ajouter pageant_integrated.o à la compilation de putty.exe +# Ajouter ../../blocnote/notepad.rc à la compilation de putty.res.o +# Ajouter -DMOD_INTEGRATED_KEYGEN à la compiltation de putty.res.o +# Ajouter -DMOD_INTEGRATED_KEYGEN à la compiltation de window.o +# Ajouter winpgen_integrated.o à la compiltation de putty.exe +# Ajouter import.o sshrsag.o sshdssg.o sshprime.o sshecdsag.o sshbcrypt.o à la compilation de putty.exe + +# Pour putty_notrans.exe copier putty.exe et remplacer window.o par window_notrans.o +# Pour putty_portable.exe copier putty.exe et remplacer window.o, kitty.o, kitty_commun.o par window_portable.o, kitty_portable.o, kitty_commun_portable.o + +################################################################################ +# END MOD_PERSO +################################################################################ + diff --git a/0.77_My_PuTTY/windows/kitty.mft b/0.77_My_PuTTY/windows/kitty.mft new file mode 100644 index 0000000..0a19b87 --- /dev/null +++ b/0.77_My_PuTTY/windows/kitty.mft @@ -0,0 +1,45 @@ + + + + + A network client and terminal emulator + + + + + + + + + + + + + + + + + + + + + + + + true + + + diff --git a/0.77_My_PuTTY/windows/pageant-rc.h b/0.77_My_PuTTY/windows/pageant-rc.h new file mode 100644 index 0000000..107d261 --- /dev/null +++ b/0.77_My_PuTTY/windows/pageant-rc.h @@ -0,0 +1,38 @@ +/* + * Constant definitions for the Pageant resource file. + */ + +#ifndef MOD_INTEGRATED_AGENT +#define IDI_MAINICON 200 +#define IDI_TRAYICON 201 +#else +#define IDI_MAINICON_AGENT 900 +#define IDI_TRAYICON 901 +#endif + +#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/0.77_My_PuTTY/windows/pageant.ico b/0.77_My_PuTTY/windows/pageant.ico new file mode 100644 index 0000000000000000000000000000000000000000..93274fd92449f2d0ba88784627db7bf6ded89aea GIT binary patch literal 4078 zcmZQzU}Rup5D*YxU}Run&|qX>NMm4NP*8yIUobH+^f59p7#KkK8Egy;Wh@K~0s>47 zj0}tn8yFZE?s0+CL-+=a3=DnTAoWl_GXuju9tH*t1_lNJkhu&X`&bx2paDc+#SIJ$ zAjyUX2*uFA(9poZaNxiJh6WIW;XeZiG5r4zp&0&ym<<2_|3{d{-qF#~(ZImK|NnnS zM+E}|1Bm~Ffq|jCyd1=3_;cnAUK-gjh_M_Hs-ql2bAX%(VIs%}>C!jFfuSa z0AXeZ29OvWgTxpa7(TEwFf_^w1`z)D9}-Rq3=9koNa4i5gfJhKMTKxeH4qOE z9H5{;1cfRC0|S3K2sAJ-w0D4YLHHmshywoqFi-&rr~F z{yW4t1_p*NXF%XM{rD~*;0|>S11LSABm(wwXv{-BLKM{j^EOy@NB#c~UtA1H@0FxdZZVEF%of%*RdDE|L|`Tze9Q2c}W|NkG!|NsAi+4}!Ks5~%WU|>i< zO6LubVgZYX>6y;4SdU+b9h#0IsTq>^K|0XH;mMf)J1CQsLlP5v1zZDM94uYY0n>oU zNTA^R4ldO|=A(-LM;KI&oaE|%z%5`eKyoSjf4De90nA0^=86bABYcb z>DV(cfHNhCKES{L7JtCN0MhsW0mNLY(#IJXK;|*9BhjGhOr8N$_=DSI|Nk&Bfc)|Q zAH;wE4}dBMi2LpTGcbUh2czqud=L!^BM9BV0HF_{(GQ?}82!Njr04(t5A_iG2RK!t zhcl?2L9{y=!Kn$Nn<3J2d3|7y;zz?OtMGB-j2T}`a7J_Jd5CQGIL75a#pkRdL dR}c%FhQZMSD%VEKLrD1uDgPLt + + + + PuTTY SSH authentication agent + + + + + + + + + + true + + + diff --git a/0.77_My_PuTTY/windows/pageant.rc b/0.77_My_PuTTY/windows/pageant.rc new file mode 100644 index 0000000..162702c --- /dev/null +++ b/0.77_My_PuTTY/windows/pageant.rc @@ -0,0 +1,119 @@ +/* + * Windows resources for Pageant. + */ + +#include "pageant-rc.h" + +#include "rcstuff.h" + +#ifdef MOD_INTEGRATED_AGENT +#define APPNAME_AGENT "Kageant" +#define APPDESC_AGENT "KiTTY SSH authentication agent" +#else +#define APPNAME "Pageant" +#define APPDESC "PuTTY SSH authentication agent" +#endif + +#include "winhelp.rc2" + +#ifdef MOD_INTEGRATED_AGENT +900 ICON "pageant.ico" +901 ICON "pageants.ico" +#else +IDI_MAINICON ICON "pageant.ico" +IDI_TRAYICON ICON "pageants.ico" +#endif + +IDD_LOAD_PASSPHRASE DIALOG DISCARDABLE 0, 0, 140, 60 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +#ifdef MOD_PERSO +CAPTION "Kageant: Loading Encrypted Key" +#else +CAPTION "Pageant: Loading Encrypted Key" +#endif +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 +#ifdef MOD_PERSO +CAPTION "Kageant Key List" +#else +CAPTION "Pageant Key List" +#endif +FONT 8, "MS Shell Dlg" +BEGIN + LISTBOX 100, 10, 10, 420, 155, + LBS_EXTENDEDSEL | LBS_HASSTRINGS | LBS_USETABSTOPS | 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, 60, 12, CBS_DROPDOWNLIST +END + +/* Accelerators used: cl */ +IDD_ABOUT DIALOG DISCARDABLE 140, 40, 270, 136 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +#ifdef MOD_PERSO +CAPTION "About Kageant" +#else +CAPTION "About Pageant" +#endif +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 + +#ifndef MOD_INTEGRATED_AGENT +#include "version.rc2" + +#ifndef NO_MANIFESTS +1 RT_MANIFEST "pageant.mft" +#endif /* NO_MANIFESTS */ +#endif diff --git a/0.77_My_PuTTY/windows/pageants.ico b/0.77_My_PuTTY/windows/pageants.ico new file mode 100644 index 0000000000000000000000000000000000000000..28a6d01e548e7ce40cf5ecab4900dfff5c50cd96 GIT binary patch literal 510 zcmZQzU}Ruo5D*YxU}Run&|qX>P-9?V5D;KuU}Ruq*ucQR;K#_opaBwNU|>*S0I6qT z0D%S&ffY9}Fn}Z*8Xy!y14Bat1H*v>2N)Va42J&|NkFh8hb}a zM@It#1ONa39UT=63=AOt4+aK?^73*Jm*LNuGk9ra!yv|TK&Xy#2+aXNMm4NP*8yIUobH+^f59p7#KkK8Egy;Wh@K~0s>47 zj0}tn8yFZE?s0+CL-+=a3=DnTAoWl_GXuju9tH*t1_lNJkhu&X`&bx2paDc+#SIJ$ zAjyUX2*uFA(9poZaNxiJh6WIW;XeZiG5r4zp&0&ym<<2_|A(8#59RQG04rgD^BMZV ziuix*fY1!x9Ux8o|2uYbbby$|_BdG02L=X)Ka808A2TGJ6c`v79KgyU;l#iM)`o?kJe**Lg2D$H zWMCow{~s6__`yL0<^Kl(FdIbhGk~P=%R?dwq#CRiRsRQglz<338m*u8uA?hWka<>labhY9@u|GxsNfg550`wu9e0b*W5J&ez=oB#j+?@;4F&fwl~ z=FD*a2N?pw z9}r~&Ob$vz@;H=(M4_1kkpUGMkU{`du0X`VBv^t2B-jC|PQaoJ4BQ}z4WLqofgN1m zK*hm@&+ZK%r~R*hl|LW{gRKR7<^OjiafSl82iX53i?cKAW`J1RP!BJsK$h@x?*^4^ zvo=mrLPNP7y=?qmeJ4b-**GtdZF z0Dzis$;G z18K&F!8ImC4X6YouKfpbFRtVTckdqth6))5hWbAY3=TC=ng>EB{$XIKw_#u?j$vS^ zU&6rPaD#!N{tW|z^EU>D`rrG1FfiV+VX%MxkAeU9KL&ONIR@ta?-&^Fe_&utC}3bT zC}3dZC}3b@DPUk^K*FHHAA~`b_h4#AK*I|{K>{0zgoPJKs2-Xw>ll83xC}5FCZ50! z5r@%maRv*p`UEHq6ECobsE5%o@tR_YcugsUuKxw06L_KK@ZA8LSN}f)OefSs+O!Gv zcfkDm|IffQq{#(hG9Cbt3=IF@Lt?=GJH#vUKM&M^WcdGq^D2WlBZTH`V5kF$vz-6G z4@5JT|Gx*OXa5IhRmL7>2;Byy8=-VVGeo?>8A`*%anS#v=0Rx&7+nt$2iXb2gS{Mt Jxo>!uhXA6|J9q#9 literal 0 HcmV?d00001 diff --git a/0.77_My_PuTTY/windows/pscp.rc b/0.77_My_PuTTY/windows/pscp.rc new file mode 100644 index 0000000..f993245 --- /dev/null +++ b/0.77_My_PuTTY/windows/pscp.rc @@ -0,0 +1,8 @@ +#include "rcstuff.h" + +#define APPNAME "PSCP" +#define APPDESC "Command-line SCP/SFTP client" + +200 ICON "pscp.ico" + +#include "version.rc2" diff --git a/0.77_My_PuTTY/windows/psftp.rc b/0.77_My_PuTTY/windows/psftp.rc new file mode 100644 index 0000000..6b39531 --- /dev/null +++ b/0.77_My_PuTTY/windows/psftp.rc @@ -0,0 +1,8 @@ +#include "rcstuff.h" + +#define APPNAME "PSFTP" +#define APPDESC "Command-line interactive SFTP client" + +200 ICON "pscp.ico" + +#include "version.rc2" diff --git a/0.77_My_PuTTY/windows/putty.ico b/0.77_My_PuTTY/windows/putty.ico new file mode 100644 index 0000000000000000000000000000000000000000..fcd641a0a9b002b453e9f746ceb19b859384657b GIT binary patch literal 4078 zcmZQzU}Rup5D*YxU}Run&|qX>NMm4NP*8yIUobH+^f59p7#KkK8Egy;Wh@K~0s>47 zj0}tn8yFZE?s0+CL-+=a3=DnTAoWl_GXuju9tH*t1_lNJkhu&X`&bx2paDc+#SIJ$ zAjyUX2*uFA(9poZaNxiJh6WIW;XeZiG5r4zp&0&ym<<2_|A(8#-qFzkGJ^mA|Beoj zXh#Qxu4pd@i8FlJavV(m@%DhwF1r~(Dt_$Vy#b;X!~jWg?`W?8nZV)g1E%?pY=O{c zTp;xS|6m%VhM}RJfq}uEfq_AufuR9}?HL#vK$w|Rp8<`}&4Vg`U;__|`ZL@cQ27UT zb3mO7GT+6;g&UMSp$-7K_X8+yp(%sCyqtl7dpCOp14BF1(Ytr=-o1gnyu2KobQl;I zzykjf&gR|?HjMoT%o>P!4fQZS!*2fn|KVZ6z`)PF;mn!iP~$gd{pyXTwGk-k<17A63KoBP&Ae!k|)^tAfkc6{yzhQ{C@@p{{IXN>|or$ z!2bUL1N;964E+BeFxcb34D1gW7??jWFff9m_WuC}hW{TJ82PyZPKa*u@WQz%G7bs#5L(_q<+wo-8$hKF14=>ymo>XLfIWlkMSifgkYt9EkP6_18(KnQ099ll zi_sGj_inJ)D>~2;QUN#ET8sd1Ik1}tdqjguH7pT#<^bslfCrm7XU-hJA&%WW;1rMP zeo*1);({R#3Y(6O4&+bB2Zts|5=|Ur7z{TsFxW$zCGt?3 zA4-GDL69P^$L3T6nGcf%B z$H2hA4ywf&7#NsA^Z`&qj^RJ30$_mh!Sn-=pBVmwDg`hfR7-&5|3KCMVE~)=pW5_4 zsJWmj4P@W{22dLeRL>yVos3|&f!bDJ1{witKZ0uWdQcjOBL)V>ISdR8J`441`gf^1`aj`2L9jsf3P3CW5Zzo{2v4V z?|%&J3~~(2``cn1Kn`SJ1f@(+&Uz1t0sHR| zm&*S=Gu7}tIvJ;eEM$1D`Nc;zte~h4VirVEM%slMnApmA4ImQ40 literal 0 HcmV?d00001 diff --git a/0.77_My_PuTTY/windows/putty.mft b/0.77_My_PuTTY/windows/putty.mft new file mode 100644 index 0000000..c5d701f --- /dev/null +++ b/0.77_My_PuTTY/windows/putty.mft @@ -0,0 +1,35 @@ + + + + + A network client and terminal emulator + + + + + + + + + + true + + PerMonitorV2 + + + + diff --git a/0.77_My_PuTTY/windows/putty.rc b/0.77_My_PuTTY/windows/putty.rc new file mode 100644 index 0000000..9331c5a --- /dev/null +++ b/0.77_My_PuTTY/windows/putty.rc @@ -0,0 +1,17 @@ +#include "rcstuff.h" + +#define APPNAME "PuTTY" +#define APPDESC "SSH, Telnet, Rlogin, and SUPDUP client" + +#include "winhelp.rc2" +#include "win_res.rc2" + +#ifdef MOD_PERSO +#ifndef NO_MANIFESTS +1 RT_MANIFEST "kitty.mft" +#endif /* NO_MANIFESTS */ +#else +#ifndef NO_MANIFESTS +1 RT_MANIFEST "putty.mft" +#endif /* NO_MANIFESTS */ +#endif diff --git a/0.77_My_PuTTY/windows/puttycfg.ico b/0.77_My_PuTTY/windows/puttycfg.ico new file mode 100644 index 0000000000000000000000000000000000000000..bb32be844b0bb68f748a3406b275a0b8af75bb7c GIT binary patch literal 4078 zcmZQzU}Rup5D*YxU}Run&|qX>NMm4NP*8yIUobH+^f59p7#KkK8Egy;Wh@K~0s>47 zj0}tn8yFZE?s0+CL-+=a3=DnTAoWl_GXuju9tH*t1_lNJkhu&X`&bx2paDc+#SIJ$ zAjyUX2*uFA(9poZaNxiJh6WIW;XeZiG5r4zp&0&ym<<2_|A(8#-qF#~z`(qL|NsAv z4hHKD3>_UE9n8BK7%JM!8>}}lFnrl^9K>b#Bd-p$d$!0^G?dIMOi@owu4 z9SscDyEpG>uV7%_z`eoSr-Ff*fqBP~Efow54A%T-Tq+nCxItEc_zFfbfoU|?uq1P26z03!p#0!9Xg1B?s|&zK?Mq`<(y-~g6}gcAc3 z#5_!r@^FHg2?{Sz5OqKW_{+=7%NrOtDnRkTz`y|JmzS4UfD{-oF!29}fbSg)49wOH z3=9PjjrAQ2492@bsiFdG9QzLjX6xM`Ik0+=aS&;SFK5o2IaAR9lVfM8eq~O{oETW;L;%ef!$EMLH2!dadF{BNP|+x{}0ByH-MZ4 z@)Ub{ImmAI3I>LDkf#_J7&aL1-mrnayu7>vqyXfx-Q2r3{Qv(SFaa68dp83=_l7fPj)RP6-o1PG1_pkI-8>M!^#*2; zybr?$D4&7BdN%{V&lzqWuzFAm+W_Jpfbp5F!F;HBAlEQ3@VmIUxVVGNXJ7z@GB{em z_Je|efq?-Ojpc~s2^9rpFZ=%t4D$aO82IlqFtGPCFtD>XFt9NnU|?Z?z`()(fWbii z0fU150|tir2Mi4VA22Zff55=}{{aI7`vV3B<_`=Ej6WC{82%q%VEEI(!0>^Af#CrI z1LFY}2KECy4D1ao4D4+z4D9E47})>tFtGoSVPOAZ!@&OM9|QaU{|x+~@&J@g5|Gk) z12nnA9SLVpFP+1TWQQbAP-5tS1Rq2OMI02%5Mc)X@9>ahN7euiaB$N8|Gxr~Y?wh> zkj1}4#En6@f}tEF3&!mK!Q#*)$_~$g4fT-Z1xha{IgkB+12|3Y2E{Z3e?7$7A7E1$ zjKS#(q`n1|vCf=!Zl5|Xho zI1%u3?*@Clq63nUjCb$02AR%Zzzr(n{zDQBI3a=441ddk-8|5UhB^hL929Y&QVkSw zpg^$Rz`zYoV*D*<4!{xs$cdm53{(?<5*{=GFfcGMgIsM569<*?PQTf&1u+98A-TA?z|990xS$LIjTC5h1y$i7JCLe0M09~AAPK3w z99j#2#Xtl&G(nPR>Opca+`zzK4{esnLuq~}&HfM4Tw(rG4_3|m!yZ&FGcbRU2e%QJ zAMk_AROSQh;PREdfgeoUGsuH!I|h3&ZNpFxrY%5?p$`lU28{o~dr8AO9H13v@9|9_x_#sF?-F)%~FVs6Lng;X{SN=7K~%Fz|pHehd#7WEeo^Jz%hb(lucE-~$N%FqD4&03!Y$ zMjwFk8~%XR|7V2K%uss%KZrQU!65hk|K9)#Cr~|uXm^4dU68gF$W7=N)_$ySVqmC0 z#=yYf!@!_EgMmTQfPq0Jfq_Bc4g-VYJO&2lBnAe%ISdSHJ`4*0?CgY z1p*&g3K$+TfHC7ih5}~B{~s6_Kp2!>L8%Coq6R}d0vcWr3O#y3EkL+f{eMu)g@K{| zKd4Cqq8UM10mKKV8i2wih1Jv+g|Np;&{{sW45z`?5fPsOH zfxp520E7m$0zrIGwq|4CZvO_p_>U1xGyiR1r~_+1|9>ASgMKXke-D(oAI=8(oc;g79%fLZ?tfz&h~|@LYy{Cf z{EZFG4;UCY_!}FXK@R0-YygQfpwn_taT`WRNG3pOP*DNW2MSsU4G#O!@(>&h3NMm4NP*8yIUobH+^f59p7#KkK8Egy;Wh@K~0s>47 zj0}tn8yFZE?s0+CL-+=a3=DnTAoWl_GXuju9tH*t1_lNJkhu&X`&bx2paDc+#SIJ$ zAjyUX2*uFA(9poZaNxiJh6WIW;XeZiG5r4zp&0&ym<<2_|A(8#-qFzkGJ^mA|Beoj zXh#QxF7GG@i8K81c8AbMc7UWA{v7cJ`Gw(s_wL;c3=GT+4BWeSGczzVGc)XFFg9Q| zHa2G1U;w6#xfu+=dJLE$G_x_73nCio85kJs85kJk85kNs*q(u*0SSW)W?%%VM8*FP zGBEsq3<)O%1_lNPq;O(jLRE|4Q65e(vq0em3L zK%E2C&%FVae*hG9Pz%4fxVX4*gX0t|0(Nh~Znyw@d3iY~lEIQtV)yRd2n8T9gn$7w zO)xMp7=xnQn1KOA?cTt^Y;0`6z+lV_=7SOoD5)?QGaG>98MrqX8-o;pNRWvPyP1ta z=?26HV}=dJ3=9SgU^bZGW(M)WYC#0E0SJOv5DZF*APXTpuyeo;g7YEnfpekmMdE{) z4Gi}G85rdMGcfS~XJB9l;|2!y{|6Y@|36^h|Nnr&9tV~O%YR^C|Nn!5@&Eh@4E+5v z4D3yEJj@O942%u(2cUVDL85^Hj2Zs_|G@D7{}0Ch|Nk)m|NoEu|NsB|pz;8ec@mJ) zc>|5$%){c$NnEhydGrd|NlRbO#$WH7Es1IbB3nk z2Y9fXe_#W4@iW{!*xhqr0|$otK^fPDdjq;SB(55EBb5Q5u!O|`QdS2EGcbT7th^j4 z%R|IL86PCafK*sOWS}I5NlV8x(30<{*J z%fWRHn1FZyRAzvbf`z~Y1IPpLVgg(_K?{uC8=&P3sD?9UFlK;?LrX2N4a~;O#s)BP zNa+O?2iXL256HIN;8F%;9oQm}6c{sX04oNSmS8@d;0C(`Q``Vl{DJCIRCA0$u0tsY zLAEl03R`0X29UiFKAePD1n0sTq>6)`0rMr?6fDIoTn_AEI1i1{z`$S+ZI;MGX?`fp z4yBoq=>`S{F#iCQegLIEfC}mV3=AKj=24se0X6pzs8aaP!0-=h-~ay)!0!3~{{SP% zo&WzofVQ=MfNPM?|7{p#>=_t7{Nuz`#&{0MZCEZeU2c5Lu`SNaQFT{z)=720Ym-&4`8go@PUEIO+yhI|3SB5DF4VNF*%0KtlEZ z|AS(fp&pc=85kJq|1*O9Q~&=z$ZsHVFl_+wD}(s|e_%fQ|No#i00aAfNM+2%zz>cd z!gN1Woj;UThtkqun*IM + + + + SSH key generator for PuTTY + + + + + + + + + + true + + + diff --git a/0.77_My_PuTTY/windows/puttygen.rc b/0.77_My_PuTTY/windows/puttygen.rc new file mode 100644 index 0000000..ff13c81 --- /dev/null +++ b/0.77_My_PuTTY/windows/puttygen.rc @@ -0,0 +1,114 @@ +/* + * Windows resources for PuTTYgen. + */ + +#include "rcstuff.h" + +#ifdef MOD_INTEGRATED_KEYGEN +#define APPNAME_KEYGEN "PuTTYgen" +#define APPDESC_KEYGEN "PuTTY SSH key generation utility" + +#include "winhelp.rc2" +#include "puttygen-rc.h" + +800 ICON "puttygen.ico" +#else + +#define APPNAME "PuTTYgen" +#define APPDESC "PuTTY SSH key generation utility" + +#include "winhelp.rc2" +#include "puttygen-rc.h" + +200 ICON "puttygen.ico" +#endif + +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 + +#ifdef MOD_INTEGRATED_KEYGEN +810 DIALOG DISCARDABLE 0, 0, 140, 60 +#else +210 DIALOG DISCARDABLE 0, 0, 140, 60 +#endif +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 */ +#ifdef MOD_INTEGRATED_KEYGEN +813 DIALOG DISCARDABLE 140, 40, 270, 136 +#else +213 DIALOG DISCARDABLE 140, 40, 270, 136 +#endif +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 */ +#ifdef MOD_INTEGRATED_KEYGEN +814 DIALOG DISCARDABLE 50, 50, 326, 239 +#else +214 DIALOG DISCARDABLE 50, 50, 326, 239 +#endif +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 + +#ifndef MOD_INTEGRATED_KEYGEN +#include "version.rc2" + +#ifndef NO_MANIFESTS +1 RT_MANIFEST "puttygen.mft" +#endif /* NO_MANIFESTS */ +#endif diff --git a/0.77_My_PuTTY/windows/puttyins.ico b/0.77_My_PuTTY/windows/puttyins.ico new file mode 100644 index 0000000000000000000000000000000000000000..2bd92c6da2425c32bc31fe9aebe53e46d84cf063 GIT binary patch literal 11478 zcmZQzU}WHA5D);-91Iz(3=Gp47#I{3Ap8{^3=IER85j%u6K41Tg8^$`9GCI*HwIgok?KZA{dAx;sbo{52xfstVY0|Ub`b&z@p-++;Up-cm$ z9?EBCU|6Qfz@Wjvz#sr}2Lr@j1`uEb5m>N80|Ntt0|P_-ey9!s28Q~228RFt859&0 z806*U85$ZI81(h^8RFyP8S3ln8LAl=7(O#FFg#;mVA#KZKg0k3{~2l-7#Q9$FfdeN zv0_wcGz3ONU?_wDBNQ+}9Sh|$GC>8<_{>lMlZSGd8JUUFR z0yYO^2RjI`fCV@}dq@(c_O{0s~Y>=4Yr&cMLH%)rpV$iM(96%Q~lFg##jVEDku z!0-nR|6_)fvkH*XlYx-|;(i7exbdhA;zR@o zPdf$FmVmWYKz+Z*(Do6iy>g9#f#CuJ14Aja-SGk19|W~so!8c=%%)CK~zcR=kLP#Xyp7N9;T zs7+JCz`#(*z`&4)@WCi+Gz3ONfLGQjGwX+Tv5=M4$o3Gz2g9gK!4K%*fo zP!cpiTBO)Cz-&ULK`NmbRe}K$GobtdY74@6$oUAT22j%vQv;fE280cuCM}!`vjIan zL31l*?wG3)-gIxvEfT|A810^lEdL#|-rY>A15(7yCOcc^829Yok zWE!D?g#pw6gth)at#lY0Sv^v79p0t{GjM7E#Q<0Z8UfOPVlte=f@Q#ojh!8>-vCz* z=|X@!h=duy8j#eWGFc$L1^F0+K}Lf#pejP}*wH)zvIrrI$zo>(X@K-7`1l1tPRAOU zFrDnIf}kFTkg$lT7>~FF5$YwSq#0Nkq@{#pIC;d0P%kSdufV{BwL4RNX_m>zzeXjri89+26 zlm<|s#OQ7(>#0`)^ z`i2JZSUrerXkcJCaNq!VY#lrb4hoq6|3N{;03tzg37V^fn`LNdXb9p63kwT_EC%rz z85p>wrKLf9#*G^{Ze%oIV2}n02!kdE3>XXz4H&q0?`B{I@j(m&kP~+^bb!bIIkubu z^>7*Zy?r3`5h(rtKbQ})jmq;5kZ@wa*7k(hMtL}a%>fM_fC2~_W*{+UP{2XM4#a1K z%EQ`WaCuPRBI$<>y%CTH#RE9#K@;pC?V$MBxN)N)Og~5x6g2{%xL{ylfXZ)VGz1NI zKq3;PMpziC0OUvp1_nb=B!bui0s;cNHwb{-0pc=%4Cda=2#VwmP_!}#?B2b5_Xbd8 zw|9V4Ffu>{49d$P3K+O|gT(p&|E~aRU|`q{nxCN8`J=xVX3@ng1Wn{&EmNBu|K= z7#J7^*SHlVoi{)zxEm1+DwGEZ!=Nl!f(E5)s1TR}B^YSh2Ne`xF%W^Q9+n9};wb8& znE@n@q8>>Ds(OS5RP_iA)KL#E@1S|e&=9G21j<8DEG#Sl&5cO;4WtKIJuKsb^au;U zawh`=iUo|YT*?3`U_pu@nHiB=K?cJx$nTJBjmX#_J>Yl%1tuB>#Tm3504;_SwQUR_Wv_?ljfO`Xo1_dtMY)}aS%HyEWft4a)Ndaz9$*>!u0W1O` z7(ung?%f+e8Vo=M0Yn%?LNqWiFhB|jkPsL{OkytwWl8o9M8<^_Euh@{|Ns9AX!!%y zv6~+%3MyD&clj;0G64 z|Ns9$F@=GFzX4QuojHRc-U2Gbu!*1H<~f5^{J;k6;%69eh@YW~IH+)RalvpuG3C&3+fAj zIt>tErd6vJfLN@dp#dP8Qoa_{JW;55$b3+G<65<90mwqu(9l4TgTffH`fMLS6Qt1oFqA*q52s3h9D6?;0DM=f AY5)KL literal 0 HcmV?d00001 diff --git a/0.77_My_PuTTY/windows/puttytel.rc b/0.77_My_PuTTY/windows/puttytel.rc new file mode 100644 index 0000000..259bc68 --- /dev/null +++ b/0.77_My_PuTTY/windows/puttytel.rc @@ -0,0 +1,11 @@ +#include "rcstuff.h" + +#define APPNAME "PuTTYtel" +#define APPDESC "Telnet and Rlogin client" + +#include "winhelp.rc2" +#include "win_res.rc2" + +#ifndef NO_MANIFESTS +1 RT_MANIFEST "puttytel.mft" +#endif /* NO_MANIFESTS */ diff --git a/0.77_My_PuTTY/windows/rcstuff.h b/0.77_My_PuTTY/windows/rcstuff.h new file mode 100644 index 0000000..ee2c769 --- /dev/null +++ b/0.77_My_PuTTY/windows/rcstuff.h @@ -0,0 +1,50 @@ +/* + * Miscellaneous stuff to include in all .rc files. + */ + +#ifndef PUTTY_RCSTUFF_H +#define PUTTY_RCSTUFF_H + +#ifdef __LCC__ +#include +#else + +/* Some compilers don't have winresrc.h */ +#ifndef NO_WINRESRC_H +#ifndef MSVC4 +#include +#else +#include +#endif +#endif + +#endif /* end #ifdef __LCC__ */ + +/* 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/0.77_My_PuTTY/windows/sizetip.c b/0.77_My_PuTTY/windows/sizetip.c new file mode 100644 index 0000000..a40a607 --- /dev/null +++ b/0.77_My_PuTTY/windows/sizetip.c @@ -0,0 +1,192 @@ +/* + * 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/0.77_My_PuTTY/windows/version.rc2 b/0.77_My_PuTTY/windows/version.rc2 new file mode 100644 index 0000000..532b79c --- /dev/null +++ b/0.77_My_PuTTY/windows/version.rc2 @@ -0,0 +1,82 @@ +/* + * 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 +#ifdef MOD_PERSO + VALUE "CompanyName", "9bis.com" /* required :/ */ + VALUE "ProductName", "KiTTY" + VALUE "FileDescription", APPDESC + VALUE "InternalName", APPNAME + VALUE "OriginalFilename", "KiTTY" +#else + VALUE "CompanyName", "Simon Tatham" /* required :/ */ + VALUE "ProductName", "PuTTY suite" + VALUE "FileDescription", APPDESC + VALUE "InternalName", APPNAME + VALUE "OriginalFilename", APPNAME +#endif + +#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 +#if (defined MOD_PERSO) && (!defined FLJ) + VALUE "ProductVersion", TEXTVER " - Additional features on KiTTY That's all folks! by 9bis.com, 2005-2022" +#else + VALUE "ProductVersion", TEXTVER +#endif + 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/0.77_My_PuTTY/windows/version_major.txt b/0.77_My_PuTTY/windows/version_major.txt new file mode 100644 index 0000000..6c99b07 --- /dev/null +++ b/0.77_My_PuTTY/windows/version_major.txt @@ -0,0 +1 @@ +"0.76.0" diff --git a/0.77_My_PuTTY/windows/version_minor.txt b/0.77_My_PuTTY/windows/version_minor.txt new file mode 100644 index 0000000..45a4fb7 --- /dev/null +++ b/0.77_My_PuTTY/windows/version_minor.txt @@ -0,0 +1 @@ +8 diff --git a/0.77_My_PuTTY/windows/win_res.h b/0.77_My_PuTTY/windows/win_res.h new file mode 100644 index 0000000..55d20cd --- /dev/null +++ b/0.77_My_PuTTY/windows/win_res.h @@ -0,0 +1,143 @@ +/* + * win_res.h - constants shared between win_res.rc2 and the C code. + */ + +#ifndef PUTTY_WIN_RES_H +#define PUTTY_WIN_RES_H + +#define IDI_MAINICON 200 + + + +#ifdef MOD_PERSO + +#ifdef FLJ +#define NB_ICONES 4 +#else +#define NB_ICONES 50 +#endif + +#define IDI_MAINICON_0 1 +#define IDI_MAINICON_1 2 +#define IDI_MAINICON_2 3 +#define IDI_MAINICON_3 4 +#define IDI_MAINICON_4 5 +#define IDI_MAINICON_5 6 +#define IDI_MAINICON_6 7 +#define IDI_MAINICON_7 8 +#define IDI_MAINICON_8 9 +#define IDI_MAINICON_9 10 +#define IDI_MAINICON_10 11 +#define IDI_MAINICON_11 12 +#define IDI_MAINICON_12 13 +#define IDI_MAINICON_13 14 +#define IDI_MAINICON_14 15 +#define IDI_MAINICON_15 16 +#define IDI_MAINICON_16 17 +#define IDI_MAINICON_17 18 +#define IDI_MAINICON_18 19 +#define IDI_MAINICON_19 20 +#define IDI_MAINICON_20 21 +#define IDI_MAINICON_21 22 +#define IDI_MAINICON_22 23 +#define IDI_MAINICON_23 24 +#define IDI_MAINICON_24 25 +#define IDI_MAINICON_25 26 +#define IDI_MAINICON_26 27 +#define IDI_MAINICON_27 28 +#define IDI_MAINICON_28 29 +#define IDI_MAINICON_29 30 +#define IDI_MAINICON_30 31 +#define IDI_MAINICON_31 32 +#define IDI_MAINICON_32 33 +#define IDI_MAINICON_33 34 +#define IDI_MAINICON_34 35 +#define IDI_MAINICON_35 36 +#define IDI_MAINICON_36 37 +#define IDI_MAINICON_37 38 +#define IDI_MAINICON_38 39 +#define IDI_MAINICON_39 40 +#define IDI_MAINICON_40 41 +#define IDI_MAINICON_41 42 +#define IDI_MAINICON_42 43 +#define IDI_MAINICON_43 44 +#define IDI_MAINICON_44 45 +#define IDI_MAINICON_45 46 +#define IDI_MAINICON_46 47 +#define IDI_MAINICON_47 48 +#define IDI_MAINICON_48 49 +#define IDI_MAINICON_49 50 + +#define IDC_HOVER 360 + + +#define IDI_PUTTY_LAUNCH 9901 +#define IDI_BLACKBALL 9902 +#define IDI_EDITICON 9903 + +#define IDI_FILEASSOC 9904 +#define IDI_NOCON 9905 +#define IDI_NUCLEAR 9906 + +//#define IDI_PUTTY_LAUNCH IDI_MAINICON_0 + +#define IDD_KITTYABOUT 121 +#define IDC_WEBPAGE 401 +#define IDC_EMAIL 402 +#define IDC_BAN 403 + +#define IDC_RESULT 1008 + +#define IDA_DON 1007 + +#define IDA_TEXT2 1005 +#define IDA_VERSION 1006 + +#endif +#ifdef MOD_RUTTY +#define IDA_WEB2 1007 +#endif + + + +#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_HK_ABSENT 114 +#define IDD_HK_WRONG 115 +#define IDD_HK_MOREINFO 116 + +#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_ACCEPT 1001 +#define IDC_HK_ONCE 1000 +#define IDC_HK_FINGERPRINT 1002 +#define IDC_HK_MOREINFO 1003 + +#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/0.77_My_PuTTY/windows/win_res.rc2 b/0.77_My_PuTTY/windows/win_res.rc2 new file mode 100644 index 0000000..5512a3d --- /dev/null +++ b/0.77_My_PuTTY/windows/win_res.rc2 @@ -0,0 +1,315 @@ +/* + * 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'). + */ + +#include "win_res.h" + +IDI_MAINICON ICON "putty.ico" + +#ifdef MOD_PERSO + +IDI_FILEASSOC ICON "../../res/file_assoc/kitty_assoc.ico" +IDI_PUTTY_LAUNCH ICON "../../res/putty_launcher.ico" +IDI_BLACKBALL ICON "../../res/black.ico" +IDI_NOCON ICON "../../res/nocon/kitty_nocon.ico" + +#ifndef FLJ +IDI_MAINICON_0 ICON "../../kitty.ico" +IDI_MAINICON_1 ICON "putty.ico" +#else +IDI_MAINICON_0 ICON "putty.ico" +IDI_MAINICON_1 ICON "../../kitty.ico" +#endif + +IDI_MAINICON_2 ICON "../../res/puttyprd.ico" +IDI_MAINICON_3 ICON "../../res/putty2.ico" +IDI_MAINICON_4 ICON "../../res/putty3.ico" + +#ifndef FLJ +IDI_MAINICON_5 ICON "../../res/coyote.ico" +IDI_MAINICON_6 ICON "../../res/daffy.ico" +IDI_MAINICON_7 ICON "../../res/tweety.ico" +IDI_MAINICON_8 ICON "../../res/sylvester.ico" + +IDI_MAINICON_9 ICON "../../res/0.ico" +IDI_MAINICON_10 ICON "../../res/1.ico" +IDI_MAINICON_11 ICON "../../res/2.ico" +IDI_MAINICON_12 ICON "../../res/3.ico" +IDI_MAINICON_13 ICON "../../res/4.ico" +IDI_MAINICON_14 ICON "../../res/5.ico" +IDI_MAINICON_15 ICON "../../res/6.ico" +IDI_MAINICON_16 ICON "../../res/7.ico" +IDI_MAINICON_17 ICON "../../res/8.ico" +IDI_MAINICON_18 ICON "../../res/9.ico" + +IDI_MAINICON_19 ICON "../../res/porky.ico" +IDI_MAINICON_20 ICON "../../res/taz.ico" +IDI_MAINICON_21 ICON "../../res/coq.ico" +IDI_MAINICON_22 ICON "../../res/elmer.ico" +IDI_MAINICON_23 ICON "../../res/mars.ico" +IDI_MAINICON_24 ICON "../../res/speedy.ico" +IDI_MAINICON_25 ICON "../../res/rocky.ico" +IDI_MAINICON_26 ICON "../../res/pepepu.ico" +IDI_MAINICON_27 ICON "../../res/general.ico" +IDI_MAINICON_28 ICON "../../res/hagar.ico" +IDI_MAINICON_29 ICON "../../res/jerry.ico" +IDI_MAINICON_30 ICON "../../res/tom.ico" +IDI_MAINICON_31 ICON "../../res/snoopy.ico" +IDI_MAINICON_32 ICON "../../res/woods.ico" +IDI_MAINICON_33 ICON "../../res/yogi.ico" +IDI_MAINICON_34 ICON "../../res/scooby.ico" +IDI_MAINICON_35 ICON "../../res/shaggy.ico" +IDI_MAINICON_36 ICON "../../res/freddie.ico" +IDI_MAINICON_37 ICON "../../res/velma.ico" +IDI_MAINICON_38 ICON "../../res/daphne.ico" +IDI_MAINICON_39 ICON "../../res/woody.ico" +IDI_MAINICON_40 ICON "../../res/sam.ico" +IDI_MAINICON_41 ICON "../../res/runner.ico" +IDI_MAINICON_42 ICON "../../res/bunny.ico" +IDI_MAINICON_43 ICON "../../res/hellokitty.ico" +IDI_MAINICON_44 ICON "../../cthelper/puttycyg.ico" +IDI_MAINICON_45 ICON "../../res/putty4.ico" +IDI_MAINICON_46 ICON "../../res/putty5.ico" +IDI_MAINICON_47 ICON "../../res/putty6.ico" +IDI_MAINICON_48 ICON "../../res/putty7.ico" +IDI_MAINICON_49 ICON "../../res/putty8.ico" +#endif + +IDC_HOVER CURSOR "../../res/Hover.cur" + +//IDI_NUCLEAR ICON "../../res/nuclear.ico" + +#define IDB_OK 1098 + +#else +IDI_MAINICON ICON "putty.ico" +#endif + +IDI_CFGICON ICON "puttycfg.ico" + +/* 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 +#if (defined MOD_PERSO) && (!defined FLJ) + CTEXT "KiTTY (https://www.9bis.net/kitty), by 9bis.com", IDA_TEXT2, 10, 26, 194, 16 +#endif +#if (defined MOD_PERSO) && (defined FLJ) + CTEXT "PuTTY", IDA_TEXT2, 10, 26, 194, 16 +#endif +END + +/* Accelerators used: aco */ +#if (defined MOD_PERSO) && (!defined FLJ) +IDD_MAINBOX DIALOG DISCARDABLE 0, 0, 300, 610 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU /*| WS_SIZEBOX*/ +#else +IDD_MAINBOX DIALOG DISCARDABLE 0, 0, 300, 252 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +#endif +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 + +/* No accelerators used */ +IDD_HK_ABSENT DIALOG DISCARDABLE 50, 50, 340, 148 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Security Alert" +FONT 8, "MS Shell Dlg" +BEGIN + LTEXT "The server's host key is not cached in the registry. You have no", 100, 40, 20, 300, 8 + LTEXT "guarantee that the server is the computer you think it is.", 101, 40, 28, 300, 8 + LTEXT "The server's {KEYTYPE} key fingerprint is:", 102, 40, 40, 300, 8 + LTEXT "If you trust this host, press ""Accept"" to add the key to {APPNAME}'s", 103, 40, 60, 300, 8 + LTEXT "cache and carry on connecting.", 104, 40, 68, 300, 8 + LTEXT "If you want to carry on connecting just once, without adding the key", 105, 40, 80, 300, 8 + LTEXT "to the cache, press ""Connect Once"".", 106, 40, 88, 300, 8 + LTEXT "If you do not trust this host, press ""Cancel"" to abandon the connection.", 107, 40, 100, 300, 8 + + ICON "", IDC_HK_ICON, 10, 18, 0, 0 + + PUSHBUTTON "Cancel", IDCANCEL, 288, 128, 40, 14 + PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 128, 40, 14 + PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 128, 64, 14 + PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 128, 64, 14 + PUSHBUTTON "Help", IDHELP, 12, 128, 40, 14 + + EDITTEXT IDC_HK_FINGERPRINT, 40, 48, 300, 12, ES_READONLY | ES_LEFT, 0 +END + +/* No accelerators used */ +IDD_HK_WRONG DIALOG DISCARDABLE 50, 50, 340, 188 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY Security Alert" +FONT 8, "MS Shell Dlg" +BEGIN + LTEXT "WARNING - POTENTIAL SECURITY BREACH!", IDC_HK_TITLE, 40, 20, 300, 12 + + LTEXT "The server's host key does not match the one {APPNAME} has cached in", 100, 40, 36, 300, 8 + LTEXT "the registry. This means that either the server administrator has", 101, 40, 44, 300, 8 + LTEXT "changed the host key, or you have actually connected to another", 102, 40, 52, 300, 8 + LTEXT "computer pretending to be the server.", 103, 40, 60, 300, 8 + LTEXT "The new {KEYTYPE} key fingerprint is:", 104, 40, 72, 300, 8 + LTEXT "If you were expecting this change and trust the new key, press", 105, 40, 92, 300, 8 + LTEXT """Accept"" to update {APPNAME}'s cache and continue connecting.", 106, 40, 100, 300, 8 + LTEXT "If you want to carry on connecting but without updating the cache,", 107, 40, 112, 300, 8 + LTEXT "press ""Connect Once"".", 108, 40, 120, 300, 8 + LTEXT "If you want to abandon the connection completely, press ""Cancel"".", 109, 40, 132, 300, 8 + LTEXT "Pressing ""Cancel"" is the ONLY guaranteed safe choice.", 110, 40, 140, 300, 8 + + ICON "", IDC_HK_ICON, 10, 16, 0, 0 + + PUSHBUTTON "Cancel", IDCANCEL, 288, 168, 40, 14 + PUSHBUTTON "Accept", IDC_HK_ACCEPT, 168, 168, 40, 14 + PUSHBUTTON "Connect Once", IDC_HK_ONCE, 216, 168, 64, 14 + PUSHBUTTON "More info...", IDC_HK_MOREINFO, 60, 168, 64, 14 + PUSHBUTTON "Help", IDHELP, 12, 168, 40, 14 + + EDITTEXT IDC_HK_FINGERPRINT, 40, 80, 300, 12, ES_READONLY | ES_LEFT, 0 +END + +/* Accelerators used: clw */ +IDD_HK_MOREINFO DIALOG DISCARDABLE 140, 40, 400, 156 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "PuTTY: information about the server's host key" +FONT 8, "MS Shell Dlg" +BEGIN + LTEXT "SHA256 fingerprint:", 100, 12, 12, 80, 8 + EDITTEXT IDC_HKI_SHA256, 100, 10, 288, 12, ES_READONLY + LTEXT "MD5 fingerprint:", 101, 12, 28, 80, 8 + EDITTEXT IDC_HKI_MD5, 100, 26, 288, 12, ES_READONLY + LTEXT "Full public key:", 102, 12, 44, 376, 8 + EDITTEXT IDC_HKI_PUBKEY, 12, 54, 376, 64, ES_READONLY | ES_MULTILINE | ES_LEFT | ES_AUTOVSCROLL, WS_EX_STATICEDGE + DEFPUSHBUTTON "&Close", IDOK, 176, 130, 48, 14 +END + +#ifdef MOD_PERSO + +#ifndef IDC_STATIC +#define IDC_STATIC -1 +#endif + +117 DIALOG DISCARDABLE 0, 0, 248, 50 +STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Text input" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_RESULT,7,20,184,14,ES_AUTOHSCROLL + /*DEFPUSHBUTTON "OK",IDOK,141,44,50,14*/ + DEFPUSHBUTTON "OK",IDOK,198,20,42,14 + LTEXT "Please write your text here", IDC_STATIC,7,7,184,13 + /*PUSHBUTTON "Cancel",IDCANCEL,84,44,52,14*/ +END + +118 DIALOG DISCARDABLE 0, 0, 248, 100 +STYLE DS_CENTER | WS_SYSMENU | WS_CAPTION | WS_BORDER | WS_SIZEBOX +CAPTION "Text input" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_RESULT,7,20,232,74,ES_AUTOHSCROLL|ES_AUTOVSCROLL|ES_MULTILINE|ES_WANTRETURN|WS_HSCROLL|WS_VSCROLL + /*PUSHBUTTON "OK",IDB_OK,198,20,42,14*/ + PUSHBUTTON "OK",IDB_OK,115,3,42,14 + PUSHBUTTON "CANCEL",IDCANCEL,165,3,42,14 + LTEXT "Please write your text here\n(SHIFT+RETURN to send)", IDC_STATIC,7,2,90,17 +END + +119 DIALOG DISCARDABLE 0, 0, 248, 50 +STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Text input" +FONT 8, "MS Sans Serif" +BEGIN + EDITTEXT IDC_RESULT,7,20,184,14,ES_AUTOHSCROLL|ES_PASSWORD + /*DEFPUSHBUTTON "OK",IDOK,141,44,50,14*/ + DEFPUSHBUTTON "OK",IDOK,198,20,42,14 + LTEXT "Please write your password", IDC_STATIC,7,7,184,13 + /*PUSHBUTTON "Cancel",IDCANCEL,84,44,52,14*/ +END + +120 DIALOG DISCARDABLE 0, 0, 350, 28 +//STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_VISIBLE +STYLE WS_DLGFRAME | DS_CENTER | WS_POPUP | WS_VISIBLE +#ifdef FLJ +CAPTION "PuTTY" +#else +CAPTION "KiTTY" +#endif +FONT 8, "MS Sans Serif" +BEGIN + //EDITTEXT IDC_RESULT, 7, 20, 220, 14, ES_AUTOHSCROLL|ES_READONLY + LTEXT "Empty", IDC_RESULT, 7, 10, 336, 14 +END + +//IDD_KITTYABOUT DIALOGEX 0, 0, 240, 70 +IDD_KITTYABOUT DIALOGEX 0, 0, 245, 90 +STYLE DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_SETFONT +CAPTION "About KiTTY" +FONT 8, "MS Shell Dlg", 0, 0, 0x0 +BEGIN + ICON IDI_MAINICON_0,IDC_STATIC,7,7,30,20 + LTEXT "",IDA_VERSION,45,7,198,8 + LTEXT "Copyright \251 2005-2022",IDC_STATIC,45,19,78,8 + LTEXT "9bis software - https://www.9bis.net/kitty", + IDC_WEBPAGE,45,31,145,8 + LTEXT "Contact:",IDC_STATIC,45,43,46,8 + LTEXT "kitty@9bis.com",IDC_EMAIL,77,43,83,8 + LTEXT "Message",IDC_BAN,45,55,188,8 + DEFPUSHBUTTON "OK", IDOK, 189, 69, 50, 14 + PUSHBUTTON "Licence", IDA_LICENCE, 18, 69, 50, 14 + PUSHBUTTON "Check &Update", IDA_WEB, 75, 69, 50, 14 + PUSHBUTTON "&Donate", IDA_DON, 132, 69, 50, 14 +END + +#include "../../blocnote/notepad.rc" +IDI_EDITICON ICON "../../res/edit.ico" + +#ifdef MOD_INTEGRATED_KEYGEN +#include "puttygen.rc" +#endif +#ifdef MOD_INTEGRATED_AGENT +#include "pageant.rc" +#endif + +#endif + +#include "version.rc2" diff --git a/0.77_My_PuTTY/windows/wincapi.c b/0.77_My_PuTTY/windows/wincapi.c new file mode 100644 index 0000000..de94734 --- /dev/null +++ b/0.77_My_PuTTY/windows/wincapi.c @@ -0,0 +1,94 @@ +/* + * wincapi.c: implementation of wincapi.h. + */ + +#include "putty.h" + +#if !defined NO_SECURITY + +#include "putty.h" +#include "ssh.h" + +#include "wincapi.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); +} + +#endif /* !defined NO_SECURITY */ diff --git a/0.77_My_PuTTY/windows/wincapi.h b/0.77_My_PuTTY/windows/wincapi.h new file mode 100644 index 0000000..88bdcae --- /dev/null +++ b/0.77_My_PuTTY/windows/wincapi.h @@ -0,0 +1,40 @@ +/* + * wincapi.h: Windows Crypto API functions defined in wincapi.c that + * use the crypt32 library. Also centralises the machinery for + * dynamically loading that library, and our own functions using that + * in turn. + */ + +#if !defined NO_SECURITY + +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); + +#endif + + +#ifndef CRYPTPROTECTMEMORY_CROSS_PROCESS +#define CRYPTPROTECTMEMORY_CROSS_PROCESS 0x1 +#endif +#ifndef CRYPTPROTECTMEMORY_BLOCK_SIZE +#define CRYPTPROTECTMEMORY_BLOCK_SIZE 16 +#endif + diff --git a/0.77_My_PuTTY/windows/wincfg.c b/0.77_My_PuTTY/windows/wincfg.c new file mode 100644 index 0000000..a1a103c --- /dev/null +++ b/0.77_My_PuTTY/windows/wincfg.c @@ -0,0 +1,631 @@ +/* + * wincfg.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(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + HWND *hwndp = (HWND *)ctrl->generic.context.p; + if (event == EVENT_ACTION) { + modal_about_box(*hwndp); + } +} + +static void help_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + HWND *hwndp = (HWND *)ctrl->generic.context.p; + + if (event == EVENT_ACTION) { + show_help(*hwndp); + } +} + +static void variable_pitch_handler(union control *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)); + } +} + +#ifdef MOD_PERSO +#include "kitty.h" +int get_param( const char * val ) ; +char * get_param_str( const char * val ) ; +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) +void DisableBackgroundImage( void ) ; +#endif +void CheckVersionFromWebSite( HWND hwnd ) ; +static void checkupdate_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + HWND *hwndp = (HWND *)ctrl->generic.context.p; + + if (event == EVENT_ACTION) { + CheckVersionFromWebSite(*hwndp); + } +} +#endif +#ifdef MOD_TUTTY +void dlg_control_enable(union control *ctrl, void *dlg, int enable); +struct window_behaviour_data { + union control *has_sysmenu, *window_closable, *window_minimizable, + *window_maximizable, *sysmenu_alt_space, *sysmenu_alt_only; +}; + +static void behaviour_handler(union control *ctrl, dlgparam *dlg, + void *data, int event) +{ + Conf *conf = (Conf *)data; + struct window_behaviour_data *wbd = + (struct window_behaviour_data *)ctrl->generic.context.p; + + if (ctrl == wbd->has_sysmenu) { + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dlg); + dlg_checkbox_set(ctrl, dlg, conf_get_int(conf,CONF_window_has_sysmenu)); + dlg_checkbox_set(wbd->window_closable, dlg, conf_get_int(conf,CONF_window_has_sysmenu) ? conf_get_int(conf,CONF_window_closable) : 0); + dlg_control_enable(wbd->window_closable, dlg, conf_get_int(conf,CONF_window_has_sysmenu)); + dlg_checkbox_set(wbd->window_minimizable, dlg, conf_get_int(conf,CONF_window_has_sysmenu) ? conf_get_int(conf,CONF_window_minimizable) : 0); + dlg_control_enable(wbd->window_minimizable, dlg, conf_get_int(conf,CONF_window_has_sysmenu)); + dlg_checkbox_set(wbd->window_maximizable, dlg, conf_get_int(conf,CONF_window_has_sysmenu) ? conf_get_int(conf,CONF_window_maximizable) : 0); + dlg_control_enable(wbd->window_maximizable, dlg, conf_get_int(conf,CONF_window_has_sysmenu)); + dlg_checkbox_set(wbd->sysmenu_alt_space, dlg, conf_get_int(conf,CONF_window_has_sysmenu) ? conf_get_bool(conf,CONF_alt_space) : 0); + dlg_control_enable(wbd->sysmenu_alt_space, dlg, conf_get_int(conf,CONF_window_has_sysmenu)); + dlg_checkbox_set(wbd->sysmenu_alt_only, dlg, conf_get_int(conf,CONF_window_has_sysmenu) ? conf_get_bool(conf,CONF_alt_only) : 0); + dlg_control_enable(wbd->sysmenu_alt_only, dlg, conf_get_int(conf,CONF_window_has_sysmenu)); + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_VALCHANGE) { + conf_set_int(conf,CONF_window_has_sysmenu, dlg_checkbox_get(ctrl, dlg)); + dlg_refresh(ctrl, dlg); + }; + } else if (ctrl == wbd->window_closable) { + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dlg); + dlg_checkbox_set(ctrl, dlg, conf_get_int(conf,CONF_window_has_sysmenu) ? conf_get_int(conf,CONF_window_closable) : 0); + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_VALCHANGE) + conf_set_int(conf,CONF_window_closable, dlg_checkbox_get(ctrl, dlg)); + } else if (ctrl == wbd->window_minimizable) { + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dlg); + dlg_checkbox_set(ctrl, dlg, conf_get_int(conf,CONF_window_has_sysmenu) ? conf_get_int(conf,CONF_window_minimizable) : 0); + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_VALCHANGE) + conf_set_int(conf,CONF_window_minimizable,dlg_checkbox_get(ctrl, dlg)); + } else if (ctrl == wbd->window_maximizable) { + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dlg); + dlg_checkbox_set(ctrl, dlg, conf_get_int(conf,CONF_window_has_sysmenu) ? conf_get_int(conf,CONF_window_maximizable) : 0); + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_VALCHANGE) + conf_set_int(conf,CONF_window_maximizable,dlg_checkbox_get(ctrl, dlg)); + } else if (ctrl == wbd->sysmenu_alt_space) { + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dlg); + dlg_checkbox_set(ctrl, dlg, conf_get_int(conf,CONF_window_has_sysmenu) ? conf_get_bool(conf,CONF_alt_space) : 0); + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_VALCHANGE) + conf_set_int(conf,CONF_alt_space, dlg_checkbox_get(ctrl, dlg)); + } else if (ctrl == wbd->sysmenu_alt_only) { + if (event == EVENT_REFRESH) { + dlg_update_start(ctrl, dlg); + dlg_checkbox_set(ctrl, dlg, conf_get_int(conf,CONF_window_has_sysmenu) ? conf_get_bool(conf,CONF_alt_only) : 0); + dlg_update_done(ctrl, dlg); + } else if (event == EVENT_VALCHANGE) + conf_set_bool(conf,CONF_alt_only,dlg_checkbox_get(ctrl, dlg)); + } +} +#endif + +void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, + bool midsession, int protocol) +{ +#ifdef MOD_TUTTY + struct window_behaviour_data *wbd; +#endif + const struct BackendVtable *backvt; + bool resize_forbidden = false; + struct controlset *s; + union control *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->generic.column = 0; +#ifdef MOD_PERSO + if (has_help) { + c = ctrl_pushbutton(s, "Help", 'h', HELPCTX(no_help), + help_handler, P(hwndp)); + if( GetConfigBoxHeight() > 7 ) c->generic.column = 0 ; else + c->generic.column = 1; + } +#ifndef FLJ + if( !get_param("PUTTY") ) { + c = ctrl_pushbutton(s, "Check Update", NO_SHORTCUT, HELPCTX(no_help), + checkupdate_handler, P(hwndp)); + if( GetConfigBoxHeight() > 7 ) c->generic.column = 0 ; else + c->generic.column = 2; + } +#endif +#else + if (has_help) { + c = ctrl_pushbutton(s, "Help", 'h', HELPCTX(no_help), + help_handler, P(hwndp)); + c->generic.column = 1; + } +#endif + } + + /* + * 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->generic.type == CTRL_CHECKBOX && + c->generic.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(union control *)); + 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->generic.type == CTRL_RADIO && + c->generic.context.i == CONF_beep) { + assert(c->generic.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), NULL); + + /* + * 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), NULL); + + /* + * 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->generic.type == CTRL_RADIO && + c->generic.context.i == CONF_vtmode) { + assert(c->generic.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), NULL); + /* + * 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(union control *)); + 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), NULL); + } + + /* + * 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)); +#ifdef MOD_TUTTY + wbd = (struct window_behaviour_data *)ctrl_alloc(b, + sizeof(struct window_behaviour_data)); + memset(wbd, 0, sizeof(*wbd)); + wbd->has_sysmenu = ctrl_checkbox(s, "Window has system menu (in upper left corner)", NO_SHORTCUT, + HELPCTX(no_help), + behaviour_handler, P(wbd)); + wbd->window_closable = ctrl_checkbox(s, "Window has Close button", NO_SHORTCUT, + HELPCTX(no_help), + behaviour_handler, P(wbd)); + wbd->window_minimizable = ctrl_checkbox(s, "Window has Minimize button", NO_SHORTCUT, + HELPCTX(no_help), + behaviour_handler, P(wbd)); + wbd->window_maximizable = ctrl_checkbox(s, "Window has Maximize button", NO_SHORTCUT, + HELPCTX(no_help), + behaviour_handler, P(wbd)); + wbd->sysmenu_alt_space = ctrl_checkbox(s, "System menu appears on ALT-Space", 'y', + HELPCTX(no_help), + behaviour_handler, P(wbd)); + wbd->sysmenu_alt_only = ctrl_checkbox(s, "System menu appears on ALT alone", 'l', + HELPCTX(no_help), + behaviour_handler, P(wbd)); +#else + 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)); +#endif + ctrl_checkbox(s, "Ensure window is always on top", 'e', + HELPCTX(behaviour_alwaysontop), + conf_checkbox_handler, I(CONF_alwaysontop)); +#ifdef MOD_PERSO + if( !get_param("PUTTY") ) { + ctrl_checkbox(s, "Send to tray on startup", NO_SHORTCUT, + HELPCTX(no_help), + conf_checkbox_handler, I(CONF_sendtotray)); + ctrl_checkbox(s, "Maximize on startup", NO_SHORTCUT, + HELPCTX(no_help), + conf_checkbox_handler, I(CONF_maximize)); + ctrl_checkbox(s, "Full screen on startup", NO_SHORTCUT, + HELPCTX(no_help), + conf_checkbox_handler, I(CONF_fullscreen)); + ctrl_checkbox(s, "Save position and size on exit", NO_SHORTCUT, + HELPCTX(no_help), + conf_checkbox_handler, I(CONF_saveonexit)); + if (!midsession && GetCtrlTabFlag() ) + ctrl_checkbox(s, "Switch PuTTY windows with Ctrl + TAB", NO_SHORTCUT, + HELPCTX(no_help), + conf_checkbox_handler, + I(CONF_ctrl_tab_switch)); + } +#endif + ctrl_checkbox(s, "Full screen on Alt-Enter", 'f', + HELPCTX(behaviour_altenter), + conf_checkbox_handler, + I(CONF_fullscreenonaltenter)); + +#ifdef MOD_HYPERLINK + if( !GetPuttyFlag() && GetHyperlinkFlag() ) { + /* + * HACK: PuttyTray / Nutty + * Hyperlink stuff: The Window/Hyperlinks panel. + */ + ctrl_settitle(b, "Window/Hyperlinks", "Options controlling behaviour of hyperlinks"); + s = ctrl_getset(b, "Window/Hyperlinks", "general", "General options for hyperlinks"); + + ctrl_radiobuttons(s, "Underline hyperlinks:", 'u', 1, + HELPCTX(no_help), + conf_radiobutton_handler, + I(CONF_url_underline), + "Always", I(URLHACK_UNDERLINE_ALWAYS), + "When hovered upon", I(URLHACK_UNDERLINE_HOVER), + "Never", I(URLHACK_UNDERLINE_NEVER), + NULL); + + ctrl_checkbox(s, "Use ctrl+click to launch hyperlinks", 'l', + HELPCTX(no_help), + conf_checkbox_handler, I(CONF_url_ctrl_click)); + + s = ctrl_getset(b, "Window/Hyperlinks", "browser", "Browser application"); + + ctrl_checkbox(s, "Use the default browser", 'b', + HELPCTX(no_help), + conf_checkbox_handler, I(CONF_url_defbrowser)); + + ctrl_filesel(s, "or specify an application to open hyperlinks with:", 's', + "Application (*.exe)\0*.exe\0All files (*.*)\0*.*\0\0", TRUE, + "Select executable to open hyperlinks with", HELPCTX(no_help), + conf_filesel_handler, I(CONF_url_browser)); + + s = ctrl_getset(b, "Window/Hyperlinks", "regexp", "Regular expression"); + + ctrl_checkbox(s, "Use the default regular expression", 'r', + HELPCTX(no_help), + conf_checkbox_handler, I(CONF_url_defregex)); + + ctrl_editbox(s, "or specify your own:", NO_SHORTCUT, 100, + HELPCTX(no_help), + conf_editbox_handler, I(CONF_url_regex), + I(1)); + + ctrl_text(s, "The single white space will be cropped in front of the link, if exists.", + HELPCTX(no_help)); + } +#endif + + /* + * Windows supports a local-command proxy. This also means we + * must adjust the text on the `Telnet command' control. + */ + if (!midsession) { + int i; + s = ctrl_getset(b, "Connection/Proxy", "basics", NULL); + for (i = 0; i < s->ncontrols; i++) { + c = s->ctrls[i]; + if (c->generic.type == CTRL_RADIO && + c->generic.context.i == CONF_proxy_type) { + assert(c->generic.handler == conf_radiobutton_handler); + c->radio.nbuttons++; + c->radio.buttons = + sresize(c->radio.buttons, c->radio.nbuttons, char *); + c->radio.buttons[c->radio.nbuttons-1] = + dupstr("Local"); + c->radio.buttondata = + sresize(c->radio.buttondata, c->radio.nbuttons, intorptr); + c->radio.buttondata[c->radio.nbuttons-1] = I(PROXY_CMD); + break; + } + } + + for (i = 0; i < s->ncontrols; i++) { + c = s->ctrls[i]; + if (c->generic.type == CTRL_EDITBOX && + c->generic.context.i == CONF_proxy_telnet_command) { + assert(c->generic.handler == conf_editbox_handler); + sfree(c->generic.label); + c->generic.label = dupstr("Telnet command, or local" + " proxy command"); + break; + } + } + } + + /* + * $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)); + } +#ifdef MOD_PERSO + if( !GetPuttyFlag() && 0 ) { + + ctrl_settitle(b, get_param_str("NAME"), "Specific program options"); + s = ctrl_getset(b, get_param_str("NAME"), "general", "PATH definitions"); + + ctrl_filesel(s, "Full path to scp", 's', + "Application (*.exe)\0*.exe\0All files (*.*)\0*.*\0\0", TRUE, + "Select executable to scp/sftp transfer", HELPCTX(no_help), + conf_filesel_handler, I(CONF_url_browser)); + } + +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + /* Le patch Background image ne marche plus bien sur la version PuTTY 0.61 + - il est en erreur lorsqu'on passe par la config box + - il est ok lorsqu'on démarrer par -load ou par duplicate session + On le désactive dans la config box + */ + // DisableBackgroundImage() ; + /* Un fix a été appliqué dans CONFIG.C. Lorsqu'on clique sur Open ou qu'on double-clique on charge la session par -load. + */ +#endif +#endif +} diff --git a/0.77_My_PuTTY/windows/wincliloop.c b/0.77_My_PuTTY/windows/wincliloop.c new file mode 100644 index 0000000..572f3d8 --- /dev/null +++ b/0.77_My_PuTTY/windows/wincliloop.c @@ -0,0 +1,136 @@ +#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) { + int nhandles; + HANDLE *handles; + 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 */ + } + + handles = handle_get_events(&nhandles); + size_t winselcli_index = -(size_t)1; + size_t extra_base = nhandles; + if (winselcli_event != INVALID_HANDLE_VALUE) { + winselcli_index = extra_base++; + handles = sresize(handles, extra_base, HANDLE); + handles[winselcli_index] = winselcli_event; + } + size_t total_handles = extra_base + n_extra_handles; + handles = sresize(handles, total_handles, HANDLE); + for (size_t i = 0; i < n_extra_handles; i++) + handles[extra_base + i] = extra_handles[i]; + + n = WaitForMultipleObjects(total_handles, handles, false, ticks); + + size_t extra_handle_index = n_extra_handles; + + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { + handle_got_event(handles[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(); + } + + sfree(handles); + + 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/0.77_My_PuTTY/windows/wincons.c b/0.77_My_PuTTY/windows/wincons.c new file mode 100644 index 0000000..876550c --- /dev/null +++ b/0.77_My_PuTTY/windows/wincons.c @@ -0,0 +1,472 @@ +/* + * wincons.c - various interactive-prompt routines shared between + * the Windows console PuTTY tools + */ + +#include +#include + +#include "putty.h" +#include "storage.h" +#include "ssh.h" + +#ifdef MOD_PERSO +int GetAutoStoreSSHKeyFlag(void) ; +#endif + +#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); +} + +int console_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **fingerprints, + void (*callback)(void *ctx, int result), void *ctx) +{ + int ret; + HANDLE hin; + DWORD savemode, i; + + const char *common_fmt, *intro, *prompt; + + char line[32]; + + /* + * Verify the key against the registry. + */ + ret = verify_host_key(host, port, keytype, keystr); + + if (ret == 0) /* success - key matched OK */ + return 1; + + if (ret == 2) { /* key was different */ + common_fmt = hk_wrongmsg_common_fmt; + intro = hk_wrongmsg_interactive_intro; + prompt = hk_wrongmsg_interactive_prompt; + } else { /* key was absent */ + common_fmt = hk_absentmsg_common_fmt; + intro = hk_absentmsg_interactive_intro; + prompt = hk_absentmsg_interactive_prompt; + } + + FingerprintType fptype_default = + ssh2_pick_default_fingerprint(fingerprints); + + fprintf(stderr, common_fmt, keytype, fingerprints[fptype_default]); + #ifdef MOD_PERSO + if( !GetAutoStoreSSHKeyFlag() ) +#endif + if (console_batch_mode) { + fputs(console_abandoned_msg, stderr); + return 0; + } + fputs(intro, stderr); + fflush(stderr); + while (true) { + fputs(prompt, stderr); + fflush(stderr); + + line[0] = '\0'; /* fail safe if ReadFile returns no data */ + +#ifdef MOD_PERSO + if( GetAutoStoreSSHKeyFlag() ) { + fprintf( stderr, "\nAutostore key is on\n" ); + strcpy(line,"y\r\n"); + } else { +#endif + + hin = GetStdHandle(STD_INPUT_HANDLE); + GetConsoleMode(hin, &savemode); + SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | + ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); + ReadFile(hin, line, sizeof(line) - 1, &i, NULL); + SetConsoleMode(hin, savemode); + +#ifdef MOD_PERSO + } +#endif + + if (line[0] == 'i' || line[0] == 'I') { + fprintf(stderr, "Full public key:\n%s\n", keydisp); + if (fingerprints[SSH_FPTYPE_SHA256]) + fprintf(stderr, "SHA256 key fingerprint:\n%s\n", + fingerprints[SSH_FPTYPE_SHA256]); + if (fingerprints[SSH_FPTYPE_MD5]) + fprintf(stderr, "MD5 key fingerprint:\n%s\n", + fingerprints[SSH_FPTYPE_MD5]); + } 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(host, port, keytype, keystr); + return 1; + } else { + fputs(console_abandoned_msg, stderr); + return 0; + } +} + +int console_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + HANDLE hin; + DWORD savemode, i; + + char line[32]; + + fprintf(stderr, weakcrypto_msg_common_fmt, algtype, algname); + + if (console_batch_mode) { + fputs(console_abandoned_msg, stderr); + return 0; + } + + fputs(console_continue_prompt, stderr); + fflush(stderr); + + hin = GetStdHandle(STD_INPUT_HANDLE); + GetConsoleMode(hin, &savemode); + SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | + ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); + ReadFile(hin, line, sizeof(line) - 1, &i, NULL); + SetConsoleMode(hin, savemode); + + if (line[0] == 'y' || line[0] == 'Y') { + return 1; + } else { + fputs(console_abandoned_msg, stderr); + return 0; + } +} + +int console_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) +{ + HANDLE hin; + DWORD savemode, i; + + char line[32]; + + fprintf(stderr, weakhk_msg_common_fmt, algname, betteralgs); + + if (console_batch_mode) { + fputs(console_abandoned_msg, stderr); + return 0; + } + + fputs(console_continue_prompt, stderr); + fflush(stderr); + + hin = GetStdHandle(STD_INPUT_HANDLE); + GetConsoleMode(hin, &savemode); + SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | + ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); + ReadFile(hin, line, sizeof(line) - 1, &i, NULL); + SetConsoleMode(hin, savemode); + + if (line[0] == 'y' || line[0] == 'Y') { + return 1; + } else { + fputs(console_abandoned_msg, stderr); + return 0; + } +} + +bool is_interactive(void) +{ + return is_console_handle(GetStdHandle(STD_INPUT_HANDLE)); +} + +bool console_antispoof_prompt = true; +bool console_set_trust_status(Seat *seat, bool trusted) +{ + if (console_batch_mode || !is_interactive() || !console_antispoof_prompt) { + /* + * 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. + * + * If standard input isn't connected to a terminal, likewise, + * because even if the server did send a spoof authentication + * prompt, the user couldn't respond to it via the terminal + * anyway. + * + * We also vacuously return success if the user has purposely + * disabled the antispoof prompt. + */ + return true; + } + + return false; +} + +/* + * 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) +{ + HANDLE hin; + DWORD savemode, i; + + 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]; + + 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); + + hin = GetStdHandle(STD_INPUT_HANDLE); + GetConsoleMode(hin, &savemode); + SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT | + ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)); + ReadFile(hin, line, sizeof(line) - 1, &i, NULL); + SetConsoleMode(hin, savemode); + + if (line[0] == 'y' || line[0] == 'Y') + return 2; + else if (line[0] == 'n' || line[0] == 'N') + return 1; + else + return 0; +} + +/* + * Warn about the obsolescent key file format. + * + * Uniquely among these functions, this one does _not_ expect a + * frontend handle. This means that if PuTTY is ported to a + * platform which requires frontend handles, this function will be + * an anomaly. Fortunately, the problem it addresses will not have + * been present on that platform, so it can plausibly be + * implemented as an empty function. + */ +void old_keyfile_warning(void) +{ + static const char message[] = + "You are loading an SSH-2 private key which has an\n" + "old version of the file format. This means your key\n" + "file is not fully tamperproof. Future versions of\n" + "PuTTY may stop supporting this private key format,\n" + "so we recommend you convert your key to the new\n" + "format.\n" + "\n" + "Once the key is loaded into PuTTYgen, you can perform\n" + "this conversion simply by saving it again.\n"; + + 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); +} + +static void console_write(HANDLE hout, ptrlen data) +{ + DWORD dummy; + WriteFile(hout, data.ptr, data.len, &dummy, NULL); +} + +int console_get_userpass_input(prompts_t *p) +{ + HANDLE hin = INVALID_HANDLE_VALUE, hout = INVALID_HANDLE_VALUE; + 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) + return 0; + hin = GetStdHandle(STD_INPUT_HANDLE); + if (hin == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Cannot get standard input handle\n"); + cleanup_exit(1); + } + } + + /* + * And if we have anything to print, we need standard output. + */ + if ((p->name_reqd && p->name) || p->instruction || p->n_prompts) { + hout = GetStdHandle(STD_OUTPUT_HANDLE); + if (hout == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Cannot get standard output handle\n"); + cleanup_exit(1); + } + } + + /* + * 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(hout, plname); + if (!ptrlen_endswith(plname, PTRLEN_LITERAL("\n"), NULL)) + console_write(hout, PTRLEN_LITERAL("\n")); + } + /* ...but we always print any `instruction'. */ + if (p->instruction) { + ptrlen plinst = ptrlen_from_asciz(p->instruction); + console_write(hout, plinst); + if (!ptrlen_endswith(plinst, PTRLEN_LITERAL("\n"), NULL)) + console_write(hout, PTRLEN_LITERAL("\n")); + } + + for (curr_prompt = 0; curr_prompt < p->n_prompts; curr_prompt++) { + + DWORD savemode, newmode; + prompt_t *pr = p->prompts[curr_prompt]; + + GetConsoleMode(hin, &savemode); + newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT; + if (!pr->echo) + newmode &= ~ENABLE_ECHO_INPUT; + else + newmode |= ENABLE_ECHO_INPUT; + SetConsoleMode(hin, newmode); + + console_write(hout, ptrlen_from_asciz(pr->prompt)); + + bool failed = false; + while (1) { + /* + * Amount of data to try to read from the console in one + * go. This isn't completely arbitrary: a user reported + * that trying to read more than 31366 bytes at a time + * would fail with ERROR_NOT_ENOUGH_MEMORY on Windows 7, + * and Ruby's Win32 support module has evidence of a + * similar workaround: + * + * https://github.com/ruby/ruby/blob/0aa5195262d4193d3accf3e6b9bad236238b816b/win32/win32.c#L6842 + * + * To keep things simple, I stick with a nice round power + * of 2 rather than trying to go to the very limit of that + * bug. (We're typically reading user passphrases and the + * like here, so even this much is overkill really.) + */ + DWORD toread = 16384; + + size_t prev_result_len = pr->result->len; + void *ptr = strbuf_append(pr->result, toread); + + DWORD ret = 0; + + if (!ReadFile(hin, ptr, toread, &ret, NULL) || ret == 0) { + failed = true; + break; + } + + strbuf_shrink_to(pr->result, prev_result_len + ret); + if (strbuf_chomp(pr->result, '\n')) { + strbuf_chomp(pr->result, '\r'); + break; + } + } + + SetConsoleMode(hin, savemode); + + if (!pr->echo) + console_write(hout, PTRLEN_LITERAL("\r\n")); + + if (failed) { + return 0; /* failure due to read error */ + } + + } + + return 1; /* success */ +} diff --git a/0.77_My_PuTTY/windows/winctrls.c b/0.77_My_PuTTY/windows/winctrls.c new file mode 100644 index 0000000..92dd108 --- /dev/null +++ b/0.77_My_PuTTY/windows/winctrls.c @@ -0,0 +1,2784 @@ +/* + * winctrls.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 + +#ifdef MOD_PERSO +#include "kitty.h" +#endif +#ifdef MOD_ZMODEM +#include +#endif + +#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, + char *wclass, int wstyle, int exstyle, 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, 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, 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, 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), + WS_EX_CLIENTEDGE, "", editid); + cp->ypos += EDITHEIGHT + GAPBETWEEN; +} + +/* + * A static line, followed by a full-width combo box. + */ +void combobox(struct ctlpos *cp, 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 { char *text; int id; }; + +static void radioline_common(struct ctlpos *cp, 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++) { + 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, char *text, int id, int nacross, ...) +{ + va_list ap; + struct radio *buttons; + int i, nbuttons; + + va_start(ap, nacross); + nbuttons = 0; + while (1) { + char *btext = va_arg(ap, 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, 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) { + char *btext = va_arg(ap, 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, 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, char *text, int id, ...) +{ + va_list ap; + struct radio *buttons; + int i, nbuttons; + + va_start(ap, id); + nbuttons = 0; + while (1) { + char *btext = va_arg(ap, 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, 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, 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, char *text, int *lines) +{ + HDC hdc = GetDC(hwnd); + int width, nlines, j; + INT *pwidths, nfit; + SIZE size; + char *ret, *p, *q; + RECT r; + HFONT oldfont, newfont; + + ret = snewn(1+strlen(text), char); + p = text; + q = ret; + 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. + */ + strcpy(q, 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; + } + } + + strncpy(q, p, nfit); + q[nfit] = '\n'; + q += nfit+1; + + p += nfit; + while (*p && isspace((unsigned char)*p)) + p++; + + nlines++; + } + + SelectObject(hdc, oldfont); + ReleaseDC(cp->hwnd, hdc); + + if (lines) *lines = nlines; + + sfree(pwidths); + + return ret; +} + +/* + * A single standalone static text control. + */ +void statictext(struct ctlpos *cp, 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, char *stext, int sid, + 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, 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, char *stext, int sid, + char *btext1, int bid1, 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, 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, char *stext, + int sid, int eid, int percentedit) +{ + staticedit_internal(cp, stext, sid, eid, percentedit, 0); +} + +void staticpassedit(struct ctlpos *cp, 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, 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, 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, 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, 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, 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, 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, char *stext, int sid, + int eid, 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, + 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) +{ + char *ret; + char const *p; + char *q; + + if (!text) + return NULL; /* sfree won't choke on this */ + + ret = snewn(2*strlen(text)+1, char); /* size potentially doubles! */ + shortcut = tolower((unsigned char)shortcut); + + p = text; + q = ret; + while (*p) { + if (shortcut != NO_SHORTCUT && + tolower((unsigned char)*p) == shortcut) { + *q++ = '&'; + shortcut = NO_SHORTCUT; /* stop it happening twice */ + } else if (*p == '&') { + *q++ = '&'; + } + *q++ = *p++; + } + *q = '\0'; + return ret; +} + +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) +{ + union control *a = (union control *)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, union control *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]; + union control *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; + + /* 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++) { + union control *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->generic.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->generic.type == CTRL_TABDELAY) { + int i; + + assert(!ctrl->generic.tabdelay); + 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->generic.column); + colspan = COLUMN_SPAN(ctrl->generic.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->generic.tabdelay) { + 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->generic.type) { + case CTRL_TEXT: { + char *wrapped, *escaped; + int lines; + num_ids = 1; + wrapped = staticwrap(&pos, cp->hwnd, + ctrl->generic.label, &lines); + escaped = shortcut_escape(wrapped, NO_SHORTCUT); + statictext(&pos, escaped, lines, base_id); + sfree(escaped); + sfree(wrapped); + break; + } + case CTRL_EDITBOX: + num_ids = 2; /* static, edit */ + escaped = shortcut_escape(ctrl->editbox.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, 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->radio.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(buttons[i].text); + } + sfree(buttons); + sfree(escaped); + break; + } + case CTRL_CHECKBOX: + num_ids = 1; + escaped = shortcut_escape(ctrl->checkbox.label, + ctrl->checkbox.shortcut); + shortcuts[nshortcuts++] = ctrl->checkbox.shortcut; + checkbox(&pos, escaped, base_id); + sfree(escaped); + break; + case CTRL_BUTTON: + escaped = shortcut_escape(ctrl->button.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->listbox.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: + num_ids = 3; + escaped = shortcut_escape(ctrl->fileselect.label, + ctrl->fileselect.shortcut); + shortcuts[nshortcuts++] = ctrl->fileselect.shortcut; + editbutton(&pos, escaped, base_id, base_id+1, + "Bro&wse...", base_id+2); +#if (defined MOD_PERSO) && (!defined MOD_ZMODEM) + if(GetZModemFlag()) { shortcuts[nshortcuts++] = 'w'; } +#endif + shortcuts[nshortcuts++] = 'w'; + sfree(escaped); + break; + case CTRL_FONTSELECT: + num_ids = 3; + escaped = shortcut_escape(ctrl->fontselect.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("", false, 0, 0); + sfree(escaped); + break; +#ifdef MOD_ZMODEM + case CTRL_DIRECTORYSELECT: + num_ids = 3; + escaped = shortcut_escape(ctrl->fileselect.label, + ctrl->fileselect.shortcut); + shortcuts[nshortcuts++] = ctrl->fileselect.shortcut; + editbutton(&pos, escaped, base_id, base_id+1, + "Bro&wse...", base_id+2); + sfree(escaped); + break; +#endif + 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->generic.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. + */ + struct winctrl *c2 = winctrl_findbyctrl( + wc, ctrl->generic.align_next_to); + HWND win1 = GetDlgItem(pos.hwnd, c->align_id); + HWND win2 = GetDlgItem(pos.hwnd, c2->align_id); + RECT rect1, rect2; + if (win1 && win2 && + GetWindowRect(win1, &rect1) && + GetWindowRect(win2, &rect2)) { + LONG top = (rect1.top < rect2.top ? rect1.top : rect2.top); + LONG bottom = (rect1.bottom > rect2.bottom ? + rect1.bottom : rect2.bottom); + move_windows(pos.hwnd, c->base_id, c->num_ids, + (top + bottom - rect1.top - rect1.bottom)/2); + move_windows(pos.hwnd, c2->base_id, c2->num_ids, + (top + bottom - rect2.top - rect2.bottom)/2); + } + } + } 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(union control *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; + } +} + +union control *dlg_last_focused(union control *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; + union control *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->generic.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->generic.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->generic.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->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + } else if (HIWORD(wParam) == CBN_EDITCHANGE) { + ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + } else if (HIWORD(wParam) == CBN_KILLFOCUS) { + ctrl->generic.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->generic.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->generic.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->generic.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->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + ret = pret & 1; + } else { + if (msg == WM_COMMAND && HIWORD(wParam) == LBN_DBLCLK) { + SetCapture(dp->hwnd); + ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION); + } else if (msg == WM_COMMAND && HIWORD(wParam) == LBN_SELCHANGE) { + ctrl->generic.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->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + if (id == 2 && + (msg == WM_COMMAND && + (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DOUBLECLICKED))) { + OPENFILENAME of; + char filename[FILENAME_MAX]; + + memset(&of, 0, sizeof(of)); + of.hwndOwner = dp->hwnd; + if (ctrl->fileselect.filter) + of.lpstrFilter = ctrl->fileselect.filter; + else + of.lpstrFilter = "All Files (*.*)\0*\0\0\0"; + of.lpstrCustomFilter = NULL; + of.nFilterIndex = 1; + of.lpstrFile = filename; + GetDlgItemText(dp->hwnd, c->base_id+1, filename, lenof(filename)); + filename[lenof(filename)-1] = '\0'; + of.nMaxFile = lenof(filename); + of.lpstrFileTitle = NULL; + of.lpstrTitle = ctrl->fileselect.title; + of.Flags = 0; + if (request_file(NULL, &of, false, ctrl->fileselect.for_writing)) { + SetDlgItemText(dp->hwnd, c->base_id + 1, filename); + ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + } + } + 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->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + } + } + break; +#ifdef MOD_ZMODEM + case CTRL_DIRECTORYSELECT: + 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->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + if (id == 2 && + (msg == WM_COMMAND && + (HIWORD(wParam) == BN_CLICKED || + HIWORD(wParam) == BN_DOUBLECLICKED))) { + BROWSEINFO bi; + char filename[FILENAME_MAX]; + LPITEMIDLIST folder; + + memset(&bi, 0, sizeof(bi)); + bi.hwndOwner = dp->hwnd; + bi.pszDisplayName = filename; + bi.lpszTitle = ctrl->fileselect.title; + bi.ulFlags = BIF_RETURNONLYFSDIRS; + + CoInitialize(NULL); + if( (folder=SHBrowseForFolder(&bi))!=NULL ) { + LPMALLOC shmalloc; + if (SHGetPathFromIDList(folder, filename)) { + SetDlgItemText(dp->hwnd, c->base_id + 1, filename); + ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE); + } + SHGetMalloc(&shmalloc); + (*shmalloc->lpVtbl->Free)(shmalloc, folder); + } + } + break; +#endif + } + + /* + * 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->generic.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.p' is a context string. + */ + if (!c->ctrl || !c->ctrl->generic.helpctx.p) + return false; /* no help available for this ctrl */ + + launch_help(hwnd, c->ctrl->generic.helpctx.p); + 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, union control *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(union control *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(union control *ctrl, dlgparam *dp, int whichbutton) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.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(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + int i; + assert(c && c->ctrl->generic.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(union control *ctrl, dlgparam *dp, bool checked) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); + CheckDlgButton(dp->hwnd, c->base_id, checked); +} + +bool dlg_checkbox_get(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_CHECKBOX); + return 0 != IsDlgButtonChecked(dp->hwnd, c->base_id); +} + +void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + SetDlgItemText(dp->hwnd, c->base_id+1, text); +} + +char *dlg_editbox_get(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_EDITBOX); + return GetDlgItemText_alloc(dp->hwnd, c->base_id+1); +} + +/* The `listbox' functions can also apply to combo boxes. */ +void dlg_listbox_clear(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + int msg; + assert(c && + (c->ctrl->generic.type == CTRL_LISTBOX || + (c->ctrl->generic.type == CTRL_EDITBOX && + c->ctrl->editbox.has_list))); + msg = (c->ctrl->generic.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(union control *ctrl, dlgparam *dp, int index) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + int msg; + assert(c && + (c->ctrl->generic.type == CTRL_LISTBOX || + (c->ctrl->generic.type == CTRL_EDITBOX && + c->ctrl->editbox.has_list))); + msg = (c->ctrl->generic.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(union control *ctrl, dlgparam *dp, char const *text) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + int msg; + assert(c && + (c->ctrl->generic.type == CTRL_LISTBOX || + (c->ctrl->generic.type == CTRL_EDITBOX && + c->ctrl->editbox.has_list))); + msg = (c->ctrl->generic.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(union control *ctrl, dlgparam *dp, + char const *text, int id) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + int msg, msg2, index; + assert(c && + (c->ctrl->generic.type == CTRL_LISTBOX || + (c->ctrl->generic.type == CTRL_EDITBOX && + c->ctrl->editbox.has_list))); + msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ? + LB_ADDSTRING : CB_ADDSTRING); + msg2 = (c->ctrl->generic.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(union control *ctrl, dlgparam *dp, int index) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + int msg; + assert(c && c->ctrl->generic.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(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + int msg, ret; + assert(c && c->ctrl->generic.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; +} + +#ifdef MOD_PERSO +int dlg_listbox_get(union control *ctrl, void *dlg, int index, char * pstr, int maxcount) { + struct dlgparam *dp = (struct dlgparam *)dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + return + GetDlgItemText( dp->hwnd, c->base_id+1, pstr, maxcount ); + } + +int dlg_listbox_gettext(union control *ctrl, void *dlg, int index, char * pstr, int maxcount) { + struct dlgparam *dp = (struct dlgparam *)dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + return + //GetDlgItemText( dp->hwnd, c->base_id+1, pstr, maxcount ); + SendMessage(GetDlgItem( dp->hwnd, c->base_id+1 ), LB_GETTEXT, index, (LPARAM)pstr); + //SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETTEXT, (LPARAM)pstr, 1024); + } +#endif + +bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.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(union control *ctrl, dlgparam *dp, int index) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + int msg; + assert(c && c->ctrl->generic.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(union control *ctrl, dlgparam *dp, char const *text) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_TEXT); + SetDlgItemText(dp->hwnd, c->base_id, text); +} + +void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + char *escaped = NULL; + int id = -1; + + assert(c); + switch (c->ctrl->generic.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); + id = c->base_id; + 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(union control *ctrl, dlgparam *dp, Filename *fn) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_FILESELECT); + SetDlgItemText(dp->hwnd, c->base_id+1, fn->path); +} + +Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + char *tmp; + Filename *ret; + assert(c && c->ctrl->generic.type == CTRL_FILESELECT); + tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1); + ret = filename_from_str(tmp); + sfree(tmp); + return ret; +} + +void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs) +{ + char *buf, *boldstr; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.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(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.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(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + if (c && c->ctrl->generic.type == CTRL_LISTBOX) { + SendDlgItemMessage(dp->hwnd, c->base_id+1, WM_SETREDRAW, false, 0); + } +} + +void dlg_update_done(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + if (c && c->ctrl->generic.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(union control *ctrl, dlgparam *dp) +{ + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + int id; + HWND ctl; + if (!c) + return; + switch (ctrl->generic.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; +#ifdef MOD_ZMODEM + case CTRL_DIRECTORYSELECT: id = c->base_id + 1; break; +#endif + 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(union control *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->generic.handler != NULL) + c->ctrl->generic.handler(c->ctrl, dp, + dp->data, EVENT_REFRESH); + } + } + } else { + /* + * Send EVENT_REFRESH to a specific control. + */ + if (ctrl->generic.handler != NULL) + ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH); + } +} + +void dlg_coloursel_start(union control *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(union control *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); +} + +#ifdef MOD_ZMODEM +void dlg_directorysel_set(union control *ctrl, void *dlg, Filename fn) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_DIRECTORYSELECT); + SetDlgItemText(dp->hwnd, c->base_id+1, fn.path); +} + +void dlg_directorysel_get(union control *ctrl, void *dlg, Filename *fn) +{ + struct dlgparam *dp = (struct dlgparam *)dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + assert(c && c->ctrl->generic.type == CTRL_DIRECTORYSELECT); + GetDlgItemText(dp->hwnd, c->base_id+1, fn->path, lenof(fn->path)); + fn->path[lenof(fn->path)-1] = '\0'; +} +#endif +#ifdef MOD_TUTTY +void dlg_control_hide(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *) dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + + if (dp && ctrl) { + HWND ctl = GetDlgItem(dp->hwnd, c->base_id + 1); + if (ctl) + ShowWindow(ctl, FALSE); + }; +}; + +void dlg_control_show(union control *ctrl, void *dlg) +{ + struct dlgparam *dp = (struct dlgparam *) dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + + if (dp && ctrl) { + HWND ctl = GetDlgItem(dp->hwnd, c->base_id + 1); + if (ctl) + ShowWindow(ctl, TRUE); + }; +}; + +void dlg_control_enable(union control *ctrl, void *dlg, int enable) +{ + struct dlgparam *dp = (struct dlgparam *) dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + HWND ctl; + int i, max; + + if (dp && ctrl) { + switch (ctrl->generic.type) { + case CTRL_RADIO: + max = ctrl->radio.nbuttons + 1; + break; +/* case CTRL_SPECIALEDIT: + max = 3; + break;*/ + default: + max = 1; + }; + for (i = 0; i < max; i++) { + ctl = GetDlgItem(dp->hwnd, c->base_id + i); + if (ctl) + EnableWindow(ctl, enable); + }; + }; +}; + +void dlg_specialedit_switch(union control *ctrl, void *dlg, int which) +{ + struct dlgparam *dp = (struct dlgparam *) dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + + HWND ctrl1 = GetDlgItem(dp->hwnd, c->base_id + 1); + HWND ctrl2 = GetDlgItem(dp->hwnd, c->base_id + 2); + + if (which == 0) { + ShowWindow(ctrl2, SW_HIDE); + ShowWindow(ctrl1, SW_SHOW); + } else if (which == 1) { + ShowWindow(ctrl1, SW_HIDE); + ShowWindow(ctrl2, SW_SHOW); + }; +}; + +void dlg_setcontroltext(union control *ctrl, void *dlg, char *stext) +{ + struct dlgparam *dp = (struct dlgparam *) dlg; + struct winctrl *c = dlg_findbyctrl(dp, ctrl); + HWND ctl = GetDlgItem(dp->hwnd, c->base_id); + + SetWindowText(ctl, stext); +}; + +/* A simple yes/no question box. Returns TRUE if yes, otherwise FALSE. */ +int dlg_yesnobox(void *dlg, const char *msg) +{ + struct dlgparam *dp = (struct dlgparam *) dlg; + int ret = MessageBox(dp->hwnd, msg, NULL, + MB_YESNO | MB_ICONQUESTION); + return ret == IDYES ? TRUE : FALSE; +}; +#endif diff --git a/0.77_My_PuTTY/windows/windefs.c b/0.77_My_PuTTY/windows/windefs.c new file mode 100644 index 0000000..2a91bc2 --- /dev/null +++ b/0.77_My_PuTTY/windows/windefs.c @@ -0,0 +1,44 @@ +/* + * windefs.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("", false, 0, 0); +} + +Filename *platform_default_filename(const char *name) +{ + if (!strcmp(name, "LogFileName")) +#ifdef MOD_PERSO + return filename_from_str("kitty.log"); +#else + return filename_from_str("putty.log"); +#endif + 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/0.77_My_PuTTY/windows/windlg.c b/0.77_My_PuTTY/windows/windlg.c new file mode 100644 index 0000000..bbf9886 --- /dev/null +++ b/0.77_My_PuTTY/windows/windlg.c @@ -0,0 +1,1532 @@ +/* + * windlg.c - dialogs for PuTTY(tel), including the configuration dialog. + */ + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "win_res.h" +#include "winseat.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 + +/* + * These are the various bits of data required to handle the + * portable-dialog stuff in the config box. Having them at file + * scope in here isn't too bad a place to put them; if we were ever + * to need more than one config box per process we could always + * shift them to a per-config-box structure stored in GWL_USERDATA. + */ +static struct controlbox *ctrlbox; +/* + * ctrls_base holds the OK and Cancel buttons: the controls which + * are present in all dialog panels. ctrls_panel holds the ones + * which change from panel to panel. + */ +static struct winctrls ctrls_base, ctrls_panel; +static struct dlgparam dp; + +#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; + + +#ifdef MOD_PERSO +#include +#include +#include "kitty.h" +#include "kitty_commun.h" +#include "kitty_registry.h" + +extern char BuildVersionTime[256] ; + +void CenterDlgInParent(HWND hDlg) ; +int get_param( const char * val ) ; +void CheckVersionFromWebSite( HWND hwnd ) ; +bool SessPathIsInitial( void ) ; +bool IsThereDefaultSessionFile( void ) ; + +#ifndef TIMER_SLIDEBG +//#define TIMER_SLIDEBG 12343 +#define TIMER_SLIDEBG 8703 +#endif + +static char *getevent(int i) ; +int print_event_log( FILE * fp, int i ) { + char * pst = getevent(i) ; + if( pst == NULL ) { return 0 ; } + fprintf( fp, "%s\n", pst ) ; + return 1 ; + } +void do_eventlog( const char * st ) ; + +#endif +#ifdef MOD_PRINTCLIP +#define PRINT_TO_CLIPBOARD_STRING "Windows clipboard" +#endif + +#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); + int i; + int size; + char *clipdata; + static unsigned char sel_nl[] = SEL_NL; + + if (count == 0) { /* can't copy zero stuff */ + MessageBeep(0); + break; + } + + size = 0; + for (i = 0; i < count; i++) + size += + strlen(getevent(selitems[i])) + sizeof(sel_nl); + + clipdata = snewn(size, char); + if (clipdata) { + char *p = clipdata; + for (i = 0; i < count; i++) { + char *q = getevent(selitems[i]); + int qlen = strlen(q); + memcpy(p, q, qlen); + p += qlen; + memcpy(p, sel_nl, sizeof(sel_nl)); + p += sizeof(sel_nl); + } + write_aclip(CLIP_SYSTEM, clipdata, size, true); + sfree(clipdata); + } + 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; +} + + + +#if (defined MOD_PERSO) && (!defined FLJ) + +//static const char MESSAGE[] = ""; +static const char MESSAGE[] = " KiTTY software is developed by Cyd for 9bis.com, copyright \251 2005-2022, thanks to Leo for bcrypt and mini libraries, thanks to all contributors " ; + +static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + char *str; + static HFONT hFontTitle ; + static HFONT hFontHover ; + static HFONT hFontNormal ; + static BOOL hover_email; + static BOOL capture_email; + static BOOL hover_webpage; + static BOOL capture_webpage; + static HCURSOR hCursorNormal; + static HCURSOR hCursorHover; + static int message_timer = 1000 ; + static char * mess = NULL ; + + switch (msg) { + case WM_INITDIALOG: { + char buffer[1024] ; + LOGFONT lf; +#ifdef FLJ + /* Positionnement du ssh handler */ + CreateSSHHandler() ; + /* Creation des association de fichiers .ktx */ + CreateFileAssoc() ; +#endif + + sprintf( buffer, "KiTTY - %s", BuildVersionTime ) ; + SetDlgItemText(hwnd,IDA_VERSION,buffer); + + str = dupprintf("About %s That's all folks !", appname); + SetWindowText(hwnd, str); + sfree(str); + + if (hFontTitle == NULL) { + if (NULL == (hFontTitle = (HFONT)SendDlgItemMessage(hwnd,IDA_VERSION,WM_GETFONT,0,0))) + hFontTitle = GetStockObject(DEFAULT_GUI_FONT); + GetObject(hFontTitle,sizeof(LOGFONT),&lf); + lf.lfWeight = FW_BOLD; + hFontTitle = CreateFontIndirect(&lf); + } + // Font setup + if (NULL == (hFontHover = (HFONT)SendDlgItemMessage(hwnd,IDC_EMAIL,WM_GETFONT,0,0))) + hFontHover = GetStockObject(DEFAULT_GUI_FONT); + GetObject(hFontHover,sizeof(LOGFONT),&lf); + lf.lfUnderline = TRUE; + hFontNormal = CreateFontIndirect(&lf); + + // Cursor setup + hCursorNormal = LoadCursor( NULL, MAKEINTRESOURCE((DWORD)IDC_ARROW) ) ; + if (!(hCursorHover = LoadCursor( NULL, (LPCTSTR)MAKEINTRESOURCE((DWORD)IDC_HAND) ))) + hCursorHover = LoadCursor( GetModuleHandle(NULL), MAKEINTRESOURCE(IDC_HOVER) ) ; + + hover_email = FALSE; + capture_email = FALSE; + hover_webpage = FALSE; + capture_webpage = FALSE; + + CenterDlgInParent(hwnd); + + mess = (char*)MESSAGE ; + SetDlgItemText(hwnd,IDC_BAN,mess); + if( strlen( mess ) > 0 ) SetTimer(hwnd, message_timer, 100, NULL) ; + return 1; + } + break ; + + case WM_TIMER: + if ((UINT_PTR)wParam == message_timer) { + mess++ ; + SetDlgItemText(hwnd,IDC_BAN,mess); + if( strlen( mess ) < strlen(" ") ) mess = (char*)MESSAGE ; + } + break ; + + case WM_NCACTIVATE: + if (!(BOOL)wParam) { // we're not active, clear hover states + hover_email = FALSE; + capture_email = FALSE; + hover_webpage = FALSE; + capture_webpage = FALSE; + InvalidateRect(GetDlgItem(hwnd,IDC_EMAIL),NULL,FALSE); + InvalidateRect(GetDlgItem(hwnd,IDC_WEBPAGE),NULL,FALSE); + } + return FALSE; + + case WM_CTLCOLORSTATIC: { + DWORD dwId = GetWindowLong((HWND)lParam,GWL_ID); + HDC hdc = (HDC)wParam; + + if (dwId == IDA_VERSION) { + SetBkMode(hdc,TRANSPARENT); + SetTextColor(hdc,GetSysColor(COLOR_BTNTEXT)); + SelectObject(hdc,hFontTitle); + return(LONG)GetSysColorBrush(COLOR_BTNFACE); + } + if (dwId == IDC_EMAIL || dwId == IDC_WEBPAGE) { + SetBkMode(hdc,TRANSPARENT); + if (GetSysColorBrush(26)) + SetTextColor(hdc,GetSysColor(26)); + else + SetTextColor(hdc,RGB(0,0,255)); + if (dwId == IDC_EMAIL) + SelectObject(hdc,hover_email?hFontHover:hFontNormal); + else + SelectObject(hdc,hover_webpage?hFontHover:hFontNormal); + return(LONG)GetSysColorBrush(COLOR_BTNFACE); + } + } + break ; + + case WM_MOUSEMOVE: { + POINT pt = { LOWORD(lParam), HIWORD(lParam) }; + HWND hwndHover = ChildWindowFromPoint(hwnd,pt); + DWORD dwId = GetWindowLong(hwndHover,GWL_ID); + + if (GetActiveWindow() == hwnd) { + if (wParam & MK_LBUTTON && !capture_email && !capture_webpage) { + ; + } + else if (hover_email != (dwId == IDC_EMAIL) && !capture_webpage) { + hover_email = !hover_email; + InvalidateRect(GetDlgItem(hwnd,IDC_EMAIL),NULL,FALSE); + } + else if (hover_webpage != (dwId == IDC_WEBPAGE) && !capture_email) { + hover_webpage = !hover_webpage; + InvalidateRect(GetDlgItem(hwnd,IDC_WEBPAGE),NULL,FALSE); + } + SetCursor((hover_email || hover_webpage)?hCursorHover:hCursorNormal); + } + } + break; + + case WM_LBUTTONDOWN: { + POINT pt = { LOWORD(lParam), HIWORD(lParam) }; + HWND hwndHover = ChildWindowFromPoint(hwnd,pt); + DWORD dwId = GetWindowLong(hwndHover,GWL_ID); + + if (dwId == IDC_EMAIL) { + GetCapture(); + hover_email = TRUE; + capture_email = TRUE; + InvalidateRect(GetDlgItem(hwnd,IDC_EMAIL),NULL,FALSE); + } + else if (dwId == IDC_WEBPAGE) { + GetCapture(); + hover_webpage = TRUE; + capture_webpage = TRUE; + InvalidateRect(GetDlgItem(hwnd,IDC_WEBPAGE),NULL,FALSE); + } + SetCursor((hover_email || hover_webpage)?hCursorHover:hCursorNormal); + } + break; + + case WM_LBUTTONUP: { + POINT pt = { LOWORD(lParam), HIWORD(lParam) }; + HWND hwndHover = ChildWindowFromPoint(hwnd,pt); + DWORD dwId = GetWindowLong(hwndHover,GWL_ID); + + if (capture_email || capture_webpage) { + ReleaseCapture(); + if (dwId == IDC_EMAIL && capture_email) { + ShellExecute(hwnd,"open","mailto:kitty@9bis.com",NULL,NULL,SW_SHOWNORMAL); + } + else if (dwId == IDC_WEBPAGE && capture_webpage) { + ShellExecute(hwnd,"open","https://www.9bis.net/kitty",NULL,NULL,SW_SHOWNORMAL); + } + capture_email = FALSE; + capture_webpage = FALSE; + } + SetCursor((hover_email || hover_webpage)?hCursorHover:hCursorNormal); + } + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + hover_email = FALSE; + capture_email = FALSE; + hover_webpage = FALSE; + capture_webpage = FALSE; + KillTimer(hwnd, message_timer); + 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 */ + CheckVersionFromWebSite( hwnd ) ; +/* { + char buffer[1024]="", vers[1024]="" ; + int i ; + strcpy( vers, BuildVersionTime ) ; + for( i = 0 ; i < strlen( vers ) ; i ++ ) { + if( !(((vers[i]>='0')&&(vers[i]<='9'))||(vers[i]=='.')) ) { vers[i] = '\0' ; break ; } + } + sprintf( buffer, "https://www.9bis.net/kitty/check_update.php?version=%s", vers ) ; + ShellExecute(hwnd, "open", buffer, 0, 0, SW_SHOWDEFAULT); + }*/ + return 0; + case IDA_DON: + /* Load web browser */ + { char buffer[1024]=""; + sprintf( buffer, "https://www.9bis.net/kitty/#!pages/donation.md" ) ; + ShellExecute(hwnd, "open", buffer, 0, 0, SW_SHOWDEFAULT); + } + return 0; + } + return 0; + case WM_CLOSE: + KillTimer(hwnd, message_timer); + EndDialog(hwnd, TRUE); + return 0; + } + return 0; +} + +static INT_PTR CALLBACK AboutProcOrig(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + +#else +static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ +#endif + 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); +#ifdef MOD_PERSO + if( get_param("PUTTY") ) SetDlgItemText(hwnd, IDA_TEXT2, "" ) ; +#endif + 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; +} + +static int SaneDialogBox(HINSTANCE hinst, + LPCTSTR tmpl, + HWND hwndparent, + DLGPROC lpDialogFunc) +{ + WNDCLASS wc; + HWND hwnd; + MSG msg; + int flags; + int ret; + int gm; + + wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW; + wc.lpfnWndProc = DefDlgProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = DLGWINDOWEXTRA + 2*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 = "PuTTYConfigBox"; + RegisterClass(&wc); + + hwnd = CreateDialog(hinst, tmpl, hwndparent, lpDialogFunc); + + SetWindowLongPtr(hwnd, BOXFLAGS, 0); /* flags */ + SetWindowLongPtr(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */ + + while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) { + flags=GetWindowLongPtr(hwnd, BOXFLAGS); + if (!(flags & DF_END) && !IsDialogMessage(hwnd, &msg)) + DispatchMessage(&msg); + if (flags & DF_END) + break; + } + + if (gm == 0) + PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */ + + ret=GetWindowLongPtr(hwnd, BOXRESULT); + DestroyWindow(hwnd); + return ret; +} + +static void SaneEndDialog(HWND hwnd, int ret) +{ + SetWindowLongPtr(hwnd, BOXRESULT, ret); + SetWindowLongPtr(hwnd, BOXFLAGS, DF_END); +} + +/* + * 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; +} + +/* + * Create the panelfuls of controls in the configuration box. + */ +static void create_controls(HWND hwnd, char *path) +{ + struct ctlpos cp; + int index; + int base_id; + struct winctrls *wc; + + if (!path[0]) { + /* + * Here we must create the basic standard controls. + */ + ctlposinit(&cp, hwnd, 3, 3, 235); + wc = &ctrls_base; + base_id = IDCX_STDBASE; + } else { + /* + * Otherwise, we're creating the controls for a particular + * panel. + */ + ctlposinit(&cp, hwnd, 100, 3, 13); + wc = &ctrls_panel; + base_id = IDCX_PANELBASE; + } + + for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) { + struct controlset *s = ctrlbox->ctrlsets[index]; + winctrl_layout(&dp, wc, &cp, s, &base_id); + } +} + +/* + * 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 CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + HWND hw, treeview; + struct treeview_faff tvfaff; + int ret; + + switch (msg) { + case WM_INITDIALOG: +#ifdef MOD_PERSO + { + RECT rcClient ; + int h ; + GetWindowRect(hwnd, &rcClient) ; + + int NormalSize = 555 ; + + if( GetConfigBoxWindowHeight() > 0 ) { h = GetConfigBoxWindowHeight() ; } + else if( GetConfigBoxHeight() >= 100 ) { h = GetConfigBoxHeight() ; } + else { + if( GetConfigBoxHeight() <= 7 ) { h = ceil(12*7+354) ; } + else if( GetConfigBoxHeight() <= 16 ) { h = NormalSize ; } + else { + h = NormalSize + 12 * ( GetConfigBoxHeight()-16 ) ; + if( h < NormalSize ) h = NormalSize ; + } + } + if( get_param("INIFILE")==SAVEMODE_DIR ) { + h = h - 12 ; // il n'y a pas la liste déroulant des folders en mode portable + } +#ifdef MOD_PROXY + if( GetProxySelectionFlag() ) { h = h + 20 ; } +#endif + // Initialise la taille de la ConfigBox (en cas de DPI speciaux) + double ScaleY = GetDeviceCaps(GetDC(hwnd),LOGPIXELSY)/96.0 ; // La police standard (100%) vaut 96ppp (pixels per pouce) + if( ScaleY!=1.0 ) { h = (int)( h*ScaleY ) ; } + MoveWindow( hwnd, rcClient.left, rcClient.top, rcClient.right-rcClient.left, h, TRUE ) ; + } +#endif + dp.hwnd = hwnd; + create_controls(hwnd, ""); /* Open and Cancel buttons etc */ + SetWindowText(hwnd, dp.wintitle); + SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); + if (has_help()) + SetWindowLongPtr(hwnd, GWL_EXSTYLE, + GetWindowLongPtr(hwnd, GWL_EXSTYLE) | + WS_EX_CONTEXTHELP); + else { + HWND item = GetDlgItem(hwnd, IDC_HELPBTN); + if (item) + DestroyWindow(item); + } + SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG, + (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON))); + /* + * Centre the window. + */ + { /* centre the window */ + RECT rs, rd; + + hw = GetDesktopWindow(); + if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd)) + MoveWindow(hwnd, +#ifdef MOD_PERSO + GetConfigBoxLeft()>0 ? GetConfigBoxLeft() : (rs.right + rs.left + rd.left - rd.right) / 2, + GetConfigBoxTop()>0 ? GetConfigBoxTop() : (rs.bottom + rs.top + rd.top - rd.bottom) / 2, +#else + (rs.right + rs.left + rd.left - rd.right) / 2, + (rs.bottom + rs.top + rd.top - rd.bottom) / 2, +#endif + rd.right - rd.left, rd.bottom - rd.top, true); + } + + /* + * 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 < ctrlbox->nctrlsets; i++) { + struct controlset *s = 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_ */ + create_controls(hwnd, firstpath); + dlg_refresh(NULL, &dp); /* and set up control values */ + } + + /* + * Set focus into the first available control. + */ + { + int i; + struct winctrl *c; + + for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL; + i++) { + if (c->ctrl) { + dlg_set_focus(c->ctrl, &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(hwnd, SW_SHOWNORMAL); + + /* + * Set the flag that activates a couple of the other message + * handlers below, which were disabled until now to avoid + * spurious firing during the above setup procedure. + */ + SetWindowLongPtr(hwnd, GWLP_USERDATA, 1); +#ifdef MOD_PERSO + //ShowWindow(hwnd,SW_HIDE); + ShowWindow(hwnd,SW_SHOW); +#endif + return 0; + case WM_LBUTTONUP: + /* + * Button release should trigger WM_OK if there was a + * previous double click on the session list. + */ + ReleaseCapture(); + if (dp.ended) + SaneEndDialog(hwnd, dp.endresult ? 1 : 0); + break; + 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 (GetWindowLongPtr(hwnd, GWLP_USERDATA) != 1) + 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(&ctrls_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(&dp, c); + winctrl_remove(&ctrls_panel, c); + sfree(c->data); + sfree(c); + } + } + create_controls(hwnd, (char *)item.lParam); + + dlg_refresh(NULL, &dp); /* set up control values */ + + SendMessage (hwnd, WM_SETREDRAW, true, 0); + InvalidateRect (hwnd, NULL, true); + + SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */ + return 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. + */ + if (GetWindowLongPtr(hwnd, GWLP_USERDATA) == 1) { + ret = winctrl_handle_command(&dp, msg, wParam, lParam); + if (dp.ended && GetCapture() != hwnd) + SaneEndDialog(hwnd, dp.endresult ? 1 : 0); + } else + ret = 0; + return ret; + case WM_HELP: + if (!winctrl_context_help(&dp, hwnd, + ((LPHELPINFO)lParam)->iCtrlId)) + MessageBeep(0); + break; + case WM_CLOSE: + quit_help(hwnd); + SaneEndDialog(hwnd, 0); + return 0; + + /* Grrr Explorer will maximize Dialogs! */ + case WM_SIZE: + if (wParam == SIZE_MAXIMIZED) + force_normal(hwnd); + return 0; + + } + return 0; +} + +void modal_about_box(HWND hwnd) +{ + EnableWindow(hwnd, 0); +#if (defined MOD_PERSO) && (!defined FLJ) + if( GetPuttyFlag() ) { + DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProcOrig) ; + } else { + DialogBox(hinst, MAKEINTRESOURCE(IDD_KITTYABOUT), hwnd, AboutProc) ; + } +#else + DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc); +#endif + 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; +#ifdef MOD_PERSO + // On cree la session "Default Settings" si elle n'existe pas + if( GetDefaultSettingsFlag() ) if( (IniFileFlag==SAVEMODE_REG) || SessPathIsInitial() ) { + char buffer[1024] ; + if( !IsThereDefaultSessionFile() ) { + Conf *defconf = conf_new() ; + load_settings(NULL,defconf); + if( !strcmp(FileExtension,"") ) { + if( save_settings( "Default Settings", defconf ) != NULL ) { + do_eventlog( "Can not create Default Settings file" ) ; + } + } else { + sprintf(buffer,"Default Settings%s", FileExtension); + if( save_settings( buffer, defconf ) != NULL ) { + do_eventlog("Can not create Default Settings file" ) ; + } + } + conf_free( defconf ) ; + } + } +#endif + ctrlbox = ctrl_new_box(); + setup_config_box(ctrlbox, false, 0, 0); + win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), false, 0); + dp_init(&dp); + winctrl_init(&ctrls_base); + winctrl_init(&ctrls_panel); + dp_add_tree(&dp, &ctrls_base); + dp_add_tree(&dp, &ctrls_panel); + dp.wintitle = dupprintf("%s Configuration", appname); + dp.errtitle = dupprintf("%s Error", appname); + dp.data = conf; + dlg_auto_set_fixed_pitch_flag(&dp); + dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */ + + ret = + SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, + GenericMainDlgProc); + + ctrl_free_box(ctrlbox); + winctrl_cleanup(&ctrls_panel); + winctrl_cleanup(&ctrls_base); + dp_cleanup(&dp); + +#ifdef MOD_PERSO + GotoConfigDirectory() ; + if( ret==false ) SaveRegistryKey( ) ; // On sort de la config box par ESCAPE ou cancel + else _beginthread( routine_SaveRegistryKey, 0, (void*)NULL ) ; // On démarre une session +#endif + return ret; +} + +bool do_reconfig(HWND hwnd, Conf *conf, int protcfginfo) +{ + Conf *backup_conf; + bool ret; + int protocol; + + backup_conf = conf_copy(conf); + + ctrlbox = ctrl_new_box(); + protocol = conf_get_int(conf, CONF_protocol); + setup_config_box(ctrlbox, true, protocol, protcfginfo); + win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), true, protocol); + dp_init(&dp); + winctrl_init(&ctrls_base); + winctrl_init(&ctrls_panel); + dp_add_tree(&dp, &ctrls_base); + dp_add_tree(&dp, &ctrls_panel); + dp.wintitle = dupprintf("%s Reconfiguration", appname); + dp.errtitle = dupprintf("%s Error", appname); + dp.data = conf; + dlg_auto_set_fixed_pitch_flag(&dp); + dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */ + + ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, + GenericMainDlgProc) ; + + ctrl_free_box(ctrlbox); + winctrl_cleanup(&ctrls_base); + winctrl_cleanup(&ctrls_panel); + dp_cleanup(&dp); + + if (!ret) + conf_copy_into(conf, backup_conf); +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + if( GetBackgroundImageFlag() && (conf_get_int(conf,CONF_bg_slideshow)!=conf_get_int(backup_conf,CONF_bg_slideshow)) ) { + KillTimer( hwnd, TIMER_SLIDEBG ) ; + if((conf_get_int(conf,CONF_bg_type)!=0)&&(conf_get_int(conf,CONF_bg_slideshow)>0)) + SetTimer(hwnd, TIMER_SLIDEBG, (int)(conf_get_int(conf,CONF_bg_slideshow)*1000), NULL) ; + InvalidateRect(hwnd, NULL, TRUE); + } +#endif + + 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); +} + +#if (defined MOD_PERSO) && (!defined FLJ) +void showabout(HWND hwnd) +{ + /* + char buffer[1024] ; + DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc); + sprintf( buffer, "That's all folks ! version\r\n%s", BuildVersionTime ) ; + MessageBox( hwnd, buffer, "Info", MB_OK ) ; + */ + if( get_param("PUTTY") ) DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProcOrig); + else { + DialogBox(hinst, MAKEINTRESOURCE(IDD_KITTYABOUT), hwnd, AboutProc); + /*if( GetIconeFlag() != -1 ) SetNewIcon( hwnd, conf_get_filename(conf,CONF_iconefile)->path, conf_get_int(conf,CONF_icone), SI_NEXT ) ; + if( GetIconeFlag() > 0 ) { + time_t ttime = time( NULL ) % GetNumberOfIcons() ; + SendMessage( hwnd, WM_SETICON, ICON_BIG, (LPARAM)LoadIcon( hinst, MAKEINTRESOURCE(IDI_MAINICON_0 + ttime ) ) ); + }*/ + } +} +#else +void showabout(HWND hwnd) +{ + DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc); +} +#endif + +struct hostkey_dialog_ctx { + const char *const *keywords; + const char *const *values; + FingerprintType fptype_default; + char **fingerprints; + const char *keydisp; + LPCTSTR iconid; + const char *helpctx; +}; + +static INT_PTR CALLBACK HostKeyMoreInfoProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: { + const struct hostkey_dialog_ctx *ctx = + (const struct hostkey_dialog_ctx *)lParam; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx); + + if (ctx->fingerprints[SSH_FPTYPE_SHA256]) + SetDlgItemText(hwnd, IDC_HKI_SHA256, + ctx->fingerprints[SSH_FPTYPE_SHA256]); + if (ctx->fingerprints[SSH_FPTYPE_MD5]) + SetDlgItemText(hwnd, IDC_HKI_MD5, + ctx->fingerprints[SSH_FPTYPE_MD5]); + + SetDlgItemText(hwnd, IDA_TEXT, ctx->keydisp); + return 1; + } + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + EndDialog(hwnd, 0); + return 0; + } + return 0; + case WM_CLOSE: + EndDialog(hwnd, 0); + return 0; + } + return 0; +} + +static INT_PTR CALLBACK HostKeyDialogProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: { + strbuf *sb = strbuf_new(); + const struct hostkey_dialog_ctx *ctx = + (const struct hostkey_dialog_ctx *)lParam; + SetWindowLongPtr(hwnd, GWLP_USERDATA, (INT_PTR)ctx); + for (int id = 100;; id++) { + char buf[256]; + + if (!GetDlgItemText(hwnd, id, buf, (int)lenof(buf))) + break; + + strbuf_clear(sb); + for (const char *p = buf; *p ;) { + if (*p == '{') { + for (size_t i = 0; ctx->keywords[i]; i++) { + if (strstartswith(p, ctx->keywords[i])) { + p += strlen(ctx->keywords[i]); + put_datapl(sb, ptrlen_from_asciz(ctx->values[i])); + goto matched; + } + } + } else { + put_byte(sb, *p++); + } + matched:; + } + + SetDlgItemText(hwnd, id, sb->s); + } + strbuf_free(sb); + + SetDlgItemText(hwnd, IDC_HK_FINGERPRINT, + ctx->fingerprints[ctx->fptype_default]); + MakeDlgItemBorderless(hwnd, IDC_HK_FINGERPRINT); + + HANDLE icon = LoadImage( + NULL, ctx->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); + } + +#ifdef MOD_PERSO + SaveRegistryKey() ; +#endif + return 1; + } + case WM_CTLCOLORSTATIC: { + HDC hdc = (HDC)wParam; + HWND control = (HWND)lParam; + + if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_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: + EndDialog(hwnd, LOWORD(wParam)); + return 0; + case IDHELP: { + const struct hostkey_dialog_ctx *ctx = + (const struct hostkey_dialog_ctx *) + GetWindowLongPtr(hwnd, GWLP_USERDATA); + launch_help(hwnd, ctx->helpctx); + return 0; + } + case IDC_HK_MOREINFO: { + const struct hostkey_dialog_ctx *ctx = + (const struct hostkey_dialog_ctx *) + GetWindowLongPtr(hwnd, GWLP_USERDATA); + DialogBoxParam(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO), + hwnd, HostKeyMoreInfoProc, (LPARAM)ctx); + } + } + return 0; + case WM_CLOSE: + EndDialog(hwnd, IDCANCEL); + return 0; + } + return 0; +} + +int win_seat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **fingerprints, + void (*callback)(void *ctx, int result), void *ctx) +{ + int ret; + + WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat); + + /* + * Verify the key against the registry. + */ + ret = verify_host_key(host, port, keytype, keystr); + + if (ret == 0) /* success - key matched OK */ + return 1; + else { + static const char *const keywords[] = + { "{KEYTYPE}", "{APPNAME}", NULL }; + + const char *values[2]; + values[0] = keytype; + values[1] = appname; + + struct hostkey_dialog_ctx ctx[1]; + ctx->keywords = keywords; + ctx->values = values; + ctx->fingerprints = fingerprints; + ctx->fptype_default = ssh2_pick_default_fingerprint(fingerprints); + ctx->keydisp = keydisp; + ctx->iconid = (ret == 2 ? IDI_WARNING : IDI_QUESTION); + ctx->helpctx = (ret == 2 ? WINHELP_CTX_errors_hostkey_changed : + WINHELP_CTX_errors_hostkey_absent); + int dlgid = (ret == 2 ? IDD_HK_WRONG : IDD_HK_ABSENT); + int mbret = DialogBoxParam( + hinst, MAKEINTRESOURCE(dlgid), wgs->term_hwnd, + HostKeyDialogProc, (LPARAM)ctx); + assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL); + if (mbret == IDC_HK_ACCEPT) { + store_host_key(host, port, keytype, keystr); + return 1; + } else if (mbret == IDC_HK_ONCE) + return 1; + } + return 0; /* abandon the connection */ +} + +/* + * Ask whether the selected algorithm is acceptable (since it was + * below the configured 'warn' threshold). + */ +int win_seat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char mbtitle[] = "%s Security Alert"; + static const char msg[] = + "The first %s supported by the server\n" + "is %s, which is below the configured\n" + "warning threshold.\n" + "Do you want to continue with this connection?\n"; + char *message, *title; + int mbret; + + message = dupprintf(msg, algtype, algname); + title = dupprintf(mbtitle, appname); + mbret = MessageBox(NULL, message, title, + MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); + socket_reselect_all(); + sfree(message); + sfree(title); + if (mbret == IDYES) + return 1; + else + return 0; +} + +int win_seat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx) +{ + static const char mbtitle[] = "%s Security Alert"; + static const char msg[] = + "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" + "Do you want to continue with this connection?\n"; + char *message, *title; + int mbret; + + message = dupprintf(msg, algname, betteralgs); + title = dupprintf(mbtitle, appname); + mbret = MessageBox(NULL, message, title, + MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); + socket_reselect_all(); + sfree(message); + sfree(title); + if (mbret == IDYES) + return 1; + else + return 0; +} + +/* + * Ask whether to wipe a session log file before writing to it. + * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). + */ +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->path); + mbtitle = dupprintf("%s Log to File", appname); + + mbret = MessageBox(NULL, message, mbtitle, + MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3); + + 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); +} diff --git a/0.77_My_PuTTY/windows/window.c b/0.77_My_PuTTY/windows/window.c new file mode 100644 index 0000000..96bea08 --- /dev/null +++ b/0.77_My_PuTTY/windows/window.c @@ -0,0 +1,8949 @@ +/* + * window.c - the PuTTY(tel) main program, which runs a PuTTY terminal + * emulator and backend in a window. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef MOD_FAR2L +/* far2l extensions support - base64 encode/decode libs */ +#include <../../far2l/cencode.h> +#include <../../far2l/cdecode.h> +#endif + +#ifdef __WINE__ +#define NO_MULTIMON /* winelib doesn't have this */ +#endif + +#ifndef NO_MULTIMON +#define COMPILE_MULTIMON_STUBS +#endif + +#include "putty.h" +#include "terminal.h" +#include "storage.h" +#include "win_res.h" +#include "winsecur.h" +#include "winseat.h" +#include "tree234.h" + +#ifndef NO_MULTIMON +#ifdef MOD_PERSO +#include +#else +#include +#endif +#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_NEWDUPSESS 0x01B0 + +#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 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(Mouse_Button button); +static void show_mouseptr(bool show); +static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, + unsigned char *output); +static void init_palette(void); +static void init_fonts(int, int); +static void init_dpi_info(void); +static void another_font(int); +static void deinit_fonts(void); +static void set_input_locale(HKL); +static void update_savedsess_menu(void); +static void init_winfuncs(void); + +static bool is_full_screen(void); +static void make_full_screen(void); +static void clear_full_screen(void); +static void flip_full_screen(void); +static void process_clipdata(HGLOBAL clipdata, bool unicode); +static void setup_clipboards(Terminal *, Conf *); + +/* Window layout information */ +static void reset_window(int); +static int extra_width, extra_height; +static int font_width, font_height; +static bool font_dualwidth, font_varpitch; +static int offset_width, offset_height; +static bool was_zoomed = false; +static int prev_rows, prev_cols; + +static void flash_window(int mode); +static void sys_cursor_update(void); +static bool get_fullscreen_rect(RECT * ss); + +static int caret_x = -1, caret_y = -1; + +static int kbd_codepage; + +static Ldisc *ldisc; +static Backend *backend; + +static struct unicode_data ucsdata; +static bool session_closed; +static bool reconfiguring = false; + +static const SessionSpecial *specials = NULL; +static HMENU specials_menu = NULL; +static int n_specials = 0; + +#define TIMING_TIMER_ID 1234 +static long timing_next_time; + +static struct { + HMENU menu; +} popup_menus[2]; +enum { SYSMENU, CTXMENU }; +static HMENU savedsess_menu; + +#ifdef MOD_PERSO +Conf *conf; +#else +static Conf *conf; +#endif +static LogContext *logctx; +static Terminal *term; + +struct wm_netevent_params { + /* Used to pass data to wm_netevent_callback */ + WPARAM wParam; + LPARAM lParam; +}; + +static void conf_cache_data(void); +static int cursor_type; +static int vtmode; + +static struct sesslist sesslist; /* for saved-session menu */ + +#define FONT_NORMAL 0 +#define FONT_BOLD 1 +#define FONT_UNDERLINE 2 +#define FONT_BOLDUND 3 + +#ifdef MOD_PERSO +#define FONT_ITALIC 4 +#define FONT_WIDE 0x08 +#define FONT_HIGH 0x10 +#define FONT_NARROW 0x20 + +#define FONT_OEM 0x40 +#define FONT_OEMBOLD 0x41 +#define FONT_OEMUND 0x42 +#define FONT_OEMBOLDUND 0x43 + +#define FONT_MAXNO 0x4F +#define FONT_SHIFT 6 +static int use_italics; +#else +#define FONT_WIDE 0x04 +#define FONT_HIGH 0x08 +#define FONT_NARROW 0x10 + +#define FONT_OEM 0x20 +#define FONT_OEMBOLD 0x21 +#define FONT_OEMUND 0x22 +#define FONT_OEMBOLDUND 0x23 + +#define FONT_MAXNO 0x40 +#define FONT_SHIFT 5 +#endif + +static HFONT fonts[FONT_MAXNO]; +static LOGFONT lfont; +static bool fontflag[FONT_MAXNO]; +static enum { + BOLD_NONE, BOLD_SHADOW, BOLD_FONT +} bold_font_mode; +static bool bold_colours; +static enum { + UND_LINE, UND_FONT +} und_mode; +static int descent, font_strikethrough_y; + +static COLORREF colours[OSC4_NCOLOURS]; +static HPALETTE pal; +static LPLOGPALETTE logpal; +bool tried_pal = false; +COLORREF colorref_modifier = 0; + +enum MONITOR_DPI_TYPE { MDT_EFFECTIVE_DPI, MDT_ANGULAR_DPI, MDT_RAW_DPI, MDT_DEFAULT }; +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 struct _dpi_info { + POINT cur_dpi; + RECT new_wnd_rect; +} dpi_info; + +static HBITMAP caretbm; + +#ifdef MOD_HYPERLINK +#include "urlhack.h" +static int urlhack_cursor_is_hand = 0; +void urlhack_enable(void); +#endif +#ifdef MOD_SAVEDUMP +void SaveDump( void ) ; +#endif +#ifdef MOD_PERSO +#include +#include +#include +#include "../../kitty.h" +#include "../../kitty_commun.h" +#include "../../kitty_crypt.h" +#include "../../kitty_launcher.h" +#include "../../kitty_registry.h" +#include "../../kitty_ssh.h" +#include "../../kitty_tools.h" +#include "../../kitty_win.h" +extern int PuttyFlag ; +int GetCursorType() { return cursor_type ; } +void SetCursorType( const int ct ) { cursor_type = ct ; } +extern char BuildVersionTime[256] ; +void SendStrToTerminal( const char * str, const int len ) { + char c ; + int i ; + if( len <= 0 ) return ; +// if( term!=NULL ) term_seen_key_event(term) ; + for( i=0 ; irows; + prev_cols = term->cols; + + w = width / font_width ; + if (w < 1) w = 1; + h = height / font_height ; + if (h < 1) h = 1; + } + else { + h = prev_rows ; + w = prev_cols ; + } + + term_size(term, h, w, conf_get_int(conf,CONF_savelines)) ; + reset_window(0); + + w = (width-conf_get_int(conf,CONF_window_border)*2) / font_width ; + if (w < 1) w = 1; + h = (height-conf_get_int(conf,CONF_window_border)*2) / font_height ; + if (h < 1) h = 1; + conf_set_int(conf,CONF_height,h); + conf_set_int(conf,CONF_width,w); + } +int Convert1Reg( const char * filename ) ; +int get_param( const char * val ) ; +void SendKeyboardPlus( HWND hwnd, const char * st ) ; +//`colours' +int return_offset_height(void) { return offset_height ; } +int return_offset_width(void) { return offset_width ; } +int return_font_height(void) { return font_height ; } +int return_font_width(void) { return font_width ; } +COLORREF return_colours258(void) { return colours[258]; } +// prototypes de fonctions decrites dans winnet.c +struct netscheduler_tag* netscheduler_new(void) ; +void netscheduler_free(struct netscheduler_tag* netscheduler) ; + +void AddDynamicSFTPConnect( Conf *conf ) ; + +/* String pour charger automatiquement au démarrage un fichier dans un editeur connecté */ +static char *LoadFile = NULL ; + +/* Flag pour interdire l'ouverture de boite configuration */ +int force_reconf = 1 ; + +// Reinitialiser le fichier de logs +void logfile_reinit(void *handle) ; + +void set_cmd_line( const char * st ) ; + +/* Flag to manage first connection */ +bool first_connect = true ; + +/* Start a session with conf structure */ +void RunSessionWithConfSettings( Conf *conf ) { + char b[2048]; + char *cl=NULL; + const char *argprefix; + bool inherit_handles; + STARTUPINFO si; + PROCESS_INFORMATION pi; + HANDLE filemap = NULL; + if (restricted_acl) + argprefix = "&R"; + else + argprefix = ""; + /* + * 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), 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); + 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); +} +/* Start new session with current settings */ +void RunSessionWithCurrentSettings( HWND hwnd, Conf * oldconf, const char * host, const char * user, const char * pass, const int port, const char * remotepath ) { + Conf *newconf = conf_copy(oldconf) ; + if( host != NULL ) { conf_set_str(newconf,CONF_host,host) ; } + if( user != NULL ) { conf_set_str(newconf,CONF_username,user) ; } + if( pass != NULL ) { conf_set_str(newconf,CONF_password,pass) ; } + +#ifndef MOD_NOPASSWORD + char pst[4096] ; + strcpy( pst, conf_get_str(newconf, CONF_password ) ) ; + MASKPASS(GetCryptSaltFlag(), pst); + if( pass != NULL ) { strcpy( pst, pass ) ; } + conf_set_str( newconf, CONF_password, pst ) ; + memset(pst,0,strlen(pst)); +#else + conf_set_str( newconf, CONF_password, "" ) ; +#endif + + + + if( remotepath != NULL ) { + char *buf=(char*)malloc(strlen(remotepath)+5); + sprintf(buf,"cd %s",remotepath); + conf_set_str(newconf,CONF_autocommand,buf) ; + } //else { conf_set_str(newconf,CONF_autocommand,"") ; } + //if( port != 0 ) { conf_set_int(newconf,CONF_port,port) ; } else { conf_set_int(newconf,CONF_port,22) ; } + + if( conf_launchable(newconf) ) { + RunSessionWithConfSettings( newconf ) ; + } else { + save_settings("__STARTUP",newconf) ; + RunSession( hwnd, conf_get_str(conf,CONF_folder), "__STARTUP" ) ; + del_settings("__STARTUP"); + } + conf_free( newconf ) ; +} + + +#ifdef MOD_INTEGRATED_KEYGEN +int WINAPI KeyGen_WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show); +#endif +#ifdef MOD_INTEGRATED_AGENT +int WINAPI Agent_WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show); +#endif + +#endif +#ifdef MOD_WTS +typedef enum _WTS_VIRTUAL_CLASS { WTSVirtualClientData, WTSVirtualFileHandle } WTS_VIRTUAL_CLASS; // WTS_VIRTUAL_CLASS is not definned in file wtsapi32.h !!! +#include +#endif +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) +#include "../../kitty_image.h" +#endif +#ifdef MOD_RECONNECT +static time_t last_reconnect = 0; +void SetConnBreakIcon( HWND hwnd ) ; +static void close_session(void *ignored_context); +#endif +/* rutty: */ +#ifdef MOD_RUTTY +#define IDM_SCRIPT (0x5100) +#define IDM_SCRIPTSEND (0x5110) +#define IDM_SCRIPTHALT (0x5120) + +#include "script.h" +ScriptData scriptdata; + +#include "script_win.c" +#include "script.c" + +#endif /* rutty */ +#ifdef MOD_PROXY +#include "kitty_proxy.h" +#endif + +static int dbltime, lasttime, lastact; +static Mouse_Button lastbtn; + +/* this allows xterm-style mouse handling. */ +static bool send_raw_mouse = false; +static int wheel_accumulator = 0; + +static bool pointer_indicates_raw_mouse = false; + +static BusyStatus busy_status = BUSY_NOT; + +static char *window_name, *icon_name; + +static int compose_state = 0; + +static UINT wm_mousewheel = WM_MOUSEWHEEL; + +#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); +#ifdef MOD_PERSO +static void wintw_set_focus_reporting_mode(TermWin *, bool enable); +#endif +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); +static void wintw_set_icon_title(TermWin *, const char *icontitle); +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 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, +#ifdef MOD_PERSO + .set_focus_reporting_mode = wintw_set_focus_reporting_mode, +#endif + .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, +}; + +static TermWin wintw[1]; +static HDC wintw_hdc; + +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(void) +{ + return ucsdata.line_codepage == CP_UTF8; +} + +static bool win_seat_is_utf8(Seat *seat) +{ + return is_utf8(); +} + +static char *win_seat_get_ttymode(Seat *seat, const char *mode) +{ + return term_get_ttymode(term, mode); +} + +static StripCtrlChars *win_seat_stripctrl_new( + Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) +{ + return stripctrl_new_term(bs_out, false, 0, term); +} + +static size_t win_seat_output( + Seat *seat, bool is_stderr, const void *, size_t); +static bool win_seat_eof(Seat *seat); +static int win_seat_get_userpass_input( + Seat *seat, prompts_t *p, bufchain *input); +static void win_seat_notify_remote_exit(Seat *seat); +static void win_seat_connection_fatal(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 bool win_seat_set_trust_status(Seat *seat, bool trusted); +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, + .get_userpass_input = win_seat_get_userpass_input, + .notify_remote_exit = win_seat_notify_remote_exit, + .connection_fatal = win_seat_connection_fatal, + .update_specials_menu = win_seat_update_specials_menu, + .get_ttymode = win_seat_get_ttymode, + .set_busy_status = win_seat_set_busy_status, + .verify_ssh_host_key = win_seat_verify_ssh_host_key, + .confirm_weak_crypto_primitive = win_seat_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = win_seat_confirm_weak_cached_hostkey, + .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, + .verbose = nullseat_verbose_yes, + .interactive = nullseat_interactive_yes, + .get_cursor_position = win_seat_get_cursor_position, +}; +#ifdef MOD_FAR2L +// "static" removed by far2l extensions support patch +// we need to access wgs from terminal.c to open dialog boxes, etc +WinGuiSeat wgs = { .seat.vt = &win_seat_vt, +#else +static WinGuiSeat wgs = { .seat.vt = &win_seat_vt, +#endif + .logpolicy.vt = &win_gui_logpolicy_vt }; + +#ifdef MOD_PERSO +Terminal* GetTerminal() { return term ; } +void do_eventlog( const char * st ) { lp_eventlog(&wgs.logpolicy,st); } +#endif + +static void start_backend(void) +{ + const struct BackendVtable *vt; + char *error, *realhost; + int i; + + /* + * 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) { + char *str = dupprintf("%s Internal Error", appname); + MessageBox(NULL, "Unsupported protocol number found", + str, MB_OK | MB_ICONEXCLAMATION); + sfree(str); + cleanup_exit(1); + } + + seat_set_trust_status(&wgs.seat, true); + error = backend_init(vt, &wgs.seat, &backend, logctx, conf, + conf_get_str(conf, CONF_host), + conf_get_int(conf, CONF_port), + &realhost, + conf_get_bool(conf, CONF_tcp_nodelay), + conf_get_bool(conf, CONF_tcp_keepalives)); + if (error) { +#ifdef MOD_RECONNECT + char *str = dupprintf("%s Error", appname); + char *msg = dupprintf("Unable to open connection to\n%s\n%s", + conf_dest(conf), error); + sfree(error); + if( GetAutoreconnectFlag() && conf_get_int(conf,CONF_failure_reconnect) && is_backend_first_connected ) { + lp_eventlog(&wgs.logpolicy, msg) ; + } else { + if( GetAutoreconnectFlag() && conf_get_int(conf,CONF_failure_reconnect) ) { + if( MessageBox(NULL, msg, str, MB_ICONERROR | MB_RETRYCANCEL | MB_DEFBUTTON2) == IDCANCEL ) { + sfree(str); + sfree(msg); + exit(0); + } else { + start_backend() ; + return ; + } + } else { + MessageBox(NULL, msg, str, MB_OK); + } + } + sfree(str); + sfree(msg); + if( GetAutoreconnectFlag() && conf_get_int(conf,CONF_failure_reconnect) && is_backend_first_connected ) { + SetConnBreakIcon(wgs.term_hwnd) ; + SetSSHConnected(0) ; + queue_toplevel_callback(close_session, NULL); + session_closed = true; + lp_eventlog(&wgs.logpolicy, "Unable to connect, trying to reconnect...") ; + SetTimer(wgs.term_hwnd, TIMER_RECONNECT, GetReconnectDelay()*1000, NULL) ; + return ; + } + else +#else + char *str = dupprintf("%s Error", appname); + char *msg = dupprintf("Unable to open connection to\n%s\n%s", + conf_dest(conf), error); + sfree(error); + MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK); + sfree(str); + sfree(msg); +#endif + exit(0); + } + term_setup_window_titles(term, realhost); + sfree(realhost); + + /* + * Connect the terminal to the backend for resize purposes. + */ + term_provide_backend(term, backend); + + /* + * Set up a line discipline. + */ + ldisc = ldisc_create(conf, term, 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(popup_menus); i++) { + DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); + } + + session_closed = false; +/* rutty: */ +#ifdef MOD_RUTTY + script_init(&scriptdata, conf); + if(conf_get_int(conf, CONF_script_mode) == SCRIPT_PLAY && !filename_is_null(conf_get_filename(conf, CONF_script_filename))) + script_sendfile(&scriptdata, conf_get_filename(conf, CONF_script_filename)); + else if(conf_get_int(conf, CONF_script_mode) == SCRIPT_RECORD && !filename_is_null(conf_get_filename(conf, CONF_script_filename))) + script_record(&scriptdata, conf_get_filename(conf, CONF_script_filename)); +#endif /* rutty */ + +} + +static void close_session(void *ignored_context) +{ + char *newtitle; + int i; + + session_closed = true; + newtitle = dupprintf("%s (inactive)", appname); +#ifdef MOD_PERSO + newtitle = dupprintf("%s (inactive)", conf_get_str(conf,CONF_wintitle)); +#endif + win_set_icon_title(wintw, newtitle); + win_set_title(wintw, newtitle); + sfree(newtitle); + + if (ldisc) { + ldisc_free(ldisc); + ldisc = NULL; + } + if (backend) { + backend_free(backend); + backend = NULL; + term_provide_backend(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(popup_menus); i++) { + DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND); + InsertMenu(popup_menus[i].menu, IDM_DUPSESS, MF_BYCOMMAND | MF_ENABLED, + IDM_RESTART, "&Restart Session"); + } +} + +#ifdef MOD_RECONNECT +void RestartSession( void ) { + if( backend ) { + SetSSHConnected(0) ; + queue_toplevel_callback(close_session, NULL) ; backend = NULL ; + } + if( GetAutoreconnectFlag() ) { + lp_eventlog(&wgs.logpolicy, "User request session restart..." ) ; + } else { + win_seat_connection_fatal( &wgs.seat, "User request session restart..." ) ; + SetTimer(wgs.term_hwnd, TIMER_RECONNECT, 10, NULL) ; + } + + PostMessage(wgs.term_hwnd,WM_KEYDOWN,VK_RETURN ,0) ; + PostMessage(wgs.term_hwnd,WM_KEYUP,VK_RETURN ,1) ; +} +#endif + +const unsigned cmdline_tooltype = + TOOLTYPE_HOST_ARG | + TOOLTYPE_PORT_ARG | + TOOLTYPE_NO_VERBOSE_OPTION; + +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(); +#ifdef MOD_ZMODEM + struct netscheduler_tag *netsc = NULL ; +#endif + + hinst = inst; + + 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(); + conf = conf_new(); +#ifdef MOD_HYPERLINK + urlhack_init(); +#endif +#ifdef MOD_PERSO + // Initialisation specifique a KiTTY + set_cmd_line( cmdline ) ; + SethInstIcons( hinst ) ; + InitWinMain(); + if( debug_flag ) { + if( IniFileFlag == SAVEMODE_REG ) { debug_logevent( "Save mode is set to registry (classic)" ) ; } + if( IniFileFlag == SAVEMODE_DIR ) { debug_logevent( "Save mode is set to directory (portable)" ) ; } + debug_logevent( "Command-line: %s", cmdline ) ; + debug_logevent( "Version: %s", BuildVersionTime ) ; + } +#endif +#ifdef MOD_NOTRANSPARENCY + SetTransparencyFlag(0); +#endif + + /* + * 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. + */ + { + char *p; + bool special_launchable_argument = 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 = cmdline; + /* + * Process a couple of command-line options which are more + * easily dealt with before the line is broken up into words. + * These are the old-fashioned but convenient @sessionname and + * the internal-use-only &sharedmemoryhandle, plus the &R + * prefix for -restrict-acl, all of which are used by PuTTYs + * auto-launching each other via System-menu options. + */ + while (*p && isspace(*p)) + p++; + if (*p == '&' && p[1] == 'R' && + (!p[2] || p[2] == '@' || p[2] == '&')) { + /* &R restrict-acl prefix */ + restrict_process_acl(); + p += 2; + } + + if (*p == '@') { + /* + * An initial @ means that the whole of the rest of the + * command line should be treated as the name of a saved + * session, with _no quoting or escaping_. This makes it a + * very convenient means of automated saved-session + * launching, via IDM_SAVEDSESS or Windows 7 jump lists. + */ + int i = strlen(p); + while (i > 1 && isspace(p[i - 1])) + i--; + p[i] = '\0'; +#ifdef MOD_PERSO + if( GetDirectoryBrowseFlag() ) { + char *pst = strstr( p+1, "/" ) ; + if( pst!=NULL ) { + pst[0]='\0'; + char *name = (char*)malloc( strlen(p)+1 ) ; + strcpy( name, p+1 ) ; + conf_set_str(conf,CONF_folder,name) ; + SetSessPath( name ) ; + SetInitCurrentFolder( name ) ; + free(name) ; + pst[0]='@' ; + p=pst; + } + char * pfolder ; + if( (pfolder=strstr(p+1," -folder \"")) ) { + pfolder[0]='\0'; + pfolder += 10 ; + pfolder[strlen(pfolder)-1]='\0'; + SetSessPath( pfolder ) ; + SetInitCurrentFolder( pfolder ) ; + } + } +#endif + do_defaults(p + 1, conf); + if (!conf_launchable(conf) && !do_config(conf)) { + cleanup_exit(0); + } + special_launchable_argument = true; + } else if (*p == '&') { + /* + * An initial & means we've been given a command line + * containing the hex value of a HANDLE for a file + * mapping object, which we must then interpret as a + * serialised Conf. + */ + HANDLE filemap; + void *cp; + unsigned cpsize; + if (sscanf(p + 1, "%p:%u", &filemap, &cpsize) == 2 && + (cp = MapViewOfFile(filemap, FILE_MAP_READ, + 0, 0, cpsize)) != NULL) { + BinarySource src[1]; + BinarySource_BARE_INIT(src, cp, cpsize); + if (!conf_deserialise(conf, src)) + modalfatalbox("Serialised configuration data was invalid"); + UnmapViewOfFile(cp); + CloseHandle(filemap); + } else if (!do_config(conf)) { + cleanup_exit(0); + } + 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, &argc, &argv, NULL); + + for (i = 0; i < argc; i++) { + char *p = argv[i]; + int ret; + + ret = cmdline_process_param(p, i+1255 ) { r=255; } + if( g<0 ) { g=0 ; } if( g>255 ) { g=255; } + if( b<0 ) { b=0 ; } if( b>255 ) { b=255; } + conf_set_int_int(conf, CONF_colours, j*3+0, r); + conf_set_int_int(conf, CONF_colours, j*3+1, g); + conf_set_int_int(conf, CONF_colours, j*3+2, b); + } else if( !strcmp(p, "-cmd") ) { + i++ ; + conf_set_str( conf, CONF_autocommand, argv[i] ) ; + } else if( !strcmp(p, "-codepage") ) { + i++ ; + conf_set_str( conf, CONF_line_codepage, argv[i] ) ; + } else if( !strcmp(p, "-defini") ) { + CreateIniFile( "kitty.template.ini" ) ; + exit(0); + } else if( !strcmp(p, "-rcmd") ) { + i++ ; + conf_set_str( conf, CONF_remote_cmd, argv[i] ) ; + } else if( !strcmp(p, "-sftpconnect") ) { + i++ ; + conf_set_str( conf, CONF_sftpconnect, argv[i] ) ; + } else if( !strcmp(p, "-debug") ) { + debug_flag = 1 ; + } else if( !strcmp(p, "-fullscreen") ) { + conf_set_int( conf, CONF_fullscreen, 1 ) ; + } else if( !strcmp(p, "-funky") ) { + i++; + conf_set_int( conf, CONF_funky_type, atoi(argv[1]) ) ; + } else if( !strcmp(p, "-initdelay") ) { + i++ ; + init_delay = (int)(1000*atof( argv[i] )) ; + } else if( !strcmp(p, "-icon") ) { + i++; + conf_set_int( conf, CONF_icone, atoi(argv[i])) ; + } else if( !strcmp(p, "-iconfile") ) { + i++; + if( existfile( argv[i] ) ) { + conf_set_filename( conf, CONF_iconefile,filename_from_str(argv[i])) ; + } + } else if( !strcmp(p, "-log") ) { + i++ ; + conf_set_filename( conf, CONF_logfilename,filename_from_str(argv[i])) ; + conf_set_int( conf, CONF_logtype,1 ) ; + conf_set_int( conf, CONF_logxfovr,1 ) ; + conf_set_bool( conf, CONF_logflush, true ) ; + } else if( !strcmp(p, "-nofiles") ) { + SetNoKittyFileFlag( 1 ) ; + } else if( !strcmp(p, "-edit") ) { + i++ ; + if( existfile(argv[i]) ) { + LoadFile = (char*) malloc( strlen(argv[i])+1 ) ; + strcpy( LoadFile, argv[i] ) ; + RunPuttyEd( wgs.term_hwnd, LoadFile ) ; + free( LoadFile ) ; + } else { + MessageBox(wgs.term_hwnd,"Unable to find requested file","Error",MB_OK|MB_ICONERROR) ; + } + exit( 0 ) ; + } else if( !strcmp(p, "-folder") ) { + char *pfolder ; + pfolder=p+7; + pfolder[0]='\0'; + pfolder += 1 ; + if( pfolder[0]=='\"' ) pfolder++; + if( pfolder[strlen(pfolder)-1]=='\"' ) pfolder[strlen(pfolder)-1]='\0'; + if( GetDirectoryBrowseFlag() ) SetSessPath( pfolder ) ; + SetInitCurrentFolder( pfolder ) ; + i++ ; + } else if( !strcmp(p, "-localproxy") ) { + i++ ; + conf_set_str( conf, CONF_host, "localhost" ) ; + conf_set_int( conf, CONF_port, 23 ) ; + conf_set_int( conf, CONF_protocol, I(PROT_TELNET) ) ; + conf_set_int( conf, CONF_proxy_type, 5 ) ; + conf_set_str( conf, CONF_proxy_host, "proxy" ) ; + conf_set_int( conf, CONF_proxy_port, 80 ) ; + conf_set_bool( conf, CONF_even_proxy_localhost, true ) ; + conf_set_str( conf, CONF_proxy_telnet_command, argv[i] ) ; + SetBackgroundImageFlag(0) ; + } else if( !strcmp(p, "-loginscript") ) { + i++ ; + if( existfile( argv[i] ) ) { + conf_set_filename( conf, CONF_scriptfile, filename_from_str(argv[i])) ; + ReadInitScript(argv[i]); + Filename * fn = filename_from_str( "" ) ; + conf_set_filename(conf,CONF_scriptfile,fn) ; + filename_free(fn) ; + } + + } else if( !strcmp(p, "-version") || !strcmp(p, "-about") ) { + showabout(wgs.term_hwnd) ; exit( 0 ) ; + } else if( !strcmp(p, "-noctrltab") ) { + SetCtrlTabFlag( 0 ) ; + } else if( !strcmp(p, "-noicon") ) { + SetIconeFlag( -1 ) ; + } else if( !strcmp(p, "-noshortcuts") ) { + SetShortcutsFlag( 0 ) ; + SetMouseShortcutsFlag( 0 ) ; + } else if( !strcmp(p, "-notrans") ) { + SetTransparencyFlag( 0 ) ; + conf_set_int(conf,CONF_transparencynumber, -1) ; +#ifdef MOD_RECONNECT + } else if( !strcmp(p, "-autorecnt") ) { + SetAutoreconnectFlag(1) ; + conf_set_int(conf,CONF_wakeup_reconnect, 1) ; + conf_set_int(conf,CONF_failure_reconnect, 1) ; +#endif +#ifdef MOD_SAVEDUMP + } else if( !strcmp(p, "-savedump") ) { + SaveDump() ; + return 0; +#endif +#ifdef MOD_RUTTY + } else if( !strcmp(p, "-norutty") ) { + SetRuttyFlag( 0 ) ; +#endif +#ifdef MOD_ADB + } else if( !strcmp(p, "-adb") ) { + SetADBFlag( 1 ) ; +#endif +#ifdef MOD_ZMODEM + } else if( !strcmp(p, "-zmodem") ) { + SetZModemFlag( 1 ) ; + } else if( !strcmp(p, "-nozmodem") ) { + SetZModemFlag( 0 ) ; +#endif +#ifdef MOD_HYPERLINK +#ifndef MOD_NOHYPERLINK + } else if( !strcmp(p, "-hyperlink") ) { + HyperlinkFlag = 1 ; + } else if( !strcmp(p, "-nohyperlink") ) { + HyperlinkFlag = 0 ; + } else if( !strcmp(p, "-hyperlinkfix") ) { + FixWrongRegex(); +#endif +#endif + } else if( !strcmp(p, "-xpos") ) { + i++ ; + if( atoi(argv[i])>=0 ) { + conf_set_int( conf, CONF_xpos,atoi(argv[i])); + if( conf_get_int( conf, CONF_ypos)<0 ) conf_set_int( conf, CONF_ypos,0); + conf_set_bool( conf, CONF_save_windowpos, true ) ; + } + } else if( !strcmp(p, "-ypos") ) { + i++ ; + if( atoi(argv[i])>=0 ) { + conf_set_int( conf, CONF_ypos, atoi(argv[i])); + if( conf_get_int( conf, CONF_xpos)<0) conf_set_int( conf, CONF_xpos,0); + conf_set_bool( conf, CONF_save_windowpos, true ) ; + } + } else if( !strcmp(p, "-mungestr") ) { + i++ ; + char *b = (char*)malloc(4*strlen(argv[i])+1); + mungestr(argv[i],b); + MessageBox(NULL,b,"mungestr",MB_OK); + SetTextToClipboard(b); + free(b) ; + return 0; + } else if( !strcmp(p, "-unmungestr") ) { + i++ ; + char *b = (char*)malloc(4*strlen(argv[i])+1); + unmungestr(argv[i],b,MAX_VALUE_NAME) ; + MessageBox(NULL,b,"unmungestr",MB_OK); + SetTextToClipboard(b); + free(b); + return 0; +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + } else if( !strcmp(p, "-nobgimage") ) { + SetBackgroundImageFlag(0) ; + } else if( !strcmp(p, "-bgimage") ) { + SetBackgroundImageFlag(1) ; +#endif + } else if( !strcmp(p, "-putty") ) { + SetAutoStoreSSHKeyFlag( 0 ) ; + SetUserPassSSHNoSave( 1 ) ; + SetNoKittyFileFlag( 1 ) ; + HyperlinkFlag = 0 ; + SetIconeFlag( -1 ) ; + SethInstIcons( hinst ) ; + SetTransparencyFlag( 0 ) ; + conf_set_int(conf,CONF_transparencynumber, -1) ; + SetShortcutsFlag( 0 ) ; + SetMouseShortcutsFlag( 0 ) ; + SetSizeFlag( 0 ) ; + SetWinrolFlag( 0 ) ; + SetCapsLockFlag( 0 ) ; + SetWinHeight( -1 ) ; + SetConfigBoxHeight( 7 ) ; + SetCtrlTabFlag( 0 ) ; + SetRuttyFlag( 0 ) ; + SetDefaultSettingsFlag(1); + SetReadOnlyFlag(0); +#ifdef MOD_PROXY + SetProxySelectionFlag(0); +#endif +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + SetBackgroundImageFlag(0) ; +#endif +#ifdef MOD_RECONNECT + SetAutoreconnectFlag( 0 ) ; +#endif +#ifdef MOD_ADB + SetADBFlag(0); +#endif + SetSessionFilterFlag( 0 ) ; + PuttyFlag = 1 ; + //SetWindowPos(GetForegroundWindow(), HWND_TOP, 0, 0, 0, 252, SWP_SHOWWINDOW|SWP_NOMOVE); + } else if( !strcmp(p, "-readonly") ) { + SetDefaultSettingsFlag(0); + SetReadOnlyFlag(1); + } else if( !strcmp(p, "-send-to-tray") ) { + SetAutoSendToTray( 1 ) ; + } else if( !strcmp(p, "-sshhandler") ) { + CreateSSHHandler() ; return 0 ; + } else if( !strcmp(p, "-fileassoc") ) { + CreateFileAssoc() ; return 0 ; + } else if( !strcmp(p, "-ed") ) { + char buffer[1024]; + sprintf(buffer, "%s|%s|0|0", (char*)get_param_str("INI"), (char*)get_param_str("SAV") ) ; + return Notepad_WinMain(inst, prev, buffer, show) ; + } else if( !strcmp(p, "-edb") ) { + i++; + if( existfile(argv[i]) || !strcmp(argv[i],"1") ) { + char buffer[1024]; + sprintf(buffer, "%s|%s|%s|0", (char*)get_param_str("INI"), (char*)get_param_str("SAV"), argv[i] ) ; + return Notepad_WinMain(inst, prev, buffer, show) ; + } else { + MessageBox(wgs.term_hwnd,"Unable to find requested file","Error",MB_OK|MB_ICONERROR) ; + } + exit(0); + } else if( !strcmp(p, "-help") || !strcmp(p, "-h") ) { + char *hmsg ; + if( debug_flag ) { + hmsg=(char*)malloc(strlen(GetHelpMessage())+1024); + sprintf(hmsg,"%s",GetHelpMessage()); + strcat(hmsg,"\r\nDebug Mode\r\n\r\n"); + strcat(hmsg,"* -mungestr\r\n"); + strcat(hmsg,"* -unmungestr\r\n"); + + } else { + hmsg = GetHelpMessage() ; + } + SetTextToClipboard(hmsg) ; + char buffer[1024]; + sprintf(buffer, "%s|%s|1|1", (char*)get_param_str("INI"), (char*)get_param_str("SAV") ) ; + if( hmsg!=GetHelpMessage()) { free(hmsg); } + return Notepad_WinMain(inst, prev, buffer, show) ; +#ifdef MOD_LAUNCHER + } else if( !strcmp(p, "-launcher") ) { + char *np; + np=strstr(cmdline,"-launcher")+9; + return Launcher_WinMain(inst, prev, np, show) ; +#endif +#ifdef MOD_INTEGRATED_KEYGEN + } else if( !strcmp(p, "-keygen") ) { + char *np; + np=strstr(cmdline,"-keygen")+7; + return KeyGen_WinMain(inst, prev, np, show) ; +#endif +#ifdef MOD_INTEGRATED_AGENT + } else if( !strcmp(p, "-runagent") ) { + char *np; + np=strstr(cmdline,"-runagent")+9; + return Agent_WinMain(inst, prev, np, show) ; +#endif + } else if( !strcmp(p, "-convert-dir") ) { + return Convert2Dir( ConfigDirectory ) ; + } else if( !strcmp(p, "-convert-reg") ) { // NE FONCTIONNE PAS / preferer le parametre -convert1reg + return Convert2Reg( ConfigDirectory ) ; + } else if( !strcmp(p, "-convert1reg") ) { + i++ ; + return Convert1Reg(argv[i]) ; + } else if( !strcmp(p, "-title") ) { + i++ ; + conf_set_str( conf, CONF_wintitle, argv[i] ); +#ifndef FLJ + } else if( !strcmp(p, "-classname") ) { + i++ ; + if( strlen( argv[i] ) > 0 ) { + strcpy( KiTTYClassName, argv[i] ) ; + appname = KiTTYClassName ; + } +#endif + } else if( !strcmp(p, "-sendcmd") ) { + i++ ; + if( strlen(argv[i])>0 ) + SendCommandAllWindows( wgs.term_hwnd, argv[i] ) ; + exit(0); + } else if( !strcmp(p, "-kload") ) { + i++ ; + if( strlen(argv[i])>0 ) { + load_open_settings_forced( argv[i], conf ) ; + special_launchable_argument = true ; + } + } else if( !strcmp(p, "-loadfile") ) { + i++ ; + if( strlen(argv[i])>0 ) { + load_open_settings_forced( argv[i], conf ) ; + special_launchable_argument = true ; + } +#endif + } else if (!strcmp(p, "-cleanup")) { + /* + * `putty -cleanup'. Remove all registry + * entries associated with PuTTY, and also find + * and delete the random seed file. + */ + char *s1, *s2; + s1 = dupprintf("This procedure will remove ALL Registry entries\n" + "associated with %s, and will also remove\n" + "the random seed file. (This only affects the\n" + "currently logged-in user.)\n" + "\n" + "THIS PROCESS WILL DESTROY YOUR SAVED SESSIONS.\n" + "Are you really sure you want to continue?", + appname); + s2 = dupprintf("%s Warning", appname); + if (message_box(NULL, s1, s2, + MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2, + HELPCTXID(option_cleanup)) == IDYES) { + cleanup_all(); + } + sfree(s1); + sfree(s2); + exit(0); + } else if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints_msgbox(NULL); + exit(1); + } else if (*p != '-') { + cmdline_error("unexpected argument \"%s\"", p); + } else { + cmdline_error("unknown option \"%s\"", p); + } + } + } +#ifdef MOD_PERSO + // Creation du fichier kitty.ini par defaut si besoin + CreateDefaultIniFile() ; +#endif + cmdline_run_saved(conf); + + /* + * 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); + } + +#ifdef MOD_PERSO + /* Generate a dump file just after load the configuration to be able to analyse crashes */ + //SaveDump() ; +#endif +#ifdef MOD_PERSO + if( debug_flag ) { + if( strlen(conf_get_str(conf,CONF_sessionname))>0 ) { + debug_logevent( "Session name: %s", conf_get_str(conf,CONF_sessionname) ) ; + } else { + debug_logevent( "Session name: -" ) ; + } + } + HICON icon_m ; + if( GetIconeFlag() > 0 ) + icon_m = LoadIcon( GethInstIcons(), MAKEINTRESOURCE(IDI_MAINICON_0 + GetIconeNum() ) ); + else + icon_m = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON)); +int xpos_init=0, ypos_init=0 ; + if( (conf_get_bool(conf,CONF_saveonexit)||conf_get_bool(conf,CONF_save_windowpos) ) + /*&& (conf_get_int(conf,CONF_xpos)>=0) && (conf_get_int(conf,CONF_ypos)>=0)*/ ) { + xpos_init=conf_get_int(conf,CONF_xpos) ; + ypos_init=conf_get_int(conf,CONF_ypos) ; + } + +while( conf_get_int(conf,CONF_icone) > GetNumberOfIcons() ) { + conf_set_int( conf, CONF_icone, conf_get_int( conf, CONF_icone) - GetNumberOfIcons() ) ; +} + +if( conf_get_int(conf,CONF_icone) == 0 ) { + if( GetIconeFlag() > 0 ) SetIconeNum( ( GetCurrentProcessId() * time( NULL ) ) % GetNumberOfIcons() ) ; else SetIconeNum( 0 ) ; +} else{ + if( GetIconeFlag() > 0 ) SetIconeNum( conf_get_int(conf,CONF_icone) - 1 ) ; else SetIconeNum( 0 ) ; +} +#endif + + if (!prev) { + WNDCLASSW wndclass; + + wndclass.style = 0; + wndclass.lpfnWndProc = WndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; +#ifdef MOD_PERSO + if( conf_get_int(conf, CONF_ctrl_tab_switch) && GetCtrlTabFlag() ) + wndclass.cbWndExtra += 8; +#endif + wndclass.hInstance = inst; +#ifdef MOD_PERSO + wndclass.hIcon = icon_m; +#else + wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON)); +#endif + wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM); + wndclass.hbrBackground = NULL; + wndclass.lpszMenuName = NULL; +#if (defined MOD_PERSO) && (!defined FLJ) + wndclass.lpszClassName = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, KiTTYClassName); +#else + wndclass.lpszClassName = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname); +#endif + + RegisterClassW(&wndclass); + } + +#ifdef MOD_PERSO +// Initialisation de la structure NOTIFYICONDATA +TrayIcone.cbSize = sizeof(TrayIcone); // On alloue la taille necessaire pour la structure +TrayIcone.uID = IDI_MAINICON_0 ; // On lui donne un ID +TrayIcone.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE; // On lui indique les champs valables + // On lui dit qu'il devra "ecouter" son environement (clique de souris, etc) +TrayIcone.uCallbackMessage = MYWM_NOTIFYICON; +TrayIcone.hIcon = LoadIcon(NULL, NULL); // On ne load aucune icone pour le moment +//TrayIcone.szTip[1024] = "PuTTY That\'s all folks!\0" ; // Le tooltip par defaut, soit rien +strcpy( TrayIcone.szTip, "PuTTY That\'s all folks!\0") ; // Le tooltip par defaut, soit rien +TrayIcone.hWnd = wgs.term_hwnd ; +#endif + + memset(&ucsdata, 0, sizeof(ucsdata)); + + conf_cache_data(); + + /* + * 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... + */ + + font_width = 10; + font_height = 20; + extra_width = 25; + extra_height = 28; + guess_width = extra_width + font_width * conf_get_int(conf, CONF_width); + guess_height = extra_height + font_height*conf_get_int(conf, CONF_height); + { + RECT r; + get_fullscreen_rect(&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; + } +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + const char* winname = appname; +#endif + { + 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); + if (!conf_get_bool(conf, CONF_scrollbar)) + winmode &= ~(WS_VSCROLL); + if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED || + resize_forbidden) + winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); + if (conf_get_bool(conf, CONF_alwaysontop)) + exwinmode |= WS_EX_TOPMOST; +#ifdef MOD_TUTTY + if (!conf_get_int(conf, CONF_window_has_sysmenu)) + winmode &= ~(WS_SYSMENU); + if (!conf_get_int(conf, CONF_window_minimizable)) + winmode &= ~(WS_MINIMIZEBOX); + if (!conf_get_int(conf, CONF_window_maximizable)) + winmode &= ~(WS_MAXIMIZEBOX); +#endif + if (conf_get_bool(conf, CONF_sunken_edge)) + exwinmode |= WS_EX_CLIENTEDGE; +#ifdef MOD_PERSO +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + // TODO: This is the beginning of some work to have windows with fancy + // no-client-edge borders. It's not ready yet. + if( GetBackgroundImageFlag() && (!GetPuttyFlag()) ) + if(0) + { + winmode = WS_POPUP; + exwinmode = 0; + winname = NULL; + + // TODO: This is proof-of-concept. For this to really work, we'll + // have to do some additional mods, like creating our own title/move + // window to glue to the top, and some kind of drag-resizing window + // to glue to the bottom-right. Otherwise there'll be no way to move + // or resize the window, which will get old extremely quickly. Finally, + // this won't work as written anyway, b/c when you call SetWindowText + // anywhere Win32 forces a window border to appear anyway. So, we'll + // want to create a new function, set_window_text, that checks whether + // to really call SetWindowText or to set the window text in whatever + // other location is necessary for our custom window text display for + // when we're handling our own border here. + } + + if( GetBackgroundImageFlag() && (!GetPuttyFlag()) ) { + wgs.term_hwnd = CreateWindowExW(exwinmode|WS_EX_ACCEPTFILES, uappname, (wchar_t *)winname, + winmode, CW_USEDEFAULT, CW_USEDEFAULT, + guess_width, guess_height, + NULL, NULL, inst, NULL); + init_dc_blend(); + } + else + wgs.term_hwnd = CreateWindowExW(exwinmode|WS_EX_ACCEPTFILES, uappname, uappname, + winmode, CW_USEDEFAULT, CW_USEDEFAULT, + guess_width, guess_height, + NULL, NULL, inst, NULL); + + if( GetBackgroundImageFlag() ) init_dc_blend() ; +#else + wgs.term_hwnd = CreateWindowExW(exwinmode|WS_EX_ACCEPTFILES, uappname, uappname, + winmode, CW_USEDEFAULT, CW_USEDEFAULT, + guess_width, guess_height, + NULL, NULL, inst, NULL); +#endif +#else + wgs.term_hwnd = CreateWindowExW( + exwinmode, uappname, uappname, winmode, CW_USEDEFAULT, + CW_USEDEFAULT, guess_width, guess_height, NULL, NULL, inst, NULL); + memset(&dpi_info, 0, sizeof(struct _dpi_info)); +#endif + init_dpi_info(); + sfree(uappname); + } + + /* + * Initialise the fonts, simultaneously correcting the guesses + * for font_{width,height}. + */ + init_fonts(0,0); + + /* + * Prepare a logical palette. + */ + init_palette(); + + /* + * 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.) + */ + wintw->vt = &windows_termwin_vt; + term = term_init(conf, &ucsdata, wintw); + setup_clipboards(term, conf); + logctx = log_init(&wgs.logpolicy, conf); + term_provide_logctx(term, logctx); + term_size(term, conf_get_int(conf, CONF_height), + conf_get_int(conf, CONF_width), + conf_get_int(conf, CONF_savelines)); + + /* + * Correct the guesses for extra_{width,height}. + */ + { + RECT cr, wr; + GetWindowRect(wgs.term_hwnd, &wr); + GetClientRect(wgs.term_hwnd, &cr); + offset_width = offset_height = conf_get_int(conf, CONF_window_border); + extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; + extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; + } + + /* + * Resize the window, now we know what size we _really_ want it + * to be. + */ + guess_width = extra_width + font_width * term->cols; + guess_height = extra_height + font_height * term->rows; + SetWindowPos(wgs.term_hwnd, NULL, 0, 0, guess_width, guess_height, + SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER); +#ifdef MOD_PERSO + if( !PuttyFlag ) + if( (conf_get_bool(conf,CONF_saveonexit)||conf_get_bool(conf,CONF_save_windowpos)) + /*&& (xpos_init>=0) && (ypos_init>=0)*/ ) { + MoveWindow(wgs.term_hwnd, xpos_init, ypos_init, guess_width, guess_height, TRUE ); + } +#endif + + /* + * Set up a caret bitmap, with no content. + */ + { + char *bits; + int size = (font_width + 15) / 16 * 2 * font_height; + bits = snewn(size, char); + memset(bits, 0, size); + caretbm = CreateBitmap(font_width, font_height, 1, 1, bits); + sfree(bits); + } + CreateCaret(wgs.term_hwnd, caretbm, font_width, font_height); + + /* + * Initialise the scroll bar. + */ + { + SCROLLINFO si; + + si.cbSize = sizeof(si); + si.fMask = SIF_ALL | SIF_DISABLENOSCROLL; + si.nMin = 0; + si.nMax = term->rows - 1; + si.nPage = term->rows; + si.nPos = 0; + SetScrollInfo(wgs.term_hwnd, SB_VERT, &si, false); + } + + /* + * Prepare the mouse handler. + */ + lastact = MA_NOTHING; + lastbtn = MBT_NOTHING; + dbltime = GetDoubleClickTime(); + + /* + * Set up the session-control options on the system menu. + */ + { + HMENU m; + int j; + char *str; + + popup_menus[SYSMENU].menu = GetSystemMenu(wgs.term_hwnd, false); +#ifdef MOD_TUTTY + EnableMenuItem(popup_menus[SYSMENU].menu, SC_CLOSE, conf_get_int(conf,CONF_window_closable) ? MF_ENABLED : MF_GRAYED); +#endif + popup_menus[CTXMENU].menu = CreatePopupMenu(); + AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_COPY, "&Copy"); + AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_PASTE, "&Paste"); + + savedsess_menu = CreateMenu(); + get_sesslist(&sesslist, true); + update_savedsess_menu(); + + for (j = 0; j < lenof(popup_menus); j++) { + m = popup_menus[j].menu; + + AppendMenu(m, MF_SEPARATOR, 0, 0); +#ifdef MOD_PERSO + if( !PuttyFlag ) InitSpecialMenu( m, conf_get_str(conf,CONF_folder), conf_get_str(conf,CONF_sessionname) ) ; +#endif +/* rutty: */ +#ifdef MOD_RUTTY + AppendMenu(m, MF_ENABLED, IDM_SCRIPTSEND, "Send recorded &script file" ) ; + AppendMenu(m, MF_SEPARATOR, 0, 0); +#endif /* rutty */ + AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log"); +#ifdef MOD_PERSO + AppendMenu(m, MF_ENABLED, IDM_EXPORTSETTINGS, "Export ¤t settings" ) ; +#endif + AppendMenu(m, MF_SEPARATOR, 0, 0); + AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session..."); + AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session"); +#ifdef MOD_RECONNECT + AppendMenu(m, MF_ENABLED, IDM_NEWDUPSESS, "New duplicated session..."); + AppendMenu(m, MF_ENABLED, IDM_RESTARTSESSION, "Close+Restart") ; +#endif + AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT_PTR) 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"); +#ifdef MOD_PERSO + if( conf_get_int(conf, CONF_logtype)!=LGTYP_NONE ) { + AppendMenu(m, MF_ENABLED, IDM_CLEARLOGFILE, "Clear log file"); + } else { + AppendMenu(m, MF_DISABLED|MF_GRAYED, IDM_CLEARLOGFILE, "Clear log file"); + } +#endif + AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal"); + AppendMenu(m, MF_SEPARATOR, 0, 0); + AppendMenu(m, (conf_get_int(conf, CONF_resize_action) + == RESIZE_DISABLED) ? MF_GRAYED : MF_ENABLED, + IDM_FULLSCREEN, "&Full Screen"); +#ifdef MOD_PERSO + if( !PuttyFlag ) { + // if( !IsWow64() ) { AppendMenu(m, MF_ENABLED, IDM_PRINT, "Print clip&board") ; } // Le menu print clipboard avait été desactivé un temps sur les machine 64bits + AppendMenu(m, MF_ENABLED, IDM_PRINT, "Print clip&board") ; + AppendMenu(m, MF_ENABLED, IDM_TOTRAY, "Send to tra&y"); + if( conf_get_bool(conf,CONF_alwaysontop) ) + AppendMenu(m, MF_ENABLED|MF_CHECKED, IDM_VISIBLE, "Always visi&ble"); + else + AppendMenu(m, MF_ENABLED, IDM_VISIBLE, "Always visi&ble"); + AppendMenu(m, MF_ENABLED, IDM_PROTECT, "Prote&ct"); +//char b[256];sprintf(b,"%d",GetWinrolFlag());MessageBox(NULL,b,"info",MB_OK); + if( GetWinrolFlag() ) AppendMenu(m, MF_ENABLED, IDM_WINROL, "Roll-u&p"); + HMENU FontMenu = CreateMenu(); + AppendMenu(FontMenu, MF_ENABLED, IDM_FONTUP, "Font up"); + AppendMenu(FontMenu, MF_ENABLED, IDM_FONTDOWN, "Font down"); + AppendMenu(FontMenu, MF_ENABLED, IDM_FONTNEGATIVE, "Negative"); + AppendMenu(FontMenu, MF_ENABLED, IDM_FONTBLACKANDWHITE, "Black and White"); + AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) FontMenu, "Font settings"); + + if( GetShortcutsFlag() ) + AppendMenu(m, MF_ENABLED|MF_CHECKED, IDM_SHORTCUTSTOGGLE, "Shortcuts toggle"); + else + AppendMenu(m, MF_ENABLED, IDM_SHORTCUTSTOGGLE, "Shortcuts toggle"); + + AppendMenu(m, MF_SEPARATOR, 0, 0); + + AppendMenu(m, MF_ENABLED, IDM_SCRIPTFILE, "Send scr&ipt file" ) ; + if( PSCPPath!=NULL ) AppendMenu(m, MF_ENABLED, IDM_PSCP, "Send wit&h pscp"); + else AppendMenu(m, MF_DISABLED|MF_GRAYED, IDM_PSCP, "Send wit&h pscp"); + if( WinSCPPath!=NULL ) AppendMenu(m, MF_ENABLED, IDM_WINSCP, "&Start WinSCP"); + else AppendMenu(m, MF_DISABLED|MF_GRAYED, IDM_WINSCP, "&Start WinSCP"); + + + AppendMenu(m, MF_ENABLED, IDM_SHOWPORTFWD, "Po&rt forwarding"); +#ifdef MOD_PORTKNOCKING + if( strlen(conf_get_str(conf,CONF_portknockingoptions))>0 ) { + AppendMenu(m, MF_ENABLED, IDM_PORTKNOCK, "Port &knock"); + } +#endif + } +#endif +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + AppendMenu(m, MF_SEPARATOR, 0, 0); + AppendMenu(m, term->xyz_transfering?MF_GRAYED:MF_ENABLED, IDM_XYZSTART, "&Zmodem Receive"); + AppendMenu(m, term->xyz_transfering?MF_GRAYED:MF_ENABLED, IDM_XYZUPLOAD, "Zmodem &Upload"); + AppendMenu(m, !term->xyz_transfering?MF_GRAYED:MF_ENABLED, IDM_XYZABORT, "Zmodem &Abort"); + } +#endif + 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); + } + } +#ifdef MOD_PERSO + // SETTINGS specifique a la session + + if( !PuttyFlag ) { + // Lancement automatique dans le Tray + if( conf_get_int(conf,CONF_sendtotray) ) SetAutoSendToTray( 1 ) ; + + // Charge le fichier de script d'initialisation si il existe + ScriptFileContent = NULL ; + ReadInitScript( NULL ) ; + + static char reg_buffer[4096]; + + // Lancement du serveur de chat + if( ReadParameter( INIT_SECTION, "chat", reg_buffer ) ) { + int chat_flag = atoi( reg_buffer ) ; + if ( chat_flag > 0 ) { + //if( chat_flag != 1 ) PORT = chat_flag ; + _beginthread( routine_server, 0, NULL ) ; + } + } + + // Parametrage specifique a la session + if( GetSessionField( conf_get_str(conf,CONF_sessionname), conf_get_str(conf,CONF_folder), "InitDelay", reg_buffer ) ) { + if( init_delay != (int)(1000*atof( reg_buffer ) ) ) { + init_delay = (int)(1000*atof( reg_buffer ) ) ; + conf_set_int(conf,CONF_initdelay,init_delay) ; + } + } + if( GetSessionField( conf_get_str(conf,CONF_sessionname), conf_get_str(conf,CONF_folder), "BCDelay", reg_buffer ) ) { + if( between_char_delay != atof( reg_buffer ) ) { + between_char_delay = atof( reg_buffer ) ; + conf_set_int(conf,CONF_bcdelay, atof( reg_buffer ) ); + } + } + +#ifndef MOD_NOTRANSPARENCY + if( GetTransparencyFlag() && conf_get_int(conf,CONF_transparencynumber) != -1 ) { + if( conf_get_int(conf,CONF_transparencynumber) > 0 ) { + SetTransparency( wgs.term_hwnd, 255-conf_get_int(conf,CONF_transparencynumber) ) ; + } + } +#endif + // Lancement du timer auto-command pour les connexions non SSH + if(conf_get_int(conf,CONF_protocol) != PROT_SSH) { is_backend_connected = 1 ; } + SetTimer(wgs.term_hwnd, TIMER_INIT, init_delay, NULL) ; + + if( IniFileFlag == SAVEMODE_REG ) { + sprintf( reg_buffer, "%s@%s:%d (prot=%d) name=%s", conf_get_str(conf,CONF_username), conf_get_str(conf,CONF_host), conf_get_int(conf,CONF_port), conf_get_int(conf,CONF_protocol), conf_get_str(conf,CONF_sessionname)) ; + cryptstring( GetCryptSaltFlag(), reg_buffer, MASTER_PASSWORD ) ; + WriteParameter( INIT_SECTION, "KiLastSe", reg_buffer ) ; + } + + // Lancement des timer (changement image de fond, rafraichissement) +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + if( (!GetBackgroundImageFlag()) || GetPuttyFlag() ) conf_set_int(conf,CONF_bg_type,0); + if( conf_get_int(conf,CONF_bg_type)!=0 ) { + if(conf_get_int(conf,CONF_bg_slideshow)>0) + SetTimer(wgs.term_hwnd, TIMER_SLIDEBG, (int)(conf_get_int(conf,CONF_bg_slideshow)*1000), NULL) ; + else + if(ImageSlideDelay>0) + SetTimer(wgs.term_hwnd, TIMER_SLIDEBG, (int)(ImageSlideDelay*1000), NULL) ; + } + + // Lancement du rafraichissement toutes les 10 minutes (pour l'image de fond, pour pallier bug d'affichage) + if( GetBackgroundImageFlag() && ReadParameter( INIT_SECTION, "redraw", reg_buffer ) ) { + if( stricmp( reg_buffer, "NO" ) ) SetTimer(wgs.term_hwnd, TIMER_REDRAW, (int)(600*1000), NULL) ; + } else SetTimer(wgs.term_hwnd, TIMER_REDRAW, (int)(600*1000), NULL) ; +#endif + // Lancement du timer d'anti-idle + SetTimer(wgs.term_hwnd, TIMER_ANTIIDLE, (int)(30*1000), NULL) ; + + } // fin de if( !PuttyFlag ) +#ifdef MOD_HYPERLINK +else { + conf_set_int(conf,CONF_url_underline,URLHACK_UNDERLINE_NEVER); + } +#endif + +#endif + +#ifdef MOD_PROXY +if( GetProxySelectionFlag() ) { + LoadProxyInfo( conf, conf_get_str(conf,CONF_proxyselection) ) ; +} +#endif +#ifdef MOD_PORTKNOCKING +ManagePortKnocking(conf_get_str(conf,CONF_host),conf_get_str(conf,CONF_portknockingoptions)); +#endif +#ifdef MOD_PERSO +AddDynamicSFTPConnect( conf ) ; +#endif + + if (restricted_acl()) { + lp_eventlog(&wgs.logpolicy, "Running with restricted process ACL"); + } + + winselgui_set_hwnd(wgs.term_hwnd); + start_backend(); +#ifdef MOD_RECONNECT + if (conf_get_int(conf, CONF_protocol) != PROT_SSH) { is_backend_first_connected = 1 ; } + last_reconnect = time(NULL); +#endif +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + * Hyperlink stuff: Set the regular expression + */ + if( !GetPuttyFlag() && GetHyperlinkFlag() ) { + if( strlen( conf_get_str(conf,CONF_url_regex))==0 ) { conf_set_str(conf,CONF_url_regex,"@°@°@NO REGEX--") ; } + if( strlen( conf_get_str(term->conf,CONF_url_regex))==0 ) { conf_set_str(term->conf,CONF_url_regex,"@°@°@NO REGEX--") ; } + + if( conf_get_int(term->conf,CONF_url_defregex) != 0 ) { + urlhack_set_regular_expression(URLHACK_REGEX_CLASSIC, conf_get_str(term->conf,CONF_url_regex) ) ; + } else { + urlhack_set_regular_expression(URLHACK_REGEX_CUSTOM, conf_get_str(term->conf, CONF_url_regex) ) ; + } + } +#endif + + /* + * Set up the initial input locale. + */ + set_input_locale(GetKeyboardLayout(0)); + + /* + * Finally show the window! + */ + ShowWindow(wgs.term_hwnd, show); + SetForegroundWindow(wgs.term_hwnd); + + term_set_focus(term, GetForegroundWindow() == wgs.term_hwnd); + UpdateWindow(wgs.term_hwnd); + +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + netsc = netscheduler_new(); + } +#endif + + while (1) { + HANDLE *handles; + int nhandles, 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(term, GetForegroundWindow() == wgs.term_hwnd); + } + + handles = handle_get_events(&nhandles); + + n = MsgWaitForMultipleObjects(nhandles, handles, false, + timeout, QS_ALLINPUT); + + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { + handle_got_event(handles[n - WAIT_OBJECT_0]); + sfree(handles); +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { continue ; } +#endif + } else + sfree(handles); + + while (PeekMessageW(&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))) + DispatchMessageW(&msg); +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() && xyz_Process(backend, backend, term)) + continue; +#endif + + /* + * 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: +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + netscheduler_free(netsc); + } +#endif + cleanup_exit(msg.wParam); /* this doesn't return... */ + return msg.wParam; /* ... but optimiser doesn't know */ +} + +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. + */ +#ifdef MOD_PERSO + chdir( InitialDirectory ) ; + if( conf_get_bool(conf,CONF_saveonexit) ) { SaveWindowCoord( conf ) ; } + + if( IniFileFlag == SAVEMODE_REG ) { // Mode de sauvegarde registry + //SaveRegistryKey() ; + } + else if( IniFileFlag == SAVEMODE_FILE ) { // Mode de sauvegarde fichier + int nb ; + nb = WindowsCount( MainHwnd ) ; + if( nb == 1 ) { // c'est la derniere instance de kitty on vide la cle de registre + HWND hdlg = InfoBox( hinst, NULL ) ; + InfoBoxSetText( hdlg, "Cleaning registry" ) ; + RegDelTree( HKEY_CURRENT_USER, TEXT(PUTTY_REG_POS) ) ; + if( RegTestKey( HKEY_CURRENT_USER, TEXT(PUTTY_REG_POS_SAVE) ) ) { + InfoBoxSetText( hdlg, "Restoring backup registry" ) ; + RegRenameTree( NULL, HKEY_CURRENT_USER, TEXT(PUTTY_REG_POS_SAVE), TEXT(PUTTY_REG_POS) ) ; + } + else DelRegistryKey() ; + InfoBoxClose( hdlg ) ; + } + } + + + /* + Embryon de mecanisme de retour a la config box en sortant d'une session. + Le probleme est que ca retourne a la config box aussi en sortant ... de la config box si une session est chargee ... + */ + if( !PuttyFlag && GetConfigBoxNoExitFlag() ) + if( is_backend_connected && strlen(conf_get_str(conf,CONF_sessionname))>0 ) { + char buffer[4096]="",shortname[1024]="" ; ; + if( GetModuleFileName( NULL, (LPTSTR)buffer, 1023 ) ) + if( GetShortPathName( buffer, shortname, 1023 ) ) { + STARTUPINFO si ; + PROCESS_INFORMATION pi ; + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + if( !CreateProcess(NULL, shortname, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) ) ; + } + } +#endif +#ifdef MOD_HYPERLINK + urlhack_cleanup(); +#endif + deinit_fonts(); + sfree(logpal); + if (pal) + DeleteObject(pal); + sk_cleanup(); + + if (conf_get_int(conf, CONF_protocol) == PROT_SSH) { + random_save_seed(); + } + shutdown_help(); + + /* Clean up COM. */ + CoUninitialize(); + + exit(code); +} + +/* + * Refresh the saved-session submenu from `sesslist'. + */ +static void update_savedsess_menu(void) +{ + int i; + while (DeleteMenu(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(savedsess_menu, MF_ENABLED, + IDM_SAVED_MIN + (i-1)*MENU_SAVED_STEP, + sesslist.sessions[i]); + if (sesslist.nsessions <= 1) + AppendMenu(savedsess_menu, MF_GRAYED, IDM_SAVED_MIN, "(No sessions)"); +} + +/* + * Update the Special Commands submenu. + */ +static void win_seat_update_specials_menu(Seat *seat) +{ + HMENU new_menu; + int i, j; + + if (backend) + specials = backend_get_specials(backend); + else + specials = NULL; + + if (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 (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, 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, + specials[i].name); + break; + } + } + /* Squirrel the highest special. */ + n_specials = i - 1; + } else { + new_menu = NULL; + n_specials = 0; + } + + for (j = 0; j < lenof(popup_menus); j++) { + if (specials_menu) { + /* XXX does this free up all submenus? */ + DeleteMenu(popup_menus[j].menu, (UINT_PTR)specials_menu, + MF_BYCOMMAND); + DeleteMenu(popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND); + } + if (new_menu) { + InsertMenu(popup_menus[j].menu, IDM_SHOWLOG, + MF_BYCOMMAND | MF_POPUP | MF_ENABLED, + (UINT_PTR) new_menu, "S&pecial Command"); + InsertMenu(popup_menus[j].menu, IDM_SHOWLOG, + MF_BYCOMMAND | MF_SEPARATOR, IDM_SPECIALSEP, 0); + } + } + specials_menu = new_menu; +} + +static void update_mouse_pointer(void) +{ + LPTSTR curstype = NULL; + bool force_visible = false; + static bool forced_visible = false; + switch (busy_status) { + case BUSY_NOT: + if (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) +{ + busy_status = status; + update_mouse_pointer(); +} + +static void wintw_set_raw_mouse_mode(TermWin *tw, bool activate) +{ + send_raw_mouse = activate; +} + +static void wintw_set_raw_mouse_mode_pointer(TermWin *tw, bool activate) +{ + pointer_indicates_raw_mouse = activate; + update_mouse_pointer(); +} + +#ifdef MOD_PERSO +/* + * set or clear the "window focus messages" mode + */ +static void wintw_set_focus_reporting_mode(TermWin *tw, bool activate) +{ + activate = activate && !conf_get_bool(conf, CONF_no_focus_rep); + + if (activate != term->report_focus) + { + term->report_focus = activate; + //send first activation notification message + if (activate) + { + SendKeyboard(wgs.term_hwnd, "\033[I"); + } + } +} +#endif + +/* + * Print a message box and close the connection. + */ +static void win_seat_connection_fatal(Seat *seat, const char *msg) +{ +#ifdef MOD_RECONNECT + if( GetAutoreconnectFlag() && is_backend_first_connected ) { + SetConnBreakIcon(wgs.term_hwnd) ; + SetSSHConnected(0); + queue_toplevel_callback(close_session, NULL); + session_closed = true; + ReadInitScript(NULL); + + debug_logevent( "%s Fatal Error: %s", appname,msg ) ; + show_mouseptr(true); + + if( conf_get_int(conf,CONF_failure_reconnect) ) { + queue_toplevel_callback(close_session, NULL); + session_closed = true; + lp_eventlog(&wgs.logpolicy, "Lost connection, trying to reconnect...") ; + SetTimer(wgs.term_hwnd, TIMER_RECONNECT, GetReconnectDelay()*1000, NULL) ; + } + } else { + char *title = dupprintf("%s Fatal Error", appname); + show_mouseptr(true); + MessageBox(wgs.term_hwnd, msg, title, MB_ICONERROR | MB_OK); + sfree(title); + + if (conf_get_int(conf, CONF_close_on_exit) == FORCE_ON) + PostQuitMessage(1); + else { + queue_toplevel_callback(close_session, NULL); + session_closed = true; + } + } +#else + char *title = dupprintf("%s Fatal Error", appname); + show_mouseptr(true); + MessageBox(wgs.term_hwnd, msg, title, MB_ICONERROR | MB_OK); + sfree(title); + + if (conf_get_int(conf, CONF_close_on_exit) == FORCE_ON) + PostQuitMessage(1); + else { + queue_toplevel_callback(close_session, NULL); + } +#endif +} + +/* + * 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(wgs.term_hwnd, message, title, MB_ICONERROR | MB_OK); + sfree(message); + sfree(title); + exit(1); +} + +/* + * Actually do the job requested by a WM_NETEVENT + */ +static void wm_netevent_callback(void *vctx) +{ + struct wm_netevent_params *params = (struct wm_netevent_params *)vctx; + select_result(params->wParam, params->lParam); + sfree(vctx); +} + +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) +{ + if (conf_get_bool(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) +{ +#ifdef __LCC__ + /* + * The LCC include files apparently don't supply the + * GCP_RESULTSW type, but 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; +#else + GCP_RESULTSW 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(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, + font_varpitch ? NULL : lpDx+i, opaque); + } else { + ExtTextOutW(hdc, xp, y, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0), + lprc, lpString+i, j-i, + 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(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; + + font_varpitch = true; + 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(void) +{ + if (dpi_info.cur_dpi.x == 0 || dpi_info.cur_dpi.y == 0) { + if (p_GetDpiForMonitor) { + UINT dpiX, dpiY; + HMONITOR currentMonitor = MonitorFromWindow( + wgs.term_hwnd, MONITOR_DEFAULTTOPRIMARY); + if (p_GetDpiForMonitor(currentMonitor, MDT_EFFECTIVE_DPI, + &dpiX, &dpiY) == S_OK) { + dpi_info.cur_dpi.x = (int)dpiX; + dpi_info.cur_dpi.y = (int)dpiY; + } + } + + /* Fall back to system DPI */ + if (dpi_info.cur_dpi.x == 0 || dpi_info.cur_dpi.y == 0) { + HDC hdc = GetDC(wgs.term_hwnd); + dpi_info.cur_dpi.x = GetDeviceCaps(hdc, LOGPIXELSX); + 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(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++) + fonts[i] = NULL; + + bold_font_mode = conf_get_int(conf, CONF_bold_style) & 1 ? + BOLD_FONT : BOLD_NONE; + bold_colours = conf_get_int(conf, CONF_bold_style) & 2 ? true : false; + und_mode = UND_FONT; + + font = conf_get_fontspec(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) + font_height = pick_height; + else { + font_height = font->height; + if (font_height > 0) { + font_height = + -MulDiv(font_height, dpi_info.cur_dpi.y, 72); + } + } + font_width = pick_width; + + quality = conf_get_int(conf, CONF_font_quality); +#ifdef MOD_PERSO +#define f(i,c,w,u,t) \ + fonts[i] = CreateFont (font_height, font_width, 0, 0, w, t, 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, FALSE); +#else + +#define f(i,c,w,u) \ + fonts[i] = CreateFont (font_height, 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); +#endif + + SelectObject(hdc, fonts[FONT_NORMAL]); + GetTextMetrics(hdc, &tm); + if (GetOutlineTextMetrics(hdc, sizeof(otm), &otm)) + font_strikethrough_y = tm.tmAscent - otm.otmsStrikeoutPosition; + else + font_strikethrough_y = tm.tmAscent - (tm.tmAscent * 3 / 8); + + GetObject(fonts[FONT_NORMAL], sizeof(LOGFONT), &lfont); + + /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */ + if (!(tm.tmPitchAndFamily & TMPF_FIXED_PITCH)) { + font_varpitch = false; + font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth); + } else { + font_varpitch = true; + font_dualwidth = true; + } + if (pick_width == 0 || pick_height == 0) { + font_height = tm.tmHeight; + font_width = get_font_width(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) + ucsdata.font_codepage = GetOEMCP(); + else + if (TranslateCharsetInfo ((DWORD *)(ULONG_PTR)cset, + &info, TCI_SRCCHARSET)) + ucsdata.font_codepage = info.ciACP; + else + ucsdata.font_codepage = -1; + + GetCPInfo(ucsdata.font_codepage, &cpinfo); + ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1); + } +#ifdef MOD_PERSO + /* Check whether italic face is acceptable... */ + f(FONT_ITALIC, font->charset, fw_dontcare, FALSE, TRUE); + + { + LPOUTLINETEXTMETRICA regularOtm, italicOtm; + + use_italics = 0; + + i = GetOutlineTextMetricsA(hdc, 0, NULL); + if (i > 0) { + regularOtm = (LPOUTLINETEXTMETRICA)safemalloc(1, i, 0); + regularOtm->otmSize = sizeof(OUTLINETEXTMETRICA); + if (GetOutlineTextMetricsA(hdc, i, regularOtm)) { + /* Now get the italic version */ + SelectObject(hdc, fonts[FONT_ITALIC]); + i = GetOutlineTextMetricsA(hdc, 0, NULL); + if (i > 0) { + italicOtm = (LPOUTLINETEXTMETRICA)safemalloc(1, i, 0); + italicOtm->otmSize = sizeof(OUTLINETEXTMETRICA); + if (GetOutlineTextMetricsA(hdc, i, italicOtm)) { + /* Compare... */ + char *regStyle = (char*)regularOtm + (int)regularOtm->otmpStyleName; + char *itaStyle = (char*)italicOtm + (int)italicOtm->otmpStyleName; + + /* Weed out "italic" fonts that... + - Do not specify an italic slant (probably just the regular font) + - Have the same style as the regular font. Then it *is* just the regular + font with a linear transformation. + - Report the style name of "Oblique". + + My experience is these a) don't look very good b) tend to overhang the + next character and get cut off during paints... which doesn't look very good. */ + if (strcmp(regStyle, itaStyle) && stricmp(itaStyle, "Oblique") && italicOtm->otmItalicAngle != 0) { + use_italics = 1; + } + } + + safefree(italicOtm); + } + } + + safefree(regularOtm); + } + + if (!use_italics) { + DeleteObject(fonts[FONT_ITALIC]); + fonts[FONT_ITALIC] = NULL; + } + } + + f(FONT_UNDERLINE, font->charset, fw_dontcare, TRUE, FALSE); +#else + f(FONT_UNDERLINE, font->charset, fw_dontcare, true); +#endif + + /* + * 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, font_width, font_height); + und_oldbm = SelectObject(und_dc, und_bm); + SelectObject(und_dc, 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 < font_height; i++) { + c = GetPixel(und_dc, 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) { + und_mode = UND_LINE; + DeleteObject(fonts[FONT_UNDERLINE]); + fonts[FONT_UNDERLINE] = 0; + } + } + + if (bold_font_mode == BOLD_FONT) { +#ifdef MOD_PERSO + f(FONT_BOLD, font->charset, fw_bold, FALSE, FALSE); +#else + f(FONT_BOLD, font->charset, fw_bold, false); +#endif + } +#undef f + + descent = tm.tmAscent + 1; + if (descent >= font_height) + descent = font_height - 1; + + for (i = 0; i < 3; i++) { + if (fonts[i]) { + if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm)) + fontsize[i] = get_font_width(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, font_width*2, font_height, + LR_DEFAULTCOLOR); + + if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) { + und_mode = UND_LINE; + DeleteObject(fonts[FONT_UNDERLINE]); + fonts[FONT_UNDERLINE] = 0; + } + + if (bold_font_mode == BOLD_FONT && + fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) { + bold_font_mode = BOLD_SHADOW; + DeleteObject(fonts[FONT_BOLD]); + fonts[FONT_BOLD] = 0; + } + fontflag[0] = true; + fontflag[1] = true; + fontflag[2] = true; + + init_ucs(conf, &ucsdata); +} + +static void another_font(int fontno) +{ + int basefont; + int fw_dontcare, fw_bold, quality; +#ifdef MOD_PERSO + int c, w, x ; + bool t; +#else + int c, w, x; +#endif + bool u; + char *s; + FontSpec *font; + + if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno]) + return; + + basefont = (fontno & ~(FONT_BOLDUND)); + if (basefont != fontno && !fontflag[basefont]) + another_font(basefont); + + font = conf_get_fontspec(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 = font_width; +#ifdef MOD_PERSO + t = false; +#endif + + 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; +#ifdef MOD_PERSO + if (fontno & FONT_ITALIC && use_italics) + t = true; +#endif + + quality = conf_get_int(conf, CONF_font_quality); + + fonts[fontno] = + CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w, +#ifdef MOD_PERSO + t, u, FALSE, c, OUT_DEFAULT_PRECIS, +#else + false, u, false, c, OUT_DEFAULT_PRECIS, +#endif + CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality), + DEFAULT_PITCH | FF_DONTCARE, s); + + fontflag[fontno] = true; +} + +static void deinit_fonts(void) +{ + int i; + for (i = 0; i < FONT_MAXNO; i++) { + if (fonts[i]) + DeleteObject(fonts[i]); + fonts[i] = 0; + 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) +{ + const struct BackendVtable *vt; + int width, height; + + /* If the window is maximized suppress resizing attempts */ + if (IsZoomed(wgs.term_hwnd)) { + if (conf_get_int(conf, CONF_resize_action) == RESIZE_TERM) + return; + } + + if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED) return; + vt = backend_vt_from_proto(be_default_protocol); + if (vt && vt->flags & BACKEND_RESIZE_FORBIDDEN) + return; + if (h == term->rows && w == term->cols) return; + + /* Sanity checks ... */ + { + static int first_time = 1; + static RECT ss; + + switch (first_time) { + case 1: + /* Get the size of the screen */ + if (get_fullscreen_rect(&ss)) + /* first_time = 0 */ ; + else { + first_time = 2; + break; + } + case 0: + /* Make sure the values are sane */ + width = (ss.right - ss.left - extra_width) / 4; + height = (ss.bottom - ss.top - extra_height) / 6; + + if (w > width || h > height) + return; + if (w < 15) + w = 15; + if (h < 1) + h = 1; + } + } + + term_size(term, h, w, conf_get_int(conf, CONF_savelines)); + + if (conf_get_int(conf, CONF_resize_action) != RESIZE_FONT && + !IsZoomed(wgs.term_hwnd)) { + width = extra_width + font_width * w; + height = extra_height + font_height * h; + + SetWindowPos(wgs.term_hwnd, NULL, 0, 0, width, height, + SWP_NOACTIVATE | SWP_NOCOPYBITS | + SWP_NOMOVE | SWP_NOZORDER); + } else + reset_window(0); + + InvalidateRect(wgs.term_hwnd, NULL, true); +} + +static void recompute_window_offset(void) +{ + 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-font_width*term->cols)/2; + int new_offset_height = (win_height-font_height*term->rows)/2; + + if (offset_width != new_offset_width || + offset_height != new_offset_height) { + offset_width = new_offset_width; + offset_height = new_offset_height; + InvalidateRect(wgs.term_hwnd, NULL, true); + } +} + +static void reset_window(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(conf, CONF_resize_action); + window_border = conf_get_int(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(); + init_fonts(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(); +#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. + */ + + extra_width = wr.right - wr.left - cr.right + cr.left; + extra_height = wr.bottom - wr.top - cr.bottom + cr.top; + + if (resize_action != RESIZE_TERM) { + if (font_width != win_width/term->cols || + font_height != win_height/term->rows) { + deinit_fonts(); + init_fonts(win_width/term->cols, win_height/term->rows); + offset_width = (win_width-font_width*term->cols)/2; + offset_height = (win_height-font_height*term->rows)/2; + InvalidateRect(wgs.term_hwnd, NULL, true); +#ifdef RDB_DEBUG_PATCH + debug("reset_window() -> Z font resize to (%d, %d)\n", + font_width, font_height); +#endif + } + } else { + if (font_width * term->cols != win_width || + font_height * term->rows != win_height) { + /* Our only choice at this point is to change the + * size of the terminal; Oh well. + */ + term_size(term, win_height/font_height, win_width/font_width, + conf_get_int(conf, CONF_savelines)); + offset_width = (win_width-font_width*term->cols)/2; + offset_height = (win_height-font_height*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 = (font_width * term->cols); + if (conf_get_bool(conf, CONF_scrollbar)) + rect.right += p_GetSystemMetricsForDpi(SM_CXVSCROLL, + dpi_info.cur_dpi.x); + rect.bottom = (font_height * term->rows); + p_AdjustWindowRectExForDpi( + &rect, GetWindowLongPtr(wgs.term_hwnd, GWL_STYLE), + FALSE, GetWindowLongPtr(wgs.term_hwnd, GWL_EXSTYLE), + dpi_info.cur_dpi.x); + rect.right += (window_border * 2); + rect.bottom += (window_border * 2); + OffsetRect(&dpi_info.new_wnd_rect, + ((dpi_info.new_wnd_rect.right - dpi_info.new_wnd_rect.left) - + (rect.right - rect.left)) / 2, + ((dpi_info.new_wnd_rect.bottom - dpi_info.new_wnd_rect.top) - + (rect.bottom - rect.top)) / 2); + SetWindowPos(wgs.term_hwnd, NULL, + dpi_info.new_wnd_rect.left, 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 + + offset_width = offset_height = window_border; + extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; + extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; + + if (win_width != font_width*term->cols + offset_width*2 || + win_height != font_height*term->rows + 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, + font_width*term->cols + extra_width, + font_height*term->rows + 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) { + offset_width = offset_height = window_border; + extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2; + extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2; + + if (win_width != font_width*term->cols + offset_width*2 || + win_height != font_height*term->rows + offset_height*2) { + + static RECT ss; + int width, height; + + get_fullscreen_rect(&ss); + + width = (ss.right - ss.left - extra_width) / font_width; + height = (ss.bottom - ss.top - extra_height) / font_height; + + /* Grrr too big */ + if ( term->rows > height || term->cols > width ) { + if (resize_action == RESIZE_EITHER) { + /* Make the font the biggest we can */ + if (term->cols > width) + font_width = (ss.right - ss.left - extra_width) + / term->cols; + if (term->rows > height) + font_height = (ss.bottom - ss.top - extra_height) + / term->rows; + + deinit_fonts(); + init_fonts(font_width, font_height); + + width = (ss.right - ss.left - extra_width) / font_width; + height = (ss.bottom - ss.top - extra_height) / font_height; + } else { + if ( height > term->rows ) height = term->rows; + if ( width > term->cols ) width = term->cols; + term_size(term, height, width, + conf_get_int(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, + font_width*term->cols + extra_width, + font_height*term->rows + 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", + font_width*term->cols + extra_width, + font_height*term->rows + extra_height); +#endif + } + return; + } + + /* We're allowed to or must change the font but do we want to ? */ + + if (font_width != (win_width-window_border*2)/term->cols || + font_height != (win_height-window_border*2)/term->rows) { + + deinit_fonts(); + init_fonts((win_width-window_border*2)/term->cols, + (win_height-window_border*2)/term->rows); + offset_width = (win_width-font_width*term->cols)/2; + offset_height = (win_height-font_height*term->rows)/2; + + extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2; + extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2; + + InvalidateRect(wgs.term_hwnd, NULL, true); +#ifdef RDB_DEBUG_PATCH + debug("reset_window() -> font resize to (%d,%d)\n", + font_width, font_height); +#endif + } +} + +#ifdef MOD_PERSO +void ResetWindow(int reinit) { reset_window( reinit ) ; } +#endif + +static void set_input_locale(HKL kl) +{ + char lbuf[20]; + + GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE, + lbuf, sizeof(lbuf)); + + kbd_codepage = atoi(lbuf); +} + +static void click(Mouse_Button b, int x, int y, + bool shift, bool ctrl, bool alt) +{ + int thistime = GetMessageTime(); + + if (send_raw_mouse && + !(shift && conf_get_bool(conf, CONF_mouse_override))) { + lastbtn = MBT_NOTHING; + term_mouse(term, b, translate_button(b), MA_CLICK, + x, y, shift, ctrl, alt); + return; + } + + if (lastbtn == b && thistime - lasttime < dbltime) { + lastact = (lastact == MA_CLICK ? MA_2CLK : + lastact == MA_2CLK ? MA_3CLK : + lastact == MA_3CLK ? MA_CLICK : MA_NOTHING); + } else { + lastbtn = b; + lastact = MA_CLICK; + } + if (lastact != MA_NOTHING) + term_mouse(term, b, translate_button(b), lastact, + x, y, shift, ctrl, alt); + lasttime = thistime; +} + +/* + * Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT) + * into a cooked one (SELECT, EXTEND, PASTE). + */ +static Mouse_Button translate_button(Mouse_Button button) +{ + if (button == MBT_LEFT) + return MBT_SELECT; + if (button == MBT_MIDDLE) + return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ? + MBT_PASTE : MBT_EXTEND; + if (button == MBT_RIGHT) + return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ? + MBT_EXTEND : MBT_PASTE; + return 0; /* shouldn't happen */ +} + +static void show_mouseptr(bool show) +{ + /* NB that the counter in ShowCursor() is also frobbed by + * update_mouse_pointer() */ + static bool cursor_visible = true; + if (!conf_get_bool(conf, CONF_hide_mouseptr)) + show = true; /* override if this feature disabled */ + 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; +} +#if (!defined MOD_BACKGROUNDIMAGE) || (defined FLJ) +static bool resizing; +#endif +#ifdef MOD_PERSO +struct ctrl_tab_info { + int direction; + HWND self; + DWORD self_hi_date_time; + DWORD self_lo_date_time; + HWND next; + DWORD next_hi_date_time; + DWORD next_lo_date_time; + int next_self; +}; + +static BOOL CALLBACK CtrlTabWindowProc(HWND hwnd, LPARAM lParam) { + struct ctrl_tab_info* info = (struct ctrl_tab_info*) lParam; + char lpszClassName[256]; +#if (defined MOD_PERSO) && (!defined FLJ) + strcpy(lpszClassName,KiTTYClassName) ; +#else + strcpy(lpszClassName,appname) ; +#endif + char class_name[16]; + int wndExtra; + if (info->self != hwnd && (wndExtra = GetClassLong(hwnd, GCL_CBWNDEXTRA)) >= 8 && GetClassName(hwnd, class_name, sizeof class_name) >= 5 && memcmp(class_name, lpszClassName, 5) == 0) { + DWORD hwnd_hi_date_time = GetWindowLong(hwnd, wndExtra - 8); + DWORD hwnd_lo_date_time = GetWindowLong(hwnd, wndExtra - 4); + int hwnd_self, hwnd_next; + + hwnd_self = hwnd_hi_date_time - info->self_hi_date_time; + if (hwnd_self == 0) + hwnd_self = hwnd_lo_date_time - info->self_lo_date_time; + hwnd_self *= info->direction; + hwnd_next = hwnd_hi_date_time - info->next_hi_date_time; + if (hwnd_next == 0) + hwnd_next = hwnd_lo_date_time - info->next_lo_date_time; + hwnd_next *= info->direction; + if( ((hwnd_self > 0) && (hwnd_next < 0)) || (((hwnd_self > 0) || (hwnd_next < 0)) && (info->next_self <= 0)) ) { + info->next = hwnd; + info->next_hi_date_time = hwnd_hi_date_time; + info->next_lo_date_time = hwnd_lo_date_time; + info->next_self = hwnd_self; + } + } + return TRUE; +} +#endif + +static void win_seat_notify_remote_exit(Seat *seat) +{ + int exitcode, close_on_exit; + + if (!session_closed && + (exitcode = backend_exitcode(backend)) >= 0) { + close_on_exit = conf_get_int(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, NULL); + 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. */ +#ifdef MOD_RECONNECT + if( GetAutoreconnectFlag() && is_backend_first_connected ) { + SetConnBreakIcon(wgs.term_hwnd) ; + SetSSHConnected(0); + queue_toplevel_callback(close_session, NULL); + session_closed = true; + ReadInitScript(NULL); + show_mouseptr(true); + lp_eventlog(&wgs.logpolicy, "Connection closed by remote host"); + } else +#endif + if (exitcode != INT_MAX) { + show_mouseptr(true); + MessageBox(wgs.term_hwnd, "Connection closed by remote host", + appname, MB_OK | MB_ICONINFORMATION); + } + } + } +} + +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(wgs.term_hwnd, TIMING_TIMER_ID); + SetTimer(wgs.term_hwnd, TIMING_TIMER_ID, ticks, NULL); + timing_next_time = next; +} + +static void conf_cache_data(void) +{ + /* Cache some items from conf to speed lookups in very hot code */ + cursor_type = conf_get_int(conf, CONF_cursor_type); + vtmode = conf_get_int(conf, CONF_vtmode); +} + +static const int clips_system[] = { CLIP_SYSTEM }; +#ifdef MOD_PERSO +void kitty_term_copyall(Terminal *term) { term_copyall(term,clips_system,lenof(clips_system)) ; } +#endif + +static HDC make_hdc(void) +{ + HDC hdc; + + if (!wgs.term_hwnd) + return NULL; + + hdc = GetDC(wgs.term_hwnd); + if (!hdc) + return NULL; + + SelectPalette(hdc, pal, false); + return hdc; +} + +static void free_hdc(HDC hdc) +{ + assert(wgs.term_hwnd); + SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), false); + ReleaseDC(wgs.term_hwnd, hdc); +} + + static bool need_backend_resize = false; + +static void wm_size_resize_term(LPARAM lParam, bool border) +{ + int width = LOWORD(lParam); + int height = HIWORD(lParam); + int border_size = border ? conf_get_int(conf, CONF_window_border) : 0; + + int w = (width - border_size*2) / font_width; + int h = (height - border_size*2) / font_height; + + if (w < 1) w = 1; + if (h < 1) h = 1; + + if (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. + */ + need_backend_resize = true; + conf_set_int(conf, CONF_height, h); + conf_set_int(conf, CONF_width, w); + } else { + term_size(term, h, w, + conf_get_int(conf, CONF_savelines)); + } +} + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, + WPARAM wParam, LPARAM lParam) +{ + HDC hdc; + static bool ignore_clip = false; + static bool fullscr_on_max = false; + static bool processed_resize = false; + static bool in_scrollbar_loop = false; + static UINT last_mousemove = 0; + int resize_action; +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + */ + POINT cursor_pt; +#endif + 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 { + } + } +#ifdef MOD_PERSO +else if((UINT_PTR)wParam == TIMER_INIT) { // Initialisation + char buffer[4096] = "" ; + + // On charge automatiquement au démarrage (-edit) un fichier dans l'editeur connecté + if( !PuttyFlag ) if( LoadFile!=NULL ) { + RunPuttyEd( hwnd, LoadFile ) ; + free( LoadFile ) ; + LoadFile = NULL ; + } + + if( debug_flag ) { debug_logevent( "Starting post connection operations" ) ; } + + if( (conf_get_int(conf,CONF_protocol) == PROT_SSH) && (!is_backend_connected) ) break ; // On sort si en SSH on n'est pas connecte + // Lancement d'une (ou plusieurs separees par \\n) commande(s) automatique(s) a l'initialisation + + KillTimer( hwnd, TIMER_INIT ) ; + + // Fullscreen automatic + if( conf_get_int(conf,CONF_fullscreen) ) { PostMessage( hwnd, WM_COMMAND, IDM_FULLSCREEN, (LPARAM)NULL ) ; } + + // Maximize automatic + if( conf_get_int(conf,CONF_maximize) ) { PostMessage( hwnd, WM_SYSCOMMAND, SC_MAXIMIZE, (LPARAM)NULL ) ; } + + // Affichage d'une note de la session s'il y en a une + if( GetSessionField( conf_get_str( conf, CONF_sessionname), conf_get_str( conf, CONF_folder), "Notes", buffer ) ) + { if( strlen( buffer ) > 0 ) MessageBox( hwnd, buffer, "Notes", MB_OK ) ; } + + // On met le focus sur la fenêtre + if( first_connect ) { + if( GetVisibleFlag()!=VISIBLE_TRAY ) { + BringWindowToTop( hwnd ) ; + SetForegroundWindow( hwnd ) ; + } + } + + // Envoi automatiquement dans le systeme tray si besoin + if( GetAutoSendToTray() ) ManageToTray( hwnd ) ; + + // Si telnet on essaie d'envoyer le password + if( conf_get_int(conf,CONF_protocol) == PROT_TELNET ) { + if( strlen( conf_get_str(conf,CONF_username) ) > 0 ) { + if( strlen( conf_get_str(conf,CONF_password) ) > 0 ) { + char bufpass[1024]; strcpy(bufpass,conf_get_str(conf,CONF_password)) ; + MASKPASS(GetCryptSaltFlag(), bufpass); + strcat(buffer,bufpass); + memset(bufpass,0,strlen(bufpass)); + strcat( buffer, "\\n" ) ; + } + } + if( strlen( conf_get_str(conf,CONF_autocommand) ) > 0 ) { + strcat( buffer, "\\n\\p" ) ; + strcat( buffer, conf_get_str(conf,CONF_autocommand) ) ; + strcat( buffer, "\\n" ) ; + } + if( strlen(buffer) > 0 ) { + conf_set_str( conf, CONF_autocommand, buffer ); + } + } + + RenewPassword( conf ) ; + + // On envoie l'autocommand + if( strlen( conf_get_str(conf,CONF_autocommand) ) > 0 ) { + lp_eventlog(&wgs.logpolicy, "Start autocommand timer" ); + SetTimer(hwnd, TIMER_AUTOCOMMAND, autocommand_delay, NULL) ; + } + if( conf_get_int(conf,CONF_logtimerotation) > 0 ) { + SetTimer(hwnd, TIMER_LOGROTATION, (int)( conf_get_int(conf,CONF_logtimerotation)*1000), NULL) ; + lp_eventlog(&wgs.logpolicy, "Start log rotation" ); + } + + RefreshBackground( hwnd ) ; + + first_connect = false ; + } + +else if((UINT_PTR)wParam == TIMER_DND){ + KillTimer(hwnd, TIMER_DND) ; + recupNomFichierDragDrop(hwnd, &hDropInf) ; + if( hDropInf != NULL ) { free(hDropInf) ; hDropInf = NULL ; } + InvalidateRect( hwnd, NULL, true ) ; + } + +else if((UINT_PTR)wParam == TIMER_AUTOCOMMAND) { // Autocommand au demarrage + + KillTimer( hwnd, TIMER_AUTOCOMMAND ) ; + if( AutoCommand == NULL ) { + ValidateRect( hwnd, NULL ) ; + AutoCommand = (char*) malloc( strlen(conf_get_str(conf,CONF_autocommand))+10 ) ; + strcpy( AutoCommand, conf_get_str(conf,CONF_autocommand) ) ; + } + + + char buffer[8192] = "" ; + int i = 0 ; + while( AutoCommand[i] != '\0' ) { + if( AutoCommand[i]=='\n' ) { i++ ; break ; + } else if( (AutoCommand[i] == '\\') && (AutoCommand[i+1] == '\\') ) { + strcat( buffer, "\\\\" ) ; + i += 2 ; + } else if( (AutoCommand[i] == '\\') && (AutoCommand[i+1] == 'n') ) { i += 2 ; break ; + } else if( (AutoCommand[i] == '\\') && (AutoCommand[i+1] == 'p') ) { + strcat( buffer, "\\p" ) ; + i += 2 ; + break ; + } else if( (AutoCommand[i] == '\\') && (AutoCommand[i+1] == 's') ) { + strcat( buffer, "\\s" ) ; + i += 2 ; + buffer[i] = AutoCommand[i] ; buffer[i+1] = '\0' ; i++ ; + buffer[i] = AutoCommand[i] ; buffer[i+1] = '\0' ; i++ ; + break ; + } else { buffer[i] = AutoCommand[i] ; buffer[i+1] = '\0' ; i++ ; + } + } + + del( AutoCommand, 1, i ) ; //AutoCommand += i ; + if( strlen( buffer ) > 0 ) { SendAutoCommand( hwnd, buffer ) ; } + if( AutoCommand[0] == '\0' ) { + free( AutoCommand ) ; AutoCommand = NULL ; + InvalidateRect( hwnd, NULL, true ) ; + } + else { SetTimer(hwnd, TIMER_AUTOCOMMAND, autocommand_delay, NULL) ; } + } + +else if((UINT_PTR)wParam == TIMER_AUTOPASTE) { // AutoPaste + char buffer[4096] = "" ; + int i = 0, j ; + KillTimer( hwnd, TIMER_AUTOPASTE ) ; + if( PasteCommand == NULL ) { ValidateRect( hwnd, NULL ) ; } + else if( strlen( PasteCommand ) == 0 ) { free( PasteCommand ) ; PasteCommand = NULL ; } + else { + while( PasteCommand[0] != '\0' ) { + buffer[i] = PasteCommand[0] ; buffer[i+1] = '\0' ; + j = 0 ; do { PasteCommand[j] = PasteCommand[j+1] ; j++ ; } while( PasteCommand[j] != '\0' ) ; + if( buffer[i] == '\n' ) { buffer[i] = '\0' ; break ; } + i++ ; + } + if( strlen( buffer ) > 0 ) { + SendAutoCommand( hwnd, buffer ) ; + SetTimer(hwnd, TIMER_AUTOPASTE, autocommand_delay, NULL) ; + } + } + } +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) +else if( GetBackgroundImageFlag() && ((UINT_PTR)wParam == TIMER_SLIDEBG) ) { // Changement automatique de l'image de fond + NextBgImage( hwnd ) ; + InvalidateRect( hwnd, NULL, true ) ; + } +#endif +else if((UINT_PTR)wParam == TIMER_REDRAW) { // rafraichissement automatique (bug d'affichage) + RefreshBackground( hwnd ) ; // On inhibe cette fonction a cause du probleme de fuite memoire due a l'image de fond !!! + //InvalidateRect( hwnd, NULL, true ) ; // On remplace par + } +else if((UINT_PTR)wParam == TIMER_ANTIIDLE) { // Envoi de l'anti-idle + AntiIdleCount += 1 ; + if( AntiIdleCount >= AntiIdleCountMax ) { + AntiIdleCount = 0 ; + if(strlen( conf_get_str(conf,CONF_antiidle) ) > 0) SendAutoCommand( hwnd, conf_get_str(conf,CONF_antiidle) ) ; + else if( strlen( AntiIdleStr ) > 0 ) SendAutoCommand( hwnd, AntiIdleStr ) ; + } +#ifdef MOD_RECONNECT + if(!backend||!is_backend_connected) { // On essaie de se reconnecter en cas de problème de connexion + if ( conf_get_int(conf,CONF_failure_reconnect) && is_backend_first_connected ) { + lp_eventlog(&wgs.logpolicy, "No connection, trying to reconnect...") ; + SetTimer(hwnd, TIMER_RECONNECT, GetReconnectDelay()*1000, NULL) ; + } + break; + } +#endif + } +else if((UINT_PTR)wParam == TIMER_BLINKTRAYICON) { // Clignotement de l'icone dans le systeme tray sur reception d'un signal BELL (printf '\007' pour simuler) + static int BlinkingState = 0 ; + static HICON hBlinkingIcon = NULL ; + + if( GetVisibleFlag()!=VISIBLE_TRAY ) { + KillTimer( hwnd, TIMER_BLINKTRAYICON ) ; TrayIcone.hIcon = hBlinkingIcon ; BlinkingState = 0 ; break ; + } + if( (BlinkingState%2)==0 ) { + hBlinkingIcon = TrayIcone.hIcon ; + TrayIcone.hIcon = LoadIcon(NULL, NULL) ; + Shell_NotifyIcon(NIM_MODIFY, &TrayIcone) ; + BlinkingState++ ; + } else { + if( hBlinkingIcon ) { + TrayIcone.hIcon = hBlinkingIcon ; + Shell_NotifyIcon(NIM_MODIFY, &TrayIcone) ; + BlinkingState++ ; + if(MaxBlinkingTime) if(BlinkingState>=MaxBlinkingTime) { KillTimer( hwnd, TIMER_BLINKTRAYICON ) ; BlinkingState = 0 ; break ; } + } + } +} +#ifdef MOD_RECONNECT +else if((UINT_PTR)wParam == TIMER_RECONNECT) { + if( !backend ) { + lp_eventlog(&wgs.logpolicy, "No backend connection, reconnecting...") ; + PostMessage( hwnd, WM_COMMAND, IDM_RESTART, 0 ) ; + } + KillTimer( hwnd, TIMER_RECONNECT ) ; +} +#endif +else if((UINT_PTR)wParam == TIMER_LOGROTATION) { // log rotation + timestamp_change_filename() ; + } +#endif + return 0; + case WM_CREATE: +#ifdef MOD_PERSO + { + // Initialiation + MainHwnd = hwnd ; + if( debug_flag ) { lp_eventlog(&wgs.logpolicy,"Starting window creation") ; } + if( GetIconeFlag() != -1 ) + SetNewIcon( hwnd, conf_get_filename(conf,CONF_iconefile)->path, conf_get_int(conf,CONF_icone), SI_INIT ) ; + + // Gestion de la transparence + if( GetTransparencyFlag() && conf_get_int(conf,CONF_transparencynumber) >= 0 ) { + SetWindowLongPtr(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED ) ; + //SetLayeredWindowAttributes(hwnd, 0, TransparencyNumber, LWA_ALPHA) ; + SetTransparency( hwnd, 255-conf_get_int(conf,CONF_transparencynumber) ) ; + } + +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + if( GetIconeFlag() == -1 ) conf_set_int( conf, CONF_bg_type, 0 ) ; + if( !GetBackgroundImageFlag() ) conf_set_int( conf, CONF_bg_type, 0 ); +#endif + if( !PuttyFlag ) + if( conf_get_bool( conf,CONF_saveonexit) && conf_get_int( conf,CONF_windowstate) ) + PostMessage( hwnd, WM_SYSCOMMAND, SC_MAXIMIZE, (LPARAM)NULL ); + if( conf_get_int(conf, CONF_ctrl_tab_switch) && GetCtrlTabFlag() ) { + int wndExtra = GetClassLong(hwnd, GCL_CBWNDEXTRA); + FILETIME filetime; + GetSystemTimeAsFileTime(&filetime); + SetWindowLong(hwnd, wndExtra - 8, filetime.dwHighDateTime); + SetWindowLong(hwnd, wndExtra - 4, filetime.dwLowDateTime); + } +#ifdef MOD_WTS + WTSRegisterSessionNotification(hwnd,NOTIFY_FOR_THIS_SESSION); +#endif + //SetFocus(hwnd); + //PostMessage(hwnd,WM_SETFOCUS,0,0); + } +#endif + break; + case WM_CLOSE: { +#ifdef MOD_PERSO + if( GetProtectFlag() ) { + MessageBox(hwnd, + "You are not allowed to close a protected window", + "Exit warning", MB_ICONERROR | MB_OK ) ; + return 0 ; + } + char *str; + show_mouseptr(1); + str = dupprintf("%s Exit Confirmation", appname); + +#ifdef MOD_RUTTY +/* rutty: */ + if (scriptdata.runs) + { + if (session_closed || + MessageBox(hwnd, + "session scripting is running !\n Are you sure you want to close this session?", + str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1) == IDOK) + { + script_close(&scriptdata); + script_record_stop(&scriptdata); + DestroyWindow(hwnd); + } + } + else +#endif /* rutty */ + if ( !conf_get_bool(conf,CONF_warn_on_close) || session_closed || + MessageBox(hwnd, + "Are you sure you want to close this session?", + str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1) + == IDOK) { +#ifdef MOD_RUTTY + script_record_stop(&scriptdata); +#endif + if( strlen( conf_get_str(conf,CONF_autocommandout) ) > 0 ) { //Envoie d'une command automatique en sortant + SendAutoCommand( hwnd, conf_get_str(conf,CONF_autocommandout) ) ; + conf_set_str(conf,CONF_autocommandout,"" ); + } + DestroyWindow(hwnd); + } + sfree(str); +#else + char *title, *msg, *additional = NULL; + show_mouseptr(true); + title = dupprintf("%s Exit Confirmation", appname); + if (backend && backend->vt->close_warn_text) { + additional = backend->vt->close_warn_text(backend); + } + msg = dupprintf("Are you sure you want to close this session?%s%s", + additional ? "\n" : "", + additional ? additional : ""); + if (session_closed || !conf_get_bool(conf, CONF_warn_on_close) || + MessageBox(hwnd, msg, title, + MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1) + == IDOK) + DestroyWindow(hwnd); + sfree(title); + sfree(msg); + sfree(additional); +#endif + return 0; + } + case WM_DESTROY: + show_mouseptr(true); + PostQuitMessage(0); + return 0; + case WM_INITMENUPOPUP: + if ((HMENU)wParam == 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(); + return 0; + } + break; + case WM_COMMAND: + case WM_SYSCOMMAND: +#ifdef MOD_PERSO + if( strlen( conf_get_str(conf,CONF_folder) )>0 ) SetInitCurrentFolder( conf_get_str(conf,CONF_folder) ); + if( (wParam>=IDM_USERCMD)&&(wParam<(IDM_USERCMD+NB_MENU_MAX)) ) { + ManageSpecialCommand( hwnd, wParam-IDM_USERCMD ) ; + break ; + } +#endif + 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. */ + in_scrollbar_loop = true; + LRESULT result = DefWindowProcW(hwnd, message, wParam, lParam); + 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]; +#ifdef MOD_PERSO + char *cl=NULL; +#else + char *cl; +#endif + 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(); +#ifdef MOD_PERSO +/*On gère le répertoire distant*/ +char *cmd, *cmd2=NULL ; +cmd = (char*)malloc( strlen(conf_get_str(conf,CONF_autocommand))+1 ) ; +strcpy( cmd, conf_get_str(conf,CONF_autocommand) ) ; + +if( GetRemotePath()!=NULL ) { + cmd2 = (char*)malloc( strlen(conf_get_str(conf,CONF_autocommand))+strlen(GetRemotePath())+10 ) ; + strcpy( cmd2, cmd ) ; + strcat(cmd2,"\n"); + strcat(cmd2,"cd "); + strcat(cmd2, GetRemotePath()); +} else { + cmd2 = (char*)malloc( strlen(conf_get_str(conf,CONF_autocommand))+1 ) ; + strcpy( cmd2, cmd ) ; +} +conf_set_str( conf,CONF_autocommand,cmd2 ) ; +#endif + conf_serialise(BinarySink_UPCAST(serbuf), 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); + } + } + +#ifdef MOD_PERSO +conf_set_str( conf,CONF_autocommand,cmd ) ; +free(cmd2); +free(cmd); +#endif + 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); +#ifdef MOD_PERSO + GetSessionFolderName( conf_get_str(conf,CONF_sessionname), conf_get_str(conf,CONF_folder) ) ; + if( GetDirectoryBrowseFlag() ) if( strcmp(conf_get_str(conf,CONF_folder), "")&&strcmp(conf_get_str(conf,CONF_folder),"Default") ) + { strcat( cl, " -folder \"" ) ; strcat( cl, conf_get_str(conf,CONF_folder) ) ; strcat( cl, "\"" ) ; } +#endif + inherit_handles = false; + } else + break; +#ifdef MOD_PERSO + } else /* IDM_NEWSESS */ { + if( strcmp(conf_get_str(conf,CONF_folder), "") && strcmp(conf_get_str(conf,CONF_folder),"Default") ) { + cl = realloc( cl, 20 + strlen(conf_get_str(conf,CONF_folder)) ) ; + sprintf(cl, "putty -folder \"%s\"", conf_get_str(conf,CONF_folder) ) ; + } else { if( cl==NULL ) { cl = (char*)malloc(2) ; cl[0]='\0' ; } } +#else + } else /* IDM_NEWSESS */ { + cl = dupprintf("putty%s%s", + *argprefix ? " " : "", + argprefix); +#endif + 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 (!backend) { + lp_eventlog(&wgs.logpolicy, "----- Session restarted -----"); + term_pwron(term, false); +#ifdef MOD_PERSO +#ifdef MOD_PORTKNOCKING + ManagePortKnocking(conf_get_str(conf,CONF_host),conf_get_str(conf,CONF_portknockingoptions)); +#endif +#ifdef MOD_RECONNECT + SetNewIcon( hwnd, conf_get_filename(conf,CONF_iconefile)->path, 0, SI_INIT ) ; + is_backend_connected = 0 ; + start_backend(); + //if( (!backend || !is_backend_connected) && GetAutoreconnectFlag() ) { + if( !backend ) { + if ( conf_get_int(conf,CONF_failure_reconnect) && is_backend_first_connected ) { + SetTimer(hwnd, TIMER_RECONNECT, GetReconnectDelay()*1000, NULL) ; + lp_eventlog(&wgs.logpolicy, "Unable to connect, trying to reconnect...") ; + } + break; + } +#else + is_backend_connected = 0 ; + start_backend(); +#endif + is_backend_connected = 1 ; + SetTimer(hwnd, TIMER_INIT, init_delay, NULL) ; +#else + start_backend(); +#endif + } + + break; + case IDM_RECONF: { + Conf *prev_conf; + int init_lvl = 1; + bool reconfig_result; + if (reconfiguring) + break; + else + reconfiguring = true; +#ifndef MOD_PERSO + char buftitle[1024] = "" ; + GetWindowText(hwnd, buftitle, sizeof(buftitle)); + conf_set_str( conf, CONF_wintitle, buftitle ) ; + + term_pre_reconfig(term, conf); +#endif + prev_conf = conf_copy(conf); +#ifdef MOD_PERSO + if( force_reconf++ ) { + reconfig_result = + do_reconfig(hwnd, conf, backend ? backend_cfg_info(backend) : 0); + reconfiguring = false; + if (!reconfig_result) { + conf_free(prev_conf); + break; + } + } else { + reconfig_result = true ; + reconfiguring = false ; + } + SaveRegistryKey() ; +#else + reconfig_result = do_reconfig( + hwnd, conf, backend ? backend_cfg_info(backend) : 0); + reconfiguring = false; + if (!reconfig_result) { + conf_free(prev_conf); + break; + } +#endif +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + if( GetBackgroundImageFlag() && (!GetPuttyFlag()) ) { + if(textdc) { + DeleteObject(textbm); + DeleteDC(textdc); + textdc = NULL; + textbm = NULL; + } + if(backgrounddc) { + DeleteObject(backgroundbm); + DeleteDC(backgrounddc); + backgrounddc = NULL; + backgroundbm = NULL; + } + if(backgroundblenddc) { + DeleteObject(backgroundblendbm); + DeleteDC(backgroundblenddc); + backgroundblenddc = NULL; + backgroundblendbm = NULL; + } + } +#endif + + conf_cache_data(); + + resize_action = conf_get_int(conf, CONF_resize_action); + { + /* Disable full-screen if resizing forbidden */ + int i; + for (i = 0; i < lenof(popup_menus); i++) + EnableMenuItem(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); +#ifdef MOD_TUTTY + HMENU m = GetSystemMenu (hwnd, FALSE); + EnableMenuItem(m, SC_CLOSE, conf_get_int(conf,CONF_window_closable) ? MF_ENABLED : MF_GRAYED); +#endif + + } + + /* Pass new config data to the logging module */ + log_reconfig(logctx, conf); + + sfree(logpal); + /* + * Flush the line discipline's edit buffer in the + * case where local editing has just been disabled. + */ + if (ldisc) { + ldisc_configure(ldisc, conf); + ldisc_echoedit_update(ldisc); + } + if (conf_get_bool(conf, CONF_system_colour) != + conf_get_bool(prev_conf, CONF_system_colour)) + term_notify_palette_changed(term); + + /* Pass new config data to the terminal */ + term_reconfig(term, conf); + setup_clipboards(term, conf); + + /* Reinitialise the colour palette, in case the terminal + * just read new settings out of Conf */ + if (pal) + DeleteObject(pal); + logpal = NULL; + pal = NULL; + init_palette(); + + /* Pass new config data to the back end */ + if (backend) + backend_reconfig(backend, conf); +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + * Reconfigure + */ + if( !GetPuttyFlag() && GetHyperlinkFlag() ) { + if( conf_get_int(conf, CONF_url_defregex) != 0 ) { + urlhack_set_regular_expression(URLHACK_REGEX_CLASSIC, conf_get_str(term->conf,CONF_url_regex) ) ; + } else { + urlhack_set_regular_expression(URLHACK_REGEX_CUSTOM, conf_get_str(term->conf, CONF_url_regex) ) ; + } + term->url_update = TRUE; + term_update(term); + urlhack_enable(); + } +#endif + + /* Screen size changed ? */ + if (conf_get_int(conf, CONF_height) != + conf_get_int(prev_conf, CONF_height) || + conf_get_int(conf, CONF_width) != + conf_get_int(prev_conf, CONF_width) || + conf_get_int(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(term, conf_get_int(conf, CONF_height), + conf_get_int(conf, CONF_width), + conf_get_int(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(conf, CONF_alwaysontop) != + conf_get_bool(prev_conf, CONF_alwaysontop)) { + if (conf_get_bool(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(conf, CONF_sunken_edge)) + nexflag |= WS_EX_CLIENTEDGE; + else + nexflag &= ~(WS_EX_CLIENTEDGE); + + nflg = flag; + if (conf_get_bool(conf, is_full_screen() ? + CONF_scrollbar_in_fullscreen : + CONF_scrollbar)) + nflg |= WS_VSCROLL; + else + nflg &= ~WS_VSCROLL; + + if (resize_action == RESIZE_DISABLED || + is_full_screen()) + nflg &= ~WS_THICKFRAME; + else + nflg |= WS_THICKFRAME; + +#ifdef MOD_TUTTY + if (!conf_get_int(conf,CONF_window_has_sysmenu)) + nflg &= ~WS_SYSMENU; + else + nflg |= WS_SYSMENU; + if (!conf_get_int(conf,CONF_window_minimizable)) + nflg &= ~WS_MINIMIZEBOX; + else + nflg |= WS_MINIMIZEBOX; + if (resize_action == RESIZE_DISABLED || !conf_get_int(conf,CONF_window_maximizable)) +#else + if (resize_action == RESIZE_DISABLED) +#endif + 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(conf, CONF_font); + FontSpec *prev_font = conf_get_fontspec(prev_conf, + CONF_font); + + if (!strcmp(font->name, prev_font->name) || + !strcmp(conf_get_str(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(conf, CONF_font_quality) != + conf_get_int(prev_conf, CONF_font_quality) || + conf_get_int(conf, CONF_vtmode) != + conf_get_int(prev_conf, CONF_vtmode) || + conf_get_int(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(init_lvl); +#ifdef MOD_PERSO + if( GetIconeFlag() != -1 ) { + SetNewIcon( hwnd, conf_get_filename(conf,CONF_iconefile)->path, conf_get_int(conf,CONF_icone), SI_INIT ) ; + } + { + HMENU m = GetSystemMenu(hwnd, FALSE); + if( conf_get_int(conf, CONF_logtype)!=LGTYP_NONE ) { + EnableMenuItem(m, IDM_CLEARLOGFILE, MF_BYCOMMAND|MF_ENABLED) ; + } else { + EnableMenuItem(m, IDM_CLEARLOGFILE, MF_BYCOMMAND|MF_DISABLED|MF_GRAYED) ; + } + } +#endif + + conf_free(prev_conf); + break; + } + case IDM_COPYALL: + term_copyall(term, clips_system, lenof(clips_system)); + break; + case IDM_COPY: + term_request_copy(term, clips_system, lenof(clips_system)); + break; + case IDM_PASTE: +#ifdef MOD_PERSO + if( !GetProtectFlag() ) +#endif + term_request_paste(term, CLIP_SYSTEM); + break; + case IDM_CLRSB: + term_clrsb(term); + break; + case IDM_RESET: + term_pwron(term, true); + if (ldisc) + ldisc_echoedit_update(ldisc); + break; + case IDM_ABOUT: + showabout(hwnd); + break; +/* rutty: */ +#ifdef MOD_RUTTY + case IDM_SCRIPTHALT: + script_close(&scriptdata); + lp_eventlog(&wgs.logpolicy, "script stopped"); + break; + case IDM_SCRIPTSEND: + { + char scriptfilename[FILENAME_MAX]; + Filename * scriptfile; + if(prompt_scriptfile(hwnd, scriptfilename)) + { + script_init(&scriptdata, conf); + scriptfile = filename_from_str(scriptfilename); + script_sendfile(&scriptdata, scriptfile); + filename_free(scriptfile); + } + } + break; +#endif /* rutty */ +#ifdef MOD_PERSO + case IDM_NEWDUPSESS: + conf_set_str( conf, CONF_host_alt, conf_get_str( conf, CONF_host ) ) ; + RunSessionWithCurrentSettings( hwnd, conf, "", NULL, NULL, 0, NULL ) ; + break; + /*case IDM_USERCMD: + ManageSpecialCommand( hwnd, wParam-IDM_USERCMD ) ; + break ;*/ + case IDM_QUIT: + SendMessage( hwnd, MYWM_NOTIFYICON, 0, WM_LBUTTONDBLCLK ) ; + PostQuitMessage( 0 ) ; + break ; + case IDM_PROTECT: + ManageProtect( hwnd, wintw, conf_get_str(conf, CONF_wintitle) ) ; + break ; + case IDM_VISIBLE: + ManageVisible( hwnd, wintw, conf_get_str(conf, CONF_wintitle) ) ; + break ; + case IDM_SHORTCUTSTOGGLE: + ManageShortcutsFlag( hwnd ) ; + break ; + case IDM_TOTRAY: + if( GetVisibleFlag()==VISIBLE_YES ) { + SetVisibleFlag( VISIBLE_TRAY ) ; + return ManageToTray( hwnd ) ; + } + break ; + case IDM_FROMTRAY:{ + if( GetVisibleFlag()==VISIBLE_TRAY ) { + SetVisibleFlag( VISIBLE_YES ) ; + return SendMessage( hwnd, MYWM_NOTIFYICON, 0, WM_LBUTTONDBLCLK ) ; + } + } + break ; +#ifdef MOD_LAUNCHER + case IDM_HIDE: + if( GetVisibleFlag()==VISIBLE_YES ) { + /*if (IsWindowVisible(hwnd) )*/ ShowWindow(hwnd, SW_HIDE) ; + SetVisibleFlag( VISIBLE_NO ) ; + } + break ; + case IDM_UNHIDE: + if( GetVisibleFlag()==VISIBLE_NO ) { + ShowWindow(hwnd, SW_RESTORE ) ; + SetVisibleFlag( VISIBLE_YES ) ; + } + break ; + case IDM_SWITCH_HIDE: + if( GetVisibleFlag()==VISIBLE_YES ) SendMessage( hwnd, WM_COMMAND, IDM_HIDE, 0 ) ; + else if( GetVisibleFlag()==VISIBLE_NO ) SendMessage( hwnd, WM_COMMAND, IDM_UNHIDE, 0 ) ; + break ; + case IDM_GONEXT: + GoNext( hwnd ) ; + break ; + case IDM_GOPREVIOUS: + GoPrevious( hwnd ) ; + break ; +#endif + case IDM_WINROL: + if( GetWinrolFlag() ) { + ManageWinrol( hwnd, conf_get_int(conf,CONF_resize_action) ) ; + } + break ; + case IDM_SCRIPTFILE : + OpenAndSendScriptFile( hwnd ) ; + break ; + case IDM_EXPORTSETTINGS : + SaveCurrentSetting( hwnd ) ; + break ; + case IDM_PSCP: + SendFile( hwnd ) ; + break ; + case IDM_WINSCP: + StartWinSCP( hwnd, NULL, NULL, NULL ) ; + break ; + case IDM_PRINT: + ManagePrint( hwnd ) ; + break ; + case IDM_FONTUP: + ChangeFontSize(term,conf,hwnd,1) ; + break ; + case IDM_FONTDOWN: + ChangeFontSize(term,conf,hwnd,-1) ; + break ; + case IDM_FONTBLACKANDWHITE: + BlackOnWhiteColours(hwnd) ; + break ; + case IDM_FONTNEGATIVE: + NegativeColours(hwnd) ; + break ; + case IDM_CLEARLOGFILE: + if( conf_get_int(conf, CONF_logtype)!=LGTYP_NONE ) { + logfile_reinit(logctx); + } + break; +#ifdef MOD_PORTKNOCKING + case IDM_PORTKNOCK: + ManagePortKnocking(conf_get_str(conf,CONF_host),conf_get_str(conf,CONF_portknockingoptions)); + break; +#endif + case IDM_SHOWPORTFWD: + ShowPortfwd( hwnd, conf ) ; + break ; +#ifndef MOD_NOTRANSPARENCY + case IDM_TRANSPARUP: // Augmenter l'opacite (diminuer la transparence) + if( GetTransparencyFlag() && conf_get_int(conf,CONF_transparencynumber) > 0 ) { + conf_set_int( conf, CONF_transparencynumber, conf_get_int(conf,CONF_transparencynumber)-10 ) ; + if( conf_get_int(conf,CONF_transparencynumber)<0) conf_set_int( conf, CONF_transparencynumber, 0 ); + SetTransparency( hwnd, 255-conf_get_int(conf,CONF_transparencynumber) ) ; + } + break ; + case IDM_TRANSPARDOWN: // Diminuer l'opacite (augmenter la transparence) + if( GetTransparencyFlag() && (conf_get_int(conf,CONF_transparencynumber)!=-1) && (conf_get_int(conf,CONF_transparencynumber)<255) ) { + if( conf_get_int(conf,CONF_transparencynumber)==245 ) MessageBox( hwnd, " KiTTY made by \r\nCyril Dupont\r\nLeonard Nero", "About", MB_OK ) ; + conf_set_int( conf, CONF_transparencynumber, conf_get_int(conf,CONF_transparencynumber)+10 ) ; + if( conf_get_int(conf,CONF_transparencynumber)>255) conf_set_int( conf, CONF_transparencynumber, 255 ) ; + SetTransparency( hwnd, 255-conf_get_int(conf,CONF_transparencynumber) ) ; + } + break ; +#endif + case IDM_RESIZE: //Redmensionner + { + int w = LOWORD( lParam ), h = HIWORD( lParam ) ; + if (w < 1) { w = 1 ; } if (h < 1) { h = 1 ; } + conf_set_int( conf, CONF_width, w ) ; + conf_set_int( conf, CONF_height, h ) ; + term_size( term, h, w, conf_get_int( conf, CONF_savelines) ) ; + reset_window(0); + } + break ; + case IDM_REPOS: //Redmensionner + { + int x = LOWORD( lParam ), y = HIWORD( lParam ) ; + if (x < 1) { x = 1 ; } if (y < 1) { y = 1 ; } + conf_set_int( conf, CONF_xpos, x ) ; + conf_set_int( conf, CONF_ypos, y ) ; + SetWindowPos( hwnd, 0, x, y, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOACTIVATE ) ; + //MoveWindow(hwnd, x, y, 0, 0, TRUE ) ; + //reset_window(0); + } + break ; +#endif +#ifdef MOD_RECONNECT + case IDM_RESTARTSESSION: // Redemarre une session en cours + if(!is_backend_connected) { + PostMessage( hwnd, WM_COMMAND, IDM_RESTART, 0 ) ; + } else { + RestartSession() ; + } + break ; +#endif + + 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(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(true); /* make sure pointer is visible */ + if( lParam == 0 ) + PostMessage(hwnd, WM_CHAR, ' ', 0); + break; + case IDM_FULLSCREEN: + flip_full_screen(); + break; +#ifdef MOD_ZMODEM + case IDM_XYZSTART: + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + xyz_ReceiveInit(term); + xyz_updateMenuItems(term); + } + break; + case IDM_XYZUPLOAD: + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + xyz_StartSending(term); + xyz_updateMenuItems(term); + } + break; + case IDM_XYZABORT: + xyz_Cancel(term); + xyz_updateMenuItems(term); + break; +#endif + 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 >= n_specials) + break; + if (backend) + backend_special( + backend, specials[i].code, specials[i].arg); + } + } + break; +#ifdef MOD_PERSO + case WM_DROPFILES: { + OnDropFiles(hwnd, (HDROP) wParam); + } + break; +/* + case WM_LBUTTONDBLCLK: { + URLclick( hwnd ) ; + } + break; +*/ + case MYWM_NOTIFYICON : + switch (lParam) { + case WM_LBUTTONDBLCLK : + SetVisibleFlag( VISIBLE_YES ) ; + //ShowWindow(hwnd, SW_SHOWNORMAL); + ShowWindow(hwnd, SW_RESTORE); + SetForegroundWindow( hwnd ) ; + int ResShell; + ResShell = Shell_NotifyIcon(NIM_DELETE, &TrayIcone); + if( ResShell ) return 1 ; + else return 0 ; + break ; + case WM_RBUTTONUP: + case WM_LBUTTONUP: + DisplaySystemTrayMenu( hwnd ) ; + break ; + } + break ; + case WM_NCLBUTTONDBLCLK: + return DefWindowProc(hwnd, message, wParam, lParam) ; + break ; + case WM_NCLBUTTONDOWN: + if( ( GetKeyState( VK_CONTROL ) & 0x8000 ) && ( wParam == HTCAPTION ) ) { + if( GetWinrolFlag() ) { ManageWinrol( hwnd, conf_get_int(conf,CONF_resize_action) ) ; } + } + else return DefWindowProc(hwnd, message, wParam, lParam); + break; + + case WM_COPYDATA: { // Reception d'un de donnees dans un message + PCOPYDATASTRUCT pMyCDS = (PCOPYDATASTRUCT) lParam; + switch( pMyCDS->dwData ) { + case 1: // Reception d'une chaine de caracteres a envoyer dans le terminal + if( pMyCDS->cbData > 0 ) { + SendKeyboardPlus( hwnd, (char*)pMyCDS->lpData ) ; + } + break ; + } + } + break ; + +#endif +#ifdef MOD_WTS + case WM_WTSSESSION_CHANGE: + if( wParam == WTS_SESSION_UNLOCK ) { + RefreshBackground(hwnd); + } + break ; +#endif + +#define X_POS(l) ((int)(short)LOWORD(l)) +#define Y_POS(l) ((int)(short)HIWORD(l)) + +#define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width) +#define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height) + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: +#ifdef MOD_RECONNECT + if( ((!backend) || (!is_backend_connected)) && GetAutoreconnectFlag() && is_backend_first_connected ) { // trying to reconnect + PostMessage( hwnd, WM_COMMAND, IDM_RESTART, 0 ) ; + lp_eventlog(&wgs.logpolicy, "No connection on mouse click, trying to reconnect...") ; + break ; + } +#endif + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: +#ifdef MOD_PERSO + if( GetProtectFlag() ) { break ; } + if(!PuttyFlag && GetMouseShortcutsFlag() ) { + if( (message == WM_LBUTTONUP) && ((wParam & MK_SHIFT) && (wParam & MK_CONTROL) ) ) { // shift + CTRL + left button => duplicate session + if( backend && is_backend_connected ) { + lp_eventlog(&wgs.logpolicy, "Duplicate session") ; + SendMessage( hwnd, WM_COMMAND, IDM_DUPSESS, 0 ) ; + } +#ifdef MOD_RECONNECT + else { + if( is_backend_first_connected && GetAutoreconnectFlag() ) { + SendMessage( hwnd, WM_COMMAND, IDM_RESTART, 0 ) ; + lp_eventlog(&wgs.logpolicy, "No connection on mouse click, trying to reconnect...") ; + } + } +#endif + break ; + } + + else if (message == WM_LBUTTONUP && ((wParam & MK_CONTROL) ) ) { // ctrl + left button => new icon ? + if( !GetHyperlinkFlag() || !conf_get_int(conf,CONF_url_ctrl_click) ) { // if no hyperlink feature or if ctrl + link is disable + if( GetIconeFlag() != -1 ) { + SetNewIcon( hwnd, conf_get_filename(conf,CONF_iconefile)->path, conf_get_int(conf,CONF_icone), SI_NEXT ) ; + } + break ; + } else { + if( !urlhack_is_in_link_region(TO_CHR_X(cursor_pt.x), TO_CHR_Y(cursor_pt.y)) ) { // If the is no hyperlink under mouse + if( GetIconeFlag() != -1 ) { + SetNewIcon( hwnd, conf_get_filename(conf,CONF_iconefile)->path, conf_get_int(conf,CONF_icone), SI_NEXT ) ; + } + break ; + } + } + } + + else if (message == WM_MBUTTONUP && ((wParam & MK_CONTROL) ) ) { // ctrl+bouton milieu => send to tray + SendMessage( hwnd, WM_COMMAND, IDM_TOTRAY, 0 ) ; + break ; + } + + else if ( GetPasteCommandFlag() && (message == WM_RBUTTONDOWN) && ((wParam & MK_SHIFT) ) ) {// shift+bouton droit => coller (paste) ameliore (pour serveur lent, on utilise la methode autocommand par TIMER) + SetPasteCommand(wgs.term_hwnd) ; + break ; + } + } +#endif + if (message == WM_RBUTTONDOWN && + ((wParam & MK_CONTROL) || + (conf_get_int(conf, CONF_mouse_is_xterm) == 2))) { + POINT cursorpos; + + show_mouseptr(true); /* make sure pointer is visible */ + GetCursorPos(&cursorpos); + TrackPopupMenu(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: +#ifdef MOD_PERSO + if( !GetProtectFlag() ) { button = MBT_RIGHT ; wParam |= MK_RBUTTON ; press = true ; } +#else + button = MBT_RIGHT; + wParam |= MK_RBUTTON; + press = true; +#endif + 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: +#ifdef MOD_PERSO + if( !GetProtectFlag() ) { button = MBT_RIGHT ; wParam &= ~MK_RBUTTON ; press = false ; } +#else + button = MBT_RIGHT; + wParam &= ~MK_RBUTTON; + press = false; +#endif + break; + default: /* shouldn't happen */ + button = 0; + press = false; + } + show_mouseptr(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 + { + HMONITOR mon; + MONITORINFO mi; + + mon = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL); + + if (mon != NULL) { + mi.cbSize = sizeof(MONITORINFO); + GetMonitorInfo(mon, &mi); + + if (mi.rcMonitor.left == pt.x && + mi.rcMonitor.top == pt.y) { + mouse_on_hotspot = true; + } + } + } +#else + if (pt.x == 0 && pt.y == 0) { + mouse_on_hotspot = true; + } +#endif + if (is_full_screen() && press && + button == MBT_LEFT && mouse_on_hotspot) { + SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU, + MAKELPARAM(pt.x, pt.y)); + return 0; + } + } + + if (press) { + click(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(term, button, translate_button(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. + */ + static WPARAM wp = 0; + static LPARAM lp = 0; + if (wParam != wp || lParam != lp || + last_mousemove != WM_MOUSEMOVE) { + show_mouseptr(true); + wp = wParam; lp = lParam; + last_mousemove = WM_MOUSEMOVE; + } + /* + * Add the mouse position and message time to the random + * number noise. + */ + noise_ultralight(NOISE_SOURCE_MOUSEPOS, lParam); +#ifdef MOD_HYPERLINK + if( !GetPuttyFlag() && GetHyperlinkFlag() ) { + /* + * HACK: PuttyTray / Nutty + * Hyperlink stuff: Change cursor type if hovering over link + */ + if (urlhack_mouse_old_x != TO_CHR_X(X_POS(lParam)) || urlhack_mouse_old_y != TO_CHR_Y(Y_POS(lParam))) { + urlhack_mouse_old_x = TO_CHR_X(X_POS(lParam)); + urlhack_mouse_old_y = TO_CHR_Y(Y_POS(lParam)); + if ((!conf_get_int(term->conf, CONF_url_ctrl_click) || urlhack_is_ctrl_pressed()) && + urlhack_is_in_link_region(urlhack_mouse_old_x, urlhack_mouse_old_y)) { + if (urlhack_cursor_is_hand == 0) { + SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG_PTR)LoadCursor(NULL, IDC_HAND)); + urlhack_cursor_is_hand = 1; + term_update(term); // Force the terminal to update, otherwise the underline will not show (bug somewhere, this is an ugly fix) + } + } + else if (urlhack_cursor_is_hand == 1) { + SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG_PTR)LoadCursor(NULL, IDC_IBEAM)); + urlhack_cursor_is_hand = 0; + term_update(term); // Force the terminal to update, see above + } + // If mouse jumps from one link directly into another, we need a forced terminal update too + if (urlhack_is_in_link_region(urlhack_mouse_old_x, urlhack_mouse_old_y) != urlhack_current_region) { + urlhack_current_region = urlhack_is_in_link_region(urlhack_mouse_old_x, urlhack_mouse_old_y); + term_update(term); + } + } + /* HACK: PuttyTray / Nutty : END */ + } +#endif +#ifdef MOD_PERSO + { + // debug("X %d Y %d => %d:%d\n", X_POS(lParam), Y_POS(lParam), TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam))); + Mouse_Button b; + if (wParam & MK_LBUTTON) + b = MBT_LEFT; + else if (wParam & MK_MBUTTON) + b = MBT_MIDDLE; + else if (wParam & MK_RBUTTON) + b = MBT_RIGHT; + else + b = MBT_NOTHING; + term_mouse(term, b, translate_button(b), + b == MBT_NOTHING ? MA_MOVE : MA_DRAG, + TO_CHR_X(X_POS(lParam)), + TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT, + wParam & MK_CONTROL, is_alt_pressed()); +#else + 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(term, b, translate_button(b), MA_DRAG, + TO_CHR_X(X_POS(lParam)), + TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT, + wParam & MK_CONTROL, is_alt_pressed()); +#endif + } + return 0; + } + case WM_NCMOUSEMOVE: { + static WPARAM wp = 0; + static LPARAM lp = 0; + if (wParam != wp || lParam != lp || + last_mousemove != WM_NCMOUSEMOVE) { + show_mouseptr(true); + wp = wParam; lp = lParam; + last_mousemove = WM_NCMOUSEMOVE; + } + noise_ultralight(NOISE_SOURCE_MOUSEPOS, lParam); + break; + } + case WM_IGNORE_CLIP: + ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */ + break; + case WM_DESTROYCLIPBOARD: +#ifdef MOD_FAR2L + /* far2l extensions support */ + + // In far2l extensions mode we should not do anything here, + // clipboard is handled by far2l extensions. + if (!(term->far2l_ext == 1) || (!term->clip_allowed)) { + if (!ignore_clip) + term_lost_clipboard_ownership(term, CLIP_SYSTEM); + ignore_clip = false; + } + + return 0; +#else + if (!ignore_clip) + term_lost_clipboard_ownership(term, CLIP_SYSTEM); + ignore_clip = false; + return 0; +#endif + case WM_PAINT: { + PAINTSTRUCT p; +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + HDC hdccod=0, hdcScreen, hdcBack = 0; + if( GetBackgroundImageFlag() && (!GetPuttyFlag()) ) { + HideCaret(hwnd); + hdcScreen = BeginPaint(hwnd, &p); + + // We'll draw into a temporary buffer then copy to the screen. After + // this point, the rest of this routine is written to use hdc and not + // care whether hdc is a screen or back buffer, until the very end, + // where the back buffer, if it exists, is blitted to the screen. That + // keeps the routine flexible for use with different drawing policies, + // though right now the only policy we ever use is the one implied by + // the next line, where we always use a back buffer. + //hdc = hdcBack = CreateCompatibleDC(hdcScreen); + hdccod = hdcScreen; + + if(pal) { + SelectPalette(hdcScreen, pal, true); + RealizePalette(hdcScreen); + } + } else { + HideCaret(hwnd); + hdc = BeginPaint(hwnd, &p); + if (pal) { + SelectPalette(hdc, pal, true); + RealizePalette(hdc); + } + } +#else + HideCaret(hwnd); + hdc = BeginPaint(hwnd, &p); + if (pal) { + SelectPalette(hdc, pal, true); + RealizePalette(hdc); + } +#endif + + /* + * 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(!wintw_hdc); + wintw_hdc = hdc; +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + if( GetBackgroundImageFlag() && (!GetPuttyFlag()) ) { + term_paint(term, + (p.rcPaint.left-offset_width)/font_width, + (p.rcPaint.top-offset_height)/font_height, + (p.rcPaint.right-offset_width-1)/font_width, + (p.rcPaint.bottom-offset_height-1)/font_height, + !term->window_update_pending); + + SelectObject(hdccod, GetStockObject(SYSTEM_FONT)); + SelectObject(hdccod, GetStockObject(WHITE_PEN)); + + if(hdcBack) { + // Blit the back buffer to the real DC. + BitBlt( + hdcScreen, + p.rcPaint.left - offset_width, + p.rcPaint.top - offset_height, + p.rcPaint.right - p.rcPaint.left + offset_width, + p.rcPaint.bottom - p.rcPaint.top + offset_height, + hdcBack, p.rcPaint.left, p.rcPaint.top, SRCCOPY + ); + + DeleteDC(hdcBack); + hdccod = hdcScreen; + } + + // Last paint edges + paint_term_edges(term,hdccod, p.rcPaint.left, p.rcPaint.top, p.rcPaint.right, p.rcPaint.bottom); + + EndPaint(hwnd, &p); + ShowCaret(hwnd); + } else { +#endif + term_paint(term, + (p.rcPaint.left-offset_width)/font_width, + (p.rcPaint.top-offset_height)/font_height, + (p.rcPaint.right-offset_width-1)/font_width, + (p.rcPaint.bottom-offset_height-1)/font_height, + !term->window_update_pending); + wintw_hdc = NULL; + + if (p.fErase || + p.rcPaint.left < offset_width || + p.rcPaint.top < offset_height || + p.rcPaint.right >= offset_width + font_width*term->cols || + p.rcPaint.bottom>= offset_height + font_height*term->rows) + { + HBRUSH fillcolour, oldbrush; + HPEN edge, oldpen; + fillcolour = CreateSolidBrush ( + colours[ATTR_DEFBG>>ATTR_BGSHIFT]); + oldbrush = SelectObject(hdc, fillcolour); + edge = CreatePen(PS_SOLID, 0, + 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, + offset_width, offset_height, + offset_width+font_width*term->cols, + offset_height+font_height*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); +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + } +#endif + return 0; + } + case WM_NETEVENT: { + /* + * 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); + return 0; + } + case WM_SETFOCUS: +#ifdef MOD_PERSO + if( GetTransparencyFlag() && conf_get_int(conf,CONF_transparencynumber) >= 0 ) + { SetTransparency( hwnd, 255-conf_get_int(conf,CONF_transparencynumber) ) ; } + //RefreshBackground( hwnd ) ; +#endif + term_set_focus(term, true); + CreateCaret(hwnd, caretbm, font_width, font_height); + ShowCaret(hwnd); + flash_window(0); /* stop */ + compose_state = 0; + term_update(term); + break; + case WM_KILLFOCUS: + show_mouseptr(true); + term_set_focus(term, false); + DestroyCaret(); + caret_x = caret_y = -1; /* ensure caret is replaced next time */ + term_update(term); + break; + case WM_ENTERSIZEMOVE: +#ifdef RDB_DEBUG_PATCH + debug("WM_ENTERSIZEMOVE\n"); +#endif + EnableSizeTip(true); +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + if( GetBackgroundImageFlag() && (!PuttyFlag) ) GetClientRect(hwnd, &size_before); +#endif + resizing = true; + need_backend_resize = false; + break; + case WM_EXITSIZEMOVE: + EnableSizeTip(false); + resizing = false; +#ifdef RDB_DEBUG_PATCH + debug("WM_EXITSIZEMOVE\n"); +#endif + if (need_backend_resize) { + term_size(term, conf_get_int(conf, CONF_height), + conf_get_int(conf, CONF_width), + conf_get_int(conf, CONF_savelines)); +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + if( GetBackgroundImageFlag() && (conf_get_int(conf,CONF_bg_image_abs_fixed)==1) && (conf_get_int(conf,CONF_bg_type)!=0) ) RefreshBackground( hwnd ) ; +#endif + InvalidateRect(hwnd, NULL, true); + } + recompute_window_offset(); + 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(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 (!need_backend_resize && resize_action == RESIZE_EITHER && + (conf_get_int(conf, CONF_height) != term->rows || + conf_get_int(conf, CONF_width) != 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(conf, CONF_height, term->rows); + conf_set_int(conf, CONF_width, term->cols); + + InvalidateRect(hwnd, NULL, true); + need_backend_resize = true; + } + + width = r->right - r->left - extra_width; + height = r->bottom - r->top - extra_height; + w = (width + font_width / 2) / font_width; + if (w < 1) + w = 1; + h = (height + font_height / 2) / font_height; + if (h < 1) + h = 1; + UpdateSizeTip(hwnd, w, h); + ew = width - w * font_width; + eh = height - h * 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(conf, CONF_window_border); + int ex_width = extra_width + (window_border - offset_width) * 2; + int ex_height = extra_height + (window_border - offset_height) * 2; + LPRECT r = (LPRECT) lParam; + + width = r->right - r->left - ex_width; + height = r->bottom - r->top - ex_height; + + w = (width + term->cols/2)/term->cols; + h = (height + term->rows/2)/term->rows; + if ( r->right != r->left + w*term->cols + ex_width) + rv = 1; + + if (wParam == WMSZ_LEFT || + wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT) + r->left = r->right - w*term->cols - ex_width; + else + r->right = r->left + w*term->cols + ex_width; + + if (r->bottom != r->top + h*term->rows + ex_height) + rv = 1; + + if (wParam == WMSZ_TOP || + wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT) + r->top = r->bottom - h*term->rows - ex_height; + else + r->bottom = r->top + h*term->rows + ex_height; + + return rv; + } + /* break; (never reached) */ + case WM_FULLSCR_ON_MAX: + fullscr_on_max = true; + break; +#ifdef MOD_PERSO +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + case WM_DISPLAYCHANGE: + if( (!GetBackgroundImageFlag()) || GetPuttyFlag() ) return DefWindowProc(hwnd, message, wParam, lParam) ; + case WM_MOVE: + if( (!GetBackgroundImageFlag()) || GetPuttyFlag() ) { + sys_cursor_update(); + if( conf_get_bool(conf,CONF_saveonexit) ) GetWindowCoord( hwnd ) ; + break; + } + if(backgrounddc) + { + // When using a background image based on the desktop, correct display + // may depend on the current position of the window. + InvalidateRect(hwnd, NULL, true); + } + sys_cursor_update(); + + if( conf_get_bool(conf,CONF_saveonexit) ) GetWindowCoord( hwnd ) ; + + break; + + + case WM_SETTINGCHANGE: + // It's sometimes hard to tell what setting changed, but our decisions + // regarding background drawing depends on some system settings, so force + // it to be redone. + + if(textdc) + { + DeleteObject(textbm); + DeleteDC(textdc); + textdc = NULL; + textbm = NULL; + } + + if(backgrounddc) + { + DeleteObject(backgroundbm); + DeleteDC(backgrounddc); + backgrounddc = NULL; + backgroundbm = NULL; + } + + if(backgroundblenddc) + { + DeleteObject(backgroundblendbm); + DeleteDC(backgroundblenddc); + backgroundblenddc = NULL; + backgroundblendbm = NULL; + } + + if(textdc || backgrounddc || backgroundblenddc) + { + InvalidateRect(hwnd, NULL, true ); + } + + break; +#else + case WM_MOVE: + sys_cursor_update(); + if( conf_get_bool(conf,CONF_saveonexit) ) GetWindowCoord( hwnd ) ; + break; +#endif +#else + case WM_MOVE: + term_notify_window_pos(term, LOWORD(lParam), HIWORD(lParam)); + sys_cursor_update(); + break; +#endif + case WM_SIZE: + resize_action = conf_get_int(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(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( + term, r.right - r.left, r.bottom - r.top); + } + +#ifdef MOD_PERSO + if (wParam == SIZE_MINIMIZED) { + if( GetKeyState( VK_CONTROL ) & 0x8000 ) { + SendMessage( hwnd, WM_COMMAND, IDM_TOTRAY, 0 ) ; return 0 ; + } + else SetWindowText(hwnd, conf_get_bool(conf,CONF_win_name_always) ? window_name : icon_name); + } +#else + if (wParam == SIZE_MINIMIZED) + SetWindowText(hwnd, + conf_get_bool(conf, CONF_win_name_always) ? + window_name : icon_name); +#endif + if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) + SetWindowText(hwnd, window_name); + if (wParam == SIZE_RESTORED) { + processed_resize = false; + clear_full_screen(); + if (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 && fullscr_on_max) { + fullscr_on_max = false; + processed_resize = false; + make_full_screen(); + if (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; + } + } + + processed_resize = true; + + if (resize_action == RESIZE_DISABLED) { + /* A resize, well it better be a minimize. */ + reset_window(-1); + } else { + if (wParam == SIZE_MAXIMIZED) { + was_zoomed = true; + prev_rows = term->rows; + prev_cols = term->cols; + if (resize_action == RESIZE_TERM) + wm_size_resize_term(lParam, false); + reset_window(0); + } else if (wParam == SIZE_RESTORED && was_zoomed) { + was_zoomed = false; + if (resize_action == RESIZE_TERM) { + wm_size_resize_term(lParam, true); + reset_window(2); + } else if (resize_action != RESIZE_FONT) + reset_window(2); + else + reset_window(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(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 (!resizing) + recompute_window_offset(); +#ifdef MOD_PERSO + else + SetWinHeight( -1 ) ; +#endif + } else { + reset_window(0); + } + } +#ifdef MOD_PERSO + if( GetTitleBarFlag() ) wintw_set_title( NULL, conf_get_str(conf,CONF_wintitle) ) ; // Pour refaire la barre de titre si option SizeFlag + if( conf_get_bool(conf,CONF_saveonexit) ) GetWindowCoord( hwnd ) ; +#endif +#ifdef MOD_BACKGROUNDIMAGE + /* + if( ((wParam == SIZE_RESTORED)&&(resizing!=TRUE)) || (wParam == SIZE_MAXIMIZED) ) { + if( GetBackgroundImageFlag() && (conf_get_int(conf,CONF_bg_image_abs_fixed)==1) && (conf_get_int(conf,CONF_bg_type)!=0) ) RefreshBackground( hwnd ) ; + }*/ + if( (wParam == SIZE_RESTORED) || (wParam == SIZE_MAXIMIZED) ) { + RefreshBackground( hwnd ) ; + } +#endif + sys_cursor_update(); + return 0; + case WM_DPICHANGED: + dpi_info.cur_dpi.x = LOWORD(wParam); + dpi_info.cur_dpi.y = HIWORD(wParam); + dpi_info.new_wnd_rect = *(RECT*)(lParam); + reset_window(3); + return 0; + case WM_VSCROLL: + switch (LOWORD(wParam)) { + case SB_BOTTOM: + term_scroll(term, -1, 0); + break; + case SB_TOP: + term_scroll(term, +1, 0); + break; + case SB_LINEDOWN: + term_scroll(term, 0, +1); + break; + case SB_LINEUP: + term_scroll(term, 0, -1); + break; + case SB_PAGEDOWN: + term_scroll(term, 0, +term->rows / 2); + break; + case SB_PAGEUP: + term_scroll(term, 0, -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(term, 1, si.nTrackPos); + break; + } + } + + if (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(term); + } + break; + case WM_PALETTECHANGED: + if ((HWND) wParam != hwnd && pal != NULL) { + HDC hdc = make_hdc(); + if (hdc) { + if (RealizePalette(hdc) > 0) + UpdateColors(hdc); + free_hdc(hdc); + } + } + break; + case WM_QUERYNEWPALETTE: + if (pal != NULL) { + HDC hdc = make_hdc(); + if (hdc) { + if (RealizePalette(hdc) > 0) + UpdateColors(hdc); + free_hdc(hdc); + return true; + } + } + return false; +#ifdef MOD_HYPERLINK + /* + * HACK: PuttyTray / Nutty + * Hyperlink stuff: Change cursor if we are in ctrl+click link mode + * + * WARNING: Spans over multiple CASEs + */ + case WM_KEYDOWN: + if( GetPuttyFlag() || !GetHyperlinkFlag() ) goto KEY_END; + if(wParam == VK_CONTROL && conf_get_int(term->conf,CONF_url_ctrl_click)) { + GetCursorPos(&cursor_pt); + ScreenToClient(hwnd, &cursor_pt); + + if (urlhack_is_in_link_region(TO_CHR_X(cursor_pt.x), TO_CHR_Y(cursor_pt.y))) { + SetCursor(LoadCursor(NULL, IDC_HAND)); + term_update(term); + } + goto KEY_END; + } + case WM_KEYUP: + if( GetPuttyFlag() || !GetHyperlinkFlag() ) goto KEY_END; + if (wParam == VK_CONTROL && conf_get_int(term->conf,CONF_url_ctrl_click)) { + SetCursor(LoadCursor(NULL, IDC_IBEAM)); + term_update(term); + goto KEY_END; + } + KEY_END: + case WM_SYSKEYUP: + case WM_SYSKEYDOWN: + /* HACK: PuttyTray / Nutty : END */ +#else + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: +#endif +#ifdef MOD_RECONNECT + //if( !back && GetAutoreconnectFlag() && is_backend_first_connected && (WM_COMMAND==WM_KEYDOWN) && !(GetKeyState(VK_CONTROL)&0x8000) && !(GetKeyState(VK_SHIFT)&0x8000) && !(GetKeyState(VK_MENU)&0x8000) && (wParam!=VK_TAB) && (wParam!=VK_LEFT) && (wParam!=VK_UP) && (wParam!=VK_RIGHT) && (wParam!=VK_DOWN) && !((wParam>=VK_F1)&&(wParam<=VK_F16)) ) { + if( (!backend || !is_backend_connected) && (message==WM_KEYDOWN) && GetAutoreconnectFlag() && is_backend_first_connected && (wParam!=VK_CONTROL) && (wParam!=VK_SHIFT) && (wParam!=VK_MENU) && (wParam!=VK_TAB) && (wParam!=VK_LEFT) && (wParam!=VK_UP) && (wParam!=VK_RIGHT) && (wParam!=VK_DOWN) && !((wParam>=VK_F1)&&(wParam<=VK_F16)) ) { + lp_eventlog(&wgs.logpolicy, "No connection on key pressed, trying to reconnect...") ; + PostMessage( hwnd, WM_COMMAND, IDM_RESTART, 0 ) ; + break ; + } +#endif +#ifdef MOD_PERSO + if( (wParam == VK_TAB) && (GetKeyState(VK_CONTROL) & 0x8000) ) { // CTRL + TAB to switch between windows + if (conf_get_int(conf, CONF_ctrl_tab_switch) && GetCtrlTabFlag()) { + if( message==WM_KEYUP ) { + struct ctrl_tab_info info = { (GetKeyState(VK_SHIFT) & 0x8000) ? 1 : -1, hwnd, } ; + + info.next_hi_date_time = info.self_hi_date_time = GetWindowLong(hwnd, 0); + info.next_lo_date_time = info.self_lo_date_time = GetWindowLong(hwnd, 4); + EnumWindows(CtrlTabWindowProc, (LPARAM) &info); + if (info.next != NULL) + if( info.next != hwnd ) + SetForegroundWindow(info.next); + return 0; + } + return DefWindowProc(hwnd, message, wParam, lParam); + } + } + + AntiIdleCount = 0 ; + + /* Permet de sauvegarder tous les caracteres tapes au clavier pour les avoir dans le /savedump */ +#ifdef MOD_SAVEDUMP + if( debug_flag ) addkeypressed( message, wParam, lParam, GetKeyState(VK_SHIFT)&0x8000, GetKeyState(VK_CONTROL)&0x8000, (GetKeyState(VK_MENU)&0x8000)||(GetKeyState(VK_LMENU)&0x8000),GetKeyState(VK_RMENU)&0x8000, (GetKeyState(VK_RWIN)&0x8000)||(GetKeyState(VK_LWIN)&0x8000 ) ); +#endif + +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + if( (wParam==VK_SNAPSHOT)&&(GetKeyState(VK_CONTROL)&0x8000) ) { // CTRL + PrintScreen => Screenshot + char screenShotFile[1024] ; + sprintf( screenShotFile, "%s\\screenshot-%d-%lld.jpg", InitialDirectory, getpid(), time(0) ); + screenCaptureClientRect( hwnd, screenShotFile, 100 ) ; + } +#endif + +/* + if((wParam==VK_F1)&&(message==WM_KEYDOWN)&&(GetKeyState(VK_CONTROL)&0x8000)&&(GetKeyState(VK_SHIFT)&0x8000)) // CTRL+SHIFT+F1 => Manage shortcut flag + { SetShortcutsFlag( abs(GetShortcutsFlag()-1) ) ; return 0 ; } +*/ + if( GetShortcutsFlag() ) { + if ( (message==WM_KEYDOWN)||(message==WM_SYSKEYDOWN) ) { + + if( ManageShortcuts(term, conf, hwnd, clips_system, wParam + , GetKeyState(VK_SHIFT)&0x8000 + , GetKeyState(VK_CONTROL)&0x8000 + , (GetKeyState(VK_MENU)&0x8000)||(GetKeyState(VK_LMENU)&0x8000) + , GetKeyState(VK_RMENU)&0x8000 + //, is_alt_pressed() + , (GetKeyState(VK_RWIN)&0x8000)||(GetKeyState(VK_LWIN)&0x8000) + ) ) return 0 ; + } + } else { + if( GetProtectFlag() == 1 ) return 0 ; + } + + // Majuscule uniquement + if( GetCapsLockFlag() ) { + if( ( wParam>='A' ) && ( wParam <= 'Z' ) + && !(GetKeyState( VK_CAPITAL ) & 0x0001) + && !(GetKeyState( VK_SHIFT ) & 0x8000) ) { + keybd_event( VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0 ) ; + keybd_event( VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0) ; + SendMessage(hwnd, WM_CHAR, wParam, 0) ; + return 0 ; + } + } +/* +if( (GetKeyState(VK_MENU)&0x8000) && (wParam==VK_SPACE) ) { + PopUpSystemMenu( hwnd, 0 ) ; + return 0; +} +*/ +#endif + + /* + * Add the scan code and keypress timing to the random + * number noise. + */ + noise_ultralight(NOISE_SOURCE_KEY, lParam); +#ifdef MOD_FAR2L + /* far2l extensions support */ + if (term->far2l_ext) { + + // extensions mode enabled + + // far2l_ext keyboard input event structure + WORD repeat; // 2 bytes + WORD vkc; // 2 bytes + WORD vsc; // 2 bytes + DWORD ctrl; // 4 bytes + DWORD uchar; // 4 bytes + CHAR type; // 1 byte + + // set repeat, virtual keycode, virtual scancode + repeat = LOWORD(lParam); + vsc = HIWORD(lParam) & 0xFF; + vkc = LOWORD(wParam); + + // this fixes far2l's "editor autocomplete" plugin behavior + if ((vkc == VK_TAB) || (vkc == VK_BACK) || (vkc == VK_ESCAPE) || (vkc == VK_DELETE)) { + vsc = 0; + } + + // fixes strange alt+arrows behavior + if ((vkc == VK_LEFT) || (vkc == VK_RIGHT) || (vkc == VK_UP) || (vkc == VK_DOWN)) { + vsc = 0; + } + + // set control keys state + ctrl = 0; + if (GetAsyncKeyState(VK_LCONTROL)) { ctrl |= LEFT_CTRL_PRESSED; } + if (GetAsyncKeyState(VK_RCONTROL)) { ctrl |= RIGHT_CTRL_PRESSED; } + if (GetAsyncKeyState(VK_LMENU)) { ctrl |= LEFT_ALT_PRESSED; } + if (GetAsyncKeyState(VK_RMENU)) { ctrl |= RIGHT_ALT_PRESSED; } + if (GetAsyncKeyState(VK_SHIFT)) { ctrl |= SHIFT_PRESSED; } + // begin: reserved for future usage + // Console WinAPI does not allow us to distinguish between left and right + // shift keys. But PuTTY is not a console app, so why not to send + // all information about control keys state that we actually have here? + // Using bits not used by any other status for backward compatibility. + #define RIGHT_SHIFT_PRESSED 0x1000 + #define LEFT_SHIFT_PRESSED 0x2000 + if (GetAsyncKeyState(VK_LSHIFT)) { ctrl |= RIGHT_SHIFT_PRESSED; } + if (GetAsyncKeyState(VK_RSHIFT)) { ctrl |= LEFT_SHIFT_PRESSED; } + // end + if ((lParam & ( 1 << 24 )) >> 24) { ctrl |= ENHANCED_KEY; } + if ((((u_short)GetKeyState(VK_NUMLOCK)) & 0xffff) != 0) { ctrl |= NUMLOCK_ON; } + if ((((u_short)GetKeyState(VK_SCROLL)) & 0xffff) != 0) { ctrl |= SCROLLLOCK_ON; } + if ((((u_short)GetKeyState(VK_CAPITAL)) & 0xffff) != 0) { ctrl |= CAPSLOCK_ON; } + + // set unicode character + BYTE kb[256]; + GetKeyboardState(kb); + WCHAR uc[5] = {}; + ToUnicode(wParam, MapVirtualKey(wParam, MAPVK_VK_TO_VSC), kb, uc, 4, 0); + // todo: check result + //int result = ToUnicode(wParam, MapVirtualKey(wParam, MAPVK_VK_TO_VSC), kb, uc, 4, 0); + uchar = uc[0]; + + // set event type + if ((message == WM_KEYDOWN) || (message == WM_SYSKEYDOWN)) { + type = 'K'; + } else { + type = 'k'; + } + + char* kev = malloc(15); // keyboard event structure length + memcpy(kev, &repeat, sizeof(repeat)); + memcpy(kev + 2, &vkc, sizeof(vkc)); + memcpy(kev + 4, &vsc, sizeof(vsc)); + memcpy(kev + 6, &ctrl, sizeof(ctrl)); + memcpy(kev + 10, &uchar, sizeof(uchar)); + memcpy(kev + 14, &type, sizeof(type)); + + // base64-encode kev + // result in null-terminated char* out + base64_encodestate _state; + base64_init_encodestate(&_state); + char* out = malloc(15*2); + int count = base64_encode_block(kev, 15, out, &_state); +#ifdef MOD_PERSO + count += base64_encode_blockend(out + count, &_state); +#else + // finishing '=' characters + char* next_char = out + count; + switch (_state.step) + { + case step_B: + *next_char++ = base64_encode_value(_state.result); + *next_char++ = '='; + *next_char++ = '='; + break; + case step_C: + *next_char++ = base64_encode_value(_state.result); + *next_char++ = '='; + break; + case step_A: + break; + } + count = next_char - out; + out[count] = 0; +#endif + + // send escape seq + + char* str = "\x1b_f2l"; + backend_send(backend, str, strlen(str)); + + backend_send(backend, out, count); + + char* str2 = "\x07"; + backend_send(backend, str2, strlen(str2)); + + // don't forget to free memory :) + free(out); + + // we should not do any other key processing in this mode + return 0; + } + + +/* + * 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. + */ +#else + /* + * 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. + */ +#endif + { + 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(message, wParam, lParam, buf); + if (len == -1) + return DefWindowProcW(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(term, -1, buf, len); + show_mouseptr(false); + } + } + } + return 0; + case WM_INPUTLANGCHANGE: + /* wParam == Font number */ + /* lParam == Locale */ + set_input_locale((HKL)lParam); + sys_cursor_update(); + break; + case WM_IME_STARTCOMPOSITION: { + HIMC hImc = ImmGetContext(hwnd); + ImmSetCompositionFont(hImc, &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 (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( + term, (unsigned short *)(buff+i), 2); + i += 2; + continue; + } + } + term_keyinputw( + 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(term, kbd_codepage, buf, 2); + } else { + char c = (unsigned char) wParam; + term_seen_key_event(term); + term_keyinput(term, 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. + */ + { + static wchar_t pending_surrogate = 0; + wchar_t c = wParam; + + if (IS_HIGH_SURROGATE(c)) { + pending_surrogate = c; + } else if (IS_SURROGATE_PAIR(pending_surrogate, c)) { + wchar_t pair[2]; + pair[0] = pending_surrogate; + pair[1] = c; + term_keyinputw(term, pair, 2); + } else if (!IS_SURROGATE(c)) { + term_keyinputw(term, &c, 1); + } + } + return 0; + case WM_SYSCOLORCHANGE: + if (conf_get_bool(conf, CONF_system_colour)) { + /* Refresh palette from system colours. */ + term_notify_palette_changed(term); + init_palette(); + /* Force a repaint of the terminal window. */ + term_invalidate(term); + } + break; + case WM_GOT_CLIPDATA: + process_clipdata((HGLOBAL)lParam, wParam); + return 0; +#ifdef MOD_RECONNECT + case WM_POWERBROADCAST: + if( GetAutoreconnectFlag() && conf_get_int(conf,CONF_wakeup_reconnect) && is_backend_first_connected) { + switch(wParam) { + case PBT_APMRESUMESUSPEND: + case PBT_APMRESUMEAUTOMATIC: + case PBT_APMRESUMECRITICAL: + case PBT_APMQUERYSUSPENDFAILED: + if(session_closed && !backend) { + /* + time_t tnow = time(NULL); + + if(last_reconnect && ((tnow - last_reconnect) < GetReconnectDelay()) ) { + lp_eventlog(default_logpolicy, "Woken up from suspend, waiting for delay..." ); + Sleep(GetReconnectDelay()*1000); + } + last_reconnect = tnow; + lp_eventlog(default_logpolicy, "Woken up from suspend, reconnecting..."); + term_pwron(term, FALSE); + is_backend_connected = 0 ; + start_backend(); + SetTimer(hwnd, TIMER_INIT, init_delay, NULL) ; + */ + lp_eventlog(&wgs.logpolicy, "Unable to connect on wakeup, trying to reconnect...") ; + SetTimer(hwnd, TIMER_RECONNECT, GetReconnectDelay()*1000, NULL) ; + } + break; + case PBT_APMSUSPEND: + if(!session_closed && backend) { + lp_eventlog(&wgs.logpolicy, "Suspend detected, disconnecting cleanly..."); + queue_toplevel_callback(close_session, NULL); // close_session(); + session_closed = true; + } + break; + } + } + break; +#endif + + default: + if (message == wm_mousewheel || message == WM_MOUSEWHEEL) { + bool shift_pressed = false, control_pressed = false; + + if (message == WM_MOUSEWHEEL) { + wheel_accumulator += (short)HIWORD(wParam); + shift_pressed=LOWORD(wParam) & MK_SHIFT; + control_pressed=LOWORD(wParam) & MK_CONTROL; + } else { + BYTE keys[256]; + 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(wheel_accumulator) >= WHEEL_DELTA) { + int b; + + /* reduce amount for next time */ + if (wheel_accumulator > 0) { + b = MBT_WHEEL_UP; + wheel_accumulator -= WHEEL_DELTA; + } else if (wheel_accumulator < 0) { + b = MBT_WHEEL_DOWN; + wheel_accumulator += WHEEL_DELTA; + } else + break; + + if (send_raw_mouse && + !(conf_get_bool(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(term, b, translate_button(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 */ +#ifdef MOD_PERSO + } else if ( control_pressed && shift_pressed ) { + /* + // region tray-zoom + conf_get_fontspec(conf, CONF_font)->height += MBT_WHEEL_UP == b ? 1 : -1; + // short version of IDM_RECONF's reconfig: + term_size(term, + conf_get_int(conf, CONF_height), + conf_get_int(conf, CONF_width), + conf_get_int(conf, CONF_savelines)); + reset_window(2); + // endregion + */ + } else if (control_pressed) { + ChangeFontSize(term,conf,hwnd, MBT_WHEEL_UP == b ? 1 : -1); + } else { + /* + * PuttyFeatures: Scroll Lines + */ + // if the wheel accumulator has multiples of the wheel delta, deal with it in one scroll call + // this will fix the laggy scrolling in original PuTTY. + int multiple = 1; + if (abs(wheel_accumulator) >= WHEEL_DELTA) { + multiple = abs(wheel_accumulator) / WHEEL_DELTA; + + if (wheel_accumulator > 0) { + wheel_accumulator -= multiple * WHEEL_DELTA; + } else { + wheel_accumulator += multiple * WHEEL_DELTA; + } + + // We started with a 1x already. Accounting for that here. + multiple++; + // to make scrolling more smooth, square the multiple value so that fast wheels scrolls more + multiple *= multiple; + } + // determine how much to scroll. + // -1 = half screen, -2 = full screen, and any positive value is the number of lines to scroll + int conf_scroll = conf_get_int(conf, CONF_scrolllines); + int scrollLines = conf_scroll == -1 ? term->rows / 2 + : conf_scroll == -2 ? term->rows + : conf_scroll < -2 ? 3 + : conf_scroll; + // account for the multiples from the wheel accumulator + scrollLines *= multiple; + + /* trigger a scroll */ + term_scroll(term, 0, + b == MBT_WHEEL_UP ? + -scrollLines : scrollLines); +#else + } else { + /* trigger a scroll */ + term_scroll(term, 0, + b == MBT_WHEEL_UP ? + -term->rows / 2 : term->rows / 2); +#endif + } + } + return 0; + } + } + + /* + * Any messages we don't process completely above are passed through to + * DefWindowProc() for default processing. + */ + return DefWindowProcW(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) +{ + int cx, cy; + + if (!term->has_focus) return; + + /* + * Avoid gratuitously re-updating the cursor position and IMM + * window if there's no actual change required. + */ + cx = x * font_width + offset_width; + cy = y * font_height + offset_height; + if (cx == caret_x && cy == caret_y) + return; + caret_x = cx; + caret_y = cy; + + sys_cursor_update(); +} + +static void sys_cursor_update(void) +{ + COMPOSITIONFORM cf; + HIMC hIMC; + + if (!term->has_focus) return; + + if (caret_x < 0 || caret_y < 0) + return; + + SetCaretPos(caret_x, 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 = caret_x; + cf.ptCurrentPos.y = caret_y; + ImmSetCompositionWindow(hIMC, &cf); + + ImmReleaseContext(wgs.term_hwnd, hIMC); +} + +static void draw_horizontal_line_on_text(int y, int lattr, RECT line_box, + COLORREF colour) +{ + if (lattr == LATTR_TOP || lattr == LATTR_BOT) { + y *= 2; + if (lattr == LATTR_BOT) + y -= font_height; + } + + if (!(0 <= y && y < font_height)) + return; + + HPEN oldpen = SelectObject(wintw_hdc, CreatePen(PS_SOLID, 0, colour)); + MoveToEx(wintw_hdc, line_box.left, line_box.top + y, NULL); + LineTo(wintw_hdc, line_box.right, line_box.top + y); + oldpen = SelectObject(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( + 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; + static int *lpDx = NULL; + static size_t lpDx_len = 0; + int *lpDx_maybe; + int len2; /* for SURROGATE PAIR */ +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + int transBg = backgrounddc ? 1 : 0; + UINT etoFlagOpaque = transBg ? 0 : ETO_OPAQUE; +#endif + + lattr &= LATTR_MODE; + + char_width = fnt_width = 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 >= term->cols) + return; + + x *= fnt_width; + y *= font_height; + x += offset_width; + y += offset_height; + + if ((attr & TATTR_ACTCURS) && (cursor_type == 0 || 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; + } +#ifdef MOD_TUTTYCOLOR + if (!conf_get_int(conf,CONF_sel_colour) && (attr & ATTR_SELECTED)) { + attr &= ~ATTR_SELECTED; + attr |= ATTR_REVERSE; + }; +#endif + + nfont = 0; + if (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 * font_height / 5; + break; + case 0xBB: + text_adjust = -1 * font_height / 5; + break; + case 0xBC: + text_adjust = font_height / 5; + break; + case 0xBD: + text_adjust = 2 * font_height / 5; + break; + } + if (lattr == LATTR_TOP || lattr == LATTR_BOT) + text_adjust *= 2; + text[0] = 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 (bold_font_mode == BOLD_FONT && (attr & ATTR_BOLD)) + nfont |= FONT_BOLD; +#ifdef MOD_PERSO + if (attr & ATTR_ITALIC) + nfont |= FONT_ITALIC; +#endif + if (und_mode == UND_FONT && (attr & ATTR_UNDER)) + nfont |= FONT_UNDERLINE; + another_font(nfont); + if (!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(nfont); + if (!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 (bold_colours && (attr & ATTR_BOLD) && !is_cursor) { + if (nfg < 16) nfg |= 8; + else if (nfg >= 256) nfg |= 1; + } + if (bold_colours && (attr & ATTR_BLINK)) { + if (nbg < 16) nbg |= 8; + else if (nbg >= 256) nbg |= 1; + } +#ifdef MOD_TUTTYCOLOR + /* + * quick & dirty hack: underlined text has colour preference over bold & normal + * another one: selected text have absolute preference over all other attributes + */ + if (attr & ATTR_SELECTED) { + nfg = 272; + nbg = 273; + } else { + if (conf_get_int(conf,CONF_under_colour) && (nfont & FONT_UNDERLINE)) { + if (nfg < 7) + nfg += 264; + else if (nfg > 7 && nfg < 16) + nfg += 256; + else + switch (nfg) { + case 256: + nfg = 262; + break; + case 257: + nfg = 262; + break; + case 258: + nfg = 263; + break; + case 259: + nfg = 263; + }; + if (nbg < 7) + nbg += 264; + else if (nbg > 7 && nbg < 16) + nbg += 256; + else + switch (nbg) { + case 256: + nbg = 262; + break; + case 257: + nbg = 262; + break; + case 258: + nbg = 263; + break; + case 259: + nbg = 263; + }; + }; + }; +#endif + if (!pal && truecolour.fg.enabled) + fg = RGB(truecolour.fg.r, truecolour.fg.g, truecolour.fg.b); + else + fg = colours[nfg]; + + if (!pal && truecolour.bg.enabled) + bg = RGB(truecolour.bg.r, truecolour.bg.g, truecolour.bg.b); + else + bg = colours[nbg]; + + if (!pal && (attr & ATTR_DIM)) { + fg = RGB(GetRValue(fg) * 2 / 3, + GetGValue(fg) * 2 / 3, + GetBValue(fg) * 2 / 3); + } +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + HDC hdc = wintw_hdc ; + if( GetBackgroundImageFlag() && (!GetPuttyFlag()) ) { + line_box.left = x; + line_box.top = y; + line_box.right = x + char_width * len; + line_box.bottom = y + font_height; + + if(textdc) { + int x = line_box.left; + int y = line_box.top; + int width = line_box.right - line_box.left; + int height = line_box.bottom - line_box.top; + + POINT bgloc = { x, y }; + COLORREF backgroundcolor = colours[258]; // Default Background + + if(!bBgRelToTerm) + ClientToScreen(wgs.term_hwnd, &bgloc); + + if(bg == backgroundcolor) { + // Use fast screen fill for default background. + BitBlt(textdc, x, y, width, height, backgroundblenddc, bgloc.x, bgloc.y, SRCCOPY); + } else { + BitBlt(textdc, x, y, width, height, backgrounddc, bgloc.x, bgloc.y, SRCCOPY); + color_blend(textdc, x, y, width, height, bg, conf_get_int(conf,CONF_bg_opacity)) ; + } + hdc = textdc; + } + SelectObject(hdc, fonts[nfont]); + SetTextColor(hdc, fg); + SetBkColor(hdc, bg); + if (transBg || attr & TATTR_COMBINING) + SetBkMode(hdc, TRANSPARENT); + else + SetBkMode(hdc, OPAQUE); + } else { + SelectObject(hdc, fonts[nfont]); + SetTextColor(hdc, fg); + SetBkColor(hdc, bg); + if (attr & TATTR_COMBINING) + SetBkMode(hdc, TRANSPARENT); + else + SetBkMode(hdc, OPAQUE); + line_box.left = x; + line_box.top = y; + line_box.right = x + char_width * len; + line_box.bottom = y + font_height; + } +#else + SelectObject(wintw_hdc, fonts[nfont]); + SetTextColor(wintw_hdc, fg); + SetBkColor(wintw_hdc, bg); + if (attr & TATTR_COMBINING) + SetBkMode(wintw_hdc, TRANSPARENT); + else + SetBkMode(wintw_hdc, OPAQUE); + line_box.left = x; + line_box.top = y; + line_box.right = x + char_width * len; + line_box.bottom = y + font_height; +#endif + /* 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 > font_width*term->cols+offset_width) + line_box.right = font_width*term->cols+offset_width; + + if (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(wintw_hdc, TA_TOP | TA_CENTER | TA_NOUPDATECP); + lpDx_maybe = NULL; + maxlen = 1; + } else { + /* + * In a fixed-pitch font, we draw the whole string in one go + * in the normal way. + */ + xoffset = 0; + SetTextAlign(wintw_hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP); + lpDx_maybe = lpDx; + 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); + if (lpDx_maybe) lpDx_maybe = lpDx; + } + + { + 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 (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 */ + static wchar_t *uni_buf = 0; + static int uni_len = 0; + int nlen, mptr; + if (len > uni_len) { + sfree(uni_buf); + uni_len = len; + uni_buf = snewn(uni_len, wchar_t); + } + + for(nlen = mptr = 0; mptr= 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(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(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(x, y, text, 2, attr | a, lattr, truecolour); + len--; + text++; + } else + do_text_internal(x, y, text, 1, attr | a, lattr, truecolour); + + text++; + a = TATTR_COMBINING; + } + } else + do_text_internal(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) +{ + int fnt_width; + int char_width; + int ctype = cursor_type; + + lattr &= LATTR_MODE; + + if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) { + if (*text != UCSWIDE) { + win_draw_text(tw, x, y, text, len, attr, lattr, truecolour); + return; + } + ctype = 2; + attr |= TATTR_RIGHTCURS; + } + + fnt_width = char_width = font_width * (1 + (lattr != LATTR_NORM)); + if (attr & ATTR_WIDE) + char_width *= 2; + x *= fnt_width; + y *= font_height; + x += offset_width; + y += offset_height; + + if ((attr & TATTR_PASCURS) && (ctype == 0 || 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 + font_height - 1; + oldpen = SelectObject(wintw_hdc, CreatePen(PS_SOLID, 0, colours[261])); + Polyline(wintw_hdc, pts, 5); + oldpen = SelectObject(wintw_hdc, oldpen); + DeleteObject(oldpen); + } else if ((attr & (TATTR_ACTCURS | TATTR_PASCURS)) && ctype != 0) { + int startx, starty, dx, dy, length, i; + if (ctype == 1) { + startx = x; + starty = y + descent; + dx = 1; + dy = 0; + length = char_width; + } else { + int xadjust = 0; + if (attr & TATTR_RIGHTCURS) + xadjust = char_width - 1; + startx = x + xadjust; + starty = y; + dx = 0; + dy = 1; + length = font_height; + } + if (attr & TATTR_ACTCURS) { + HPEN oldpen; + oldpen = + SelectObject(wintw_hdc, CreatePen(PS_SOLID, 0, colours[261])); + MoveToEx(wintw_hdc, startx, starty, NULL); + LineTo(wintw_hdc, startx + dx * length, starty + dy * length); + oldpen = SelectObject(wintw_hdc, oldpen); + DeleteObject(oldpen); + } else { + for (i = 0; i < length; i++) { + if (i % 2 == 0) { + SetPixel(wintw_hdc, startx, starty, colours[261]); + } + startx += dx; + starty += dy; + } + } + } +} + +static void wintw_draw_trust_sigil(TermWin *tw, int x, int y) +{ + x *= font_width; + y *= font_height; + x += offset_width; + y += offset_height; + + DrawIconEx(wintw_hdc, x, y, trust_icon, font_width * 2, 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) +{ + int ibuf = 0; + + /* If the font max is the same as the font ave width then this + * function is a no-op. + */ + if (!font_dualwidth) return 1; + + switch (uc & CSET_MASK) { + case CSET_ASCII: + uc = ucsdata.unitab_line[uc & 0xFF]; + break; + case CSET_LINEDRW: + uc = ucsdata.unitab_xterm[uc & 0xFF]; + break; + case CSET_SCOACS: + uc = ucsdata.unitab_scoacs[uc & 0xFF]; + break; + } + if (DIRECT_FONT(uc)) { + if (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(wintw_hdc, fonts[FONT_NORMAL]); + } else if ( (uc & CSET_MASK) == CSET_OEMCP ) { + another_font(FONT_OEM); + if (!fonts[FONT_OEM]) return 0; + + SelectObject(wintw_hdc, fonts[FONT_OEM]); + } else + return 0; + + if (GetCharWidth32(wintw_hdc, uc & ~CSET_MASK, + uc & ~CSET_MASK, &ibuf) != 1 && + GetCharWidth(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(wintw_hdc, fonts[FONT_NORMAL]); + if (GetCharWidth32W(wintw_hdc, uc, uc, &ibuf) == 1) + /* Okay that one worked */ ; + else if (GetCharWidthW(wintw_hdc, uc, uc, &ibuf) == 1) + /* This should work on 9x too, but it's "less accurate" */ ; + else + return 0; + } + + ibuf += font_width / 2 -1; + ibuf /= 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, PlaySound, (LPCTSTR, 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_PP(winmm_module, PlaySound); + 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(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; + static int alt_sum = 0; + int funky_type = conf_get_int(conf, CONF_funky_type); + bool no_applic_k = conf_get_bool(conf, CONF_no_applic_k); + bool ctrlaltkeys = conf_get_bool(conf, CONF_ctrlaltkeys); + bool nethack_keypad = conf_get_bool(conf, CONF_nethack_keypad); + char keypad_key = '\0'; + + HKL kbd_layout = GetKeyboardLayout(0); + + static wchar_t keys_unicode[3]; + static int compose_char = 0; + static WPARAM compose_keycode = 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 (keys_unicode[0]) + debug(", KB0=%04x", keys_unicode[0]); + if (keys_unicode[1]) + debug(", KB1=%04x", keys_unicode[1]); + if (keys_unicode[2]) + debug(", KB2=%04x", keys_unicode[2]); + + 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]; + } + +#ifdef MOD_DISABLEALTGR +/*disable altgr*/ +if( !get_param("PUTTY") && conf_get_int(conf, CONF_disablealtgr) ) { + keystate[VK_RMENU] = 0; +} +#endif + + /* Nastyness with NUMLock - Shift-NUMLock is left alone though */ + if ((funky_type == FUNKY_VT400 || + (funky_type <= FUNKY_LINUX && 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 (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 (!compose_state) { + compose_keycode = 0x100; + if (conf_get_bool(conf, CONF_compose_key)) { + if (wParam == VK_MENU && (HIWORD(lParam) & KF_EXTENDED)) + compose_keycode = wParam; + } + if (wParam == VK_APPS) + compose_keycode = wParam; + } + + if (wParam == compose_keycode) { + if (compose_state == 0 + && (HIWORD(lParam) & (KF_UP | KF_REPEAT)) == 0) compose_state = + 1; + else if (compose_state == 1 && (HIWORD(lParam) & KF_UP)) + compose_state = 2; + else + compose_state = 0; + } else if (compose_state == 1 && wParam != VK_CONTROL) + compose_state = 0; + + if (compose_state > 1 && left_alt) + compose_state = 0; + + /* Sanitize the number pad if not using a PC NumPad */ + if (left_alt || (term->app_keypad_keys && !no_applic_k + && funky_type != FUNKY_XTERM) + || funky_type == FUNKY_VT400 || nethack_keypad || 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 && !compose_state) { + /* Okay, prepare for most alts then ... */ +#ifdef MOD_KEYMAPPING + if( !GetPuttyFlag() ) { + if (left_alt && shift_state != 1 && !(wParam == VK_UP || wParam == VK_DOWN || wParam == VK_RIGHT || wParam == VK_LEFT)) { + int fkey = 0; + switch (wParam) { + case VK_F1: + case VK_F2: + case VK_F3: + case VK_F4: + case VK_F5: + case VK_F6: + case VK_F7: + case VK_F8: + case VK_F9: + case VK_F10: + case VK_F11: + case VK_F12: + case VK_INSERT: + case VK_DELETE: + case VK_HOME: + case VK_END: + case VK_PRIOR: + case VK_NEXT: + fkey = 1; + break; + default: + break; + } + + if (!(term->funky_type == FUNKY_XTERM && !term->vt52_mode) || fkey == 0) { + *p++ = '\033'; + } + } + } + else +#endif + 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(term, (wParam == VK_PRIOR ? 0 : 1)); + return 0; + } + if (wParam == VK_INSERT && shift_state == 2) { + switch (conf_get_int(conf, CONF_ctrlshiftins)) { + case CLIPUI_IMPLICIT: + break; /* no need to re-copy to CLIP_LOCAL */ + case CLIPUI_EXPLICIT: + term_request_copy(term, clips_system, lenof(clips_system)); + break; + default: + break; + } + return 0; + } + if (wParam == VK_INSERT && shift_state == 1) { + switch (conf_get_int(conf, CONF_ctrlshiftins)) { + case CLIPUI_IMPLICIT: + term_request_paste(term, CLIP_LOCAL); + break; + case CLIPUI_EXPLICIT: + term_request_paste(term, CLIP_SYSTEM); + break; + default: + break; + } + return 0; + } + if (wParam == 'C' && shift_state == 3) { + switch (conf_get_int(conf, CONF_ctrlshiftcv)) { + case CLIPUI_IMPLICIT: + break; /* no need to re-copy to CLIP_LOCAL */ + case CLIPUI_EXPLICIT: + term_request_copy(term, clips_system, lenof(clips_system)); + break; + default: + break; + } + return 0; + } + if (wParam == 'V' && shift_state == 3) { + switch (conf_get_int(conf, CONF_ctrlshiftcv)) { + case CLIPUI_IMPLICIT: + term_request_paste(term, CLIP_LOCAL); + break; + case CLIPUI_EXPLICIT: + term_request_paste(term, CLIP_SYSTEM); + break; + default: + break; + } + return 0; + } + if (left_alt && wParam == VK_F4 && conf_get_bool(conf, CONF_alt_f4)) { + return -1; + } + if (left_alt && wParam == VK_SPACE && conf_get_bool(conf, + CONF_alt_space)) { + SendMessage(wgs.term_hwnd, WM_SYSCOMMAND, SC_KEYMENU, 0); + return -1; + } + if (left_alt && wParam == VK_RETURN && + conf_get_bool(conf, CONF_fullscreenonaltenter) && + (conf_get_int(conf, CONF_resize_action) != RESIZE_DISABLED)) { + if ((HIWORD(lParam) & (KF_UP | KF_REPEAT)) != KF_REPEAT) + flip_full_screen(); + return -1; + } + /* Control-Numlock for app-keypad mode switch */ + if (wParam == VK_PAUSE && shift_state == 2) { + term->app_keypad_keys = !term->app_keypad_keys; + return 0; + } + +#ifdef MOD_KEYMAPPING + if (wParam == VK_BACK && shift_state <= 1) { /* Backspace of Shift Backspace */ +#else + if (wParam == VK_BACK && shift_state == 0) { /* Backspace */ +#endif + *p++ = (conf_get_bool(conf, CONF_bksp_is_delete) ? 0x7F : 0x08); + *p++ = 0; + return -2; + } +#ifdef MOD_KEYMAPPING + if (wParam == VK_BACK) { /* Ctrl Backspace or Ctrl+Shift Backspace */ +#else + if (wParam == VK_BACK && shift_state == 1) { /* Shift Backspace */ +#endif + /* We do the opposite of what is configured */ + *p++ = (conf_get_bool(conf, CONF_bksp_is_delete) ? 0x08 : 0x7F); + *p++ = 0; + return -2; + } +#ifdef MOD_KEYMAPPING + if( !GetPuttyFlag() ) { + if (wParam == VK_TAB && shift_state == 2) { /* Ctrl-Tab */ + p += sprintf((char *) p, "\x1B[27;5;9~"); + return p - output; + } + if (wParam == VK_TAB && shift_state == 3) { /* Ctrl-Shift-Tab */ + p += sprintf((char *) p, "\x1B[27;6;9~"); + return p - output; + } + } +#endif + 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 (backend) + backend_special(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) { + 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') + alt_sum = alt_sum * 10 + keypad_key - '0'; + else + alt_sum = 0; + break; + } + + { + int nchars = format_numeric_keypad_key( + (char *)p, 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: +#ifdef MOD_KEYMAPPING + p += format_function_key((char *)p, term, fkey_number, shift_state, left_alt); +#else + p += format_function_key((char *)p, term, fkey_number, + shift_state & 1, shift_state & 2); +#endif + 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: +#ifdef MOD_KEYMAPPING + p += format_small_keypad_key((char *)p, term, sk_key, shift_state, left_alt); +#else + /* These keys don't generate terminal input with Ctrl */ + if (shift_state & 2) + break; + p += format_small_keypad_key((char *)p, term, sk_key); +#endif + 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: +#ifdef MOD_KEYMAPPING + p += format_arrow_key((char*)p, term, xkey, shift_state, left_alt); +#else + p += format_arrow_key((char *)p, term, xkey, shift_state & 2); +#endif + return p - output; + + case VK_RETURN: + if (HIWORD(lParam) & KF_EXTENDED) { + keypad_key = '\r'; + goto numeric_keypad; + } + ordinary_return_key: + if (shift_state == 0 && term->cr_lf_return) { + *p++ = '\r'; + *p++ = '\n'; + return p - output; + } else { + *p++ = 0x0D; +#ifdef MOD_PERSO + if (conf_get_int(conf, CONF_enter_sends_crlf)) *p++ = 0x0A; +#endif + *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(conf, CONF_xlat_capslockcyr)) { + capsOn= !left_alt; + keystate[VK_CAPITAL] = 0; + } + + /* 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; + static WORD keys[3]; + static 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 (alt_sum) { + if (in_utf(term) || ucsdata.dbcs_screenfont) + debug(", (U+%04x)", alt_sum); + else + debug(", LCH(%d)", alt_sum); + } 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 (compose_state == 2 && wch >= ' ' && wch < 0x80) { + compose_char = wch; + compose_state++; + continue; + } + if (compose_state == 3 && wch >= ' ' && wch < 0x80) { + int nc; + compose_state = 0; + + if ((nc = check_compose(compose_char, wch)) == -1) { + MessageBeep(MB_ICONHAND); + return 0; + } + keybuf = nc; + term_keyinputw(term, &keybuf, 1); + continue; + } + + compose_state = 0; + + if (!key_down) { + if (alt_sum) { + if (in_utf(term) || ucsdata.dbcs_screenfont) { + keybuf = alt_sum; + term_keyinputw(term, &keybuf, 1); + } else { + char ch = (char) alt_sum; + /* + * 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(term, -1, &ch, 1); + } + alt_sum = 0; + } else { + term_keyinputw(term, &wch, 1); + } + } else { + if(capsOn && wch < 0x80) { + WCHAR cbuf[2]; + cbuf[0] = 27; + cbuf[1] = xlat_uskbd2cyrllic(wch); + term_keyinputw(term, cbuf+!left_alt, 1+!!left_alt); + } else { + WCHAR cbuf[2]; + cbuf[0] = '\033'; + cbuf[1] = wch; + term_keyinputw(term, cbuf+!left_alt, 1+!!left_alt); + } + } + show_mouseptr(false); + } + + /* This is so the ALT-Numpad and dead keys work correctly. */ + keys_unicode[0] = 0; + + return p - output; + } + /* If we're definitely not building up an ALT-54321 then clear it */ + if (!left_alt) + keys_unicode[0] = 0; + /* If we will be using alt_sum fix the 256s */ + else if (keys_unicode[0] && (in_utf(term) || ucsdata.dbcs_screenfont)) + keys_unicode[0] = 10; + } + + /* + * 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(conf, CONF_alt_only)) + return 0; + + return -1; +} + +#ifdef MOD_PERSO +/* Creer un titre de fenetre a partir d'un schema donne + %%f: le folder auquel apprtient le session + %%h: le hostname + %%i: le pid du process + %%p: le port + %%P: le protocole + %%s: nom de la session (vide sinon) + %%u: le user + %%l: la liste des port forward locaux + %%d: la liste des port forward dynamic +Ex: %%P://%%u@%%h:%%p +Ex: %%f / %%s +*/ +void make_title( char * res, char * fmt, const char * title ) { + int p; + char b[1024] ; + int port ; + + sprintf( res, fmt, title ) ; + + while( (p=poss( "%%s", res)) > 0 ) { del(res,p,3); if(strlen(conf_get_str(conf,CONF_sessionname))>0) insert(res,conf_get_str(conf,CONF_sessionname),p); } + while( (p=poss( "%%h", res)) > 0 ) { + del(res,p,3); + if( conf_get_int(conf,CONF_protocol) == PROT_SERIAL ) { + insert(res,conf_get_str(conf,CONF_serline),p); + } else { + insert(res,conf_get_str(conf,CONF_host),p); + } + } + while( (p=poss( "%%u", res)) > 0 ) { del(res,p,3); insert(res,conf_get_str(conf,CONF_username),p); } + while( (p=poss( "%%f", res)) > 0 ) { del(res,p,3); if(strlen(conf_get_str(conf,CONF_folder))>0) insert(res,conf_get_str(conf,CONF_folder),p); } + + port = conf_get_int(conf,CONF_port); + switch(conf_get_int(conf,CONF_protocol)) { + case PROT_RAW: strcpy(b,"raw"); break; + case PROT_TELNET: strcpy(b,"telnet"); if(port==-1) port=23 ; break; + case PROT_RLOGIN: strcpy(b,"rlogin"); break; + case PROT_SSH: strcpy(b,"ssh"); if(port==-1) port=22 ; break; + case PROT_SERIAL: strcpy(b,"serial"); break; + } + while( (p=poss( "%%P", res)) > 0 ) { del(res,p,3); insert(res,b,p); } + + sprintf(b,"%d", port ) ; + while( (p=poss( "%%p", res)) > 0 ) { del(res,p,3); insert(res,b,p); } + + sprintf(b,"%ld", GetCurrentProcessId() ) ; + while( (p=poss( "%%i", res)) > 0 ) { del(res,p,3); insert(res,b,p); } + while( (p=poss( "%%d", res)) > 0 ) { // forward port dynamic + char *key, *val, *k; + int nb=0 ; + del(res,p,3) ; + b[0]='\0'; + for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) { + k=key; if( (k[0]=='4')||(k[0]=='6') ) { k++; } + if( (k[0]=='L') && (!strcmp(val,"D")) ) { + if(nb!=0) {strcat(b,"|");} + strcat(b,k+1); + switch( GetPortFwdState( atoi(k+1), GetCurrentProcessId() ) ) { + case -1: + break ; + case 0: + strcat(b,"+"); + break ; + default: + strcat(b,"~"); + } + nb++; + } + } + insert(res,b,p) ; + } + while( (p=poss( "%%l", res)) > 0 ) { // forward port locaux + char *key, *val, *k; + int nb=0 ; + del(res,p,3) ; + b[0]='\0'; + for (val = conf_get_str_strs(conf, CONF_portfwd, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_portfwd, key, &key)) { + k=key; if( (k[0]=='4')||(k[0]=='6') ) { k++; } + if( (k[0]=='L') && (strcmp(val,"D")) ) { + if(nb!=0) {strcat(b,"|");} + strcat(b,k+1); + nb++; + } + } + insert(res,b,p) ; + } +} + + +void set_title_internal(TermWin *tw, const char *title) { + sfree(window_name); + window_name = dupstr(title); + if (conf_get_bool(conf, CONF_win_name_always) || !IsIconic(wgs.term_hwnd)) + SetWindowText(wgs.term_hwnd, title); +} + +static void wintw_set_title(TermWin *tw, const char *title_in) { + char *buffer, fmt[256]="%s" ; + char *title ; + + if( title_in==NULL ) { return ; } + title = (char*)malloc(strlen(title_in)+1); strcpy(title,title_in); + + if( (title[0]=='_')&&(title[1]=='_') ) { // Mode commande a distance + if( ManageLocalCmd( MainHwnd, title+2 ) ) { free(title); return ; } + } + + if( !GetTitleBarFlag() ) { set_title_internal( tw, title ) ; free(title) ; return ; } + + if( strstr(title, " (PROTECTED)")==(title+strlen(title)-12) ) + { title[strlen(title)-12]='\0' ; } + +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + buffer = (char*) malloc( strlen( title ) + strlen( conf_get_str(conf,CONF_host)) + strlen( conf_get_filename(conf,CONF_bg_image_filename)->path ) + 40 ) ; + if( GetBackgroundImageFlag() && GetImageViewerFlag() && (!PuttyFlag) ) { sprintf( buffer, "%s", conf_get_filename(conf,CONF_bg_image_filename)->path ) ; } + else +#else + buffer = (char*) malloc( strlen( title ) + strlen( conf_get_str(conf,CONF_host)) + 40 ) ; +#endif + if( GetSizeFlag() && (!IsZoomed( MainHwnd )) ) { + if( strlen( title ) > 0 ) { + if( title[strlen(title)-1] == ']' ) { + make_title( buffer, "%s", title ) ; + } else { + sprintf( fmt, "%%s [%dx%d]", conf_get_int(conf,CONF_height), conf_get_int(conf,CONF_width)) ; + make_title( buffer, fmt, title ) ; + } + } else { + sprintf( buffer, "%s [%dx%d] - %s", conf_get_str(conf,CONF_host), conf_get_int(conf,CONF_height), conf_get_int(conf,CONF_width), appname ) ; + } + } else { + if( strlen( title ) > 0 ) { + make_title( buffer, "%s", title ) ; + } else { + sprintf( buffer, "%s - %s", conf_get_str(conf,CONF_host), appname ) ; + } + } + if( GetProtectFlag() ) if( strstr(buffer, " (PROTECTED)")==NULL ) { strcat( buffer, " (PROTECTED)" ) ; } + if( conf_get_bool(conf, CONF_alwaysontop) ) if( strstr(buffer, " (ONTOP)")==NULL ) { strcat( buffer, " (ONTOP)" ) ; } + if( conf_get_bool(conf, CONF_ssh_tunnel_print_in_title) ) if( strstr(buffer, " (SOCKS: ")==NULL ) { + make_title( fmt, " (SOCKS: %s)", "%%d") ; + strcat( buffer, fmt ) ; + } + set_title_internal( tw, buffer ) ; + free(title); + free(buffer); +} + +static void wintw_set_icon_title(TermWin *tw, const char *title2) +{ + char title[1024]="",buf[512]=""; ; + int i=0; + do { buf[i]=title2[i]; i++ ; } + while ( (i<511)&&(title2[i]!='\0') ) ; + buf[i+1]='\0'; + + make_title( title, "%s", buf ) ; + sfree(icon_name); + icon_name = snewn(1 + strlen(title), char); + strcpy(icon_name, title); + if (conf_get_bool(conf, CONF_win_name_always) || !IsIconic(wgs.term_hwnd)) + SetWindowText(wgs.term_hwnd, title); +} +#else +static void wintw_set_title(TermWin *tw, const char *title) +{ + sfree(window_name); + window_name = dupstr(title); + if (conf_get_bool(conf, CONF_win_name_always) || !IsIconic(wgs.term_hwnd)) + SetWindowText(wgs.term_hwnd, title); +} + +static void wintw_set_icon_title(TermWin *tw, const char *title) +{ + sfree(icon_name); + icon_name = dupstr(title); + if (!conf_get_bool(conf, CONF_win_name_always) && IsIconic(wgs.term_hwnd)) + SetWindowText(wgs.term_hwnd, title); +} +#endif + +static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page) +{ + SCROLLINFO si; + + if (!conf_get_bool(conf, is_full_screen() ? + 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) +{ +// assert(!wintw_hdc); + wintw_hdc = make_hdc(); + return wintw_hdc != NULL; +} + +static void wintw_free_draw_ctx(TermWin *tw) +{ + assert(wintw_hdc); + free_hdc(wintw_hdc); + wintw_hdc = NULL; +} + +/* + * Set up the colour palette. + */ +static void init_palette(void) +{ + pal = NULL; + logpal = snew_plus(LOGPALETTE, (OSC4_NCOLOURS - 1) * sizeof(PALETTEENTRY)); + logpal->palVersion = 0x300; + logpal->palNumEntries = OSC4_NCOLOURS; + for (unsigned i = 0; i < OSC4_NCOLOURS; i++) + logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE; +} + +static void wintw_palette_set(TermWin *win, unsigned start, + unsigned ncolours, const rgb *colours_in) +{ + assert(start <= OSC4_NCOLOURS); + assert(ncolours <= OSC4_NCOLOURS - start); + + for (unsigned i = 0; i < ncolours; i++) { + const rgb *in = &colours_in[i]; + PALETTEENTRY *out = &logpal->palPalEntry[i + start]; + out->peRed = in->r; + out->peGreen = in->g; + out->peBlue = in->b; + colours[i + start] = RGB(in->r, in->g, in->b) ^ colorref_modifier; + } + + bool got_new_palette = false; + + if (!tried_pal && conf_get_bool(conf, CONF_try_palette)) { + HDC hdc = GetDC(wgs.term_hwnd); + if (GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) { + pal = CreatePalette(logpal); + if (pal) { + SelectPalette(hdc, 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 */ + colorref_modifier = PALETTERGB(0, 0, 0) ^ RGB(0, 0, 0); + for (unsigned i = 0; i < OSC4_NCOLOURS; i++) + colours[i] ^= colorref_modifier; + + /* Inhibit the SetPaletteEntries call below */ + got_new_palette = true; + } + } + ReleaseDC(wgs.term_hwnd, hdc); + tried_pal = true; + } + + if (pal && !got_new_palette) { + /* We already had a palette, so replace the changed colours in the + * existing one. */ + SetPaletteEntries(pal, start, ncolours, logpal->palPalEntry + start); + + HDC hdc = make_hdc(); + UnrealizeObject(pal); + RealizePalette(hdc); + free_hdc(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(int clipboard, char *data, int len, bool must_deselect) +{ + 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 (!must_deselect) + SendMessage(wgs.term_hwnd, WM_IGNORE_CLIP, true, 0); + + if (OpenClipboard(wgs.term_hwnd)) { + EmptyClipboard(); + SetClipboardData(CF_TEXT, clipdata); + CloseClipboard(); + } else + GlobalFree(clipdata); + + if (!must_deselect) + SendMessage(wgs.term_hwnd, WM_IGNORE_CLIP, false, 0); +} + +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) +{ + 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(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, totallen, 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(conf, CONF_font); + + get_unitab(CP_ACP, unitab, 0); + + strbuf_catf( + 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 (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 = &logpal->palPalEntry[i]; + strbuf_catf(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++) + strbuf_catf(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 (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 (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 (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) { + strbuf_catf(rtf, "\\cf%d ", + (fgcolour >= 0) ? palette[fgcolour] : 0); + } else { + rgbindex rgb, *rgbp; + rgb.ref = fg; + if ((rgbp = find234(rgbtree, &rgb, NULL)) != NULL) + strbuf_catf(rtf, "\\cf%d ", rgbp->index); + } + } + + if ((lastbgcolour != bgcolour) || (lastbg != bg)) { + lastbgcolour = bgcolour; + lastbg = bg; + if (bg == -1) + strbuf_catf(rtf, "\\highlight%d ", + (bgcolour >= 0) ? palette[bgcolour] : 0); + else { + rgbindex rgb, *rgbp; + rgb.ref = bg; + if ((rgbp = find234(rgbtree, &rgb, NULL)) != NULL) + strbuf_catf(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); + totallen = blen + alen; + for (i = 0; i < multilen; i++) { + if (tdata[tindex+i] == '\\' || + tdata[tindex+i] == '{' || + tdata[tindex+i] == '}') + totallen += 2; + else if (tdata[tindex+i] == 0x0D || tdata[tindex+i] == 0x0A) + totallen += 6; /* \par\r\n */ + else if (tdata[tindex+i] > 0x7E || tdata[tindex+i] < 0x20) + totallen += 4; + else + totallen++; + } + + 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) { + strbuf_catf(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(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'; +#ifdef MOD_PERSO + if( (GetPasteSize()==0) || (clipboard_length<=GetPasteSize()) || MessageBox(NULL,"Clipboard content is very large.\nAre you sure ?","Confirmation",MB_YESNO|MB_ICONWARNING)==IDYES ) +#endif + term_do_paste(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'; +#ifdef MOD_PERSO + if( (GetPasteSize()==0) || (clipboard_length<=GetPasteSize()) || MessageBox(NULL,"Clipboard content is very large.\nAre you sure ?","Confirmation",MB_YESNO|MB_ICONWARNING)==IDYES ) +#endif + term_do_paste(term, clipboard_contents, clipboard_length); + } + } + + sfree(clipboard_contents); +} + +static void wintw_clip_request_paste(TermWin *tw, int clipboard) +{ + 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(true); + title = dupprintf("%s Fatal Error", appname); + MessageBox(wgs.term_hwnd, 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(true); + title = dupprintf("%s Error", appname); + MessageBox(wgs.term_hwnd, message, title, MB_ICONERROR | MB_OK); + sfree(message); + sfree(title); +} + +static bool flash_window_ex(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 */ +} + +static void flash_window(int mode); +static long next_flash; +static bool flashing = false; + +/* + * Timer for platforms where we must maintain window flashing manually + * (e.g., Win95). + */ +static void flash_window_timer(void *ctx, unsigned long now) +{ + if (flashing && now == next_flash) { + flash_window(1); + } +} + +/* + * Manage window caption / taskbar flashing, if enabled. + * 0 = stop, 1 = maintain, 2 = start + */ +static void flash_window(int mode) +{ +#ifdef MOD_PERSO + static int BlinkingState = 0 ; + if(MaxBlinkingTime && (mode!=0)) { + if( BlinkingState>=MaxBlinkingTime ) { + BlinkingState = 0 ; + flash_window(0); + return ; + } else { + BlinkingState++ ; + } + } + if( mode==0 ) { BlinkingState=0 ; } +#endif + int beep_ind = conf_get_int(conf, CONF_beep_ind); + if ((mode == 0) || (beep_ind == B_IND_DISABLED)) { + /* stop */ + if (flashing) { + flashing = false; + if (p_FlashWindowEx) + flash_window_ex(FLASHW_STOP, 0, 0); + else + FlashWindow(wgs.term_hwnd, false); + } + + } else if (mode == 2) { + /* start */ + if (!flashing) { + 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. */ +#ifdef MOD_PERSO + flash_window_ex(FLASHW_ALL | FLASHW_TIMER, + MaxBlinkingTime, + 0 /* system cursor blink rate */); +#else + flash_window_ex(FLASHW_ALL | FLASHW_TIMER, + (beep_ind == B_IND_FLASH ? 0 : 2), + 0 /* system cursor blink rate */); +#endif + /* No need to schedule timer */ + } else { + FlashWindow(wgs.term_hwnd, true); + next_flash = schedule_timer(450, flash_window_timer, + wgs.term_hwnd); + } + } + + } else if ((mode == 1) && (beep_ind == B_IND_FLASH)) { + /* maintain */ + if (flashing && !p_FlashWindowEx) { + FlashWindow(wgs.term_hwnd, true); /* toggle */ + next_flash = schedule_timer(450, flash_window_timer, + wgs.term_hwnd); + } + } +} + +/* + * Beep. + */ +static void wintw_bell(TermWin *tw, int mode) +{ + 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. + */ + static long lastbeep = 0; + long beepdiff; + + beepdiff = GetTickCount() - lastbeep; + 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. + */ + lastbeep = GetTickCount(); + } else if (mode == BELL_WAVEFILE) { + Filename *bell_wavefile = conf_get_filename(conf, CONF_bell_wavefile); + if (!p_PlaySound || !p_PlaySound(bell_wavefile->path, NULL, + SND_ASYNC | SND_FILENAME)) { + char *buf, *otherbuf; + show_mouseptr(true); + buf = dupprintf( + "Unable to play sound file\n%s\nUsing default sound instead", + bell_wavefile->path); + otherbuf = dupprintf("%s Sound Error", appname); + MessageBox(wgs.term_hwnd, buf, otherbuf, + MB_OK | MB_ICONEXCLAMATION); + sfree(buf); + sfree(otherbuf); + conf_set_int(conf, CONF_beep, BELL_DEFAULT); + } + } else if (mode == BELL_PCSPEAKER) { + static long lastbeep = 0; + long beepdiff; + + beepdiff = GetTickCount() - lastbeep; + 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); + lastbeep = GetTickCount(); + } + /* Otherwise, either visual bell or disabled; do nothing here */ + if (!term->has_focus) { +#ifdef MOD_PERSO + if( GetVisibleFlag()!=VISIBLE_TRAY ) { + if(conf_get_bool(conf,CONF_foreground_on_bell) ) { // Tester avec sleep 4 ; echo -e '\a' + if( IsIconic(wgs.term_hwnd) ) SwitchToThisWindow( wgs.term_hwnd, TRUE ) ; + else SetForegroundWindow( wgs.term_hwnd ) ; + } else { + if( mode == BELL_VISUAL ) { + if( IsIconic(wgs.term_hwnd) ) + FlashWindow(wgs.term_hwnd, TRUE) ; + else + flash_window(2) ; + } else { + if( conf_get_int(conf,CONF_beep_ind)==B_IND_FLASH ) flash_window(2) ; + } + } + } else if( GetVisibleFlag()==VISIBLE_TRAY ) { + if( conf_get_bool(conf,CONF_foreground_on_bell) ) { + SendMessage( wgs.term_hwnd, WM_COMMAND, IDM_FROMTRAY, 0 ) ; + } else if(mode == BELL_VISUAL) + SetTimer(wgs.term_hwnd, TIMER_BLINKTRAYICON, (int)500, NULL) ; + //SendMessage( MainHwnd, WM_COMMAND, IDM_FROMTRAY, 0 ); + //flash_window(2); /* start */ + //ShowWindow( MainHwnd, SW_MINIMIZE); + } else +#endif + flash_window(2); /* start */ + } +} + +/* + * Minimise or restore the window in response to a server-side + * request. + */ +static void wintw_set_minimised(TermWin *tw, bool minimised) +{ + 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) +{ + int resize_action = conf_get_int(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) +{ + if (conf_get_bool(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) +{ + 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) +{ + 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() +{ + 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(RECT * ss) +{ +#if defined(MONITOR_DEFAULTTONEAREST) && !defined(NO_MULTIMON) + HMONITOR mon; + MONITORINFO mi; + mon = MonitorFromWindow(wgs.term_hwnd, MONITOR_DEFAULTTONEAREST); + mi.cbSize = sizeof(mi); + GetMonitorInfo(mon, &mi); + + /* structure copy */ + *ss = mi.rcMonitor; + return true; +#else +/* 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); +#endif +} + + +/* + * Go full-screen. This should only be called when we are already + * maximised. + */ +static void make_full_screen() +{ + DWORD style; + RECT ss; + + assert(IsZoomed(wgs.term_hwnd)); + + if (is_full_screen()) + return; + + /* Remove the window furniture. */ + style = GetWindowLongPtr(wgs.term_hwnd, GWL_STYLE); + style &= ~(WS_CAPTION | WS_BORDER | WS_THICKFRAME); + if (conf_get_bool(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(&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(0); + + /* Tick the menu item in the System and context menus. */ + { + int i; + for (i = 0; i < lenof(popup_menus); i++) + CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_CHECKED); + } +} + +/* + * Clear the full-screen attributes. + */ +static void clear_full_screen() +{ + DWORD oldstyle, style; + + /* Reinstate the window furniture. */ + style = oldstyle = GetWindowLongPtr(wgs.term_hwnd, GWL_STYLE); + style |= WS_CAPTION | WS_BORDER; + if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED) + style &= ~WS_THICKFRAME; + else + style |= WS_THICKFRAME; + if (conf_get_bool(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(popup_menus); i++) + CheckMenuItem(popup_menus[i].menu, IDM_FULLSCREEN, MF_UNCHECKED); + } +} + +/* + * Toggle full-screen mode. + */ +static void flip_full_screen() +{ + if (is_full_screen()) { + ShowWindow(wgs.term_hwnd, SW_RESTORE); + } else if (IsZoomed(wgs.term_hwnd)) { + make_full_screen(); + } else { + SendMessage(wgs.term_hwnd, WM_FULLSCR_ON_MAX, 0, 0); + ShowWindow(wgs.term_hwnd, SW_MAXIMIZE); + } +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) + if( GetBackgroundImageFlag()&&(!PuttyFlag)&&(conf_get_int(conf,CONF_bg_image_abs_fixed)==1)&&(conf_get_int(conf,CONF_bg_type)!=0) ) RefreshBackground( wgs.term_hwnd ) ; +#endif +} + +#ifdef MOD_PERSO +/* rutty: special entry point for ldisc.c - local backend + modified version for all other backends + */ +#ifdef MOD_RUTTY +size_t win_seat_output_local(Seat *seat, bool is_stderr, const void *data, size_t len) +{ + int res = term_data(term, is_stderr, data, len) ; + if( (!GetPuttyFlag()) && (ScriptFileContent!=NULL) ) ManageInitScript( data, len ) ; + return res ; +} + +static size_t win_seat_output(Seat *seat, bool is_stderr, const void *data, size_t len) +{ + if( (!GetPuttyFlag()) && (GetRuttyFlag()) ) script_remote(&scriptdata, data, len); + int res = term_data(term, is_stderr, data, len) ; + if( (!GetPuttyFlag()) && (ScriptFileContent!=NULL) ) ManageInitScript( data, len ) ; + return res ; +} +#else +/* Version avec MOD_PERSO seule sans MOD_RUTTY */ +static size_t win_seat_output(Seat *seat, bool is_stderr, const void *data, size_t len) +{ + int res = term_data(term, is_stderr, data, len) ; + if( (!GetPuttyFlag()) && (ScriptFileContent!=NULL) ) ManageInitScript( data, len ) ; + return res ; +} +#endif +#else +static size_t win_seat_output(Seat *seat, bool is_stderr, + const void *data, size_t len) +{ + return term_data(term, is_stderr, data, len); +} +#endif + +static bool win_seat_eof(Seat *seat) +{ + return true; /* do respond to incoming EOF with outgoing */ +} + +static int win_seat_get_userpass_input( + Seat *seat, prompts_t *p, bufchain *input) +{ + int ret; + ret = cmdline_get_passwd_input(p); + if (ret == -1) + ret = term_get_userpass_input(term, p, input); + return ret; +} + +static bool win_seat_set_trust_status(Seat *seat, bool trusted) +{ + term_set_trust_status(term, trusted); + return true; +} + +static bool win_seat_get_cursor_position(Seat *seat, int *x, int *y) +{ + term_get_cursor_position(term, x, y); + return true; +} + +static bool win_seat_get_window_pixel_size(Seat *seat, int *x, int *y) +{ + RECT r; + GetWindowRect(wgs.term_hwnd, &r); + *x = r.right - r.left; + *y = r.bottom - r.top; + return true; +} + +#include "../../kitty_light.c" diff --git a/0.77_My_PuTTY/windows/wingss.c b/0.77_My_PuTTY/windows/wingss.c new file mode 100644 index 0000000..a7972dc --- /dev/null +++ b/0.77_My_PuTTY/windows/wingss.c @@ -0,0 +1,702 @@ +#ifndef NO_GSSAPI + +#include +#include "putty.h" + +#define SECURITY_WIN32 +#include + +#include "pgssapi.h" +#include "sshgss.h" +#include "sshgssc.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"}; + +const char *gsslogmsg = NULL; + +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; + HKEY regkey; + struct ssh_gss_liblist *list = snew(struct ssh_gss_liblist); + char *path; + static HMODULE kernel32_module; + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); + } +#if defined _MSC_VER && _MSC_VER < 1900 + /* 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; + if (RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MIT\\Kerberos", ®key) + == ERROR_SUCCESS) { + DWORD type, size; + LONG ret; + char *buffer; + + /* Find out the string length */ + ret = RegQueryValueEx(regkey, "InstallDir", NULL, &type, NULL, &size); + + if (ret == ERROR_SUCCESS && type == REG_SZ) { + buffer = snewn(size + 20, char); + ret = RegQueryValueEx(regkey, "InstallDir", NULL, + &type, (LPBYTE)buffer, &size); + if (ret == ERROR_SUCCESS && type == REG_SZ) { + strcat (buffer, "\\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, buffer); + p_AddDllDirectory(dllPath); + sfree(dllPath); + } + strcat (buffer, "\\gssapi"MIT_KERB_SUFFIX".dll"); + module = LoadLibraryEx (buffer, 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(buffer); + } + RegCloseKey(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; + + GET_WINDOWS_FUNCTION(module, AcquireCredentialsHandleA); + GET_WINDOWS_FUNCTION(module, InitializeSecurityContextA); + GET_WINDOWS_FUNCTION(module, FreeContextBuffer); + GET_WINDOWS_FUNCTION(module, FreeCredentialsHandle); + GET_WINDOWS_FUNCTION(module, DeleteSecurityContext); + GET_WINDOWS_FUNCTION(module, QueryContextAttributesA); + GET_WINDOWS_FUNCTION(module, MakeSignature); + GET_WINDOWS_FUNCTION(module, VerifySignature); + + ssh_sspi_bind_fns(lib); + } + + /* + * Custom GSSAPI DLL. + */ + module = NULL; + path = conf_get_filename(conf, CONF_ssh_gss_custom)->path; + if (*path) { + 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 = strlen(path); + + while (pathlen > 0 && path[pathlen-1] != ':' && + path[pathlen-1] != '\\') + pathlen--; + + if (pathlen > 0 && path[pathlen-1] != '\\') + pathlen--; + + if (pathlen > 0) { + char *dirpath = dupprintf("%.*s", pathlen, path); + wchar_t *dllPath = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, dirpath); + p_AddDllDirectory(dllPath); + sfree(dllPath); + sfree(dirpath); + } + } + + module = LoadLibraryEx(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'", path); + lib->handle = (void *)module; + +#define BIND_GSS_FN(name) \ + lib->u.gssapi.name = (t_gss_##name) GetProcAddress(module, "gss_" #name) + + BIND_GSS_FN(delete_sec_context); + BIND_GSS_FN(display_status); + BIND_GSS_FN(get_mic); + BIND_GSS_FN(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; + unsigned long 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/0.77_My_PuTTY/windows/winhandl.c b/0.77_My_PuTTY/windows/winhandl.c new file mode 100644 index 0000000..8d6ddbf --- /dev/null +++ b/0.77_My_PuTTY/windows/winhandl.c @@ -0,0 +1,731 @@ +/* + * winhandl.c: Module to give Windows front ends the general + * ability to deal with consoles, pipes, serial ports, or any other + * type of data stream accessed through a Windows API HANDLE rather + * than a WinSock SOCKET. + * + * We do this by spawning a subthread to continuously try to read + * from the handle. Every time a read successfully returns some + * data, the subthread sets an event object which is picked up by + * the main thread, and the main thread then sets an event in + * return to instruct the subthread to resume reading. + * + * Output works precisely the other way round, in a second + * subthread. The output subthread should not be attempting to + * write all the time, because it hasn't always got data _to_ + * write; so the output thread waits for an event object notifying + * it to _attempt_ a write, and then it sets an event in return + * when one completes. + * + * (It's terribly annoying having to spawn a subthread for each + * direction of each handle. Technically it isn't necessary for + * serial ports, since we could use overlapped I/O within the main + * thread and wait directly on the event objects in the OVERLAPPED + * structures. However, we can't use this trick for some types of + * file handle at all - for some reason Windows restricts use of + * OVERLAPPED to files which were opened with the overlapped flag - + * and so we must use threads for those. This being the case, it's + * simplest just to use threads for everything rather than trying + * to keep track of multiple completely separate mechanisms.) + */ + +#include + +#include "putty.h" + +/* ---------------------------------------------------------------------- + * Generic definitions. + */ + +/* + * Maximum amount of backlog we will allow to build up on an input + * handle before we stop reading from it. + */ +#define MAX_BACKLOG 32768 + +struct handle_generic { + /* + * Initial fields common to both handle_input and handle_output + * structures. + * + * The three HANDLEs are set up at initialisation time and are + * thereafter read-only to both main thread and subthread. + * `moribund' is only used by the main thread; `done' is + * written by the main thread before signalling to the + * subthread. `defunct' and `busy' are used only by the main + * thread. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + 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, HT_FOREIGN } HandleType; + +/* ---------------------------------------------------------------------- + * Input threads. + */ + +/* + * Data required by an input thread. + */ +struct handle_input { + /* + * Copy of the handle_generic structure. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + 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 signalling ev_to_main, + * and read by the main thread after receiving that signal. + */ + char buffer[4096]; /* the data read from the handle */ + DWORD len; /* how much data that was */ + int readerr; /* lets us know about read errors */ + + /* + * Callback function called by this module when data arrives on + * an input handle. + */ + handle_inputfn_t gotdata; +}; + +/* + * The actual thread procedure for an input thread. + */ +static DWORD WINAPI handle_input_threadfunc(void *param) +{ + struct handle_input *ctx = (struct handle_input *) param; + OVERLAPPED ovl, *povl; + HANDLE oev; + 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); + + SetEvent(ctx->ev_to_main); + + if (finished) + break; + + WaitForSingleObject(ctx->ev_from_main, INFINITE); + if (ctx->done) { + /* + * The main thread has asked us to shut down. Send back an + * event indicating that we've done so. Hereafter we must + * not touch ctx at all, because the main thread might + * have freed it. + */ + SetEvent(ctx->ev_to_main); + break; + } + } + + if (povl) + CloseHandle(oev); + + return 0; +} + +/* + * This is called after a 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 ev_to_main; /* event used to signal main thread */ + 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 signalling ev_to_main, + * and read by the main thread after receiving that signal. + */ + DWORD lenwritten; /* how much data we actually wrote */ + int writeerr; /* return value from WriteFile */ + + /* + * Data only ever read or written by the main thread. + */ + bufchain queued_data; /* data still waiting to be written */ + enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof; + + /* + * Callback function called when the backlog in the bufchain + * drops. + */ + handle_outputfn_t sentdata; +}; + +static DWORD WINAPI handle_output_threadfunc(void *param) +{ + struct handle_output *ctx = (struct handle_output *) param; + OVERLAPPED ovl, *povl; + HANDLE oev; + 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. + */ + SetEvent(ctx->ev_to_main); + break; + } + if (povl) { + memset(povl, 0, sizeof(OVERLAPPED)); + povl->hEvent = oev; + } + + writeret = WriteFile(ctx->h, ctx->buffer, ctx->len, + &ctx->lenwritten, povl); + if (!writeret) + ctx->writeerr = GetLastError(); + else + ctx->writeerr = 0; + if (povl && !writeret && GetLastError() == ERROR_IO_PENDING) { + writeret = GetOverlappedResult(ctx->h, povl, + &ctx->lenwritten, true); + if (!writeret) + ctx->writeerr = GetLastError(); + else + ctx->writeerr = 0; + } + + SetEvent(ctx->ev_to_main); + if (!writeret) { + /* + * The write operation has suffered an error. Telling that + * to the main thread will cause it to set its 'defunct' + * flag and dispose of the handle structure at the next + * opportunity, so we must not touch ctx at all after + * this. + */ + break; + } + } + + if (povl) + CloseHandle(oev); + + return 0; +} + +static void handle_try_output(struct handle_output *ctx) +{ + 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) { + CloseHandle(ctx->h); + ctx->h = INVALID_HANDLE_VALUE; + ctx->outgoingeof = EOF_SENT; + } +} + +/* ---------------------------------------------------------------------- + * 'Foreign events'. These are handle structures which just contain a + * single event object passed to us by another module such as + * winnps.c, so that they can make use of our handle_get_events / + * handle_got_event mechanism for communicating with application main + * loops. + */ +struct handle_foreign { + /* + * Copy of the handle_generic structure. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + 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 */ + + /* + * Our own data, just consisting of knowledge of who to call back. + */ + void (*callback)(void *); + void *ctx; +}; + +/* ---------------------------------------------------------------------- + * Unified code handling both input and output threads. + */ + +struct handle { + HandleType type; + union { + struct handle_generic g; + struct handle_input i; + struct handle_output o; + struct handle_foreign f; + } u; +}; + +static tree234 *handles_by_evtomain; + +static int handle_cmp_evtomain(void *av, void *bv) +{ + struct handle *a = (struct handle *)av; + struct handle *b = (struct handle *)bv; + + if ((uintptr_t)a->u.g.ev_to_main < (uintptr_t)b->u.g.ev_to_main) + return -1; + else if ((uintptr_t)a->u.g.ev_to_main > (uintptr_t)b->u.g.ev_to_main) + return +1; + else + return 0; +} + +static int handle_find_evtomain(void *av, void *bv) +{ + HANDLE *a = (HANDLE *)av; + struct handle *b = (struct handle *)bv; + + if ((uintptr_t)*a < (uintptr_t)b->u.g.ev_to_main) + return -1; + else if ((uintptr_t)*a > (uintptr_t)b->u.g.ev_to_main) + return +1; + else + return 0; +} + +struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, + void *privdata, int flags) +{ + struct handle *h = snew(struct handle); + DWORD in_threadid; /* required for Win9x */ + + h->type = HT_INPUT; + h->u.i.h = handle; + h->u.i.ev_to_main = CreateEvent(NULL, false, false, NULL); + h->u.i.ev_from_main = CreateEvent(NULL, false, false, NULL); + h->u.i.gotdata = gotdata; + h->u.i.defunct = false; + h->u.i.moribund = false; + h->u.i.done = false; + h->u.i.privdata = privdata; + h->u.i.flags = flags; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + 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_to_main = CreateEvent(NULL, false, false, NULL); + h->u.o.ev_from_main = CreateEvent(NULL, false, false, NULL); + h->u.o.busy = false; + h->u.o.defunct = false; + h->u.o.moribund = false; + h->u.o.done = false; + h->u.o.privdata = privdata; + bufchain_init(&h->u.o.queued_data); + h->u.o.outgoingeof = EOF_NO; + h->u.o.sentdata = sentdata; + h->u.o.flags = flags; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + 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; +} + +struct handle *handle_add_foreign_event(HANDLE event, + void (*callback)(void *), void *ctx) +{ + struct handle *h = snew(struct handle); + + h->type = HT_FOREIGN; + h->u.f.h = INVALID_HANDLE_VALUE; + h->u.f.ev_to_main = event; + h->u.f.ev_from_main = INVALID_HANDLE_VALUE; + h->u.f.defunct = true; /* we have no thread in the first place */ + h->u.f.moribund = false; + h->u.f.done = false; + h->u.f.privdata = NULL; + h->u.f.callback = callback; + h->u.f.ctx = ctx; + h->u.f.busy = true; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + return h; +} + +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); + } +} + +HANDLE *handle_get_events(int *nevents) +{ + HANDLE *ret; + struct handle *h; + int i; + size_t n, size; + + /* + * Go through our tree counting the handle objects currently + * engaged in useful activity. + */ + ret = NULL; + n = size = 0; + if (handles_by_evtomain) { + for (i = 0; (h = index234(handles_by_evtomain, i)) != NULL; i++) { + if (h->u.g.busy) { + sgrowarray(ret, size, n); + ret[n++] = h->u.g.ev_to_main; + } + } + } + + *nevents = n; + return ret; +} + +static void handle_destroy(struct handle *h) +{ + if (h->type == HT_OUTPUT) + bufchain_clear(&h->u.o.queued_data); + CloseHandle(h->u.g.ev_from_main); + CloseHandle(h->u.g.ev_to_main); + del234(handles_by_evtomain, h); + sfree(h); +} + +void handle_free(struct handle *h) +{ + assert(h && !h->u.g.moribund); + if (h->u.g.busy && h->type != HT_FOREIGN) { + /* + * If the handle is currently busy, we cannot immediately free + * it, because its subthread is in the middle of something. + * (Exception: foreign handles don't have a subthread.) + * + * Instead we must wait until it's finished its current + * operation, because otherwise the subthread will write to + * invalid memory after we free its context from under it. So + * we set the moribund flag, which will be noticed next time + * an operation completes. + */ + h->u.g.moribund = true; + } else if (h->u.g.defunct) { + /* + * There isn't even a subthread; we can go straight to + * handle_destroy. + */ + handle_destroy(h); + } else { + /* + * The subthread is alive but not busy, so we now signal it + * to die. Set the moribund flag to indicate that it will + * want destroying after that. + */ + h->u.g.moribund = true; + h->u.g.done = true; + h->u.g.busy = true; + SetEvent(h->u.g.ev_from_main); + } +} + +void handle_got_event(HANDLE event) +{ + struct handle *h; + + assert(handles_by_evtomain); + h = find234(handles_by_evtomain, &event, handle_find_evtomain); + if (!h) { + /* + * This isn't an error condition. If two or more event + * objects were signalled during the same select operation, + * and processing of the first caused the second handle to + * be closed, then it will sometimes happen that we receive + * an event notification here for a handle which is already + * deceased. In that situation we simply do nothing. + */ + return; + } + + 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); + } 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); + handle_try_output(&h->u.o); + } + break; + + case HT_FOREIGN: + /* Just call the callback. */ + h->u.f.callback(h->u.f.ctx); + 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/0.77_My_PuTTY/windows/winhelp.c b/0.77_My_PuTTY/windows/winhelp.c new file mode 100644 index 0000000..521dda2 --- /dev/null +++ b/0.77_My_PuTTY/windows/winhelp.c @@ -0,0 +1,253 @@ +/* + * winhelp.c: centralised functions to launch Windows HTML Help files. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "win_res.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) { } +#ifdef MOD_PERSO +int has_embedded_chm(void) { return 0 ; } +#endif +#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; pos++) { + 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 = registry_get_string( + 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/0.77_My_PuTTY/windows/winhelp.h b/0.77_My_PuTTY/windows/winhelp.h new file mode 100644 index 0000000..18b47bd --- /dev/null +++ b/0.77_My_PuTTY/windows/winhelp.h @@ -0,0 +1,208 @@ +/* + * winhelp.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. */ + +#define HELPCTX(x) P(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_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_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_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_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_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" + +/* 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/0.77_My_PuTTY/windows/winhelp.rc2 b/0.77_My_PuTTY/windows/winhelp.rc2 new file mode 100644 index 0000000..3499d25 --- /dev/null +++ b/0.77_My_PuTTY/windows/winhelp.rc2 @@ -0,0 +1,8 @@ +#include "win_res.h" + +#ifdef EMBED_CHM +ID_CUSTOM_CHMFILE TYPE_CUSTOM_CHMFILE "../doc/putty.chm" +#define HELPVER " (with embedded help)" +#else +#define HELPVER " (without embedded help)" +#endif diff --git a/0.77_My_PuTTY/windows/winhsock.c b/0.77_My_PuTTY/windows/winhsock.c new file mode 100644 index 0000000..7316102 --- /dev/null +++ b/0.77_My_PuTTY/windows/winhsock.c @@ -0,0 +1,343 @@ +/* + * 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" + +typedef struct HandleSocket { + HANDLE send_H, recv_H, stderr_H; + struct handle *send_h, *recv_h, *stderr_h; + + /* + * Freezing one of these sockets is a slightly fiddly business, + * because the reads from the handle are happening in a separate + * thread as blocking system calls and so once one is in progress + * it can't sensibly be interrupted. Hence, after the user tries + * to freeze one of these sockets, it's unavoidable that we may + * receive one more load of data before we manage to get + * winhandl.c to stop reading. + */ + enum { + UNFROZEN, /* reading as normal */ + FREEZING, /* have been set to frozen but winhandl is still reading */ + FROZEN, /* really frozen - winhandl has been throttled */ + THAWING /* we're gradually releasing our remaining data */ + } frozen; + /* We buffer data here if we receive it from winhandl while frozen. */ + bufchain inputdata; + + /* Handle logging proxy error messages from stderr_H, if we have one. */ + ProxyStderrBuf psb; + + bool defer_close, deferred_close; /* in case of re-entrance */ + + char *error; + + 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(hs->plug, "Read error from handle", 0, 0); + return 0; + } else if (len == 0) { + plug_closing(hs->plug, NULL, 0, 0); + 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 winhandl.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) +{ + HandleSocket *hs = (HandleSocket *)handle_get_privdata(h); + + if (err) { + plug_closing(hs->plug, win_strerror(err), err, 0); + 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); + CloseHandle(hs->send_H); + if (hs->recv_H != hs->send_H) + CloseHandle(hs->recv_H); + bufchain_clear(&hs->inputdata); + + 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, winhandl.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 (defined _MSC_VER && _MSC_VER < 1900) || defined __MINGW32__ + /* 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, +}; + +Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H, + Plug *plug, bool overlapped) +{ + HandleSocket *hs; + int flags = (overlapped ? HANDLE_FLAG_OVERLAPPED : 0); + + hs = snew(HandleSocket); + hs->sock.vt = &HandleSocket_sockvt; + 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; + + return &hs->sock; +} diff --git a/0.77_My_PuTTY/windows/winjump.c b/0.77_My_PuTTY/windows/winjump.c new file mode 100644 index 0000000..7faf33c --- /dev/null +++ b/0.77_My_PuTTY/windows/winjump.c @@ -0,0 +1,752 @@ +/* + * winjump.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 persistantly 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: */ +#ifndef _PROPVARIANTINIT_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) + { +#ifdef MOD_PERSO + if (p_SetCurrentProcessExplicitAppUserModelID(L"9bis.com.KiTTY") == S_OK) +#else + if (p_SetCurrentProcessExplicitAppUserModelID(L"SimonTatham.PuTTY") == S_OK) +#endif + { + return true; + } + return false; + } + /* Function doesn't exist, which is ok for Pre-7 systems */ + + return true; + +} diff --git a/0.77_My_PuTTY/windows/winmisc.c b/0.77_My_PuTTY/windows/winmisc.c new file mode 100644 index 0000000..e80226a --- /dev/null +++ b/0.77_My_PuTTY/windows/winmisc.c @@ -0,0 +1,467 @@ +/* + * winmisc.c: miscellaneous Windows-specific things + */ + +#include +#include +#include +#include "putty.h" +#ifndef SECURITY_WIN32 +#define SECURITY_WIN32 +#endif +#include + +DWORD osMajorVersion, osMinorVersion, osPlatformId; + +char *platform_get_x_display(void) { + /* We may as well check for DISPLAY in case it's useful. */ + return dupstr(getenv("DISPLAY")); +} + +Filename *filename_from_str(const char *str) +{ + Filename *ret = snew(Filename); + ret->path = dupstr(str); + return ret; +} + +Filename *filename_copy(const Filename *fn) +{ + return filename_from_str(fn->path); +} + +const char *filename_to_str(const Filename *fn) +{ + return fn->path; +} + +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; +} + +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 (strchr("<>:\"/\\|?*", c)) + return '.'; + return c; +} + +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; +} + +void dll_hijacking_protection(void) +{ + /* + * 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. + */ + static HMODULE kernel32_module; + DECL_WINDOWS_FUNCTION(static, BOOL, SetDefaultDllDirectories, (DWORD)); + + if (!kernel32_module) { + kernel32_module = load_system32_dll("kernel32.dll"); +#if (defined _MSC_VER && _MSC_VER < 1900) + /* 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); + } +} + +void init_winver(void) +{ + 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 */ + } +} + +HMODULE load_system32_dll(const char *libname) +{ + /* + * Wrapper function to load a DLL out of c:\windows\system32 + * without going through the full DLL search path. (Hence no + * attack is possible by placing a substitute DLL earlier on that + * path.) + */ + static char *sysdir = NULL; + static size_t sysdirsize = 0; + char *fullpath; + HMODULE ret; + + if (!sysdir) { + size_t len; + while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize) + sgrowarray(sysdir, sysdirsize, len); + } + + fullpath = dupcat(sysdir, "\\", libname); + ret = LoadLibrary(fullpath); + sfree(fullpath); + return ret; +} + +/* + * A tree234 containing mappings from system error codes to strings. + */ + +struct errstring { + int error; + char *text; +}; + +static int errstring_find(void *av, void *bv) +{ + int *a = (int *)av; + struct errstring *b = (struct errstring *)bv; + if (*a < b->error) + return -1; + if (*a > b->error) + return +1; + return 0; +} +static int errstring_compare(void *av, void *bv) +{ + struct errstring *a = (struct errstring *)av; + return errstring_find(&a->error, bv); +} + +static tree234 *errstrings = NULL; + +const char *win_strerror(int error) +{ + struct errstring *es; + + if (!errstrings) + errstrings = newtree234(errstring_compare); + + es = find234(errstrings, &error, errstring_find); + + if (!es) { + 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; +} + +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_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); +} + +bool open_for_write_would_lose_data(const Filename *fn) +{ + WIN32_FILE_ATTRIBUTE_DATA attrs; + if (!GetFileAttributesEx(fn->path, 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; + } + if (attrs.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 (attrs.nFileSizeHigh == 0 && attrs.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; +} + +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++); + } + } +} + +#ifdef DEBUG +static FILE *debug_fp = NULL; +static HANDLE debug_hdl = INVALID_HANDLE_VALUE; +static int debug_got_console = 0; + +void dputs(const char *buf) +{ + DWORD dw; + + if (!debug_got_console) { + if (AllocConsole()) { + debug_got_console = 1; + debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE); + } + } + if (!debug_fp) { + debug_fp = fopen("debug.log", "w"); + } + + if (debug_hdl != INVALID_HANDLE_VALUE) { + WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL); + } + fputs(buf, debug_fp); + fflush(debug_fp); +} +#endif + +char *registry_get_string(HKEY root, const char *path, const char *leaf) +{ + HKEY key = root; + bool need_close_key = false; + char *toret = NULL, *str = NULL; + + if (path) { + if (RegCreateKey(key, path, &key) != ERROR_SUCCESS) + goto out; + need_close_key = true; + } + + DWORD type, size; + if (RegQueryValueEx(key, leaf, 0, &type, NULL, &size) != ERROR_SUCCESS) + goto out; + if (type != REG_SZ) + goto out; + + str = snewn(size + 1, char); + DWORD size_got = size; + if (RegQueryValueEx(key, leaf, 0, &type, (LPBYTE)str, + &size_got) != ERROR_SUCCESS) + goto out; + if (type != REG_SZ || size_got > size) + goto out; + str[size_got] = '\0'; + + toret = str; + str = NULL; + + out: + if (need_close_key) + RegCloseKey(key); + sfree(str); + return toret; +} diff --git a/0.77_My_PuTTY/windows/winmiscs.c b/0.77_My_PuTTY/windows/winmiscs.c new file mode 100644 index 0000000..3f06970 --- /dev/null +++ b/0.77_My_PuTTY/windows/winmiscs.c @@ -0,0 +1,285 @@ +/* + * winmiscs.c: Windows-specific standalone functions. Has the same + * relationship to winmisc.c that utils.c does to misc.c, but the + * corresponding name 'winutils.c' was already taken. + */ + +#include "putty.h" + +#ifndef NO_SECUREZEROMEMORY +/* + * Windows implementation of smemclr (see misc.c) using SecureZeroMemory. + */ +void smemclr(void *b, size_t n) { + if (b && n > 0) + SecureZeroMemory(b, n); +} +#endif + +#ifdef MINEFIELD +/* + * Minefield - a Windows equivalent for Electric Fence + */ + +#define PAGESIZE 4096 + +/* + * Design: + * + * We start by reserving as much virtual address space as Windows + * will sensibly (or not sensibly) let us have. We flag it all as + * invalid memory. + * + * Any allocation attempt is satisfied by committing one or more + * pages, with an uncommitted page on either side. The returned + * memory region is jammed up against the _end_ of the pages. + * + * Freeing anything causes instantaneous decommitment of the pages + * involved, so stale pointers are caught as soon as possible. + */ + +static int minefield_initialised = 0; +static void *minefield_region = NULL; +static long minefield_size = 0; +static long minefield_npages = 0; +static long minefield_curpos = 0; +static unsigned short *minefield_admin = NULL; +static void *minefield_pages = NULL; + +static void minefield_admin_hide(int hide) +{ + int access = hide ? PAGE_NOACCESS : PAGE_READWRITE; + VirtualProtect(minefield_admin, minefield_npages * 2, access, NULL); +} + +static void minefield_init(void) +{ + int size; + int admin_size; + int i; + + for (size = 0x40000000; size > 0; size = ((size >> 3) * 7) & ~0xFFF) { + minefield_region = VirtualAlloc(NULL, size, + MEM_RESERVE, PAGE_NOACCESS); + if (minefield_region) + break; + } + minefield_size = size; + + /* + * Firstly, allocate a section of that to be the admin block. + * We'll need a two-byte field for each page. + */ + minefield_admin = minefield_region; + minefield_npages = minefield_size / PAGESIZE; + admin_size = (minefield_npages * 2 + PAGESIZE - 1) & ~(PAGESIZE - 1); + minefield_npages = (minefield_size - admin_size) / PAGESIZE; + minefield_pages = (char *) minefield_region + admin_size; + + /* + * Commit the admin region. + */ + VirtualAlloc(minefield_admin, minefield_npages * 2, + MEM_COMMIT, PAGE_READWRITE); + + /* + * Mark all pages as unused (0xFFFF). + */ + for (i = 0; i < minefield_npages; i++) + minefield_admin[i] = 0xFFFF; + + /* + * Hide the admin region. + */ + minefield_admin_hide(1); + + minefield_initialised = 1; +} + +static void minefield_bomb(void) +{ + div(1, *(int *) minefield_pages); +} + +static void *minefield_alloc(int size) +{ + int npages; + int pos, lim, region_end, region_start; + int start; + int i; + + npages = (size + PAGESIZE - 1) / PAGESIZE; + + minefield_admin_hide(0); + + /* + * Search from current position until we find a contiguous + * bunch of npages+2 unused pages. + */ + pos = minefield_curpos; + lim = minefield_npages; + while (1) { + /* Skip over used pages. */ + while (pos < lim && minefield_admin[pos] != 0xFFFF) + pos++; + /* Count unused pages. */ + start = pos; + while (pos < lim && pos - start < npages + 2 && + minefield_admin[pos] == 0xFFFF) + pos++; + if (pos - start == npages + 2) + break; + /* If we've reached the limit, reset the limit or stop. */ + if (pos >= lim) { + if (lim == minefield_npages) { + /* go round and start again at zero */ + lim = minefield_curpos; + pos = 0; + } else { + minefield_admin_hide(1); + return NULL; + } + } + } + + minefield_curpos = pos - 1; + + /* + * We have npages+2 unused pages starting at start. We leave + * the first and last of these alone and use the rest. + */ + region_end = (start + npages + 1) * PAGESIZE; + region_start = region_end - size; + /* FIXME: could align here if we wanted */ + + /* + * Update the admin region. + */ + for (i = start + 2; i < start + npages + 1; i++) + minefield_admin[i] = 0xFFFE; /* used but no region starts here */ + minefield_admin[start + 1] = region_start % PAGESIZE; + + minefield_admin_hide(1); + + VirtualAlloc((char *) minefield_pages + region_start, size, + MEM_COMMIT, PAGE_READWRITE); + return (char *) minefield_pages + region_start; +} + +static void minefield_free(void *ptr) +{ + int region_start, i, j; + + minefield_admin_hide(0); + + region_start = (char *) ptr - (char *) minefield_pages; + i = region_start / PAGESIZE; + if (i < 0 || i >= minefield_npages || + minefield_admin[i] != region_start % PAGESIZE) + minefield_bomb(); + for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++) { + minefield_admin[j] = 0xFFFF; + } + + VirtualFree(ptr, j * PAGESIZE - region_start, MEM_DECOMMIT); + + minefield_admin_hide(1); +} + +static int minefield_get_size(void *ptr) +{ + int region_start, i, j; + + minefield_admin_hide(0); + + region_start = (char *) ptr - (char *) minefield_pages; + i = region_start / PAGESIZE; + if (i < 0 || i >= minefield_npages || + minefield_admin[i] != region_start % PAGESIZE) + minefield_bomb(); + for (j = i; j < minefield_npages && minefield_admin[j] != 0xFFFF; j++); + + minefield_admin_hide(1); + + return j * PAGESIZE - region_start; +} + +void *minefield_c_malloc(size_t size) +{ + if (!minefield_initialised) + minefield_init(); + return minefield_alloc(size); +} + +void minefield_c_free(void *p) +{ + if (!minefield_initialised) + minefield_init(); + minefield_free(p); +} + +/* + * realloc _always_ moves the chunk, for rapid detection of code + * that assumes it won't. + */ +void *minefield_c_realloc(void *p, size_t size) +{ + size_t oldsize; + void *q; + if (!minefield_initialised) + minefield_init(); + q = minefield_alloc(size); + oldsize = minefield_get_size(p); + memcpy(q, p, (oldsize < size ? oldsize : size)); + minefield_free(p); + return q; +} + +#endif /* MINEFIELD */ + +#if defined _MSC_VER && _MSC_VER < 1800 + +/* + * Work around lack of strtoumax in older MSVC libraries + */ +uintmax_t strtoumax(const char *nptr, char **endptr, int base) +{ + return _strtoui64(nptr, endptr, base); +} + +#endif + +#if defined _M_ARM || defined _M_ARM64 + +bool platform_aes_hw_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_sha256_hw_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_sha1_hw_available(void) +{ + return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE); +} + +bool platform_sha512_hw_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; +} + +#endif + +bool is_console_handle(HANDLE handle) +{ + DWORD ignored_output; + if (GetConsoleMode(handle, &ignored_output)) + return true; + return false; +} diff --git a/0.77_My_PuTTY/windows/winnet.c b/0.77_My_PuTTY/windows/winnet.c new file mode 100644 index 0000000..36780e1 --- /dev/null +++ b/0.77_My_PuTTY/windows/winnet.c @@ -0,0 +1,2106 @@ +/* + * 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 + +#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; +#ifdef MOD_ZMODEM + int closing; /* used when closing the connection, while we flush the remaining items */ +#endif + 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; +}; + +struct SockAddr { + int refcount; + char *error; + bool resolved; + bool namedpipe; /* indicates that this SockAddr is phony, holding a Windows + * named pipe pathname instead of a network address */ +#ifndef NO_IPV6 + struct addrinfo *ais; /* Addresses IPv6 style. */ +#endif + unsigned long *addresses; /* Addresses IPv4 style. */ + int naddresses; + char hostname[512]; /* Store an unresolved host name. */ +}; + +/* + * Which address family this address belongs to. AF_INET for IPv4; + * AF_INET6 for IPv6; AF_UNSPEC indicates that name resolution has + * not been done and a simple host name is held in this SockAddr + * structure. + */ +#ifndef NO_IPV6 +#define SOCKADDR_FAMILY(addr, step) \ + (!(addr)->resolved ? AF_UNSPEC : \ + (step).ai ? (step).ai->ai_family : AF_INET) +#else +#define SOCKADDR_FAMILY(addr, step) \ + (!(addr)->resolved ? AF_UNSPEC : AF_INET) +#endif + +/* + * Start a SockAddrStep structure to step through multiple + * addresses. + */ +#ifndef NO_IPV6 +#define START_STEP(addr, step) \ + ((step).ai = (addr)->ais, (step).curraddr = 0) +#else +#define START_STEP(addr, step) \ + ((step).curraddr = 0) +#endif + +static tree234 *sktree; + +#ifdef MOD_PERSO +#include "kitty.h" +int get_param( const char * val ) ; +#endif +#ifdef MOD_ZMODEM +static int curpass; +#endif + +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, u_long, ntohl, (u_long)); +DECL_WINDOWS_FUNCTION(static, u_long, htonl, (u_long)); +DECL_WINDOWS_FUNCTION(static, u_short, htons, (u_short)); +DECL_WINDOWS_FUNCTION(static, u_short, ntohs, (u_short)); +DECL_WINDOWS_FUNCTION(static, int, gethostname, (char *, int)); +DECL_WINDOWS_FUNCTION(static, struct hostent FAR *, gethostbyname, + (const char FAR *)); +DECL_WINDOWS_FUNCTION(static, struct servent FAR *, getservbyname, + (const char FAR *, const char FAR *)); +DECL_WINDOWS_FUNCTION(static, unsigned long, inet_addr, (const char FAR *)); +DECL_WINDOWS_FUNCTION(static, char FAR *, inet_ntoa, (struct in_addr)); +DECL_WINDOWS_FUNCTION(static, const char FAR *, inet_ntop, + (int, void FAR *, char *, size_t)); +DECL_WINDOWS_FUNCTION(static, int, connect, + (SOCKET, const struct sockaddr FAR *, int)); +DECL_WINDOWS_FUNCTION(static, int, bind, + (SOCKET, const struct sockaddr FAR *, int)); +DECL_WINDOWS_FUNCTION(static, int, setsockopt, + (SOCKET, int, int, const char FAR *, int)); +DECL_WINDOWS_FUNCTION(static, SOCKET, socket, (int, int, int)); +DECL_WINDOWS_FUNCTION(static, int, listen, (SOCKET, int)); +DECL_WINDOWS_FUNCTION(static, int, send, (SOCKET, const char FAR *, int, int)); +DECL_WINDOWS_FUNCTION(static, int, shutdown, (SOCKET, int)); +DECL_WINDOWS_FUNCTION(static, int, ioctlsocket, + (SOCKET, long, u_long FAR *)); +DECL_WINDOWS_FUNCTION(static, SOCKET, accept, + (SOCKET, struct sockaddr FAR *, int FAR *)); +DECL_WINDOWS_FUNCTION(static, int, getpeername, + (SOCKET, struct sockaddr FAR *, int FAR *)); +DECL_WINDOWS_FUNCTION(static, int, recv, (SOCKET, char FAR *, int, int)); +DECL_WINDOWS_FUNCTION(static, int, WSAIoctl, + (SOCKET, DWORD, LPVOID, DWORD, LPVOID, DWORD, + LPDWORD, LPWSAOVERLAPPED, + LPWSAOVERLAPPED_COMPLETION_ROUTINE)); +#ifndef NO_IPV6 +DECL_WINDOWS_FUNCTION(static, int, getaddrinfo, + (const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res)); +DECL_WINDOWS_FUNCTION(static, void, freeaddrinfo, (struct addrinfo *res)); +DECL_WINDOWS_FUNCTION(static, int, getnameinfo, + (const struct sockaddr FAR * sa, socklen_t salen, + char FAR * host, DWORD hostlen, char FAR * serv, + DWORD servlen, int flags)); +DECL_WINDOWS_FUNCTION(static, char *, gai_strerror, (int ecode)); +DECL_WINDOWS_FUNCTION(static, int, WSAAddressToStringA, + (LPSOCKADDR, DWORD, LPWSAPROTOCOL_INFO, + LPSTR, LPDWORD)); +#endif + +static HMODULE winsock_module = NULL; +static WSADATA wsadata; +#ifndef NO_IPV6 +static HMODULE winsock2_module = NULL; +static HMODULE wship6_module = NULL; +#endif + +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. */ + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror); + } 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); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(winsock_module, gai_strerror); + } 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); + GET_WINDOWS_FUNCTION(winsock_module, ntohl); + GET_WINDOWS_FUNCTION(winsock_module, htonl); + GET_WINDOWS_FUNCTION(winsock_module, htons); + GET_WINDOWS_FUNCTION(winsock_module, ntohs); + GET_WINDOWS_FUNCTION_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); +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + curpass = 1; + } +#endif +} + +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); +} + +SockAddr *sk_namelookup(const char *host, char **canonicalname, + int address_family) +{ + SockAddr *ret = snew(SockAddr); + unsigned long a; + char realhost[8192]; + int hint_family; + + /* Default to IPv4. */ + hint_family = (address_family == ADDRTYPE_IPV4 ? AF_INET : +#ifndef NO_IPV6 + address_family == ADDRTYPE_IPV6 ? AF_INET6 : +#endif + AF_UNSPEC); + + /* Clear the structure and default to IPv4. */ + memset(ret, 0, sizeof(SockAddr)); +#ifndef NO_IPV6 + ret->ais = NULL; +#endif + ret->namedpipe = false; + ret->addresses = NULL; + ret->resolved = false; + ret->refcount = 1; + *realhost = '\0'; + + if ((a = p_inet_addr(host)) == (unsigned long) INADDR_NONE) { + struct hostent *h = NULL; + int err = 0; +#ifndef NO_IPV6 + /* + * Use getaddrinfo when it's available + */ + if (p_getaddrinfo) { + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = hint_family; + hints.ai_flags = AI_CANONNAME; + { + /* strip [] on IPv6 address literals */ + char *trimmed_host = host_strduptrim(host); + err = p_getaddrinfo(trimmed_host, NULL, &hints, &ret->ais); + sfree(trimmed_host); + } + if (err == 0) + ret->resolved = true; + } else +#endif + { + /* + * Otherwise use the IPv4-only gethostbyname... + * (NOTE: we don't use gethostbyname as a fallback!) + */ + if ( (h = p_gethostbyname(host)) ) + ret->resolved = true; + else + err = p_WSAGetLastError(); + } + + if (!ret->resolved) { + ret->error = (err == WSAENETDOWN ? "Network is down" : + err == WSAHOST_NOT_FOUND ? "Host does not exist" : + err == WSATRY_AGAIN ? "Host not found" : +#ifndef NO_IPV6 + p_getaddrinfo&&p_gai_strerror ? p_gai_strerror(err) : +#endif + "gethostbyname: unknown error"); + } else { + ret->error = NULL; + +#ifndef NO_IPV6 + /* If we got an address info use that... */ + if (ret->ais) { + /* Are we in IPv4 fallback mode? */ + /* We put the IPv4 address into the a variable so we can further-on use the IPv4 code... */ + if (ret->ais->ai_family == AF_INET) + memcpy(&a, + (char *) &((SOCKADDR_IN *) ret->ais-> + ai_addr)->sin_addr, sizeof(a)); + + if (ret->ais->ai_canonname) + strncpy(realhost, ret->ais->ai_canonname, lenof(realhost)); + else + strncpy(realhost, host, lenof(realhost)); + } + /* We used the IPv4-only gethostbyname()... */ + else +#endif + { + int n; + for (n = 0; h->h_addr_list[n]; n++); + ret->addresses = snewn(n, unsigned long); + ret->naddresses = n; + for (n = 0; n < ret->naddresses; n++) { + memcpy(&a, h->h_addr_list[n], sizeof(a)); + ret->addresses[n] = p_ntohl(a); + } + memcpy(&a, h->h_addr, sizeof(a)); + /* This way we are always sure the h->h_name is valid :) */ + strncpy(realhost, h->h_name, sizeof(realhost)); + } + } + } else { + /* + * This must be a numeric IPv4 address because it caused a + * success return from inet_addr. + */ + ret->addresses = snewn(1, unsigned long); + ret->naddresses = 1; + ret->addresses[0] = p_ntohl(a); + ret->resolved = true; + strncpy(realhost, host, sizeof(realhost)); + } + realhost[lenof(realhost)-1] = '\0'; + *canonicalname = dupstr(realhost); + return ret; +} + +SockAddr *sk_nonamelookup(const char *host) +{ + SockAddr *ret = snew(SockAddr); + ret->error = NULL; + ret->resolved = false; +#ifndef NO_IPV6 + ret->ais = NULL; +#endif + ret->namedpipe = false; + ret->addresses = NULL; + ret->naddresses = 0; + ret->refcount = 1; + strncpy(ret->hostname, host, lenof(ret->hostname)); + ret->hostname[lenof(ret->hostname)-1] = '\0'; + return ret; +} + +SockAddr *sk_namedpipe_addr(const char *pipename) +{ + SockAddr *ret = snew(SockAddr); + ret->error = NULL; + ret->resolved = false; +#ifndef NO_IPV6 + ret->ais = NULL; +#endif + ret->namedpipe = true; + ret->addresses = NULL; + ret->naddresses = 0; + ret->refcount = 1; + strncpy(ret->hostname, pipename, lenof(ret->hostname)); + ret->hostname[lenof(ret->hostname)-1] = '\0'; + return ret; +} + +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->namedpipe; +} + +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, +}; + +#ifdef MOD_PERSO +Socket sk_register(void *sock, Plug *plug) +{ + DWORD err; + const char *errstr; + NetSocket *ret; + + /* + * Create Socket structure. + */ + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + ret->closing = 0; + } +#endif + ret->writable = 1; /* to start with */ + ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; + ret->frozen = 1; + ret->frozen_readable = 0; + ret->localhost_only = 0; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->addr = NULL; + + ret->s = (SOCKET)sock; + + if (ret->s == INVALID_SOCKET) { + err = p_WSAGetLastError(); + ret->error = winsock_error_string(err); + return ret->sock; + } + + ret->oobinline = 0; + + /* Set up a select mechanism. This could be an AsyncSelect on a + * window, or an EventSelect on an event object. */ + errstr = do_select(ret->s, 1); + if (errstr) { + ret->error = errstr; + return ret->sock; + } + + add234(sktree, ret); + + return ret->sock; +} +#endif + +#ifdef MOD_PERSO +Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug) +#else +static Socket *sk_net_accept(accept_ctx_t ctx, Plug *plug) +#endif +{ + DWORD err; + char *errstr; + NetSocket *ret; + + /* + * Create NetSocket structure. + */ + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + ret->closing = 0; + } +#endif + ret->writable = true; /* to start with */ + ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; + ret->frozen = true; + ret->frozen_readable = false; + ret->localhost_only = false; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->addr = NULL; + + ret->s = (SOCKET)ctx.p; + + if (ret->s == INVALID_SOCKET) { + err = p_WSAGetLastError(); + ret->error = winsock_error_string(err); + return &ret->sock; + } + + ret->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(ret->s, true); + if (errstr) { + ret->error = errstr; + return &ret->sock; + } + + add234(sktree, ret); + + return &ret->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); + } +#ifdef MOD_ZMODEM + if(!GetZModemFlag() || !sock->closing) { + SockAddr thisaddr = sk_extractaddr_tmp( + sock->addr, &sock->step); + plug_log(sock->plug, 0, &thisaddr, sock->port, NULL, 0); + } +#else + { + SockAddr thisaddr = sk_extractaddr_tmp( + sock->addr, &sock->step); + plug_log(sock->plug, PLUGLOG_CONNECT_TRYING, + &thisaddr, sock->port, NULL, 0); + } +#endif + /* + * 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); +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + if( err && !sock->closing) { + SockAddr thisaddr = sk_extractaddr_tmp( + sock->addr, &sock->step); + plug_log(sock->plug, PLUGLOG_CONNECT_FAILED, + &thisaddr, sock->port, sock->error, err); + } + } + else + if (err) { + SockAddr thisaddr = sk_extractaddr_tmp( + sock->addr, &sock->step); + plug_log(sock->plug, PLUGLOG_CONNECT_FAILED, + &thisaddr, sock->port, sock->error, err); + } +#else + if (err) { + SockAddr thisaddr = sk_extractaddr_tmp( + sock->addr, &sock->step); + plug_log(sock->plug, PLUGLOG_CONNECT_FAILED, + &thisaddr, sock->port, sock->error, err); + } +#endif + + return err; +} + +Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline, + bool nodelay, bool keepalive, Plug *plug) +{ + NetSocket *ret; + DWORD err; + + /* + * Create NetSocket structure. + */ + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); + ret->connected = false; /* to start with */ +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + ret->closing = 0; + } +#endif + ret->writable = false; /* to start with */ + ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; + ret->frozen = false; + ret->frozen_readable = false; + ret->localhost_only = false; /* unused, but best init anyway */ + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->oobinline = oobinline; + ret->nodelay = nodelay; + ret->keepalive = keepalive; + ret->privport = privport; + ret->port = port; + ret->addr = addr; + START_STEP(ret->addr, ret->step); + ret->s = INVALID_SOCKET; + + err = 0; + do { + err = try_connect(ret); + } while (err && sk_nextaddr(ret->addr, &ret->step)); + + return &ret->sock; +} + +Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug, + bool local_host_only, int orig_address_family) +{ + SOCKET s; +#ifndef NO_IPV6 + SOCKADDR_IN6 a6; +#endif + SOCKADDR_IN a; + + DWORD err; + const char *errstr; + NetSocket *ret; + int retcode; + + int address_family; + + /* + * Create NetSocket structure. + */ + ret = snew(NetSocket); + ret->sock.vt = &NetSocket_sockvt; + ret->error = NULL; + ret->plug = plug; + bufchain_init(&ret->output_data); +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + ret->closing = 0; + } +#endif + ret->writable = false; /* to start with */ + ret->sending_oob = 0; + ret->outgoingeof = EOF_NO; + ret->frozen = false; + ret->frozen_readable = false; + ret->localhost_only = local_host_only; + ret->pending_error = 0; + ret->parent = ret->child = NULL; + ret->addr = NULL; + + /* + * Translate address_family from platform-independent constants + * into local reality. + */ + address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET : +#ifndef NO_IPV6 + orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 : +#endif + AF_UNSPEC); + + /* + * Our default, if passed the `don't care' value + * ADDRTYPE_UNSPEC, is to listen on IPv4. If IPv6 is supported, + * we will also set up a second socket listening on IPv6, but + * the v4 one is primary since that ought to work even on + * non-v6-supporting systems. + */ + if (address_family == AF_UNSPEC) address_family = AF_INET; + + /* + * Open socket. + */ + s = p_socket(address_family, SOCK_STREAM, 0); + ret->s = s; + + if (s == INVALID_SOCKET) { + err = p_WSAGetLastError(); + ret->error = winsock_error_string(err); + return &ret->sock; + } + + SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0); + + ret->oobinline = false; + + { + BOOL on = true; + p_setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + (const char *)&on, sizeof(on)); + } + +#ifndef NO_IPV6 + if (address_family == AF_INET6) { + memset(&a6, 0, sizeof(a6)); + a6.sin6_family = AF_INET6; + if (local_host_only) + a6.sin6_addr = in6addr_loopback; + else + a6.sin6_addr = in6addr_any; + if (srcaddr != NULL && p_getaddrinfo) { + struct addrinfo hints; + struct addrinfo *ai; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_flags = 0; + { + /* strip [] on IPv6 address literals */ + char *trimmed_addr = host_strduptrim(srcaddr); + err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai); + sfree(trimmed_addr); + } + if (err == 0 && ai->ai_family == AF_INET6) { + a6.sin6_addr = + ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; + } + } + a6.sin6_port = p_htons(port); + } else +#endif + { + 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. */ + ret->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); + } +#ifndef NO_IPV6 + retcode = p_bind(s, (address_family == AF_INET6 ? + (struct sockaddr *) &a6 : + (struct sockaddr *) &a), + (address_family == + AF_INET6 ? sizeof(a6) : sizeof(a))); +#else + retcode = p_bind(s, (struct sockaddr *) &a, sizeof(a)); +#endif + if (retcode != SOCKET_ERROR) { + err = 0; + } else { + err = p_WSAGetLastError(); + } + + if (err) { + p_closesocket(s); + ret->error = winsock_error_string(err); + return &ret->sock; + } + + + if (p_listen(s, SOMAXCONN) == SOCKET_ERROR) { + p_closesocket(s); + ret->error = winsock_error_string(p_WSAGetLastError()); + return &ret->sock; + } + + /* 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) { + p_closesocket(s); + ret->error = errstr; + return &ret->sock; + } + + add234(sktree, ret); + +#ifndef NO_IPV6 + /* + * If we were given ADDRTYPE_UNSPEC, we must also create an + * IPv6 listening socket and link it to this one. + */ + if (address_family == AF_INET && orig_address_family == ADDRTYPE_UNSPEC) { + Socket *other = sk_newlistener(srcaddr, port, plug, + local_host_only, ADDRTYPE_IPV6); + + if (other) { + NetSocket *ns = container_of(other, NetSocket, sock); + if (!ns->error) { + ns->parent = ret; + ret->child = ns; + } else { + sfree(ns); + } + } + } +#endif + + return &ret->sock; +} + +#ifdef NO_MOD_ZMODEM /* A partir de 0.64.0.3 on prend le code normal */ +static void sk_tcp_really_close(Actual_Socket s) +{ + extern char *do_select(SOCKET skt, int startup); + + if (s->child) { + sk_tcp_close((Socket)s->child); + s->child = NULL; + } + + del234(sktree, s); + do_select(s->s, 0); + p_closesocket(s->s); + if (s->addr) + sk_addr_free(s->addr); + sfree(s); +} + +static void sk_tcp_close(Socket sock) +{ + Actual_Socket s = (Actual_Socket) sock; + + if (s->pending_error != 0) { + sk_tcp_really_close(s); + return; + } + + if (s->child) { + sk_tcp_close((Socket)s->child); + s->child = NULL; + } + + s->closing = curpass; +} +static void sk_net_close(Socket *sock) { return sk_tcp_close( *sock ) ; } +#else + +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); +} +#endif + +/* + * 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(s->plug, winsock_error_string(s->pending_error), + s->pending_error, 0); +} + +/* + * The function which tries to send on a socket once it's deemed + * writable. + */ +void try_send(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) { +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + if (!s->closing) { + SockAddr thisaddr = sk_extractaddr_tmp( + s->addr, &s->step); + plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port, + winsock_error_string(err), err); + } + } + else { + SockAddr thisaddr = sk_extractaddr_tmp( + s->addr, &s->step); + plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port, + winsock_error_string(err), err); + } +#else + SockAddr thisaddr = sk_extractaddr_tmp( + s->addr, &s->step); + plug_log(s->plug, PLUGLOG_CONNECT_FAILED, &thisaddr, s->port, + winsock_error_string(err), err); +#endif + while (err && s->addr && sk_nextaddr(s->addr, &s->step)) { + err = try_connect(s); + } + } +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + if (err != 0 && !s->closing) + plug_closing(s->plug, winsock_error_string(err), err, 0); + return; + } + else { + if (err != 0) + plug_closing(s->plug, winsock_error_string(err), err, 0); + return; + } +#else + if (err != 0) + plug_closing(s->plug, winsock_error_string(err), err, 0); + return; +#endif + } + + 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; + } + } +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() &&0 ) { + if( !s->closing ) { + if (ret < 0) { + plug_closing(s->plug, winsock_error_string(err), err, 0); + } else if (0 == ret) { + plug_closing(s->plug, NULL, 0, 0); + } else { + plug_receive(s->plug, atmark ? 0 : 1, buf, ret); + } + } + } + else +#endif + if (ret < 0) { + plug_closing(s->plug, winsock_error_string(err), err, 0); + } else if (0 == ret) { + plug_closing(s->plug, NULL, 0, 0); + } 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(s->plug, winsock_error_string(err), err, 0); +#ifdef MOD_ZMODEM + } else if( (!GetPuttyFlag()) && GetZModemFlag() && s->closing) { + return ; +#endif + } 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); +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + if (bufsize_after < bufsize_before && !s->closing) + plug_sent(s->plug, bufsize_after); + } + else +#endif + 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; +#ifdef MOD_ZMODEM + if( (!GetPuttyFlag()) && GetZModemFlag() && s->closing ) + return ; +#endif + plug_closing(s->plug, winsock_error_string(err), err, 0); +#ifdef MOD_ZMODEM + } else if( (!GetPuttyFlag()) && GetZModemFlag() && s->closing) { +#endif + } else { + if (ret) + plug_receive(s->plug, 0, buf, ret); + else + plug_closing(s->plug, NULL, 0, 0); + } + } while (ret > 0); + return; + case FD_ACCEPT: { +#ifdef NO_IPV6 + struct sockaddr_in isa; +#else + struct sockaddr_storage isa; +#endif + int addrlen = sizeof(isa); + SOCKET t; /* socket of connection */ + accept_ctx_t actx; + + memset(&isa, 0, sizeof(isa)); + err = 0; + t = p_accept(s->s,(struct sockaddr *)&isa,&addrlen); + if (t == INVALID_SOCKET) + { + err = p_WSAGetLastError(); + if (err == WSATRY_AGAIN) + break; + } + + actx.p = (void *)t; + +#ifndef NO_IPV6 + if (isa.ss_family == AF_INET && + s->localhost_only && + !ipv4_is_local_addr(((struct sockaddr_in *)&isa)->sin_addr)) +#else + if (s->localhost_only && !ipv4_is_local_addr(isa.sin_addr)) +#endif + { + p_closesocket(t); /* dodgy WinSock let nonlocal through */ +#ifdef MOD_ZMODEM + } + if( (!GetPuttyFlag()) && GetZModemFlag() ) { + if (!s->closing && plug_accepting(s->plug, sk_net_accept, actx)) { + p_closesocket(t); /* denied or error */ + } + } + else { + if (plug_accepting(s->plug, sk_net_accept, actx)) { + p_closesocket(t); /* denied or error */ + } + } +#else + } else if (plug_accepting(s->plug, sk_net_accept, actx)) { + p_closesocket(t); /* denied or error */ + } +#endif + break; + } + } +} + +#if (defined MOD_ZMODEM) +struct netscheduler_tag { + int interval; + int pending; + long next; +}; + +static void netscheduler_schedule(struct netscheduler_tag *netscheduler); + +static void netscheduler_timer(void *ctx, long now) +{ + struct netscheduler_tag *netscheduler = (struct netscheduler_tag *)ctx; + + if (netscheduler->pending && now - netscheduler->next >= 0) { + // net_pending_errors(); /* fonction supprimee en 0.64 */ + netscheduler->pending = FALSE; + netscheduler_schedule(netscheduler); + } +} + +static void netscheduler_schedule(struct netscheduler_tag *netscheduler) +{ + int next; + + if (!netscheduler->interval) { + netscheduler->pending = FALSE; /* cancel any pending ping */ + return; + } + + next = schedule_timer(netscheduler->interval * TICKSPERSEC, + (void *) netscheduler_timer, netscheduler); + if (!netscheduler->pending || next < netscheduler->next) { + netscheduler->next = next; + netscheduler->pending = TRUE; + } +} + +struct netscheduler_tag* netscheduler_new() +{ + struct netscheduler_tag* netscheduler = snew(struct netscheduler_tag); + + netscheduler->interval = 1; + netscheduler->pending = FALSE; + netscheduler_schedule(netscheduler); + + return netscheduler; +} + +void netscheduler_free(struct netscheduler_tag* netscheduler) +{ + expire_timer_context(netscheduler); + sfree(netscheduler); +} +#endif + +/* + * Special error values are returned from sk_namelookup and sk_new + * if there's a problem. These functions extract an error message, + * or return NULL if there's no problem. + */ +const char *sk_addr_error(SockAddr *addr) +{ + 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; + 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(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 *ret = snew(SockAddr); + memset(ret, 0, sizeof(SockAddr)); + ret->error = "unix sockets not supported on this platform"; + ret->refcount = 1; + return ret; +} diff --git a/0.77_My_PuTTY/windows/winnohlp.c b/0.77_My_PuTTY/windows/winnohlp.c new file mode 100644 index 0000000..62ddc65 --- /dev/null +++ b/0.77_My_PuTTY/windows/winnohlp.c @@ -0,0 +1,15 @@ +/* + * nohelp.c: implement the has_embedded_chm() function for + * applications that have no help file at all, so that misc.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/0.77_My_PuTTY/windows/winnoise.c b/0.77_My_PuTTY/windows/winnoise.c new file mode 100644 index 0000000..842a69b --- /dev/null +++ b/0.77_My_PuTTY/windows/winnoise.c @@ -0,0 +1,142 @@ +/* + * 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/0.77_My_PuTTY/windows/winnojmp.c b/0.77_My_PuTTY/windows/winnojmp.c new file mode 100644 index 0000000..dd61dc6 --- /dev/null +++ b/0.77_My_PuTTY/windows/winnojmp.c @@ -0,0 +1,8 @@ +/* + * winnojmp.c: stub jump list functions for Windows executables that + * don't update the jump list. + */ + +void add_session_to_jumplist(const char * const sessionname) {} +void remove_session_from_jumplist(const char * const sessionname) {} +void clear_jumplist(void) {} diff --git a/0.77_My_PuTTY/windows/winnpc.c b/0.77_My_PuTTY/windows/winnpc.c new file mode 100644 index 0000000..35d345d --- /dev/null +++ b/0.77_My_PuTTY/windows/winnpc.c @@ -0,0 +1,98 @@ +/* + * Windows support module which deals with being a named-pipe client. + */ + +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#if !defined NO_SECURITY + +#include "winsecur.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 winnps.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, plug, true); +} + +#endif /* !defined NO_SECURITY */ diff --git a/0.77_My_PuTTY/windows/winnps.c b/0.77_My_PuTTY/windows/winnps.c new file mode 100644 index 0000000..6cdf43a --- /dev/null +++ b/0.77_My_PuTTY/windows/winnps.c @@ -0,0 +1,240 @@ +/* + * Windows support module which deals with being a named-pipe server. + */ + +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#if !defined NO_SECURITY + +#include "winsecur.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; + struct handle *callback_handle; /* winhandl.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) + handle_free(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, 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 *ret = snew(NamedPipeServerSocket); + ret->sock.vt = &NamedPipeServerSocket_sockvt; + ret->plug = plug; + ret->error = NULL; + ret->psd = NULL; + ret->pipename = dupstr(pipename); + ret->acl = NULL; + ret->callback_handle = NULL; + + assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); + assert(strchr(pipename + 9, '\\') == NULL); + + if (!make_private_security_descriptor(GENERIC_READ | GENERIC_WRITE, + &ret->psd, &ret->acl, &ret->error)) { + goto cleanup; + } + + if (!create_named_pipe(ret, true)) { + ret->error = dupprintf("unable to create named pipe '%s': %s", + pipename, win_strerror(GetLastError())); + goto cleanup; + } + + memset(&ret->connect_ovl, 0, sizeof(ret->connect_ovl)); + ret->connect_ovl.hEvent = CreateEvent(NULL, true, false, NULL); + ret->callback_handle = + handle_add_foreign_event(ret->connect_ovl.hEvent, + named_pipe_connect_callback, ret); + named_pipe_accept_loop(ret, false); + + cleanup: + return &ret->sock; +} + +#endif /* !defined NO_SECURITY */ diff --git a/0.77_My_PuTTY/windows/winpgen.c b/0.77_My_PuTTY/windows/winpgen.c new file mode 100644 index 0000000..2b39566 --- /dev/null +++ b/0.77_My_PuTTY/windows/winpgen.c @@ -0,0 +1,2054 @@ +/* + * PuTTY key generation front end (Windows). + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshkeygen.h" +#include "licence.h" +#include "winsecur.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; + +#ifdef MOD_PERSO +Conf *conf; +#endif +#ifndef MOD_INTEGRATED_KEYGEN +/* + * 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); +} +#endif + +/* ---------------------------------------------------------------------- + * 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); +#ifdef MOD_INTEGRATED_KEYGEN + DialogBox(hinst, MAKEINTRESOURCE(814), hwnd, LicenceProc); +#else + DialogBox(hinst, MAKEINTRESOURCE(214), hwnd, LicenceProc); +#endif + 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 dss_key *dsskey; + 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->dsskey, 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 MainDlgState { + bool collecting_entropy; + bool generation_thread_exists; + bool key_exists; + int entropy_got, entropy_required, entropy_size; + 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; + unsigned *entropy; + union { + RSAKey key; + struct dss_key dsskey; + struct ecdsa_key eckey; + struct eddsa_key edkey; + }; + HMENU filemenu, keymenu, cvtmenu; +}; + +static void hidemany(HWND hwnd, const int *ids, bool hideit) +{ + while (*ids) { + ShowWindow(GetDlgItem(hwnd, *ids++), (hideit ? SW_HIDE : SW_SHOW)); + } +} + +static void setupbigedit1(HWND hwnd, int id, int idstatic, RSAKey *key) +{ + char *buffer = ssh1_pubkey_str(key); + SetDlgItemText(hwnd, id, buffer); + SetDlgItemText(hwnd, idstatic, + "&Public key for pasting into authorized_keys file:"); + sfree(buffer); +} + +static void setupbigedit2(HWND hwnd, int id, int idstatic, + ssh2_userkey *key) +{ + char *buffer = ssh2_pubkey_openssh_str(key); + SetDlgItemText(hwnd, id, buffer); + SetDlgItemText(hwnd, idstatic, "&Public key for pasting into " + "OpenSSH authorized_keys file:"); + sfree(buffer); +} + +#ifndef MOD_INTEGRATED_KEYGEN +/* + * 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); +} +#endif + +enum { + controlidstart = 100, + IDC_QUIT, + IDC_TITLE, + IDC_BOX_KEY, + IDC_NOKEY, + IDC_GENERATING, + IDC_PROGRESS, + IDC_PKSTATIC, IDC_KEYDISPLAY, + 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 +}; + +static const int nokey_ids[] = { IDC_NOKEY, 0 }; +static const int generating_ids[] = { IDC_GENERATING, IDC_PROGRESS, 0 }; +static const int gotkey_ids[] = { + IDC_PKSTATIC, IDC_KEYDISPLAY, + IDC_FPSTATIC, IDC_FINGERPRINT, + IDC_COMMENTSTATIC, IDC_COMMENTEDIT, + IDC_PASSPHRASE1STATIC, IDC_PASSPHRASE1EDIT, + IDC_PASSPHRASE2STATIC, IDC_PASSPHRASE2EDIT, 0 +}; + +/* + * 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, 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); + break; + case 1: /* generating key */ + hidemany(hwnd, nokey_ids, true); + hidemany(hwnd, generating_ids, false); + hidemany(hwnd, gotkey_ids, 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); + break; + case 2: + hidemany(hwnd, nokey_ids, true); + hidemany(hwnd, generating_ids, true); + hidemany(hwnd, gotkey_ids, false); + 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 + 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); + } +} + +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, + 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, +#ifdef MOD_INTEGRATED_KEYGEN + MAKEINTRESOURCE(810), +#else + MAKEINTRESOURCE(210), +#endif + NULL, PassphraseProc, + (LPARAM) &pps); + if (!dlgret) { + ret = -2; + break; + } +#ifndef MOD_PERSO + assert(passphrase != NULL); +#endif + } 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, + HELPCTXID(errors_cantloadkey)); + sfree(msg); + } else if (ret == 1) { + /* + * Now update the key controls with all the + * key data. + */ + { + 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; + + /* + * 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, IDC_KEYDISPLAY, + IDC_PKSTATIC, &state->key); + } else { + char *fp; + char *savecomment; + + state->ssh2 = true; + state->commentptr = + &state->ssh2key.comment; + state->ssh2key = *newkey2; /* structure copy */ + sfree(newkey2); + + savecomment = state->ssh2key.comment; + state->ssh2key.comment = NULL; + fp = ssh2_fingerprint(state->ssh2key.key, state->fptype); + state->ssh2key.comment = savecomment; + + SetDlgItemText(hwnd, IDC_FINGERPRINT, fp); + sfree(fp); + + setupbigedit2(hwnd, IDC_KEYDISPLAY, + IDC_PKSTATIC, &state->ssh2key); + } + 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; + + /* + * 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); +} + +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->dsskey = &state->dsskey; + + 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 for the main PuTTYgen dialog box. + */ +static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + 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. + */ + } +#ifdef MOD_INTEGRATED_KEYGEN + SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG, + (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(800))); +#else + SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG, + (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(200))); +#endif + + state = snew(struct MainDlgState); + state->generation_thread_exists = false; + state->collecting_entropy = 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_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); + 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); + } + ui_set_key_type(hwnd, state, IDC_KEYSSH2RSA); + ui_set_primepolicy(hwnd, state, IDC_PRIMEGEN_PROB); + ui_set_rsa_strong(hwnd, state, false); + ui_set_fptype(hwnd, state, fptype_to_idc(SSH_FPTYPE_DEFAULT)); + SetDlgItemInt(hwnd, IDC_BITS, DEFAULT_KEY_BITS, false); + SendDlgItemMessage(hwnd, IDC_ECCURVE, CB_SETCURSEL, + DEFAULT_ECCURVE_INDEX, 0); + SendDlgItemMessage(hwnd, IDC_EDCURVE, CB_SETCURSEL, + DEFAULT_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); + } + + return 1; + case WM_MOUSEMOVE: + state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); + if (state->collecting_entropy && + state->entropy && state->entropy_got < state->entropy_required) { + state->entropy[state->entropy_got++] = lParam; + state->entropy[state->entropy_got++] = GetMessageTime(); + SendDlgItemMessage(hwnd, IDC_PROGRESS, PBM_SETPOS, + state->entropy_got, 0); + if (state->entropy_got >= state->entropy_required) { + /* + * Seed the entropy pool + */ + random_reseed( + make_ptrlen(state->entropy, state->entropy_size)); + smemclr(state->entropy, state->entropy_size); + sfree(state->entropy); + state->collecting_entropy = false; + + 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, IDC_KEYDISPLAY, IDC_PKSTATIC, + &state->ssh2key); + } else { + setupbigedit1(hwnd, IDC_KEYDISPLAY, IDC_PKSTATIC, + &state->key); + } + } + } + break; + case IDC_ABOUT: + EnableWindow(hwnd, 0); +#ifdef MOD_INTEGRATED_KEYGEN + DialogBox(hinst, MAKEINTRESOURCE(813), hwnd, AboutProc); +#else + DialogBox(hinst, MAKEINTRESOURCE(213), hwnd, AboutProc); +#endif + 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->collecting_entropy = true; + + state->entropy_got = 0; + state->entropy_size = (state->entropy_required * + sizeof(unsigned)); + state->entropy = snewn(state->entropy_required, unsigned); + + 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; + } + 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->dsskey.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, IDC_KEYDISPLAY, + IDC_PKSTATIC, &state->ssh2key); + } else { + setupbigedit1(hwnd, IDC_KEYDISPLAY, + IDC_PKSTATIC, &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; +} + +#ifndef MOD_INTEGRATED_KEYGEN +void cleanup_exit(int code) +{ + shutdown_help(); + exit(code); +} + +HINSTANCE hinst; + +int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) +#else +int WINAPI KeyGen_WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) +#endif +{ + int argc, i; + char **argv; + int ret; + + dll_hijacking_protection(); + + init_common_controls(); + hinst = inst; + + /* + * See if we can find our Help file. + */ + init_help(); + + split_into_argv(cmdline, &argc, &argv, NULL); + + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "-pgpfp")) { + pgp_fingerprints_msgbox(NULL); + return 1; + } else if (!strcmp(argv[i], "-restrict-acl") || + !strcmp(argv[i], "-restrict_acl") || + !strcmp(argv[i], "-restrictacl")) { + restrict_process_acl(); + } else { + /* + * Assume the first argument to be a private key file, and + * attempt to load it. + */ + cmdline_keyfile = argv[i]; + break; + } + } + + save_params = ppk_save_default_parameters; + + random_setup_special(); + + ret = DialogBox(hinst, MAKEINTRESOURCE(201), NULL, MainDlgProc) != IDOK; + + cleanup_exit(ret); + return ret; /* just in case optimiser complains */ +} diff --git a/0.77_My_PuTTY/windows/winpgnt.c b/0.77_My_PuTTY/windows/winpgnt.c new file mode 100644 index 0000000..baf092a --- /dev/null +++ b/0.77_My_PuTTY/windows/winpgnt.c @@ -0,0 +1,1893 @@ +/* + * Pageant: the PuTTY Authentication Agent. + */ + +#include +#include +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "misc.h" +#include "tree234.h" +#include "winsecur.h" +#include "wincapi.h" +#include "pageant.h" +#include "licence.h" +#include "pageant-rc.h" + +#include + +#ifdef MOD_PERSO +void scrumble_int(); +// Flag pour le fonctionnement en mode "portable" (gestion par fichiers) +extern int IniFileFlag ; + +// Flag permettant la gestion de l'arborscence (dossier=folder) dans le cas d'un savemode=dir +extern int DirectoryBrowseFlag ; + +#include "../../kitty_crypt.h" +#include "../../kitty_commun.h" +#include "../../kitty_store.h" +#include "../storage.h" + +static char pphrase[2048]=""; +Conf *conf; +#endif + +#ifndef NO_SECURITY +#include +#ifdef DEBUG_IPC +#define _WIN32_WINNT 0x0500 /* for ConvertSidToStringSid */ +#include +#endif +#endif + +#define WM_SYSTRAY (WM_APP + 6) +#define WM_SYSTRAY2 (WM_APP + 7) + +#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ + +#ifdef MOD_PERSO +#define APPNAME "Pageant" +#endif + +/* 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 +#ifdef MOD_PERSO +#define PUTTY_REGKEY "Software\\9bis.com\\KiTTY\\Sessions" +#else +#define PUTTY_REGKEY "Software\\SimonTatham\\PuTTY\\Sessions" +#endif + +#define PUTTY_DEFAULT "Default%20Settings" +static int initial_menuitems_count; + +#ifndef MOD_INTEGRATED_AGENT +/* + * 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); +} +#endif + +static bool has_security; + +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 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; +} + +#ifndef MOD_INTEGRATED_AGENT +/* + * 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); +} +#endif + +struct keylist_update_ctx { + bool enable_remove_controls; + bool enable_reencrypt_controls; +}; + +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); + const char *fingerprint = fingerprints[this_type]; + strbuf *listentry = 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: { + strbuf_catf(listentry, "ssh1\t%s\t%s", fingerprint, comment); + + /* + * Replace the space in the fingerprint (between bit count and + * hash) with a tab, for nice alignment in the box. + */ + char *p = strchr(listentry->s, ' '); + if (p) + *p = '\t'; + break; + } + + case 2: { + /* + * For nice alignment in the list box, we would ideally want + * every entry to align to the tab stop settings, and have a + * column for algorithm name, one for bit count, one for hex + * fingerprint, and one for key comment. + * + * Unfortunately, some of the algorithm names are so long that + * they overflow into the bit-count field. Fortunately, at the + * moment, those are _precisely_ the algorithm names that + * don't need a bit count displayed anyway (because for + * NIST-style ECDSA the bit count is mentioned in the + * algorithm name, and for ssh-ed25519 there is only one + * possible value anyway). So we fudge this by simply omitting + * the bit count field in that situation. + * + * This is fragile not only in the face of further key types + * that don't follow this pattern, but also in the face of + * font metrics changes - the Windows semantics for list box + * tab stops is that \t aligns to the next one you haven't + * already exceeded, so I have to guess when the key type will + * overflow past the bit-count tab stop and leave out a tab + * character. Urgh. + */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(key->blob)); + ptrlen algname = get_string(src); + const ssh_keyalg *alg = find_pubkey_alg_len(algname); + + bool include_bit_count = (alg == &ssh_dss || alg == &ssh_rsa); + + int wordnumber = 0; + for (const char *p = fingerprint; *p; p++) { + char c = *p; + if (c == ' ') { + if (wordnumber < 2) + c = '\t'; + wordnumber++; + } + if (include_bit_count || wordnumber != 1) + put_byte(listentry, c); + } + + strbuf_catf(listentry, "\t%s", comment); + break; + } + } + + if (ext_flags & LIST_EXTENDED_FLAG_HAS_NO_CLEARTEXT_KEY) { + strbuf_catf(listentry, "\t(encrypted)"); + } else if (ext_flags & LIST_EXTENDED_FLAG_HAS_ENCRYPTED_KEY_FILE) { + strbuf_catf(listentry, "\t(re-encryptable)"); + + /* At least one key can be re-encrypted */ + ctx->enable_reencrypt_controls = true; + } + + SendDlgItemMessage(keylist, IDC_KEYLIST_LISTBOX, + LB_ADDSTRING, 0, (LPARAM)listentry->s); + strbuf_free(listentry); +} + +/* + * Update the visible key list. + */ +void keylist_update(void) +{ + if (keylist) { + 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; + int status = pageant_enum_keys(keylist_update_callback, ctx, &errmsg); + 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; +#ifdef MOD_PERSO + if( strlen(pphrase)>0 ) { + pps.passphrase=(char*)malloc(strlen(pphrase)+1); + strcpy(pps.passphrase,pphrase); + } + else +#endif + 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, + 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; + 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}, + }; + + 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; + { + static int tabs[] = { 35, 75, 300 }; + SendDlgItemMessage(hwnd, IDC_KEYLIST_LISTBOX, LB_SETTABSTOPS, + sizeof(tabs) / sizeof(*tabs), + (LPARAM) tabs); + } + 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_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; +#ifdef MOD_INTEGRATED_AGENT + tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(901)); +#else + tnid.hIcon = hicon = LoadIcon(hinst, MAKEINTRESOURCE(201)); +#endif +#ifdef MOD_PERSO + strcpy(tnid.szTip, "Kageant (KiTTY authentication agent)"); +#else + strcpy(tnid.szTip, "Pageant (PuTTY authentication agent)"); +#endif + + res = Shell_NotifyIcon(NIM_ADD, &tnid); + +#ifdef MOD_PERSO + trayIcone = tnid ; +#endif + + if (hicon) DestroyIcon(hicon); + + return res; +} + +#ifdef MOD_PERSO +#include +#include +#ifndef MOD_INTEGRATED_AGENT +// Teste l'existance d'un repertoire +int existdirectory( const char * filename ) { + struct _stat statBuf ; + + if( filename == NULL ) return 0 ; + if( strlen(filename)==0 ) return 0 ; + if( _stat( filename, &statBuf ) == -1 ) return 0 ; + + if( ( statBuf.st_mode & _S_IFMT ) == _S_IFDIR ) { return 1 ; } + else { return 0 ; } +} +#endif +int MakeSessionMenu( HMENU session_menu, const int start_menuitem, const char * directory ) { + int index_menu = start_menuitem ; + MENUITEMINFO mii; + char buffer[MAX_PATH],fList[MAX_PATH]; + DIR * dir ; + struct dirent * de ; + if( strlen(directory)>0 ) { + sprintf( buffer, ".\\Sessions\\%s", directory ) ; + } else { + sprintf( buffer, ".\\Sessions" ) ; + } + if( (dir=opendir(buffer)) != NULL ) { + while( (de=readdir(dir)) != NULL ) + if( strcmp(de->d_name, ".")&&strcmp(de->d_name, "..") ) { + unmungestr( de->d_name, fList, MAX_PATH ) ; + char *newpath; + newpath = (char*)malloc( strlen(buffer)+strlen(de->d_name)+2 ); + sprintf(newpath,"%s\\%s",buffer,de->d_name); + if( existdirectory(newpath) ) { + HMENU sub = CreateMenu(); + if( strlen(directory)>0 ) { + sprintf(newpath,"%s/%s",directory,de->d_name); + } else { + sprintf(newpath,"%s",de->d_name); + } + index_menu=MakeSessionMenu(sub,index_menu,newpath); + AppendMenu( session_menu, MF_POPUP, (UINT_PTR)sub, fList ) ; + index_menu++; + } else if( !strcmp(FileExtension,"") || !strcmp(FileExtension,fList+strlen(fList)-strlen(FileExtension)) ) { + 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; + if( strlen(directory)>0 ) { + sprintf(newpath,"%s/%s",directory,fList); + } else { + sprintf(newpath,"%s",fList); + } + mii.dwTypeData = newpath; + InsertMenuItem(session_menu, index_menu, true, &mii); + index_menu++; + } + free(newpath); + } + } + return index_menu ; +} +#endif + +/* 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; + +#ifdef MOD_PERSO + if( IniFileFlag==2 ) { + + for(num_entries = GetMenuItemCount(session_menu); + num_entries > initial_menuitems_count; + num_entries--) + RemoveMenu(session_menu, 0, MF_BYPOSITION); + + index_menu = 0; + index_menu = MakeSessionMenu( session_menu, index_menu, "" ) ; + + + } else { +#endif + 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); + +#ifdef MOD_PERSO + } +#endif + + 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); + } +} + +#ifndef NO_SECURITY +/* + * 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() in winpgntc.c. + */ +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; +} +#endif + +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; + +#ifndef NO_SECURITY + PSID mapsid = NULL; + PSID expectedsid = NULL; + PSID expectedsid_bc = NULL; + PSECURITY_DESCRIPTOR psd = NULL; +#endif + + 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 + +#ifndef NO_SECURITY + if (has_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 +#endif /* NO_SECURITY */ + { +#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); +//MessageBox(NULL,param,"Launching",MB_OK) ; + 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_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); + } +} + +#ifndef MOD_INTEGRATED_AGENT +void logevent(LogContext *logctx, const char *event) +{ + unreachable("Pageant can't create a LogContext, so this can't be called"); +} + +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); +} +#endif + +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; + +#ifndef MOD_INTEGRATED_AGENT +int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) +#else +int WINAPI Agent_WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) +#endif +{ + MSG msg; + const char *command = NULL; + bool added_keys = false; + bool show_keylist_on_startup = false; + int argc, i; + char **argv, **argstart; + + dll_hijacking_protection(); + + hinst = inst; + +#ifdef MOD_PERSO + IniFileFlag = 0 ; + DirectoryBrowseFlag = 0 ; + LoadParametersLight() ; +#endif + + /* + * Determine whether we're an NT system (should have security + * APIs) or a non-NT system (don't do security). + */ + init_winver(); + has_security = (osPlatformId == VER_PLATFORM_WIN32_NT); + + if (has_security) { +#ifndef NO_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; + } +#else + MessageBox(NULL, + "This program has been compiled for Win9X and will\n" + "not run on NT, in case it causes a security breach.", + "Pageant Fatal Error", MB_ICONERROR | MB_OK); + return 1; +#endif + } + + /* + * 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); +#ifdef MOD_PERSO + } else { + strcpy(r, "kitty.exe"); + if ( (fp = fopen(b, "r")) != NULL) { + putty_path = dupstr(b); + fclose(fp); + } else { + strcpy(r, "kitty_portable.exe"); + if ( (fp = fopen(b, "r")) != NULL) { + putty_path = dupstr(b); + fclose(fp); + } else putty_path = NULL; + } + } +#else + } else + putty_path = NULL; +#endif + } + + /* + * Find out if Pageant is already running. + */ + already_running = agent_exists(); + + /* + * Initialise the cross-platform Pageant code. + */ + if (!already_running) { + pageant_init(); + } + + /* + * Process the command line and add keys as listed on it. + */ + split_into_argv(cmdline, &argc, &argv, &argstart); + bool doing_opts = true; + bool add_keys_encrypted = false; + for (i = 0; i < argc; i++) { + char *p = argv[i]; + if (*p == '-' && doing_opts) { + if (!strcmp(p, "-pgpfp")) { + pgp_fingerprints_msgbox(NULL); + return 1; + } else if (!strcmp(p, "-restrict-acl") || + !strcmp(p, "-restrict_acl") || + !strcmp(p, "-restrictacl")) { + restrict_process_acl(); + } else if (!strcmp(p, "-restrict-putty-acl") || + !strcmp(p, "-restrict_putty_acl")) { + restrict_putty_acl = true; +#ifdef MOD_PERSO + } else if (!strcmp(argv[i], "-pass")) { + i++; + strcpy(pphrase,argv[i]); +#endif + } 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")) { + add_keys_encrypted = true; + } else if (!strcmp(p, "-keylist") || !strcmp(p, "--keylist")) { + show_keylist_on_startup = true; + } else if (!strcmp(p, "-c")) { + /* + * If we see `-c', then the rest of the + * command line should be treated as a + * command to be spawned. + */ + if (i < argc-1) + command = argstart[i+1]; + else + command = ""; + break; + } else if (!strcmp(p, "--")) { + doing_opts = false; + } else { + char *msg = dupprintf("unrecognised command-line option\n" + "'%s'", p); + MessageBox(NULL, msg, "Pageant command-line syntax error", + MB_ICONERROR | MB_OK); + exit(1); + } + } else { + Filename *fn = filename_from_str(p); + win_add_keyfile(fn, add_keys_encrypted); + filename_free(fn); + added_keys = true; + } + } + + /* + * Forget any passphrase that we retained while going over + * command line keyfiles. + */ + pageant_forget_passphrases(); + + if (command) { + char *args; + if (command[0] == '"') + args = strchr(++command, '"'); + else + args = strchr(command, ' '); + if (args) { + *args++ = 0; + while(*args && isspace(*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 && !added_keys) { + MessageBox(NULL, "Pageant is already running", "Pageant Error", + MB_ICONERROR | MB_OK); + } + return 0; + } + +#if !defined NO_SECURITY + + /* + * Set up a named-pipe listener. + */ + { + Plug *pl_plug; + wpc->plc.vt = &winpgnt_vtable; + wpc->plc.suppress_logging = true; + 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); + sfree(pipename); + } + +#endif /* !defined NO_SECURITY */ + + /* + * Set up window classes for two hidden windows: one that receives + * all the messages to do with our presence in the system tray, + * and one 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 = TrayWndProc; + wndclass.hInstance = inst; +#ifdef MOD_INTEGRATED_AGENT + wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON_AGENT)); +#else + wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON)); +#endif + wndclass.lpszClassName = TRAYCLASSNAME; + + RegisterClass(&wndclass); + + memset(&wndclass, 0, sizeof(wndclass)); + wndclass.lpfnWndProc = wm_copydata_WndProc; + wndclass.hInstance = inst; + wndclass.lpszClassName = IPCCLASSNAME; + + 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); + + /* 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); + + 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 */ + handle_add_foreign_event(wmct.ev_msg_ready, wm_copydata_got_msg, NULL); + + if (show_keylist_on_startup) + create_keylist_window(); + + /* + * Main message loop. + */ + while (true) { + HANDLE *handles; + int nhandles, n; + + handles = handle_get_events(&nhandles); + + n = MsgWaitForMultipleObjects(nhandles, handles, false, + INFINITE, QS_ALLINPUT); + + if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) { + handle_got_event(handles[n - WAIT_OBJECT_0]); + sfree(handles); + } else + sfree(handles); + + while (PeekMessageW(&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); + + cleanup_exit(msg.wParam); + return msg.wParam; /* just in case optimiser complains */ +} diff --git a/0.77_My_PuTTY/windows/winpgntc.c b/0.77_My_PuTTY/windows/winpgntc.c new file mode 100644 index 0000000..c9d18b2 --- /dev/null +++ b/0.77_My_PuTTY/windows/winpgntc.c @@ -0,0 +1,348 @@ +/* + * Pageant client code. + */ + +#include +#include +#include + +#include "putty.h" +#include "pageant.h" /* for AGENT_MAX_MSGLEN */ + +#ifndef NO_SECURITY +#include "winsecur.h" +#include "wincapi.h" +#endif + +#define AGENT_COPYDATA_ID 0x804e50ba /* random goop */ + +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; +#ifndef NO_SECURITY + if (got_advapi()) { + /* + * Make the file mapping we create for communication with + * Pageant owned by the user SID rather than the default. This + * should make communication between processes with slightly + * different contexts more reliable: in particular, command + * prompts launched as administrator should still be able to + * run PSFTPs which refer back to the owning user's + * unprivileged Pageant. + */ + usersid = get_user_sid(); + + if (usersid) { + psd = (PSECURITY_DESCRIPTOR) + LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (psd) { + if (p_InitializeSecurityDescriptor + (psd, SECURITY_DESCRIPTOR_REVISION) && + p_SetSecurityDescriptorOwner(psd, usersid, false)) { + sa.nLength = sizeof(sa); + sa.bInheritHandle = true; + sa.lpSecurityDescriptor = psd; + psa = &sa; + } else { + LocalFree(psd); + psd = NULL; + } + } + } + } +#endif /* NO_SECURITY */ + + filemap = CreateFileMapping(INVALID_HANDLE_VALUE, psa, PAGE_READWRITE, + 0, AGENT_MAX_MSGLEN, mapname); + if (filemap == NULL || filemap == INVALID_HANDLE_VALUE) { + sfree(mapname); + return; /* *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); +} + +#ifndef NO_SECURITY + +char *agent_named_pipe_name(void) +{ + char *username, *suffix, *pipename; + username = get_username(); + suffix = capi_obfuscate_string("Pageant"); + pipename = dupprintf("\\\\.\\pipe\\pageant.%s.%s", username, suffix); + sfree(username); + sfree(suffix); + return pipename; +} + +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; +} + +#else /* NO_SECURITY */ + +Socket *agent_connect(void *vctx, Plug *plug) +{ + unreachable("no agent_connect_ctx can be constructed on this platform"); +} + +agent_connect_ctx *agent_get_connect_ctx(void) +{ + return NULL; +} + +void agent_free_connect_ctx(agent_connect_ctx *ctx) +{ +} + +bool agent_exists(void) +{ + return wm_copydata_agent_exists(); +} + +agent_pending_query *agent_query( + strbuf *query, void **out, int *outlen, + void (*callback)(void *, void *, int), void *callback_ctx) +{ + wm_copydata_agent_query(query, out, outlen); + return NULL; +} + +void agent_cancel_query(agent_pending_query *q) +{ + unreachable("Windows agent queries are never asynchronous!"); +} + +#endif /* NO_SECURITY */ diff --git a/0.77_My_PuTTY/windows/winplink.c b/0.77_My_PuTTY/windows/winplink.c new file mode 100644 index 0000000..29b8d2b --- /dev/null +++ b/0.77_My_PuTTY/windows/winplink.c @@ -0,0 +1,650 @@ +/* + * PLink - a Windows command-line (stdin/stdout) variant of PuTTY. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "storage.h" +#include "tree234.h" +#include "winsecur.h" + +#ifdef MOD_PERSO +// Flag pour le fonctionnement en mode "portable" (gestion par fichiers) +extern int IniFileFlag ; + +// Flag permettant la gestion de l'arborscence (dossier=folder) dans le cas d'un savemode=dir +extern int DirectoryBrowseFlag ; + +#include "../../kitty_crypt.c" +#include "../../kitty_commun.h" + +size_t win_seat_output_local(Seat *seat, bool is_stderr, const void *data, size_t len) { return 0 ; } +int get_param( const char * val ) { + if( !stricmp( val, "INIFILE" ) ) { return IniFileFlag ; } + else if( !stricmp( val, "DIRECTORYBROWSE" ) ) { return DirectoryBrowseFlag ; } + return 0 ; +} +void SetPasswordInConfig( char * password ) { + int len ; + if( password!=NULL ) { + len = strlen( password ) ; + if( len > 126 ) len = 126 ; + } +} +void SetUsernameInConfig( char * username ) { + int len ; + if( username!=NULL ) { + len = strlen( username ) ; + if( len > 126 ) len = 126 ; + } +} +void debug_logevent( const char *fmt, ... ) { + va_list ap; + char *buf; + va_start(ap, fmt); + buf = dupvprintf(fmt, ap) ; + va_end(ap); + printf(buf) ; + free(buf); +} +#endif +#ifdef MOD_PROXY +#include "kitty_proxy.h" +#endif + +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; +#ifdef MOD_PERSO +Conf *conf; +#else +static Conf *conf; +#endif + +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, bool is_stderr, const void *data, size_t len) +{ + 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 int plink_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +{ + int ret; + ret = cmdline_get_passwd_input(p); + if (ret == -1) + ret = console_get_userpass_input(p); + return ret; +} + +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, + .get_userpass_input = plink_get_userpass_input, + .notify_remote_exit = nullseat_notify_remote_exit, + .connection_fatal = console_connection_fatal, + .update_specials_menu = nullseat_update_specials_menu, + .get_ttymode = nullseat_get_ttymode, + .set_busy_status = nullseat_set_busy_status, + .verify_ssh_host_key = console_verify_ssh_host_key, + .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive, + .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey, + .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, + .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(" -pw passw login with specified password\n"); + printf(" -D [listen-IP:]listen-port\n"); + printf(" Dynamic SOCKS-based port forwarding\n"); + printf(" -L [listen-IP:]listen-port:host:port\n"); + printf(" Forward local port to remote address\n"); + printf(" -R [listen-IP:]listen-port:host:port\n"); + printf(" Forward remote port to local address\n"); + printf(" -X -x enable / disable X11 forwarding\n"); + printf(" -A -a enable / disable agent forwarding\n"); + printf(" -t -T enable / disable pty allocation\n"); + printf(" -1 -2 force use of particular 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"); +#ifdef MOD_PERSO + printf(" -auto-store-sshkey\n"); + printf(" store automatically the servers ssh keys\n"); +#endif + 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) { + return backend_send(backend, data, len); + } else { + backend_special(backend, SS_EOF, 0); + return 0; + } + } else + return 0; +} + +void stdouterr_sent(struct handle *h, size_t new_backlog, 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 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; +} + +#ifdef MOD_PORTKNOCKING +int ManagePortKnocking( char* host, char *portknockseq ) ; +#endif +#ifdef MOD_PERSO +char * AutoCommand = NULL ; +void ManageAutocommand( struct handle *h ) { + char c = '\n' ; + while( strlen(AutoCommand) > 0 ) { + c = (char)AutoCommand[0] ; + if( c=='\\' ) { + if( AutoCommand[1]=='n' ) stdin_gotdata(h, "\n", 1, 0 ) ; + AutoCommand++ ; + c = '\n' ; + } + else stdin_gotdata(h, AutoCommand, 1, 0 ) ; + AutoCommand++ ; + } + if( c != '\n' ) stdin_gotdata(h, "\n", 1, 0 ) ; + } + +int plink_main(int argc, char **argv) ; + +int main(int argc, char **argv) { + int arc=0, ret=0, i ; + char **arv ; + + IniFileFlag = 0 ; + DirectoryBrowseFlag = 0 ; + + arv=(char**)malloc( argc*sizeof(char*) ) ; + for( i=0 ; iprotocol); + 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, "-batch")) { + console_batch_mode = true; + } 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_datapl(cmdbuf, ptrlen_from_asciz(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); + 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); + 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; + } +#ifdef MOD_PROXY +if( GetProxySelectionFlag() ) { + LoadProxyInfo( conf, conf_get_str(conf,CONF_proxyselection) ) ; +} +#endif +#ifdef MOD_PORTKNOCKING + ManagePortKnocking(conf_get_str(conf,CONF_host),conf_get_str(conf,CONF_portknockingoptions)); +#endif + + 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/0.77_My_PuTTY/windows/winprint.c b/0.77_My_PuTTY/windows/winprint.c new file mode 100644 index 0000000..36699f0 --- /dev/null +++ b/0.77_My_PuTTY/windows/winprint.c @@ -0,0 +1,224 @@ +/* + * 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. Discard the return value since it + * will almost certainly be a failure due to lack of space. + */ + p_EnumPrinters(param, NULL, level, (LPBYTE)((*buffer)+offset), 512, + &needed, &nprinters); + + 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 *ret = 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) { + ret->enum_level = 5; + } else { + ret->enum_level = 4; + } + + if (!printer_add_enum(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, + ret->enum_level, &buffer, 0, nprinters_ptr)) + goto error; + + switch (ret->enum_level) { + case 4: + ret->info.i4 = (LPPRINTER_INFO_4)buffer; + break; + case 5: + ret->info.i5 = (LPPRINTER_INFO_5)buffer; + break; + } + ret->nprinters = *nprinters_ptr; + + return ret; + + error: + sfree(buffer); + sfree(ret); + *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 *ret = snew(printer_job); + DOC_INFO_1 docinfo; + bool jobstarted = false, pagestarted = false; + + init_winfuncs(); + + ret->hprinter = NULL; + if (!p_OpenPrinter(printer, &ret->hprinter, NULL)) + goto error; + + docinfo.pDocName = "PuTTY remote printer output"; + docinfo.pOutputFile = NULL; + docinfo.pDatatype = "RAW"; + + if (!p_StartDocPrinter(ret->hprinter, 1, (LPBYTE)&docinfo)) + goto error; + jobstarted = true; + + if (!p_StartPagePrinter(ret->hprinter)) + goto error; + pagestarted = true; + + return ret; + + error: + if (pagestarted) + p_EndPagePrinter(ret->hprinter); + if (jobstarted) + p_EndDocPrinter(ret->hprinter); + if (ret->hprinter) + p_ClosePrinter(ret->hprinter); + sfree(ret); + 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/0.77_My_PuTTY/windows/winproxy.c b/0.77_My_PuTTY/windows/winproxy.c new file mode 100644 index 0000000..fb42b28 --- /dev/null +++ b/0.77_My_PuTTY/windows/winproxy.c @@ -0,0 +1,107 @@ +/* + * winproxy.c: Windows implementation of platform_new_connection(), + * supporting an OpenSSH-like proxy command via the winhandl.c + * mechanism. + */ + +#include +#include + +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" + +Socket *platform_new_connection(SockAddr *addr, const char *hostname, + int port, bool privport, + bool oobinline, bool nodelay, bool keepalive, + Plug *plug, Conf *conf) +{ + char *cmd; + HANDLE us_to_cmd, cmd_from_us; + HANDLE us_from_cmd, cmd_to_us; + HANDLE us_from_cmd_err, cmd_err_to_us; + SECURITY_ATTRIBUTES sa; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + if (conf_get_int(conf, CONF_proxy_type) != PROXY_CMD) + return NULL; + + cmd = format_telnet_command(addr, port, conf); + + /* We are responsible for this and don't need it any more */ + sk_addr_free(addr); + + { + char *msg = dupprintf("Starting local proxy command: %s", cmd); + plug_log(plug, PLUGLOG_PROXY_MSG, NULL, 0, msg, 0); + sfree(msg); + } + + /* + * Create the pipes to the proxy command, and spawn the proxy + * command process. + */ + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; /* default */ + sa.bInheritHandle = true; + if (!CreatePipe(&us_from_cmd, &cmd_to_us, &sa, 0)) { + sfree(cmd); + return new_error_socket_fmt( + plug, "Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); + } + + if (!CreatePipe(&cmd_from_us, &us_to_cmd, &sa, 0)) { + sfree(cmd); + CloseHandle(us_from_cmd); + CloseHandle(cmd_to_us); + return new_error_socket_fmt( + plug, "Unable to create pipes for proxy command: %s", + win_strerror(GetLastError())); + } + + if (!CreatePipe(&us_from_cmd_err, &cmd_err_to_us, &sa, 0)) { + sfree(cmd); + CloseHandle(us_from_cmd); + CloseHandle(cmd_to_us); + CloseHandle(us_to_cmd); + CloseHandle(cmd_from_us); + return new_error_socket_fmt( + plug, "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; + CreateProcess(NULL, cmd, NULL, NULL, true, + CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS, + NULL, NULL, &si, &pi); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + sfree(cmd); + + CloseHandle(cmd_from_us); + CloseHandle(cmd_to_us); + + if (cmd_err_to_us != NULL) + CloseHandle(cmd_err_to_us); + + return make_handle_socket(us_to_cmd, us_from_cmd, us_from_cmd_err, + plug, false); +} diff --git a/0.77_My_PuTTY/windows/winseat.h b/0.77_My_PuTTY/windows/winseat.h new file mode 100644 index 0000000..05d0bc3 --- /dev/null +++ b/0.77_My_PuTTY/windows/winseat.h @@ -0,0 +1,17 @@ +/* + * Small implementation of Seat and LogPolicy shared between window.c + * and windlg.c. + */ + +#ifdef MOD_FAR2L +#pragma once +#endif +typedef struct WinGuiSeat WinGuiSeat; + +struct WinGuiSeat { + HWND term_hwnd; + Seat seat; + LogPolicy logpolicy; +}; + +extern const LogPolicyVtable win_gui_logpolicy_vt; /* in windlg.c */ diff --git a/0.77_My_PuTTY/windows/winsecur.c b/0.77_My_PuTTY/windows/winsecur.c new file mode 100644 index 0000000..b38ba1d --- /dev/null +++ b/0.77_My_PuTTY/windows/winsecur.c @@ -0,0 +1,334 @@ +/* + * winsecur.c: implementation of winsecur.h. + */ + +#include +#include + +#include "putty.h" + +#if !defined NO_SECURITY + +#include "winsecur.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 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; +} +#endif /* !defined NO_SECURITY */ + +/* + * 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; + +#if !defined NO_SECURITY + ret = really_restrict_process_acl(&error); +#else + ret = false; + error = dupstr("ACL restrictions not compiled into this binary"); +#endif + if (!ret) + modalfatalbox("Could not restrict process ACL: %s", error); +} diff --git a/0.77_My_PuTTY/windows/winsecur.h b/0.77_My_PuTTY/windows/winsecur.h new file mode 100644 index 0000000..62d9f4b --- /dev/null +++ b/0.77_My_PuTTY/windows/winsecur.h @@ -0,0 +1,53 @@ +/* + * winsecur.h: some miscellaneous security-related helper functions, + * defined in winsecur.c, that use the advapi32 library. Also + * centralises the machinery for dynamically loading that library. + */ + +#if !defined NO_SECURITY + +#include + +/* + * 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); + +#endif diff --git a/0.77_My_PuTTY/windows/winselcli.c b/0.77_My_PuTTY/windows/winselcli.c new file mode 100644 index 0000000..b3c9273 --- /dev/null +++ b/0.77_My_PuTTY/windows/winselcli.c @@ -0,0 +1,78 @@ +/* + * Implementation of do_select() for winnet.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/0.77_My_PuTTY/windows/winselgui.c b/0.77_My_PuTTY/windows/winselgui.c new file mode 100644 index 0000000..ed38faf --- /dev/null +++ b/0.77_My_PuTTY/windows/winselgui.c @@ -0,0 +1,38 @@ +/* + * Implementation of do_select() for winnet.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; +} diff --git a/0.77_My_PuTTY/windows/winser.c b/0.77_My_PuTTY/windows/winser.c new file mode 100644 index 0000000..ddcb7a6 --- /dev/null +++ b/0.77_My_PuTTY/windows/winser.c @@ -0,0 +1,465 @@ +/* + * 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) +{ + 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; + } +} + +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); + 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 size_t serial_send(Backend *be, const char *buf, size_t len) +{ + Serial *serial = container_of(be, Serial, backend); + + if (serial->out == NULL) + return 0; + + serial->bufsize = handle_write(serial->out, buf, len); + return serial->bufsize; +} + +/* + * 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 = "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/0.77_My_PuTTY/windows/winsftp.c b/0.77_My_PuTTY/windows/winsftp.c new file mode 100644 index 0000000..4e0cb5a --- /dev/null +++ b/0.77_My_PuTTY/windows/winsftp.c @@ -0,0 +1,697 @@ +/* + * winsftp.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 "winsecur.h" + +#ifdef MOD_PERSO +#include + +// Flag pour le fonctionnement en mode "portable" (gestion par fichiers) +extern int IniFileFlag ; + +// Flag permettant la gestion de l'arborscence (dossier=folder) dans le cas d'un savemode=dir +extern int DirectoryBrowseFlag ; + +#include "../../kitty_crypt.c" +#include "../../kitty_commun.h" + +int get_param( const char * val ) { + if( !stricmp( val, "INIFILE" ) ) {return IniFileFlag ; } + else if( !stricmp( val, "DIRECTORYBROWSE" ) ) { return DirectoryBrowseFlag ; } + return 0 ; +} +void SetPasswordInConfig( const char * password ) { + int len ; + if( password!=NULL ) { + len = strlen( password ) ; + if( len > 126 ) len = 126 ; + } +} +void SetUsernameInConfig( const char * username ) { + int len ; + if( username!=NULL ) { + len = strlen( username ) ; + if( len > 126 ) len = 126 ; + } +} +void debug_logevent( const char *fmt, ... ) { + va_list ap; + char *buf; + va_start(ap, fmt); + buf = dupvprintf(fmt, ap) ; + va_end(ap); + printf(buf) ; + free(buf); +} +#endif + +int filexfer_get_userpass_input(Seat *seat, prompts_t *p, bufchain *input) +{ + int ret; + ret = cmdline_get_passwd_input(p); + if (ret == -1) + ret = console_get_userpass_input(p); + return ret; +} + +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 *ret; + + h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, 0, 0); + if (h == INVALID_HANDLE_VALUE) + return NULL; + + ret = snew(RFile); + ret->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 ret; +} + +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 *ret; + + h = CreateFile(name, GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + if (h == INVALID_HANDLE_VALUE) + return NULL; + + ret = snew(WFile); + ret->h = h; + + return ret; +} + +WFile *open_existing_wfile(const char *name, uint64_t *size) +{ + HANDLE h; + WFile *ret; + + h = CreateFile(name, GENERIC_WRITE, FILE_SHARE_READ, NULL, + OPEN_EXISTING, 0, 0); + if (h == INVALID_HANDLE_VALUE) + return NULL; + + ret = snew(WFile); + ret->h = h; + + if (size) { + DWORD lo, hi; + lo = GetFileSize(h, &hi); + *size = uint64_from_words(hi, lo); + } + + return ret; +} + +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 *ret; + + /* 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); + + ret = snew(DirHandle); + ret->h = h; + ret->name = dupstr(fdat.cFileName); + return ret; +} + +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 *ret; + char *last; + + h = FindFirstFile(name, &fdat); + if (h == INVALID_HANDLE_VALUE) + return NULL; + + ret = snew(WildcardMatcher); + ret->h = h; + ret->srcpath = dupstr(name); + last = stripslashes(ret->srcpath, true); + *last = '\0'; + if (fdat.cFileName[0] == '.' && + (fdat.cFileName[1] == '\0' || + (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0'))) + ret->name = NULL; + else + ret->name = dupcat(ret->srcpath, fdat.cFileName); + + return ret; +} + +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; +#ifdef MOD_PERSO + IniFileFlag = 0 ; + DirectoryBrowseFlag = 0 ; + LoadParametersLight() ; +#endif + + dll_hijacking_protection(); + + ret = psftp_main(argc, argv); + + return ret; +} diff --git a/0.77_My_PuTTY/windows/winshare.c b/0.77_My_PuTTY/windows/winshare.c new file mode 100644 index 0000000..212ecd2 --- /dev/null +++ b/0.77_My_PuTTY/windows/winshare.c @@ -0,0 +1,151 @@ +/* + * Windows implementation of SSH connection-sharing IPC setup. + */ + +#include +#include + +#if !defined NO_SECURITY + +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#include "wincapi.h" +#include "winsecur.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; + PSECURITY_DESCRIPTOR psd; + PACL acl; + + /* + * 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. + */ + { + SECURITY_ATTRIBUTES sa; + + mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name); + if (!make_private_security_descriptor(MUTEX_ALL_ACCESS, + &psd, &acl, logtext)) { + sfree(mutexname); + sfree(name); + return SHARE_NONE; + } + + memset(&sa, 0, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = psd; + sa.bInheritHandle = false; + + mutex = CreateMutex(&sa, false, mutexname); + + if (!mutex) { + *logtext = dupprintf("CreateMutex(\"%s\") failed: %s", + mutexname, win_strerror(GetLastError())); + sfree(mutexname); + sfree(name); + LocalFree(psd); + LocalFree(acl); + return SHARE_NONE; + } + + sfree(mutexname); + LocalFree(psd); + LocalFree(acl); + + WaitForSingleObject(mutex, INFINITE); + } + + 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); + ReleaseMutex(mutex); + CloseHandle(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) +{ +} + +#else /* !defined NO_SECURITY */ + +#include "noshare.c" + +#endif /* !defined NO_SECURITY */ diff --git a/0.77_My_PuTTY/windows/winsocks.c b/0.77_My_PuTTY/windows/winsocks.c new file mode 100644 index 0000000..001ce5d --- /dev/null +++ b/0.77_My_PuTTY/windows/winsocks.c @@ -0,0 +1,24 @@ +/* + * Main program for Windows psocks. + */ + +#include "putty.h" +#include "ssh.h" +#include "psocks.h" + +static const PsocksPlatform platform = { + NULL /* open_pipes */, + NULL /* start_subcommand */, +}; + +int main(int argc, char **argv) +{ + psocks_state *ps = psocks_new(&platform); + psocks_cmdline(ps, argc, argv); + + sk_init(); + winselcli_setup(); + psocks_start(ps); + + cli_main_loop(cliloop_null_pre, cliloop_null_post, NULL); +} diff --git a/0.77_My_PuTTY/windows/winstore.c b/0.77_My_PuTTY/windows/winstore.c new file mode 100644 index 0000000..89ddaa9 --- /dev/null +++ b/0.77_My_PuTTY/windows/winstore.c @@ -0,0 +1,1451 @@ +/* + * winstore.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"; + +#ifdef MOD_PERSO +#include "kitty_store.h" +#endif + +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; +#ifdef MOD_PERSO + HSettingsList list ; + char *filename ; +#endif +}; + +settings_w *open_settings_w(const char *sessionname, char **errmsg) +{ + HKEY subkey1, sesskey; + int ret; + strbuf *sb; + + *errmsg = NULL; + + if (!sessionname || !*sessionname) + sessionname = "Default Settings"; + +#ifdef MOD_PERSO + //MessageBox(NULL,sessionname,sesspath,MB_OK); + if( sessionname==NULL ) return NULL ; + if( get_param("INIFILE")==SAVEMODE_DIR ) { + char *p; + if ( *(sessionname+strlen(sessionname)-1) == ']') { + if( ( p = strrchr(sessionname, '[') ) != NULL ) *(p-1) = '\0'; + } + + if( (strlen(sessionname)==0)||((strstr( sessionname, " [" )==sessionname)&&(sessionname[strlen(sessionname)-1]==']')) ) + { return NULL ; } + sb = strbuf_new(); + escape_registry_key(sessionname, sb); + settings_w *toret = snew(settings_w); + toret->filename = (char*)malloc(strlen(sb->s)+1); + strcpy(toret->filename,sb->s); + toret->list = SettingsInit() ; + strbuf_free(sb); + return toret ; + } +#endif + + sb = strbuf_new(); + escape_registry_key(sessionname, sb); + + ret = RegCreateKey(HKEY_CURRENT_USER, puttystr, &subkey1); + if (ret != ERROR_SUCCESS) { + strbuf_free(sb); + *errmsg = dupprintf("Unable to create registry key\n" + "HKEY_CURRENT_USER\\%s", puttystr); + return NULL; + } + ret = RegCreateKey(subkey1, sb->s, &sesskey); + RegCloseKey(subkey1); + if (ret != ERROR_SUCCESS) { + *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 *toret = snew(settings_w); + toret->sesskey = sesskey; + return toret; +} + +void write_setting_s(settings_w *handle, const char *key, const char *value) +{ +#ifdef MOD_PERSO + if (handle) + if( get_param("INIFILE")==SAVEMODE_DIR ) { + SettingsAddItem( handle->list, key, value ) ; + return ; + } +#endif + if (handle) + RegSetValueEx(handle->sesskey, key, 0, REG_SZ, (CONST BYTE *)value, + 1 + strlen(value)); +} + +void write_setting_i(settings_w *handle, const char *key, int value) +{ +#ifdef MOD_PERSO + if (handle) + if( get_param("INIFILE")==SAVEMODE_DIR ) { + char b[16] ; + itoa( value, b, 10 ) ; + SettingsAddItem( handle->list, key, b ) ; + return ; + } +#endif + if (handle) + RegSetValueEx(handle->sesskey, key, 0, REG_DWORD, + (CONST BYTE *) &value, sizeof(value)); +} + +void close_settings_w(settings_w *handle) +{ +#ifdef MOD_PERSO + if (!handle) return ; + if( GetReadOnlyFlag() ) return ; + if( get_param("INIFILE")==SAVEMODE_DIR ) { + HANDLE hFile; + WIN32_FIND_DATA FindFile; + if ((hFile = FindFirstFile(sesspath, &FindFile)) == INVALID_HANDLE_VALUE) { + if (!createPath(sesspath)) { + errorShow("Unable to create directory for storing sessions", sesspath); + return; + } + } + FindClose(hFile); + GetCurrentDirectory( (MAX_PATH*2), oldpath); + SetCurrentDirectory(sesspath); + SettingsSave( handle->list, handle->filename ) ; + CloseHandle( (HANDLE)hFile ); + SetCurrentDirectory(oldpath); + free( handle->filename ) ; + handle->filename = NULL ; + SettingsFree( handle->list ) ; + handle->list = NULL ; + sfree( handle ) ; + return ; + } +#endif + RegCloseKey(handle->sesskey); + sfree(handle); +} + +struct settings_r { + HKEY sesskey; +#ifdef MOD_PERSO + HSettingsList list ; +#endif +}; + +settings_r *open_settings_r(const char *sessionname) +{ + HKEY subkey1, sesskey; + strbuf *sb; + + if (!sessionname || !*sessionname) + sessionname = "Default Settings"; + +#ifdef MOD_PERSO + //MessageBox(NULL,sessionname,sesspath,MB_OK); + loadPath() ; + if( get_param("INIFILE")==SAVEMODE_DIR ) { + + HANDLE hFile; + char *p; + p = snewn(3 * strlen(sessionname) + 1 + 16, char); + mungestr(sessionname, p); + strcat(p, sessionsuffix); + settings_r *toret = snew(settings_r) ; + toret->list = SettingsInit() ; + GetCurrentDirectory( (MAX_PATH*2), oldpath); + + if (SetCurrentDirectory(sesspath)) { + hFile = CreateFile(p, GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); + } else { + hFile = INVALID_HANDLE_VALUE; + } + + if( strcmp(sessionname,"Default Settings") && (hFile == INVALID_HANDLE_VALUE) ) { + //errorShow("Unable to load file for reading", p); + SetCurrentDirectory(oldpath); + SettingsFree(toret->list); + sfree(toret); + sfree(p); + return NULL; + } + + if( hFile != INVALID_HANDLE_VALUE ) { + CloseHandle(hFile); + SettingsLoad( toret->list, p ) ; + } else { + CloseHandle(hFile) ; + SetCurrentDirectory(oldpath); + SettingsFree(toret->list); + sfree(toret); + sfree(p); + return NULL ; + } + + sfree(p) ; + SetCurrentDirectory(oldpath) ; + return toret ; + } + +#endif + sb = strbuf_new(); + escape_registry_key(sessionname, sb); + + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) { + sesskey = NULL; + } else { + if (RegOpenKey(subkey1, sb->s, &sesskey) != ERROR_SUCCESS) { + sesskey = NULL; + } + RegCloseKey(subkey1); + } + + strbuf_free(sb); + + if (!sesskey) + return NULL; + + settings_r *toret = snew(settings_r); + toret->sesskey = sesskey; + return toret; +} + +char *read_setting_s(settings_r *handle, const char *key) +{ + DWORD type, allocsize, size; + char *ret; + + if (!handle) + return NULL; + +#ifdef MOD_PERSO + if( get_param("INIFILE")==SAVEMODE_DIR ) { + return SettingsKey_str( handle->list, key ) ; + } +#endif + + /* Find out the type and size of the data. */ + if (RegQueryValueEx(handle->sesskey, key, 0, + &type, NULL, &size) != ERROR_SUCCESS || + type != REG_SZ) + return NULL; + + allocsize = size+1; /* allow for an extra NUL if needed */ + ret = snewn(allocsize, char); + if (RegQueryValueEx(handle->sesskey, key, 0, + &type, (BYTE *)ret, &size) != ERROR_SUCCESS || + type != REG_SZ) { + sfree(ret); + return NULL; + } + assert(size < allocsize); + ret[size] = '\0'; /* add an extra NUL in case RegQueryValueEx + * didn't supply one */ + + return ret; +} + +int read_setting_i(settings_r *handle, const char *key, int defvalue) +{ + DWORD type, val, size; + size = sizeof(val); +#ifdef MOD_PERSO + if( get_param("INIFILE")==SAVEMODE_DIR ) { + if( handle==NULL ) return defvalue ; + return SettingsKey_int( handle->list, key, defvalue ) ; + } +#endif + + if (!handle || + RegQueryValueEx(handle->sesskey, key, 0, &type, + (BYTE *) &val, &size) != ERROR_SUCCESS || + size != sizeof(val) || type != REG_DWORD) + 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) +{ + write_setting_s(handle, name, result->path); +} + +void close_settings_r(settings_r *handle) +{ +#ifdef MOD_PERSO + if (!handle) return; + if( get_param("INIFILE")==SAVEMODE_DIR ) { + SettingsFree(handle->list); + handle->list = NULL ; + sfree( handle ); + return ; + } +#endif + if (handle) { + RegCloseKey(handle->sesskey); + sfree(handle); + } +} + +void del_settings(const char *sessionname) +{ + HKEY subkey1; + strbuf *sb; +#ifdef MOD_PERSO + if (!sessionname) return; + if( get_param("INIFILE")==SAVEMODE_DIR ) { + char *p, *p2; + if( GetReadOnlyFlag() ) return ; + if( (strstr( sessionname, " [" )==sessionname)&&(sessionname[strlen(sessionname)-1]==']') ) { + if( !strcmp(sessionname," [..]") ) { + MessageBox(NULL,"It is not allowed to delete .. directory","Error",MB_OK|MB_ICONERROR) ; + return ; + } + // La session a purger est un folder + p = snewn(3 * strlen(sessionname) + 1, char); + strcpy( p, sessionname+2 ) ; + p[strlen(p)-1] = '\0' ; + GetCurrentDirectory( (MAX_PATH*2), oldpath); + char buffer[1024]; + sprintf( buffer, "Are your sure you want to delete %s directory ?", p ); + if( MessageBox( NULL, buffer, "Confirmation", MB_YESNO|MB_ICONWARNING|MB_DEFBUTTON2|MB_APPLMODAL ) == IDYES ) + if (SetCurrentDirectory(sesspath)) { + DelDir( p ) ; + SetCurrentDirectory(oldpath); + } + sfree(p); + } else { + /* JK: if sessionname contains [registry] -> cut it off */ + if( get_param("INIFILE")==SAVEMODE_DIR ) { + if ( *(sessionname+strlen(sessionname)-1) == ']') { + if( ( p = strrchr(sessionname, '[') ) != NULL ) *(p-1) = '\0'; + } + } + p = snewn(3 * strlen(sessionname) + 1, char); + mungestr(sessionname, p); + p2 = snewn(3 * strlen(p) + 1, char); + packstr(p, p2); + char buffer[1024] ; + sprintf( buffer, "Sessions_Commands\\%s", p ) ; + DelDir( buffer ) ; + GetCurrentDirectory( (MAX_PATH*2), oldpath); + if (SetCurrentDirectory(sesspath)) { + if (!DeleteFile(p2)) { + errorShow("Unable to delete settings.", NULL) ; + } + SetCurrentDirectory(oldpath); + } + sfree(p); + sfree(p2); + } + return ; + } +#endif + + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &subkey1) != ERROR_SUCCESS) + return; + + sb = strbuf_new(); + escape_registry_key(sessionname, sb); + RegDeleteKey(subkey1, sb->s); + strbuf_free(sb); + + RegCloseKey(subkey1); + + remove_session_from_jumplist(sessionname); +} + +struct settings_e { + HKEY key; + int i; +#ifdef MOD_PERSO + int fromFile; + HANDLE hFile; +#endif +}; + +settings_e *enum_settings_start(void) +{ + settings_e *ret; + HKEY key; + +#ifdef MOD_PERSO + if (*sesspath == '\0') { loadPath() ; } + ret = snew(settings_e); + + if( get_param("INIFILE")!=SAVEMODE_DIR ) { + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS) return NULL; + } + if (ret) { + ret->key = key; + ret->i = 0; + ret->fromFile = 0; + ret->hFile = NULL; + } +#else + if (RegOpenKey(HKEY_CURRENT_USER, puttystr, &key) != ERROR_SUCCESS) + return NULL; + + ret = snew(settings_e); + if (ret) { + ret->key = key; + ret->i = 0; + } +#endif + return ret; +} + +bool enum_settings_next(settings_e *e, strbuf *sb) +{ +#ifdef MOD_PERSO + WIN32_FIND_DATA FindFileData; + HANDLE hFile; + char buffer[MAX_PATH] ; + + if( e==NULL ) return false ; + +if( (get_param("INIFILE")==SAVEMODE_DIR) && (!e->fromFile) ) { // On cherche le premier fichier + e->fromFile = 1; + GetCurrentDirectory( (MAX_PATH*2), oldpath); + + if (!SetCurrentDirectory(sesspath)) { return false ; } + hFile = FindFirstFile("*", &FindFileData) ; + + if( !get_param("DIRECTORYBROWSE") ) { + while ( (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) { + if (!FindNextFile(hFile,&FindFileData)) { return false ; } + } + } else { + while( (!strcmp(FindFileData.cFileName,".")) + ||( (!strcmp(sesspath,initialsesspath)) && (!strcmp(FindFileData.cFileName,".."))) ) + if( !FindNextFile(hFile,&FindFileData) ) { return false ; } + } + if (hFile != INVALID_HANDLE_VALUE) { + if( get_param("DIRECTORYBROWSE") + &&(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) { + e->hFile = hFile; + sprintf( buffer, " [%s]",FindFileData.cFileName ) ; + } else { + e->hFile = hFile; + unmungestr( FindFileData.cFileName, buffer, strlen(FindFileData.cFileName)+1 ) ; + /* JK: cut off sessionsuffix */ + char * otherbuf = buffer + strlen(buffer) - strlen(sessionsuffix); + if (strncmp(otherbuf, sessionsuffix, strlen(sessionsuffix)) == 0) { *otherbuf = '\0'; } + } + unescape_registry_key(buffer, sb); + return true ; + } else { + return false ; + } + return false ; +} else if ( (get_param("INIFILE")==SAVEMODE_DIR) && (e->fromFile) ) { // On cherche les suivants + if (FindNextFile(e->hFile,&FindFileData)) { + if( !get_param("DIRECTORYBROWSE") ) { + while ( (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) { + if( !FindNextFile(e->hFile,&FindFileData) ) { return false ; } + } + unmungestr(FindFileData.cFileName, buffer, strlen(FindFileData.cFileName)+1 ); + /* JK: cut off sessionsuffix */ + char * otherbuf = buffer + strlen(buffer) - strlen(sessionsuffix); + if (strncmp(otherbuf, sessionsuffix, strlen(sessionsuffix)) == 0) { *otherbuf = '\0'; } + } else { + while( (!strcmp(FindFileData.cFileName,".")) + ||( (!strcmp(sesspath,"\\Sessions"))&&(!strcmp(FindFileData.cFileName,".."))) ) + //while( (!strcmp(FindFileData.cFileName,".")) + // || (!strcmp(FindFileData.cFileName,"..")) ) + if (!FindNextFile(e->hFile,&FindFileData)) { return false ; } + if( (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) { + sprintf(buffer, " [%s]", FindFileData.cFileName ) ; + } else { + unmungestr(FindFileData.cFileName, buffer, strlen(FindFileData.cFileName)+1 ) ; + /* JK: cut off sessionsuffix */ + char * otherbuf = buffer + strlen(buffer) - strlen(sessionsuffix) ; + if (strncmp(otherbuf, sessionsuffix, strlen(sessionsuffix)) == 0) { *otherbuf = '\0' ; } + } + } + unescape_registry_key(buffer, sb) ; + return true ; + } else { + return false ; + } +} +#endif + size_t regbuf_size = MAX_PATH + 1; + char *regbuf = snewn(regbuf_size, char); + bool success; + + while (1) { + DWORD retd = RegEnumKey(e->key, e->i, regbuf, regbuf_size); + if (retd != ERROR_MORE_DATA) { + success = (retd == ERROR_SUCCESS); + break; + } + sgrowarray(regbuf, regbuf_size, regbuf_size); + } + + if (success) + unescape_registry_key(regbuf, sb); + + e->i++; + sfree(regbuf); + return success; +} + +void enum_settings_finish(settings_e *e) +{ +#ifdef MOD_PERSO + if( e==NULL ) return ; + if(get_param("INIFILE")==SAVEMODE_DIR) { + + RegCloseKey(e->key); + FindClose(e->hFile); + SetCurrentDirectory(oldpath); + } +#else + RegCloseKey(e->key); +#endif + + sfree(e); +} + +static void hostkey_regname(strbuf *sb, const char *hostname, + int port, const char *keytype) +{ + strbuf_catf(sb, "%s@%d:", keytype, port); + escape_registry_key(hostname, sb); +} + +int verify_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + char *otherstr; + strbuf *regname; + int len; + HKEY rkey; + DWORD readlen; + DWORD type; + int ret, compare; + + + len = 1 + strlen(key); + + /* + * Now read a saved key in from the registry and see what it + * says. + */ + regname = strbuf_new(); + hostkey_regname(regname, hostname, port, keytype); + +#ifdef MOD_PERSO + int userMB; + WIN32_FIND_DATA FindFile; + if(*sshkpath == '\0') { loadPath() ; } + char * p ; + if( get_param("INIFILE") == SAVEMODE_DIR ) { + DWORD fileSize; + DWORD bytesRW; + HANDLE hFile; + GetCurrentDirectory( (MAX_PATH*2), oldpath); + /* Now read a saved key in from the registry and see what it says. */ + otherstr = snewn(len, char); + + if (SetCurrentDirectory(sshkpath)) { + p = snewn(3 * strlen(regname->s) + 1 + 16, char); + packstr(regname->s, p); + strcat(p, keysuffix); + + hFile = CreateFile(p, GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); + SetCurrentDirectory(oldpath); + if (hFile != INVALID_HANDLE_VALUE) { /* JK: ok we got it -> read it to otherstr */ + fileSize = GetFileSize(hFile, NULL); + sfree(otherstr); + otherstr = snewn(fileSize+1, char); + ReadFile(hFile, otherstr, fileSize, &bytesRW, NULL); + *(otherstr+fileSize) = '\0'; + compare = strcmp(otherstr, key); + + CloseHandle(hFile); + sfree(otherstr); + strbuf_free(regname); + sfree(p); + + if (compare) { /* key is here, but different */ + return 2; + } + else { /* key is here and match */ + return 0; + } + } else { /* not found as file -> try registry */ + sfree(p); + } + } else { /* JK: there are no hostkeys as files -> try registry -> nothing to do here now */ + if (!createPath(sshkpath)) { errorShow("Unable to verify key and jump into ssh host keys directory ", sshkpath ); } + } + + /* JK: directory/file not found -> try registry */ + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", &rkey) != ERROR_SUCCESS) { + return 1; /* key does not exist in registry */ + } + + readlen = len; + ret = RegQueryValueEx(rkey, regname->s, NULL, &type, (BYTE*)otherstr, &readlen); + + if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA && + !strcmp(keytype, "rsa")) { + /* + * Key didn't exist. If the key type is RSA, we'll try + * another trick, which is to look up the _old_ key format + * under just the hostname and translate that. + */ + char *justhost = regname->s + 1 + strcspn(regname->s, ":"); + char *oldstyle = snewn(len + 10, char); /* safety margin */ + readlen = len; + ret = RegQueryValueEx(rkey, justhost, NULL, &type, (BYTE*)oldstyle, &readlen); + + if (ret == ERROR_SUCCESS && type == REG_SZ) { + /* + * The old format is two old-style bignums separated by + * a slash. An old-style bignum is made of groups of + * four hex digits: digits are ordered in sensible + * (most to least significant) order within each group, + * but groups are ordered in silly (least to most) + * order within the bignum. The new format is two + * ordinary C-format hex numbers (0xABCDEFG...XYZ, with + * A nonzero except in the special case 0x0, which + * doesn't appear anyway in RSA keys) separated by a + * comma. All hex digits are lowercase in both formats. + */ + char *p = otherstr; + char *q = oldstyle; + int i, j; + + for (i = 0; i < 2; i++) { + int ndigits, nwords; + *p++ = '0'; + *p++ = 'x'; + ndigits = strcspn(q, "/"); /* find / or end of string */ + nwords = ndigits / 4; + /* now trim ndigits to remove leading zeros */ + while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1) { + ndigits--; + } + /* now move digits over to new string */ + for (j = 0; j < ndigits; j++) + p[ndigits - 1 - j] = q[j ^ 3]; + p += ndigits; + q += nwords * 4; + if (*q) { + q++; /* eat the slash */ + *p++ = ','; /* add a comma */ + } + *p = '\0'; /* terminate the string */ + } + + /* + * Now _if_ this key matches, we'll enter it in the new + * format. If not, we'll assume something odd went + * wrong, and hyper-cautiously do nothing. + */ + if (!strcmp(otherstr, key)) + RegSetValueEx(rkey, regname->s, 0, REG_SZ, (const BYTE*)otherstr, strlen(otherstr) + 1); + /* JK: session is not saved to file - fixme */ + } + } + + compare = strcmp(otherstr, key); + + if (ret == ERROR_MORE_DATA || (ret == ERROR_SUCCESS && type == REG_SZ && compare)) { + RegCloseKey(rkey); + return 2; /* key is different in registry */ + } + else if (ret != ERROR_SUCCESS || type != REG_SZ) { + RegCloseKey(rkey); + return 1; /* key does not exist in registry */ + } + else if( get_param("INIFILE")==SAVEMODE_DIR ) { /* key matched OK in registry */ + /* JK: matching key found in registry -> warn user, ask what to do */ + p = snewn(256, char); + if( GetAutoStoreSSHKeyFlag() ) { userMB=IDYES ; } + else if( GetReadOnlyFlag() ) { userMB=IDCANCEL ; } + else + userMB = MessageBox(NULL, "Host key is cached but in registry. " + "Do you want to move it to file? \n\n" + "Yes \t-> Move (delete key in registry)\n" + "No \t-> Copy (keep key in registry)\n" + "Cancel \t-> nothing will be done\n", "Security risk", MB_YESNOCANCEL|MB_ICONWARNING); + + if ((userMB == IDYES) || (userMB == IDNO)) { + /* JK: save key to file */ + if ((hFile = FindFirstFile(sshkpath, &FindFile)) == INVALID_HANDLE_VALUE) { + if( !createPath(sshkpath) ){ + errorShow("Unable to create directory for storing ssh server keys", sshkpath); + } + } + FindClose(hFile); + if( !SetCurrentDirectory(sshkpath) ) { + if (!createPath(sshkpath)) { + errorShow("Unable to save key to file and jump into ssh host keys directory ", sshkpath); + } + } + + p = snewn(3*strlen(regname->s) + 1 + 16, char); + packstr(regname->s, p); + strcat(p, keysuffix); + + hFile = CreateFile(p, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + SetCurrentDirectory(oldpath); + + if (hFile == INVALID_HANDLE_VALUE) { + errorShow("Unable to create file (key won't be deleted from registry)", p); + userMB = IDNO; + } + else { + if (!WriteFile(hFile, key, strlen(key), &bytesRW, NULL)) { + errorShow("Unable to save key to file (key won't be deleted from registry)", NULL); + userMB = IDNO; + } + CloseHandle(hFile); + } + } + if (userMB == IDYES) { + /* delete from registry */ + if (RegDeleteValue(rkey, regname->s) != ERROR_SUCCESS) { + errorShow("Unable to delete registry value", regname->s); + } + } + /* JK: else (Cancel) -> nothing to be done right now */ + + RegCloseKey(rkey); + + sfree(otherstr); + strbuf_free(regname); + return 0; + } + } +//return 0; +#endif + + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", + &rkey) != ERROR_SUCCESS) { + strbuf_free(regname); + return 1; /* key does not exist in registry */ + } + + readlen = len; + otherstr = snewn(len, char); + ret = RegQueryValueEx(rkey, regname->s, NULL, + &type, (BYTE *)otherstr, &readlen); + + if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA && + !strcmp(keytype, "rsa")) { + /* + * Key didn't exist. If the key type is RSA, we'll try + * another trick, which is to look up the _old_ key format + * under just the hostname and translate that. + */ + char *justhost = regname->s + 1 + strcspn(regname->s, ":"); + char *oldstyle = snewn(len + 10, char); /* safety margin */ + readlen = len; + ret = RegQueryValueEx(rkey, justhost, NULL, &type, + (BYTE *)oldstyle, &readlen); + + if (ret == ERROR_SUCCESS && type == REG_SZ) { + /* + * The old format is two old-style bignums separated by + * a slash. An old-style bignum is made of groups of + * four hex digits: digits are ordered in sensible + * (most to least significant) order within each group, + * but groups are ordered in silly (least to most) + * order within the bignum. The new format is two + * ordinary C-format hex numbers (0xABCDEFG...XYZ, with + * A nonzero except in the special case 0x0, which + * doesn't appear anyway in RSA keys) separated by a + * comma. All hex digits are lowercase in both formats. + */ + char *p = otherstr; + char *q = oldstyle; + int i, j; + + for (i = 0; i < 2; i++) { + int ndigits, nwords; + *p++ = '0'; + *p++ = 'x'; + ndigits = strcspn(q, "/"); /* find / or end of string */ + nwords = ndigits / 4; + /* now trim ndigits to remove leading zeros */ + while (q[(ndigits - 1) ^ 3] == '0' && ndigits > 1) + ndigits--; + /* now move digits over to new string */ + for (j = 0; j < ndigits; j++) + p[ndigits - 1 - j] = q[j ^ 3]; + p += ndigits; + q += nwords * 4; + if (*q) { + q++; /* eat the slash */ + *p++ = ','; /* add a comma */ + } + *p = '\0'; /* terminate the string */ + } + + /* + * Now _if_ this key matches, we'll enter it in the new + * format. If not, we'll assume something odd went + * wrong, and hyper-cautiously do nothing. + */ + if (!strcmp(otherstr, key)) + RegSetValueEx(rkey, regname->s, 0, REG_SZ, (BYTE *)otherstr, + strlen(otherstr) + 1); + } + + sfree(oldstyle); + } + + RegCloseKey(rkey); + + compare = strcmp(otherstr, key); + + sfree(otherstr); + strbuf_free(regname); + + if (ret == ERROR_MORE_DATA || + (ret == ERROR_SUCCESS && type == REG_SZ && compare)) + return 2; /* key is different in registry */ + else if (ret != ERROR_SUCCESS || type != REG_SZ) + return 1; /* key does not exist 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, verify_host_key will return 0 or 2. + * If we don't have one, it'll return 1. + */ + return verify_host_key(hostname, port, keytype, "") != 1; +} + +void store_host_key(const char *hostname, int port, + const char *keytype, const char *key) +{ + strbuf *regname; + HKEY rkey; + + regname = strbuf_new(); + hostkey_regname(regname, hostname, port, keytype); +#ifdef MOD_PERSO + if( get_param("INIFILE")==SAVEMODE_DIR ) { + if(*sshkpath == '\0') { loadPath() ; } + GetCurrentDirectory( (MAX_PATH*2), oldpath); + + WIN32_FIND_DATA FindFile; + HANDLE hFile = NULL; + char* p = NULL; + DWORD bytesWritten; + + if( GetReadOnlyFlag() ) return ; + /* JK: save hostkey to file in dir */ + if ((hFile = FindFirstFile(sshkpath, &FindFile)) == INVALID_HANDLE_VALUE) { + if( !createPath(sshkpath) ) { errorShow("Unable to create directory for storing ssh host keys", sshkpath); } + } + FindClose(hFile); + if( !SetCurrentDirectory(sshkpath) ) { errorShow("Unable to jump into ssh host keys directory", sshkpath); } + + p = snewn(3*strlen(regname->s) + 1, char); + packstr(regname->s, p); + strcat(p, keysuffix); + + hFile = CreateFile(p, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if (hFile == INVALID_HANDLE_VALUE) { + errorShow("Unable to create file", p); + } + else { + if (!WriteFile(hFile, key, strlen(key), &bytesWritten, NULL)) { + errorShow("Unable to save key to file", NULL); + } + CloseHandle(hFile); + } + SetCurrentDirectory(oldpath); + + sfree(p); + strbuf_free(regname); + return ; + } +#endif + if (RegCreateKey(HKEY_CURRENT_USER, PUTTY_REG_POS "\\SshHostKeys", + &rkey) == ERROR_SUCCESS) { + RegSetValueEx(rkey, regname->s, 0, REG_SZ, + (BYTE *)key, strlen(key) + 1); + RegCloseKey(rkey); + } /* else key does not exist in registry */ + + strbuf_free(regname); +} + +/* + * 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) +{ + HKEY rkey; + 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. + */ + { + char regpath[MAX_PATH + 1]; + DWORD type, size = sizeof(regpath); + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &rkey) == + ERROR_SUCCESS) { + int ret = RegQueryValueEx(rkey, "RandSeedFile", + 0, &type, (BYTE *)regpath, &size); + RegCloseKey(rkey); + if (ret == ERROR_SUCCESS && type == REG_SZ && + try_random_seed(regpath, action, &rethandle)) + return rethandle; + } + } + + /* + * Next, try the user's local Application Data directory, + * followed by their non-local one. This is found using the + * SHGetFolderPath function, which won't be present on all + * versions of Windows. + */ + if (!tried_shgetfolderpath) { + /* This is likely only to bear fruit on systems with IE5+ + * installed, or WinMe/2K+. There is some faffing with + * SHFOLDER.DLL we could do to try to find an equivalent + * on older versions of Windows if we cared enough. + * However, the invocation below requires IE5+ anyway, + * so stuff that. */ + shell32_module = load_system32_dll("shell32.dll"); + GET_WINDOWS_FUNCTION(shell32_module, SHGetFolderPathA); + tried_shgetfolderpath = true; + } + if (p_SHGetFolderPathA) { + 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) +{ +#ifdef MOD_PERSO + if( GetReadOnlyFlag() ) return ; +#endif + HANDLE seedf = access_random_seed(OPEN_W); + + if (seedf != INVALID_HANDLE_VALUE) { + DWORD lenwritten; + + WriteFile(seedf, data, len, &lenwritten, NULL); + CloseHandle(seedf); + } +} + +/* + * Internal function supporting the jump list registry code. All the + * functions to add, remove and read the list have substantially + * similar content, so this is a generalisation of all of them which + * transforms the list in the registry by prepending 'add' (if + * non-null), removing 'rem' from what's left (if non-null), and + * returning the resulting concatenated list of strings in 'out' (if + * non-null). + */ +static int transform_jumplist_registry + (const char *add, const char *rem, char **out) +{ + int ret; + HKEY pjumplist_key; + DWORD type; + DWORD value_length; + char *old_value, *new_value; + char *piterator_old, *piterator_new, *piterator_tmp; + +#ifdef MOD_PERSO + old_value = NULL ; + char RecentSessionsFile[2*MAX_PATH] ; + if(*jumplistpath == '\0') { loadPath() ; } +if( get_param("INIFILE")==SAVEMODE_DIR ) { + ret = ERROR_SUCCESS ; + if( !existdirectory(jumplistpath) ) { + if( !createPath(jumplistpath) ) { errorShow("Unable to create directory for storing jump list", jumplistpath); } + } + sprintf(RecentSessionsFile,"%s/RecentSessions",jumplistpath); + if( !existfile(RecentSessionsFile) ) { + ret = ERROR_FILE_NOT_FOUND; + value_length=200 ; + old_value = snewn(value_length, char); + *old_value = '\0'; + *(old_value + 1) = '\0'; + } else { + FILE *fp ; + if( (fp = fopen( RecentSessionsFile, "r" )) != NULL ) { + value_length = filesize( RecentSessionsFile ) ; + old_value = snewn(value_length+2, char); + fread (old_value, value_length, 1, fp ) ; + old_value[value_length]='\0'; + old_value[value_length+1]='\0'; + fclose(fp) ; + } else { ret = JUMPLISTREG_ERROR_VALUEREAD_FAILURE ; } + } +} +else { +#endif + + ret = RegCreateKeyEx(HKEY_CURRENT_USER, reg_jumplist_key, 0, NULL, + REG_OPTION_NON_VOLATILE, (KEY_READ | KEY_WRITE), NULL, + &pjumplist_key, NULL); + if (ret != ERROR_SUCCESS) { + return JUMPLISTREG_ERROR_KEYOPENCREATE_FAILURE; + } + + /* Get current list of saved sessions in the registry. */ + value_length = 200; + old_value = snewn(value_length, char); + ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type, + (BYTE *)old_value, &value_length); + /* When the passed buffer is too small, ERROR_MORE_DATA is + * returned and the required size is returned in the length + * argument. */ + if (ret == ERROR_MORE_DATA) { + sfree(old_value); + old_value = snewn(value_length, char); + ret = RegQueryValueEx(pjumplist_key, reg_jumplist_value, NULL, &type, + (BYTE *)old_value, &value_length); + } + + if (ret == ERROR_FILE_NOT_FOUND) { + /* Value doesn't exist yet. Start from an empty value. */ + *old_value = '\0'; + *(old_value + 1) = '\0'; + } else if (ret != ERROR_SUCCESS) { + /* Some non-recoverable error occurred. */ + sfree(old_value); + RegCloseKey(pjumplist_key); + return JUMPLISTREG_ERROR_VALUEREAD_FAILURE; + } else if (type != REG_MULTI_SZ) { + /* The value present in the registry has the wrong type: we + * try to delete it and start from an empty value. */ + ret = RegDeleteValue(pjumplist_key, reg_jumplist_value); + if (ret != ERROR_SUCCESS) { + sfree(old_value); + RegCloseKey(pjumplist_key); + return JUMPLISTREG_ERROR_VALUEREAD_FAILURE; + } + + *old_value = '\0'; + *(old_value + 1) = '\0'; + } +#ifdef MOD_PERSO +} +#endif + + /* Check validity of registry data: REG_MULTI_SZ value must end + * with \0\0. */ + piterator_tmp = old_value; + while (((piterator_tmp - old_value) < (value_length - 1)) && + !(*piterator_tmp == '\0' && *(piterator_tmp+1) == '\0')) { + ++piterator_tmp; + } + + if ((piterator_tmp - old_value) >= (value_length-1)) { + /* Invalid value. Start from an empty value. */ + *old_value = '\0'; + *(old_value + 1) = '\0'; + } + + /* + * Modify the list, if we're modifying. + */ + if (add || rem) { + /* Walk through the existing list and construct the new list of + * saved sessions. */ + new_value = snewn(value_length + (add ? strlen(add) + 1 : 0), char); + piterator_new = new_value; + piterator_old = old_value; + + /* First add the new item to the beginning of the list. */ + if (add) { + strcpy(piterator_new, add); + piterator_new += strlen(piterator_new) + 1; + } + /* Now add the existing list, taking care to leave out the removed + * item, if it was already in the existing list. */ + while (*piterator_old != '\0') { + if (!rem || strcmp(piterator_old, rem) != 0) { + /* Check if this is a valid session, otherwise don't add. */ + settings_r *psettings_tmp = open_settings_r(piterator_old); + if (psettings_tmp != NULL) { + close_settings_r(psettings_tmp); + strcpy(piterator_new, piterator_old); + piterator_new += strlen(piterator_new) + 1; + } + } + piterator_old += strlen(piterator_old) + 1; + } + *piterator_new = '\0'; + ++piterator_new; + + /* Save the new list to the registry. */ +#ifdef MOD_PERSO +if( get_param("INIFILE")==SAVEMODE_DIR ) { + FILE *fp ; + if( !GetReadOnlyFlag() ) + if( (fp = fopen( RecentSessionsFile, "w" )) != NULL ) { + fwrite ( new_value, piterator_new - new_value, 1, fp ) ; + fclose(fp) ; + } +} else +#endif + ret = RegSetValueEx(pjumplist_key, reg_jumplist_value, 0, REG_MULTI_SZ, + (BYTE *)new_value, piterator_new - new_value); + + sfree(old_value); + old_value = new_value; + } else + ret = ERROR_SUCCESS; + + /* + * Either return or free the result. + */ + if (out && ret == ERROR_SUCCESS) + *out = old_value; + else + sfree(old_value); + + /* Clean up and return. */ +#ifdef MOD_PERSO +if( get_param("INIFILE")!=SAVEMODE_DIR ) { + RegCloseKey(pjumplist_key); +} +#else + RegCloseKey(pjumplist_key); +#endif + + if (ret != ERROR_SUCCESS) { + return JUMPLISTREG_ERROR_VALUEWRITE_FAILURE; + } else { + return JUMPLISTREG_OK; + } +} + +/* Adds a new entry to the jumplist entries in the registry. */ +int add_to_jumplist_registry(const char *item) +{ + return transform_jumplist_registry(item, item, NULL); +} + +/* Removes an item from the jumplist entries in the registry. */ +int remove_from_jumplist_registry(const char *item) +{ + return transform_jumplist_registry(NULL, item, NULL); +} + +/* Returns the jumplist entries from the registry. Caller must free + * the returned pointer. */ +char *get_jumplist_registry_entries (void) +{ + char *list_value; + + if (transform_jumplist_registry(NULL,NULL,&list_value) != JUMPLISTREG_OK) { + list_value = snewn(2, char); + *list_value = '\0'; + *(list_value + 1) = '\0'; + } + return list_value; +} + +/* + * Recursively delete a registry key and everything under it. + */ +static void registry_recursive_remove(HKEY key) +{ + DWORD i; + char name[MAX_PATH + 1]; + HKEY subkey; + + i = 0; + while (RegEnumKey(key, i, name, sizeof(name)) == ERROR_SUCCESS) { + if (RegOpenKey(key, name, &subkey) == ERROR_SUCCESS) { + registry_recursive_remove(subkey); + RegCloseKey(subkey); + } + RegDeleteKey(key, name); + } +} + +void cleanup_all(void) +{ + HKEY key; + int ret; + char name[MAX_PATH + 1]; + + /* ------------------------------------------------------------ + * Wipe out the random seed file, in all of its possible + * locations. + */ + access_random_seed(DEL); + + /* ------------------------------------------------------------ + * Ask Windows to delete any jump list information associated + * with this installation of PuTTY. + */ + clear_jumplist(); + + /* ------------------------------------------------------------ + * Destroy all registry information associated with PuTTY. + */ + + /* + * Open the main PuTTY registry key and remove everything in it. + */ + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_POS, &key) == + ERROR_SUCCESS) { + registry_recursive_remove(key); + RegCloseKey(key); + } + /* + * Now open the parent key and remove the PuTTY main key. Once + * we've done that, see if the parent key has any other + * children. + */ + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_PARENT, + &key) == ERROR_SUCCESS) { + RegDeleteKey(key, PUTTY_REG_PARENT_CHILD); + ret = RegEnumKey(key, 0, name, sizeof(name)); + RegCloseKey(key); + /* + * If the parent key had no other children, we must delete + * it in its turn. That means opening the _grandparent_ + * key. + */ + if (ret != ERROR_SUCCESS) { + if (RegOpenKey(HKEY_CURRENT_USER, PUTTY_REG_GPARENT, + &key) == ERROR_SUCCESS) { + RegDeleteKey(key, PUTTY_REG_GPARENT_CHILD); + RegCloseKey(key); + } + } + } + /* + * Now we're done. + */ +} diff --git a/0.77_My_PuTTY/windows/winstuff.h b/0.77_My_PuTTY/windows/winstuff.h new file mode 100644 index 0000000..a6e8021 --- /dev/null +++ b/0.77_My_PuTTY/windows/winstuff.h @@ -0,0 +1,740 @@ +/* + * winstuff.h: Windows-specific inter-module stuff. + */ + +#ifndef PUTTY_WINSTUFF_H +#define PUTTY_WINSTUFF_H + +#ifndef AUTO_WINSOCK +#include +#endif + +#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 - including the one + * used for official PuTTY builds as of 2015-09-28 - don't provide + * stdint.h at all, but do (non-standardly) define uintptr_t in + * stddef.h. So here we try to make sure _some_ standard header is + * included which defines uintptr_t. */ +#include +#if !defined _MSC_VER || _MSC_VER >= 1600 || defined __clang__ +#include +#endif + +#include "defs.h" +#include "marshal.h" + +#include "tree234.h" + +#include "winhelp.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 + +struct Filename { + char *path; +}; +static inline FILE *f_open(const Filename *filename, const char *mode, + bool isprivate) +{ + return fopen(filename->path, mode); +} + +struct FontSpec { + char *name; + bool isbold; + int height; + int charset; +}; +struct FontSpec *fontspec_new( + const char *name, bool bold, int height, int charset); + +#ifndef CLEARTYPE_QUALITY +#define CLEARTYPE_QUALITY 5 +#endif +#define FONT_QUALITY(fq) ( \ + (fq) == FQ_DEFAULT ? DEFAULT_QUALITY : \ + (fq) == FQ_ANTIALIASED ? ANTIALIASED_QUALITY : \ + (fq) == FQ_NONANTIALIASED ? NONANTIALIASED_QUALITY : \ + CLEARTYPE_QUALITY) + +#define PLATFORM_IS_UTF16 /* enable UTF-16 processing when exchanging + * wchar_t strings with environment */ + +#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 + +#define BOXFLAGS DLGWINDOWEXTRA +#define BOXRESULT (DLGWINDOWEXTRA + sizeof(LONG_PTR)) +#define DF_END 0x0001 + +#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 + +#define BROKEN_PIPE_ERROR_CODE ERROR_BROKEN_PIPE /* used in sshshare.c */ + +/* + * Dynamically linked functions. These come in two flavours: + * + * - GET_WINDOWS_FUNCTION does not expose "name" to the preprocessor, + * so will always dynamically link against exactly what is specified + * in "name". If you're not sure, use this one. + * + * - GET_WINDOWS_FUNCTION_PP allows "name" to be redirected via + * preprocessor definitions like "#define foo bar"; this is principally + * intended for the ANSI/Unicode DoSomething/DoSomethingA/DoSomethingW. + * If your function has an argument of type "LPTSTR" or similar, this + * is the variant to use. + * (However, it can't always be used, as it trips over more complicated + * macro trickery such as the WspiapiGetAddrInfo wrapper for getaddrinfo.) + * + * (DECL_WINDOWS_FUNCTION works with both these variants.) + */ +#define DECL_WINDOWS_FUNCTION(linkage, rettype, name, params) \ + typedef rettype (WINAPI *t_##name) params; \ + linkage t_##name p_##name +/* 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 STR1(x) #x +#define STR(x) STR1(x) +#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) + +#ifdef MOD_PERSO +#ifdef FLJ +#define PUTTY_REG_POS_SAVE "Software\\9bis.com\\PuTTY_save" +#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" +#else +#define PUTTY_REG_POS "Software\\9bis.com\\KiTTY" +#define PUTTY_REG_POS_SAVE "Software\\9bis.com\\KiTTY_save" +#define PUTTY_REG_PARENT "Software\\9bis.com" +#define PUTTY_REG_PARENT_CHILD "KiTTY" +#define PUTTY_REG_GPARENT "Software" +#define PUTTY_REG_GPARENT_CHILD "9bis.com" +#endif +#else +#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" +#endif + +/* 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 + +#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 winhelp.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 windlg.c, so that the vtable definition in + * window.c can refer to them. + */ +int win_seat_verify_ssh_host_key( + Seat *seat, const char *host, int port, const char *keytype, + char *keystr, const char *keydisp, char **key_fingerprints, + void (*callback)(void *ctx, int result), void *ctx); +int win_seat_confirm_weak_crypto_primitive( + Seat *seat, const char *algtype, const char *algname, + void (*callback)(void *ctx, int result), void *ctx); +int win_seat_confirm_weak_cached_hostkey( + Seat *seat, const char *algname, const char *betteralgs, + void (*callback)(void *ctx, int result), void *ctx); + +/* + * Windows-specific clipboard helper function shared with windlg.c, + * which takes the data string in the system code page instead of + * Unicode. + */ +void write_aclip(int clipboard, char *, int, bool); + +#define WM_NETEVENT (WM_APP + 5) + +/* + * On Windows, we send MA_2CLK as the only event marking the second + * press of a mouse button. Compare unix.h. + */ +#define MULTICLICK_ONLY_EVENT 1 + +/* + * On Windows, data written to the clipboard must be NUL-terminated. + */ +#define SELECTION_NUL_TERMINATED 1 + +/* + * On Windows, copying to the clipboard terminates lines with CRLF. + */ +#define SEL_NL { 13, 10 } + +/* + * sk_getxdmdata() does not exist under Windows (not that I + * couldn't write it if I wanted to, but I haven't bothered), so + * it's a macro which always returns NULL. With any luck this will + * cause the compiler to notice it can optimise away the + * implementation of XDM-AUTHORIZATION-1 in x11fwd.c :-) + */ +#define sk_getxdmdata(socket, lenp) (NULL) + +/* + * File-selector filter strings used in the config box. On Windows, + * these strings are of exactly the type needed to go in + * `lpstrFilter' in an OPENFILENAME structure. + */ +#define FILTER_KEY_FILES ("PuTTY Private Key Files (*.ppk)\0*.ppk\0" \ + "All Files (*.*)\0*\0\0\0") +#define FILTER_WAVE_FILES ("Wave Files (*.wav)\0*.WAV\0" \ + "All Files (*.*)\0*\0\0\0") +#define FILTER_DYNLIB_FILES ("Dynamic Library Files (*.dll)\0*.dll\0" \ + "All Files (*.*)\0*\0\0\0") + +#if (defined MOD_BACKGROUNDIMAGE) && (!defined FLJ) +#define FILTER_IMAGE_FILES ("Image Files\0*.BMP;*.JPG;*.JPEG\0" \ + "BMP Files (*.bmp)\0*.BMP\0" \ + "JPEG Files (*.jpg)\0*.JPG;*.JPEG\0" \ + "All Files (*.*)\0*\0\0\0") +#endif +#ifdef MOD_PERSO +#define FILTER_ICON_FILES ("Icon Files (*.ico)\0*.ICO\0" \ + "All Files (*.*)\0*\0\0\0") +#endif +#ifdef MOD_ZMODEM +#define FILTER_EXE_FILES ("Executable Files (*.exe)\0*.EXE\0" \ + "All Files (*.*)\0*\0\0\0") +#endif + +/* + * Exports from winnet.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 winnet.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); + +/* + * winnet.c dynamically loads WinSock 2 or WinSock 1 depending on + * what it can get, which means any WinSock routines used outside + * that module must be exported from it as function pointers. So + * here they are. + */ +DECL_WINDOWS_FUNCTION(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 winnet.c, and + * called by winnet.c to turn on or off WSA*Select for a given socket. + */ +const char *do_select(SOCKET skt, bool enable); + +/* + * Exports from winselgui.c and winselcli.c, each of which provides an + * implementation of do_select. + */ +void winselgui_set_hwnd(HWND hwnd); +void winselgui_clear_hwnd(void); + +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, + Plug *plug, bool overlapped); /* winhsock */ +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 winnpc.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 winctrls.c. + */ + +struct ctlpos { + HWND hwnd; + WPARAM font; + int dlu4inpix; + int ypos, width; + int xoff; + int boxystart, boxid; + char *boxtext; +}; +void init_common_controls(void); /* also does some DLL-loading */ + +/* + * Exports from winutils.c. + */ +typedef struct filereq_tag filereq; /* cwd for file requester */ +bool request_file(filereq *state, OPENFILENAME *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, DWORD helpctxid); +void MakeDlgItemBorderless(HWND parent, int id); +char *GetDlgItemText_alloc(HWND hwnd, int id); +void split_into_argv(char *, int *, char ***, char ***); + +/* + * Private structure for prefslist state. Only in the header file + * so that we can delegate allocation to callers. + */ +struct prefslist { + int listid, upbid, dnbid; + int srcitem; + int dummyitem; + 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 */ + union control *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 winctrls.c. + */ +void ctlposinit(struct ctlpos *cp, HWND hwnd, + int leftborder, int rightborder, int topborder); +HWND doctl(struct ctlpos *cp, RECT r, + char *wclass, int wstyle, int exstyle, char *wtext, int wid); +void bartitle(struct ctlpos *cp, char *name, int id); +void beginbox(struct ctlpos *cp, char *name, int idbox); +void endbox(struct ctlpos *cp); +void editboxfw(struct ctlpos *cp, bool password, char *text, + int staticid, int editid); +void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...); +void bareradioline(struct ctlpos *cp, int nacross, ...); +void radiobig(struct ctlpos *cp, char *text, int id, ...); +void checkbox(struct ctlpos *cp, char *text, int id); +void statictext(struct ctlpos *cp, char *text, int lines, int id); +void staticbtn(struct ctlpos *cp, char *stext, int sid, + char *btext, int bid); +void static2btn(struct ctlpos *cp, char *stext, int sid, + char *btext1, int bid1, char *btext2, int bid2); +void staticedit(struct ctlpos *cp, char *stext, + int sid, int eid, int percentedit); +void staticddl(struct ctlpos *cp, char *stext, + int sid, int lid, int percentlist); +void combobox(struct ctlpos *cp, char *text, int staticid, int listid); +void staticpassedit(struct ctlpos *cp, char *stext, + int sid, int eid, int percentedit); +void bigeditctrl(struct ctlpos *cp, char *stext, + int sid, int eid, int lines); +void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id); +void editbutton(struct ctlpos *cp, char *stext, int sid, + int eid, char *btext, int bid); +void sesssaver(struct ctlpos *cp, char *text, + int staticid, int editid, int listid, ...); +void envsetter(struct ctlpos *cp, char *stext, int sid, + char *e1stext, int e1sid, int e1id, + char *e2stext, int e2sid, int e2id, + int listid, char *b1text, int b1id, char *b2text, int b2id); +void charclass(struct ctlpos *cp, char *stext, int sid, int listid, + char *btext, int bid, int eid, char *s2text, int s2id); +void colouredit(struct ctlpos *cp, char *stext, int sid, int listid, + char *btext, int bid, ...); +void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines, + char *stext, int sid, int listid, int upbid, int dnbid); +int handle_prefslist(struct prefslist *hdl, + int *array, int maxmemb, + bool is_dlmsg, HWND hwnd, + WPARAM wParam, LPARAM lParam); +void progressbar(struct ctlpos *cp, int id); +void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid, + char *e1stext, int e1sid, int e1id, + char *e2stext, int e2sid, int e2id, + char *btext, int bid, + char *r1text, int r1id, char *r2text, int r2id); + +void dlg_auto_set_fixed_pitch_flag(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 `union control' in the + * portable-dialog interface. + */ +struct winctrl { + union control *ctrl; + /* + * The control may have several components at the Windows + * level, with different dialog IDs. To avoid needing N + * separate platformsidectrl structures (which could be stored + * separately in a tree234 so that lookup by ID worked), we + * impose the constraint that those IDs must be in a contiguous + * block. + */ + int base_id; + int num_ids; + /* + * 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 `union control' or by + * dialog ID. + */ +struct winctrls { + tree234 *byctrl, *byid; +}; +struct controlset; +struct controlbox; + +void winctrl_init(struct winctrls *); +void winctrl_cleanup(struct winctrls *); +void winctrl_add(struct winctrls *, struct winctrl *); +void winctrl_remove(struct winctrls *, struct winctrl *); +struct winctrl *winctrl_findbyctrl(struct winctrls *, union control *); +struct winctrl *winctrl_findbyid(struct winctrls *, int); +struct winctrl *winctrl_findbyindex(struct winctrls *, int); +void winctrl_layout(struct dlgparam *dp, struct winctrls *wc, + struct ctlpos *cp, struct controlset *s, int *id); +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 wincfg.c. + */ +void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, + bool midsession, int protocol); + +/* + * Exports from windlg.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 winmisc.c. + */ +extern DWORD osMajorVersion, osMinorVersion, osPlatformId; +void init_winver(void); +void dll_hijacking_protection(void); +HMODULE load_system32_dll(const char *libname); +const char *win_strerror(int error); +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. + */ +struct unicode_data; +void init_ucs(Conf *, struct unicode_data *); + +/* + * Exports from winhandl.c. + */ +#define HANDLE_FLAG_OVERLAPPED 1 +#define HANDLE_FLAG_IGNOREEOF 2 +#define HANDLE_FLAG_UNITBUFFER 4 +struct handle; +typedef 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); +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); +HANDLE *handle_get_events(int *nevents); +void handle_free(struct handle *h); +void handle_got_event(HANDLE event); +void handle_unthrottle(struct handle *h, size_t backlog); +size_t handle_backlog(struct handle *h); +void *handle_get_privdata(struct handle *h); +struct handle *handle_add_foreign_event(HANDLE event, + void (*callback)(void *), void *ctx); +/* 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 winpgntc.c. + */ +char *agent_named_pipe_name(void); + +/* + * Exports from winser.c. + */ +extern const struct BackendVtable serial_backend; + +/* + * Exports from winjump.c. + */ +#define JUMPLIST_SUPPORTED /* suppress #defines in putty.h */ +void add_session_to_jumplist(const char * const sessionname); +void remove_session_from_jumplist(const char * const sessionname); +void clear_jumplist(void); +bool set_explicit_app_user_model_id(void); + +/* + * Exports from winnoise.c. + */ +bool win_read_random(void *buf, unsigned wanted); /* returns true on success */ + +/* + * Extra functions in winstore.c over and above the interface in + * storage.h. + * + * These functions manipulate the Registry section which mirrors the + * current Windows 7 jump list. (Because the real jump list storage is + * write-only, we need to keep another copy of whatever we put in it, + * so that we can put in a slightly modified version the next time.) + */ + +/* Adds a saved session to the registry jump list mirror. 'item' is a + * string naming a saved session. */ +int add_to_jumplist_registry(const char *item); + +/* Removes an item from the registry jump list mirror. */ +int remove_from_jumplist_registry(const char *item); + +/* Returns the current jump list entries from the registry. Caller + * must free the returned pointer, which points to a contiguous + * sequence of NUL-terminated strings in memory, terminated with an + * empty one. */ +char *get_jumplist_registry_entries(void); + +/* + * 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 winmisc.c */ +char *registry_get_string(HKEY root, const char *path, const char *leaf); + +/* In wincliloop.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); + +#endif diff --git a/0.77_My_PuTTY/windows/wintime.c b/0.77_My_PuTTY/windows/wintime.c new file mode 100644 index 0000000..5fa3b0d --- /dev/null +++ b/0.77_My_PuTTY/windows/wintime.c @@ -0,0 +1,26 @@ +/* + * wintime.c - Avoid trouble with time() returning (time_t)-1 on Windows. + */ + +#include "putty.h" +#include + +struct tm ltime(void) +{ + SYSTEMTIME st; + struct tm tm; + + 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/0.77_My_PuTTY/windows/winucs.c b/0.77_My_PuTTY/windows/winucs.c new file mode 100644 index 0000000..9a6265e --- /dev/null +++ b/0.77_My_PuTTY/windows/winucs.c @@ -0,0 +1,1213 @@ +#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); + +void init_ucs(Conf *conf, struct unicode_data *ucsdata) +{ + int i, j; + 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['_'] = ' '; + + /* Generate UCS ->line page table. */ + if (ucsdata->uni_tbl) { + for (i = 0; i < 256; i++) + if (ucsdata->uni_tbl[i]) + sfree(ucsdata->uni_tbl[i]); + sfree(ucsdata->uni_tbl); + ucsdata->uni_tbl = 0; + } + if (!used_dtf) { + for (i = 0; i < 256; i++) { + if (DIRECT_CHAR(ucsdata->unitab_line[i])) + continue; + if (DIRECT_FONT(ucsdata->unitab_line[i])) + continue; + if (!ucsdata->uni_tbl) { + ucsdata->uni_tbl = snewn(256, char *); + memset(ucsdata->uni_tbl, 0, 256 * sizeof(char *)); + } + j = ((ucsdata->unitab_line[i] >> 8) & 0xFF); + if (!ucsdata->uni_tbl[j]) { + ucsdata->uni_tbl[j] = snewn(256, char); + memset(ucsdata->uni_tbl[j], 0, 256 * sizeof(char)); + } + ucsdata->uni_tbl[j][ucsdata->unitab_line[i] & 0xFF] = i; + } + } + + /* 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]); + } +} + +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]; +} + +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(first), toupper(second), 1); + if (nc == -1) + nc = check_compose_internal(toupper(second), toupper(first), 1); + } + return nc; +} + +int check_compose(int first, int second) +{ + return check_compose_internal(first, second, 0); +} + +int decode_codepage(char *cp_name) +{ + 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(*s) && *s != ':') + s++; + while (*d && !isalnum(*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, + struct unicode_data *ucsdata) +{ + char *p; + int i; + if (ucsdata && codepage == ucsdata->line_codepage && ucsdata->uni_tbl) { + /* Do this by array lookup if we can. */ + if (wclen < 0) { + for (wclen = 0; wcstr[wclen++] ;); /* will include the NUL */ + } + for (p = mbstr, i = 0; i < wclen; i++) { + wchar_t ch = wcstr[i]; + int by; + char *p1; + + #define WRITECH(chr) do \ + { \ + assert(p - mbstr < mblen); \ + *p++ = (char)(chr); \ + } while (0) + + if (ucsdata->uni_tbl && + (p1 = ucsdata->uni_tbl[(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; + return WideCharToMultiByte(codepage, flags, wcstr, wclen, + mbstr, mblen, defchr, &defused); + } +} + +int mb_to_wc(int codepage, int flags, const char *mbstr, int mblen, + wchar_t *wcstr, int wclen) +{ + return MultiByteToWideChar(codepage, flags, mbstr, mblen, wcstr, wclen); +} + +bool is_dbcs_leadbyte(int codepage, char byte) +{ + return IsDBCSLeadByteEx(codepage, byte); +} diff --git a/0.77_My_PuTTY/windows/winutils.c b/0.77_My_PuTTY/windows/winutils.c new file mode 100644 index 0000000..1e04a06 --- /dev/null +++ b/0.77_My_PuTTY/windows/winutils.c @@ -0,0 +1,788 @@ +/* + * winutils.c: miscellaneous Windows utilities for GUI apps + */ + +#include +#include +#include + +#include "putty.h" +#include "misc.h" + +#ifdef TESTMODE +/* Definitions to allow this module to be compiled standalone for testing + * split_into_argv(). */ +#define smalloc malloc +#define srealloc realloc +#define sfree free +#endif + +/* + * 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. + */ + +struct filereq_tag { + TCHAR cwd[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; +} + +filereq *filereq_new(void) +{ + filereq *ret = snew(filereq); + ret->cwd[0] = '\0'; + return ret; +} + +void filereq_free(filereq *state) +{ + sfree(state); +} + +/* + * Message box with optional context help. + */ + +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, DWORD helpctxid) +{ + MSGBOXPARAMS 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.hwndOwner = message_box_owner = owner; + mbox.lpfnMsgBoxCallback = &message_box_help_callback; + mbox.dwLanguageId = LANG_NEUTRAL; + mbox.lpszText = text; + mbox.lpszCaption = caption; + mbox.dwContextHelpId = helpctxid; + mbox.dwStyle = style; + if (helpctxid != 0 && has_help()) mbox.dwStyle |= MB_HELP; + return MessageBoxIndirect(&mbox); +} + +/* + * Display the fingerprints of the PGP Master Keys to the user. + */ +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, + HELPCTXID(pgp_fingerprints)); +} + +/* + * Helper function to remove the border around a dialog item such as + * a read-only edit control. + */ +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); +} + +/* + * Handy wrapper around GetDlgItemText which doesn't make you invent + * an arbitrary length limit on the output string. Returned string is + * dynamically allocated; caller must free. + */ +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; +} + +/* + * 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. + */ + +/* + * 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. + * + * 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. + * + * 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. + */ + +/* + * We select between two behaviours depending on the version of Visual + * Studio (see large comment below). I don't know exactly when the bug + * fix happened, but I know that VS7 had the odd mod-3 behaviour. + */ +#if _MSC_VER < 1400 +#define MOD3 1 +#else +#define MOD3 0 +#endif + +void split_into_argv(char *cmdline, 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 && isspace(*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. + */ + outputline = snewn(1+strlen(cmdline), char); + outputargv = snewn(strlen(cmdline)+1 / 2, char *); + outputargstart = snewn(strlen(cmdline)+1 / 2, char *); + + p = cmdline; q = outputline; outputargc = 0; + + while (*p) { + bool quote; + + /* Skip whitespace searching for start of argument. */ + while (*p && isspace(*p)) p++; + if (!*p) break; + + /* 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 && isspace(*p)) + break; /* argument is finished */ + + if (*p == '"' || *p == '\\') { + /* + * 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); +} + +#ifdef TESTMODE + +const struct argv_test { + const char *cmdline; + const char *argv[10]; +} 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 */ +}; + +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) { + char *str = malloc(20 + strlen(argv[0]) + strlen(argv[2])); + char *p, *q; + + q = str + sprintf(str, "%s -splat ", argv[0]); + printf(" {\""); + for (p = argv[2]; *p; p++, q++) { + switch (*p) { + case '/': printf("\\\\"); *q = '\\'; break; + case '\'': printf("\\\""); *q = '"'; break; + case '_': printf(" "); *q = ' '; break; + default: putchar(*p); *q = *p; break; + } + } + *p = '\0'; + printf("\", {"); + fflush(stdout); + + system(str); + + printf("}},\n"); + + 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; + } + + 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. + */ + + for (i = 0; i < lenof(argv_tests); i++) { + int ac; + char **av; + + split_into_argv(argv_tests[i].cmdline, &ac, &av); + + 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]); + } +#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); + if (argv_tests[i].argv[j]) + printf("failed test %d (|%s|): %d args returned, should be more\n", + i, argv_tests[i].cmdline, ac); + } + + return 0; +} + +#endif diff --git a/0.77_My_PuTTY/windows/winx11.c b/0.77_My_PuTTY/windows/winx11.c new file mode 100644 index 0000000..e85fe4f --- /dev/null +++ b/0.77_My_PuTTY/windows/winx11.c @@ -0,0 +1,19 @@ +/* + * winx11.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) +{ + char *xauthpath = conf_get_filename(conf, CONF_xauthfile)->path; + if (xauthpath[0]) + x11_get_auth_from_authfile(disp, xauthpath); +} + +const bool platform_uses_x11_unix_by_default = false; diff --git a/0.77_My_PuTTY/x11fwd.c b/0.77_My_PuTTY/x11fwd.c new file mode 100644 index 0000000..579a064 --- /dev/null +++ b/0.77_My_PuTTY/x11fwd.c @@ -0,0 +1,1201 @@ +/* + * Platform-independent bits of X11 forwarding. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshchan.h" +#include "tree234.h" + +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); +} + +const char *const x11_authnames[] = { + "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1" +}; + +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); + } +} + +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); +} + +#define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */ + +static const char *x11_verify(unsigned long peer_ip, int peer_port, + tree234 *authtree, char *proto, + unsigned char *data, int dlen, + struct X11FakeAuth **auth_ret) +{ + struct X11FakeAuth match_dummy; /* for passing to find234 */ + struct X11FakeAuth *auth; + + /* + * First, do a lookup in our tree to find the only authorisation + * record that _might_ match. + */ + if (!strcmp(proto, x11_authnames[X11_MIT])) { + /* + * Just look up the whole cookie that was presented to us, + * which x11_authcmp will compare against the cookies we + * currently believe in. + */ + match_dummy.proto = X11_MIT; + match_dummy.datalen = dlen; + match_dummy.data = data; + } else if (!strcmp(proto, x11_authnames[X11_XDM])) { + /* + * Look up the first cipher block, against the stored first + * cipher blocks for the XDM-AUTHORIZATION-1 cookies we + * currently know. (See comment in x11_invent_fake_auth.) + */ + match_dummy.proto = X11_XDM; + match_dummy.xa1_firstblock = data; + } else { + return "Unsupported authorisation protocol"; + } + + if ((auth = find234(authtree, &match_dummy, 0)) == NULL) + return "Authorisation not recognised"; + + /* + * If we're using MIT-MAGIC-COOKIE-1, that was all we needed. If + * we're doing XDM-AUTHORIZATION-1, though, we have to check the + * rest of the auth data. + */ + if (auth->proto == X11_XDM) { + unsigned long t; + time_t tim; + int i; + struct XDMSeen *seen, *ret; + + if (dlen != 24) + return "XDM-AUTHORIZATION-1 data was wrong length"; + if (peer_port == -1) + return "cannot do XDM-AUTHORIZATION-1 without remote address data"; + des_decrypt_xdmauth(auth->data+9, data, 24); + if (memcmp(auth->data, data, 8) != 0) + return "XDM-AUTHORIZATION-1 data failed check"; /* cookie wrong */ + if (GET_32BIT_MSB_FIRST(data+8) != peer_ip) + return "XDM-AUTHORIZATION-1 data failed check"; /* IP wrong */ + if ((int)GET_16BIT_MSB_FIRST(data+12) != peer_port) + return "XDM-AUTHORIZATION-1 data failed check"; /* port wrong */ + t = GET_32BIT_MSB_FIRST(data+14); + for (i = 18; i < 24; i++) + if (data[i] != 0) /* zero padding wrong */ + return "XDM-AUTHORIZATION-1 data failed check"; + tim = time(NULL); + if (((unsigned long)t - (unsigned long)tim + + XDM_MAXSKEW) > 2*XDM_MAXSKEW) + return "XDM-AUTHORIZATION-1 time stamp was too far out"; + seen = snew(struct XDMSeen); + seen->time = t; + memcpy(seen->clientid, data+8, 6); + assert(auth->xdmseen != NULL); + ret = add234(auth->xdmseen, seen); + if (ret != seen) { + sfree(seen); + return "XDM-AUTHORIZATION-1 data replayed"; + } + /* While we're here, purge entries too old to be replayed. */ + for (;;) { + seen = index234(auth->xdmseen, 0); + assert(seen != NULL); + if (t - seen->time <= XDM_MAXSKEW) + break; + sfree(delpos234(auth->xdmseen, 0)); + } + } + /* implement other protocols here if ever required */ + + *auth_ret = auth; + return NULL; +} + +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)) + +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, + const char *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 = fopen(authfilename, "rb"); + 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); +} + +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, const char *error_msg, int error_code, + bool calling_back) +{ + struct X11Connection *xconn = container_of( + plug, struct X11Connection, plug); + + if (error_msg) { + /* + * Socket error. If we're still at the connection setup stage, + * construct an X11 error packet passing on the problem. + */ + if (xconn->no_data_sent_to_x_client) { + char *err_message = dupprintf("unable to connect to forwarded " + "X server: %s", error_msg); + x11_send_init_error(xconn, err_message); + sfree(err_message); + } + + /* + * Whether we did that or not, now we slam the connection + * shut. + */ + sshfwd_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); +} + +/* + * When setting up X forwarding, we should send the screen number + * from the specified local display. This function extracts it from + * the display string. + */ +int x11_get_screen_number(char *display) +{ + int n; + + n = host_strcspn(display, ":"); + if (!display[n]) + return 0; + n = strcspn(display, "."); + if (!display[n]) + return 0; + return atoi(display + n + 1); +} + +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); +} + +static bool x11_parse_ip(const char *addr_string, unsigned long *ip) +{ + + /* + * See if we can make sense of this string as an IPv4 address, for + * XDM-AUTHORIZATION-1 purposes. + */ + int i[4]; + if (addr_string && + 4 == sscanf(addr_string, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) { + *ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; + return true; + } else { + return false; + } +} + +/* + * Called to send data down the raw connection. + */ +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; + 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 */ + + err = x11_verify(peer_ip, peer_port, + xconn->authtree, xconn->auth_protocol, + xconn->auth_data, xconn->auth_dlen, &auth_matched); + if (err) { + x11_send_init_error(xconn, err); + return 0; + } + assert(auth_matched); + + /* + * If this auth points to a connection-sharing downstream + * rather than an X display we know how to connect to + * directly, pass it off to the sharing module now. (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)); + 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"); +} + +/* + * Utility functions used by connection sharing to convert textual + * representations of an X11 auth protocol name + hex cookie into our + * usual integer protocol id and binary auth data. + */ +int x11_identify_auth_proto(ptrlen protoname) +{ + int protocol; + + for (protocol = 1; protocol < lenof(x11_authnames); protocol++) + if (ptrlen_eq_string(protoname, x11_authnames[protocol])) + return protocol; + return -1; +} + +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; +} + +/* + * Construct an X11 greeting packet, including making up the right + * authorisation data. + */ +void *x11_make_greeting(int endian, int protomajor, int protominor, + int auth_proto, const void *auth_data, int auth_len, + const char *peer_addr, int peer_port, + int *outlen) +{ + unsigned char *greeting; + unsigned char realauthdata[64]; + const char *authname; + const unsigned char *authdata; + int authnamelen, authnamelen_pad; + int authdatalen, authdatalen_pad; + int greeting_len; + + authname = x11_authnames[auth_proto]; + authnamelen = strlen(authname); + authnamelen_pad = (authnamelen + 3) & ~3; + + if (auth_proto == X11_MIT) { + authdata = auth_data; + authdatalen = auth_len; + } else if (auth_proto == X11_XDM && auth_len == 16) { + time_t t; + unsigned long peer_ip = 0; + + x11_parse_ip(peer_addr, &peer_ip); + + authdata = realauthdata; + authdatalen = 24; + memset(realauthdata, 0, authdatalen); + memcpy(realauthdata, auth_data, 8); + PUT_32BIT_MSB_FIRST(realauthdata+8, peer_ip); + PUT_16BIT_MSB_FIRST(realauthdata+12, peer_port); + t = time(NULL); + PUT_32BIT_MSB_FIRST(realauthdata+14, t); + + des_encrypt_xdmauth((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; +}